KQ-2086 · TIME/FIT
Time Fit
An agent's plan packed against a time budget: steps are ranked by value and packed greedily, pinned steps first, so what fits sits above a budget line and what does not sits below it, greyed. Dragging the line keeps it 1:1 under the finger while the budget moves in snapped minutes; every change re-packs, the steps that no longer fit sliding below the line on springs.glide while the ones that now fit rise above it on the same spring, the finish time rolling digit by digit on springs.snap, and the line settling into its slot on snap with the release velocity. The rows are a multi-select listbox where P or Space pins, the line is a vertical slider on the arrow, Page, Home and End keys, and an over-budget state turns the line danger when the pins alone need more time than there is.
Fernworks · 7 steps by value
Launch checklist
Finish15:20Fits 5 of 7 · 2h 15m of 2h 30m
Up and Down move between steps; P or Space pins a step above the line. On the budget line, Down and Right add time, Up and Left take it away, Page keys move an hour.
5 of 7 fit · 2h 15m of 2h 30m · finish 15:20
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/time-fit
Props
- lineStyle
- "rule" | "dashed" | "band"
- default "rule"
- The budget line's character: a rule with a grab pill, a dashed cut line with the pill centred, or a band that carries the budget and spare time inside it.
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Row height and type size: 34, 40 or 48px one-line rows, 42, 46 or 52px two-line rows on narrow containers.
- accent
- string
- default "var(--accent-bright)"
- The line, pins, value meters and Approve — any CSS colour.
- steps
- TimeFitStep[]
- default defaultTimeFitSteps
- The plan's steps in any order: id, title, minutes (the estimate), value (1–10) and an optional owner. The component ranks them by value, then by the shorter estimate.
- budget / defaultBudget
- number
- default — / 150
- The time budget in minutes, controlled or initial.
- onBudgetChange
- (minutes: number) => void
- default —
- Fires from the drag or key that moved the line, once per budgetStep crossed.
- pinned / defaultPinned
- string[]
- default — / []
- Pinned step ids, controlled or initial. Pinned steps always stay above the line.
- onPinStep
- (id: string, pinned: boolean) => void
- default —
- Fires from the click or key that pinned or unpinned a step.
- onApprove
- (plan: TimeFitPlan) => void
- default —
- Approve was pressed: the step ids above the line, the budget, the minutes they take, the finish time in minutes after midnight, and whether the pins are over budget.
- minBudget
- number
- default 30
- The shortest budget the line allows, in minutes.
- maxBudget
- number
- default 360
- The longest budget the line allows, in minutes.
- budgetStep
- number
- default 5
- Minutes per arrow key and the grain a drag snaps to.
- now
- number
- default 785
- When the plan starts, in minutes after midnight (13:05); the finish time counts from it, so the server and the browser agree.
- title
- string
- default "Launch checklist"
- The plan's name.
- product
- string
- default "Fernworks"
- Who the plan is for, shown above the title.
- label
- string
- default "<product> <title>"
- The surface's accessible name.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the plan has arrived: loading draws placeholder rows, error offers Try again. An empty steps list says so.
- onRetry
- () => void
- default —
- Try again was pressed after the plan failed to load.
- disabled
- boolean
- default false
- Read only: the line, pins and Approve do nothing.
- className
- string
- default —
- Classes for the outer surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| lineStyle | "rule" | "dashed" | "band" | "rule" | The budget line's character: a rule with a grab pill, a dashed cut line with the pill centred, or a band that carries the budget and spare time inside it. |
| density | "compact" | "regular" | "roomy" | "regular" | Row height and type size: 34, 40 or 48px one-line rows, 42, 46 or 52px two-line rows on narrow containers. |
| accent | string | "var(--accent-bright)" | The line, pins, value meters and Approve — any CSS colour. |
| steps | TimeFitStep[] | defaultTimeFitSteps | The plan's steps in any order: id, title, minutes (the estimate), value (1–10) and an optional owner. The component ranks them by value, then by the shorter estimate. |
| budget / defaultBudget | number | — / 150 | The time budget in minutes, controlled or initial. |
| onBudgetChange | (minutes: number) => void | — | Fires from the drag or key that moved the line, once per budgetStep crossed. |
| pinned / defaultPinned | string[] | — / [] | Pinned step ids, controlled or initial. Pinned steps always stay above the line. |
| onPinStep | (id: string, pinned: boolean) => void | — | Fires from the click or key that pinned or unpinned a step. |
| onApprove | (plan: TimeFitPlan) => void | — | Approve was pressed: the step ids above the line, the budget, the minutes they take, the finish time in minutes after midnight, and whether the pins are over budget. |
| minBudget | number | 30 | The shortest budget the line allows, in minutes. |
| maxBudget | number | 360 | The longest budget the line allows, in minutes. |
| budgetStep | number | 5 | Minutes per arrow key and the grain a drag snaps to. |
| now | number | 785 | When the plan starts, in minutes after midnight (13:05); the finish time counts from it, so the server and the browser agree. |
| title | string | "Launch checklist" | The plan's name. |
| product | string | "Fernworks" | Who the plan is for, shown above the title. |
| label | string | "<product> <title>" | The surface's accessible name. |
| status | "ready" | "loading" | "error" | "ready" | Whether the plan has arrived: loading draws placeholder rows, error offers Try again. An empty steps list says so. |
| onRetry | () => void | — | Try again was pressed after the plan failed to load. |
| disabled | boolean | false | Read only: the line, pins and Approve do nothing. |
| className | string | — | Classes for the outer surface. |