@flamingo-stack/openframe-frontend-core 0.0.535 → 0.0.536-1768.4650.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/chat-protocol/events.d.ts +15 -1
  2. package/dist/chat-protocol/events.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +15 -0
  4. package/dist/chat-protocol/index.cjs.map +1 -1
  5. package/dist/chat-protocol/index.js +15 -0
  6. package/dist/chat-protocol/index.js.map +1 -1
  7. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
  8. package/dist/{chunk-LIX5OUHH.js → chunk-2TQQIXQ3.js} +2 -2
  9. package/dist/{chunk-ZKKEKG73.cjs → chunk-3FOJEMRF.cjs} +3 -3
  10. package/dist/{chunk-ZKKEKG73.cjs.map → chunk-3FOJEMRF.cjs.map} +1 -1
  11. package/dist/{chunk-Y72MXG5N.js → chunk-47VVYAJW.js} +2 -2
  12. package/dist/{chunk-U2Q7GEX2.cjs → chunk-4NMIWFTP.cjs} +26 -26
  13. package/dist/{chunk-U2Q7GEX2.cjs.map → chunk-4NMIWFTP.cjs.map} +1 -1
  14. package/dist/{chunk-QUVIT4JP.js → chunk-5QENIFGO.js} +5 -5
  15. package/dist/{chunk-ZR5CJTNR.cjs → chunk-7ZE3SPVS.cjs} +4 -4
  16. package/dist/{chunk-ZR5CJTNR.cjs.map → chunk-7ZE3SPVS.cjs.map} +1 -1
  17. package/dist/{chunk-7MHYRBBQ.cjs → chunk-B2N2NDZX.cjs} +5 -5
  18. package/dist/{chunk-7MHYRBBQ.cjs.map → chunk-B2N2NDZX.cjs.map} +1 -1
  19. package/dist/{chunk-5OLF6WXF.cjs → chunk-CP4VGMGK.cjs} +37 -37
  20. package/dist/{chunk-5OLF6WXF.cjs.map → chunk-CP4VGMGK.cjs.map} +1 -1
  21. package/dist/{chunk-E3YB55RR.cjs → chunk-DSGASYE6.cjs} +193 -37
  22. package/dist/chunk-DSGASYE6.cjs.map +1 -0
  23. package/dist/{chunk-P65EZEBW.cjs → chunk-ECLKNH72.cjs} +9 -9
  24. package/dist/{chunk-P65EZEBW.cjs.map → chunk-ECLKNH72.cjs.map} +1 -1
  25. package/dist/{chunk-PSBL3S2B.cjs → chunk-GMFYYVNC.cjs} +7 -7
  26. package/dist/{chunk-PSBL3S2B.cjs.map → chunk-GMFYYVNC.cjs.map} +1 -1
  27. package/dist/{chunk-LIQJAC6Z.js → chunk-HSJ7LXDC.js} +2 -2
  28. package/dist/{chunk-KLFGP6PR.js → chunk-I5DOYYPD.js} +2 -2
  29. package/dist/{chunk-2P4J2YLI.cjs → chunk-KEKCA7QQ.cjs} +87 -87
  30. package/dist/{chunk-2P4J2YLI.cjs.map → chunk-KEKCA7QQ.cjs.map} +1 -1
  31. package/dist/{chunk-4DTUUM5V.js → chunk-KQ4NDWIM.js} +4224 -4068
  32. package/dist/chunk-KQ4NDWIM.js.map +1 -0
  33. package/dist/{chunk-PLGITXT7.js → chunk-MDBLST2A.js} +2 -2
  34. package/dist/{chunk-N3AEJTCK.js → chunk-N5XVBG2S.js} +4 -4
  35. package/dist/{chunk-K6566PEB.js → chunk-O4UN26ET.js} +2 -2
  36. package/dist/{chunk-JVO5CYMK.js → chunk-OBGFWHDE.js} +6 -6
  37. package/dist/{chunk-GV4FDDFV.cjs → chunk-OLSAFBZX.cjs} +11 -11
  38. package/dist/{chunk-GV4FDDFV.cjs.map → chunk-OLSAFBZX.cjs.map} +1 -1
  39. package/dist/{chunk-NB6BP6JB.js → chunk-PEK3X323.js} +2 -2
  40. package/dist/{chunk-VPDTDKO5.js → chunk-PUXDD7XV.js} +2 -2
  41. package/dist/{chunk-TS2EHMEX.cjs → chunk-S5URTCRA.cjs} +26 -26
  42. package/dist/{chunk-TS2EHMEX.cjs.map → chunk-S5URTCRA.cjs.map} +1 -1
  43. package/dist/{chunk-4HQFV4PK.cjs → chunk-TLJDUNO2.cjs} +14 -14
  44. package/dist/{chunk-4HQFV4PK.cjs.map → chunk-TLJDUNO2.cjs.map} +1 -1
  45. package/dist/{chunk-EHPQKXXW.js → chunk-VKAH66LI.js} +2 -2
  46. package/dist/{chunk-MZVFZMQ6.js → chunk-WJ75GDIO.js} +4 -4
  47. package/dist/{chunk-RO67OOKJ.cjs → chunk-Z5TGYKIK.cjs} +31 -31
  48. package/dist/{chunk-RO67OOKJ.cjs.map → chunk-Z5TGYKIK.cjs.map} +1 -1
  49. package/dist/{chunk-Y55XQFJK.cjs → chunk-ZIBIQKMA.cjs} +61 -61
  50. package/dist/{chunk-Y55XQFJK.cjs.map → chunk-ZIBIQKMA.cjs.map} +1 -1
  51. package/dist/components/case-studies/index.cjs +8 -8
  52. package/dist/components/case-studies/index.js +2 -2
  53. package/dist/components/chat/ai-assistant-info.d.ts +31 -0
  54. package/dist/components/chat/ai-assistant-info.d.ts.map +1 -0
  55. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  56. package/dist/components/chat/index.cjs +8 -2
  57. package/dist/components/chat/index.cjs.map +1 -1
  58. package/dist/components/chat/index.d.ts +2 -0
  59. package/dist/components/chat/index.d.ts.map +1 -1
  60. package/dist/components/chat/index.js +7 -1
  61. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
  62. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
  63. package/dist/components/chat/ticket-escalated-message.d.ts +2 -8
  64. package/dist/components/chat/ticket-escalated-message.d.ts.map +1 -1
  65. package/dist/components/chat/ticket-event-message.d.ts +24 -0
  66. package/dist/components/chat/ticket-event-message.d.ts.map +1 -0
  67. package/dist/components/chat/types/message.types.d.ts +51 -2
  68. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  69. package/dist/components/chat/utils/message-segment-accumulator.d.ts +13 -1
  70. package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
  71. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  72. package/dist/components/contact/index.cjs +3 -3
  73. package/dist/components/contact/index.js +2 -2
  74. package/dist/components/docs/index.cjs +5 -5
  75. package/dist/components/docs/index.js +4 -4
  76. package/dist/components/embeds/index.cjs +3 -3
  77. package/dist/components/embeds/index.js +2 -2
  78. package/dist/components/faq/index.cjs +3 -3
  79. package/dist/components/faq/index.js +2 -2
  80. package/dist/components/features/index.cjs +2 -2
  81. package/dist/components/features/index.js +1 -1
  82. package/dist/components/help-center-pages/index.cjs +22 -22
  83. package/dist/components/help-center-pages/index.js +13 -13
  84. package/dist/components/index.cjs +138 -132
  85. package/dist/components/index.cjs.map +1 -1
  86. package/dist/components/index.js +17 -11
  87. package/dist/components/index.js.map +1 -1
  88. package/dist/components/meeting-scheduler/index.cjs +34 -34
  89. package/dist/components/meeting-scheduler/index.js +3 -3
  90. package/dist/components/navigation/index.cjs +2 -2
  91. package/dist/components/navigation/index.js +1 -1
  92. package/dist/components/onboarding-guides/index.cjs +5 -5
  93. package/dist/components/onboarding-guides/index.js +4 -4
  94. package/dist/components/related-content/index.cjs +3 -3
  95. package/dist/components/related-content/index.js +2 -2
  96. package/dist/components/tickets/index.cjs +6 -6
  97. package/dist/components/tickets/index.js +5 -5
  98. package/dist/components/ui/index.cjs +8 -2
  99. package/dist/components/ui/index.cjs.map +1 -1
  100. package/dist/components/ui/index.js +7 -1
  101. package/dist/index.cjs +8 -2
  102. package/dist/index.cjs.map +1 -1
  103. package/dist/index.js +7 -1
  104. package/dist/utils/index.cjs.map +1 -1
  105. package/dist/utils/index.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +44 -0
  108. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +32 -0
  109. package/src/chat-protocol/events.ts +16 -0
  110. package/src/chat-protocol/nats-decoder.ts +27 -0
  111. package/src/components/chat/__tests__/ticket-event.test.ts +194 -0
  112. package/src/components/chat/ai-assistant-info.tsx +71 -0
  113. package/src/components/chat/chat-message-enhanced.tsx +3 -0
  114. package/src/components/chat/index.ts +2 -0
  115. package/src/components/chat/stream/chat-stream-reducer.ts +19 -0
  116. package/src/components/chat/stream/message-mutations.ts +5 -0
  117. package/src/components/chat/ticket-escalated-message.tsx +10 -32
  118. package/src/components/chat/ticket-event-message.tsx +79 -0
  119. package/src/components/chat/types/message.types.ts +60 -1
  120. package/src/components/chat/utils/message-segment-accumulator.ts +41 -0
  121. package/src/components/chat/utils/process-historical-messages.ts +31 -0
  122. package/src/stories/TicketEventMessage.stories.tsx +100 -0
  123. package/dist/chunk-4DTUUM5V.js.map +0 -1
  124. package/dist/chunk-E3YB55RR.cjs.map +0 -1
  125. /package/dist/{chunk-LIX5OUHH.js.map → chunk-2TQQIXQ3.js.map} +0 -0
  126. /package/dist/{chunk-Y72MXG5N.js.map → chunk-47VVYAJW.js.map} +0 -0
  127. /package/dist/{chunk-QUVIT4JP.js.map → chunk-5QENIFGO.js.map} +0 -0
  128. /package/dist/{chunk-LIQJAC6Z.js.map → chunk-HSJ7LXDC.js.map} +0 -0
  129. /package/dist/{chunk-KLFGP6PR.js.map → chunk-I5DOYYPD.js.map} +0 -0
  130. /package/dist/{chunk-PLGITXT7.js.map → chunk-MDBLST2A.js.map} +0 -0
  131. /package/dist/{chunk-N3AEJTCK.js.map → chunk-N5XVBG2S.js.map} +0 -0
  132. /package/dist/{chunk-K6566PEB.js.map → chunk-O4UN26ET.js.map} +0 -0
  133. /package/dist/{chunk-JVO5CYMK.js.map → chunk-OBGFWHDE.js.map} +0 -0
  134. /package/dist/{chunk-NB6BP6JB.js.map → chunk-PEK3X323.js.map} +0 -0
  135. /package/dist/{chunk-VPDTDKO5.js.map → chunk-PUXDD7XV.js.map} +0 -0
  136. /package/dist/{chunk-EHPQKXXW.js.map → chunk-VKAH66LI.js.map} +0 -0
  137. /package/dist/{chunk-MZVFZMQ6.js.map → chunk-WJ75GDIO.js.map} +0 -0
@@ -18,6 +18,7 @@ export const MESSAGE_TYPE = {
18
18
  APPROVAL_RESULT: 'APPROVAL_RESULT',
19
19
  ESCALATION_OFFER: 'ESCALATION_OFFER',
20
20
  TICKET_ESCALATED: 'TICKET_ESCALATED',
21
+ TICKET_EVENT: 'TICKET_EVENT',
21
22
  ERROR: 'ERROR',
22
23
  MESSAGE_START: 'MESSAGE_START',
23
24
  MESSAGE_END: 'MESSAGE_END',
@@ -158,6 +159,34 @@ export interface TicketEscalatedData {
158
159
  text?: string
159
160
  }
160
161
 
162
+ // ========== Ticket Event Types ==========
163
+
164
+ /** Known `TicketEventData.kind` values. The vocabulary is OPEN on the wire:
165
+ * an unknown kind still renders (as a neutral info line) rather than being
166
+ * dropped, so the backend can add lifecycle kinds without a client release. */
167
+ export const TICKET_EVENT_KIND = {
168
+ RESOLVED: 'RESOLVED',
169
+ REOPENED: 'REOPENED',
170
+ } as const
171
+
172
+ export type TicketEventKind =
173
+ | typeof TICKET_EVENT_KIND[keyof typeof TICKET_EVENT_KIND]
174
+ | (string & {})
175
+
176
+ /** Ticket lifecycle receipt (`TICKET_EVENT`) — the ticket was resolved,
177
+ * reopened, etc. Same field names live (NATS chunk) and persisted
178
+ * (GraphQL `TicketEventData` inside `messageData`), so ONE mapper covers
179
+ * both paths. All actor fields are optional: the event stays renderable
180
+ * when the backend can't attribute it. */
181
+ export interface TicketEventData {
182
+ kind: TicketEventKind
183
+ actorId?: string
184
+ actorName?: string
185
+ /** Who acted — e.g. an AI agent vs a human. Open string like `kind`. */
186
+ actorType?: string
187
+ reason?: string
188
+ }
189
+
161
190
  /**
162
191
  * Single tool call inside a batch approval request.
163
192
  * Mirrors backend PendingToolCallDto.
@@ -310,6 +339,23 @@ export type TicketEscalatedSegment = {
310
339
  data: TicketEscalatedData
311
340
  }
312
341
 
342
+ /**
343
+ * Ticket lifecycle receipt (resolved / reopened / an unknown future kind).
344
+ * Arrives as a standalone `TICKET_EVENT` chunk outside MESSAGE_START/END —
345
+ * same delivery shape as `ticket_escalated`.
346
+ *
347
+ * `streamSeq` is the chunk's JetStream sequence, kept ON the segment because
348
+ * it is the event's only stable identity: the accumulator upserts on it so a
349
+ * redelivered chunk (catch-up over hydrated history) replaces rather than
350
+ * stacks a second card, while two genuinely distinct events of the same kind
351
+ * (resolve → reopen → resolve) keep distinct sequences and both render.
352
+ */
353
+ export type TicketEventSegment = {
354
+ type: 'ticket_event'
355
+ data: TicketEventData
356
+ streamSeq?: number
357
+ }
358
+
313
359
  export type ErrorSegment = {
314
360
  type: 'error'
315
361
  title: string
@@ -322,7 +368,7 @@ export type ContextCompactionSegment = {
322
368
  summary?: string
323
369
  }
324
370
 
325
- export type MessageSegment = TextSegment | ThinkingSegment | GuideSegment | AskSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | EscalationOfferSegment | TicketEscalatedSegment | ErrorSegment | ContextCompactionSegment
371
+ export type MessageSegment = TextSegment | ThinkingSegment | GuideSegment | AskSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | EscalationOfferSegment | TicketEscalatedSegment | TicketEventSegment | ErrorSegment | ContextCompactionSegment
326
372
 
327
373
  export type MessageContent = string | MessageSegment[]
328
374
 
@@ -427,6 +473,18 @@ export interface TicketEscalatedMessageData extends MessageDataBase {
427
473
  text?: string
428
474
  }
429
475
 
476
+ /** Persisted ticket lifecycle row — same field names as the live chunk
477
+ * (see `TicketEventData`), decoded by the SAME mapper on replay. Fields are
478
+ * wire-shaped (`| null`): every one is a nullable GraphQL scalar. */
479
+ export interface TicketEventMessageData extends MessageDataBase {
480
+ type: 'TICKET_EVENT'
481
+ kind?: string | null
482
+ actorId?: string | null
483
+ actorName?: string | null
484
+ actorType?: string | null
485
+ reason?: string | null
486
+ }
487
+
430
488
  export interface ErrorMessageData extends MessageDataBase {
431
489
  type: 'ERROR'
432
490
  error?: string
@@ -473,6 +531,7 @@ export type MessageData =
473
531
  | ApprovalResultMessageData
474
532
  | EscalationOfferMessageData
475
533
  | TicketEscalatedMessageData
534
+ | TicketEventMessageData
476
535
  | ErrorMessageData
477
536
  | AIMetadataMessageData
478
537
  | SystemMessageData
@@ -20,6 +20,8 @@ import type {
20
20
  EscalationOfferSegment,
21
21
  TicketEscalatedData,
22
22
  TicketEscalatedSegment,
23
+ TicketEventData,
24
+ TicketEventSegment,
23
25
  ContextCompactionSegment,
24
26
  ErrorSegment,
25
27
  PendingApproval,
@@ -535,6 +537,42 @@ export class MessageSegmentAccumulator {
535
537
  return this.getSegments()
536
538
  }
537
539
 
540
+ /**
541
+ * Add a ticket lifecycle receipt (resolved / reopened / unknown kind).
542
+ *
543
+ * Upsert identity is the chunk's stream sequence when BOTH sides know it;
544
+ * otherwise full payload equality. History-hydrated segments come back
545
+ * seq-less (the persisted row's seq lives on the message, not the
546
+ * `messageData`), so a JetStream catch-up redelivery of the same event must
547
+ * still match its hydrated twin — payload equality is what catches that.
548
+ * Two genuinely distinct events (resolve → reopen → resolve) differ in seq
549
+ * or payload and both render.
550
+ */
551
+ addTicketEvent(data: TicketEventData, streamSeq?: number): MessageSegment[] {
552
+ const segment: TicketEventSegment = {
553
+ type: 'ticket_event',
554
+ data,
555
+ ...(streamSeq !== undefined ? { streamSeq } : {}),
556
+ }
557
+ const existingIndex = this.segments.findIndex((s) => {
558
+ if (s.type !== 'ticket_event') return false
559
+ if (s.streamSeq !== undefined && streamSeq !== undefined) return s.streamSeq === streamSeq
560
+ return (
561
+ s.data.kind === data.kind &&
562
+ s.data.actorId === data.actorId &&
563
+ s.data.actorName === data.actorName &&
564
+ s.data.actorType === data.actorType &&
565
+ s.data.reason === data.reason
566
+ )
567
+ })
568
+ if (existingIndex !== -1) {
569
+ this.segments[existingIndex] = segment
570
+ return this.getSegments()
571
+ }
572
+ this.segments.push(segment)
573
+ return this.getSegments()
574
+ }
575
+
538
576
  /**
539
577
  * Update status of an existing approval segment (single, batch, or
540
578
  * escalation offer).
@@ -680,6 +718,9 @@ export class MessageSegmentAccumulator {
680
718
  case 'ticket_escalated':
681
719
  this.addTicketEscalated(segment.data)
682
720
  break
721
+ case 'ticket_event':
722
+ this.addTicketEvent(segment.data, segment.streamSeq)
723
+ break
683
724
  case 'error':
684
725
  this.addError(segment.title, segment.details)
685
726
  break
@@ -243,6 +243,23 @@ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent
243
243
  }
244
244
  return null
245
245
 
246
+ // Same completeness gate as the live decoder: `kind` (an OPEN string —
247
+ // unknown kinds render as a neutral line) is the only required field.
248
+ // Field names match the chunk, so ONE mapping covers both paths; the
249
+ // typeof gates also fold the row's GraphQL nulls to undefined.
250
+ case MESSAGE_TYPE.TICKET_EVENT: {
251
+ const kind = 'kind' in data && typeof data.kind === 'string' ? data.kind.trim() : ''
252
+ if (!kind) return null
253
+ return {
254
+ type: 'ticket-event',
255
+ kind,
256
+ actorId: typeof data.actorId === 'string' ? data.actorId : undefined,
257
+ actorName: typeof data.actorName === 'string' ? data.actorName : undefined,
258
+ actorType: typeof data.actorType === 'string' ? data.actorType : undefined,
259
+ reason: typeof data.reason === 'string' && data.reason.trim() ? data.reason : undefined,
260
+ }
261
+ }
262
+
246
263
  case MESSAGE_TYPE.ERROR:
247
264
  if ('error' in data) {
248
265
  return {
@@ -336,6 +353,20 @@ function applyHistoryEvent(
336
353
  })
337
354
  break
338
355
 
356
+ // Seq-less on purpose: the persisted row's sequence lives on the message
357
+ // (`lastChunkStreamSeq`), not in `messageData`. A live redelivery of the
358
+ // same event matches this segment by payload equality (see
359
+ // `addTicketEvent`).
360
+ case 'ticket-event':
361
+ accumulator.addTicketEvent({
362
+ kind: event.kind,
363
+ actorId: event.actorId,
364
+ actorName: event.actorName,
365
+ actorType: event.actorType,
366
+ reason: event.reason,
367
+ })
368
+ break
369
+
339
370
  case 'escalation-offer-resolved':
340
371
  // Recorded, not applied here: `applyOfferResolutions` runs after every
341
372
  // flush and covers the same-bubble case as well as the cross-bubble one.
@@ -0,0 +1,100 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import type React from "react";
3
+
4
+ import { TicketEventMessage } from "../components/chat/ticket-event-message";
5
+
6
+ /**
7
+ * `ticket_event` message segment — the ticket lifecycle receipt (Figma:
8
+ * `ai-assistant-info`, types resolved-fae / resolved-tech / reopened-fae /
9
+ * reopened-tech / reopened-reason).
10
+ *
11
+ * Decoded from the standalone `TICKET_EVENT` chunk (live) or the persisted
12
+ * `TicketEventData` row (history). `kind` is an OPEN vocabulary: unknown
13
+ * kinds render as a neutral info line rather than being dropped, so the
14
+ * backend can add lifecycle kinds without a client release.
15
+ */
16
+
17
+ /** Fixed so the timestamp doesn't churn between snapshots. */
18
+ const TIMESTAMP = new Date("2026-08-07T14:47:00");
19
+
20
+ const plainDecorator = (Story: React.ComponentType) => (
21
+ <div style={{ maxWidth: 600, background: "var(--color-bg)" }} className="p-4">
22
+ <Story />
23
+ </div>
24
+ );
25
+
26
+ const meta = {
27
+ title: "Chat/Client/TicketEventMessage",
28
+ component: TicketEventMessage,
29
+ tags: ["autodocs"],
30
+ parameters: {
31
+ docs: {
32
+ description: {
33
+ component:
34
+ "Renders a `TICKET_EVENT` block: a green check for RESOLVED, a pink info tile for REOPENED and for any kind this build doesn't know, with copy composed from the event's actor and reason fields.",
35
+ },
36
+ },
37
+ },
38
+ decorators: [plainDecorator],
39
+ } satisfies Meta<typeof TicketEventMessage>;
40
+
41
+ export default meta;
42
+ type Story = StoryObj<typeof meta>;
43
+
44
+ /** Fae (the AI agent) resolved the ticket — Figma type=resolved-fae. */
45
+ export const ResolvedByFae: Story = {
46
+ args: {
47
+ data: { kind: "RESOLVED", actorId: "fae", actorName: "Fae", actorType: "AI" },
48
+ timestamp: TIMESTAMP,
49
+ },
50
+ };
51
+
52
+ /** A human technician resolved the ticket — Figma type=resolved-tech. */
53
+ export const ResolvedByTechnician: Story = {
54
+ args: {
55
+ data: { kind: "RESOLVED", actorId: "u-42", actorName: "Roman Smith", actorType: "TECHNICIAN" },
56
+ timestamp: TIMESTAMP,
57
+ },
58
+ };
59
+
60
+ /** Reopened with the user's stated reason — Figma type=reopened-reason. */
61
+ export const ReopenedWithReason: Story = {
62
+ args: {
63
+ data: {
64
+ kind: "REOPENED",
65
+ actorId: "u-7",
66
+ actorName: "John Smith",
67
+ actorType: "CLIENT",
68
+ reason: "The printer stopped working again",
69
+ },
70
+ timestamp: TIMESTAMP,
71
+ },
72
+ };
73
+
74
+ /** Reopened, no reason, AI keeps assisting — Figma type=reopened-fae. */
75
+ export const ReopenedFaeContinues: Story = {
76
+ args: {
77
+ data: { kind: "REOPENED", actorId: "fae", actorName: "Fae", actorType: "AI" },
78
+ timestamp: TIMESTAMP,
79
+ },
80
+ };
81
+
82
+ /** Reopened, no reason, routed to a human — Figma type=reopened-tech. */
83
+ export const ReopenedTechnicianReplies: Story = {
84
+ args: {
85
+ data: { kind: "REOPENED", actorId: "u-42", actorName: "Roman Smith", actorType: "TECHNICIAN" },
86
+ timestamp: TIMESTAMP,
87
+ },
88
+ };
89
+
90
+ /**
91
+ * A kind the client has never seen. The open vocabulary means the backend can
92
+ * add lifecycle kinds without a client release — this story is the regression
93
+ * guard: neutral info line, humanized title, never dropped.
94
+ */
95
+ export const UnknownKind: Story = {
96
+ args: {
97
+ data: { kind: "ON_HOLD", actorName: "Roman Smith", reason: "Waiting for replacement hardware" },
98
+ timestamp: TIMESTAMP,
99
+ },
100
+ };