KQ-2096 · UNIT/COST
Unit Cost
An AI usage panel that divides the period's spend by the outcomes it produced and draws it as a real fraction: when the period or outcome changes, the numerator and denominator roll independently on springs.snap, each changed digit's strip travelling to its new face with the denominator a beat behind, while a number that gains a place grows a column on springs.glide and the quotient fades in once both have landed. Beside it a trend of cost per outcome over twelve weeks, its target band shaded, works as the period picker: the cursor follows the finger 1:1, picks each week it passes and settles on snap with the release velocity, a change of outcome morphs the whole line on glide, and the target line's handle drags to a new target that commits to the cent on snap. Outcomes are a radiogroup with arrow keys, the trend and the target are sliders, and the trend also reads as a table.
tickets resolved · Oct 12 · $0.37 each · in band
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/unit-cost
Props
- outcomes
- UnitCostOutcome[]
- default defaultUnitCostOutcomes
- What spend is divided by: { id, label, short?, unit, target, tolerance? }. The target is the cost per outcome the team aims for; tolerance is half the band's width as a share of it.
- usages
- UnitCostUsage[]
- default defaultUnitCostUsages
- The usage lines spend is reported under: { id, name }. Unmapped lines are named in the unmapped note.
- periods
- UnitCostPeriod[]
- default defaultUnitCostPeriods
- The periods, oldest first: { id, label, spend (dollars per usage id), outcomes (count per outcome id), partial? }. A partial last period is drawn dashed.
- mapping
- Record<string, string | null>
- default defaultUnitCostMapping
- Which outcome each usage line produces. Usage mapped to nothing is summed as unmapped spend; an outcome with no usage mapped shows a dash and says so.
- outcome / defaultOutcome
- string
- default the first outcome
- The outcome spend is divided by, controlled or initial.
- onOutcomeChange
- (id: string, outcome: UnitCostOutcome) => void
- default —
- Fires from the radio or arrow key that changed the outcome.
- period / defaultPeriod
- string
- default the last period
- The period the fraction reads, controlled or initial.
- onPeriodChange
- (id: string, period: UnitCostPeriod) => void
- default —
- Fires as the trend's cursor, a table row or an arrow key picks a period.
- targets
- Record<string, number>
- default —
- Controlled targets per outcome id, overriding each outcome's own.
- onSetTarget
- (outcomeId: string, value: number) => void
- default —
- Fires when the target handle is dragged or stepped to a new value.
- onMapUsage
- (usageIds: string[]) => void
- default —
- “Map usage” was pressed on the unmapped-spend note, with the usage ids that feed no outcome. Omit it and the note is plain text.
- minOutcomes
- number
- default 40
- Below this many outcomes a period's cost is called too few to trust and drawn as a hollow dot.
- defaultView
- "chart" | "table"
- default "chart"
- The trend as a chart or a table at first; the Chart and Table buttons switch it.
- status
- "ready" | "loading" | "error"
- default "ready"
- Usage arrived, is on its way (a still skeleton), or failed (a message and Try again).
- onRetry
- () => void
- default —
- “Try again” was pressed after usage failed to load.
- title
- string
- default "Gaugeworks · support assistant"
- The panel's heading.
- subtitle
- string
- default "AI spend per finished outcome"
- Under the heading.
- formatMoney
- (amount: number, kind: "total" | "each") => string
- default hand formatter
- How money prints: whole totals (“$1,284”) and per-outcome costs (“$0.37”, three places under twenty cents).
- fractionStyle
- "stacked" | "slashed" | "ratio"
- default "stacked"
- The fraction's form: one figure over a rule over the other, a slashed nut fraction, or one line with a division sign.
- bandStyle
- "shade" | "hatch" | "rules"
- default "shade"
- How the target band is drawn: a soft wash, diagonal hatching with edges, or only its two dashed edges.
- accent
- "cobalt" | "plum" | "mint" | "ink" | string
- default "cobalt"
- The line, band and selection colour; any CSS colour works.
- disabled
- boolean
- default false
- Read only: nothing can be picked, dragged or set.
- className
- string
- default —
- Classes for the panel, e.g. a max height; it scrolls inside its own box.
| Prop | Type | Default | Description |
|---|---|---|---|
| outcomes | UnitCostOutcome[] | defaultUnitCostOutcomes | What spend is divided by: { id, label, short?, unit, target, tolerance? }. The target is the cost per outcome the team aims for; tolerance is half the band's width as a share of it. |
| usages | UnitCostUsage[] | defaultUnitCostUsages | The usage lines spend is reported under: { id, name }. Unmapped lines are named in the unmapped note. |
| periods | UnitCostPeriod[] | defaultUnitCostPeriods | The periods, oldest first: { id, label, spend (dollars per usage id), outcomes (count per outcome id), partial? }. A partial last period is drawn dashed. |
| mapping | Record<string, string | null> | defaultUnitCostMapping | Which outcome each usage line produces. Usage mapped to nothing is summed as unmapped spend; an outcome with no usage mapped shows a dash and says so. |
| outcome / defaultOutcome | string | the first outcome | The outcome spend is divided by, controlled or initial. |
| onOutcomeChange | (id: string, outcome: UnitCostOutcome) => void | — | Fires from the radio or arrow key that changed the outcome. |
| period / defaultPeriod | string | the last period | The period the fraction reads, controlled or initial. |
| onPeriodChange | (id: string, period: UnitCostPeriod) => void | — | Fires as the trend's cursor, a table row or an arrow key picks a period. |
| targets | Record<string, number> | — | Controlled targets per outcome id, overriding each outcome's own. |
| onSetTarget | (outcomeId: string, value: number) => void | — | Fires when the target handle is dragged or stepped to a new value. |
| onMapUsage | (usageIds: string[]) => void | — | “Map usage” was pressed on the unmapped-spend note, with the usage ids that feed no outcome. Omit it and the note is plain text. |
| minOutcomes | number | 40 | Below this many outcomes a period's cost is called too few to trust and drawn as a hollow dot. |
| defaultView | "chart" | "table" | "chart" | The trend as a chart or a table at first; the Chart and Table buttons switch it. |
| status | "ready" | "loading" | "error" | "ready" | Usage arrived, is on its way (a still skeleton), or failed (a message and Try again). |
| onRetry | () => void | — | “Try again” was pressed after usage failed to load. |
| title | string | "Gaugeworks · support assistant" | The panel's heading. |
| subtitle | string | "AI spend per finished outcome" | Under the heading. |
| formatMoney | (amount: number, kind: "total" | "each") => string | hand formatter | How money prints: whole totals (“$1,284”) and per-outcome costs (“$0.37”, three places under twenty cents). |
| fractionStyle | "stacked" | "slashed" | "ratio" | "stacked" | The fraction's form: one figure over a rule over the other, a slashed nut fraction, or one line with a division sign. |
| bandStyle | "shade" | "hatch" | "rules" | "shade" | How the target band is drawn: a soft wash, diagonal hatching with edges, or only its two dashed edges. |
| accent | "cobalt" | "plum" | "mint" | "ink" | string | "cobalt" | The line, band and selection colour; any CSS colour works. |
| disabled | boolean | false | Read only: nothing can be picked, dragged or set. |
| className | string | — | Classes for the panel, e.g. a max height; it scrolls inside its own box. |