@elabs-ai/components-ai 4.2.0 → 5.1.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.
- package/README.md +1 -1
- package/dist/{_audio-player-media-chrome-T3XVXWRZ.js → _audio-player-media-chrome.js} +1 -1
- package/dist/_code-block-theme.js +12 -0
- package/dist/_code-block-theme.js.map +1 -0
- package/dist/{_flow-boundary-SHNWLQG5.js → _flow-boundary.js} +1 -1
- package/dist/_flow-boundary.js.map +1 -0
- package/dist/_flow-lazy.js +8 -0
- package/dist/_flow-lazy.js.map +1 -0
- package/dist/_lazy-boundary-conformance.js +2 -0
- package/dist/_lazy-boundary-conformance.js.map +1 -0
- package/dist/_lazy-cjk.js +13 -0
- package/dist/_lazy-cjk.js.map +1 -0
- package/dist/_lazy-engine-boundary.js +9 -0
- package/dist/_lazy-engine-boundary.js.map +1 -0
- package/dist/_lazy-math.js +13 -0
- package/dist/_lazy-math.js.map +1 -0
- package/dist/_lazy-mermaid.js +12 -0
- package/dist/_lazy-mermaid.js.map +1 -0
- package/dist/_mermaid-error-panel.js +9 -0
- package/dist/_mermaid-error-panel.js.map +1 -0
- package/dist/{_persona-rive-JEG44YHX.js → _persona-rive.js} +19 -24
- package/dist/_persona-rive.js.map +1 -0
- package/dist/_streamdown-i18n.js +21 -0
- package/dist/_streamdown-i18n.js.map +1 -0
- package/dist/_streamdown-safety.js +10 -0
- package/dist/_streamdown-safety.js.map +1 -0
- package/dist/_theme-scope-store.js +13 -0
- package/dist/_theme-scope-store.js.map +1 -0
- package/dist/a2ui/a2ui-surface.js +17 -0
- package/dist/a2ui/a2ui-surface.js.map +1 -0
- package/dist/a2ui/core/catalog.generated.js +10 -0
- package/dist/a2ui/core/catalog.generated.js.map +1 -0
- package/dist/a2ui/core/complete-json.js +10 -0
- package/dist/a2ui/core/complete-json.js.map +1 -0
- package/dist/a2ui/core/index.js +37 -0
- package/dist/a2ui/core/index.js.map +1 -0
- package/dist/a2ui/core/schema.js +11 -0
- package/dist/a2ui/core/schema.js.map +1 -0
- package/dist/a2ui/core/spec.js +10 -0
- package/dist/a2ui/core/spec.js.map +1 -0
- package/dist/a2ui/core/validate.js +16 -0
- package/dist/a2ui/core/validate.js.map +1 -0
- package/dist/a2ui/index.js +54 -0
- package/dist/a2ui/index.js.map +1 -0
- package/dist/a2ui/ui-catalog.js +18 -0
- package/dist/a2ui/ui-catalog.js.map +1 -0
- package/dist/agent-event.js +10 -0
- package/dist/agent-event.js.map +1 -0
- package/dist/agent-timeline.js +11 -0
- package/dist/agent-timeline.js.map +1 -0
- package/dist/agent.js +25 -0
- package/dist/agent.js.map +1 -0
- package/dist/artifact.js +23 -0
- package/dist/artifact.js.map +1 -0
- package/dist/asset-preview.js +22 -0
- package/dist/asset-preview.js.map +1 -0
- package/dist/attachments.js +33 -0
- package/dist/attachments.js.map +1 -0
- package/dist/audio-player.js +30 -0
- package/dist/audio-player.js.map +1 -0
- package/dist/audio-visualizer.js +9 -0
- package/dist/audio-visualizer.js.map +1 -0
- package/dist/canvas.js +10 -0
- package/dist/canvas.js.map +1 -0
- package/dist/chain-of-thought.js +22 -0
- package/dist/chain-of-thought.js.map +1 -0
- package/dist/chat-greeting.js +8 -0
- package/dist/chat-greeting.js.map +1 -0
- package/dist/chat-shell.js +9 -0
- package/dist/chat-shell.js.map +1 -0
- package/dist/checkpoint.js +13 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/chunk-2XOIG5TG.js +72 -0
- package/dist/chunk-2XOIG5TG.js.map +1 -0
- package/dist/chunk-34CQ2VHM.js +172 -0
- package/dist/chunk-34CQ2VHM.js.map +1 -0
- package/dist/chunk-3TDHPXZZ.js +60 -0
- package/dist/chunk-3TDHPXZZ.js.map +1 -0
- package/dist/chunk-3XNPMDZY.js +478 -0
- package/dist/chunk-3XNPMDZY.js.map +1 -0
- package/dist/chunk-3YPT3V7L.js +56 -0
- package/dist/chunk-3YPT3V7L.js.map +1 -0
- package/dist/chunk-475UEU2Q.js +87 -0
- package/dist/chunk-475UEU2Q.js.map +1 -0
- package/dist/chunk-4CBTII2I.js +95 -0
- package/dist/chunk-4CBTII2I.js.map +1 -0
- package/dist/chunk-4XRNIZMG.js +22 -0
- package/dist/chunk-4XRNIZMG.js.map +1 -0
- package/dist/chunk-5NNWUKK5.js +2 -0
- package/dist/chunk-5NNWUKK5.js.map +1 -0
- package/dist/chunk-5UOMU27Z.js +164 -0
- package/dist/chunk-5UOMU27Z.js.map +1 -0
- package/dist/chunk-5VGYZ62I.js +264 -0
- package/dist/chunk-5VGYZ62I.js.map +1 -0
- package/dist/chunk-6IQPH2OS.js +21 -0
- package/dist/chunk-6IQPH2OS.js.map +1 -0
- package/dist/chunk-6PB2MXCI.js +482 -0
- package/dist/chunk-6PB2MXCI.js.map +1 -0
- package/dist/chunk-7FD5CZEU.js +215 -0
- package/dist/chunk-7FD5CZEU.js.map +1 -0
- package/dist/chunk-7RL6KZDF.js +266 -0
- package/dist/chunk-7RL6KZDF.js.map +1 -0
- package/dist/chunk-ADOYAGGK.js +482 -0
- package/dist/chunk-ADOYAGGK.js.map +1 -0
- package/dist/chunk-BA3AEMOU.js +21 -0
- package/dist/chunk-BA3AEMOU.js.map +1 -0
- package/dist/chunk-BBHFOYML.js +336 -0
- package/dist/chunk-BBHFOYML.js.map +1 -0
- package/dist/chunk-BHUVISIY.js +221 -0
- package/dist/chunk-BHUVISIY.js.map +1 -0
- package/dist/chunk-BM2LWERA.js +100 -0
- package/dist/chunk-BM2LWERA.js.map +1 -0
- package/dist/chunk-CMOYXNPJ.js +211 -0
- package/dist/chunk-CMOYXNPJ.js.map +1 -0
- package/dist/chunk-CZP3RHC4.js +74 -0
- package/dist/chunk-CZP3RHC4.js.map +1 -0
- package/dist/chunk-DDG5JHQT.js +112 -0
- package/dist/chunk-DDG5JHQT.js.map +1 -0
- package/dist/chunk-DJODXO7I.js +38 -0
- package/dist/chunk-DJODXO7I.js.map +1 -0
- package/dist/chunk-DWJDJLPU.js +192 -0
- package/dist/chunk-DWJDJLPU.js.map +1 -0
- package/dist/chunk-DZW6LM22.js +45 -0
- package/dist/chunk-DZW6LM22.js.map +1 -0
- package/dist/chunk-ELGCZLZF.js +305 -0
- package/dist/chunk-ELGCZLZF.js.map +1 -0
- package/dist/chunk-EV3U6ZZB.js +209 -0
- package/dist/chunk-EV3U6ZZB.js.map +1 -0
- package/dist/chunk-EY7LVUKQ.js +87 -0
- package/dist/chunk-EY7LVUKQ.js.map +1 -0
- package/dist/chunk-F4C74F7M.js +230 -0
- package/dist/chunk-F4C74F7M.js.map +1 -0
- package/dist/chunk-FERYDFZA.js +108 -0
- package/dist/chunk-FERYDFZA.js.map +1 -0
- package/dist/chunk-FUIYG2DS.js +166 -0
- package/dist/chunk-FUIYG2DS.js.map +1 -0
- package/dist/chunk-GA7M34KU.js +236 -0
- package/dist/chunk-GA7M34KU.js.map +1 -0
- package/dist/chunk-GKYZT3LA.js +136 -0
- package/dist/chunk-GKYZT3LA.js.map +1 -0
- package/dist/chunk-H42LRUQ4.js +102 -0
- package/dist/chunk-H42LRUQ4.js.map +1 -0
- package/dist/chunk-HVXM2G5F.js +222 -0
- package/dist/chunk-HVXM2G5F.js.map +1 -0
- package/dist/chunk-I4RDAGVY.js +53 -0
- package/dist/chunk-I4RDAGVY.js.map +1 -0
- package/dist/chunk-IQJG4IDX.js +208 -0
- package/dist/chunk-IQJG4IDX.js.map +1 -0
- package/dist/chunk-IX2PXG4Y.js +144 -0
- package/dist/chunk-IX2PXG4Y.js.map +1 -0
- package/dist/chunk-IXICSOOP.js +287 -0
- package/dist/chunk-IXICSOOP.js.map +1 -0
- package/dist/chunk-JY5HSN4D.js +84 -0
- package/dist/chunk-JY5HSN4D.js.map +1 -0
- package/dist/chunk-L4SU3QJV.js +174 -0
- package/dist/chunk-L4SU3QJV.js.map +1 -0
- package/dist/chunk-LMNSBOBG.js +19 -0
- package/dist/chunk-LMNSBOBG.js.map +1 -0
- package/dist/chunk-MBI7D6IL.js +42 -0
- package/dist/chunk-MBI7D6IL.js.map +1 -0
- package/dist/chunk-MLFMYCPH.js +192 -0
- package/dist/chunk-MLFMYCPH.js.map +1 -0
- package/dist/chunk-MOK66734.js +33 -0
- package/dist/chunk-MOK66734.js.map +1 -0
- package/dist/chunk-MXTAWPYM.js +196 -0
- package/dist/chunk-MXTAWPYM.js.map +1 -0
- package/dist/chunk-NCL2UZKI.js +96 -0
- package/dist/chunk-NCL2UZKI.js.map +1 -0
- package/dist/chunk-NET4YLRY.js +107 -0
- package/dist/chunk-NET4YLRY.js.map +1 -0
- package/dist/chunk-NIG67CFM.js +329 -0
- package/dist/chunk-NIG67CFM.js.map +1 -0
- package/dist/chunk-NMVNNO3L.js +10 -0
- package/dist/chunk-NMVNNO3L.js.map +1 -0
- package/dist/chunk-NP7VID4K.js +2 -0
- package/dist/chunk-NP7VID4K.js.map +1 -0
- package/dist/chunk-NPUCMAG2.js +382 -0
- package/dist/chunk-NPUCMAG2.js.map +1 -0
- package/dist/chunk-NSCLRHES.js +102 -0
- package/dist/chunk-NSCLRHES.js.map +1 -0
- package/dist/chunk-OEUXHVFW.js +86 -0
- package/dist/chunk-OEUXHVFW.js.map +1 -0
- package/dist/chunk-ONBHFIMZ.js +137 -0
- package/dist/chunk-ONBHFIMZ.js.map +1 -0
- package/dist/chunk-OPH2HHL2.js +228 -0
- package/dist/chunk-OPH2HHL2.js.map +1 -0
- package/dist/chunk-OQERHIQP.js +287 -0
- package/dist/chunk-OQERHIQP.js.map +1 -0
- package/dist/chunk-P6YJAZFG.js +103 -0
- package/dist/chunk-P6YJAZFG.js.map +1 -0
- package/dist/chunk-PG7ERDOD.js +65 -0
- package/dist/chunk-PG7ERDOD.js.map +1 -0
- package/dist/chunk-PIB5JBMA.js +90 -0
- package/dist/chunk-PIB5JBMA.js.map +1 -0
- package/dist/chunk-PQ5BPIWQ.js +22 -0
- package/dist/chunk-PQ5BPIWQ.js.map +1 -0
- package/dist/chunk-PQBF6ZIQ.js +55 -0
- package/dist/chunk-PQBF6ZIQ.js.map +1 -0
- package/dist/chunk-PTWCTSZL.js +173 -0
- package/dist/chunk-PTWCTSZL.js.map +1 -0
- package/dist/chunk-QJUNPVOS.js +101 -0
- package/dist/chunk-QJUNPVOS.js.map +1 -0
- package/dist/chunk-QOBSEGCG.js +996 -0
- package/dist/chunk-QOBSEGCG.js.map +1 -0
- package/dist/chunk-QUDZOTRV.js +43 -0
- package/dist/chunk-QUDZOTRV.js.map +1 -0
- package/dist/chunk-RB7G65JC.js +213 -0
- package/dist/chunk-RB7G65JC.js.map +1 -0
- package/dist/chunk-RPHAOR7P.js +45 -0
- package/dist/chunk-RPHAOR7P.js.map +1 -0
- package/dist/chunk-RTCSH2AN.js +331 -0
- package/dist/chunk-RTCSH2AN.js.map +1 -0
- package/dist/chunk-S3ISPW3A.js +28 -0
- package/dist/chunk-S3ISPW3A.js.map +1 -0
- package/dist/chunk-SEVN656N.js +36 -0
- package/dist/chunk-SEVN656N.js.map +1 -0
- package/dist/chunk-SKVG672B.js +64 -0
- package/dist/chunk-SKVG672B.js.map +1 -0
- package/dist/chunk-SSGJ6MEM.js +1076 -0
- package/dist/chunk-SSGJ6MEM.js.map +1 -0
- package/dist/chunk-SSKZ2AED.js +118 -0
- package/dist/chunk-SSKZ2AED.js.map +1 -0
- package/dist/chunk-T56JFGXI.js +561 -0
- package/dist/chunk-T56JFGXI.js.map +1 -0
- package/dist/chunk-TCB6O74A.js +137 -0
- package/dist/chunk-TCB6O74A.js.map +1 -0
- package/dist/chunk-TTEYPO6X.js +57 -0
- package/dist/chunk-TTEYPO6X.js.map +1 -0
- package/dist/chunk-TWPHAJNM.js +462 -0
- package/dist/chunk-TWPHAJNM.js.map +1 -0
- package/dist/chunk-TXJJLP4O.js +336 -0
- package/dist/chunk-TXJJLP4O.js.map +1 -0
- package/dist/chunk-TZ6TB7VM.js +245 -0
- package/dist/chunk-TZ6TB7VM.js.map +1 -0
- package/dist/chunk-UBLKJJ7K.js +278 -0
- package/dist/chunk-UBLKJJ7K.js.map +1 -0
- package/dist/chunk-UCJ6IUFS.js +52 -0
- package/dist/chunk-UCJ6IUFS.js.map +1 -0
- package/dist/chunk-UCKFJ3QF.js +197 -0
- package/dist/chunk-UCKFJ3QF.js.map +1 -0
- package/dist/chunk-UPXSLILN.js +20 -0
- package/dist/chunk-UPXSLILN.js.map +1 -0
- package/dist/chunk-V4DORGWJ.js +92 -0
- package/dist/chunk-V4DORGWJ.js.map +1 -0
- package/dist/chunk-V6E5DYZD.js +80 -0
- package/dist/chunk-V6E5DYZD.js.map +1 -0
- package/dist/chunk-VLLYKF3P.js +96 -0
- package/dist/chunk-VLLYKF3P.js.map +1 -0
- package/dist/chunk-VLNBTOH6.js +43 -0
- package/dist/chunk-VLNBTOH6.js.map +1 -0
- package/dist/chunk-WMDSMSPL.js +303 -0
- package/dist/chunk-WMDSMSPL.js.map +1 -0
- package/dist/chunk-WU5BLGTF.js +149 -0
- package/dist/chunk-WU5BLGTF.js.map +1 -0
- package/dist/chunk-WYTZVSGK.js +245 -0
- package/dist/chunk-WYTZVSGK.js.map +1 -0
- package/dist/chunk-X47QMM6K.js +187 -0
- package/dist/chunk-X47QMM6K.js.map +1 -0
- package/dist/chunk-YHUSW7D2.js +40 -0
- package/dist/chunk-YHUSW7D2.js.map +1 -0
- package/dist/chunk-YSKD7UXX.js +126 -0
- package/dist/chunk-YSKD7UXX.js.map +1 -0
- package/dist/chunk-YYRDGWOU.js +72 -0
- package/dist/chunk-YYRDGWOU.js.map +1 -0
- package/dist/chunk-ZIGZMWM7.js +19 -0
- package/dist/chunk-ZIGZMWM7.js.map +1 -0
- package/dist/chunk-ZKRTUKNP.js +40 -0
- package/dist/chunk-ZKRTUKNP.js.map +1 -0
- package/dist/code-block.js +38 -0
- package/dist/code-block.js.map +1 -0
- package/dist/commit.js +51 -0
- package/dist/commit.js.map +1 -0
- package/dist/composer.js +15 -0
- package/dist/composer.js.map +1 -0
- package/dist/confirmation.js +53 -0
- package/dist/confirmation.js.map +1 -0
- package/dist/connection.js +8 -0
- package/dist/connection.js.map +1 -0
- package/dist/context-panel.js +25 -0
- package/dist/context-panel.js.map +1 -0
- package/dist/controls.js +10 -0
- package/dist/controls.js.map +1 -0
- package/dist/conversation.js +19 -0
- package/dist/conversation.js.map +1 -0
- package/dist/diff-view.js +15 -0
- package/dist/diff-view.js.map +1 -0
- package/dist/edge.js +10 -0
- package/dist/edge.js.map +1 -0
- package/dist/environment-variables.js +29 -0
- package/dist/environment-variables.js.map +1 -0
- package/dist/file-tree.js +23 -0
- package/dist/file-tree.js.map +1 -0
- package/dist/gallery.js +9 -0
- package/dist/gallery.js.map +1 -0
- package/dist/grouped-parts.js +21 -0
- package/dist/grouped-parts.js.map +1 -0
- package/dist/image.js +9 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +366 -98
- package/dist/index.js +672 -13577
- package/dist/index.js.map +1 -1
- package/dist/inline-citation.js +39 -0
- package/dist/inline-citation.js.map +1 -0
- package/dist/jsx-preview.js +17 -0
- package/dist/jsx-preview.js.map +1 -0
- package/dist/markdown-view.js +17 -0
- package/dist/markdown-view.js.map +1 -0
- package/dist/message-compare.js +12 -0
- package/dist/message-compare.js.map +1 -0
- package/dist/message-edit.js +19 -0
- package/dist/message-edit.js.map +1 -0
- package/dist/message-feedback.js +11 -0
- package/dist/message-feedback.js.map +1 -0
- package/dist/message-form-spec.js +38 -0
- package/dist/message-form-spec.js.map +1 -0
- package/dist/message-form.js +26 -0
- package/dist/message-form.js.map +1 -0
- package/dist/message-table-spec.js +26 -0
- package/dist/message-table-spec.js.map +1 -0
- package/dist/message-table.js +12 -0
- package/dist/message-table.js.map +1 -0
- package/dist/message.js +51 -0
- package/dist/message.js.map +1 -0
- package/dist/mic-selector.js +27 -0
- package/dist/mic-selector.js.map +1 -0
- package/dist/model-provider-logo.js +13 -0
- package/dist/model-provider-logo.js.map +1 -0
- package/dist/motion-config.js +9 -0
- package/dist/motion-config.js.map +1 -0
- package/dist/node.js +22 -0
- package/dist/node.js.map +1 -0
- package/dist/open-in-chat.js +31 -0
- package/dist/open-in-chat.js.map +1 -0
- package/dist/package-info.js +25 -0
- package/dist/package-info.js.map +1 -0
- package/dist/panel.js +10 -0
- package/dist/panel.js.map +1 -0
- package/dist/part-groups.js +18 -0
- package/dist/part-groups.js.map +1 -0
- package/dist/permission-mode-select.js +9 -0
- package/dist/permission-mode-select.js.map +1 -0
- package/dist/persona-sources.js +8 -0
- package/dist/persona-sources.js.map +1 -0
- package/dist/persona.js +14 -0
- package/dist/persona.js.map +1 -0
- package/dist/plan.js +32 -0
- package/dist/plan.js.map +1 -0
- package/dist/prompt-input-effort.js +9 -0
- package/dist/prompt-input-effort.js.map +1 -0
- package/dist/prompt-input-mode.js +10 -0
- package/dist/prompt-input-mode.js.map +1 -0
- package/dist/prompt-input-slash.js +12 -0
- package/dist/prompt-input-slash.js.map +1 -0
- package/dist/prompt-input.js +89 -0
- package/dist/prompt-input.js.map +1 -0
- package/dist/queue.js +37 -0
- package/dist/queue.js.map +1 -0
- package/dist/reasoning.js +23 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/sandbox.js +28 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schema-display.js +33 -0
- package/dist/schema-display.js.map +1 -0
- package/dist/selection-toolbar.js +11 -0
- package/dist/selection-toolbar.js.map +1 -0
- package/dist/session-header.js +9 -0
- package/dist/session-header.js.map +1 -0
- package/dist/session-status-bar.js +9 -0
- package/dist/session-status-bar.js.map +1 -0
- package/dist/shimmer.js +9 -0
- package/dist/shimmer.js.map +1 -0
- package/dist/snippet.js +17 -0
- package/dist/snippet.js.map +1 -0
- package/dist/sources.js +17 -0
- package/dist/sources.js.map +1 -0
- package/dist/speech-input.js +9 -0
- package/dist/speech-input.js.map +1 -0
- package/dist/stack-trace.js +27 -0
- package/dist/stack-trace.js.map +1 -0
- package/dist/suggestion.js +16 -0
- package/dist/suggestion.js.map +1 -0
- package/dist/task.js +18 -0
- package/dist/task.js.map +1 -0
- package/dist/test-results.js +43 -0
- package/dist/test-results.js.map +1 -0
- package/dist/token-usage.js +27 -0
- package/dist/token-usage.js.map +1 -0
- package/dist/tool-result-card.js +9 -0
- package/dist/tool-result-card.js.map +1 -0
- package/dist/tool.js +27 -0
- package/dist/tool.js.map +1 -0
- package/dist/toolbar.js +10 -0
- package/dist/toolbar.js.map +1 -0
- package/dist/transcription.js +11 -0
- package/dist/transcription.js.map +1 -0
- package/dist/turn-status.js +9 -0
- package/dist/turn-status.js.map +1 -0
- package/dist/use-audio-level.js +9 -0
- package/dist/use-audio-level.js.map +1 -0
- package/dist/voice-selector.js +47 -0
- package/dist/voice-selector.js.map +1 -0
- package/dist/web-preview.js +19 -0
- package/dist/web-preview.js.map +1 -0
- package/package.json +11 -9
- package/schemas/a2ui-surface.v1.schema.json +5434 -0
- package/src/_chat-shell-rail.tsx +3 -1
- package/src/_flow-boundary.tsx +2 -2
- package/src/_persona-rive.tsx +27 -41
- package/src/a2ui/a2ui-surface.stories.tsx +662 -0
- package/src/a2ui/a2ui-surface.test.tsx +201 -0
- package/src/a2ui/a2ui-surface.tsx +354 -0
- package/src/a2ui/catalog.source.json +772 -0
- package/src/a2ui/core/a2ui-core.test.ts +178 -0
- package/src/a2ui/core/catalog.generated.ts +1019 -0
- package/src/a2ui/core/complete-json.ts +81 -0
- package/src/a2ui/core/index.ts +10 -0
- package/src/a2ui/core/schema.ts +111 -0
- package/src/a2ui/core/spec.ts +148 -0
- package/src/a2ui/core/validate.ts +266 -0
- package/src/a2ui/index.ts +7 -0
- package/src/a2ui/ui-catalog.tsx +225 -0
- package/src/a2ui/wave1-chart-spec.test.ts +87 -0
- package/src/a2ui/wave2-chart-spec.test.ts +108 -0
- package/src/agentic-workspace.stories.tsx +6 -45
- package/src/chat-shell.test.tsx +40 -10
- package/src/chat-shell.tsx +90 -44
- package/src/chat.stories.tsx +47 -42
- package/src/composer.stories.tsx +4 -5
- package/src/composer.test.tsx +45 -0
- package/src/composer.tsx +104 -21
- package/src/context-panel.stories.tsx +1 -1
- package/src/context-panel.tsx +1 -1
- package/src/conversation.stories.tsx +1 -0
- package/src/conversation.test.tsx +16 -0
- package/src/conversation.tsx +16 -2
- package/src/diff-view.stories.tsx +79 -0
- package/src/diff-view.tsx +3 -2
- package/src/file-tree.tsx +24 -17
- package/src/gallery.stories.tsx +42 -0
- package/src/gallery.tsx +2 -2
- package/src/grouped-parts.stories.tsx +23 -0
- package/src/grouped-parts.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/message-compare.stories.tsx +40 -0
- package/src/message-compare.tsx +2 -1
- package/src/message-form.tsx +1 -1
- package/src/message-table.stories.tsx +24 -0
- package/src/message-table.tsx +1 -1
- package/src/package-info.tsx +12 -9
- package/src/reasoning.stories.tsx +27 -0
- package/src/reasoning.tsx +1 -1
- package/src/session-header.stories.tsx +35 -1
- package/src/session-header.tsx +1 -1
- package/src/sources.stories.tsx +30 -0
- package/src/sources.tsx +4 -1
- package/src/templates-ai-assistant.stories.tsx +151 -87
- package/src/tool.stories.tsx +39 -0
- package/src/tool.tsx +3 -3
- package/src/toolbar.tsx +0 -10
- package/dist/_flow-boundary-SHNWLQG5.js.map +0 -1
- package/dist/_persona-rive-JEG44YHX.js.map +0 -1
- /package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map} +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/message-table-spec.ts
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
var cellFormatSchema = z.enum(["text", "number", "currency", "percent", "date", "badge"]);
|
|
6
|
+
var columnSpecSchema = z.object({
|
|
7
|
+
/** The row-object key this column reads. */
|
|
8
|
+
key: z.string(),
|
|
9
|
+
/** Header label. Falls back to the `key` when omitted. */
|
|
10
|
+
label: z.string().optional(),
|
|
11
|
+
/** How cells in this column are formatted + aligned. @default "text" */
|
|
12
|
+
format: cellFormatSchema.optional()
|
|
13
|
+
});
|
|
14
|
+
var tableSpecSchema = z.object({
|
|
15
|
+
/** Optional heading + accessible label for the table. */
|
|
16
|
+
title: z.string().optional(),
|
|
17
|
+
/** Ordered column definitions. */
|
|
18
|
+
columns: z.array(columnSpecSchema),
|
|
19
|
+
/** Row objects keyed by column `key`. */
|
|
20
|
+
rows: z.array(z.record(z.string(), z.unknown())),
|
|
21
|
+
/** Message shown when there are zero rows. @default "No rows to display" */
|
|
22
|
+
emptyText: z.string().optional()
|
|
23
|
+
});
|
|
24
|
+
function isPlainObject(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function strOrUndefined(value) {
|
|
28
|
+
return typeof value === "string" ? value : void 0;
|
|
29
|
+
}
|
|
30
|
+
function normalizeTableSpec(spec) {
|
|
31
|
+
if (!isPlainObject(spec)) {
|
|
32
|
+
return { ok: false, reason: "Table specification is missing or malformed." };
|
|
33
|
+
}
|
|
34
|
+
const columns = [];
|
|
35
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36
|
+
if (Array.isArray(spec.columns)) {
|
|
37
|
+
for (const candidate of spec.columns) {
|
|
38
|
+
const parsed = columnSpecSchema.safeParse(candidate);
|
|
39
|
+
if (!parsed.success) continue;
|
|
40
|
+
if (seen.has(parsed.data.key)) continue;
|
|
41
|
+
seen.add(parsed.data.key);
|
|
42
|
+
columns.push(parsed.data);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const rows = Array.isArray(spec.rows) ? spec.rows.filter(isPlainObject) : [];
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
spec: {
|
|
49
|
+
title: strOrUndefined(spec.title),
|
|
50
|
+
columns,
|
|
51
|
+
rows,
|
|
52
|
+
emptyText: strOrUndefined(spec.emptyText)
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
var EMPTY_CELL = "\u2014";
|
|
57
|
+
function isNumericFormat(format) {
|
|
58
|
+
return format === "number" || format === "currency" || format === "percent";
|
|
59
|
+
}
|
|
60
|
+
function isEmptyCell(value) {
|
|
61
|
+
return value === null || value === void 0 || value === "";
|
|
62
|
+
}
|
|
63
|
+
function toNumber(value) {
|
|
64
|
+
if (typeof value === "number") return value;
|
|
65
|
+
if (typeof value === "string" && value.trim() !== "") return Number(value);
|
|
66
|
+
return Number.NaN;
|
|
67
|
+
}
|
|
68
|
+
var numberFormatter = new Intl.NumberFormat(void 0, { maximumFractionDigits: 2 });
|
|
69
|
+
var currencyFormatter = new Intl.NumberFormat(void 0, {
|
|
70
|
+
style: "currency",
|
|
71
|
+
currency: "USD",
|
|
72
|
+
maximumFractionDigits: 0
|
|
73
|
+
});
|
|
74
|
+
var percentFormatter = new Intl.NumberFormat(void 0, {
|
|
75
|
+
style: "percent",
|
|
76
|
+
maximumFractionDigits: 1
|
|
77
|
+
});
|
|
78
|
+
function formatCellValue(value, format) {
|
|
79
|
+
switch (format) {
|
|
80
|
+
case "number": {
|
|
81
|
+
const n = toNumber(value);
|
|
82
|
+
return Number.isNaN(n) ? String(value) : numberFormatter.format(n);
|
|
83
|
+
}
|
|
84
|
+
case "currency": {
|
|
85
|
+
const n = toNumber(value);
|
|
86
|
+
return Number.isNaN(n) ? String(value) : currencyFormatter.format(n);
|
|
87
|
+
}
|
|
88
|
+
case "percent": {
|
|
89
|
+
const n = toNumber(value);
|
|
90
|
+
return Number.isNaN(n) ? String(value) : percentFormatter.format(n);
|
|
91
|
+
}
|
|
92
|
+
case "date": {
|
|
93
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
94
|
+
return Number.isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
return String(value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
var SUCCESS_WORDS = /* @__PURE__ */ new Set([
|
|
101
|
+
"active",
|
|
102
|
+
"success",
|
|
103
|
+
"successful",
|
|
104
|
+
"complete",
|
|
105
|
+
"completed",
|
|
106
|
+
"done",
|
|
107
|
+
"passed",
|
|
108
|
+
"pass",
|
|
109
|
+
"approved",
|
|
110
|
+
"ok",
|
|
111
|
+
"online",
|
|
112
|
+
"enabled",
|
|
113
|
+
"healthy",
|
|
114
|
+
"live",
|
|
115
|
+
"paid",
|
|
116
|
+
"yes",
|
|
117
|
+
"true"
|
|
118
|
+
]);
|
|
119
|
+
var WARNING_WORDS = /* @__PURE__ */ new Set([
|
|
120
|
+
"pending",
|
|
121
|
+
"warning",
|
|
122
|
+
"in progress",
|
|
123
|
+
"in-progress",
|
|
124
|
+
"review",
|
|
125
|
+
"in review",
|
|
126
|
+
"waiting",
|
|
127
|
+
"partial",
|
|
128
|
+
"degraded",
|
|
129
|
+
"low",
|
|
130
|
+
"hold",
|
|
131
|
+
"on hold"
|
|
132
|
+
]);
|
|
133
|
+
var DESTRUCTIVE_WORDS = /* @__PURE__ */ new Set([
|
|
134
|
+
"failed",
|
|
135
|
+
"fail",
|
|
136
|
+
"error",
|
|
137
|
+
"rejected",
|
|
138
|
+
"inactive",
|
|
139
|
+
"offline",
|
|
140
|
+
"disabled",
|
|
141
|
+
"blocked",
|
|
142
|
+
"cancelled",
|
|
143
|
+
"canceled",
|
|
144
|
+
"overdue",
|
|
145
|
+
"no",
|
|
146
|
+
"false",
|
|
147
|
+
"critical",
|
|
148
|
+
"high",
|
|
149
|
+
"expired"
|
|
150
|
+
]);
|
|
151
|
+
var INFO_WORDS = /* @__PURE__ */ new Set(["info", "new", "draft", "queued", "scheduled", "beta", "todo", "open"]);
|
|
152
|
+
function badgeToneForValue(value) {
|
|
153
|
+
const key = String(value).trim().toLowerCase();
|
|
154
|
+
if (SUCCESS_WORDS.has(key)) return "success";
|
|
155
|
+
if (WARNING_WORDS.has(key)) return "warning";
|
|
156
|
+
if (DESTRUCTIVE_WORDS.has(key)) return "destructive";
|
|
157
|
+
if (INFO_WORDS.has(key)) return "info";
|
|
158
|
+
return "secondary";
|
|
159
|
+
}
|
|
160
|
+
function compareCellValues(a, b, format) {
|
|
161
|
+
const aEmpty = isEmptyCell(a);
|
|
162
|
+
const bEmpty = isEmptyCell(b);
|
|
163
|
+
if (aEmpty && bEmpty) return 0;
|
|
164
|
+
if (aEmpty) return 1;
|
|
165
|
+
if (bEmpty) return -1;
|
|
166
|
+
if (isNumericFormat(format)) {
|
|
167
|
+
return toNumber(a) - toNumber(b);
|
|
168
|
+
}
|
|
169
|
+
if (format === "date") {
|
|
170
|
+
const da = new Date(String(a)).getTime();
|
|
171
|
+
const db = new Date(String(b)).getTime();
|
|
172
|
+
return (Number.isNaN(da) ? 0 : da) - (Number.isNaN(db) ? 0 : db);
|
|
173
|
+
}
|
|
174
|
+
const na = toNumber(a);
|
|
175
|
+
const nb = toNumber(b);
|
|
176
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb)) return na - nb;
|
|
177
|
+
return String(a).localeCompare(String(b));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
cellFormatSchema,
|
|
182
|
+
columnSpecSchema,
|
|
183
|
+
tableSpecSchema,
|
|
184
|
+
normalizeTableSpec,
|
|
185
|
+
EMPTY_CELL,
|
|
186
|
+
isNumericFormat,
|
|
187
|
+
isEmptyCell,
|
|
188
|
+
formatCellValue,
|
|
189
|
+
badgeToneForValue,
|
|
190
|
+
compareCellValues
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=chunk-DWJDJLPU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/message-table-spec.ts"],"sourcesContent":["/**\n * message-table-spec.ts — Serializable TableSpec for MessageTable.\n *\n * Pure data types + zod schemas (no React). The shape is what an LLM tool-call\n * emits when it wants to show tabular data inside a chat message. It is\n * column-oriented: `columns` declare the shape + per-column `format`, and each\n * `rows` entry is an object keyed by column `key`.\n *\n * The exported zod schemas are the source of truth downstream catalogs compile\n * prompts + validators from. Specs carry NO style-bearing props — the model\n * chooses the data and its semantic format, never the look.\n */\n\nimport { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Columns\n// ---------------------------------------------------------------------------\n\n/**\n * How a cell's value is rendered + aligned.\n * - `text` (default) — as-is, start-aligned.\n * - `number` / `currency` / `percent` — `Intl`-formatted, end-aligned, tabular.\n * - `date` — localized date.\n * - `badge` — a tonal badge whose tone is inferred from the value's semantics.\n */\nexport const cellFormatSchema = z.enum([\"text\", \"number\", \"currency\", \"percent\", \"date\", \"badge\"]);\nexport type CellFormat = z.infer<typeof cellFormatSchema>;\n\n/** One column definition. */\nexport const columnSpecSchema = z.object({\n /** The row-object key this column reads. */\n key: z.string(),\n /** Header label. Falls back to the `key` when omitted. */\n label: z.string().optional(),\n /** How cells in this column are formatted + aligned. @default \"text\" */\n format: cellFormatSchema.optional(),\n});\nexport type ColumnSpec = z.infer<typeof columnSpecSchema>;\n\n// ---------------------------------------------------------------------------\n// TableSpec\n// ---------------------------------------------------------------------------\n\n/** A single row: an object keyed by column `key`. */\nexport type TableRowData = Record<string, unknown>;\n\n/**\n * The serializable table specification produced by an LLM tool-call.\n * MessageTable reads this and renders a lightweight, message-body table.\n */\nexport const tableSpecSchema = z.object({\n /** Optional heading + accessible label for the table. */\n title: z.string().optional(),\n /** Ordered column definitions. */\n columns: z.array(columnSpecSchema),\n /** Row objects keyed by column `key`. */\n rows: z.array(z.record(z.string(), z.unknown())),\n /** Message shown when there are zero rows. @default \"No rows to display\" */\n emptyText: z.string().optional(),\n});\nexport type TableSpec = z.infer<typeof tableSpecSchema>;\n\n// ---------------------------------------------------------------------------\n// Normalization (lenient — streaming-tolerant, never throws)\n// ---------------------------------------------------------------------------\n\n/** A validated, render-ready TableSpec. */\nexport interface NormalizedTableSpec {\n title?: string;\n columns: ColumnSpec[];\n rows: TableRowData[];\n emptyText?: string;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction strOrUndefined(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\n/**\n * Lenient parse for the render path. NEVER throws. Individually invalid columns\n * are dropped and non-object rows are filtered out, so a partial (streaming)\n * spec still renders progressively. Returns `{ ok: false }` only when the whole\n * spec is unusable (not an object).\n */\nexport function normalizeTableSpec(\n spec: unknown,\n): { ok: true; spec: NormalizedTableSpec } | { ok: false; reason: string } {\n if (!isPlainObject(spec)) {\n return { ok: false, reason: \"Table specification is missing or malformed.\" };\n }\n\n const columns: ColumnSpec[] = [];\n const seen = new Set<string>();\n if (Array.isArray(spec.columns)) {\n for (const candidate of spec.columns) {\n const parsed = columnSpecSchema.safeParse(candidate);\n if (!parsed.success) continue; // drop invalid / still-streaming column\n if (seen.has(parsed.data.key)) continue; // de-dupe by key\n seen.add(parsed.data.key);\n columns.push(parsed.data);\n }\n }\n\n const rows: TableRowData[] = Array.isArray(spec.rows) ? spec.rows.filter(isPlainObject) : [];\n\n return {\n ok: true,\n spec: {\n title: strOrUndefined(spec.title),\n columns,\n rows,\n emptyText: strOrUndefined(spec.emptyText),\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Formatting\n// ---------------------------------------------------------------------------\n\n/** The em-dash shown for a missing / empty cell. */\nexport const EMPTY_CELL = \"—\";\n\n/** Whether a format is numeric (drives end-alignment + `tabular-nums`). */\nexport function isNumericFormat(format: CellFormat | undefined): boolean {\n return format === \"number\" || format === \"currency\" || format === \"percent\";\n}\n\n/** Whether a value is considered empty (renders the em-dash). */\nexport function isEmptyCell(value: unknown): boolean {\n return value === null || value === undefined || value === \"\";\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"string\" && value.trim() !== \"\") return Number(value);\n return Number.NaN;\n}\n\nconst numberFormatter = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 });\nconst currencyFormatter = new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency: \"USD\",\n maximumFractionDigits: 0,\n});\nconst percentFormatter = new Intl.NumberFormat(undefined, {\n style: \"percent\",\n maximumFractionDigits: 1,\n});\n\n/**\n * Format a present value to a display string per its column format.\n * Unknown formats fall through to `String(value)` (never throws). Empty values\n * are the caller's concern (render `EMPTY_CELL`). `percent` follows `Intl`\n * semantics — pass fractions (0.42 → \"42%\").\n */\nexport function formatCellValue(value: unknown, format: CellFormat | undefined): string {\n switch (format) {\n case \"number\": {\n const n = toNumber(value);\n return Number.isNaN(n) ? String(value) : numberFormatter.format(n);\n }\n case \"currency\": {\n const n = toNumber(value);\n return Number.isNaN(n) ? String(value) : currencyFormatter.format(n);\n }\n case \"percent\": {\n const n = toNumber(value);\n return Number.isNaN(n) ? String(value) : percentFormatter.format(n);\n }\n case \"date\": {\n const d = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(d.getTime())\n ? String(value)\n : d.toLocaleDateString(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" });\n }\n default:\n return String(value);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Badge tone inference (semantic, not style — the spec never picks a tone)\n// ---------------------------------------------------------------------------\n\nexport type BadgeTone = \"secondary\" | \"success\" | \"warning\" | \"destructive\" | \"info\";\n\nconst SUCCESS_WORDS = new Set([\n \"active\",\n \"success\",\n \"successful\",\n \"complete\",\n \"completed\",\n \"done\",\n \"passed\",\n \"pass\",\n \"approved\",\n \"ok\",\n \"online\",\n \"enabled\",\n \"healthy\",\n \"live\",\n \"paid\",\n \"yes\",\n \"true\",\n]);\nconst WARNING_WORDS = new Set([\n \"pending\",\n \"warning\",\n \"in progress\",\n \"in-progress\",\n \"review\",\n \"in review\",\n \"waiting\",\n \"partial\",\n \"degraded\",\n \"low\",\n \"hold\",\n \"on hold\",\n]);\nconst DESTRUCTIVE_WORDS = new Set([\n \"failed\",\n \"fail\",\n \"error\",\n \"rejected\",\n \"inactive\",\n \"offline\",\n \"disabled\",\n \"blocked\",\n \"cancelled\",\n \"canceled\",\n \"overdue\",\n \"no\",\n \"false\",\n \"critical\",\n \"high\",\n \"expired\",\n]);\nconst INFO_WORDS = new Set([\"info\", \"new\", \"draft\", \"queued\", \"scheduled\", \"beta\", \"todo\", \"open\"]);\n\n/** Infer a badge tone from a value's semantics (status vocabulary). */\nexport function badgeToneForValue(value: unknown): BadgeTone {\n const key = String(value).trim().toLowerCase();\n if (SUCCESS_WORDS.has(key)) return \"success\";\n if (WARNING_WORDS.has(key)) return \"warning\";\n if (DESTRUCTIVE_WORDS.has(key)) return \"destructive\";\n if (INFO_WORDS.has(key)) return \"info\";\n return \"secondary\";\n}\n\n// ---------------------------------------------------------------------------\n// Sorting\n// ---------------------------------------------------------------------------\n\n/** Compare two cell values for sorting, honoring numeric/date formats. */\nexport function compareCellValues(a: unknown, b: unknown, format: CellFormat | undefined): number {\n const aEmpty = isEmptyCell(a);\n const bEmpty = isEmptyCell(b);\n if (aEmpty && bEmpty) return 0;\n if (aEmpty) return 1; // empties sort last\n if (bEmpty) return -1;\n\n if (isNumericFormat(format)) {\n return toNumber(a) - toNumber(b);\n }\n if (format === \"date\") {\n const da = new Date(String(a)).getTime();\n const db = new Date(String(b)).getTime();\n return (Number.isNaN(da) ? 0 : da) - (Number.isNaN(db) ? 0 : db);\n }\n // Mixed/auto: numeric when both parse as numbers, else locale string compare.\n const na = toNumber(a);\n const nb = toNumber(b);\n if (!Number.isNaN(na) && !Number.isNaN(nb)) return na - nb;\n return String(a).localeCompare(String(b));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/_streamdown-safety.ts
|
|
4
|
+
var SANITIZER_OVERRIDE_KEYS = ["rehypePlugins"];
|
|
5
|
+
function stripSanitizerOverrides(props) {
|
|
6
|
+
const record = props;
|
|
7
|
+
for (const key of SANITIZER_OVERRIDE_KEYS) {
|
|
8
|
+
if (key in record) {
|
|
9
|
+
if (process.env.NODE_ENV !== "production") {
|
|
10
|
+
console.warn(
|
|
11
|
+
`[@elabs-ai/components-ai] \`${key}\` is not a supported prop on this component: it would replace Streamdown's default sanitiser chain (rehype-raw \u2192 rehype-sanitize \u2192 rehype-harden) and let untrusted markdown execute script in the host page. The value was ignored. Use \`allowedTags\` / \`literalTagContent\` to widen sanitisation instead \u2014 see issue #36.`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
delete record[key];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
var TRUSTED_PLUGIN_SLOT_ADVICE = "This is a supported, TRUSTED-CODE seam \u2014 the plugin still runs and nothing was dropped \u2014 so treat it as code you ship: it can put arbitrary markup in the page. If you only needed KaTeX options, prefer `createMathPlugin({ singleDollarTextMath, errorColor })` and leave KaTeX's `trust` option off for model-authored content. See docs/CSP-AND-NETWORK.md and issue #76.";
|
|
19
|
+
var isPluginObject = (value) => typeof value === "object" && value !== null;
|
|
20
|
+
function warnOnTrustedPluginSlots(overrides, internal) {
|
|
21
|
+
if (process.env.NODE_ENV === "production") return;
|
|
22
|
+
if (!overrides) return;
|
|
23
|
+
const consumer = overrides;
|
|
24
|
+
const defaults = internal;
|
|
25
|
+
if (isPluginObject(consumer.math)) {
|
|
26
|
+
const theirs = consumer.math.rehypePlugin;
|
|
27
|
+
const ours = isPluginObject(defaults.math) ? defaults.math.rehypePlugin : void 0;
|
|
28
|
+
if (theirs !== ours) {
|
|
29
|
+
console.warn(
|
|
30
|
+
`[@elabs-ai/components-ai] \`plugins.math.rehypePlugin\` is appended to the END of Streamdown's rehype pipeline, so it runs AFTER the sanitiser chain (rehype-raw \u2192 rehype-sanitize \u2192 rehype-harden) and whatever it emits is never re-sanitised. ${TRUSTED_PLUGIN_SLOT_ADVICE}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (isPluginObject(consumer.mermaid) && consumer.mermaid !== defaults.mermaid) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`[@elabs-ai/components-ai] \`plugins.mermaid\` never enters the rehype pipeline at all: its rendered SVG is injected as raw HTML through React's dangerous inner-HTML prop, i.e. after and outside the sanitiser chain (rehype-raw \u2192 rehype-sanitize \u2192 rehype-harden). A replacement plugin must sanitise its own output \u2014 brand-ui's default pins mermaid's strict security level. ${TRUSTED_PLUGIN_SLOT_ADVICE}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
stripSanitizerOverrides,
|
|
43
|
+
warnOnTrustedPluginSlots
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=chunk-DZW6LM22.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/_streamdown-safety.ts"],"sourcesContent":["/**\n * The runtime half of the #36 fix (the type-level `Omit` is the other half — see\n * `MarkdownViewProps`/`MessageResponseProps`).\n *\n * Streamdown installs its sanitiser chain (`rehype-raw` → `rehype-sanitize` →\n * `rehype-harden`) as the DEFAULT VALUE of its `rehypePlugins` prop — a plain\n * JS default parameter. Supplying the prop REPLACES the whole chain; there is\n * no merge (verified against `streamdown@2.5.0`'s `dist/chunk-BO2N2NFS.js`:\n * `xt = { raw, sanitize, harden }; gn = Object.values(xt)` is the default, and\n * the component signature reads `rehypePlugins: a = gn`).\n *\n * `MarkdownView`/`MessageResponse` render UNTRUSTED, model-authored markdown,\n * so that prop may never reach `<Streamdown>` — not even through a plain\n * JavaScript consumer, an `as any`/`as never` cast, or a wider\n * `{...spreadProps}` object a type-level `Omit` cannot see at runtime. `Omit`\n * closes the TypeScript surface; this function closes the runtime one. Both\n * are required — see issue #36.\n *\n * `remarkPlugins` is deliberately NOT stripped (PR #74 review, round 1). It is\n * the same *shape* of prop — a consumer array replaces Streamdown's own remark\n * defaults (`gfm`, `codeMeta`) rather than merging with them — but it is NOT a\n * sanitiser override: the remark stage runs strictly UPSTREAM of the rehype\n * chain, and Streamdown derives its rehype list without reading `remarkPlugins`\n * at all. Anything a remark plugin injects — a raw `html` mdast node, a\n * `data.hName`/`hChildren` hast element, an `onerror` via `hProperties`, a\n * `javascript:` link URL, a smuggled `raw` hast child — still passes through\n * `rehype-raw` → `rehype-sanitize` → `rehype-harden` before it can reach the\n * DOM. Measured with this strip removed: all five channels executed and none\n * produced a `<script>`, an `[onerror]` attribute or a `javascript:` href,\n * while the identical payload injected AFTER the sanitiser did. Stripping it\n * therefore removed real capability (remark-directive, footnotes, custom\n * syntax) and closed nothing.\n *\n * If a consumer needs to widen what survives SANITISATION, `allowedTags` /\n * `literalTagContent` are the supported seam: they MERGE into the sanitize\n * schema instead of replacing the pipeline outright.\n *\n * THIS LIST HAS A PARITY COUNTERPART, AND EDITING ONE SIDE FAILS CI (#75).\n * `scripts/check/rules/sanitizer-passthrough.mjs` declares the same key set as\n * `SAFE_RENDERERS[streamdown].dangerousProps` and reads THIS array literal back\n * out of this file, asserting set-equality in BOTH directions — so removing a\n * key here (the runtime stops stripping it) and removing a key there (the gate\n * stops looking for it) are each a red build, and so is renaming the constant\n * or rewriting it into a shape the gate cannot parse. Change the key set in\n * both places, in the same commit, or don't change it.\n */\nconst SANITIZER_OVERRIDE_KEYS = [\"rehypePlugins\"] as const;\n\n/**\n * Deletes `rehypePlugins` off `props` in place (if present) and warns in dev.\n * Call this BEFORE spreading `props` onto `<Streamdown>`.\n *\n * Takes a plain object rather than a typed `MarkdownViewProps`/\n * `MessageResponseProps` on purpose: the whole point is to catch a value that\n * reached this call with a wider runtime shape than its type says (a `.tsx`\n * consumer with no type checking at all, a force-cast, a spread of a bigger\n * object) — a parameter typed to the post-`Omit` props would only accept\n * values TypeScript already believes are safe.\n */\nexport function stripSanitizerOverrides(props: object): void {\n const record = props as Record<string, unknown>;\n for (const key of SANITIZER_OVERRIDE_KEYS) {\n if (key in record) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n `[@elabs-ai/components-ai] \\`${key}\\` is not a supported prop on this component: ` +\n \"it would replace Streamdown's default sanitiser chain (rehype-raw → \" +\n \"rehype-sanitize → rehype-harden) and let untrusted markdown execute \" +\n \"script in the host page. The value was ignored. Use `allowedTags` / \" +\n \"`literalTagContent` to widen sanitisation instead — see issue #36.\",\n );\n }\n delete record[key];\n }\n }\n}\n\n/**\n * The CONVERSE of `SANITIZER_OVERRIDE_KEYS`: the plugin slots that deliberately\n * stay reachable even though they land in the DOM *after* — or entirely outside\n * — Streamdown's sanitiser chain (#76).\n *\n * - **`math.rehypePlugin` is APPENDED to the END of the rehype pipeline**, i.e.\n * after `rehype-raw` → `rehype-sanitize` → `rehype-harden` (verified against\n * `streamdown@2.5.0`'s `dist/chunk-BO2N2NFS.js`). Its output is never\n * re-sanitised.\n * - **`mermaid` never enters the rehype/remark pipeline at all.** Its\n * `getMermaid().render()` result is written via `dangerouslySetInnerHTML`\n * (streamdown's only such sink). brand-ui's own default pins mermaid's\n * `securityLevel: \"strict\"` (`_lazy-mermaid.ts`).\n *\n * `cjk` (remark-stage, re-sanitised downstream), `code` (feeds `shikiTheme`\n * only) and `renderers` (ordinary React components) are NOT trust-bearing and\n * are deliberately absent — a warning that fired on them would be noise\n * consumers learn to ignore.\n *\n * **This is not a live vulnerability and must not be described as one.**\n * Supplying an executable `Pluggable` takes code in the consuming app's own\n * bundle, and an app author who can do that can already run code. The real,\n * non-hypothetical hazard is MISCONFIGURATION — most sharply KaTeX's `trust`\n * option (needed for anything beyond `singleDollarTextMath`/`errorColor`),\n * which silently disables KaTeX's own sanitisation for model-authored content\n * the consumer never intended to trust. Hence: WARN, never strip. Stripping\n * would break the legitimate trusted use and is the breaking change #76\n * explicitly rejects — `markdown-view.test.tsx`/`message.test.tsx` pin the slot\n * OPEN so a future \"hardening\" fails loudly.\n */\nconst TRUSTED_PLUGIN_SLOT_ADVICE =\n \"This is a supported, TRUSTED-CODE seam — the plugin still runs and nothing was \" +\n \"dropped — so treat it as code you ship: it can put arbitrary markup in the page. \" +\n \"If you only needed KaTeX options, prefer `createMathPlugin({ singleDollarTextMath, \" +\n \"errorColor })` and leave KaTeX's `trust` option off for model-authored content. \" +\n \"See docs/CSP-AND-NETWORK.md and issue #76.\";\n\nconst isPluginObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Dev-only `console.warn` when a consumer's `plugins` override replaces one of\n * the two slots that are NOT upstream of the sanitiser. Call it where the\n * consumer's overrides are merged over the internal defaults, passing both.\n *\n * **Reference-equality comparator, deliberately — not truthiness, not a deep\n * compare.** `useStreamdownPlugins()` returns a FRESH `code` object on every\n * theme change but a STABLE `math`/`mermaid`, so a truthiness check would fire\n * on every theme flip and a deep-equality check would stay silent for a consumer\n * who rebuilds an equivalent-looking object each render. For `math` the\n * comparison narrows further to the `rehypePlugin` member itself, so changing\n * only `remarkPlugin`/`getStyles` (upstream of, or outside, the DOM sink) stays\n * silent.\n *\n * Warns; never mutates. `stripSanitizerOverrides` is the other half of the\n * boundary — the props that must never be reachable at all.\n */\nexport function warnOnTrustedPluginSlots(overrides: object | undefined, internal: object): void {\n if (process.env.NODE_ENV === \"production\") return;\n if (!overrides) return;\n const consumer = overrides as Record<string, unknown>;\n const defaults = internal as Record<string, unknown>;\n\n if (isPluginObject(consumer.math)) {\n const theirs = consumer.math.rehypePlugin;\n const ours = isPluginObject(defaults.math) ? defaults.math.rehypePlugin : undefined;\n if (theirs !== ours) {\n console.warn(\n \"[@elabs-ai/components-ai] `plugins.math.rehypePlugin` is appended to the END of \" +\n \"Streamdown's rehype pipeline, so it runs AFTER the sanitiser chain \" +\n \"(rehype-raw → rehype-sanitize → rehype-harden) and whatever it emits is never \" +\n `re-sanitised. ${TRUSTED_PLUGIN_SLOT_ADVICE}`,\n );\n }\n }\n\n if (isPluginObject(consumer.mermaid) && consumer.mermaid !== defaults.mermaid) {\n // The message describes React's `dangerouslySetInnerHTML` in prose rather than\n // naming the identifier: the `trusted-types-sinks` check preserves string literals on\n // purpose (a sink hidden in a string is still a sink), so spelling the prop out\n // inside this warning makes the gate read THIS file as a new HTML-assigning\n // module. Comments like this one are stripped by that gate, so the name is safe\n // here and in the TSDoc above — just not inside a runtime string.\n console.warn(\n \"[@elabs-ai/components-ai] `plugins.mermaid` never enters the rehype pipeline at \" +\n \"all: its rendered SVG is injected as raw HTML through React's dangerous \" +\n \"inner-HTML prop, i.e. after and outside the sanitiser chain \" +\n \"(rehype-raw → rehype-sanitize → rehype-harden). A \" +\n \"replacement plugin must sanitise its own output — brand-ui's default pins \" +\n `mermaid's strict security level. ${TRUSTED_PLUGIN_SLOT_ADVICE}`,\n );\n }\n}\n"],"mappings":";;;AA8CA,IAAM,0BAA0B,CAAC,eAAe;AAazC,SAAS,wBAAwB,OAAqB;AAC3D,QAAM,SAAS;AACf,aAAW,OAAO,yBAAyB;AACzC,QAAI,OAAO,QAAQ;AACjB,UAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,gBAAQ;AAAA,UACN,+BAA+B,GAAG;AAAA,QAKpC;AAAA,MACF;AACA,aAAO,OAAO,GAAG;AAAA,IACnB;AAAA,EACF;AACF;AAgCA,IAAM,6BACJ;AAMF,IAAM,iBAAiB,CAAC,UACtB,OAAO,UAAU,YAAY,UAAU;AAmBlC,SAAS,yBAAyB,WAA+B,UAAwB;AAC9F,MAAI,QAAQ,IAAI,aAAa,aAAc;AAC3C,MAAI,CAAC,UAAW;AAChB,QAAM,WAAW;AACjB,QAAM,WAAW;AAEjB,MAAI,eAAe,SAAS,IAAI,GAAG;AACjC,UAAM,SAAS,SAAS,KAAK;AAC7B,UAAM,OAAO,eAAe,SAAS,IAAI,IAAI,SAAS,KAAK,eAAe;AAC1E,QAAI,WAAW,MAAM;AACnB,cAAQ;AAAA,QACN,8PAGmB,0BAA0B;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAEA,MAAI,eAAe,SAAS,OAAO,KAAK,SAAS,YAAY,SAAS,SAAS;AAO7E,YAAQ;AAAA,MACN,qYAKsC,0BAA0B;AAAA,IAClE;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/confirmation.tsx
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
AlertDescription,
|
|
7
|
+
AlertTitle,
|
|
8
|
+
APPROVAL_SCOPE_DESCRIPTION_KEYS,
|
|
9
|
+
Kbd,
|
|
10
|
+
Label,
|
|
11
|
+
RadioGroup,
|
|
12
|
+
RadioGroupItem,
|
|
13
|
+
StatusBadge,
|
|
14
|
+
Textarea,
|
|
15
|
+
useLocale
|
|
16
|
+
} from "@elabs-ai/components-ui";
|
|
17
|
+
import { Button } from "@elabs-ai/components-ui";
|
|
18
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
19
|
+
import { createContext, forwardRef, useContext, useId, useMemo, useState } from "react";
|
|
20
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
var ConfirmationContext = createContext(null);
|
|
22
|
+
var useConfirmation = () => {
|
|
23
|
+
const context = useContext(ConfirmationContext);
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error("Confirmation components must be used within Confirmation");
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
};
|
|
29
|
+
var Confirmation = ({
|
|
30
|
+
className,
|
|
31
|
+
approval,
|
|
32
|
+
state,
|
|
33
|
+
variant,
|
|
34
|
+
role,
|
|
35
|
+
children,
|
|
36
|
+
...props
|
|
37
|
+
}) => {
|
|
38
|
+
const titleId = useId();
|
|
39
|
+
const [reason, setReason] = useState("");
|
|
40
|
+
const contextValue = useMemo(
|
|
41
|
+
() => ({ approval, state, titleId, reason, setReason }),
|
|
42
|
+
[approval, state, titleId, reason]
|
|
43
|
+
);
|
|
44
|
+
if (!approval || state === "input-streaming" || state === "input-available") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const isResolved = state === "approval-responded" || state === "output-denied" || state === "output-available";
|
|
48
|
+
const isPending = state === "approval-requested";
|
|
49
|
+
const approved = isResolved ? approval.approved : void 0;
|
|
50
|
+
const railClass = approved === true ? "border-s-success" : approved === false ? "border-s-destructive" : isPending ? "border-s-border-strong" : "border-s-border";
|
|
51
|
+
return /* @__PURE__ */ jsx(ConfirmationContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
|
|
52
|
+
Alert,
|
|
53
|
+
{
|
|
54
|
+
variant: variant ?? "default",
|
|
55
|
+
role: role ?? (isPending ? "group" : "alert"),
|
|
56
|
+
"aria-labelledby": isPending ? titleId : void 0,
|
|
57
|
+
className: cn(
|
|
58
|
+
"flex flex-col gap-2 border-s-4 transition-colors duration-fast",
|
|
59
|
+
railClass,
|
|
60
|
+
isPending && "shadow-sm",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
...props,
|
|
64
|
+
children: [
|
|
65
|
+
approved === true ? /* @__PURE__ */ jsx(
|
|
66
|
+
StatusBadge,
|
|
67
|
+
{
|
|
68
|
+
className: "animate-in fade-in-0 self-start duration-base ease-entrance",
|
|
69
|
+
status: "complete",
|
|
70
|
+
children: "Approved"
|
|
71
|
+
}
|
|
72
|
+
) : null,
|
|
73
|
+
approved === false ? /* @__PURE__ */ jsx(
|
|
74
|
+
StatusBadge,
|
|
75
|
+
{
|
|
76
|
+
className: "animate-in fade-in-0 self-start duration-base ease-entrance",
|
|
77
|
+
status: "denied",
|
|
78
|
+
children: "Denied"
|
|
79
|
+
}
|
|
80
|
+
) : null,
|
|
81
|
+
children
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
) });
|
|
85
|
+
};
|
|
86
|
+
var ConfirmationTitle = ({ className, id, ...props }) => {
|
|
87
|
+
const { titleId } = useConfirmation();
|
|
88
|
+
return /* @__PURE__ */ jsx(AlertTitle, { id: id ?? titleId, className: cn("text-subtitle", className), ...props });
|
|
89
|
+
};
|
|
90
|
+
var ConfirmationDescription = ({ className, ...props }) => /* @__PURE__ */ jsx(AlertDescription, { className: cn("text-body text-muted-foreground", className), ...props });
|
|
91
|
+
var ConfirmationRequest = ({ children }) => {
|
|
92
|
+
const { state } = useConfirmation();
|
|
93
|
+
if (state !== "approval-requested") {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return children;
|
|
97
|
+
};
|
|
98
|
+
var ConfirmationAccepted = ({ children }) => {
|
|
99
|
+
const { approval, state } = useConfirmation();
|
|
100
|
+
if (!approval?.approved || state !== "approval-responded" && state !== "output-denied" && state !== "output-available") {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return children;
|
|
104
|
+
};
|
|
105
|
+
var ConfirmationRejected = ({ children }) => {
|
|
106
|
+
const { approval, state } = useConfirmation();
|
|
107
|
+
if (approval?.approved !== false || state !== "approval-responded" && state !== "output-denied" && state !== "output-available") {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return children;
|
|
111
|
+
};
|
|
112
|
+
var ConfirmationActions = ({ className, ...props }) => {
|
|
113
|
+
const { state } = useConfirmation();
|
|
114
|
+
if (state !== "approval-requested") {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return /* @__PURE__ */ jsx(
|
|
118
|
+
"div",
|
|
119
|
+
{
|
|
120
|
+
className: cn(
|
|
121
|
+
// The action band (research 11 §B.3 APPROVE-3): the divider is the SOLE
|
|
122
|
+
// cue between consequence and buttons, so `border-strong` is correct.
|
|
123
|
+
"flex w-full items-center justify-end gap-2 border-t border-border-strong pt-3",
|
|
124
|
+
className
|
|
125
|
+
),
|
|
126
|
+
...props
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
var ConfirmationAction = ({ className, ...props }) => /* @__PURE__ */ jsx(Button, { className: cn("h-8 px-3 text-body", className), type: "button", ...props });
|
|
131
|
+
var ConfirmationApprove = (props) => /* @__PURE__ */ jsx(ConfirmationAction, { variant: "default", ...props });
|
|
132
|
+
var ConfirmationDeny = (props) => /* @__PURE__ */ jsx(ConfirmationAction, { variant: "ghost", ...props });
|
|
133
|
+
var ApprovalCard = Confirmation;
|
|
134
|
+
var ApprovalCardTitle = ConfirmationTitle;
|
|
135
|
+
var ApprovalCardDescription = ConfirmationDescription;
|
|
136
|
+
var ApprovalCardRequest = ConfirmationRequest;
|
|
137
|
+
var ApprovalCardAccepted = ConfirmationAccepted;
|
|
138
|
+
var ApprovalCardRejected = ConfirmationRejected;
|
|
139
|
+
var ApprovalCardActions = ConfirmationActions;
|
|
140
|
+
var ApprovalCardAction = ConfirmationAction;
|
|
141
|
+
var ApprovalCardApprove = ConfirmationApprove;
|
|
142
|
+
var ApprovalCardDeny = ConfirmationDeny;
|
|
143
|
+
var ApprovalCardOptions = forwardRef(
|
|
144
|
+
function ApprovalCardOptions2({
|
|
145
|
+
options,
|
|
146
|
+
value,
|
|
147
|
+
defaultValue,
|
|
148
|
+
onValueChange,
|
|
149
|
+
onConfirm,
|
|
150
|
+
className,
|
|
151
|
+
dir,
|
|
152
|
+
"aria-labelledby": ariaLabelledBy,
|
|
153
|
+
...props
|
|
154
|
+
}, ref) {
|
|
155
|
+
const { t } = useLocale();
|
|
156
|
+
const { titleId, reason } = useConfirmation();
|
|
157
|
+
const baseId = useId();
|
|
158
|
+
const handleValueChange = (id) => {
|
|
159
|
+
onValueChange?.(id);
|
|
160
|
+
const option = options.find((candidate) => candidate.id === id);
|
|
161
|
+
if (option) onConfirm?.(option, reason.trim() ? reason : void 0);
|
|
162
|
+
};
|
|
163
|
+
return /* @__PURE__ */ jsx(
|
|
164
|
+
RadioGroup,
|
|
165
|
+
{
|
|
166
|
+
ref,
|
|
167
|
+
dir,
|
|
168
|
+
defaultValue,
|
|
169
|
+
value,
|
|
170
|
+
onValueChange: handleValueChange,
|
|
171
|
+
"aria-labelledby": ariaLabelledBy ?? titleId,
|
|
172
|
+
"data-slot": "approval-card-options",
|
|
173
|
+
className: cn("grid gap-2", className),
|
|
174
|
+
...props,
|
|
175
|
+
children: options.map((option) => {
|
|
176
|
+
const itemId = `${baseId}-${option.id}`;
|
|
177
|
+
const descriptionId = `${itemId}-description`;
|
|
178
|
+
const description = option.description ?? t(APPROVAL_SCOPE_DESCRIPTION_KEYS[option.scope]);
|
|
179
|
+
return /* @__PURE__ */ jsxs(
|
|
180
|
+
"div",
|
|
181
|
+
{
|
|
182
|
+
"data-slot": "approval-card-option",
|
|
183
|
+
"data-scope": option.scope,
|
|
184
|
+
className: cn(
|
|
185
|
+
"flex items-start gap-3 rounded-lg border border-border bg-card p-3",
|
|
186
|
+
"has-[[data-state=checked]]:border-primary"
|
|
187
|
+
),
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ jsx(
|
|
190
|
+
RadioGroupItem,
|
|
191
|
+
{
|
|
192
|
+
"aria-describedby": descriptionId,
|
|
193
|
+
className: "mt-1",
|
|
194
|
+
"data-slot": "approval-card-option-input",
|
|
195
|
+
id: itemId,
|
|
196
|
+
value: option.id
|
|
197
|
+
}
|
|
198
|
+
),
|
|
199
|
+
/* @__PURE__ */ jsxs("div", { className: "grid flex-1 gap-1", children: [
|
|
200
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
Label,
|
|
203
|
+
{
|
|
204
|
+
className: "text-body flex items-center gap-2 font-medium",
|
|
205
|
+
"data-slot": "approval-card-option-label",
|
|
206
|
+
htmlFor: itemId,
|
|
207
|
+
children: option.label
|
|
208
|
+
}
|
|
209
|
+
),
|
|
210
|
+
option.keyHint ? /* @__PURE__ */ jsx(Kbd, { "data-slot": "approval-card-option-key-hint", children: option.keyHint }) : null
|
|
211
|
+
] }),
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
"p",
|
|
214
|
+
{
|
|
215
|
+
className: "text-meta text-muted-foreground",
|
|
216
|
+
"data-slot": "approval-card-option-description",
|
|
217
|
+
id: descriptionId,
|
|
218
|
+
children: description
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
] })
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
option.id
|
|
225
|
+
);
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
ApprovalCardOptions.displayName = "ApprovalCardOptions";
|
|
232
|
+
var ApprovalCardTarget = forwardRef(
|
|
233
|
+
function ApprovalCardTarget2({ className, ...props }, ref) {
|
|
234
|
+
return /* @__PURE__ */ jsx(
|
|
235
|
+
"div",
|
|
236
|
+
{
|
|
237
|
+
ref,
|
|
238
|
+
"data-slot": "approval-card-target",
|
|
239
|
+
className: cn("text-body rounded-md bg-muted/50 p-3", className),
|
|
240
|
+
...props
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
ApprovalCardTarget.displayName = "ApprovalCardTarget";
|
|
246
|
+
var ApprovalCardReason = forwardRef(
|
|
247
|
+
function ApprovalCardReason2({ className, id, placeholder, ...props }, ref) {
|
|
248
|
+
const { t } = useLocale();
|
|
249
|
+
const { reason, setReason } = useConfirmation();
|
|
250
|
+
const generatedId = useId();
|
|
251
|
+
const textareaId = id ?? generatedId;
|
|
252
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-1.5", "data-slot": "approval-card-reason", children: [
|
|
253
|
+
/* @__PURE__ */ jsx(
|
|
254
|
+
Label,
|
|
255
|
+
{
|
|
256
|
+
className: "text-meta text-muted-foreground",
|
|
257
|
+
"data-slot": "approval-card-reason-label",
|
|
258
|
+
htmlFor: textareaId,
|
|
259
|
+
children: t("ai.approvalCard.reasonLabel")
|
|
260
|
+
}
|
|
261
|
+
),
|
|
262
|
+
/* @__PURE__ */ jsx(
|
|
263
|
+
Textarea,
|
|
264
|
+
{
|
|
265
|
+
ref,
|
|
266
|
+
id: textareaId,
|
|
267
|
+
placeholder: placeholder ?? t("ai.approvalCard.reasonPlaceholder"),
|
|
268
|
+
value: reason,
|
|
269
|
+
onChange: (event) => setReason(event.target.value),
|
|
270
|
+
"data-slot": "approval-card-reason-input",
|
|
271
|
+
className: cn("min-h-16 text-body", className),
|
|
272
|
+
...props
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] });
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
ApprovalCardReason.displayName = "ApprovalCardReason";
|
|
279
|
+
|
|
280
|
+
export {
|
|
281
|
+
Confirmation,
|
|
282
|
+
ConfirmationTitle,
|
|
283
|
+
ConfirmationDescription,
|
|
284
|
+
ConfirmationRequest,
|
|
285
|
+
ConfirmationAccepted,
|
|
286
|
+
ConfirmationRejected,
|
|
287
|
+
ConfirmationActions,
|
|
288
|
+
ConfirmationAction,
|
|
289
|
+
ConfirmationApprove,
|
|
290
|
+
ConfirmationDeny,
|
|
291
|
+
ApprovalCard,
|
|
292
|
+
ApprovalCardTitle,
|
|
293
|
+
ApprovalCardDescription,
|
|
294
|
+
ApprovalCardRequest,
|
|
295
|
+
ApprovalCardAccepted,
|
|
296
|
+
ApprovalCardRejected,
|
|
297
|
+
ApprovalCardActions,
|
|
298
|
+
ApprovalCardAction,
|
|
299
|
+
ApprovalCardApprove,
|
|
300
|
+
ApprovalCardDeny,
|
|
301
|
+
ApprovalCardOptions,
|
|
302
|
+
ApprovalCardTarget,
|
|
303
|
+
ApprovalCardReason
|
|
304
|
+
};
|
|
305
|
+
//# sourceMappingURL=chunk-ELGCZLZF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/confirmation.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Alert,\n AlertDescription,\n AlertTitle,\n APPROVAL_SCOPE_DESCRIPTION_KEYS,\n Kbd,\n Label,\n RadioGroup,\n RadioGroupItem,\n StatusBadge,\n Textarea,\n useLocale,\n type ApprovalOption,\n} from \"@elabs-ai/components-ui\";\nimport { Button } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { ToolUIPart } from \"ai\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { createContext, forwardRef, useContext, useId, useMemo, useState } from \"react\";\n\ntype ToolUIPartApproval =\n | {\n id: string;\n approved?: never;\n reason?: never;\n }\n | {\n id: string;\n approved: boolean;\n reason?: string;\n }\n | {\n id: string;\n approved: true;\n reason?: string;\n }\n | {\n id: string;\n approved: false;\n reason?: string;\n }\n | undefined;\n\ninterface ConfirmationContextValue {\n approval: ToolUIPartApproval;\n state: ToolUIPart[\"state\"];\n /** Default id wired from the card's `aria-labelledby` to `ConfirmationTitle`. */\n titleId: string;\n /**\n * Shared draft reason text (#103) — written by `ApprovalCardReason`, read by\n * `ApprovalCardOptions` at the moment a decision commits, so a typed\n * explanation reaches `onConfirm`'s second argument without threading a\n * prop between two sibling compound parts. Unused by the pre-existing\n * binary `Confirmation*` parts, so their rendered output is unaffected.\n */\n reason: string;\n setReason: (reason: string) => void;\n}\n\nconst ConfirmationContext = createContext<ConfirmationContextValue | null>(null);\n\nconst useConfirmation = () => {\n const context = useContext(ConfirmationContext);\n\n if (!context) {\n throw new Error(\"Confirmation components must be used within Confirmation\");\n }\n\n return context;\n};\n\nexport type ConfirmationProps = ComponentProps<typeof Alert> & {\n approval?: ToolUIPartApproval;\n state: ToolUIPart[\"state\"];\n};\n\nexport const Confirmation = ({\n className,\n approval,\n state,\n variant,\n role,\n children,\n ...props\n}: ConfirmationProps) => {\n const titleId = useId();\n const [reason, setReason] = useState(\"\");\n const contextValue = useMemo(\n () => ({ approval, state, titleId, reason, setReason }),\n [approval, state, titleId, reason],\n );\n\n if (!approval || state === \"input-streaming\" || state === \"input-available\") {\n return null;\n }\n\n const isResolved =\n state === \"approval-responded\" || state === \"output-denied\" || state === \"output-available\";\n const isPending = state === \"approval-requested\";\n const approved = isResolved ? approval.approved : undefined;\n\n // QUIET-RAIL treatment (supersedes research-11's warning/status WASH — a\n // deliberate design-authority decision to drop the filled \"AI card\" look):\n // a neutral card ground (Alert variant=\"default\") + ONE accent rail encoding\n // the outcome, plus the StatusBadge (icon + label, never color alone). The\n // rails are hue-independent enough to stay legible at high decoration / high-contrast.\n // A caller-supplied `variant` still wins (escape hatch).\n const railClass =\n approved === true\n ? \"border-s-success\"\n : approved === false\n ? \"border-s-destructive\"\n : isPending\n ? \"border-s-border-strong\"\n : \"border-s-border\";\n\n return (\n <ConfirmationContext.Provider value={contextValue}>\n <Alert\n variant={variant ?? \"default\"}\n // A pending decision containing focusable controls is a labelled\n // region, NOT an assertive live region (research 11 §B.3): pending =\n // role=\"group\" + aria-labelledby; resolved keeps role=\"alert\".\n role={role ?? (isPending ? \"group\" : \"alert\")}\n aria-labelledby={isPending ? titleId : undefined}\n className={cn(\n \"flex flex-col gap-2 border-s-4 transition-colors duration-fast\",\n railClass,\n isPending && \"shadow-sm\",\n className,\n )}\n {...props}\n >\n {/* The status line is the canonical StatusBadge (#189, research 10 §B.1). */}\n {approved === true ? (\n <StatusBadge\n className=\"animate-in fade-in-0 self-start duration-base ease-entrance\"\n status=\"complete\"\n >\n Approved\n </StatusBadge>\n ) : null}\n {approved === false ? (\n <StatusBadge\n className=\"animate-in fade-in-0 self-start duration-base ease-entrance\"\n status=\"denied\"\n >\n Denied\n </StatusBadge>\n ) : null}\n {children}\n </Alert>\n </ConfirmationContext.Provider>\n );\n};\n\nexport type ConfirmationTitleProps = ComponentProps<typeof AlertTitle>;\n\n/**\n * The question zone (research 11 §B.3 APPROVE-3) — out-ranks the surrounding\n * `text-body` and names the pending region via the card's `aria-labelledby`.\n */\nexport const ConfirmationTitle = ({ className, id, ...props }: ConfirmationTitleProps) => {\n const { titleId } = useConfirmation();\n return <AlertTitle id={id ?? titleId} className={cn(\"text-subtitle\", className)} {...props} />;\n};\n\nexport type ConfirmationDescriptionProps = ComponentProps<typeof AlertDescription>;\n\n/** The consequence zone — what approving will actually do (research 11 §B.3). */\nexport const ConfirmationDescription = ({ className, ...props }: ConfirmationDescriptionProps) => (\n <AlertDescription className={cn(\"text-body text-muted-foreground\", className)} {...props} />\n);\n\nexport interface ConfirmationRequestProps {\n children?: ReactNode;\n}\n\nexport const ConfirmationRequest = ({ children }: ConfirmationRequestProps) => {\n const { state } = useConfirmation();\n\n // Only show when approval is requested\n if (state !== \"approval-requested\") {\n return null;\n }\n\n return children;\n};\n\nexport interface ConfirmationAcceptedProps {\n children?: ReactNode;\n}\n\nexport const ConfirmationAccepted = ({ children }: ConfirmationAcceptedProps) => {\n const { approval, state } = useConfirmation();\n\n // Only show when approved and in response states\n if (\n !approval?.approved ||\n (state !== \"approval-responded\" && state !== \"output-denied\" && state !== \"output-available\")\n ) {\n return null;\n }\n\n return children;\n};\n\nexport interface ConfirmationRejectedProps {\n children?: ReactNode;\n}\n\nexport const ConfirmationRejected = ({ children }: ConfirmationRejectedProps) => {\n const { approval, state } = useConfirmation();\n\n // Only show when rejected and in response states\n if (\n approval?.approved !== false ||\n (state !== \"approval-responded\" && state !== \"output-denied\" && state !== \"output-available\")\n ) {\n return null;\n }\n\n return children;\n};\n\nexport type ConfirmationActionsProps = ComponentProps<\"div\">;\n\nexport const ConfirmationActions = ({ className, ...props }: ConfirmationActionsProps) => {\n const { state } = useConfirmation();\n\n // Only show when approval is requested\n if (state !== \"approval-requested\") {\n return null;\n }\n\n return (\n <div\n className={cn(\n // The action band (research 11 §B.3 APPROVE-3): the divider is the SOLE\n // cue between consequence and buttons, so `border-strong` is correct.\n \"flex w-full items-center justify-end gap-2 border-t border-border-strong pt-3\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport type ConfirmationActionProps = ComponentProps<typeof Button>;\n\nexport const ConfirmationAction = ({ className, ...props }: ConfirmationActionProps) => (\n <Button className={cn(\"h-8 px-3 text-body\", className)} type=\"button\" {...props} />\n);\n\nexport type ConfirmationApproveProps = ConfirmationActionProps;\n\n/**\n * The proceed action — presets the filled primary variant (research 11 §B.3\n * APPROVE-2). Role-named so the primary path cannot drift to `outline`.\n */\nexport const ConfirmationApprove = (props: ConfirmationApproveProps) => (\n <ConfirmationAction variant=\"default\" {...props} />\n);\n\nexport type ConfirmationDenyProps = ConfirmationActionProps;\n\n/** The decline action — presets the quiet `ghost` variant, never `outline`. */\nexport const ConfirmationDeny = (props: ConfirmationDenyProps) => (\n <ConfirmationAction variant=\"ghost\" {...props} />\n);\n\n/*\n * ApprovalCard — the promoted, semantically-named front door for the approval\n * interaction (research 11 §B.3). Same components, clearer name; the\n * `Confirmation*` exports stay for the AI-Elements-shaped API (non-breaking).\n * New code reaches for `ApprovalCard`.\n *\n * The `Confirmation*` family below is CLOSED — frozen at exactly the ten\n * names aliased here. New parts (#103 and beyond) are declared as real\n * `ApprovalCard*` implementations, never added to this alias block. See\n * `docs/decisions/2026-09-01-brainless-adoption-architecture.md` § 4 and the\n * `FROZEN_CONFIRMATION_EXPORTS` lock in `confirmation.test.tsx`.\n */\nexport const ApprovalCard = Confirmation;\nexport type ApprovalCardProps = ConfirmationProps;\nexport const ApprovalCardTitle = ConfirmationTitle;\nexport type ApprovalCardTitleProps = ConfirmationTitleProps;\nexport const ApprovalCardDescription = ConfirmationDescription;\nexport type ApprovalCardDescriptionProps = ConfirmationDescriptionProps;\nexport const ApprovalCardRequest = ConfirmationRequest;\nexport type ApprovalCardRequestProps = ConfirmationRequestProps;\nexport const ApprovalCardAccepted = ConfirmationAccepted;\nexport type ApprovalCardAcceptedProps = ConfirmationAcceptedProps;\nexport const ApprovalCardRejected = ConfirmationRejected;\nexport type ApprovalCardRejectedProps = ConfirmationRejectedProps;\nexport const ApprovalCardActions = ConfirmationActions;\nexport type ApprovalCardActionsProps = ConfirmationActionsProps;\nexport const ApprovalCardAction = ConfirmationAction;\nexport type ApprovalCardActionProps = ConfirmationActionProps;\nexport const ApprovalCardApprove = ConfirmationApprove;\nexport type ApprovalCardApproveProps = ConfirmationApproveProps;\nexport const ApprovalCardDeny = ConfirmationDeny;\nexport type ApprovalCardDenyProps = ConfirmationDenyProps;\n\n/*\n * ---------------------------------------------------------------------------\n * N-option, scoped approval (#103)\n * ---------------------------------------------------------------------------\n * Real coding-agent permission prompts are rarely a plain yes/no — they ask\n * an N-option question whose options encode SCOPE: \"Yes\", \"Yes, and don't ask\n * again this session\", \"No, and tell the agent what to do instead\". The\n * binary `Confirmation`/`ApprovalCard` pair above cannot express the middle\n * option; these three parts extend it additively, composed as CHILDREN of the\n * existing card (`ApprovalCard` / `ApprovalCardRequest`), never as a\n * replacement for it. Every new export here is a real `ApprovalCard*`\n * implementation — see the closed-family note above the alias block.\n *\n * Vocabulary note: this is the PER-CALL decision surface (\"may I run this\n * command?\"). `PermissionModeSelect` (#104) is the separate, standing POLICY\n * surface (\"how much may you do without asking?\") — the two compose rather\n * than merge into one boolean-flagged component, per\n * `.claude/rules/component-api.md` (\"Avoid boolean-prop proliferation\").\n */\n\n/**\n * `ApprovalScope`, `ApprovalOption` and `APPROVAL_SCOPE_DESCRIPTION_KEYS`\n * moved to `@elabs-ai/components-ui` (`lib/approval-option.ts`) — the\n * terminal CLI look-alike family's own permission row (issue #117) reuses\n * the same model, and `@elabs-ai/components-ai`/`@elabs-ai/components-terminal`\n * are layer-2 DAG siblings that may not import each other (T0; see\n * docs/decisions/2026-09-01-brainless-adoption-architecture.md § 4). Imported\n * above.\n */\n\nexport interface ApprovalCardOptionsProps extends ComponentProps<\"div\"> {\n options: ApprovalOption[];\n value?: string;\n defaultValue?: string;\n onValueChange?: (id: string) => void;\n /**\n * Fires when an option is selected — by click, or by ANY keyboard move\n * within the radio group (Home/End/arrow keys select immediately, per the\n * native/ARIA radiogroup pattern Radix implements; there is no separate\n * \"highlight vs commit\" step). Reports the full resolved `ApprovalOption`\n * plus whatever reason text is currently held by a sibling\n * `ApprovalCardReason` — type a reason BEFORE choosing an option to have it\n * included in this call.\n */\n onConfirm?: (option: ApprovalOption, reason?: string) => void;\n}\n\n/**\n * ApprovalCardOptions — the N-option, scoped decision (#103). Renders through\n * `@elabs-ai/components-ui`'s `RadioGroup` (Radix) for roving focus, arrow-key\n * wrap, Home/End and announcement — never a hand-rolled\n * `parentElement.children[i]` focus walk. Composed inside `ApprovalCardRequest`,\n * alongside the existing `ApprovalCardTitle`/`ApprovalCardDescription`.\n */\nexport const ApprovalCardOptions = forwardRef<HTMLDivElement, ApprovalCardOptionsProps>(\n function ApprovalCardOptions(\n {\n options,\n value,\n defaultValue,\n onValueChange,\n onConfirm,\n className,\n dir,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n },\n ref,\n ) {\n const { t } = useLocale();\n const { titleId, reason } = useConfirmation();\n const baseId = useId();\n\n // Controlled/uncontrolled mirrors the platform (and `PermissionModeSelect`,\n // #104): `value`/`onValueChange` for a controlled caller, `defaultValue`\n // for an uncontrolled starting point — Radix's own `RadioGroup` already\n // implements both correctly. Hand-rolling a parallel `internalValue`\n // state here and always passing a (possibly `undefined`) `value` down\n // makes Radix's controllable-state hook see a spurious\n // uncontrolled-to-controlled transition the first time a selection is\n // made, which is exactly the warning this avoids.\n const handleValueChange = (id: string) => {\n onValueChange?.(id);\n const option = options.find((candidate) => candidate.id === id);\n if (option) onConfirm?.(option, reason.trim() ? reason : undefined);\n };\n\n return (\n <RadioGroup\n ref={ref}\n // `dir` on a plain <div> is a free-form HTML string; Radix's\n // RadioGroup narrows it to \"ltr\" | \"rtl\". Cast at this single call\n // site, matching `PermissionModeSelect` (#104), rather than widening\n // (and so weakening) the public prop type.\n dir={dir as \"ltr\" | \"rtl\" | undefined}\n defaultValue={defaultValue}\n value={value}\n onValueChange={handleValueChange}\n aria-labelledby={ariaLabelledBy ?? titleId}\n data-slot=\"approval-card-options\"\n className={cn(\"grid gap-2\", className)}\n {...props}\n >\n {options.map((option) => {\n const itemId = `${baseId}-${option.id}`;\n const descriptionId = `${itemId}-description`;\n const description =\n option.description ?? t(APPROVAL_SCOPE_DESCRIPTION_KEYS[option.scope]);\n\n return (\n <div\n key={option.id}\n data-slot=\"approval-card-option\"\n data-scope={option.scope}\n className={cn(\n \"flex items-start gap-3 rounded-lg border border-border bg-card p-3\",\n \"has-[[data-state=checked]]:border-primary\",\n )}\n >\n <RadioGroupItem\n aria-describedby={descriptionId}\n className=\"mt-1\"\n data-slot=\"approval-card-option-input\"\n id={itemId}\n value={option.id}\n />\n <div className=\"grid flex-1 gap-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <Label\n className=\"text-body flex items-center gap-2 font-medium\"\n data-slot=\"approval-card-option-label\"\n htmlFor={itemId}\n >\n {option.label}\n </Label>\n {option.keyHint ? (\n <Kbd data-slot=\"approval-card-option-key-hint\">{option.keyHint}</Kbd>\n ) : null}\n </div>\n <p\n className=\"text-meta text-muted-foreground\"\n data-slot=\"approval-card-option-description\"\n id={descriptionId}\n >\n {description}\n </p>\n </div>\n </div>\n );\n })}\n </RadioGroup>\n );\n },\n);\n\nApprovalCardOptions.displayName = \"ApprovalCardOptions\";\n\nexport type ApprovalCardTargetProps = ComponentProps<\"div\">;\n\n/**\n * ApprovalCardTarget — the preview slot for WHAT is being approved: a\n * command, a path, or a richer node such as a `DiffView` (#102). Accepts\n * arbitrary children — this file never imports `DiffView`, the composition\n * happens entirely in the consumer's tree. Ungated by state (unlike\n * `ApprovalCardRequest`/`Accepted`/`Rejected`): the subject of a decision\n * stays visible after it resolves, not only while pending.\n */\nexport const ApprovalCardTarget = forwardRef<HTMLDivElement, ApprovalCardTargetProps>(\n function ApprovalCardTarget({ className, ...props }, ref) {\n return (\n <div\n ref={ref}\n data-slot=\"approval-card-target\"\n className={cn(\"text-body rounded-md bg-muted/50 p-3\", className)}\n {...props}\n />\n );\n },\n);\n\nApprovalCardTarget.displayName = \"ApprovalCardTarget\";\n\nexport type ApprovalCardReasonProps = Omit<\n ComponentProps<typeof Textarea>,\n \"value\" | \"defaultValue\" | \"onChange\"\n>;\n\n/**\n * ApprovalCardReason — the deny-with-reason input, wired to the `reason`\n * field that already exists on `ToolUIPartApproval` and was previously\n * unreachable from the UI. Shares its value with `ApprovalCardOptions`\n * through `Confirmation`'s own context (not a prop threaded between the two\n * sibling parts) — type here BEFORE choosing an option to have the text\n * included in that option's `onConfirm` call.\n */\nexport const ApprovalCardReason = forwardRef<HTMLTextAreaElement, ApprovalCardReasonProps>(\n function ApprovalCardReason({ className, id, placeholder, ...props }, ref) {\n const { t } = useLocale();\n const { reason, setReason } = useConfirmation();\n const generatedId = useId();\n const textareaId = id ?? generatedId;\n\n return (\n <div className=\"grid gap-1.5\" data-slot=\"approval-card-reason\">\n <Label\n className=\"text-meta text-muted-foreground\"\n data-slot=\"approval-card-reason-label\"\n htmlFor={textareaId}\n >\n {t(\"ai.approvalCard.reasonLabel\")}\n </Label>\n <Textarea\n ref={ref}\n id={textareaId}\n placeholder={placeholder ?? t(\"ai.approvalCard.reasonPlaceholder\")}\n value={reason}\n onChange={(event) => setReason(event.target.value)}\n data-slot=\"approval-card-reason-input\"\n className={cn(\"min-h-16 text-body\", className)}\n {...props}\n />\n </div>\n );\n },\n);\n\nApprovalCardReason.displayName = \"ApprovalCardReason\";\n"],"mappings":"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","names":["ApprovalCardOptions","ApprovalCardTarget","ApprovalCardReason"]}
|