@flamingo-stack/openframe-frontend-core 0.0.537-snapshot.20260814101439 → 0.0.537

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 +1 -15
  2. package/dist/chat-protocol/events.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +0 -15
  4. package/dist/chat-protocol/index.cjs.map +1 -1
  5. package/dist/chat-protocol/index.js +0 -15
  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-FDKMA7H4.js → chunk-3GXMS4OP.js} +2 -2
  9. package/dist/{chunk-O56ABJ43.js → chunk-5JPSQOZQ.js} +2 -2
  10. package/dist/{chunk-YXKFS6XF.js → chunk-7EHGP4QK.js} +4094 -4250
  11. package/dist/chunk-7EHGP4QK.js.map +1 -0
  12. package/dist/{chunk-MXB347SP.js → chunk-7FXZHI5D.js} +2 -2
  13. package/dist/{chunk-LR7NVXH7.cjs → chunk-7WNJ7RFO.cjs} +14 -14
  14. package/dist/{chunk-LR7NVXH7.cjs.map → chunk-7WNJ7RFO.cjs.map} +1 -1
  15. package/dist/{chunk-5CA7WS2F.js → chunk-C4LEH3VE.js} +2 -2
  16. package/dist/{chunk-KAQG3TDR.cjs → chunk-CATY4DVR.cjs} +87 -87
  17. package/dist/{chunk-KAQG3TDR.cjs.map → chunk-CATY4DVR.cjs.map} +1 -1
  18. package/dist/{chunk-ODENAPJ4.js → chunk-CQQXB25K.js} +2 -2
  19. package/dist/{chunk-APQOHCJH.js → chunk-CZHCXCWL.js} +2 -2
  20. package/dist/{chunk-F3PJTTFA.cjs → chunk-DHSVBGOX.cjs} +9 -9
  21. package/dist/{chunk-F3PJTTFA.cjs.map → chunk-DHSVBGOX.cjs.map} +1 -1
  22. package/dist/{chunk-YMBPNIRC.js → chunk-HRC56BQW.js} +5 -5
  23. package/dist/{chunk-QODV5KPW.cjs → chunk-KKZ2QMQD.cjs} +26 -26
  24. package/dist/{chunk-QODV5KPW.cjs.map → chunk-KKZ2QMQD.cjs.map} +1 -1
  25. package/dist/{chunk-DQAEKYH2.js → chunk-LFMIAPBP.js} +2 -2
  26. package/dist/{chunk-NX3RTMFX.cjs → chunk-MDP3YKFI.cjs} +5 -5
  27. package/dist/{chunk-NX3RTMFX.cjs.map → chunk-MDP3YKFI.cjs.map} +1 -1
  28. package/dist/{chunk-M6Q6BD4I.js → chunk-MOIHWYSA.js} +2 -2
  29. package/dist/{chunk-3NXYQJBY.cjs → chunk-OYTM3YXO.cjs} +7 -7
  30. package/dist/{chunk-3NXYQJBY.cjs.map → chunk-OYTM3YXO.cjs.map} +1 -1
  31. package/dist/{chunk-WPXQ2UBV.cjs → chunk-QPPG3KHW.cjs} +3 -3
  32. package/dist/{chunk-WPXQ2UBV.cjs.map → chunk-QPPG3KHW.cjs.map} +1 -1
  33. package/dist/{chunk-OPFMSQ5Q.js → chunk-SGNWSUL3.js} +6 -6
  34. package/dist/{chunk-OTDF4VUJ.cjs → chunk-SKIM263K.cjs} +37 -37
  35. package/dist/{chunk-OTDF4VUJ.cjs.map → chunk-SKIM263K.cjs.map} +1 -1
  36. package/dist/{chunk-QY7FHRWF.js → chunk-TCSSYTDN.js} +4 -4
  37. package/dist/{chunk-PT34VDFM.cjs → chunk-V4D5MPB7.cjs} +4 -4
  38. package/dist/{chunk-PT34VDFM.cjs.map → chunk-V4D5MPB7.cjs.map} +1 -1
  39. package/dist/{chunk-UV5LYSUJ.cjs → chunk-VSIHO7DJ.cjs} +61 -61
  40. package/dist/{chunk-UV5LYSUJ.cjs.map → chunk-VSIHO7DJ.cjs.map} +1 -1
  41. package/dist/{chunk-SCW6UZ3F.js → chunk-WHUEUACV.js} +2 -2
  42. package/dist/{chunk-64GAFOBF.cjs → chunk-WJIBB3KT.cjs} +31 -31
  43. package/dist/{chunk-64GAFOBF.cjs.map → chunk-WJIBB3KT.cjs.map} +1 -1
  44. package/dist/{chunk-W3MCQKDV.cjs → chunk-YEKXTTAC.cjs} +26 -26
  45. package/dist/{chunk-W3MCQKDV.cjs.map → chunk-YEKXTTAC.cjs.map} +1 -1
  46. package/dist/{chunk-4OHZCP7L.js → chunk-YIDGB5KP.js} +4 -4
  47. package/dist/{chunk-FQ53MYZ4.cjs → chunk-YMPRMZDD.cjs} +37 -193
  48. package/dist/chunk-YMPRMZDD.cjs.map +1 -0
  49. package/dist/{chunk-K5RWDOAC.cjs → chunk-YPI3JIR6.cjs} +11 -11
  50. package/dist/{chunk-K5RWDOAC.cjs.map → chunk-YPI3JIR6.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/chat-message-enhanced.d.ts.map +1 -1
  54. package/dist/components/chat/index.cjs +2 -8
  55. package/dist/components/chat/index.cjs.map +1 -1
  56. package/dist/components/chat/index.d.ts +0 -2
  57. package/dist/components/chat/index.d.ts.map +1 -1
  58. package/dist/components/chat/index.js +1 -7
  59. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
  60. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
  61. package/dist/components/chat/ticket-escalated-message.d.ts +8 -2
  62. package/dist/components/chat/ticket-escalated-message.d.ts.map +1 -1
  63. package/dist/components/chat/types/message.types.d.ts +2 -51
  64. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  65. package/dist/components/chat/utils/message-segment-accumulator.d.ts +1 -13
  66. package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
  67. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  68. package/dist/components/contact/index.cjs +3 -3
  69. package/dist/components/contact/index.js +2 -2
  70. package/dist/components/docs/index.cjs +5 -5
  71. package/dist/components/docs/index.js +4 -4
  72. package/dist/components/embeds/index.cjs +3 -3
  73. package/dist/components/embeds/index.js +2 -2
  74. package/dist/components/faq/index.cjs +3 -3
  75. package/dist/components/faq/index.js +2 -2
  76. package/dist/components/features/index.cjs +2 -2
  77. package/dist/components/features/index.js +1 -1
  78. package/dist/components/help-center-pages/index.cjs +22 -22
  79. package/dist/components/help-center-pages/index.js +13 -13
  80. package/dist/components/index.cjs +132 -138
  81. package/dist/components/index.cjs.map +1 -1
  82. package/dist/components/index.js +11 -17
  83. package/dist/components/index.js.map +1 -1
  84. package/dist/components/meeting-scheduler/index.cjs +34 -34
  85. package/dist/components/meeting-scheduler/index.js +3 -3
  86. package/dist/components/navigation/index.cjs +2 -2
  87. package/dist/components/navigation/index.js +1 -1
  88. package/dist/components/onboarding-guides/index.cjs +5 -5
  89. package/dist/components/onboarding-guides/index.js +4 -4
  90. package/dist/components/related-content/index.cjs +3 -3
  91. package/dist/components/related-content/index.js +2 -2
  92. package/dist/components/tickets/index.cjs +6 -6
  93. package/dist/components/tickets/index.js +5 -5
  94. package/dist/components/ui/index.cjs +2 -8
  95. package/dist/components/ui/index.cjs.map +1 -1
  96. package/dist/components/ui/index.js +1 -7
  97. package/dist/index.cjs +2 -8
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.js +1 -7
  100. package/dist/utils/index.cjs.map +1 -1
  101. package/dist/utils/index.js.map +1 -1
  102. package/package.json +1 -1
  103. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -44
  104. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -32
  105. package/src/chat-protocol/events.ts +0 -16
  106. package/src/chat-protocol/nats-decoder.ts +0 -27
  107. package/src/components/chat/chat-message-enhanced.tsx +0 -3
  108. package/src/components/chat/index.ts +0 -2
  109. package/src/components/chat/stream/chat-stream-reducer.ts +0 -19
  110. package/src/components/chat/stream/message-mutations.ts +0 -5
  111. package/src/components/chat/ticket-escalated-message.tsx +32 -10
  112. package/src/components/chat/types/message.types.ts +1 -60
  113. package/src/components/chat/utils/message-segment-accumulator.ts +0 -41
  114. package/src/components/chat/utils/process-historical-messages.ts +0 -31
  115. package/dist/chunk-FQ53MYZ4.cjs.map +0 -1
  116. package/dist/chunk-YXKFS6XF.js.map +0 -1
  117. package/dist/components/chat/ai-assistant-info.d.ts +0 -31
  118. package/dist/components/chat/ai-assistant-info.d.ts.map +0 -1
  119. package/dist/components/chat/ticket-event-message.d.ts +0 -24
  120. package/dist/components/chat/ticket-event-message.d.ts.map +0 -1
  121. package/src/components/chat/__tests__/ticket-event.test.ts +0 -194
  122. package/src/components/chat/ai-assistant-info.tsx +0 -71
  123. package/src/components/chat/ticket-event-message.tsx +0 -79
  124. package/src/stories/TicketEventMessage.stories.tsx +0 -100
  125. /package/dist/{chunk-FDKMA7H4.js.map → chunk-3GXMS4OP.js.map} +0 -0
  126. /package/dist/{chunk-O56ABJ43.js.map → chunk-5JPSQOZQ.js.map} +0 -0
  127. /package/dist/{chunk-MXB347SP.js.map → chunk-7FXZHI5D.js.map} +0 -0
  128. /package/dist/{chunk-5CA7WS2F.js.map → chunk-C4LEH3VE.js.map} +0 -0
  129. /package/dist/{chunk-ODENAPJ4.js.map → chunk-CQQXB25K.js.map} +0 -0
  130. /package/dist/{chunk-APQOHCJH.js.map → chunk-CZHCXCWL.js.map} +0 -0
  131. /package/dist/{chunk-YMBPNIRC.js.map → chunk-HRC56BQW.js.map} +0 -0
  132. /package/dist/{chunk-DQAEKYH2.js.map → chunk-LFMIAPBP.js.map} +0 -0
  133. /package/dist/{chunk-M6Q6BD4I.js.map → chunk-MOIHWYSA.js.map} +0 -0
  134. /package/dist/{chunk-OPFMSQ5Q.js.map → chunk-SGNWSUL3.js.map} +0 -0
  135. /package/dist/{chunk-QY7FHRWF.js.map → chunk-TCSSYTDN.js.map} +0 -0
  136. /package/dist/{chunk-SCW6UZ3F.js.map → chunk-WHUEUACV.js.map} +0 -0
  137. /package/dist/{chunk-4OHZCP7L.js.map → chunk-YIDGB5KP.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.537-snapshot.20260814101439",
3
+ "version": "0.0.537",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -406,50 +406,6 @@ exports[`decodeNatsChunk — golden corpus > maps the full recorded corpus to no
406
406
  },
407
407
  "ticket_escalated_missing_reason": null,
408
408
  "ticket_escalated_missing_ticket_id": null,
409
- "ticket_event_blank_reason_dropped": {
410
- "actorId": undefined,
411
- "actorName": undefined,
412
- "actorType": undefined,
413
- "kind": "RESOLVED",
414
- "reason": undefined,
415
- "type": "ticket-event",
416
- },
417
- "ticket_event_missing_kind": null,
418
- "ticket_event_reopened_with_reason": {
419
- "actorId": "user-42",
420
- "actorName": "John Smith",
421
- "actorType": "CLIENT",
422
- "kind": "REOPENED",
423
- "reason": "The printer stopped working again",
424
- "seq": 413,
425
- "type": "ticket-event",
426
- },
427
- "ticket_event_resolved": {
428
- "actorId": "fae",
429
- "actorName": "Fae",
430
- "actorType": "AI",
431
- "kind": "RESOLVED",
432
- "reason": undefined,
433
- "seq": 412,
434
- "type": "ticket-event",
435
- },
436
- "ticket_event_stream_seq_wins": {
437
- "actorId": undefined,
438
- "actorName": undefined,
439
- "actorType": undefined,
440
- "kind": "RESOLVED",
441
- "reason": undefined,
442
- "seq": 500,
443
- "type": "ticket-event",
444
- },
445
- "ticket_event_unknown_kind": {
446
- "actorId": undefined,
447
- "actorName": "Roman Smith",
448
- "actorType": undefined,
449
- "kind": "ON_HOLD",
450
- "reason": undefined,
451
- "type": "ticket-event",
452
- },
453
409
  "token_usage": {
454
410
  "contextSize": 180000,
455
411
  "inputTokensSize": 1200,
@@ -257,38 +257,6 @@ const CORPUS: Record<string, unknown> = {
257
257
  ticket_escalated_missing_reason: { type: 'TICKET_ESCALATED', ticketId: 'ticket-1' },
258
258
  ticket_escalated_missing_ticket_id: { type: 'TICKET_ESCALATED', reason: 'INACTIVITY' },
259
259
 
260
- // TICKET_EVENT — standalone lifecycle receipt. `kind` is an OPEN string:
261
- // unknown kinds must decode (neutral render), only a missing/blank kind is
262
- // malformed. The chunk names its JetStream sequence `sequenceId` in the
263
- // payload; it backfills `seq` when the transport didn't stamp `streamSeq`.
264
- ticket_event_resolved: {
265
- type: 'TICKET_EVENT',
266
- kind: 'RESOLVED',
267
- actorId: 'fae',
268
- actorName: 'Fae',
269
- actorType: 'AI',
270
- sequenceId: 412,
271
- },
272
- ticket_event_reopened_with_reason: {
273
- type: 'TICKET_EVENT',
274
- kind: 'REOPENED',
275
- actorId: 'user-42',
276
- actorName: 'John Smith',
277
- actorType: 'CLIENT',
278
- reason: 'The printer stopped working again',
279
- sequenceId: 413,
280
- },
281
- ticket_event_unknown_kind: { type: 'TICKET_EVENT', kind: 'ON_HOLD', actorName: 'Roman Smith' },
282
- ticket_event_missing_kind: { type: 'TICKET_EVENT', actorName: 'Fae' },
283
- ticket_event_blank_reason_dropped: { type: 'TICKET_EVENT', kind: 'RESOLVED', reason: ' ' },
284
- // Transport-stamped streamSeq wins over the payload's sequenceId copy.
285
- ticket_event_stream_seq_wins: {
286
- type: 'TICKET_EVENT',
287
- kind: 'RESOLVED',
288
- streamSeq: 500,
289
- sequenceId: 499,
290
- },
291
-
292
260
  approval_result_approved: {
293
261
  type: 'APPROVAL_RESULT',
294
262
  approvalRequestId: 'req-1',
@@ -251,21 +251,6 @@ export interface TicketEscalatedEvent extends ChatStreamEventBase {
251
251
  text?: string
252
252
  }
253
253
 
254
- /** Ticket lifecycle receipt (`TICKET_EVENT`) — the ticket was resolved,
255
- * reopened, etc. `kind` is an OPEN vocabulary (RESOLVED/REOPENED today):
256
- * an unknown kind still decodes and renders as a neutral line rather than
257
- * being dropped, so the backend can add kinds without a client release.
258
- * Arrives standalone (outside MESSAGE_START/END), like `ticket-escalated`. */
259
- export interface TicketEventEvent extends ChatStreamEventBase {
260
- type: 'ticket-event'
261
- kind: string
262
- actorId?: string
263
- actorName?: string
264
- /** Who acted — e.g. an AI agent vs a human technician. Open string. */
265
- actorType?: string
266
- reason?: string
267
- }
268
-
269
254
  /** Per-turn metadata. Raw wire values pass through UNVALIDATED — the
270
255
  * consumer replicates the legacy truthiness/typeof gates (so a
271
256
  * malformed frame degrades identically to the pre-SSOT parser). */
@@ -380,7 +365,6 @@ export type ChatStreamEvent =
380
365
  | EscalationOfferEvent
381
366
  | EscalationOfferResolvedEvent
382
367
  | TicketEscalatedEvent
383
- | TicketEventEvent
384
368
  | ChatMetadataEvent
385
369
  | UsageEvent
386
370
  | TokenUsageEvent
@@ -370,33 +370,6 @@ export function decodeNatsChunk(chunk: unknown): ChatStreamEvent | null {
370
370
  }
371
371
  }
372
372
 
373
- case MESSAGE_TYPE.TICKET_EVENT: {
374
- // `kind` is the only required field, and deliberately an OPEN string:
375
- // unknown kinds must render (as a neutral line), not be dropped.
376
- const kind = typeof data.kind === 'string' ? data.kind.trim() : ''
377
- if (!kind) return null
378
- // This chunk names its own JetStream sequence `sequenceId` in the
379
- // payload (the persisted row's `lastChunkStreamSeq` equals it — the
380
- // dedupe key). Prefer the transport-stamped `streamSeq` envelope like
381
- // every other chunk; fall back to the payload copy when absent.
382
- const eventSeq: { seq?: number } =
383
- seq.seq !== undefined
384
- ? seq
385
- : typeof data.sequenceId === 'number'
386
- ? { seq: data.sequenceId }
387
- : {}
388
- return {
389
- type: 'ticket-event',
390
- kind,
391
- actorId: typeof data.actorId === 'string' ? data.actorId : undefined,
392
- actorName: typeof data.actorName === 'string' ? data.actorName : undefined,
393
- actorType: typeof data.actorType === 'string' ? data.actorType : undefined,
394
- reason:
395
- typeof data.reason === 'string' && data.reason.trim() ? data.reason : undefined,
396
- ...eventSeq,
397
- }
398
- }
399
-
400
373
  case MESSAGE_TYPE.ERROR:
401
374
  return {
402
375
  type: 'error',
@@ -10,7 +10,6 @@ import { ApprovalRequestMessage } from "./approval-request-message"
10
10
  import { ApprovalBatchMessage } from "./approval-batch-message"
11
11
  import { EscalationOfferMessage } from "./escalation-offer-message"
12
12
  import { TicketEscalatedMessage } from "./ticket-escalated-message"
13
- import { TicketEventMessage } from "./ticket-event-message"
14
13
  import { ErrorMessageDisplay } from "./error-message-display"
15
14
  import { ContextCompactionDisplay } from "./context-compaction-display"
16
15
  import { ThinkingDisplay } from "./thinking-display"
@@ -651,8 +650,6 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
651
650
  )
652
651
  } else if (segment.type === 'ticket_escalated') {
653
652
  return <TicketEscalatedMessage key={index} data={segment.data} timestamp={timestamp} />
654
- } else if (segment.type === 'ticket_event') {
655
- return <TicketEventMessage key={index} data={segment.data} timestamp={timestamp} />
656
653
  } else if (segment.type === 'error') {
657
654
  return (
658
655
  <ErrorMessageDisplay
@@ -5,8 +5,6 @@ export * from './approval-request-message'
5
5
  export * from './approval-batch-message'
6
6
  export * from './escalation-offer-message'
7
7
  export * from './ticket-escalated-message'
8
- export * from './ticket-event-message'
9
- export * from './ai-assistant-info'
10
8
  export * from './context-compaction-display'
11
9
  export * from './expand-chevron'
12
10
  export * from './thinking-display'
@@ -1364,25 +1364,6 @@ export function createChatStreamReducer(
1364
1364
  break
1365
1365
  }
1366
1366
 
1367
- case 'ticket-event': {
1368
- // Standalone chunk outside MESSAGE_START/END — same routing as
1369
- // `ticket-escalated`. `seq` rides onto the segment: it is the event's
1370
- // dedupe identity (see `addTicketEvent`).
1371
- const before = accumulator.getSegments().length
1372
- const segments = accumulator.addTicketEvent(
1373
- {
1374
- kind: event.kind,
1375
- actorId: event.actorId,
1376
- actorName: event.actorName,
1377
- actorType: event.actorType,
1378
- reason: event.reason,
1379
- },
1380
- seq,
1381
- )
1382
- applyAccumulated(before, segments)
1383
- break
1384
- }
1385
-
1386
1367
  case 'error': {
1387
1368
  let message: string | undefined
1388
1369
  if (event.details) {
@@ -96,11 +96,6 @@ function upsertKey(seg: MessageSegment): string | null {
96
96
  return `offer:${seg.data.offerId}`
97
97
  case 'ticket_escalated':
98
98
  return `escalated:${seg.data.ticketId}`
99
- case 'ticket_event':
100
- // The stream sequence is the event's only stable id; a seq-less segment
101
- // (hydrated history) is pushed raw — `addTicketEvent`'s payload-equality
102
- // upsert owns that case.
103
- return seg.streamSeq !== undefined ? `ticket-event:${seg.streamSeq}` : null
104
99
  default:
105
100
  return null
106
101
  }
@@ -1,8 +1,9 @@
1
1
  "use client"
2
2
 
3
3
  import { forwardRef, type HTMLAttributes } from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { formatTime } from "../../utils/format-date"
4
6
  import { InfoCircleIcon } from "../icons-v2-generated"
5
- import { AiAssistantInfo } from "./ai-assistant-info"
6
7
  import type { TicketEscalatedSegment } from "./types/message.types"
7
8
 
8
9
  const TITLE = "Handed Off to a Technician"
@@ -18,25 +19,46 @@ export interface TicketEscalatedMessageProps extends HTMLAttributes<HTMLDivEleme
18
19
 
19
20
  /**
20
21
  * Handoff receipt — the conversation now belongs to a human technician.
21
- * Figma `ai-assistant-info` (type=escalation); chrome and the wrapping/
22
- * truncation trade-offs live in the shared `AiAssistantInfo` shell.
22
+ * Figma `ai-assistant-info` (type=escalation).
23
23
  *
24
24
  * The body is whatever the backend authored, so the reason it gives stays
25
25
  * server-side; the client never composes an explanation and never branches on
26
26
  * `data.reason`, which is why a reason added server-side needs no change here.
27
+ *
28
+ * That body WRAPS, deliberately diverging from the mock's single-line ellipsis:
29
+ * the mock's 72px comes from its placeholder copy fitting one line, while the
30
+ * real wire text is a full sentence, and clipping the only explanation of why
31
+ * the chat was handed off is not recoverable on touch. The title still
32
+ * truncates — it is a constant, and holding one line keeps the timestamp from
33
+ * being pushed out of the row at narrow widths.
27
34
  */
28
35
  const TicketEscalatedMessage = forwardRef<HTMLDivElement, TicketEscalatedMessageProps>(
29
- ({ data, timestamp, ...props }, ref) => {
36
+ ({ className, data, timestamp, ...props }, ref) => {
30
37
  const body = data.text?.trim() || DEFAULT_BODY
31
38
  return (
32
- <AiAssistantInfo
39
+ <div
33
40
  ref={ref}
34
- icon={<InfoCircleIcon className="size-6 text-ods-flamingo-pink" />}
35
- title={TITLE}
36
- body={body}
37
- timestamp={timestamp}
41
+ className={cn(
42
+ "mb-[var(--spacing-system-xsf)] flex items-start gap-[var(--spacing-system-s)] rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-s)]",
43
+ className,
44
+ )}
38
45
  {...props}
39
- />
46
+ >
47
+ <div className="flex size-12 shrink-0 items-center justify-center rounded-md border border-ods-border bg-ods-bg">
48
+ <InfoCircleIcon className="size-6 text-ods-flamingo-pink" />
49
+ </div>
50
+ <div className="flex min-w-0 flex-1 flex-col">
51
+ <div className="flex w-full items-start">
52
+ <p className="text-h4 min-w-0 flex-1 truncate text-ods-text-primary" title={TITLE}>
53
+ {TITLE}
54
+ </p>
55
+ {timestamp && (
56
+ <span className="text-h6 shrink-0 text-right text-ods-text-secondary">{formatTime(timestamp)}</span>
57
+ )}
58
+ </div>
59
+ <p className="text-h4 w-full break-words text-ods-text-secondary">{body}</p>
60
+ </div>
61
+ </div>
40
62
  )
41
63
  },
42
64
  )
@@ -18,7 +18,6 @@ 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',
22
21
  ERROR: 'ERROR',
23
22
  MESSAGE_START: 'MESSAGE_START',
24
23
  MESSAGE_END: 'MESSAGE_END',
@@ -159,34 +158,6 @@ export interface TicketEscalatedData {
159
158
  text?: string
160
159
  }
161
160
 
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
-
190
161
  /**
191
162
  * Single tool call inside a batch approval request.
192
163
  * Mirrors backend PendingToolCallDto.
@@ -339,23 +310,6 @@ export type TicketEscalatedSegment = {
339
310
  data: TicketEscalatedData
340
311
  }
341
312
 
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
-
359
313
  export type ErrorSegment = {
360
314
  type: 'error'
361
315
  title: string
@@ -368,7 +322,7 @@ export type ContextCompactionSegment = {
368
322
  summary?: string
369
323
  }
370
324
 
371
- export type MessageSegment = TextSegment | ThinkingSegment | GuideSegment | AskSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | EscalationOfferSegment | TicketEscalatedSegment | TicketEventSegment | ErrorSegment | ContextCompactionSegment
325
+ export type MessageSegment = TextSegment | ThinkingSegment | GuideSegment | AskSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | EscalationOfferSegment | TicketEscalatedSegment | ErrorSegment | ContextCompactionSegment
372
326
 
373
327
  export type MessageContent = string | MessageSegment[]
374
328
 
@@ -473,18 +427,6 @@ export interface TicketEscalatedMessageData extends MessageDataBase {
473
427
  text?: string
474
428
  }
475
429
 
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
-
488
430
  export interface ErrorMessageData extends MessageDataBase {
489
431
  type: 'ERROR'
490
432
  error?: string
@@ -531,7 +473,6 @@ export type MessageData =
531
473
  | ApprovalResultMessageData
532
474
  | EscalationOfferMessageData
533
475
  | TicketEscalatedMessageData
534
- | TicketEventMessageData
535
476
  | ErrorMessageData
536
477
  | AIMetadataMessageData
537
478
  | SystemMessageData
@@ -20,8 +20,6 @@ import type {
20
20
  EscalationOfferSegment,
21
21
  TicketEscalatedData,
22
22
  TicketEscalatedSegment,
23
- TicketEventData,
24
- TicketEventSegment,
25
23
  ContextCompactionSegment,
26
24
  ErrorSegment,
27
25
  PendingApproval,
@@ -537,42 +535,6 @@ export class MessageSegmentAccumulator {
537
535
  return this.getSegments()
538
536
  }
539
537
 
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
-
576
538
  /**
577
539
  * Update status of an existing approval segment (single, batch, or
578
540
  * escalation offer).
@@ -718,9 +680,6 @@ export class MessageSegmentAccumulator {
718
680
  case 'ticket_escalated':
719
681
  this.addTicketEscalated(segment.data)
720
682
  break
721
- case 'ticket_event':
722
- this.addTicketEvent(segment.data, segment.streamSeq)
723
- break
724
683
  case 'error':
725
684
  this.addError(segment.title, segment.details)
726
685
  break
@@ -243,23 +243,6 @@ 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
-
263
246
  case MESSAGE_TYPE.ERROR:
264
247
  if ('error' in data) {
265
248
  return {
@@ -353,20 +336,6 @@ function applyHistoryEvent(
353
336
  })
354
337
  break
355
338
 
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
-
370
339
  case 'escalation-offer-resolved':
371
340
  // Recorded, not applied here: `applyOfferResolutions` runs after every
372
341
  // flush and covers the same-bubble case as well as the cross-bubble one.