KQ-1779 · CHANNEL/MERGE

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.

KQ-1779 · CHANNEL/MERGE

Invoice shows Team seats twice

BW-4821 · Dana Whitlock · Email, Text and Web chat

  1. Dana Whitlock by Email, Mon 12 Oct 09:14

    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

  2. Mara Quinn by Email, Mon 12 Oct 10:02

    Re: Invoice shows Team seats twice

    Thanks Dana. I can see both lines: the second came from a trial workspace that converted on the 3rd. I've asked billing to void it, and a corrected invoice follows within a day.

    Read

  3. Dana Whitlock by Text, Tue 13 Oct 08:40

    Corrected invoice still shows 24 seats. Charge is Friday. Can someone look today?

  4. Mara Quinn by Text, Tue 13 Oct 08:52

    Hi Dana, Mara from Basinworks. On it now, I'll come back within the hour.

    Delivered

  5. Dana Whitlock by Chat, Thu 15 Oct 16:02

    I'm in billing settings and it lists two workspaces: Harbor & Pine and “H&P trial”. Is the trial the one being charged?

  6. Mara Quinn by Chat, Thu 15 Oct 16:05

    Yes, that's the one. You can delete it from Settings, or I can close it for you.

    Read

  7. Dana Whitlock by Chat, Thu 15 Oct 16:07

    Please close it for me, I don't want to delete the wrong one.

To basinworks.app/billing

Dana sees this live

reply via web chat · 7 messages · 3 channels · assigned to Mara

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/channel-merge

Props

messages
ChannelMessage[]
default defaultChannelMessages
The conversation across channels, oldest first: { id, channel: "email" | "sms" | "chat" | "social", from: "customer" | "agent", author, at, body, subject?, quoted?, status? }.
customer
ChannelCustomer
default 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[]
default defaultChannelAgents
Who the ticket can be assigned to: { id, name, team? }.
assignee / defaultAssignee
string | null
default — / "a1"
The assigned agent's id, controlled or initial; null is unassigned.
onAssign
(agentId | null) => void
default —
Fires from the menu choice that changed the assignee.
channel / defaultChannel
"email" | "sms" | "chat" | "social"
default — / the customer's latest channel
The reply channel, controlled or initial.
onChannelChange
(channel) => void
default —
Fires from the radio (click or arrow) that changed the reply channel.
onSend
(reply) => void
default —
A reply was sent with Ctrl+Enter or Send: { channel, to, body, subject? }. Append it to messages to show it.
subject
string
default "Invoice shows Team seats twice"
The ticket's subject, in the header and the email reply's "Re:" line.
ticket
string
default "BW-4821"
The ticket's reference, under the subject.
now
number
default defaultChannelNow
The current moment in ms since the epoch, for the customer's local time. Never read from the clock during render.
channels
ChannelKind[]
default ["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>>
default —
Override a channel's stripe colour with any CSS colour; defaults are pigment hues at a fixed lightness.
stripeStyle
"edge" | "rail" | "band"
default "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"
default "regular"
Type size, message padding and the gaps between messages.
accent
string
default "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
default 160
Characters in one text before it splits; split parts hold 7 fewer, as concatenated texts do.
status / onRetry
"ready" | "loading" | "error" / () => void
default "ready"
Loading shows skeleton messages; error shows a message and Try again, which calls onRetry.
height
number
default 540
The surface's height in px; the thread and the sidebar scroll inside it.
disabled
boolean
default false
Read only: no composer and no assigning.
className
string
default —
Classes for the outer section.