KQ-2090 · CROSSROADS/PLAN
Crossroads Plan
An agent's plan that stops at its open questions: it runs on a rail until a crossroads, where the rail forks into the branches on offer, each previewed a few steps deep with the agent's recommendation and reason, and the run holds at the first undecided crossroads with everything after it folded into one faded line. Choosing drives one value per crossroads on springs.glide — the chosen column widens to the full width while its remaining steps grow downward on a cascade, and the branch not taken withers, its content fading as its column narrows to a 40px stub that keeps a dashed rail, a hollow node, a Reopen button and its title turned on its side — with witherSpeed scaling that spring (stiffness by speed², damping by speed, so its character never changes). Left and Right pick a branch, Enter chooses, Backspace reopens the last crossroads, and under 36rem the branches become tabs with the same keys.
Fieldline · 0 of 2 decided
Switch freight vendors
At a crossroads, Left and Right pick a branch and Enter chooses it. Backspace reopens the last crossroads.
- Export current lanes and rates (done)
- Score three carriers on cost and coverage (done)
Crossroads · run held here
Switch every lane at once, or region by region?
Agent recommends region by region: On-time rate is the contract’s risk; waves cap a bad week to one region.
- Freeze bookings Friday 18:00
- Move all 42 lanes
- Track both carriers Saturday
- Reopen bookings Sunday
+ 1 more step
- Start with North’s 9 lanes
- Watch on-time rate for a week
- Move South and East
- Move West and close out
- Compare cost month on month
+ 2 more steps
Recommended · Slower; two carriers live for three weeks.
- Start with North’s 9 lanes
- Watch on-time rate for a week
- Move South and East
+ 2 more steps
Then tell drivers and dispatch and 2 more steps, and 1 more crossroads — held until you choose.
held at a crossroads · 0 of 2 decided
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/crossroads-plan
Props
- branchStyle
- "rails" | "cards" | "lines"
- default "rails"
- How branches are drawn: metro rails with a drawn fork and nodes, bordered branch cards, or quiet text lists on hairlines.
- witherSpeed
- number
- default 1
- How fast the branch not taken withers and the chosen one grows, 0.5× to 2×; scales the glide spring without changing its damping ratio.
- accent
- string
- default "var(--accent-bright)"
- The chosen path, the picked branch, the recommendation and Approve — any CSS colour.
- plan
- CrossroadsSegment[]
- default defaultCrossroadsPlan
- The plan in order: { kind: "steps", id, steps } runs and { kind: "decision", decision } crossroads, each decision with an id, question, branches (id, title, summary, steps), recommended branch id and rationale.
- choices / defaultChoices
- Record<string, string>
- default — / {}
- Decision id to chosen branch id, controlled or initial.
- onChooseBranch
- (decisionId: string, branchId: string) => void
- default —
- Fires from the key, click or Take this path that chose a branch.
- onReopen
- (decisionId: string) => void
- default —
- Fires from Backspace or a stub's Reopen that reopened a crossroads.
- onApprove
- (choices: Record<string, string>) => void
- default —
- Approve plan was pressed with every crossroads decided.
- preview
- number
- default 3
- Steps of each branch shown before it is chosen; the rest grow in when it is.
- title
- string
- default "Switch freight vendors"
- The plan's name.
- product
- string
- default "Fieldline"
- Who it is for, shown above the title with the count of decided crossroads.
- maxHeight
- number
- default —
- Tallest the plan grows, in px, before it scrolls inside its own box with a bottom fade; choosing scrolls the next crossroads into view inside the plan, never the page.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the plan has arrived: loading draws placeholder rows, error offers Try again. An empty plan says so.
- onRetry
- () => void
- default —
- Try again was pressed after the plan failed to load.
- label
- string
- default the title
- The surface's accessible name.
- disabled
- boolean
- default false
- Read only: nothing can be chosen, reopened or approved.
- className
- string
- default —
- Classes for the outer surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| branchStyle | "rails" | "cards" | "lines" | "rails" | How branches are drawn: metro rails with a drawn fork and nodes, bordered branch cards, or quiet text lists on hairlines. |
| witherSpeed | number | 1 | How fast the branch not taken withers and the chosen one grows, 0.5× to 2×; scales the glide spring without changing its damping ratio. |
| accent | string | "var(--accent-bright)" | The chosen path, the picked branch, the recommendation and Approve — any CSS colour. |
| plan | CrossroadsSegment[] | defaultCrossroadsPlan | The plan in order: { kind: "steps", id, steps } runs and { kind: "decision", decision } crossroads, each decision with an id, question, branches (id, title, summary, steps), recommended branch id and rationale. |
| choices / defaultChoices | Record<string, string> | — / {} | Decision id to chosen branch id, controlled or initial. |
| onChooseBranch | (decisionId: string, branchId: string) => void | — | Fires from the key, click or Take this path that chose a branch. |
| onReopen | (decisionId: string) => void | — | Fires from Backspace or a stub's Reopen that reopened a crossroads. |
| onApprove | (choices: Record<string, string>) => void | — | Approve plan was pressed with every crossroads decided. |
| preview | number | 3 | Steps of each branch shown before it is chosen; the rest grow in when it is. |
| title | string | "Switch freight vendors" | The plan's name. |
| product | string | "Fieldline" | Who it is for, shown above the title with the count of decided crossroads. |
| maxHeight | number | — | Tallest the plan grows, in px, before it scrolls inside its own box with a bottom fade; choosing scrolls the next crossroads into view inside the plan, never the page. |
| status | "ready" | "loading" | "error" | "ready" | Whether the plan has arrived: loading draws placeholder rows, error offers Try again. An empty plan says so. |
| onRetry | () => void | — | Try again was pressed after the plan failed to load. |
| label | string | the title | The surface's accessible name. |
| disabled | boolean | false | Read only: nothing can be chosen, reopened or approved. |
| className | string | — | Classes for the outer surface. |