Invoice shows Team seats twice
Hi, our October invoice lists the Team plan twice: 12 seats on the 1st and 12 again on the 3rd. We only have one workspace. Can you take a look before it charges on Friday? Dana
KQ-1779 · CHANNEL/MERGE
A support conversation where one customer's email, texts and web chat sit in a single thread, each message carrying a thin stripe in its channel's hue — an inner edge, a rail through the gutter that changes colour where the conversation changed channel, or a top band. Choosing the reply channel slides its badge across the radio group on springs.snap while the composer reshapes: its measured height glides on springs.glide as the email subject unfolds or the text counter's segments roll in, the address cross-fades and the top stripe wipes into the new hue; Ctrl+Enter sends and the reply rises on glide with its stripe drawn on springs.flick. Arrows move through the channels and skip the ones the customer never used, the assignee menu answers to arrows, Enter and Escape, and under 44rem the customer sidebar hides behind a tab.
Invoice shows Team seats twice
BW-4821 · Dana Whitlock · Email, Text and Web chat
To basinworks.app/billing
reply via web chat · 7 messages · 3 channels · assigned to Mara
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/channel-merge
| Prop | Type | Default | Description |
|---|---|---|---|
| messages | ChannelMessage[] | defaultChannelMessages | The conversation across channels, oldest first: { id, channel: "email" | "sms" | "chat" | "social", from: "customer" | "agent", author, at, body, subject?, quoted?, status? }. |
| customer | ChannelCustomer | defaultChannelCustomer | Who the customer is and how to reach them: name, company, plan, email, phone, chat, social, online, location, utcOffset, since, tags, tickets. |
| agents | ChannelAgent[] | defaultChannelAgents | Who the ticket can be assigned to: { id, name, team? }. |
| assignee / defaultAssignee | string | null | — / "a1" | The assigned agent's id, controlled or initial; null is unassigned. |
| onAssign | (agentId | null) => void | — | Fires from the menu choice that changed the assignee. |
| channel / defaultChannel | "email" | "sms" | "chat" | "social" | — / the customer's latest channel | The reply channel, controlled or initial. |
| onChannelChange | (channel) => void | — | Fires from the radio (click or arrow) that changed the reply channel. |
| onSend | (reply) => void | — | A reply was sent with Ctrl+Enter or Send: { channel, to, body, subject? }. Append it to messages to show it. |
| subject | string | "Invoice shows Team seats twice" | The ticket's subject, in the header and the email reply's "Re:" line. |
| ticket | string | "BW-4821" | The ticket's reference, under the subject. |
| now | number | defaultChannelNow | The current moment in ms since the epoch, for the customer's local time. Never read from the clock during render. |
| channels | ChannelKind[] | ["email", "sms", "chat", "social"] | Which reply channels to offer, in order. Ones the customer never used are greyed and skipped. |
| channelColors | Partial<Record<ChannelKind, string>> | — | Override a channel's stripe colour with any CSS colour; defaults are pigment hues at a fixed lightness. |
| stripeStyle | "edge" | "rail" | "band" | "edge" | How each message shows its channel: a 3px inner edge, a continuous rail with a glyph node per message, or a 2px top band. |
| density | "compact" | "regular" | "roomy" | "regular" | Type size, message padding and the gaps between messages. |
| accent | string | "var(--accent-bright)" | Send, the agent's messages, the assignee and the tab indicator — any CSS colour; fills take its hue at a fixed lightness. |
| smsLimit | number | 160 | Characters in one text before it splits; split parts hold 7 fewer, as concatenated texts do. |
| status / onRetry | "ready" | "loading" | "error" / () => void | "ready" | Loading shows skeleton messages; error shows a message and Try again, which calls onRetry. |
| height | number | 540 | The surface's height in px; the thread and the sidebar scroll inside it. |
| disabled | boolean | false | Read only: no composer and no assigning. |
| className | string | — | Classes for the outer section. |