@flamingo-stack/openframe-frontend-core 0.0.587-1973.5528.1 → 0.0.587-2000.5532.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 (165) hide show
  1. package/dist/chat-protocol/events.d.ts +2 -8
  2. package/dist/chat-protocol/events.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +0 -188
  4. package/dist/chat-protocol/index.cjs.map +1 -1
  5. package/dist/chat-protocol/index.js +0 -188
  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-5VCDP6UA.cjs → chunk-3FHWLQDG.cjs} +10 -10
  9. package/dist/{chunk-5VCDP6UA.cjs.map → chunk-3FHWLQDG.cjs.map} +1 -1
  10. package/dist/{chunk-RE3KVI2Q.js → chunk-3PDTGDOA.js} +5 -5
  11. package/dist/{chunk-5X7AJ67N.js → chunk-436G4DI4.js} +3 -3
  12. package/dist/{chunk-BLC4JOFC.cjs → chunk-4IIFSFJ3.cjs} +15 -15
  13. package/dist/{chunk-BLC4JOFC.cjs.map → chunk-4IIFSFJ3.cjs.map} +1 -1
  14. package/dist/{chunk-JR37UTA2.cjs → chunk-5O5JMGEN.cjs} +33 -33
  15. package/dist/{chunk-JR37UTA2.cjs.map → chunk-5O5JMGEN.cjs.map} +1 -1
  16. package/dist/{chunk-7A6J4JTW.js → chunk-6BFEJXMI.js} +3 -3
  17. package/dist/{chunk-S2UY2C4A.cjs → chunk-7WIKOL25.cjs} +10 -10
  18. package/dist/{chunk-S2UY2C4A.cjs.map → chunk-7WIKOL25.cjs.map} +1 -1
  19. package/dist/{chunk-DVMQV3M4.cjs → chunk-A5PXHMCJ.cjs} +69 -69
  20. package/dist/{chunk-DVMQV3M4.cjs.map → chunk-A5PXHMCJ.cjs.map} +1 -1
  21. package/dist/{chunk-RQ5PYSRJ.js → chunk-BLJEPNMJ.js} +4 -4
  22. package/dist/{chunk-4MVGFYRE.js → chunk-D7YISHXC.js} +190 -534
  23. package/dist/chunk-D7YISHXC.js.map +1 -0
  24. package/dist/{chunk-4S7GMJYA.cjs → chunk-DMQMRFGS.cjs} +21 -21
  25. package/dist/{chunk-4S7GMJYA.cjs.map → chunk-DMQMRFGS.cjs.map} +1 -1
  26. package/dist/{chunk-AIUIIGO3.cjs → chunk-DTQCZ6AR.cjs} +24 -24
  27. package/dist/{chunk-AIUIIGO3.cjs.map → chunk-DTQCZ6AR.cjs.map} +1 -1
  28. package/dist/{chunk-QKAUBYU5.cjs → chunk-E7NFHRLX.cjs} +3 -3
  29. package/dist/{chunk-QKAUBYU5.cjs.map → chunk-E7NFHRLX.cjs.map} +1 -1
  30. package/dist/{chunk-WFWWQO2I.js → chunk-F3YRPKUA.js} +2 -2
  31. package/dist/{chunk-EKKR5ZFB.cjs → chunk-G45RGWHF.cjs} +543 -887
  32. package/dist/chunk-G45RGWHF.cjs.map +1 -0
  33. package/dist/{chunk-ZRFSUCAH.js → chunk-HCWZP44O.js} +5 -3
  34. package/dist/{chunk-ZRFSUCAH.js.map → chunk-HCWZP44O.js.map} +1 -1
  35. package/dist/{chunk-NEI7XMCM.cjs → chunk-IGBNEPTD.cjs} +9 -9
  36. package/dist/{chunk-NEI7XMCM.cjs.map → chunk-IGBNEPTD.cjs.map} +1 -1
  37. package/dist/{chunk-3UJMZADA.cjs → chunk-KQ5JQVYL.cjs} +43 -43
  38. package/dist/{chunk-3UJMZADA.cjs.map → chunk-KQ5JQVYL.cjs.map} +1 -1
  39. package/dist/{chunk-FDWGO244.js → chunk-LODCKZ72.js} +2 -2
  40. package/dist/{chunk-ULOMJH7C.cjs → chunk-MF7TMFVB.cjs} +5 -5
  41. package/dist/{chunk-ULOMJH7C.cjs.map → chunk-MF7TMFVB.cjs.map} +1 -1
  42. package/dist/{chunk-DSXD77KX.cjs → chunk-MXCWOMNT.cjs} +3 -3
  43. package/dist/{chunk-DSXD77KX.cjs.map → chunk-MXCWOMNT.cjs.map} +1 -1
  44. package/dist/{chunk-E2QFZDCP.js → chunk-OKD7MX7Y.js} +5 -5
  45. package/dist/{chunk-OW3FUJ2I.js → chunk-P24OI2CD.js} +3 -3
  46. package/dist/{chunk-QSO24MEK.js → chunk-R2U2D7LU.js} +2 -2
  47. package/dist/{chunk-KQWI25NP.js → chunk-SI57FQZ2.js} +2 -2
  48. package/dist/{chunk-JQO47UHC.js → chunk-SVR75PFW.js} +5 -5
  49. package/dist/{chunk-UYHETUX3.cjs → chunk-UBSIENYF.cjs} +162 -160
  50. package/dist/chunk-UBSIENYF.cjs.map +1 -0
  51. package/dist/{chunk-OZRT3ZI3.js → chunk-V4WUSOGS.js} +3 -3
  52. package/dist/{chunk-UODM3HJC.js → chunk-VWNU2PFX.js} +3 -3
  53. package/dist/{chunk-SCVIBVFS.cjs → chunk-WQXWH7LF.cjs} +4 -4
  54. package/dist/{chunk-SCVIBVFS.cjs.map → chunk-WQXWH7LF.cjs.map} +1 -1
  55. package/dist/{chunk-7COFWOCJ.cjs → chunk-XNEPABUD.cjs} +22 -22
  56. package/dist/{chunk-7COFWOCJ.cjs.map → chunk-XNEPABUD.cjs.map} +1 -1
  57. package/dist/{chunk-QYDIVL4X.js → chunk-YB5DFQKD.js} +6 -6
  58. package/dist/components/case-studies/index.cjs +9 -9
  59. package/dist/components/case-studies/index.js +3 -3
  60. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  61. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  62. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  63. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  64. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +1 -4
  65. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  66. package/dist/components/chat/hooks/use-slash-commands.d.ts +3 -3
  67. package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
  68. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +28 -2
  69. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  70. package/dist/components/chat/index.cjs +2 -2
  71. package/dist/components/chat/index.js +1 -1
  72. package/dist/components/chat/remark-strip-citations.d.ts.map +1 -1
  73. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
  74. package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
  75. package/dist/components/chat/types/component.types.d.ts +0 -4
  76. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  77. package/dist/components/chat/types/message.types.d.ts +1 -35
  78. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  79. package/dist/components/chat/types/unified-chat-state.types.d.ts +3 -3
  80. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  81. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  82. package/dist/components/contact/index.cjs +4 -4
  83. package/dist/components/contact/index.js +3 -3
  84. package/dist/components/docs/index.cjs +7 -7
  85. package/dist/components/docs/index.js +6 -6
  86. package/dist/components/embeds/index.cjs +4 -4
  87. package/dist/components/embeds/index.js +3 -3
  88. package/dist/components/faq/index.cjs +4 -4
  89. package/dist/components/faq/index.js +3 -3
  90. package/dist/components/features/index.cjs +3 -3
  91. package/dist/components/features/index.js +2 -2
  92. package/dist/components/help-center-pages/index.cjs +23 -23
  93. package/dist/components/help-center-pages/index.js +14 -14
  94. package/dist/components/index.cjs +36 -36
  95. package/dist/components/index.js +13 -13
  96. package/dist/components/meeting-scheduler/index.cjs +21 -21
  97. package/dist/components/meeting-scheduler/index.js +2 -2
  98. package/dist/components/navigation/index.cjs +6 -6
  99. package/dist/components/navigation/index.js +5 -5
  100. package/dist/components/onboarding-guides/index.cjs +6 -6
  101. package/dist/components/onboarding-guides/index.js +5 -5
  102. package/dist/components/tickets/index.cjs +7 -7
  103. package/dist/components/tickets/index.js +6 -6
  104. package/dist/components/ui/data-table/use-data-table.d.ts.map +1 -1
  105. package/dist/components/ui/hover-dropdown.d.ts.map +1 -1
  106. package/dist/components/ui/index.cjs +3 -3
  107. package/dist/components/ui/index.js +2 -2
  108. package/dist/index.cjs +7 -7
  109. package/dist/index.js +6 -6
  110. package/dist/utils/index.cjs.map +1 -1
  111. package/dist/utils/index.js.map +1 -1
  112. package/package.json +1 -2
  113. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -130
  114. package/src/chat-protocol/events.ts +1 -9
  115. package/src/chat-protocol/nats-decoder.ts +0 -7
  116. package/src/components/chat/__tests__/chat-message-list.test.tsx +0 -70
  117. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +0 -86
  118. package/src/components/chat/chat-message-enhanced.tsx +13 -28
  119. package/src/components/chat/chat-message-list.tsx +22 -27
  120. package/src/components/chat/embeddable-chat.tsx +36 -55
  121. package/src/components/chat/entity-cards/dispatch.tsx +17 -86
  122. package/src/components/chat/hooks/use-nats-chat-adapter.ts +0 -7
  123. package/src/components/chat/hooks/use-slash-commands.ts +5 -9
  124. package/src/components/chat/hooks/use-sse-chat-adapter.ts +29 -2
  125. package/src/components/chat/remark-strip-citations.ts +3 -5
  126. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -56
  127. package/src/components/chat/stream/chat-stream-reducer.ts +0 -31
  128. package/src/components/chat/tool-execution-display.tsx +2 -5
  129. package/src/components/chat/types/component.types.ts +0 -4
  130. package/src/components/chat/types/message.types.ts +0 -39
  131. package/src/components/chat/types/unified-chat-state.types.ts +3 -4
  132. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +0 -57
  133. package/src/components/chat/utils/process-historical-messages.ts +1 -19
  134. package/src/components/ui/__tests__/data-table-compiler-identity.test.tsx +74 -0
  135. package/src/components/ui/data-table/use-data-table.ts +36 -1
  136. package/src/components/ui/hover-dropdown.tsx +3 -8
  137. package/dist/chat-protocol/source-metadata.d.ts +0 -5
  138. package/dist/chat-protocol/source-metadata.d.ts.map +0 -1
  139. package/dist/chunk-4MVGFYRE.js.map +0 -1
  140. package/dist/chunk-EKKR5ZFB.cjs.map +0 -1
  141. package/dist/chunk-UYHETUX3.cjs.map +0 -1
  142. package/dist/components/chat/utils/chat-source-metadata.d.ts +0 -5
  143. package/dist/components/chat/utils/chat-source-metadata.d.ts.map +0 -1
  144. package/src/chat-protocol/__tests__/source-metadata.test.ts +0 -74
  145. package/src/chat-protocol/source-metadata.ts +0 -202
  146. package/src/components/chat/__tests__/embeddable-chat-sources.test.tsx +0 -411
  147. package/src/components/chat/__tests__/remark-strip-citations.test.ts +0 -52
  148. package/src/components/chat/__tests__/tool-execution-display.test.tsx +0 -93
  149. package/src/components/chat/entity-cards/__tests__/dispatch-video-refs.test.tsx +0 -74
  150. package/src/components/chat/hooks/__tests__/use-slash-commands.test.ts +0 -64
  151. package/src/components/chat/utils/chat-source-metadata.ts +0 -19
  152. /package/dist/{chunk-RE3KVI2Q.js.map → chunk-3PDTGDOA.js.map} +0 -0
  153. /package/dist/{chunk-5X7AJ67N.js.map → chunk-436G4DI4.js.map} +0 -0
  154. /package/dist/{chunk-7A6J4JTW.js.map → chunk-6BFEJXMI.js.map} +0 -0
  155. /package/dist/{chunk-RQ5PYSRJ.js.map → chunk-BLJEPNMJ.js.map} +0 -0
  156. /package/dist/{chunk-WFWWQO2I.js.map → chunk-F3YRPKUA.js.map} +0 -0
  157. /package/dist/{chunk-FDWGO244.js.map → chunk-LODCKZ72.js.map} +0 -0
  158. /package/dist/{chunk-E2QFZDCP.js.map → chunk-OKD7MX7Y.js.map} +0 -0
  159. /package/dist/{chunk-OW3FUJ2I.js.map → chunk-P24OI2CD.js.map} +0 -0
  160. /package/dist/{chunk-QSO24MEK.js.map → chunk-R2U2D7LU.js.map} +0 -0
  161. /package/dist/{chunk-KQWI25NP.js.map → chunk-SI57FQZ2.js.map} +0 -0
  162. /package/dist/{chunk-JQO47UHC.js.map → chunk-SVR75PFW.js.map} +0 -0
  163. /package/dist/{chunk-OZRT3ZI3.js.map → chunk-V4WUSOGS.js.map} +0 -0
  164. /package/dist/{chunk-UODM3HJC.js.map → chunk-VWNU2PFX.js.map} +0 -0
  165. /package/dist/{chunk-QYDIVL4X.js.map → chunk-YB5DFQKD.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.587-1973.5528.1",
3
+ "version": "0.0.587-2000.5532.1",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -444,7 +444,6 @@
444
444
  "@hookform/resolvers": "~5.2.2",
445
445
  "@storybook/nextjs-vite": "^10.1.11",
446
446
  "@svgr/cli": "^8.1.0",
447
- "@tanstack/react-query": "^5.101.4",
448
447
  "@testing-library/jest-dom": "^6.9.1",
449
448
  "@testing-library/react": "^16.3.1",
450
449
  "@types/node": "^24.10.4",
@@ -329,133 +329,3 @@ describe('decodeNatsChunk — seq envelope', () => {
329
329
  }).toMatchSnapshot();
330
330
  });
331
331
  });
332
-
333
- describe('decodeNatsChunk — Mingo source metadata', () => {
334
- it.each(['GUIDE', 'SOURCES'] as const)('decodes %s payloads into safe sources and playable refs', type => {
335
- expect(
336
- decodeNatsChunk({
337
- type,
338
- streamSeq: 81,
339
- payload: {
340
- sources: [
341
- {
342
- index: 1,
343
- name: 'Install agent',
344
- path: 'docs/install.md',
345
- documentType: 'markdown',
346
- externalUrl: 'https://docs.example.test/install',
347
- },
348
- {
349
- index: 2,
350
- name: 'Unsafe source',
351
- path: 'docs/unsafe.md',
352
- documentType: 'markdown',
353
- externalUrl: 'javascript:alert(1)',
354
- },
355
- {
356
- index: 1,
357
- name: 'Duplicate',
358
- path: 'docs/duplicate.md',
359
- documentType: 'markdown',
360
- },
361
- { index: 3, name: '', path: 'docs/empty.md', documentType: 'markdown' },
362
- ],
363
- videos: [
364
- {
365
- ref: '[card://video:MdFJNoJeqZQ]',
366
- title: 'Install the agent',
367
- url: 'https://www.youtube.com/watch?v=MdFJNoJeqZQ',
368
- sourceRepo: 'embedded-videos',
369
- metadata: { youtubeUrl: 'MdFJNoJeqZQ' },
370
- },
371
- {
372
- ref: '[card://video:mux-9b6586b494]',
373
- title: 'Community demo',
374
- url: 'https://stream.mux.com/playback-id.m3u8',
375
- metadata: { videoUrl: 'javascript:alert(1)' },
376
- },
377
- {
378
- ref: '[card://video:mp4-1b2047dc1b]',
379
- title: 'Product overview',
380
- url: 'https://cdn.example.test/product-overview.mp4',
381
- metadata: { videoUrl: 'https://cdn.example.test/product-overview.mp4' },
382
- },
383
- {
384
- ref: '[card://video:MdFJNoJeqZQ]',
385
- title: 'Duplicate video',
386
- url: 'https://cdn.example.test/duplicate.mp4',
387
- },
388
- ],
389
- cards: [
390
- {
391
- ref: '[card://markdown:install-agent]',
392
- entityType: 'markdown',
393
- entityId: 'install-agent',
394
- },
395
- ],
396
- },
397
- }),
398
- ).toEqual({
399
- type: 'sources',
400
- seq: 81,
401
- sources: [
402
- {
403
- index: 1,
404
- name: 'Install agent',
405
- path: 'docs/install.md',
406
- documentType: 'markdown',
407
- externalUrl: 'https://docs.example.test/install',
408
- },
409
- {
410
- index: 2,
411
- name: 'Unsafe source',
412
- path: 'docs/unsafe.md',
413
- documentType: 'markdown',
414
- },
415
- ],
416
- refs: [
417
- {
418
- type: 'video',
419
- id: 'MdFJNoJeqZQ',
420
- title: 'Install the agent',
421
- url: 'https://www.youtube.com/watch?v=MdFJNoJeqZQ',
422
- sourceRepo: 'embedded-videos',
423
- metadata: { youtubeUrl: 'MdFJNoJeqZQ' },
424
- },
425
- {
426
- type: 'video',
427
- id: 'mux-9b6586b494',
428
- title: 'Community demo',
429
- url: 'https://stream.mux.com/playback-id.m3u8',
430
- metadata: { videoUrl: 'https://stream.mux.com/playback-id.m3u8' },
431
- },
432
- {
433
- type: 'video',
434
- id: 'mp4-1b2047dc1b',
435
- title: 'Product overview',
436
- url: 'https://cdn.example.test/product-overview.mp4',
437
- metadata: { videoUrl: 'https://cdn.example.test/product-overview.mp4' },
438
- },
439
- {
440
- type: 'markdown',
441
- id: 'install-agent',
442
- title: 'install-agent',
443
- url: null,
444
- },
445
- ],
446
- });
447
- });
448
-
449
- it('drops a metadata payload with no valid sources or refs', () => {
450
- expect(
451
- decodeNatsChunk({
452
- type: 'SOURCES',
453
- payload: {
454
- sources: [{ index: 0, name: 'Invalid', path: '', documentType: '' }],
455
- videos: [{ ref: 'https://example.test/video' }],
456
- cards: [{ ref: '[CARD://blog:wrong-case]' }],
457
- },
458
- }),
459
- ).toBeNull();
460
- });
461
- });
@@ -12,8 +12,7 @@
12
12
  // rows straight to the accumulator, so restating them here would be two
13
13
  // declarations of one wire contract. Type-only import from a React-free
14
14
  // module; `nats-decoder.ts` already depends on the same file for MESSAGE_TYPE.
15
- import type { ChatRef } from '../components/chat/chat-ref.types';
16
- import type { AskOptionData, ChatSource } from '../components/chat/types/message.types';
15
+ import type { AskOptionData } from '../components/chat/types/message.types';
17
16
  // The wire-frame shapes these events carry through are defined ONCE in
18
17
  // `./frames.ts` — reuse them here rather than restating their fields.
19
18
  import type { ApprovalRequestField, DecisionResolvedFrame, UsageTelemetry } from './frames';
@@ -241,12 +240,6 @@ export interface ChatMetadataEvent extends ChatStreamEventBase {
241
240
  };
242
241
  }
243
242
 
244
- export interface SourcesEvent extends ChatStreamEventBase {
245
- type: 'sources';
246
- sources: ChatSource[];
247
- refs?: ChatRef[];
248
- }
249
-
250
243
  /** SSE usage frames — raw wire keys (snake_case) preserved. */
251
244
  export interface UsageEvent extends ChatStreamEventBase {
252
245
  type: 'usage';
@@ -369,7 +362,6 @@ export type ChatStreamEvent =
369
362
  | TicketEscalatedEvent
370
363
  | TicketEventEvent
371
364
  | ChatMetadataEvent
372
- | SourcesEvent
373
365
  | UsageEvent
374
366
  | TokenUsageEvent
375
367
  | CompactionEvent
@@ -20,7 +20,6 @@ import { MESSAGE_TYPE } from '../components/chat/types/message.types';
20
20
  import type { AskOptionData } from '../components/chat/types/message.types';
21
21
  import { ESCALATION_STATE, escalationResolvedStatus } from './events';
22
22
  import type { ApprovalToolCall, ChatStreamEvent } from './events';
23
- import { sourceMetadataEvent } from './source-metadata';
24
23
 
25
24
  /** Minimal structural view of a NATS chunk (see `ChunkData` in
26
25
  * `src/components/chat/types/network.types.ts`). */
@@ -126,12 +125,6 @@ export function decodeNatsChunk(chunk: unknown): ChatStreamEvent | null {
126
125
  }
127
126
  return null;
128
127
 
129
- case MESSAGE_TYPE.GUIDE:
130
- case MESSAGE_TYPE.SOURCES: {
131
- const event = sourceMetadataEvent(data.payload);
132
- return event ? { ...event, ...seq } : null;
133
- }
134
-
135
128
  // An ask card is only an ask card with something to pick: a question and at
136
129
  // least one option. Anything less is dropped rather than rendered as an
137
130
  // empty card the user can't answer — the backend already falls back to a
@@ -146,76 +146,6 @@ describe('ChatMessageList force-scroll decisions', () => {
146
146
  });
147
147
  });
148
148
 
149
- describe('ChatMessageList message footers', () => {
150
- const renderSourceFooter = (message: Message) =>
151
- message.sources?.map(source => <span key={`${message.id}-${source.index}`}>{source.name}</span>);
152
-
153
- it('keeps a sourced assistant footer after a later plain assistant message', () => {
154
- const sourcedAssistant: Message = {
155
- ...msg('a1', 'assistant'),
156
- sources: [
157
- {
158
- index: 1,
159
- name: 'Windows installation guide',
160
- path: '/docs/windows',
161
- documentType: 'openframe-docs',
162
- },
163
- ],
164
- };
165
- const plainAssistant = msg('a2', 'assistant');
166
-
167
- render(
168
- <ChatMessageList
169
- dialogId="d1"
170
- messages={[sourcedAssistant, plainAssistant]}
171
- renderAfterMessage={renderSourceFooter}
172
- />,
173
- );
174
-
175
- const sourceFooter = screen.getByText('Windows installation guide');
176
- expect(sourceFooter).toBeInTheDocument();
177
- expect(screen.getByText('body of a1')).toAppearBefore(sourceFooter);
178
- });
179
-
180
- it('renders separate source names when citation indices restart on each turn', () => {
181
- const firstAssistant: Message = {
182
- ...msg('a1', 'assistant'),
183
- sources: [
184
- {
185
- index: 1,
186
- name: 'First turn source',
187
- path: '/docs/first',
188
- documentType: 'openframe-docs',
189
- },
190
- ],
191
- };
192
- const secondAssistant: Message = {
193
- ...msg('a2', 'assistant'),
194
- sources: [
195
- {
196
- index: 1,
197
- name: 'Second turn source',
198
- path: '/docs/second',
199
- documentType: 'openframe-docs',
200
- },
201
- ],
202
- };
203
-
204
- render(
205
- <ChatMessageList
206
- dialogId="d1"
207
- messages={[firstAssistant, secondAssistant]}
208
- renderAfterMessage={renderSourceFooter}
209
- />,
210
- );
211
-
212
- const firstSourceFooter = screen.getByText('First turn source');
213
- const secondSourceFooter = screen.getByText('Second turn source');
214
- expect(screen.getByText('body of a1')).toAppearBefore(firstSourceFooter);
215
- expect(screen.getByText('body of a2')).toAppearBefore(secondSourceFooter);
216
- });
217
- });
218
-
219
149
  // The library's own `isAtBottom` lock is lost silently when the scroller's
220
150
  // box changes (source chips mounting below it) or when a card settling out
221
151
  // of its skeleton produces a resize-driven scroll the library reads as a
@@ -20,7 +20,6 @@
20
20
  import { render } from '@testing-library/react';
21
21
  import type React from 'react';
22
22
  import { describe, it, expect, vi } from 'vitest';
23
- import type { ChatRef } from '../chat-ref.types';
24
23
  import type { MessageSegment } from '../types';
25
24
 
26
25
  /** Every `componentOverrides` / `additionalRemarkPlugins` identity the
@@ -130,89 +129,4 @@ describe('ChatMessageEnhanced — override identity across a streamed turn', ()
130
129
  expect(seenRefs).toEqual([{ type: 'blog', id: 'abc' }]);
131
130
  expect(view.container.textContent).toContain('card:abc');
132
131
  });
133
-
134
- it('enriches exact video markers with YouTube, hashed Mux, and hashed MP4 metadata', () => {
135
- const seenRefs: Array<{ id: string; metadata?: Record<string, unknown> }> = [];
136
- const refs: ChatRef[] = [
137
- {
138
- type: 'video',
139
- id: 'MdFJNoJeqZQ',
140
- title: 'Install the agent',
141
- url: 'https://www.youtube.com/watch?v=MdFJNoJeqZQ',
142
- metadata: { youtubeUrl: 'MdFJNoJeqZQ' },
143
- },
144
- {
145
- type: 'video',
146
- id: 'mux-9b6586b494',
147
- title: 'Community demo',
148
- url: 'https://stream.mux.com/playback-id.m3u8',
149
- metadata: { videoUrl: 'https://stream.mux.com/playback-id.m3u8' },
150
- },
151
- {
152
- type: 'video',
153
- id: 'mp4-1b2047dc1b',
154
- title: 'Product overview',
155
- url: 'https://cdn.example.test/product-overview.mp4',
156
- metadata: { videoUrl: 'https://cdn.example.test/product-overview.mp4' },
157
- },
158
- ];
159
-
160
- render(
161
- <ChatMessageEnhanced
162
- role="assistant"
163
- content={[
164
- {
165
- type: 'text',
166
- text: 'Watch [card://video:MdFJNoJeqZQ] [card://video:mux-9b6586b494] [card://video:mp4-1b2047dc1b].',
167
- },
168
- ]}
169
- refs={refs}
170
- renderEntityCard={ref => {
171
- seenRefs.push({ id: ref.id, metadata: ref.metadata });
172
- return <div data-testid={`card-${ref.id}`}>{ref.title}</div>;
173
- }}
174
- NavLinkAnchor={NavLinkAnchor}
175
- />,
176
- );
177
-
178
- expect(seenRefs).toEqual([
179
- { id: 'MdFJNoJeqZQ', metadata: { youtubeUrl: 'MdFJNoJeqZQ' } },
180
- { id: 'mux-9b6586b494', metadata: { videoUrl: 'https://stream.mux.com/playback-id.m3u8' } },
181
- { id: 'mp4-1b2047dc1b', metadata: { videoUrl: 'https://cdn.example.test/product-overview.mp4' } },
182
- ]);
183
- });
184
-
185
- it('rerenders a completed marker when its enriched metadata arrives later', () => {
186
- const seenMetadata: Array<Record<string, unknown> | undefined> = [];
187
- const content: MessageSegment[] = [{ type: 'text', text: 'Watch [card://video:mux-9b6586b494].' }];
188
- const props = {
189
- role: 'assistant' as const,
190
- content,
191
- renderEntityCard: (ref: ChatRef) => {
192
- seenMetadata.push(ref.metadata);
193
- return <div>video</div>;
194
- },
195
- NavLinkAnchor,
196
- };
197
- const view = render(<ChatMessageEnhanced {...props} />);
198
-
199
- view.rerender(
200
- <ChatMessageEnhanced
201
- {...props}
202
- refs={[
203
- {
204
- type: 'video',
205
- id: 'mux-9b6586b494',
206
- title: 'Community demo',
207
- url: 'https://stream.mux.com/playback-id.m3u8',
208
- metadata: { videoUrl: 'https://stream.mux.com/playback-id.m3u8' },
209
- },
210
- ]}
211
- />,
212
- );
213
-
214
- expect(seenMetadata[seenMetadata.length - 1]).toEqual({
215
- videoUrl: 'https://stream.mux.com/playback-id.m3u8',
216
- });
217
- });
218
132
  });
@@ -103,7 +103,6 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
103
103
  renderContextItem,
104
104
  renderMention,
105
105
  renderEntityCard,
106
- refs,
107
106
  onAskSelect,
108
107
  NavLinkAnchor,
109
108
  ...props
@@ -128,11 +127,6 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
128
127
  // literal `[card://...]` URL.
129
128
  const hasMarkerSupport = !!renderEntityCard;
130
129
 
131
- const refsByKey = useMemo(
132
- () => new Map((refs ?? []).map(reference => [`${reference.type}:${reference.id}`, reference])),
133
- [refs],
134
- );
135
-
136
130
  const segments = useMemo(() => normalizeContent(content), [content]);
137
131
 
138
132
  const askRuns = useMemo(() => groupAskRuns(segments), [segments]);
@@ -186,14 +180,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
186
180
  // (and its open menu) survives across chunks. Invalidated per key when the
187
181
  // render fn identity changes.
188
182
  const renderedCardNodeCache = useRef(
189
- new Map<
190
- string,
191
- {
192
- render: ((ref: ChatRef) => React.ReactNode) | undefined;
193
- reference: ChatRef;
194
- node: React.ReactNode;
195
- }
196
- >(),
183
+ new Map<string, { render: ((ref: ChatRef) => React.ReactNode) | undefined; node: React.ReactNode }>(),
197
184
  );
198
185
 
199
186
  /**
@@ -255,17 +242,10 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
255
242
  // menu / re-fetching) on every stream chunk. Also dedups the same
256
243
  // key emitted twice within one message.
257
244
  let entry = cache.get(key);
258
- const enrichedRef = refsByKey.get(key);
259
- const refForRender: ChatRef = enrichedRef ?? {
260
- type: cardType,
261
- id: cardId,
262
- title: cardId,
263
- url: null,
264
- };
265
- if (!entry || entry.render !== render || entry.reference !== refForRender) {
266
- // Prefer same-turn SOURCES metadata when available. Otherwise
267
- // cards hydrate by id from the host's per-object APIs, so a
268
- // minimal {type, id} descriptor still mounts the loader.
245
+ if (!entry || entry.render !== render) {
246
+ // The marker is the ONLY data on the wire: cards hydrate by id
247
+ // from the host's per-object APIs, so a minimal {type, id}
248
+ // descriptor is all the renderer needs to mount the loader.
269
249
  // For types with nothing to fetch the host's render() returns
270
250
  // null and we fall through to the bare-cardId fallback in the
271
251
  // `<a card://…>` override below.
@@ -277,7 +257,13 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
277
257
  // the id rather than `undefined`) and `url` to null (matches
278
258
  // the no-link semantics fetch-mode cards rely on — they
279
259
  // resolve their own URL after fetch).
280
- entry = { render, reference: refForRender, node: render(refForRender) };
260
+ const refForRender: ChatRef = {
261
+ type: cardType,
262
+ id: cardId,
263
+ title: cardId,
264
+ url: null,
265
+ };
266
+ entry = { render, node: render(refForRender) };
281
267
  cache.set(key, entry);
282
268
  }
283
269
  const rendered = entry.node;
@@ -340,7 +326,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
340
326
  }
341
327
  });
342
328
  return { inlineByKey, partsBySegment, usedKeys };
343
- }, [hasMarkerSupport, refsByKey, renderEntityCard, segments]);
329
+ }, [hasMarkerSupport, renderEntityCard, segments]);
344
330
 
345
331
  // Drop cached nodes for markers no longer present so the cache can't grow
346
332
  // unbounded as a long message's markers change. Deliberately an EFFECT,
@@ -730,7 +716,6 @@ const MemoizedChatMessageEnhanced = memo(ChatMessageEnhanced, (prevProps, nextPr
730
716
  // equality holds across streaming chunks.
731
717
  prevProps.renderMention === nextProps.renderMention &&
732
718
  prevProps.renderEntityCard === nextProps.renderEntityCard &&
733
- prevProps.refs === nextProps.refs &&
734
719
  // Same stability contract as the renderers above: hosts pass a `useCallback`
735
720
  // (EmbeddableChat passes its memoized `handleSend`), so this holds across
736
721
  // streaming chunks instead of re-rendering every ask card per chunk.
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import {
4
- Fragment,
5
4
  useRef,
6
5
  useState,
7
6
  useCallback,
@@ -139,7 +138,6 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
139
138
  renderContextItem,
140
139
  renderMention,
141
140
  onAskSelect,
142
- renderAfterMessage,
143
141
  NavLinkAnchor,
144
142
  ...props
145
143
  },
@@ -988,31 +986,28 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
988
986
  (!Array.isArray(message.content) || message.content.every(s => s.type === 'text'));
989
987
  if (isEmptyPendingTurn) return null;
990
988
  return (
991
- <Fragment key={message.id}>
992
- <ChatMessageEnhanced
993
- ref={getRegisterMessageEl(message.id)}
994
- role={message.role}
995
- name={message.name}
996
- content={message.content}
997
- timestamp={message.timestamp}
998
- isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
999
- avatar={showAvatars ? message.avatar : null}
1000
- showAvatar={showAvatars}
1001
- assistantType={message.assistantType || assistantType}
1002
- approvalVariant={approvalVariant}
1003
- authorType={message.authorType}
1004
- assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
1005
- contextItems={message.contextItems}
1006
- resolveContextIcon={resolveContextIcon}
1007
- renderContextItem={renderContextItem}
1008
- renderMention={renderMention}
1009
- renderEntityCard={renderEntityCard}
1010
- refs={message.refs}
1011
- onAskSelect={index > lastUserMessageIndex ? onAskSelect : undefined}
1012
- NavLinkAnchor={NavLinkAnchor}
1013
- />
1014
- {renderAfterMessage?.(message, index)}
1015
- </Fragment>
989
+ <ChatMessageEnhanced
990
+ key={message.id}
991
+ ref={getRegisterMessageEl(message.id)}
992
+ role={message.role}
993
+ name={message.name}
994
+ content={message.content}
995
+ timestamp={message.timestamp}
996
+ isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
997
+ avatar={showAvatars ? message.avatar : null}
998
+ showAvatar={showAvatars}
999
+ assistantType={message.assistantType || assistantType}
1000
+ approvalVariant={approvalVariant}
1001
+ authorType={message.authorType}
1002
+ assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
1003
+ contextItems={message.contextItems}
1004
+ resolveContextIcon={resolveContextIcon}
1005
+ renderContextItem={renderContextItem}
1006
+ renderMention={renderMention}
1007
+ renderEntityCard={renderEntityCard}
1008
+ onAskSelect={index > lastUserMessageIndex ? onAskSelect : undefined}
1009
+ NavLinkAnchor={NavLinkAnchor}
1010
+ />
1016
1011
  );
1017
1012
  })}
1018
1013
  </div>