@elabs-ai/components-ai 5.4.0 → 5.5.0

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 (112) hide show
  1. package/README.md +4 -6
  2. package/dist/_lazy-engine-boundary.js +1 -1
  3. package/dist/_persona-rive.js.map +1 -1
  4. package/dist/_streamdown-i18n.js +2 -2
  5. package/dist/agent.js +2 -2
  6. package/dist/asset-preview.js +5 -5
  7. package/dist/attachments.js +1 -1
  8. package/dist/audio-player.js +1 -2
  9. package/dist/{chunk-7RL6KZDF.js → chunk-24M6XWVJ.js} +25 -17
  10. package/dist/chunk-24M6XWVJ.js.map +1 -0
  11. package/dist/{chunk-VJZJWNEC.js → chunk-3KXIWGHW.js} +3 -3
  12. package/dist/{chunk-TWPHAJNM.js → chunk-3UHR3RLZ.js} +12 -6
  13. package/dist/chunk-3UHR3RLZ.js.map +1 -0
  14. package/dist/{chunk-V4DORGWJ.js → chunk-5AMSNG2T.js} +2 -2
  15. package/dist/chunk-D7BZMRI3.js +2 -0
  16. package/dist/chunk-D7BZMRI3.js.map +1 -0
  17. package/dist/{chunk-PQBF6ZIQ.js → chunk-DI645WR4.js} +4 -4
  18. package/dist/{chunk-NCL2UZKI.js → chunk-FFA7OPEH.js} +2 -2
  19. package/dist/{chunk-34CQ2VHM.js → chunk-FO44KY7Y.js} +10 -7
  20. package/dist/chunk-FO44KY7Y.js.map +1 -0
  21. package/dist/{chunk-RB7G65JC.js → chunk-GP7ZZCID.js} +7 -5
  22. package/dist/chunk-GP7ZZCID.js.map +1 -0
  23. package/dist/{chunk-6PB2MXCI.js → chunk-HLODND65.js} +2 -2
  24. package/dist/{chunk-ADOYAGGK.js → chunk-IFVO7LQJ.js} +2 -2
  25. package/dist/{chunk-2XOIG5TG.js → chunk-JSEV46SH.js} +18 -12
  26. package/dist/chunk-JSEV46SH.js.map +1 -0
  27. package/dist/{chunk-CZP3RHC4.js → chunk-LY7O4BMP.js} +2 -2
  28. package/dist/{chunk-AOOIAFDT.js → chunk-OCZNA5SO.js} +2 -2
  29. package/dist/{chunk-S3ISPW3A.js → chunk-PWQPVOZJ.js} +1 -1
  30. package/dist/chunk-PWQPVOZJ.js.map +1 -0
  31. package/dist/chunk-RLV64EWK.js +41 -0
  32. package/dist/chunk-RLV64EWK.js.map +1 -0
  33. package/dist/chunk-SSES3F4Q.js +126 -0
  34. package/dist/chunk-SSES3F4Q.js.map +1 -0
  35. package/dist/{chunk-3XNPMDZY.js → chunk-TBCMCF5O.js} +9 -56
  36. package/dist/chunk-TBCMCF5O.js.map +1 -0
  37. package/dist/{chunk-UHLVPAHG.js → chunk-WXUXFDJQ.js} +2 -2
  38. package/dist/{chunk-X47QMM6K.js → chunk-YLN7E4OV.js} +2 -2
  39. package/dist/code-block.js +1 -1
  40. package/dist/diff-view.js +2 -2
  41. package/dist/gallery.js +1 -1
  42. package/dist/generated-image.js +9 -0
  43. package/dist/generated-image.js.map +1 -0
  44. package/dist/grouped-parts.js +6 -6
  45. package/dist/image.js +4 -4
  46. package/dist/index.d.ts +92 -150
  47. package/dist/index.js +67 -65
  48. package/dist/markdown-view.js +3 -3
  49. package/dist/message.js +3 -3
  50. package/dist/model-provider-logo.js +1 -1
  51. package/dist/persona.js +2 -2
  52. package/dist/queue.js +1 -1
  53. package/dist/reasoning.js +3 -3
  54. package/dist/sandbox.js +3 -3
  55. package/dist/tool.js +2 -2
  56. package/package.json +8 -13
  57. package/schemas/a2ui-surface.v1.schema.json +1 -1
  58. package/src/__contract__/{image.contract.test.tsx → attachments.contract.test.tsx} +7 -7
  59. package/src/__contract__/generated-image.contract.test.tsx +49 -0
  60. package/src/_lazy-boundary-conformance.ts +12 -18
  61. package/src/_lazy-engine-boundary.tsx +1 -2
  62. package/src/_lazy-mermaid-absent.test.ts +2 -2
  63. package/src/_media-fixtures.ts +47 -0
  64. package/src/_persona-rive.tsx +1 -2
  65. package/src/a2ui/catalog.source.json +1 -1
  66. package/src/asset-preview.stories.tsx +12 -0
  67. package/src/asset-preview.test.tsx +14 -0
  68. package/src/asset-preview.tsx +4 -2
  69. package/src/attachments.stories.tsx +147 -0
  70. package/src/attachments.test.tsx +104 -0
  71. package/src/attachments.tsx +28 -20
  72. package/src/audio-player.stories.tsx +10 -45
  73. package/src/audio-player.test.tsx +166 -61
  74. package/src/audio-player.tsx +175 -266
  75. package/src/chat.stories.tsx +75 -3
  76. package/src/code-block.test.tsx +32 -0
  77. package/src/code-block.tsx +22 -5
  78. package/src/gallery.tsx +13 -77
  79. package/src/generated-image.stories.tsx +67 -0
  80. package/src/generated-image.test.tsx +107 -0
  81. package/src/generated-image.tsx +57 -0
  82. package/src/image.tsx +2 -115
  83. package/src/index.ts +1 -0
  84. package/src/model-provider-logo.tsx +20 -18
  85. package/src/queue.test.tsx +24 -0
  86. package/src/queue.tsx +8 -5
  87. package/dist/_audio-player-media-chrome.js +0 -85
  88. package/dist/_audio-player-media-chrome.js.map +0 -1
  89. package/dist/chunk-2XOIG5TG.js.map +0 -1
  90. package/dist/chunk-34CQ2VHM.js.map +0 -1
  91. package/dist/chunk-3XNPMDZY.js.map +0 -1
  92. package/dist/chunk-7RL6KZDF.js.map +0 -1
  93. package/dist/chunk-NSCLRHES.js +0 -102
  94. package/dist/chunk-NSCLRHES.js.map +0 -1
  95. package/dist/chunk-RB7G65JC.js.map +0 -1
  96. package/dist/chunk-S3ISPW3A.js.map +0 -1
  97. package/dist/chunk-TWPHAJNM.js.map +0 -1
  98. package/dist/chunk-V6E5DYZD.js +0 -80
  99. package/dist/chunk-V6E5DYZD.js.map +0 -1
  100. package/src/_audio-player-media-chrome.tsx +0 -233
  101. package/src/image.stories.tsx +0 -67
  102. package/src/image.test.tsx +0 -105
  103. /package/dist/{chunk-VJZJWNEC.js.map → chunk-3KXIWGHW.js.map} +0 -0
  104. /package/dist/{chunk-V4DORGWJ.js.map → chunk-5AMSNG2T.js.map} +0 -0
  105. /package/dist/{chunk-PQBF6ZIQ.js.map → chunk-DI645WR4.js.map} +0 -0
  106. /package/dist/{chunk-NCL2UZKI.js.map → chunk-FFA7OPEH.js.map} +0 -0
  107. /package/dist/{chunk-6PB2MXCI.js.map → chunk-HLODND65.js.map} +0 -0
  108. /package/dist/{chunk-ADOYAGGK.js.map → chunk-IFVO7LQJ.js.map} +0 -0
  109. /package/dist/{chunk-CZP3RHC4.js.map → chunk-LY7O4BMP.js.map} +0 -0
  110. /package/dist/{chunk-AOOIAFDT.js.map → chunk-OCZNA5SO.js.map} +0 -0
  111. /package/dist/{chunk-UHLVPAHG.js.map → chunk-WXUXFDJQ.js.map} +0 -0
  112. /package/dist/{chunk-X47QMM6K.js.map → chunk-YLN7E4OV.js.map} +0 -0
package/README.md CHANGED
@@ -40,11 +40,11 @@ import { ThemeProvider } from "@elabs-ai/components-tokens";
40
40
 
41
41
  - `ai` (Vercel AI SDK) is a **types-only, optional** peer — your app owns the model calls.
42
42
  - `@xyflow/react` is a peer too (required), if you render the agent canvas.
43
- - `mermaid`, `@rive-app/react-webgl2`, `@xterm/xterm` + `@xterm/addon-fit` and `media-chrome` are optional peers reached only through a lazy `import()` (ADR 0019) — see "Only install what you render" below.
43
+ - `mermaid`, `@rive-app/react-webgl2` and `@xterm/xterm` + `@xterm/addon-fit` are optional peers reached only through a lazy `import()` (ADR 0019) — see "Only install what you render" below.
44
44
 
45
45
  ## What's in it
46
46
 
47
- 448 exported components — including `A2UI_CATALOG_SCHEMA`, `A2UI_CATALOG_VERSION`, `A2UI_COMMON_PROPS`, `A2UI_SURFACE_SCHEMA_ID`, `A2UI_VERSION`.
47
+ 449 exported components — including `A2UI_CATALOG_SCHEMA`, `A2UI_CATALOG_VERSION`, `A2UI_COMMON_PROPS`, `A2UI_SURFACE_SCHEMA_ID`, `A2UI_VERSION`.
48
48
 
49
49
  Don't guess the API — ask the CLI:
50
50
 
@@ -82,7 +82,7 @@ MIT
82
82
 
83
83
  ## Only install what you render
84
84
 
85
- Six peers are optional. The package installs and builds with none of them —
85
+ Five peers are optional. The package installs and builds with none of them —
86
86
  each is reached only through a lazy `import()` (ADR 0019), and a feature whose
87
87
  peer is absent renders an actionable message naming the package to install,
88
88
  never a blank component or an unhandled rejection (ADR
@@ -93,14 +93,12 @@ never a blank component or an unhandled rejection (ADR
93
93
  | `mermaid` | Mermaid diagrams in streamed markdown |
94
94
  | `@rive-app/react-webgl2` | `Persona`'s animated avatar |
95
95
  | `@xterm/xterm` + `@xterm/addon-fit` | `InteractiveTerminal` |
96
- | `media-chrome` | `AudioPlayer`'s native-feeling controls |
97
96
  | `ai` | Types for the Vercel AI SDK's `UIMessage` — no runtime cost |
98
97
 
99
98
  ```bash
100
99
  pnpm add mermaid
101
100
  pnpm add @rive-app/react-webgl2
102
101
  pnpm add @xterm/xterm @xterm/addon-fit
103
- pnpm add media-chrome
104
102
  pnpm add ai
105
103
  ```
106
104
 
@@ -179,5 +177,5 @@ Heavy engines load on demand rather than riding in your entry chunk:
179
177
  - **The Rive WebGL2 runtime** loads only when a `Persona` mounts.
180
178
 
181
179
  Both are enforced by `pnpm check --rule eager-heavy-deps`. See ADR 0019. The remaining eager
182
- engines (React Flow, xterm, media-chrome) are tracked in that gate's baseline
180
+ engines (React Flow, xterm) are tracked in that gate's baseline
183
181
  (the React Flow canvas set is a separate, intentional surface — see ADR 0018).
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
  import {
4
4
  LazyEngineBoundary
5
- } from "./chunk-S3ISPW3A.js";
5
+ } from "./chunk-PWQPVOZJ.js";
6
6
  export {
7
7
  LazyEngineBoundary
8
8
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/_persona-rive.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The Rive half of `Persona`, split out so it can be `lazy()`-loaded.\n *\n * `@rive-app/react-webgl2` declares no `sideEffects`, so a static import from\n * `persona.tsx` put the whole WebGL2 runtime (and its `rive.wasm`) into the entry\n * chunk of every `@elabs-ai/components-ai` consumer — including the vast majority that never\n * render a `Persona`. Keeping every Rive *value* import in this module, reached\n * only through `lazy(() => import(\"./_persona-rive\"))`, confines it to its own\n * chunk. `persona.tsx` may still `import type` from Rive (types erase).\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put Rive back in the\n * entry chunk and make the `lazy()` pointless.\n */\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport type { RiveParameters } from \"@rive-app/react-webgl2\";\n// `import type` from an optional peer is safe HERE ONLY because this module is\n// reached exclusively through `lazy(() => import(\"./_persona-rive\"))` — it is\n// never statically imported, so nothing in this file reaches the barrel's\n// `.d.ts` declaration graph. `persona.tsx`'s PUBLIC types must never do this\n// (issue #101) — see its module doc comment and `PersonaRiveEvent`/\n// `PersonaRiveEventCallback`, which this module imports below and proves\n// conformant against the real peer type at the bottom of this file.\n// A NAMED import (`import { useRive } from \"@rive-app/react-webgl2\"`) is a\n// static ESM binding a bundler must resolve at build time. Now that\n// `@rive-app/react-webgl2` is a genuinely optional peer (issue #33), a\n// consumer who has not installed it hits that resolution at the worst\n// possible time: Vite's own optional-peer-dependency handling swaps in a\n// build-time stub with no exports, and Rollup's static named-export check\n// then fails the WHOLE APP BUILD — not a runtime error `LazyEngineBoundary`\n// could ever see (confirmed against `fixtures/consumer-smoke`'s real Vite\n// build). A namespace import defers every one of these to a plain property\n// lookup, which Rollup does not statically validate, so the build always\n// succeeds; a genuinely missing peer instead resolves the destructured hooks\n// below to `undefined`, and the guard turns that into the render-phase throw\n// `LazyEngineBoundary` (see `persona.tsx`) already catches.\nimport * as RiveModule from \"@rive-app/react-webgl2\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { AssertAssignable } from \"./_lazy-boundary-conformance\";\nimport type { PersonaRiveEventCallback } from \"./persona\";\nimport type { PersonaSource, PersonaState } from \"./persona-sources\";\n\nconst {\n useRive,\n useStateMachineInput,\n useViewModel,\n useViewModelInstance,\n useViewModelInstanceColor,\n} = RiveModule;\n\nif (!useRive) {\n throw new Error(\"Cannot find module '@rive-app/react-webgl2'\");\n}\n\n// Delays Rive initialization by one frame so that React Strict Mode's\n// immediate unmount cycle never creates a WebGL2 context. Only the\n// second (real) mount will initialise, avoiding context exhaustion.\nconst useStrictModeSafeInit = () => {\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => setReady(true));\n return () => {\n cancelAnimationFrame(id);\n setReady(false);\n };\n }, []);\n\n return ready;\n};\n\n// The state machine name is always 'default' for Elements AI visuals\nconst stateMachine = \"default\";\n\nconst getCurrentTheme = (): \"light\" | \"dark\" => {\n if (typeof window !== \"undefined\") {\n if (document.documentElement.classList.contains(\"dark\")) {\n return \"dark\";\n }\n if (window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches) {\n return \"dark\";\n }\n }\n return \"light\";\n};\n\nconst useTheme = (enabled: boolean) => {\n const [theme, setTheme] = useState<\"light\" | \"dark\">(getCurrentTheme);\n\n useEffect(() => {\n // Skip if not enabled (avoids unnecessary observers for non-dynamic-color variants)\n if (!enabled) {\n return;\n }\n\n // Watch for classList changes\n const observer = new MutationObserver(() => {\n setTheme(getCurrentTheme());\n });\n\n observer.observe(document.documentElement, {\n attributeFilter: [\"class\"],\n attributes: true,\n });\n\n // Watch for OS-level theme changes\n let mql: MediaQueryList | null = null;\n const handleMediaChange = () => {\n setTheme(getCurrentTheme());\n };\n\n if (window.matchMedia) {\n mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n mql.addEventListener(\"change\", handleMediaChange);\n }\n\n return () => {\n observer.disconnect();\n if (mql) {\n mql.removeEventListener(\"change\", handleMediaChange);\n }\n };\n }, [enabled]);\n\n return theme;\n};\n\nexport interface PersonaRiveProps {\n className?: string;\n onLoad?: PersonaRiveEventCallback;\n onLoadError?: PersonaRiveEventCallback;\n onPause?: PersonaRiveEventCallback;\n onPlay?: PersonaRiveEventCallback;\n onReady?: () => void;\n onStop?: PersonaRiveEventCallback;\n source: PersonaSource;\n /** Resolved `.riv` URL — `Persona`'s `src` override, or the variant default. */\n src: string;\n state: PersonaState;\n}\n\nconst PersonaRive = ({\n className,\n onLoad,\n onLoadError,\n onPause,\n onPlay,\n onReady,\n onStop,\n source,\n src,\n state,\n}: PersonaRiveProps) => {\n // Stabilize callbacks to prevent useRive from reinitializing\n const callbacksRef = useRef({ onLoad, onLoadError, onPause, onPlay, onReady, onStop });\n\n useEffect(() => {\n callbacksRef.current = { onLoad, onLoadError, onPause, onPlay, onReady, onStop };\n }, [onLoad, onLoadError, onPause, onPlay, onReady, onStop]);\n\n const stableCallbacks = useMemo(\n () => ({\n onLoad: ((loadedRive) =>\n callbacksRef.current.onLoad?.(loadedRive)) as RiveParameters[\"onLoad\"],\n onLoadError: ((err) =>\n callbacksRef.current.onLoadError?.(err)) as RiveParameters[\"onLoadError\"],\n onPause: ((event) => callbacksRef.current.onPause?.(event)) as RiveParameters[\"onPause\"],\n onPlay: ((event) => callbacksRef.current.onPlay?.(event)) as RiveParameters[\"onPlay\"],\n onReady: () => callbacksRef.current.onReady?.(),\n onStop: ((event) => callbacksRef.current.onStop?.(event)) as RiveParameters[\"onStop\"],\n }),\n [],\n );\n\n // Delay initialisation by one frame to avoid creating (and leaking)\n // a WebGL2 context during React Strict Mode's first throw-away mount.\n const ready = useStrictModeSafeInit();\n const prefersReducedMotion = useReducedMotion();\n\n const { rive, RiveComponent } = useRive(\n ready\n ? {\n autoplay: !prefersReducedMotion,\n onLoad: stableCallbacks.onLoad,\n onLoadError: stableCallbacks.onLoadError,\n onPause: stableCallbacks.onPause,\n onPlay: stableCallbacks.onPlay,\n onRiveReady: stableCallbacks.onReady,\n onStop: stableCallbacks.onStop,\n src,\n stateMachines: stateMachine,\n }\n : null,\n );\n\n // `autoplay` only governs the INITIAL mount — honor a mid-session\n // reduced-motion preference change by pausing/resuming the running\n // instance. The state-machine inputs below keep updating either way, so\n // the artwork still shows the correct resting pose while paused.\n useEffect(() => {\n if (!rive) return;\n if (prefersReducedMotion) {\n rive.pause();\n } else {\n rive.play();\n }\n }, [rive, prefersReducedMotion]);\n\n const listeningInput = useStateMachineInput(rive, stateMachine, \"listening\");\n const thinkingInput = useStateMachineInput(rive, stateMachine, \"thinking\");\n const speakingInput = useStateMachineInput(rive, stateMachine, \"speaking\");\n const asleepInput = useStateMachineInput(rive, stateMachine, \"asleep\");\n\n // Rive state machine inputs are mutable objects that must be set via direct\n // property assignment — this is the intended Rive API, not a React anti-pattern.\n useEffect(() => {\n if (listeningInput) {\n listeningInput.value = state === \"listening\";\n }\n if (thinkingInput) {\n thinkingInput.value = state === \"thinking\";\n }\n if (speakingInput) {\n speakingInput.value = state === \"speaking\";\n }\n if (asleepInput) {\n asleepInput.value = state === \"asleep\";\n }\n }, [state, listeningInput, thinkingInput, speakingInput, asleepInput]);\n\n // The view-model hooks run here rather than in a child that takes `rive` as\n // a prop. React 19.2's development build logs each re-render's changed props\n // to the browser's performance track by walking every object prop a few\n // levels deep. That walk never finished on a loaded Rive instance: handing\n // one to a component as a prop froze the tab until it ran out of memory, in\n // development builds only. Keep the instance out of props. Each hook accepts\n // `null`, which is how a source without a view model opts out.\n const modelRive = source.hasModel ? rive : null;\n const dynamicColor = source.hasModel && source.dynamicColor;\n const theme = useTheme(dynamicColor);\n const viewModel = useViewModel(modelRive, { useDefault: true });\n const viewModelInstance = useViewModelInstance(viewModel, {\n rive: modelRive,\n useDefault: true,\n });\n const viewModelInstanceColor = useViewModelInstanceColor(\"color\", viewModelInstance);\n\n useEffect(() => {\n if (!(viewModelInstanceColor && dynamicColor)) {\n return;\n }\n\n const [r, g, b] = theme === \"dark\" ? [255, 255, 255] : [0, 0, 0];\n viewModelInstanceColor.setRgb(r, g, b);\n }, [viewModelInstanceColor, theme, dynamicColor]);\n\n return <RiveComponent className={cn(\"size-16 shrink-0\", className)} />;\n};\n\nexport default PersonaRive;\n\n/**\n * Conformance assertion (issue #101): compile-time proof that the owned\n * `PersonaRiveEventCallback` (declared in `persona.tsx`, structurally free of\n * `@rive-app/react-webgl2`/`@rive-app/webgl2`) is still assignable to the REAL\n * `RiveParameters[\"onLoad\"]` — and, since `onLoad`/`onLoadError`/`onPause`/\n * `onPlay`/`onStop` all share Rive's one `EventCallback` type, this single\n * check covers all five. `AssertAssignable`'s type parameter is constrained\n * (`TOwned extends TReal`), so if a future Rive release changes `Event`'s\n * shape in a way `PersonaRiveEvent` no longer satisfies, this line fails to\n * typecheck — caught by `pnpm --filter @elabs-ai/components-ai typecheck`\n * locally, never shipped as a silent mismatch to a consumer.\n *\n * `AssertAssignable` itself is shared with `_audio-player-media-chrome.tsx`\n * via `_lazy-boundary-conformance.ts` — see that module's doc comment for\n * what this check can and cannot prove (it is one-directional: it cannot\n * catch the owned type being NARROWER than the real one, which is exactly\n * what `PersonaRiveEvent[\"data\"]` is — see the CHANGELOG's \"Breaking (types)\"\n * entry).\n */\nexport type _PersonaRiveEventCallbackConformance = AssertAssignable<\n PersonaRiveEventCallback,\n NonNullable<RiveParameters[\"onLoad\"]>\n>;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/_persona-rive.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The Rive half of `Persona`, split out so it can be `lazy()`-loaded.\n *\n * `@rive-app/react-webgl2` declares no `sideEffects`, so a static import from\n * `persona.tsx` put the whole WebGL2 runtime (and its `rive.wasm`) into the entry\n * chunk of every `@elabs-ai/components-ai` consumer — including the vast majority that never\n * render a `Persona`. Keeping every Rive *value* import in this module, reached\n * only through `lazy(() => import(\"./_persona-rive\"))`, confines it to its own\n * chunk. `persona.tsx` may still `import type` from Rive (types erase).\n *\n * See ADR 0019 and `pnpm heavy-deps:check`.\n *\n * @lazy-boundary This module must only ever be reached via `import()`. The gate\n * fails if anything imports it statically, which would put Rive back in the\n * entry chunk and make the `lazy()` pointless.\n */\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport type { RiveParameters } from \"@rive-app/react-webgl2\";\n// `import type` from an optional peer is safe HERE ONLY because this module is\n// reached exclusively through `lazy(() => import(\"./_persona-rive\"))` — it is\n// never statically imported, so nothing in this file reaches the barrel's\n// `.d.ts` declaration graph. `persona.tsx`'s PUBLIC types must never do this\n// (issue #101) — see its module doc comment and `PersonaRiveEvent`/\n// `PersonaRiveEventCallback`, which this module imports below and proves\n// conformant against the real peer type at the bottom of this file.\n// A NAMED import (`import { useRive } from \"@rive-app/react-webgl2\"`) is a\n// static ESM binding a bundler must resolve at build time. Now that\n// `@rive-app/react-webgl2` is a genuinely optional peer (issue #33), a\n// consumer who has not installed it hits that resolution at the worst\n// possible time: Vite's own optional-peer-dependency handling swaps in a\n// build-time stub with no exports, and Rollup's static named-export check\n// then fails the WHOLE APP BUILD — not a runtime error `LazyEngineBoundary`\n// could ever see (confirmed against `fixtures/consumer-smoke`'s real Vite\n// build). A namespace import defers every one of these to a plain property\n// lookup, which Rollup does not statically validate, so the build always\n// succeeds; a genuinely missing peer instead resolves the destructured hooks\n// below to `undefined`, and the guard turns that into the render-phase throw\n// `LazyEngineBoundary` (see `persona.tsx`) already catches.\nimport * as RiveModule from \"@rive-app/react-webgl2\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { AssertAssignable } from \"./_lazy-boundary-conformance\";\nimport type { PersonaRiveEventCallback } from \"./persona\";\nimport type { PersonaSource, PersonaState } from \"./persona-sources\";\n\nconst {\n useRive,\n useStateMachineInput,\n useViewModel,\n useViewModelInstance,\n useViewModelInstanceColor,\n} = RiveModule;\n\nif (!useRive) {\n throw new Error(\"Cannot find module '@rive-app/react-webgl2'\");\n}\n\n// Delays Rive initialization by one frame so that React Strict Mode's\n// immediate unmount cycle never creates a WebGL2 context. Only the\n// second (real) mount will initialise, avoiding context exhaustion.\nconst useStrictModeSafeInit = () => {\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => setReady(true));\n return () => {\n cancelAnimationFrame(id);\n setReady(false);\n };\n }, []);\n\n return ready;\n};\n\n// The state machine name is always 'default' for Elements AI visuals\nconst stateMachine = \"default\";\n\nconst getCurrentTheme = (): \"light\" | \"dark\" => {\n if (typeof window !== \"undefined\") {\n if (document.documentElement.classList.contains(\"dark\")) {\n return \"dark\";\n }\n if (window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches) {\n return \"dark\";\n }\n }\n return \"light\";\n};\n\nconst useTheme = (enabled: boolean) => {\n const [theme, setTheme] = useState<\"light\" | \"dark\">(getCurrentTheme);\n\n useEffect(() => {\n // Skip if not enabled (avoids unnecessary observers for non-dynamic-color variants)\n if (!enabled) {\n return;\n }\n\n // Watch for classList changes\n const observer = new MutationObserver(() => {\n setTheme(getCurrentTheme());\n });\n\n observer.observe(document.documentElement, {\n attributeFilter: [\"class\"],\n attributes: true,\n });\n\n // Watch for OS-level theme changes\n let mql: MediaQueryList | null = null;\n const handleMediaChange = () => {\n setTheme(getCurrentTheme());\n };\n\n if (window.matchMedia) {\n mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n mql.addEventListener(\"change\", handleMediaChange);\n }\n\n return () => {\n observer.disconnect();\n if (mql) {\n mql.removeEventListener(\"change\", handleMediaChange);\n }\n };\n }, [enabled]);\n\n return theme;\n};\n\nexport interface PersonaRiveProps {\n className?: string;\n onLoad?: PersonaRiveEventCallback;\n onLoadError?: PersonaRiveEventCallback;\n onPause?: PersonaRiveEventCallback;\n onPlay?: PersonaRiveEventCallback;\n onReady?: () => void;\n onStop?: PersonaRiveEventCallback;\n source: PersonaSource;\n /** Resolved `.riv` URL — `Persona`'s `src` override, or the variant default. */\n src: string;\n state: PersonaState;\n}\n\nconst PersonaRive = ({\n className,\n onLoad,\n onLoadError,\n onPause,\n onPlay,\n onReady,\n onStop,\n source,\n src,\n state,\n}: PersonaRiveProps) => {\n // Stabilize callbacks to prevent useRive from reinitializing\n const callbacksRef = useRef({ onLoad, onLoadError, onPause, onPlay, onReady, onStop });\n\n useEffect(() => {\n callbacksRef.current = { onLoad, onLoadError, onPause, onPlay, onReady, onStop };\n }, [onLoad, onLoadError, onPause, onPlay, onReady, onStop]);\n\n const stableCallbacks = useMemo(\n () => ({\n onLoad: ((loadedRive) =>\n callbacksRef.current.onLoad?.(loadedRive)) as RiveParameters[\"onLoad\"],\n onLoadError: ((err) =>\n callbacksRef.current.onLoadError?.(err)) as RiveParameters[\"onLoadError\"],\n onPause: ((event) => callbacksRef.current.onPause?.(event)) as RiveParameters[\"onPause\"],\n onPlay: ((event) => callbacksRef.current.onPlay?.(event)) as RiveParameters[\"onPlay\"],\n onReady: () => callbacksRef.current.onReady?.(),\n onStop: ((event) => callbacksRef.current.onStop?.(event)) as RiveParameters[\"onStop\"],\n }),\n [],\n );\n\n // Delay initialisation by one frame to avoid creating (and leaking)\n // a WebGL2 context during React Strict Mode's first throw-away mount.\n const ready = useStrictModeSafeInit();\n const prefersReducedMotion = useReducedMotion();\n\n const { rive, RiveComponent } = useRive(\n ready\n ? {\n autoplay: !prefersReducedMotion,\n onLoad: stableCallbacks.onLoad,\n onLoadError: stableCallbacks.onLoadError,\n onPause: stableCallbacks.onPause,\n onPlay: stableCallbacks.onPlay,\n onRiveReady: stableCallbacks.onReady,\n onStop: stableCallbacks.onStop,\n src,\n stateMachines: stateMachine,\n }\n : null,\n );\n\n // `autoplay` only governs the INITIAL mount — honor a mid-session\n // reduced-motion preference change by pausing/resuming the running\n // instance. The state-machine inputs below keep updating either way, so\n // the artwork still shows the correct resting pose while paused.\n useEffect(() => {\n if (!rive) return;\n if (prefersReducedMotion) {\n rive.pause();\n } else {\n rive.play();\n }\n }, [rive, prefersReducedMotion]);\n\n const listeningInput = useStateMachineInput(rive, stateMachine, \"listening\");\n const thinkingInput = useStateMachineInput(rive, stateMachine, \"thinking\");\n const speakingInput = useStateMachineInput(rive, stateMachine, \"speaking\");\n const asleepInput = useStateMachineInput(rive, stateMachine, \"asleep\");\n\n // Rive state machine inputs are mutable objects that must be set via direct\n // property assignment — this is the intended Rive API, not a React anti-pattern.\n useEffect(() => {\n if (listeningInput) {\n listeningInput.value = state === \"listening\";\n }\n if (thinkingInput) {\n thinkingInput.value = state === \"thinking\";\n }\n if (speakingInput) {\n speakingInput.value = state === \"speaking\";\n }\n if (asleepInput) {\n asleepInput.value = state === \"asleep\";\n }\n }, [state, listeningInput, thinkingInput, speakingInput, asleepInput]);\n\n // The view-model hooks run here rather than in a child that takes `rive` as\n // a prop. React 19.2's development build logs each re-render's changed props\n // to the browser's performance track by walking every object prop a few\n // levels deep. That walk never finished on a loaded Rive instance: handing\n // one to a component as a prop froze the tab until it ran out of memory, in\n // development builds only. Keep the instance out of props. Each hook accepts\n // `null`, which is how a source without a view model opts out.\n const modelRive = source.hasModel ? rive : null;\n const dynamicColor = source.hasModel && source.dynamicColor;\n const theme = useTheme(dynamicColor);\n const viewModel = useViewModel(modelRive, { useDefault: true });\n const viewModelInstance = useViewModelInstance(viewModel, {\n rive: modelRive,\n useDefault: true,\n });\n const viewModelInstanceColor = useViewModelInstanceColor(\"color\", viewModelInstance);\n\n useEffect(() => {\n if (!(viewModelInstanceColor && dynamicColor)) {\n return;\n }\n\n const [r, g, b] = theme === \"dark\" ? [255, 255, 255] : [0, 0, 0];\n viewModelInstanceColor.setRgb(r, g, b);\n }, [viewModelInstanceColor, theme, dynamicColor]);\n\n return <RiveComponent className={cn(\"size-16 shrink-0\", className)} />;\n};\n\nexport default PersonaRive;\n\n/**\n * Conformance assertion (issue #101): compile-time proof that the owned\n * `PersonaRiveEventCallback` (declared in `persona.tsx`, structurally free of\n * `@rive-app/react-webgl2`/`@rive-app/webgl2`) is still assignable to the REAL\n * `RiveParameters[\"onLoad\"]` — and, since `onLoad`/`onLoadError`/`onPause`/\n * `onPlay`/`onStop` all share Rive's one `EventCallback` type, this single\n * check covers all five. `AssertAssignable`'s type parameter is constrained\n * (`TOwned extends TReal`), so if a future Rive release changes `Event`'s\n * shape in a way `PersonaRiveEvent` no longer satisfies, this line fails to\n * typecheck — caught by `pnpm --filter @elabs-ai/components-ai typecheck`\n * locally, never shipped as a silent mismatch to a consumer.\n *\n * `AssertAssignable` itself lives in `_lazy-boundary-conformance.ts` — see that module's doc comment for\n * what this check can and cannot prove (it is one-directional: it cannot\n * catch the owned type being NARROWER than the real one, which is exactly\n * what `PersonaRiveEvent[\"data\"]` is — see the CHANGELOG's \"Breaking (types)\"\n * entry).\n */\nexport type _PersonaRiveEventCallbackConformance = AssertAssignable<\n PersonaRiveEventCallback,\n NonNullable<RiveParameters[\"onLoad\"]>\n>;\n"],"mappings":"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","names":[]}
@@ -5,13 +5,13 @@ import {
5
5
  useStreamdownMermaidOptions,
6
6
  useStreamdownPlugins,
7
7
  useStreamdownTranslations
8
- } from "./chunk-PQBF6ZIQ.js";
9
- import "./chunk-VLNBTOH6.js";
8
+ } from "./chunk-DI645WR4.js";
10
9
  import "./chunk-PG7ERDOD.js";
11
10
  import "./chunk-SEVN656N.js";
12
11
  import "./chunk-3TDHPXZZ.js";
13
12
  import "./chunk-SSKZ2AED.js";
14
13
  import "./chunk-QUDZOTRV.js";
14
+ import "./chunk-VLNBTOH6.js";
15
15
  export {
16
16
  getStreamdownPluginsKey,
17
17
  useStreamdownMermaidOptions,
package/dist/agent.js CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  AgentOutput,
9
9
  AgentTool,
10
10
  AgentTools
11
- } from "./chunk-CZP3RHC4.js";
12
- import "./chunk-TWPHAJNM.js";
11
+ } from "./chunk-LY7O4BMP.js";
12
+ import "./chunk-3UHR3RLZ.js";
13
13
  import "./chunk-3YPT3V7L.js";
14
14
  import "./chunk-3TDHPXZZ.js";
15
15
  import "./chunk-SSKZ2AED.js";
@@ -2,20 +2,20 @@
2
2
  "use client";
3
3
  import {
4
4
  AssetPreview
5
- } from "./chunk-RB7G65JC.js";
6
- import "./chunk-V4DORGWJ.js";
5
+ } from "./chunk-GP7ZZCID.js";
6
+ import "./chunk-5AMSNG2T.js";
7
7
  import "./chunk-NPUCMAG2.js";
8
- import "./chunk-TWPHAJNM.js";
8
+ import "./chunk-3UHR3RLZ.js";
9
9
  import "./chunk-3YPT3V7L.js";
10
10
  import "./chunk-DDG5JHQT.js";
11
- import "./chunk-PQBF6ZIQ.js";
12
- import "./chunk-VLNBTOH6.js";
11
+ import "./chunk-DI645WR4.js";
13
12
  import "./chunk-PG7ERDOD.js";
14
13
  import "./chunk-SEVN656N.js";
15
14
  import "./chunk-DZW6LM22.js";
16
15
  import "./chunk-3TDHPXZZ.js";
17
16
  import "./chunk-SSKZ2AED.js";
18
17
  import "./chunk-QUDZOTRV.js";
18
+ import "./chunk-VLNBTOH6.js";
19
19
  export {
20
20
  AssetPreview
21
21
  };
@@ -14,7 +14,7 @@ import {
14
14
  getMediaCategory,
15
15
  useAttachmentContext,
16
16
  useAttachmentsContext
17
- } from "./chunk-7RL6KZDF.js";
17
+ } from "./chunk-24M6XWVJ.js";
18
18
  export {
19
19
  Attachment,
20
20
  AttachmentEmpty,
@@ -12,8 +12,7 @@ import {
12
12
  AudioPlayerTimeDisplay,
13
13
  AudioPlayerTimeRange,
14
14
  AudioPlayerVolumeRange
15
- } from "./chunk-NSCLRHES.js";
16
- import "./chunk-S3ISPW3A.js";
15
+ } from "./chunk-SSES3F4Q.js";
17
16
  export {
18
17
  AudioPlayer,
19
18
  AudioPlayerControlBar,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/attachments.tsx
4
- import { Button } from "@elabs-ai/components-ui";
4
+ import { Button, Image, Video } from "@elabs-ai/components-ui";
5
5
  import { HoverCard, HoverCardContent, HoverCardTrigger } from "@elabs-ai/components-ui";
6
6
  import { cn } from "@elabs-ai/components-ui/lib/cn";
7
7
  import {
@@ -49,23 +49,16 @@ var getAttachmentLabel = (data) => {
49
49
  const category = getMediaCategory(data);
50
50
  return data.filename || (category === "image" ? "Image" : "Attachment");
51
51
  };
52
- var renderAttachmentImage = (url, filename, isGrid) => isGrid ? /* @__PURE__ */ jsx(
53
- "img",
52
+ var renderAttachmentImage = (url, filename, isGrid) => /* @__PURE__ */ jsx(
53
+ Image,
54
54
  {
55
55
  alt: filename || "Image",
56
- className: "size-full object-cover",
57
- height: 96,
56
+ className: isGrid ? "size-full" : "size-full rounded",
57
+ fit: "cover",
58
+ height: isGrid ? 96 : 20,
59
+ showSkeleton: false,
58
60
  src: url,
59
- width: 96
60
- }
61
- ) : /* @__PURE__ */ jsx(
62
- "img",
63
- {
64
- alt: filename || "Image",
65
- className: "size-full rounded object-cover",
66
- height: 20,
67
- src: url,
68
- width: 20
61
+ width: isGrid ? 96 : 20
69
62
  }
70
63
  );
71
64
  var AttachmentsContext = createContext(null);
@@ -94,6 +87,8 @@ var Attachments = ({
94
87
  variant === "grid" && "ms-auto w-fit",
95
88
  className
96
89
  ),
90
+ "data-slot": "attachments",
91
+ "data-variant": variant,
97
92
  ...props,
98
93
  children
99
94
  }
@@ -124,6 +119,7 @@ var Attachment = ({ data, onRemove, className, children, ...props }) => {
124
119
  ],
125
120
  className
126
121
  ),
122
+ "data-slot": "attachment",
127
123
  ...props,
128
124
  children
129
125
  }
@@ -142,7 +138,19 @@ var AttachmentPreview = ({
142
138
  return renderAttachmentImage(data.url, data.filename, variant === "grid");
143
139
  }
144
140
  if (mediaCategory === "video" && data.type === "file" && data.url) {
145
- return /* @__PURE__ */ jsx("video", { className: "size-full object-cover", muted: true, src: data.url });
141
+ return /* @__PURE__ */ jsx(
142
+ Video,
143
+ {
144
+ src: data.url,
145
+ muted: true,
146
+ preload: "metadata",
147
+ controls: false,
148
+ fit: "cover",
149
+ "aria-hidden": true,
150
+ tabIndex: -1,
151
+ className: "size-full"
152
+ }
153
+ );
146
154
  }
147
155
  const Icon = mediaCategoryIcons[mediaCategory];
148
156
  return fallbackIcon ?? renderIcon(Icon);
@@ -263,4 +271,4 @@ export {
263
271
  AttachmentHoverCardContent,
264
272
  AttachmentEmpty
265
273
  };
266
- //# sourceMappingURL=chunk-7RL6KZDF.js.map
274
+ //# sourceMappingURL=chunk-24M6XWVJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/attachments.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button, Image, Video } from \"@elabs-ai/components-ui\";\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { FileUIPart, SourceDocumentUIPart } from \"ai\";\nimport {\n FileTextIcon,\n GlobeIcon,\n ImageIcon,\n Music2Icon,\n PaperclipIcon,\n VideoIcon,\n XIcon,\n} from \"lucide-react\";\nimport type { ComponentProps, HTMLAttributes, ReactNode } from \"react\";\nimport { createContext, useCallback, useContext, useMemo } from \"react\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type AttachmentData =\n | (FileUIPart & { id: string })\n | (SourceDocumentUIPart & { id: string });\n\nexport type AttachmentMediaCategory =\n | \"image\"\n | \"video\"\n | \"audio\"\n | \"document\"\n | \"source\"\n | \"unknown\";\n\nexport type AttachmentVariant = \"grid\" | \"inline\" | \"list\";\n\nconst mediaCategoryIcons: Record<AttachmentMediaCategory, typeof ImageIcon> = {\n audio: Music2Icon,\n document: FileTextIcon,\n image: ImageIcon,\n source: GlobeIcon,\n unknown: PaperclipIcon,\n video: VideoIcon,\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\nexport const getMediaCategory = (data: AttachmentData): AttachmentMediaCategory => {\n if (data.type === \"source-document\") {\n return \"source\";\n }\n\n const mediaType = data.mediaType ?? \"\";\n\n if (mediaType.startsWith(\"image/\")) {\n return \"image\";\n }\n if (mediaType.startsWith(\"video/\")) {\n return \"video\";\n }\n if (mediaType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n if (mediaType.startsWith(\"application/\") || mediaType.startsWith(\"text/\")) {\n return \"document\";\n }\n\n return \"unknown\";\n};\n\nexport const getAttachmentLabel = (data: AttachmentData): string => {\n if (data.type === \"source-document\") {\n return data.title || data.filename || \"Source\";\n }\n\n const category = getMediaCategory(data);\n return data.filename || (category === \"image\" ? \"Image\" : \"Attachment\");\n};\n\nconst renderAttachmentImage = (url: string, filename: string | undefined, isGrid: boolean) => (\n <Image\n alt={filename || \"Image\"}\n className={isGrid ? \"size-full\" : \"size-full rounded\"}\n fit=\"cover\"\n height={isGrid ? 96 : 20}\n showSkeleton={false}\n src={url}\n width={isGrid ? 96 : 20}\n />\n);\n\n// ============================================================================\n// Contexts\n// ============================================================================\n\ninterface AttachmentsContextValue {\n variant: AttachmentVariant;\n}\n\nconst AttachmentsContext = createContext<AttachmentsContextValue | null>(null);\n\ninterface AttachmentContextValue {\n data: AttachmentData;\n mediaCategory: AttachmentMediaCategory;\n onRemove?: () => void;\n variant: AttachmentVariant;\n}\n\nconst AttachmentContext = createContext<AttachmentContextValue | null>(null);\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nexport const useAttachmentsContext = () =>\n useContext(AttachmentsContext) ?? { variant: \"grid\" as const };\n\nexport const useAttachmentContext = () => {\n const ctx = useContext(AttachmentContext);\n if (!ctx) {\n throw new Error(\"Attachment components must be used within <Attachment>\");\n }\n return ctx;\n};\n\n// ============================================================================\n// Attachments - Container\n// ============================================================================\n\nexport type AttachmentsProps = HTMLAttributes<HTMLDivElement> & {\n variant?: AttachmentVariant;\n};\n\nexport const Attachments = ({\n variant = \"grid\",\n className,\n children,\n ...props\n}: AttachmentsProps) => {\n const contextValue = useMemo(() => ({ variant }), [variant]);\n\n return (\n <AttachmentsContext.Provider value={contextValue}>\n <div\n className={cn(\n \"flex items-start\",\n variant === \"list\" ? \"flex-col gap-2\" : \"flex-wrap gap-2\",\n variant === \"grid\" && \"ms-auto w-fit\",\n className,\n )}\n data-slot=\"attachments\"\n data-variant={variant}\n {...props}\n >\n {children}\n </div>\n </AttachmentsContext.Provider>\n );\n};\n\n// ============================================================================\n// Attachment - Item\n// ============================================================================\n\nexport type AttachmentProps = HTMLAttributes<HTMLDivElement> & {\n data: AttachmentData;\n onRemove?: () => void;\n};\n\nexport const Attachment = ({ data, onRemove, className, children, ...props }: AttachmentProps) => {\n const { variant } = useAttachmentsContext();\n const mediaCategory = getMediaCategory(data);\n\n const contextValue = useMemo<AttachmentContextValue>(\n () => ({ data, mediaCategory, onRemove, variant }),\n [data, mediaCategory, onRemove, variant],\n );\n\n return (\n <AttachmentContext.Provider value={contextValue}>\n <div\n className={cn(\n \"group relative\",\n variant === \"grid\" && \"size-24 overflow-hidden rounded-lg\",\n variant === \"inline\" && [\n \"flex h-8 cursor-pointer select-none items-center gap-1.5\",\n \"rounded-md border border-border px-1.5\",\n \"font-medium text-body transition-colors duration-fast ease-standard motion-reduce:transition-none\",\n \"hover:bg-accent/50 hover:text-accent-foreground\",\n ],\n variant === \"list\" && [\n \"flex w-full items-center gap-3 rounded-lg border p-3\",\n \"hover:bg-accent/50\",\n ],\n className,\n )}\n data-slot=\"attachment\"\n {...props}\n >\n {children}\n </div>\n </AttachmentContext.Provider>\n );\n};\n\n// ============================================================================\n// AttachmentPreview - Media preview\n// ============================================================================\n\nexport type AttachmentPreviewProps = HTMLAttributes<HTMLDivElement> & {\n fallbackIcon?: ReactNode;\n};\n\nexport const AttachmentPreview = ({\n fallbackIcon,\n className,\n ...props\n}: AttachmentPreviewProps) => {\n const { data, mediaCategory, variant } = useAttachmentContext();\n\n const iconSize = variant === \"inline\" ? \"size-3\" : \"size-4\";\n\n const renderIcon = (Icon: typeof ImageIcon) => (\n <Icon className={cn(iconSize, \"text-muted-foreground\")} />\n );\n\n const renderContent = () => {\n if (mediaCategory === \"image\" && data.type === \"file\" && data.url) {\n return renderAttachmentImage(data.url, data.filename, variant === \"grid\");\n }\n\n if (mediaCategory === \"video\" && data.type === \"file\" && data.url) {\n // Control-less, decorative thumbnail: the attachment's name is the label.\n return (\n <Video\n src={data.url}\n muted\n preload=\"metadata\"\n controls={false}\n fit=\"cover\"\n aria-hidden\n tabIndex={-1}\n className=\"size-full\"\n />\n );\n }\n\n const Icon = mediaCategoryIcons[mediaCategory];\n return fallbackIcon ?? renderIcon(Icon);\n };\n\n return (\n <div\n className={cn(\n \"flex shrink-0 items-center justify-center overflow-hidden\",\n variant === \"grid\" && \"size-full bg-muted\",\n variant === \"inline\" && \"size-5 rounded bg-background\",\n variant === \"list\" && \"size-12 rounded bg-muted\",\n className,\n )}\n {...props}\n >\n {renderContent()}\n </div>\n );\n};\n\n// ============================================================================\n// AttachmentInfo - Name and type display\n// ============================================================================\n\nexport type AttachmentInfoProps = HTMLAttributes<HTMLDivElement> & {\n showMediaType?: boolean;\n};\n\nexport const AttachmentInfo = ({\n showMediaType = false,\n className,\n ...props\n}: AttachmentInfoProps) => {\n const { data, variant } = useAttachmentContext();\n const label = getAttachmentLabel(data);\n\n if (variant === \"grid\") {\n return null;\n }\n\n return (\n <div className={cn(\"min-w-0 flex-1\", className)} {...props}>\n <span className=\"block truncate\">{label}</span>\n {showMediaType && data.mediaType && (\n <span className=\"block truncate text-muted-foreground text-meta\">{data.mediaType}</span>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// AttachmentRemove - Remove button\n// ============================================================================\n\nexport type AttachmentRemoveProps = ComponentProps<typeof Button> & {\n label?: string;\n};\n\nexport const AttachmentRemove = ({\n label = \"Remove\",\n className,\n children,\n ...props\n}: AttachmentRemoveProps) => {\n const { onRemove, variant } = useAttachmentContext();\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onRemove?.();\n },\n [onRemove],\n );\n\n if (!onRemove) {\n return null;\n }\n\n return (\n <Button\n aria-label={label}\n className={cn(\n variant === \"grid\" && [\n \"absolute top-2 end-2 size-6 rounded-full p-0\",\n \"bg-background/80 backdrop-blur-sm\",\n \"opacity-0 transition-opacity group-hover:opacity-100\",\n \"hover:bg-background\",\n \"[&>svg]:size-3\",\n ],\n variant === \"inline\" && [\n \"size-5 rounded p-0\",\n \"opacity-0 transition-opacity group-hover:opacity-100\",\n \"[&>svg]:size-2.5\",\n ],\n variant === \"list\" && [\"size-8 shrink-0 rounded p-0\", \"[&>svg]:size-4\"],\n className,\n )}\n onClick={handleClick}\n type=\"button\"\n variant=\"ghost\"\n {...props}\n >\n {children ?? <XIcon />}\n <span className=\"sr-only\">{label}</span>\n </Button>\n );\n};\n\n// ============================================================================\n// AttachmentHoverCard - Hover preview\n// ============================================================================\n\nexport type AttachmentHoverCardProps = ComponentProps<typeof HoverCard>;\n\nexport const AttachmentHoverCard = ({\n openDelay = 0,\n closeDelay = 0,\n ...props\n}: AttachmentHoverCardProps) => (\n <HoverCard closeDelay={closeDelay} openDelay={openDelay} {...props} />\n);\n\nexport type AttachmentHoverCardTriggerProps = ComponentProps<typeof HoverCardTrigger>;\n\nexport const AttachmentHoverCardTrigger = (props: AttachmentHoverCardTriggerProps) => (\n <HoverCardTrigger {...props} />\n);\n\nexport type AttachmentHoverCardContentProps = ComponentProps<typeof HoverCardContent>;\n\nexport const AttachmentHoverCardContent = ({\n align = \"start\",\n className,\n ...props\n}: AttachmentHoverCardContentProps) => (\n <HoverCardContent align={align} className={cn(\"w-auto p-2\", className)} {...props} />\n);\n\n// ============================================================================\n// AttachmentEmpty - Empty state\n// ============================================================================\n\nexport type AttachmentEmptyProps = HTMLAttributes<HTMLDivElement>;\n\nexport const AttachmentEmpty = ({ className, children, ...props }: AttachmentEmptyProps) => (\n <div\n className={cn(\n \"flex items-center justify-center p-4 text-muted-foreground text-body\",\n className,\n )}\n {...props}\n >\n {children ?? \"No attachments\"}\n </div>\n);\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  Reasoning,
4
4
  ReasoningContent,
5
5
  ReasoningTrigger
6
- } from "./chunk-X47QMM6K.js";
6
+ } from "./chunk-YLN7E4OV.js";
7
7
  import {
8
8
  Tool,
9
9
  ToolContent,
@@ -11,7 +11,7 @@ import {
11
11
  ToolInput,
12
12
  ToolOutput,
13
13
  statusFromToolState
14
- } from "./chunk-AOOIAFDT.js";
14
+ } from "./chunk-OCZNA5SO.js";
15
15
  import {
16
16
  buildPartGroups,
17
17
  defaultPartStatus,
@@ -146,4 +146,4 @@ function GroupedParts({
146
146
  export {
147
147
  GroupedParts
148
148
  };
149
- //# sourceMappingURL=chunk-VJZJWNEC.js.map
149
+ //# sourceMappingURL=chunk-3KXIWGHW.js.map
@@ -35,7 +35,6 @@ import {
35
35
  useRef,
36
36
  useState
37
37
  } from "react";
38
- import { createHighlighter } from "shiki";
39
38
  import { jsx, jsxs } from "react/jsx-runtime";
40
39
  var isItalic = (fontStyle) => fontStyle && fontStyle & 1;
41
40
  var isBold = (fontStyle) => fontStyle && fontStyle & 2;
@@ -112,15 +111,22 @@ var hashCode = (value) => {
112
111
  return (hash >>> 0).toString(36);
113
112
  };
114
113
  var getTokensCacheKey = (code, language, themeId) => `${themeId}:${language}:${code.length}:${hashCode(code)}`;
114
+ var shikiModulePromise;
115
+ var loadShiki = () => {
116
+ shikiModulePromise ??= import("shiki");
117
+ return shikiModulePromise;
118
+ };
115
119
  var getHighlighter = (language) => {
116
120
  const cached = highlighterCache.get(language);
117
121
  if (cached) {
118
122
  return cached;
119
123
  }
120
- const highlighterPromise = createHighlighter({
121
- langs: [language],
122
- themes: []
123
- });
124
+ const highlighterPromise = loadShiki().then(
125
+ ({ createHighlighter }) => createHighlighter({
126
+ langs: [language],
127
+ themes: []
128
+ })
129
+ );
124
130
  highlighterCache.set(language, highlighterPromise);
125
131
  return highlighterPromise;
126
132
  };
@@ -459,4 +465,4 @@ export {
459
465
  CodeBlockLanguageSelectorContent,
460
466
  CodeBlockLanguageSelectorItem
461
467
  };
462
- //# sourceMappingURL=chunk-TWPHAJNM.js.map
468
+ //# sourceMappingURL=chunk-3UHR3RLZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/code-block.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button, useCopyToClipboard, useLocale } from \"@elabs-ai/components-ui\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport type { ComponentProps, CSSProperties, HTMLAttributes } from \"react\";\nimport { Shimmer } from \"./shimmer\";\nimport {\n createContext,\n memo,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from \"react\";\nimport type { BundledLanguage, BundledTheme, HighlighterGeneric, ThemedToken } from \"shiki\";\n\nimport { buildCodeBlockTheme, codeBlockThemeId, getThemeScopeKey } from \"./_code-block-theme\";\nimport { getThemeScope, useThemeScopeRevision } from \"./_theme-scope-store\";\n\n// Shiki uses bitflags for font styles: 1=italic, 2=bold, 4=underline\n// oxlint-disable-next-line eslint(no-bitwise)\nconst isItalic = (fontStyle: number | undefined) => fontStyle && fontStyle & 1;\n// oxlint-disable-next-line eslint(no-bitwise)\nconst isBold = (fontStyle: number | undefined) => fontStyle && fontStyle & 2;\nconst isUnderline = (fontStyle: number | undefined) =>\n // oxlint-disable-next-line eslint(no-bitwise)\n fontStyle && fontStyle & 4;\n\n// Transform tokens to include pre-computed keys to avoid noArrayIndexKey lint\ninterface KeyedToken {\n token: ThemedToken;\n key: string;\n}\ninterface KeyedLine {\n tokens: KeyedToken[];\n key: string;\n}\n\nconst addKeysToTokens = (lines: ThemedToken[][]): KeyedLine[] =>\n lines.map((line, lineIdx) => ({\n key: `line-${lineIdx}`,\n tokens: line.map((token, tokenIdx) => ({\n key: `line-${lineIdx}-${tokenIdx}`,\n token,\n })),\n }));\n\n// Token rendering component. Colors come entirely from the derived brand\n// theme's inline styles (`_code-block-theme.ts`, #315) — no `dark:` escape\n// hatch needed, since the theme is already resolved for whichever brand\n// theme is active (including a monochrome ink palette).\nconst TokenSpan = ({ token }: { token: ThemedToken }) => (\n <span\n style={\n {\n backgroundColor: token.bgColor,\n color: token.color,\n fontStyle: isItalic(token.fontStyle) ? \"italic\" : undefined,\n fontWeight: isBold(token.fontStyle) ? \"bold\" : undefined,\n textDecoration: isUnderline(token.fontStyle) ? \"underline\" : undefined,\n ...token.htmlStyle,\n } as CSSProperties\n }\n >\n {token.content}\n </span>\n);\n\n// Line number styles using CSS counters\nconst LINE_NUMBER_CLASSES = cn(\n \"block\",\n \"before:content-[counter(line)]\",\n \"before:inline-block\",\n \"before:[counter-increment:line]\",\n \"before:w-8\",\n \"before:me-4\",\n \"before:text-end\",\n \"before:text-muted-foreground/50\",\n \"before:font-mono\",\n \"before:select-none\",\n);\n\n// Line rendering component\nconst LineSpan = ({\n keyedLine,\n showLineNumbers,\n}: {\n keyedLine: KeyedLine;\n showLineNumbers: boolean;\n}) => (\n <span className={showLineNumbers ? LINE_NUMBER_CLASSES : \"block\"}>\n {keyedLine.tokens.length === 0\n ? \"\\n\"\n : keyedLine.tokens.map(({ token, key }) => <TokenSpan key={key} token={token} />)}\n </span>\n);\n\n// Types\nexport type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n code: string;\n language: BundledLanguage;\n showLineNumbers?: boolean;\n /**\n * Soft-wrap long lines instead of scrolling horizontally. Use in narrow embeds\n * (e.g. a side rail) so content is not silently clipped. Default: false.\n */\n wrap?: boolean;\n /**\n * code is arriving incrementally (loading-states.md isStreaming) — code\n * keeps rendering progressively (build-up), NOT a skeleton, since a\n * streaming code block is not fetch-then-show. Shows a Shimmer generating\n * cue below the content as the in-progress affordance.\n * @default false\n */\n isStreaming?: boolean;\n};\n\ninterface TokenizedCode {\n tokens: ThemedToken[][];\n fg: string;\n bg: string;\n}\n\ninterface CodeBlockContextType {\n code: string;\n}\n\n// Context\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n code: \"\",\n});\n\n// Highlighter cache (singleton per language)\nconst highlighterCache = new Map<\n string,\n Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>\n>();\n\n// Token cache — bounded to the `MAX_TOKENS_CACHE_ENTRIES` most recently used\n// entries (perf review 1.4a). Unbounded growth showed up while streaming: a\n// growing code string re-tokenizes on every token, and every intermediate\n// string used to get its own permanent cache entry — O(n²) memory that was\n// never released for the lifetime of the tab.\nconst MAX_TOKENS_CACHE_ENTRIES = 200;\nconst tokensCache = new Map<string, TokenizedCode>();\n\n/** Reads `key`, marking it most-recently-used (moves it to the end). */\nconst tokensCacheGet = (key: string): TokenizedCode | undefined => {\n const value = tokensCache.get(key);\n if (value === undefined) return undefined;\n tokensCache.delete(key);\n tokensCache.set(key, value);\n return value;\n};\n\n/** Writes `key`, evicting the least-recently-used entries over the cap. */\nconst tokensCacheSet = (key: string, value: TokenizedCode): void => {\n tokensCache.delete(key);\n tokensCache.set(key, value);\n while (tokensCache.size > MAX_TOKENS_CACHE_ENTRIES) {\n const oldestKey = tokensCache.keys().next().value;\n if (oldestKey === undefined) break;\n tokensCache.delete(oldestKey);\n }\n};\n\n// Subscribers for async token updates\nconst subscribers = new Map<string, Set<(result: TokenizedCode) => void>>();\n\n// FNV-1a 32-bit — cheap, and (unlike the previous length + first/last 100\n// chars key) hashes the FULL string, so two different strings of the same\n// length sharing a prefix and suffix longer than 100 chars (an edit to the\n// MIDDLE of a >200-char block — the common case for a diff or a streamed\n// correction) no longer collide onto the same cache entry and show stale,\n// wrong-content highlighting (#perf 1.4b).\nconst hashCode = (value: string): string => {\n let hash = 0x811c9dc5;\n for (let i = 0; i < value.length; i++) {\n // oxlint-disable-next-line eslint(no-bitwise)\n hash ^= value.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n // oxlint-disable-next-line eslint(no-bitwise)\n return (hash >>> 0).toString(36);\n};\n\n// The theme id is part of the cache key (#315) — the SAME code+language must\n// re-tokenize (and re-cache) when the active brand theme changes, or a code\n// block would keep showing stale colors from the theme active when it was\n// first highlighted. The id is derived from `getThemeScopeKey` (the RAW\n// `data-theme` attribute, or a sentinel when unset) rather than the validated\n// `ThemeName` — see `_code-block-theme.ts`'s module doc comment for why: an\n// unset attribute and an explicit `data-theme=\"light\"` both narrow to\n// the SAME `ThemeName`, but `:root`'s `--code-*` fallback values are their own\n// distinct placeholder palette, not an alias of `light`'s — so keying on\n// the validated name would let the pre-mount render's `:root` colors poison\n// the cache under the key `ThemeProvider` later writes explicitly, and they'd\n// never be replaced.\nconst getTokensCacheKey = (code: string, language: BundledLanguage, themeId: string) =>\n `${themeId}:${language}:${code.length}:${hashCode(code)}`;\n\n// Shiki (~36KB gzip of bundled language/theme index, #597) is a\n// `@lazy-boundary`-style dependency (ADR 0019, same pattern as\n// `_lazy-mermaid.ts`'s `loadEngine`): the ONLY reference to it anywhere in\n// this module is this dynamic `import()`, fetched at most once per app and\n// shared by every language's highlighter promise below. `CodeBlockContent`\n// already renders `createRawTokens` synchronously on mount and swaps in the\n// tokenized result once `getHighlighter`'s promise resolves, so this never\n// introduces a layout shift or a loading state beyond the existing\n// raw-then-highlighted flow.\nlet shikiModulePromise: Promise<Pick<typeof import(\"shiki\"), \"createHighlighter\">> | undefined;\n\nconst loadShiki = (): Promise<Pick<typeof import(\"shiki\"), \"createHighlighter\">> => {\n shikiModulePromise ??= import(\"shiki\");\n return shikiModulePromise;\n};\n\nconst getHighlighter = (\n language: BundledLanguage,\n): Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> => {\n const cached = highlighterCache.get(language);\n if (cached) {\n return cached;\n }\n\n // No themes preloaded here (#315) — the theme is derived from brand tokens\n // per call (`buildCodeBlockTheme`) and passed directly to `codeToTokens`,\n // never a bundled `github-*` literal.\n const highlighterPromise = loadShiki().then(({ createHighlighter }) =>\n createHighlighter({\n langs: [language],\n themes: [],\n }),\n );\n\n highlighterCache.set(language, highlighterPromise);\n return highlighterPromise;\n};\n\n// Create raw tokens for immediate display while highlighting loads\nconst createRawTokens = (code: string): TokenizedCode => ({\n bg: \"transparent\",\n fg: \"inherit\",\n tokens: code.split(\"\\n\").map((line) =>\n line === \"\"\n ? []\n : [\n {\n color: \"inherit\",\n content: line,\n } as ThemedToken,\n ],\n ),\n});\n\n// Synchronous highlight with callback for async results. `callback` stays in\n// its ORIGINAL third position (pre-#315 public API — a positional break here\n// would silently mis-wire any existing 3-arg caller, see #315 follow-up); `el`\n// is a new, purely-additive fourth parameter: the element (default `<html>`)\n// whose active brand theme's `--code-*` tokens the highlighter derives its\n// colors from. Passing a descendant of a region-scoped `<div data-theme=\"…\">`\n// resolves THAT region's theme instead of the document root's.\n//\n// `skipCache` (fifth, purely-additive parameter, default `false`) is set by a\n// caller mid-stream (perf review 1.4a): a streaming code block re-tokenizes a\n// GROWING string on every token, so every intermediate value is, by\n// definition, never seen again — permanently caching it only pays rent\n// (bounded now by the LRU cap, but still pure waste) without ever paying off\n// with a hit. A cache HIT (the final, settled string matches an\n// already-cached entry) is still honored either way.\nexport const highlightCode = (\n code: string,\n language: BundledLanguage,\n // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-callbacks)\n callback?: (result: TokenizedCode) => void,\n el?: Element | null,\n skipCache = false,\n): TokenizedCode | null => {\n const themeId = codeBlockThemeId(getThemeScopeKey(el));\n const tokensCacheKey = getTokensCacheKey(code, language, themeId);\n\n // Return cached result if available\n const cached = tokensCacheGet(tokensCacheKey);\n if (cached) {\n return cached;\n }\n\n // Subscribe callback if provided\n if (callback) {\n if (!subscribers.has(tokensCacheKey)) {\n subscribers.set(tokensCacheKey, new Set());\n }\n subscribers.get(tokensCacheKey)?.add(callback);\n }\n\n // Start highlighting in background - fire-and-forget async pattern\n getHighlighter(language)\n // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-then)\n .then((highlighter) => {\n const availableLangs = highlighter.getLoadedLanguages();\n const langToUse = availableLangs.includes(language) ? language : \"text\";\n\n // Derived from brand `--code-*` tokens (#315), never a `github-*` literal.\n // Same `el` the theme NAME above came from, so id/type/colors agree.\n const result = highlighter.codeToTokens(code, {\n lang: langToUse,\n theme: buildCodeBlockTheme(el),\n });\n\n const tokenized: TokenizedCode = {\n bg: result.bg ?? \"transparent\",\n fg: result.fg ?? \"inherit\",\n tokens: result.tokens,\n };\n\n // Cache the result — unless the caller told us it is mid-stream, in\n // which case this exact string is very unlikely to recur.\n if (!skipCache) {\n tokensCacheSet(tokensCacheKey, tokenized);\n }\n\n // Notify all subscribers\n const subs = subscribers.get(tokensCacheKey);\n if (subs) {\n for (const sub of subs) {\n sub(tokenized);\n }\n subscribers.delete(tokensCacheKey);\n }\n })\n // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-then), eslint-plugin-promise(prefer-await-to-callbacks)\n .catch((error) => {\n console.error(\"Failed to highlight code:\", error);\n subscribers.delete(tokensCacheKey);\n });\n\n return null;\n};\n\nconst CodeBlockBody = memo(\n ({\n tokenized,\n showLineNumbers,\n wrap,\n className,\n }: {\n tokenized: TokenizedCode;\n showLineNumbers: boolean;\n wrap?: boolean;\n className?: string;\n }) => {\n const preStyle = useMemo(\n () => ({\n backgroundColor: tokenized.bg,\n color: tokenized.fg,\n }),\n [tokenized.bg, tokenized.fg],\n );\n\n const keyedLines = useMemo(() => addKeysToTokens(tokenized.tokens), [tokenized.tokens]);\n\n return (\n <pre\n className={cn(\"m-0 p-4 text-body\", wrap && \"whitespace-pre-wrap break-words\", className)}\n style={preStyle}\n >\n <code\n className={cn(\n \"font-mono text-body\",\n showLineNumbers && \"[counter-increment:line_0] [counter-reset:line]\",\n )}\n >\n {keyedLines.map((keyedLine) => (\n <LineSpan key={keyedLine.key} keyedLine={keyedLine} showLineNumbers={showLineNumbers} />\n ))}\n </code>\n </pre>\n );\n },\n (prevProps, nextProps) =>\n prevProps.tokenized === nextProps.tokenized &&\n prevProps.showLineNumbers === nextProps.showLineNumbers &&\n prevProps.wrap === nextProps.wrap &&\n prevProps.className === nextProps.className,\n);\n\nCodeBlockBody.displayName = \"CodeBlockBody\";\n\nexport const CodeBlockContainer = ({\n className,\n language,\n style,\n ...props\n}: HTMLAttributes<HTMLDivElement> & { language: string }) => (\n <div\n className={cn(\n \"group relative w-full overflow-hidden rounded-md border bg-background text-foreground\",\n className,\n )}\n data-language={language}\n style={{\n containIntrinsicSize: \"auto 200px\",\n contentVisibility: \"auto\",\n ...style,\n }}\n {...props}\n />\n);\n\nexport const CodeBlockHeader = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n \"flex items-center justify-between border-b bg-muted/80 px-3 py-2 text-muted-foreground text-meta\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n);\n\nexport const CodeBlockTitle = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex items-center gap-2\", className)} {...props}>\n {children}\n </div>\n);\n\nexport const CodeBlockFilename = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLSpanElement>) => (\n <span className={cn(\"font-mono\", className)} {...props}>\n {children}\n </span>\n);\n\nexport const CodeBlockActions = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"-my-1 -me-1 flex items-center gap-2\", className)} {...props}>\n {children}\n </div>\n);\n\n// Streaming re-highlight throttle (perf review 1.4e / §3.2): re-tokenizing\n// on every single streamed token is O(n²) work over the life of a response.\n// At most one highlight pass per window, ALWAYS with a trailing call so the\n// final, settled string still gets highlighted the moment streaming stops.\nconst STREAM_HIGHLIGHT_THROTTLE_MS = 200;\n\nexport const CodeBlockContent = ({\n code,\n language,\n showLineNumbers = false,\n wrap = false,\n isStreaming = false,\n}: {\n code: string;\n language: BundledLanguage;\n showLineNumbers?: boolean;\n wrap?: boolean;\n isStreaming?: boolean;\n}) => {\n // Track the active brand theme (#315), SCOPED to this code block's own\n // subtree (`getThemeScope`) rather than always `<html>` — so a CodeBlock\n // nested inside a region-scoped `<div data-theme=\"dark\">` (a supported\n // ThemeProvider/decorator pattern) picks up THAT region's `--code-*` tokens,\n // not the document root's. `useThemeScopeRevision` (`_theme-scope-store.ts`)\n // shares ONE MutationObserver per scope element across every subscriber\n // watching it, instead of one per `CodeBlock` instance (perf review §3.3).\n const scopeRef = useRef<HTMLDivElement>(null);\n // The ref only attaches after the first commit, so the scope resolved\n // during the initial render (before mount) may have fallen back to\n // `<html>`. Force one more render right after mount — synchronously, before\n // paint, via `useLayoutEffect` — so a scoped code block never flashes the\n // document root's colors first; `useThemeScopeRevision` re-subscribes to\n // the now-correct scope on that render.\n const [, forceMountRerender] = useReducer((tick: number) => tick + 1, 0);\n useLayoutEffect(() => {\n forceMountRerender();\n }, []);\n\n const scopeEl = getThemeScope(scopeRef.current);\n const themeRevision = useThemeScopeRevision(scopeRef.current);\n // Keyed on the RAW `data-theme` scope, not the validated theme name (#315\n // follow-up) — see `_code-block-theme.ts`'s module doc comment. An unset\n // attribute (the pre-mount render) and an explicit `data-theme=\"light\"`\n // both narrow to the SAME `ThemeName`, so using the validated name here would\n // mean this memo's dependency doesn't CHANGE across that mutation and the\n // stale `:root`-tokenized colors would never be recomputed once\n // `ThemeProvider` mounts and writes the attribute explicitly.\n const themeScopeKey = useMemo(\n () => getThemeScopeKey(scopeEl),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- themeRevision is the trigger; scopeEl's own identity already changes across the post-mount re-render above.\n [themeRevision, scopeEl],\n );\n\n // Memoized raw tokens for immediate display\n const rawTokens = useMemo(() => createRawTokens(code), [code]);\n\n // Synchronous cache PEEK — a plain, side-effect-free `Map.get`, never a\n // call into `highlightCode` (the OLD code called it here with no callback,\n // AND again in the effect below with one; on a cache miss both calls\n // independently kicked off `getHighlighter(language).then(...)` and\n // computed `codeToTokens` for the SAME code twice — perf review 1.4d).\n // Highlighting is now triggered from exactly ONE place: the effect.\n const themeId = codeBlockThemeId(themeScopeKey);\n const tokensCacheKey = getTokensCacheKey(code, language, themeId);\n const syncTokens = tokensCache.get(tokensCacheKey) ?? rawTokens;\n\n // Async highlighting result (populated after shiki loads, or synchronously\n // inside the effect on a cache hit).\n const [asyncTokens, setAsyncTokens] = useState<TokenizedCode | null>(null);\n const asyncKeyRef = useRef({ code, language, themeScopeKey });\n\n // Invalidate stale async tokens synchronously during render\n if (\n asyncKeyRef.current.code !== code ||\n asyncKeyRef.current.language !== language ||\n asyncKeyRef.current.themeScopeKey !== themeScopeKey\n ) {\n asyncKeyRef.current = { code, language, themeScopeKey };\n setAsyncTokens(null);\n }\n\n // Last time THIS instance actually ran a highlight pass — the throttle\n // clock while `isStreaming` (perf review 1.4e).\n const lastHighlightAtRef = useRef(0);\n\n useEffect(() => {\n let cancelled = false;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const runHighlight = () => {\n lastHighlightAtRef.current = Date.now();\n // `highlightCode` returns the tokenized result SYNCHRONOUSLY on a cache\n // hit (and never invokes the callback in that case — see its early\n // `if (cached) return cached` branch). That cache hit is the COMMON case\n // right after a theme switch (#315): the highlighter for this language is\n // already loaded, so re-tokenizing for the new theme resolves within a\n // microtask — often before this effect even runs — and without this\n // direct check `asyncTokens` would stay null forever, stranding the\n // code block on its raw/unhighlighted fallback after every theme change.\n const cached = highlightCode(\n code,\n language,\n (result) => {\n if (!cancelled) {\n setAsyncTokens(result);\n }\n },\n scopeEl,\n isStreaming,\n );\n if (cached && !cancelled) {\n setAsyncTokens(cached);\n }\n };\n\n if (!isStreaming) {\n // Not streaming: always highlight immediately (also the path a\n // just-finished stream's LAST token takes, guaranteeing the final,\n // settled code always gets a full, cacheable highlight even if the\n // throttle skipped some of the tokens before it).\n runHighlight();\n } else {\n const elapsed = Date.now() - lastHighlightAtRef.current;\n if (elapsed >= STREAM_HIGHLIGHT_THROTTLE_MS) {\n runHighlight();\n } else {\n timeoutId = setTimeout(runHighlight, STREAM_HIGHLIGHT_THROTTLE_MS - elapsed);\n }\n }\n\n return () => {\n cancelled = true;\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n };\n }, [code, language, themeScopeKey, scopeEl, isStreaming]);\n\n const tokenized = asyncTokens ?? syncTokens;\n\n return (\n <div\n ref={scopeRef}\n className={cn(\n \"relative overflow-auto\",\n // Discoverable horizontal-scroll affordance in non-wrap mode, so a narrow\n // embed doesn't silently clip long lines. Hidden when wrapping (no overflow).\n !wrap &&\n \"[scrollbar-width:thin] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border\",\n )}\n >\n <CodeBlockBody showLineNumbers={showLineNumbers} tokenized={tokenized} wrap={wrap} />\n </div>\n );\n};\n\nexport const CodeBlock = ({\n code,\n language,\n showLineNumbers = false,\n wrap = false,\n isStreaming = false,\n className,\n children,\n ...props\n}: CodeBlockProps) => {\n const contextValue = useMemo(() => ({ code }), [code]);\n const { t } = useLocale();\n\n return (\n <CodeBlockContext.Provider value={contextValue}>\n <CodeBlockContainer className={className} language={language} {...props}>\n {children}\n <CodeBlockContent\n code={code}\n language={language}\n showLineNumbers={showLineNumbers}\n wrap={wrap}\n isStreaming={isStreaming}\n />\n {isStreaming ? (\n <div\n className=\"flex items-center gap-2 border-t bg-muted/80 px-3 py-1.5 text-caption text-muted-foreground\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Shimmer>{t(\"ai.codeBlock.generating\")}</Shimmer>\n </div>\n ) : null}\n </CodeBlockContainer>\n </CodeBlockContext.Provider>\n );\n};\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n onCopy?: () => void;\n onError?: (error: Error) => void;\n timeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n onCopy,\n onError,\n timeout,\n children,\n className,\n ...props\n}: CodeBlockCopyButtonProps) => {\n const { t } = useLocale();\n const { code } = useContext(CodeBlockContext);\n // Shared implementation (`@elabs-ai/components-ui`) instead of a private\n // copy of the same copy-to-clipboard state machine — this one used to\n // duplicate `SnippetCopyButton`'s (issue-workflow.md dedupe finding).\n const { copied: isCopied, copy } = useCopyToClipboard(\n timeout === undefined ? undefined : { resetAfterMs: timeout },\n );\n\n const copyToClipboard = useCallback(async () => {\n const ok = await copy(code);\n if (ok) {\n onCopy?.();\n } else {\n onError?.(new Error(\"Clipboard API not available\"));\n }\n }, [copy, code, onCopy, onError]);\n\n const Icon = isCopied ? CheckIcon : CopyIcon;\n\n return (\n <Button\n aria-label={t(\"copy\")}\n className={cn(\"shrink-0\", className)}\n onClick={copyToClipboard}\n size=\"icon\"\n variant=\"ghost\"\n {...props}\n >\n {children ?? (\n <span\n key={isCopied ? \"check\" : \"copy\"}\n className=\"flex animate-in fade-in-0 zoom-in-95 duration-fast ease-entrance\"\n >\n <Icon size={14} />\n </span>\n )}\n </Button>\n );\n};\n\nexport type CodeBlockLanguageSelectorProps = ComponentProps<typeof Select>;\n\nexport const CodeBlockLanguageSelector = (props: CodeBlockLanguageSelectorProps) => (\n <Select {...props} />\n);\n\nexport type CodeBlockLanguageSelectorTriggerProps = ComponentProps<typeof SelectTrigger>;\n\nexport const CodeBlockLanguageSelectorTrigger = ({\n className,\n ...props\n}: CodeBlockLanguageSelectorTriggerProps) => (\n <SelectTrigger\n className={cn(\"h-7 border-none bg-transparent px-2 text-meta shadow-none\", className)}\n size=\"sm\"\n {...props}\n />\n);\n\nexport type CodeBlockLanguageSelectorValueProps = ComponentProps<typeof SelectValue>;\n\nexport const CodeBlockLanguageSelectorValue = (props: CodeBlockLanguageSelectorValueProps) => (\n <SelectValue {...props} />\n);\n\nexport type CodeBlockLanguageSelectorContentProps = ComponentProps<typeof SelectContent>;\n\nexport const CodeBlockLanguageSelectorContent = ({\n align = \"end\",\n ...props\n}: CodeBlockLanguageSelectorContentProps) => <SelectContent align={align} {...props} />;\n\nexport type CodeBlockLanguageSelectorItemProps = ComponentProps<typeof SelectItem>;\n\nexport const CodeBlockLanguageSelectorItem = (props: CodeBlockLanguageSelectorItemProps) => (\n <SelectItem {...props} />\n);\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  useStreamdownMermaidOptions,
5
5
  useStreamdownPlugins,
6
6
  useStreamdownTranslations
7
- } from "./chunk-PQBF6ZIQ.js";
7
+ } from "./chunk-DI645WR4.js";
8
8
  import {
9
9
  stripSanitizerOverrides,
10
10
  warnOnTrustedPluginSlots
@@ -89,4 +89,4 @@ var MarkdownView = ({
89
89
  export {
90
90
  MarkdownView
91
91
  };
92
- //# sourceMappingURL=chunk-V4DORGWJ.js.map
92
+ //# sourceMappingURL=chunk-5AMSNG2T.js.map
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-D7BZMRI3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,7 +1,4 @@
1
1
  "use client";
2
- import {
3
- useLazyMathPlugin
4
- } from "./chunk-VLNBTOH6.js";
5
2
  import {
6
3
  lazyMermaid
7
4
  } from "./chunk-PG7ERDOD.js";
@@ -17,6 +14,9 @@ import {
17
14
  import {
18
15
  useLazyCjkPlugin
19
16
  } from "./chunk-QUDZOTRV.js";
17
+ import {
18
+ useLazyMathPlugin
19
+ } from "./chunk-VLNBTOH6.js";
20
20
 
21
21
  // src/_streamdown-i18n.ts
22
22
  import {
@@ -52,4 +52,4 @@ export {
52
52
  getStreamdownPluginsKey,
53
53
  useStreamdownMermaidOptions
54
54
  };
55
- //# sourceMappingURL=chunk-PQBF6ZIQ.js.map
55
+ //# sourceMappingURL=chunk-DI645WR4.js.map
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YHUSW7D2.js";
5
5
  import {
6
6
  LazyEngineBoundary
7
- } from "./chunk-S3ISPW3A.js";
7
+ } from "./chunk-PWQPVOZJ.js";
8
8
 
9
9
  // src/persona.tsx
10
10
  import { cn } from "@elabs-ai/components-ui/lib/cn";
@@ -93,4 +93,4 @@ Persona.displayName = "Persona";
93
93
  export {
94
94
  Persona
95
95
  };
96
- //# sourceMappingURL=chunk-NCL2UZKI.js.map
96
+ //# sourceMappingURL=chunk-FFA7OPEH.js.map
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/queue.tsx
4
- import { Button } from "@elabs-ai/components-ui";
4
+ import { Button, Image } from "@elabs-ai/components-ui";
5
5
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@elabs-ai/components-ui";
6
6
  import { ScrollArea } from "@elabs-ai/components-ui";
7
7
  import { cn } from "@elabs-ai/components-ui/lib/cn";
@@ -77,14 +77,17 @@ var QueueItemAction = ({ className, ...props }) => /* @__PURE__ */ jsx(
77
77
  }
78
78
  );
79
79
  var QueueItemAttachment = ({ className, ...props }) => /* @__PURE__ */ jsx("div", { className: cn("mt-1 flex flex-wrap gap-2", className), ...props });
80
- var QueueItemImage = ({ className, ...props }) => /* @__PURE__ */ jsx(
81
- "img",
80
+ var QueueItemImage = ({ className, alt, ...props }) => /* @__PURE__ */ jsx(
81
+ Image,
82
82
  {
83
- alt: "",
84
- className: cn("h-8 w-8 rounded border object-cover", className),
85
83
  height: 32,
86
84
  width: 32,
87
- ...props
85
+ showSkeleton: false,
86
+ ...props,
87
+ "data-slot": "queue-item-image",
88
+ alt: alt ?? "",
89
+ fit: "cover",
90
+ className: cn("h-8 w-8 rounded border", className)
88
91
  }
89
92
  );
90
93
  var QueueItemFile = ({ children, className, ...props }) => /* @__PURE__ */ jsxs(
@@ -169,4 +172,4 @@ export {
169
172
  QueueSectionContent,
170
173
  Queue
171
174
  };
172
- //# sourceMappingURL=chunk-34CQ2VHM.js.map
175
+ //# sourceMappingURL=chunk-FO44KY7Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queue.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button, Image } from \"@elabs-ai/components-ui\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"@elabs-ai/components-ui\";\nimport { ScrollArea } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { ChevronDownIcon, PaperclipIcon } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\n\nexport interface QueueMessagePart {\n type: string;\n text?: string;\n url?: string;\n filename?: string;\n mediaType?: string;\n}\n\nexport interface QueueMessage {\n id: string;\n parts: QueueMessagePart[];\n}\n\nexport interface QueueTodo {\n id: string;\n title: string;\n description?: string;\n status?: \"pending\" | \"completed\";\n}\n\nexport type QueueItemProps = ComponentProps<\"li\">;\n\nexport const QueueItem = ({ className, ...props }: QueueItemProps) => (\n <li\n className={cn(\n \"group flex flex-col gap-1 rounded-md px-3 py-1 text-body transition-colors hover:bg-muted\",\n className,\n )}\n {...props}\n />\n);\n\nexport type QueueItemIndicatorProps = ComponentProps<\"span\"> & {\n completed?: boolean;\n};\n\nexport const QueueItemIndicator = ({\n completed = false,\n className,\n ...props\n}: QueueItemIndicatorProps) => (\n <span\n className={cn(\n \"mt-0.5 inline-block size-2.5 rounded-full border\",\n completed\n ? \"border-muted-foreground/20 bg-muted-foreground/10\"\n : \"border-muted-foreground/50\",\n className,\n )}\n {...props}\n />\n);\n\nexport type QueueItemContentProps = ComponentProps<\"span\"> & {\n completed?: boolean;\n};\n\nexport const QueueItemContent = ({\n completed = false,\n className,\n ...props\n}: QueueItemContentProps) => (\n <span\n className={cn(\n \"line-clamp-1 grow break-words\",\n completed ? \"text-muted-foreground/50 line-through\" : \"text-muted-foreground\",\n className,\n )}\n {...props}\n />\n);\n\nexport type QueueItemDescriptionProps = ComponentProps<\"div\"> & {\n completed?: boolean;\n};\n\nexport const QueueItemDescription = ({\n completed = false,\n className,\n ...props\n}: QueueItemDescriptionProps) => (\n <div\n className={cn(\n \"ms-6 text-meta\",\n completed ? \"text-muted-foreground/40 line-through\" : \"text-muted-foreground\",\n className,\n )}\n {...props}\n />\n);\n\nexport type QueueItemActionsProps = ComponentProps<\"div\">;\n\nexport const QueueItemActions = ({ className, ...props }: QueueItemActionsProps) => (\n <div className={cn(\"flex gap-1\", className)} {...props} />\n);\n\nexport type QueueItemActionProps = Omit<ComponentProps<typeof Button>, \"variant\" | \"size\">;\n\nexport const QueueItemAction = ({ className, ...props }: QueueItemActionProps) => (\n <Button\n className={cn(\n \"size-auto rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:bg-muted-foreground/10 hover:text-foreground group-hover:opacity-100\",\n className,\n )}\n size=\"icon\"\n type=\"button\"\n variant=\"ghost\"\n {...props}\n />\n);\n\nexport type QueueItemAttachmentProps = ComponentProps<\"div\">;\n\nexport const QueueItemAttachment = ({ className, ...props }: QueueItemAttachmentProps) => (\n <div className={cn(\"mt-1 flex flex-wrap gap-2\", className)} {...props} />\n);\n\nexport type QueueItemImageProps = ComponentProps<\"img\">;\n\nexport const QueueItemImage = ({ className, alt, ...props }: QueueItemImageProps) => (\n <Image\n height={32}\n width={32}\n showSkeleton={false}\n {...props}\n data-slot=\"queue-item-image\"\n alt={alt ?? \"\"}\n fit=\"cover\"\n className={cn(\"h-8 w-8 rounded border\", className)}\n />\n);\n\nexport type QueueItemFileProps = ComponentProps<\"span\">;\n\nexport const QueueItemFile = ({ children, className, ...props }: QueueItemFileProps) => (\n <span\n className={cn(\"flex items-center gap-1 rounded border bg-muted px-2 py-1 text-meta\", className)}\n {...props}\n >\n <PaperclipIcon size={12} />\n <span className=\"max-w-[100px] truncate\">{children}</span>\n </span>\n);\n\nexport type QueueListProps = ComponentProps<typeof ScrollArea>;\n\nexport const QueueList = ({ children, className, ...props }: QueueListProps) => (\n <ScrollArea className={cn(\"mt-2 -mb-1\", className)} {...props}>\n <div className=\"max-h-40 pe-4\">\n <ul>{children}</ul>\n </div>\n </ScrollArea>\n);\n\n// QueueSection - collapsible section container\nexport type QueueSectionProps = ComponentProps<typeof Collapsible>;\n\nexport const QueueSection = ({ className, defaultOpen = true, ...props }: QueueSectionProps) => (\n <Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />\n);\n\n// QueueSectionTrigger - section header/trigger\nexport type QueueSectionTriggerProps = ComponentProps<\"button\">;\n\nexport const QueueSectionTrigger = ({\n children,\n className,\n ...props\n}: QueueSectionTriggerProps) => (\n <CollapsibleTrigger asChild>\n <button\n className={cn(\n \"group flex w-full items-center justify-between rounded-md bg-muted/40 px-3 py-2 text-start font-medium text-muted-foreground text-body transition-colors hover:bg-muted\",\n className,\n )}\n type=\"button\"\n {...props}\n >\n {children}\n </button>\n </CollapsibleTrigger>\n);\n\n// QueueSectionLabel - label content with icon and count\nexport type QueueSectionLabelProps = ComponentProps<\"span\"> & {\n count?: number;\n label: string;\n icon?: React.ReactNode;\n};\n\nexport const QueueSectionLabel = ({\n count,\n label,\n icon,\n className,\n ...props\n}: QueueSectionLabelProps) => (\n <span className={cn(\"flex items-center gap-2\", className)} {...props}>\n <ChevronDownIcon className=\"size-4 transition-transform group-data-[state=closed]:-rotate-90\" />\n {icon}\n <span>\n {count} {label}\n </span>\n </span>\n);\n\n// QueueSectionContent - collapsible content area\nexport type QueueSectionContentProps = ComponentProps<typeof CollapsibleContent>;\n\nexport const QueueSectionContent = ({ className, ...props }: QueueSectionContentProps) => (\n <CollapsibleContent\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:slide-in-from-top-2 data-[state=closed]:slide-out-to-top-2 data-[state=closed]:fade-out-0 data-[state=open]:[--tw-ease:var(--ease-entrance)] data-[state=closed]:[--tw-ease:var(--ease-exit)] outline-none\",\n className,\n )}\n {...props}\n />\n);\n\nexport type QueueProps = ComponentProps<\"div\">;\n\nexport const Queue = ({ className, ...props }: QueueProps) => (\n <div\n className={cn(\n \"flex flex-col gap-2 rounded-xl border border-border bg-background px-3 pt-2 pb-2 shadow-xs\",\n className,\n )}\n {...props}\n />\n);\n"],"mappings":"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","names":[]}