KQ-2056 · COUNCIL/TABLE
Council Table
An AI chat answered by a council of specialists seated along an arc above the thread. When the floor changes, a baton travels round the arc to the new seat on springs.snap — position and tilt derived from one motion value, so it passes the seats between — and that seat rises on the same spring while its reply enters the thread on springs.glide; a reply that disagrees with an earlier one draws a thin bracket back up the gutter to the message it disputes on glide. The seats are a toolbar (arrows walk the arc, Enter addresses, Delete dismisses), @ in the composer opens the council as a listbox, and under 34rem the arc flattens into a row of avatars.
Basinworks Team pricing
4 specialists · 3 replies
- 14 min ago
We're moving Basinworks Team from $18 to $24 a seat on 1 September. What do we need to get right?
PlanningRollout and timing13 min ago
Ship it in two steps: $21 on 1 September for everyone, then $24 on 1 December. A smaller first jump keeps cancellations down while support learns which questions come up.
LegalContracts and notice11 min ago
Two steps means two price-change notices. Contracts signed before June promise 60 days' notice of any change, so a second step can't land before 1 February. One change, announced once, is cleaner.
FinanceRevenue and churn8 min ago
At $24, revenue rises 21% even if 8% of teams leave. Holding $21 until December gives up about $310k, so I'd go in one step if Legal's notice holds.
Arrow keys move between seats, Enter addresses one, Delete dismisses it.
Finance has the floor.
floor · finance · basinworks pricing council
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/council-table
Props
- seatShape
- "disc" | "tile" | "arch"
- default "disc"
- Round discs, rounded tiles, or arch-topped seat backs — on the arc, in the thread and in the @ list.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Seat size, the arc's height, type size and spacing.
- accent
- string
- default "var(--accent-bright)"
- Any CSS colour for the baton, the floor ring, the addressee chip, lit brackets and Send.
- specialists
- CouncilSpecialist[]
- default defaultCouncilSpecialists
- The council, left to right along the arc: id, name, remit, initials and a state (idle, thinking or failed).
- turns / defaultTurns
- CouncilTurn[]
- default — / defaultCouncilTurns
- The thread, controlled or initial: author ("you" or a specialist id), text, time, an optional addressee, the turn it disputes with a short note, or summary points.
- onTurnsChange
- (turns: CouncilTurn[]) => void
- default —
- Fires from a send with the whole new thread.
- floor
- string | null
- default the thinking seat, else the newest speaker
- Who holds the baton.
- dismissed / defaultDismissed
- string[]
- default — / []
- Seats sent away from the table, controlled or initial; their turns stay in the thread.
- onDismiss
- (id: string) => void
- default —
- A seat was dismissed (Delete on a seat, or its ×).
- onRestore
- (id: string) => void
- default —
- A dismissed seat was brought back from the header.
- onAddressSpecialist
- (id: string | null) => void
- default —
- A specialist was addressed from its seat or with @, or the address was cleared.
- onSend
- (text: string, addressee: string | null) => void
- default —
- A message was sent, with the addressed specialist if there was one.
- onSummarise
- () => void
- default —
- Summarise was pressed; answer with a turn that has points to show the summary card.
- summarising
- boolean
- default false
- Holds Summarise busy while the host writes the summary.
- onRetry
- (id: string) => void
- default —
- Retry was pressed on a seat that failed to answer.
- onReload
- () => void
- default —
- Try again was pressed after the thread failed to load.
- now
- number
- default defaultCouncilNow
- The moment relative times count from, in ms — never the wall clock during render.
- title
- string
- default "Basinworks Team pricing"
- The conversation's name in the header.
- you
- string
- default "You"
- How the person asking is named in the thread.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws skeleton seats and turns; error offers Try again.
- height
- number
- default 540
- The surface's height in px; the thread scrolls inside it.
- placeholder
- string
- default "Ask, or @ a specialist"
- The composer's placeholder.
- disabled
- boolean
- default false
- Read only: nothing can be addressed, sent or dismissed.
- className
- string
- default —
- Classes for the outer surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| seatShape | "disc" | "tile" | "arch" | "disc" | Round discs, rounded tiles, or arch-topped seat backs — on the arc, in the thread and in the @ list. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Seat size, the arc's height, type size and spacing. |
| accent | string | "var(--accent-bright)" | Any CSS colour for the baton, the floor ring, the addressee chip, lit brackets and Send. |
| specialists | CouncilSpecialist[] | defaultCouncilSpecialists | The council, left to right along the arc: id, name, remit, initials and a state (idle, thinking or failed). |
| turns / defaultTurns | CouncilTurn[] | — / defaultCouncilTurns | The thread, controlled or initial: author ("you" or a specialist id), text, time, an optional addressee, the turn it disputes with a short note, or summary points. |
| onTurnsChange | (turns: CouncilTurn[]) => void | — | Fires from a send with the whole new thread. |
| floor | string | null | the thinking seat, else the newest speaker | Who holds the baton. |
| dismissed / defaultDismissed | string[] | — / [] | Seats sent away from the table, controlled or initial; their turns stay in the thread. |
| onDismiss | (id: string) => void | — | A seat was dismissed (Delete on a seat, or its ×). |
| onRestore | (id: string) => void | — | A dismissed seat was brought back from the header. |
| onAddressSpecialist | (id: string | null) => void | — | A specialist was addressed from its seat or with @, or the address was cleared. |
| onSend | (text: string, addressee: string | null) => void | — | A message was sent, with the addressed specialist if there was one. |
| onSummarise | () => void | — | Summarise was pressed; answer with a turn that has points to show the summary card. |
| summarising | boolean | false | Holds Summarise busy while the host writes the summary. |
| onRetry | (id: string) => void | — | Retry was pressed on a seat that failed to answer. |
| onReload | () => void | — | Try again was pressed after the thread failed to load. |
| now | number | defaultCouncilNow | The moment relative times count from, in ms — never the wall clock during render. |
| title | string | "Basinworks Team pricing" | The conversation's name in the header. |
| you | string | "You" | How the person asking is named in the thread. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws skeleton seats and turns; error offers Try again. |
| height | number | 540 | The surface's height in px; the thread scrolls inside it. |
| placeholder | string | "Ask, or @ a specialist" | The composer's placeholder. |
| disabled | boolean | false | Read only: nothing can be addressed, sent or dismissed. |
| className | string | — | Classes for the outer surface. |