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.

KQ-2086 · TIME/FIT

Fernworks · 7 steps by value

Launch checklist

Finish15:20

Fits 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.

Freeze the release branch
Run checkout smoke tests
Rehearse the rollback
Brief the support team
Load test at twice peak
Draft the status page notice
Polish the changelog art

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.