KQ-1763 · MACRO/REPLY
Macro Reply
An agent's ticket view — the conversation, a customer sidebar with plan, fields and earlier tickets, and a reply box — where # opens the macros and picking one types its reply at a steady pace while every field it changes switches at its own moment: a wash sweeps the field, the old value rolls out and the new one rises on springs.snap, a set-by-macro dot pops on springs.flick, and a chip for the change lands in the reply's footer on snap, so nothing changes unseen even when the sidebar is a tab. Sent replies rise into the conversation on springs.glide. The reply is a combobox over the macro listbox (arrows, Enter or Tab to apply, Escape to close), any key finishes a typing macro, and Ctrl+Enter sends.
Charged twice after upgrading to Team
#3195 · Email · Mara Lindqvist
Mara Lindqvist52 min ago
Hi, we upgraded Fernworks from Starter to Team on Monday and the card was charged twice: €240 on the 12th and €240 again on the 13th. Only one invoice shows on the billing page. Can you refund the second charge?Ines40 min ago
Thanks Mara. I'm checking this with billing now and will come back to you here.Mara Lindqvist12 min ago
Thanks. For reference, the second charge shows as BSN*BASINWORKS 4471 on our statement.
open · type # for macros · ticket #3195 · mara lindqvist
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/macro-reply
Props
- sidebarSide
- "left" | "right"
- default "right"
- Which side the customer sidebar sits on; at narrow widths, which side its tab sits on.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Type size, paddings and gaps.
- accent
- "cobalt" | "jade" | "amber" | "mono"
- default "cobalt"
- The macro wash, typing hairline, change chips and Send.
- ticket
- MacroTicket
- default defaultMacroTicket
- Subject, number, channel, status, priority, tags and messages. Field changes from the host on the same ticket id flash like a macro's.
- customer
- MacroCustomer
- default defaultMacroCustomer
- The customer shown in the sidebar: name, email, plan, seats, since and earlier tickets.
- macros
- MacroDef[]
- default defaultMacros
- Each macro's name, reply text ({first}, {agent} and {number} are filled) and the fields it sets.
- agent
- string
- default "Theo"
- The agent replying; fills {agent} and signs sent messages.
- now
- number
- default defaultMacroNow
- The moment relative times count from, ms. Never read from the clock.
- status
- "ready" | "loading"
- default "ready"
- Loading draws skeletons in the same boxes.
- onApplyMacro
- (macro: MacroDef) => void
- default —
- A macro was applied, before it starts typing.
- onSend
- (reply: { text, fields, macroId }) => void | Promise
- default —
- The reply was sent with its fields. A promise holds Send; a rejection keeps the draft and offers Retry.
- onStatusChange
- (status, source: "macro" | "field" | "reopen") => void
- default —
- The ticket's status changed, and what changed it.
- onFieldsChange
- (fields: { status, priority, tags }) => void
- default —
- Any field changed, with all three.
- typeSpeed
- number
- default 90
- How fast a macro types, in characters a second.
- flash
- number
- default 640
- How long a changed field flashes, ms.
- height
- number
- default 540
- Height of the surface, px; the conversation scrolls inside it.
- placeholder
- string
- default "Reply to {first}… Type # for macros"
- The reply box's placeholder.
- disabled
- boolean
- default false
- Read only: nothing can be typed, applied or sent.
- className
- string
- default —
- Classes for the outer surface.
- ref
- Ref<MacroReplyHandle>
- default —
- applyMacro(id) applies a macro as if picked; focusReply() moves focus to the reply.
| Prop | Type | Default | Description |
|---|---|---|---|
| sidebarSide | "left" | "right" | "right" | Which side the customer sidebar sits on; at narrow widths, which side its tab sits on. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Type size, paddings and gaps. |
| accent | "cobalt" | "jade" | "amber" | "mono" | "cobalt" | The macro wash, typing hairline, change chips and Send. |
| ticket | MacroTicket | defaultMacroTicket | Subject, number, channel, status, priority, tags and messages. Field changes from the host on the same ticket id flash like a macro's. |
| customer | MacroCustomer | defaultMacroCustomer | The customer shown in the sidebar: name, email, plan, seats, since and earlier tickets. |
| macros | MacroDef[] | defaultMacros | Each macro's name, reply text ({first}, {agent} and {number} are filled) and the fields it sets. |
| agent | string | "Theo" | The agent replying; fills {agent} and signs sent messages. |
| now | number | defaultMacroNow | The moment relative times count from, ms. Never read from the clock. |
| status | "ready" | "loading" | "ready" | Loading draws skeletons in the same boxes. |
| onApplyMacro | (macro: MacroDef) => void | — | A macro was applied, before it starts typing. |
| onSend | (reply: { text, fields, macroId }) => void | Promise | — | The reply was sent with its fields. A promise holds Send; a rejection keeps the draft and offers Retry. |
| onStatusChange | (status, source: "macro" | "field" | "reopen") => void | — | The ticket's status changed, and what changed it. |
| onFieldsChange | (fields: { status, priority, tags }) => void | — | Any field changed, with all three. |
| typeSpeed | number | 90 | How fast a macro types, in characters a second. |
| flash | number | 640 | How long a changed field flashes, ms. |
| height | number | 540 | Height of the surface, px; the conversation scrolls inside it. |
| placeholder | string | "Reply to {first}… Type # for macros" | The reply box's placeholder. |
| disabled | boolean | false | Read only: nothing can be typed, applied or sent. |
| className | string | — | Classes for the outer surface. |
| ref | Ref<MacroReplyHandle> | — | applyMacro(id) applies a macro as if picked; focusReply() moves focus to the reply. |