@kitn.ai/ui 0.25.2 → 0.26.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 +2 -2
- package/dist/components/attachment-types.d.ts +46 -1
- package/dist/components/attachments.d.ts +31 -0
- package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
- package/dist/components/audio-visualizer/index.d.ts +15 -1
- package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
- package/dist/components/chat-thread.d.ts +3 -41
- package/dist/components/code-block.d.ts +22 -0
- package/dist/components/conversation-item.d.ts +54 -1
- package/dist/components/conversation-list.d.ts +65 -0
- package/dist/components/form.d.ts +42 -0
- package/dist/components/message.d.ts +7 -0
- package/dist/components/toast.d.ts +2 -1
- package/dist/components/voice-input.d.ts +8 -0
- package/dist/components/voice-output.d.ts +12 -1
- package/dist/components/workspace-shell.d.ts +87 -0
- package/dist/{create-tween-B5Y4Im8G.js → create-tween-BFT6c-Y8.js} +1 -1
- package/dist/{create-tween-4z1XYLkR.js → create-tween-CJNlzoaO.js} +1 -1
- package/dist/{create-tween-CUzOlaMS.js → create-tween-CQED4T0g.js} +1 -1
- package/dist/custom-elements.json +2931 -2572
- package/dist/diagnostics/hook.d.ts +81 -0
- package/dist/diagnostics/index.d.ts +7 -0
- package/dist/diagnostics/report-request.d.ts +61 -0
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +209 -0
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/autoloader.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-b5A8hlHs.js → Icon--Y8vsFal.js} +1 -1
- package/dist/elements/chunks/action-icons-D9u3OzYW.js +1 -0
- package/dist/elements/chunks/arrow-left-CVh1TeN7.js +1 -0
- package/dist/elements/chunks/{artifact-BSuPYFHY.js → artifact-BkYcfIlZ.js} +1 -1
- package/dist/elements/chunks/attachments-DwfNlubq.js +1 -0
- package/dist/elements/chunks/audio-visualizer-D7lB6mvR.js +1 -0
- package/dist/elements/chunks/{badge-TIMiJFKc.js → badge-Q0-Cmwzz.js} +1 -1
- package/dist/elements/chunks/{button-B9okqG73.js → button-flUbeufF.js} +1 -1
- package/dist/elements/chunks/{card-renderer-CSoTL_e1.js → card-renderer-3Dyl-FpN.js} +1 -1
- package/dist/elements/chunks/check-B5kBPKeo.js +1 -0
- package/dist/elements/chunks/chevron-down-DZ1GS39h.js +1 -0
- package/dist/elements/chunks/chevron-right-CvP7OUd2.js +1 -0
- package/dist/elements/chunks/{choice-card-CGMc3kek.js → choice-card-D4QUIOWZ.js} +1 -1
- package/dist/elements/chunks/circle-CDf9g6MQ.js +1 -0
- package/dist/elements/chunks/circle-check-CA47Nqbz.js +1 -0
- package/dist/elements/chunks/{circle-x-mhH7pOK2.js → circle-x-CJ_HVbC8.js} +1 -1
- package/dist/elements/chunks/code-block-BdLiTitd.js +1 -0
- package/dist/elements/chunks/collapsible-BzbXpPzr.js +1 -0
- package/dist/elements/chunks/{composer-CZZmtYOT.js → composer-Byve-n8K.js} +2 -2
- package/dist/elements/chunks/{confirm-card-BWjf2-xo.js → confirm-card-B0flE3MN.js} +1 -1
- package/dist/elements/chunks/context-VLIL3sJ-.js +1 -0
- package/dist/elements/chunks/controllable-DTwTIlwp.js +1 -0
- package/dist/elements/chunks/conversation-item-BDztezOG.js +1 -0
- package/dist/elements/chunks/copy-Nf-kqxXH.js +1 -0
- package/dist/elements/chunks/{create-tween-CUrM6D_o.js → create-tween-c4Z8TDYz.js} +2 -2
- package/dist/elements/chunks/default-input-C2iLFq0b.js +1 -0
- package/dist/elements/chunks/define-wjHR1MkU.js +1 -0
- package/dist/elements/chunks/disclosure-CcNbyLy0.js +1 -0
- package/dist/elements/chunks/{download-DnCyWFJE.js → download-C4V7tBdb.js} +1 -1
- package/dist/elements/chunks/dropdown-BgWTthUx.js +1 -0
- package/dist/elements/chunks/{ellipsis-CVxqfsDZ.js → ellipsis-7gU33mL2.js} +1 -1
- package/dist/elements/chunks/embed-D3KewsIJ.js +1 -0
- package/dist/elements/chunks/{external-link-DG2sRdQL.js → external-link-CSOEsNlx.js} +1 -1
- package/dist/elements/chunks/{file-text-D0J9F6M7.js → file-text-D1G8khle.js} +1 -1
- package/dist/elements/chunks/{file-tree-yGWNK0Js.js → file-tree-zvB2i7P9.js} +1 -1
- package/dist/elements/chunks/{folder-NV1pi_ud.js → folder-WW2LGLid.js} +1 -1
- package/dist/elements/chunks/form-CQirdC4W.js +1 -0
- package/dist/elements/chunks/hover-card-CJJkfHTK.js +1 -0
- package/dist/elements/chunks/icon-Cfgmf4fa.js +1 -0
- package/dist/elements/chunks/{info-C8r5Q6cc.js → info-DX3YMOt6.js} +1 -1
- package/dist/elements/chunks/input-BtW9U6Sq.js +1 -0
- package/dist/elements/chunks/{kbd-OGJDUT-O.js → kbd-LGXwuT_V.js} +1 -1
- package/dist/elements/chunks/{link-BdsfLB7b.js → link-2My69BqT.js} +1 -1
- package/dist/elements/chunks/{link-preview-Cf5KOP-i.js → link-preview-OQ9IEq0f.js} +1 -1
- package/dist/elements/chunks/{loader-B5QzK_8_.js → loader-iOxc_Tu-.js} +1 -1
- package/dist/elements/chunks/{markdown-DbozyYqa.js → markdown-DgBFaGAz.js} +1 -1
- package/dist/elements/chunks/{message-DtXKPqiR.js → message-VMZohrXx.js} +1 -1
- package/dist/elements/chunks/message-circle-CpXMM-_n.js +1 -0
- package/dist/elements/chunks/message-square-DxcWtETQ.js +1 -0
- package/dist/elements/chunks/message-tZmV2A3p.js +1 -0
- package/dist/elements/chunks/{minimize-2-SegJ_oku.js → minimize-2-DPIUKTrT.js} +1 -1
- package/dist/elements/chunks/{model-switcher-D7JrFFHp.js → model-switcher-DsT8tFB0.js} +1 -1
- package/dist/elements/chunks/{overlay-CV7z2YuX.js → overlay-DuiwdXjm.js} +1 -1
- package/dist/elements/chunks/{paperclip-Bg0ITU3b.js → paperclip-CRZVp4z_.js} +1 -1
- package/dist/elements/chunks/{progress-bar-D1_rzt6N.js → progress-bar-DNy66e_E.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-D5Sm8xF1.js → prompt-suggestion-Bn64Nl7L.js} +1 -1
- package/dist/elements/chunks/reasoning-Emn9N4-T.js +1 -0
- package/dist/elements/chunks/{resizable-Dpzjlvp4.js → resizable-D181b4fM.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-DzVDJOA1.js → rotate-cw-CPWe37D9.js} +1 -1
- package/dist/elements/chunks/{scroll-area-93A1bbeG.js → scroll-area-ClZkZxIk.js} +1 -1
- package/dist/elements/chunks/scroll-button-DPE-t3x_.js +1 -0
- package/dist/elements/chunks/{separator-DH2r_ZSZ.js → separator-CeJHQqLW.js} +1 -1
- package/dist/elements/chunks/{settings-BFMOd6yB.js → settings-CqaHgJF9.js} +1 -1
- package/dist/elements/chunks/{settings-group-B3VZKIcz.js → settings-group-CjqjLCOn.js} +1 -1
- package/dist/elements/chunks/share-CyfnNEqp.js +1 -0
- package/dist/elements/chunks/{skeleton-N8BuWYmf.js → skeleton-D8akBnKS.js} +1 -1
- package/dist/elements/chunks/slots-DaNjX0rc.js +1 -0
- package/dist/elements/chunks/{source-w9V0NnBn.js → source-UWCvmVak.js} +1 -1
- package/dist/elements/chunks/{star-DHvACxtm.js → star-DoHFAikQ.js} +1 -1
- package/dist/elements/chunks/store-Cjq0qwYN.js +1 -0
- package/dist/elements/chunks/{tasks-card-lPxoodlA.js → tasks-card-CmZ5xh6v.js} +1 -1
- package/dist/elements/chunks/{text-shimmer-BY_rI3s5.js → text-shimmer-DYn09zrz.js} +1 -1
- package/dist/elements/chunks/textarea-skiGHURM.js +1 -0
- package/dist/elements/chunks/{thumbs-up-DQe0hvkZ.js → thumbs-up-DX2Ijk3H.js} +1 -1
- package/dist/elements/chunks/{toast-store-VUZZBhzH.js → toast-store-CFnQpThX.js} +1 -1
- package/dist/elements/chunks/{tool-C1YtxwPu.js → tool-DKQ2D1DP.js} +1 -1
- package/dist/elements/chunks/{tooltip-BqwnFWhn.js → tooltip-B0G99y8w.js} +1 -1
- package/dist/elements/chunks/{triangle-alert-B6or7F5u.js → triangle-alert-F9cyUEyy.js} +1 -1
- package/dist/elements/chunks/{use-card-resolution-DJg32jGH.js → use-card-resolution-DoA2f_EU.js} +1 -1
- package/dist/elements/chunks/{variant-aurora-QXTZPyST.js → variant-aurora-qt6Zct8M.js} +2 -2
- package/dist/elements/chunks/variant-custom-tJp22_BQ.js +1 -0
- package/dist/{variant-wave-BwzrHD1s.js → elements/chunks/variant-wave-CaQ5ti39.js} +2 -2
- package/dist/elements/chunks/x-B6UnOiLw.js +1 -0
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-item.d.ts +1 -0
- package/dist/elements/conversation-item.js +1 -0
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +3 -3
- package/dist/elements/define.d.ts +27 -0
- package/dist/elements/diagnostic-events.d.ts +74 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.d.ts +1 -0
- package/dist/elements/dock.js +149 -0
- package/dist/elements/dropdown.d.ts +1 -0
- package/dist/elements/dropdown.js +1 -0
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-diagnostics.d.ts +81 -0
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register.d.ts +2 -0
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +3 -3
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slots.d.ts +35 -3
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +232 -148
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5456 -4931
- package/dist/index.server.js +4142 -3669
- package/dist/kai-provider.es.js +40 -40
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +184 -69
- package/dist/llms/llms.txt +3 -3
- package/dist/mcp.es.js +2669 -546
- package/dist/primitives/use-resize-observer.d.ts +23 -0
- package/dist/primitives/use-speech-recognition.d.ts +8 -0
- package/dist/primitives/use-text-stream.d.ts +5 -0
- package/dist/react/index.d.ts +115 -266
- package/dist/react.js +247 -221
- package/dist/register-impl-DpE7icIb.js +293 -0
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/tool-defs.d.ts +35 -0
- package/dist/schemas.js +403 -310
- package/dist/{solid-BMuDmFo_.js → solid-BtdqNZI7.js} +8435 -7394
- package/dist/{solid-CGYNdLRR.js → solid-CnavwBye.js} +5987 -5035
- package/dist/solid.d.ts +5 -0
- package/dist/solid.js +210 -203
- package/dist/solid.server.js +210 -203
- package/dist/state/index.d.ts +4 -0
- package/dist/state/persistence.d.ts +51 -0
- package/dist/state/stream.d.ts +50 -0
- package/dist/state/threads.d.ts +71 -0
- package/dist/state.js +269 -169
- package/dist/types.d.ts +4 -2
- package/dist/ui/dock.d.ts +109 -0
- package/dist/ui/hover-card.d.ts +44 -0
- package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-B8II3-I1.js} +27 -27
- package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CMNWOSJR.js} +28 -28
- package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CcuDMWwQ.js} +2 -2
- package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-BdvYC7af.js} +40 -40
- package/dist/{variant-custom-BhS9wIpU.js → variant-custom-C714mqKl.js} +65 -65
- package/dist/variant-custom-L16JLLZy.js +1 -0
- package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-2S4La2Ka.js} +2 -2
- package/dist/{variant-wave-CbVsmgwT.js → variant-wave-D7ruiA1W.js} +25 -25
- package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-ecwykeyL.js} +21 -21
- package/dist/wire/chunk.d.ts +47 -0
- package/dist/wire/diagnostics.d.ts +575 -0
- package/dist/wire/encode-probe.d.ts +70 -0
- package/dist/wire/encode.d.ts +24 -0
- package/dist/wire/index.d.ts +2 -0
- package/dist/wire/sse.d.ts +10 -2
- package/dist/wire.js +917 -480
- package/frameworks/react/index.tsx +132 -93
- package/llms-full.txt +184 -69
- package/llms.txt +3 -3
- package/package.json +26 -6
- package/src/agent-tooling/archetypes.ts +41 -9
- package/src/agent-tooling/catalog/README.md +676 -0
- package/src/agent-tooling/catalog/catalog-types.ts +231 -0
- package/src/agent-tooling/catalog/fabrications.ts +96 -0
- package/src/agent-tooling/catalog/invariants.ts +284 -0
- package/src/agent-tooling/catalog/labs-titles.ts +114 -0
- package/src/agent-tooling/catalog/scenarios.ts +87 -0
- package/src/agent-tooling/catalog/surfaces.ts +275 -0
- package/src/agent-tooling/integrations/anthropic.ts +7 -1
- package/src/agent-tooling/integrations/cloudflare.ts +43 -2
- package/src/agent-tooling/integrations/langgraph.ts +7 -1
- package/src/agent-tooling/integrations/mastra.ts +7 -1
- package/src/agent-tooling/integrations/mock.ts +92 -17
- package/src/agent-tooling/integrations/ollama.ts +7 -1
- package/src/agent-tooling/integrations/openai.ts +7 -1
- package/src/agent-tooling/integrations/openrouter.ts +7 -1
- package/src/agent-tooling/integrations/pi.ts +56 -1
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
- package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
- package/src/agent-tooling/mcp/manifest.ts +100 -1
- package/src/agent-tooling/mcp/server.ts +61 -5
- package/src/agent-tooling/mcp/tools/reference.ts +440 -4
- package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
- package/src/agent-tooling/mcp/tools/theme.ts +146 -30
- package/src/agent-tooling/mcp/validate-args.ts +141 -0
- package/src/agent-tooling/registry.ts +19 -7
- package/src/agent-tooling/route-emit.ts +26 -2
- package/src/components/attachment-types.ts +47 -1
- package/src/components/attachments.tsx +169 -22
- package/src/components/audio-visualizer/fit-scale.ts +112 -0
- package/src/components/audio-visualizer/index.tsx +80 -3
- package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
- package/src/components/audio-visualizer/variant-bar.tsx +38 -6
- package/src/components/audio-visualizer/variant-custom.tsx +8 -2
- package/src/components/audio-visualizer/variant-grid.tsx +16 -11
- package/src/components/audio-visualizer/variant-radial.tsx +15 -10
- package/src/components/audio-visualizer/variant-wave.tsx +7 -2
- package/src/components/chat-container.tsx +4 -1
- package/src/components/chat-thread.tsx +36 -7
- package/src/components/code-block.tsx +79 -3
- package/src/components/composer.tsx +8 -1
- package/src/components/conversation-item.tsx +114 -4
- package/src/components/conversation-list.tsx +212 -12
- package/src/components/embed.tsx +4 -0
- package/src/components/form.tsx +41 -14
- package/src/components/message.tsx +75 -5
- package/src/components/response-stream.tsx +13 -7
- package/src/components/screen.tsx +5 -0
- package/src/components/scroll-button.tsx +7 -0
- package/src/components/toast.tsx +11 -5
- package/src/components/voice-input.tsx +35 -3
- package/src/components/voice-output.tsx +46 -6
- package/src/components/workspace-shell.tsx +285 -0
- package/src/diagnostics/hook.ts +352 -0
- package/src/diagnostics/index.ts +82 -0
- package/src/diagnostics/report-request.ts +296 -0
- package/src/elements/attachments.tsx +6 -7
- package/src/elements/audio-visualizer.tsx +48 -4
- package/src/elements/chat-workspace.tsx +159 -287
- package/src/elements/chat.tsx +12 -9
- package/src/elements/code-block.tsx +19 -1
- package/src/elements/command.tsx +4 -1
- package/src/elements/compiled.css +1 -1
- package/src/elements/conversation-item.tsx +80 -0
- package/src/elements/conversation-list.tsx +60 -11
- package/src/elements/default-input.tsx +5 -5
- package/src/elements/define.tsx +168 -8
- package/src/elements/diagnostic-events.ts +114 -0
- package/src/elements/dialog.tsx +47 -0
- package/src/elements/disclosure.ts +34 -12
- package/src/elements/dock.tsx +204 -0
- package/src/elements/dropdown.tsx +123 -0
- package/src/elements/element-diagnostics.ts +392 -0
- package/src/elements/element-manifest.json +12 -0
- package/src/elements/element-meta.json +672 -439
- package/src/elements/element-nonscalar.json +170 -0
- package/src/elements/element-types.d.ts +232 -148
- package/src/elements/menu.tsx +13 -0
- package/src/elements/prompt-dock.tsx +2 -1
- package/src/elements/prompt-input.tsx +8 -8
- package/src/elements/register-impl.ts +46 -0
- package/src/elements/register.ts +28 -0
- package/src/elements/resizable.tsx +52 -25
- package/src/elements/scroll-button.tsx +5 -0
- package/src/elements/slots.ts +107 -12
- package/src/elements/styles.css +30 -6
- package/src/elements/thinking-bar.tsx +16 -7
- package/src/elements/toast.tsx +3 -0
- package/src/elements/tool.tsx +26 -5
- package/src/elements/voice-input.tsx +11 -2
- package/src/elements/voice-output.tsx +16 -3
- package/src/index.ts +2 -0
- package/src/primitives/use-resize-observer.ts +33 -0
- package/src/primitives/use-speech-recognition.ts +12 -1
- package/src/primitives/use-stick-to-bottom.ts +34 -2
- package/src/primitives/use-text-stream.ts +33 -2
- package/src/remote/provider-runtime.ts +6 -3
- package/src/schemas/index.ts +1 -0
- package/src/schemas/tool-defs.ts +297 -6
- package/src/solid.ts +5 -0
- package/src/state/index.ts +12 -0
- package/src/state/persistence.ts +180 -0
- package/src/state/stream.ts +71 -4
- package/src/state/threads.ts +142 -0
- package/src/types.ts +4 -2
- package/src/ui/dialog.tsx +6 -0
- package/src/ui/dock.tsx +445 -0
- package/src/ui/dropdown.tsx +67 -23
- package/src/ui/hover-card.tsx +156 -6
- package/src/ui/input.tsx +29 -10
- package/src/ui/resizable.tsx +11 -0
- package/src/wire/chunk.ts +47 -0
- package/src/wire/consume.ts +185 -26
- package/src/wire/diagnostics.ts +727 -0
- package/src/wire/encode-probe.ts +214 -0
- package/src/wire/encode.ts +267 -14
- package/src/wire/formats/anthropic.ts +8 -1
- package/src/wire/formats/openai.ts +5 -0
- package/src/wire/index.ts +22 -0
- package/src/wire/read.ts +250 -6
- package/src/wire/sse.ts +16 -3
- package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
- package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
- package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
- package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
- package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
- package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
- package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
- package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
- package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
- package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
- package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
- package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
- package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
- package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
- package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
- package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
- package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
- package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
- package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
- package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
- package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
- package/dist/elements/chunks/input-DrseEjhp.js +0 -1
- package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
- package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
- package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
- package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
- package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
- package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
- package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
- package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
- package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
- package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
- package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
- package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
- package/dist/register-impl-DGmGzrEM.js +0 -145
- package/dist/variant-custom-MkTNTycd.js +0 -1
- package/src/primitives/card-validate-generator.testlib.ts +0 -38
|
@@ -4,7 +4,7 @@ import { normalizeVolumeBands } from '../../primitives/audio-bands';
|
|
|
4
4
|
import { useSequencer } from '../../primitives/use-sequencer';
|
|
5
5
|
import { gridSequence } from '../../primitives/visualizer-sequences';
|
|
6
6
|
import { GRID_CELL, GRID_GAP, defaultGridCount } from './sizes';
|
|
7
|
-
import type
|
|
7
|
+
import { amplitudeRenderState, type VariantProps } from './variant-bar';
|
|
8
8
|
|
|
9
9
|
/*
|
|
10
10
|
* NOTE: The speaking thresholds below DIVERGE from upstream's GridCell math,
|
|
@@ -64,20 +64,25 @@ export function GridVisualizer(
|
|
|
64
64
|
const interval = () => props.interval ?? 100;
|
|
65
65
|
const items = () => Array.from({ length: rows() * cols() }, (_, i) => i);
|
|
66
66
|
|
|
67
|
+
// The state the rendering follows: `listeningAmplitude` folds `listening`
|
|
68
|
+
// into the speaking presentation. See amplitudeRenderState in variant-bar.
|
|
69
|
+
const renderState = () => amplitudeRenderState(props.state, props.listeningAmplitude);
|
|
70
|
+
|
|
67
71
|
const tick = useSequencer(() =>
|
|
68
72
|
// Speaking is driven by audio, not the clock; freezing also parks it.
|
|
69
|
-
props.frozen ||
|
|
73
|
+
props.frozen || renderState() === 'speaking' ? Infinity : interval(),
|
|
70
74
|
);
|
|
71
75
|
|
|
72
|
-
const sequence = () => gridSequence(
|
|
76
|
+
const sequence = () => gridSequence(renderState(), rows(), cols(), props.spread);
|
|
73
77
|
const active = () => sequence()[tick() % sequence().length] ?? { x: -1, y: -1 };
|
|
74
78
|
|
|
75
|
-
// Bands only mean anything while speaking
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
+
// Bands only mean anything while speaking (or listening under the
|
|
80
|
+
// `listeningAmplitude` opt-in, folded in by renderState). Everywhere else
|
|
81
|
+
// the sequence is the whole story, so a stale level never leaks into a
|
|
82
|
+
// scripted state (same guard as BarVisualizer, so the render-prop's `value`
|
|
83
|
+
// means one thing across every variant).
|
|
79
84
|
const levels = () =>
|
|
80
|
-
|
|
85
|
+
renderState() === 'speaking' ? normalizeVolumeBands(props.bands, cols()) : new Array(cols()).fill(0);
|
|
81
86
|
|
|
82
87
|
/**
|
|
83
88
|
* While speaking, a cell lights when its column's level clears a threshold
|
|
@@ -87,7 +92,7 @@ export function GridVisualizer(
|
|
|
87
92
|
* both per the divergence note at the top of this file.
|
|
88
93
|
*/
|
|
89
94
|
function isLit(index: number): boolean {
|
|
90
|
-
if (
|
|
95
|
+
if (renderState() === 'speaking') {
|
|
91
96
|
const y = Math.floor(index / cols());
|
|
92
97
|
const mid = Math.floor(rows() / 2);
|
|
93
98
|
const scaled = (Math.abs(mid - y) / (mid + 1)) * SPEECH_LEVEL_CEILING;
|
|
@@ -100,13 +105,13 @@ export function GridVisualizer(
|
|
|
100
105
|
// upstream rests one stationary centre cell here. 'disconnected' (first-
|
|
101
106
|
// class 2026-08-10) mirrors idle's dark grid for now, pending the LiveKit
|
|
102
107
|
// disconnected-state measurement.
|
|
103
|
-
if (
|
|
108
|
+
if (renderState() === 'idle' || renderState() === 'disconnected') return false;
|
|
104
109
|
return active().x === index % cols() && active().y === Math.floor(index / cols());
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
/** Snap on, fade off: highlighted cells transition 10x faster than they decay. */
|
|
108
113
|
function transition(index: number): string {
|
|
109
|
-
if (
|
|
114
|
+
if (renderState() === 'speaking') return '150ms';
|
|
110
115
|
return `${interval() / (isLit(index) ? 1000 : 100)}s`;
|
|
111
116
|
}
|
|
112
117
|
|
|
@@ -4,7 +4,7 @@ import { normalizeVolumeBands } from '../../primitives/audio-bands';
|
|
|
4
4
|
import { useSequencer } from '../../primitives/use-sequencer';
|
|
5
5
|
import { radialSequence, radialInterval } from '../../primitives/visualizer-sequences';
|
|
6
6
|
import { CONTAINER_HEIGHT, RADIAL_RADIUS, defaultRadialBarCount } from './sizes';
|
|
7
|
-
import type
|
|
7
|
+
import { amplitudeRenderState, type VariantProps } from './variant-bar';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Bars arranged around a circle, growing outward with the audio.
|
|
@@ -40,10 +40,14 @@ export function RadialVisualizer(
|
|
|
40
40
|
// many bars are on the ring.
|
|
41
41
|
const dotSize = () => (radius() * Math.PI) / count();
|
|
42
42
|
|
|
43
|
+
// The state the rendering follows: `listeningAmplitude` folds `listening`
|
|
44
|
+
// into the speaking presentation. See amplitudeRenderState in variant-bar.
|
|
45
|
+
const renderState = () => amplitudeRenderState(props.state, props.listeningAmplitude);
|
|
46
|
+
|
|
43
47
|
// Frozen (reduced motion) parks the sequence on frame 0 rather than stopping
|
|
44
48
|
// the component: the shape still reads, it just does not move.
|
|
45
|
-
const tick = useSequencer(() => (props.frozen ? Infinity : radialInterval(
|
|
46
|
-
const sequence = () => radialSequence(
|
|
49
|
+
const tick = useSequencer(() => (props.frozen ? Infinity : radialInterval(renderState())));
|
|
50
|
+
const sequence = () => radialSequence(renderState(), count());
|
|
47
51
|
|
|
48
52
|
// `thinking` parks the sequencer itself (radialInterval returns Infinity)
|
|
49
53
|
// and spins the whole ring in CSS instead, so every bar reads as lit rather
|
|
@@ -52,20 +56,21 @@ export function RadialVisualizer(
|
|
|
52
56
|
// purpose: they interleave the ring so it still looks even while rotating,
|
|
53
57
|
// not so a subset blinks).
|
|
54
58
|
const highlighted = () =>
|
|
55
|
-
|
|
59
|
+
renderState() === 'thinking'
|
|
56
60
|
? Array.from({ length: count() }, (_, i) => i)
|
|
57
61
|
: (sequence()[tick() % sequence().length] ?? []);
|
|
58
62
|
|
|
59
|
-
// Bands only mean anything while speaking
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
+
// Bands only mean anything while speaking (or listening under the
|
|
64
|
+
// `listeningAmplitude` opt-in, folded in by renderState). Everywhere else
|
|
65
|
+
// the sequence is the whole story, so a stale level never leaks into a
|
|
66
|
+
// scripted state (same guard as the other two variants, so the
|
|
67
|
+
// render-prop's `value` means one thing across every variant).
|
|
63
68
|
const levels = () =>
|
|
64
|
-
|
|
69
|
+
renderState() === 'speaking'
|
|
65
70
|
? normalizeVolumeBands(props.bands, count())
|
|
66
71
|
: new Array(count()).fill(0);
|
|
67
72
|
|
|
68
|
-
const spinning = () =>
|
|
73
|
+
const spinning = () => renderState() === 'thinking' && !props.frozen;
|
|
69
74
|
|
|
70
75
|
return (
|
|
71
76
|
<div
|
|
@@ -3,6 +3,7 @@ import { ShaderCanvas, hexToRgb, DEFAULT_SHADER_COLOR } from './shader-canvas';
|
|
|
3
3
|
import { createTween } from '../../primitives/create-tween';
|
|
4
4
|
import { waveTargets } from '../../primitives/visualizer-sequences';
|
|
5
5
|
import { CONTAINER_HEIGHT } from './sizes';
|
|
6
|
+
import { amplitudeRenderState } from './variant-bar';
|
|
6
7
|
import type { ShaderVariantProps } from './index';
|
|
7
8
|
import waveShader from './wave.glsl';
|
|
8
9
|
|
|
@@ -26,7 +27,11 @@ export default function WaveVisualizer(props: ShaderVariantProps): JSX.Element {
|
|
|
26
27
|
const frequency = createTween(0);
|
|
27
28
|
const opacity = createTween(1);
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
// The state the rendering follows: `listeningAmplitude` folds `listening`
|
|
31
|
+
// into the speaking presentation (live-volume amplitude/frequency). See
|
|
32
|
+
// amplitudeRenderState in variant-bar. `data-kai-state` keeps the real state.
|
|
33
|
+
const renderState = () => amplitudeRenderState(props.state, props.listeningAmplitude);
|
|
34
|
+
const targets = () => waveTargets(renderState());
|
|
30
35
|
|
|
31
36
|
// Reduced motion: land on the target immediately and skip every pulse.
|
|
32
37
|
const transition = () =>
|
|
@@ -61,7 +66,7 @@ export default function WaveVisualizer(props: ShaderVariantProps): JSX.Element {
|
|
|
61
66
|
// effect, one writer, ordering can no longer matter. Same effect-race
|
|
62
67
|
// class as b5795ac's shared() finding.
|
|
63
68
|
createEffect(() => {
|
|
64
|
-
if (
|
|
69
|
+
if (renderState() === 'speaking') {
|
|
65
70
|
// Live volume drives amplitude and frequency instantly while
|
|
66
71
|
// speaking, so the line never lags the audio -- and lands at the
|
|
67
72
|
// override immediately on re-entry, matching upstream's same-commit
|
|
@@ -28,7 +28,10 @@ function ChatContainerRoot(props: ChatContainerRootProps) {
|
|
|
28
28
|
<ChatContainerContext.Provider value={{ isAtBottom, scrollToBottom }}>
|
|
29
29
|
<div
|
|
30
30
|
ref={ref}
|
|
31
|
-
|
|
31
|
+
// `kai-focus-inset`: the log fills a clipping container, so the default
|
|
32
|
+
// focus outline (drawn outside the border box) was clipped away
|
|
33
|
+
// entirely — this region was tabbable with no visible indicator.
|
|
34
|
+
class={cn('flex flex-col overflow-y-auto kai-focus-inset', local.class)}
|
|
32
35
|
role="log"
|
|
33
36
|
// Keyboard users must be able to scroll the conversation even when no
|
|
34
37
|
// message contains a focusable control (WCAG 2.1.1 — axe
|
|
@@ -120,8 +120,8 @@ export interface ChatThreadProps {
|
|
|
120
120
|
accept?: MediaTypeFilter;
|
|
121
121
|
/** Files the composer refused because `accept` excluded them. */
|
|
122
122
|
onAttachmentsRejected?: (rejected: RejectedAttachment[]) => void;
|
|
123
|
-
/** Show a
|
|
124
|
-
|
|
123
|
+
/** Show a web-search (Globe) button in the input toolbar; calls `onWebSearch`. */
|
|
124
|
+
webSearch?: boolean;
|
|
125
125
|
/** Show a Voice (Mic) button in the input toolbar; fires a `voice` event. */
|
|
126
126
|
voice?: boolean;
|
|
127
127
|
/** Rich entity triggers. Each `{ char, kind, items }` opens a caret-anchored
|
|
@@ -140,7 +140,7 @@ export interface ChatThreadProps {
|
|
|
140
140
|
onSuggestionClick?: (value: string) => void;
|
|
141
141
|
onModelChange?: (modelId: string) => void;
|
|
142
142
|
onMessageAction?: (detail: MessageActionDetail) => void;
|
|
143
|
-
|
|
143
|
+
onWebSearch?: () => void;
|
|
144
144
|
onVoice?: () => void;
|
|
145
145
|
/** Receive the imperative controller once mounted. The kai-chat facade forwards
|
|
146
146
|
* these as element methods (focus/clear/send/scrollToBottom). */
|
|
@@ -194,6 +194,30 @@ export interface ChatThreadController {
|
|
|
194
194
|
* Contract: `id` must be unique per message. It already had to be — the
|
|
195
195
|
* feedback/copy state above this list is keyed by it.
|
|
196
196
|
*/
|
|
197
|
+
/** How an ASSISTANT row aligns its parts across the column (K-D11).
|
|
198
|
+
*
|
|
199
|
+
* `stretch`, not `start`. An assistant turn is a `flex flex-col` box, so under
|
|
200
|
+
* `items-start` every part is a flex item with a fit-content cross size —
|
|
201
|
+
* `min(max-content, column)`. Prose is wider than the column so a text bubble
|
|
202
|
+
* looked right, and a generative-UI card was as wide as its widest button: the
|
|
203
|
+
* ops-console parameters form measured 285px inside a 768px column while the
|
|
204
|
+
* approval card beside it filled all 768. A card in a chat thread filling its
|
|
205
|
+
* column is a fact about the medium, and no consumer can reach it — the card
|
|
206
|
+
* element is created inside `<kai-chat>`'s shadow root.
|
|
207
|
+
*
|
|
208
|
+
* Stretch rather than `w-full` on the cards, for two reasons: it is one lever
|
|
209
|
+
* instead of one per card surface (the Solid `Card` root AND every `kai-*`
|
|
210
|
+
* card host, whose shadow wrapper is `display: contents`), and stretching only
|
|
211
|
+
* applies where the cross size is `auto`, so a part that states its own width
|
|
212
|
+
* is untouched — `Attachments variant="grid"` stays `w-fit`. `Tool` and
|
|
213
|
+
* `Reasoning` already asked for `w-full` explicitly; this is the same
|
|
214
|
+
* intention, applied once.
|
|
215
|
+
*
|
|
216
|
+
* USER rows keep `items-end`: their bubble is `max-w-[85%]` and right aligned,
|
|
217
|
+
* and stretching it would break both.
|
|
218
|
+
*/
|
|
219
|
+
const ASSISTANT_ALIGN = 'items-stretch';
|
|
220
|
+
|
|
197
221
|
export function ChatThread(props: ChatThreadProps) {
|
|
198
222
|
const outer = useChatConfig();
|
|
199
223
|
const reveal = () => (props.actionsReveal === 'hover' ? 'hover' : 'always');
|
|
@@ -335,6 +359,11 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
335
359
|
const body = (
|
|
336
360
|
<MessageBody
|
|
337
361
|
parts={m().parts}
|
|
362
|
+
/* F-21: streaming-ness = the thread's ONE existing
|
|
363
|
+
loading signal + being the last message and an
|
|
364
|
+
assistant turn. No second streaming source; the
|
|
365
|
+
reasoning disclosure auto-opens on it. */
|
|
366
|
+
isStreaming={props.loading === true && m().role === 'assistant' && i() === props.messages.length - 1}
|
|
338
367
|
cardTypes={props.cardTypes}
|
|
339
368
|
cardSchemas={props.cardSchemas}
|
|
340
369
|
isUser={m().role === 'user'}
|
|
@@ -356,7 +385,7 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
356
385
|
<Show
|
|
357
386
|
when={m().avatar}
|
|
358
387
|
fallback={
|
|
359
|
-
<Message role={m().role} class={`${rowGroup()}${m().role === 'user' ? 'flex-col items-end' :
|
|
388
|
+
<Message role={m().role} class={`${rowGroup()}${m().role === 'user' ? 'flex-col items-end' : `flex-col ${ASSISTANT_ALIGN}`}`}>
|
|
360
389
|
{body}
|
|
361
390
|
</Message>
|
|
362
391
|
}
|
|
@@ -364,7 +393,7 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
364
393
|
{(av) => (
|
|
365
394
|
<Message role={m().role} class={rowGroup()}>
|
|
366
395
|
<MessageAvatar src={av().src ?? ''} alt={av().alt ?? ''} fallback={av().fallback} />
|
|
367
|
-
<div class={`flex min-w-0 flex-1 flex-col ${m().role === 'user' ? 'items-end' :
|
|
396
|
+
<div class={`flex min-w-0 flex-1 flex-col ${m().role === 'user' ? 'items-end' : ASSISTANT_ALIGN}`}>
|
|
368
397
|
{body}
|
|
369
398
|
</div>
|
|
370
399
|
</Message>
|
|
@@ -401,11 +430,11 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
401
430
|
value={current()} placeholder={props.placeholder} loading={props.loading === true}
|
|
402
431
|
suggestions={visibleSuggestions()} attachments={attachments()}
|
|
403
432
|
accept={props.accept} onAttachmentsRejected={props.onAttachmentsRejected}
|
|
404
|
-
|
|
433
|
+
webSearch={props.webSearch === true} voice={props.voice === true}
|
|
405
434
|
triggers={props.triggers} kindIcons={props.kindIcons}
|
|
406
435
|
onValueChange={handleChange} onSubmit={handleSubmit} onSuggestionClick={handleSuggestionClick}
|
|
407
436
|
onAttachmentsChange={(a) => { setAttachments(a); props.onAttachmentsChange?.(a); }}
|
|
408
|
-
|
|
437
|
+
onWebSearch={() => props.onWebSearch?.()} onVoice={() => props.onVoice?.()}
|
|
409
438
|
/>
|
|
410
439
|
}
|
|
411
440
|
>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type JSX, splitProps, createResource, Show } from 'solid-js';
|
|
1
|
+
import { type JSX, splitProps, createResource, createSignal, onCleanup, Show } from 'solid-js';
|
|
2
|
+
import { Copy, Check } from 'lucide-solid';
|
|
2
3
|
import { cn } from '../utils/cn';
|
|
3
4
|
import { useChatConfig } from '../primitives/chat-config';
|
|
4
5
|
import { highlight, isCodeHighlightingEnabled } from '../primitives/highlighter';
|
|
@@ -7,10 +8,69 @@ import { highlight, isCodeHighlightingEnabled } from '../primitives/highlighter'
|
|
|
7
8
|
|
|
8
9
|
export interface CodeBlockProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
9
10
|
children?: JSX.Element;
|
|
11
|
+
/**
|
|
12
|
+
* Render a copy button in a header row above the code.
|
|
13
|
+
*
|
|
14
|
+
* DEFAULT OFF, and deliberately. This component is shared by three surfaces —
|
|
15
|
+
* `markdown.tsx` (every fenced block in every assistant message), `artifact.tsx`
|
|
16
|
+
* (the code panel), and the `<kai-code-block>` facade — and only the ELEMENT is
|
|
17
|
+
* documented as shipping a copy button. Defaulting on would add one to every code
|
|
18
|
+
* block in every message, a kit-wide visible change. The facade opts in; the other
|
|
19
|
+
* two are untouched, which `tests/elements/code-block.test.tsx` pins both
|
|
20
|
+
* behaviourally and at the call sites.
|
|
21
|
+
*/
|
|
22
|
+
copy?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The exact text the copy button puts on the clipboard.
|
|
25
|
+
*
|
|
26
|
+
* Named for what it DOES rather than what it holds, because the distinction is the
|
|
27
|
+
* whole contract: by the time the button is clicked the source has been through
|
|
28
|
+
* Shiki, so anything read back out of the DOM is either `<span>`-laden HTML or a
|
|
29
|
+
* `textContent` reconstruction — and the reconstruction is where tabs, blank lines
|
|
30
|
+
* and trailing newlines quietly change. This prop never goes near the highlighter.
|
|
31
|
+
*/
|
|
32
|
+
copyText?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** How long the button stays in its acknowledged state. Matches MessageCopyButton. */
|
|
36
|
+
const COPIED_MS = 2000;
|
|
37
|
+
|
|
38
|
+
function CodeBlockCopyButton(props: { text: string }) {
|
|
39
|
+
const [copied, setCopied] = createSignal(false);
|
|
40
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
41
|
+
// A click just before unmount would otherwise set state on a disposed owner.
|
|
42
|
+
onCleanup(() => clearTimeout(timer));
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
part="copy"
|
|
48
|
+
// The name carries the state, so a screen-reader user gets the same
|
|
49
|
+
// acknowledgement the icon swap gives a sighted one. The glyph is decorative.
|
|
50
|
+
aria-label={copied() ? 'Copied' : 'Copy code'}
|
|
51
|
+
class={cn(
|
|
52
|
+
'inline-flex items-center justify-center rounded-md p-1.5',
|
|
53
|
+
'text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',
|
|
54
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
55
|
+
)}
|
|
56
|
+
onClick={() => {
|
|
57
|
+
// Same mechanism as MessageCopyButton. A rejected promise (no permission,
|
|
58
|
+
// insecure context) must not become an unhandled rejection.
|
|
59
|
+
void Promise.resolve(navigator.clipboard?.writeText(props.text)).catch(() => {});
|
|
60
|
+
setCopied(true);
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
timer = setTimeout(() => setCopied(false), COPIED_MS);
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<Show when={copied()} fallback={<Copy size={14} aria-hidden="true" />}>
|
|
66
|
+
<Check size={14} aria-hidden="true" class="text-emerald-500" />
|
|
67
|
+
</Show>
|
|
68
|
+
</button>
|
|
69
|
+
);
|
|
10
70
|
}
|
|
11
71
|
|
|
12
72
|
function CodeBlock(props: CodeBlockProps) {
|
|
13
|
-
const [local, rest] = splitProps(props, ['children', 'class']);
|
|
73
|
+
const [local, rest] = splitProps(props, ['children', 'class', 'copy', 'copyText']);
|
|
14
74
|
return (
|
|
15
75
|
<div
|
|
16
76
|
class={cn(
|
|
@@ -23,6 +83,15 @@ function CodeBlock(props: CodeBlockProps) {
|
|
|
23
83
|
)}
|
|
24
84
|
{...rest}
|
|
25
85
|
>
|
|
86
|
+
<Show when={local.copy}>
|
|
87
|
+
{/* A header row, NOT an overlay on the code: the code region scrolls
|
|
88
|
+
horizontally, so a button inside it would ride off-screen with the code.
|
|
89
|
+
Always visible — hover-only affordances fail touch outright and are
|
|
90
|
+
undiscoverable everywhere else. */}
|
|
91
|
+
<CodeBlockGroup class="justify-end border-b border-border px-2 py-1">
|
|
92
|
+
<CodeBlockCopyButton text={local.copyText ?? ''} />
|
|
93
|
+
</CodeBlockGroup>
|
|
94
|
+
</Show>
|
|
26
95
|
{local.children}
|
|
27
96
|
</div>
|
|
28
97
|
);
|
|
@@ -62,7 +131,11 @@ function CodeBlockCode(props: CodeBlockCodeProps) {
|
|
|
62
131
|
|
|
63
132
|
const classNames = () =>
|
|
64
133
|
cn(
|
|
65
|
-
|
|
134
|
+
// `kai-focus-inset`: this region fills a rounded `overflow: clip` wrapper,
|
|
135
|
+
// so the default focus outline (offset 2px, i.e. entirely outside the
|
|
136
|
+
// border box) was clipped away and keyboard users saw nothing. The inset
|
|
137
|
+
// ring is drawn inside the box, where the wrapper cannot erase it.
|
|
138
|
+
'w-full overflow-x-auto kai-focus-inset [&>pre]:px-4 [&>pre]:py-4',
|
|
66
139
|
codeTextSize(),
|
|
67
140
|
local.class
|
|
68
141
|
);
|
|
@@ -102,4 +175,7 @@ function CodeBlockGroup(props: CodeBlockGroupProps) {
|
|
|
102
175
|
);
|
|
103
176
|
}
|
|
104
177
|
|
|
178
|
+
// CodeBlockCopyButton is deliberately NOT exported: `src/index.ts` re-exports this
|
|
179
|
+
// module's public trio, and adding a fourth symbol here that index.ts does not carry
|
|
180
|
+
// would create a half-public component. It is an implementation detail of `copy`.
|
|
105
181
|
export { CodeBlock, CodeBlockCode, CodeBlockGroup };
|
|
@@ -799,9 +799,16 @@ export function Composer(props: ComposerProps): JSX.Element {
|
|
|
799
799
|
// This is covered by updateTriggerState() in handleInput.
|
|
800
800
|
|
|
801
801
|
const maxH = () => props.maxHeight ?? 240;
|
|
802
|
+
// No `outline-none` in the default class. This element is `contenteditable`
|
|
803
|
+
// with `role="textbox"` — a real tab stop, and the primary one in the kit —
|
|
804
|
+
// and suppressing the outline left keyboard users with no indication that the
|
|
805
|
+
// message input was focused. `kai-focus-inset` draws the ring inside the
|
|
806
|
+
// border box, because the editable fills a rounded, clipping composer shell
|
|
807
|
+
// that would otherwise erase an outset one. A consumer passing
|
|
808
|
+
// `editableClass` still replaces this wholesale.
|
|
802
809
|
const editableCls = () =>
|
|
803
810
|
props.editableClass ??
|
|
804
|
-
'text-foreground min-h-[44px] w-full overflow-y-auto
|
|
811
|
+
'text-foreground min-h-[44px] w-full overflow-y-auto kai-focus-inset whitespace-pre-wrap break-words';
|
|
805
812
|
|
|
806
813
|
const inner = (
|
|
807
814
|
<>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Show, splitProps, createMemo } from 'solid-js';
|
|
1
|
+
import { Show, splitProps, createMemo, type JSX } from 'solid-js';
|
|
2
2
|
import { MessageSquare } from 'lucide-solid';
|
|
3
3
|
import { cn } from '../utils/cn';
|
|
4
4
|
import type { ConversationSummary } from '../types';
|
|
@@ -31,12 +31,122 @@ export function relativeTimeShort(iso?: string, now: number = Date.now()): strin
|
|
|
31
31
|
return `${Math.floor(hours / 24)}d ago`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The slotted-item shape rendered by `<kai-conversation-item>` — the composed
|
|
36
|
+
* row of the consumer-owned loop (spec 2026-08-20 § 2a). Distinct from the
|
|
37
|
+
* data-mode `ConversationItem` above, which batteries mode keeps rendering
|
|
38
|
+
* unchanged: this one takes REGIONS, not a `ConversationSummary`.
|
|
39
|
+
*
|
|
40
|
+
* Regions map 1:1 to the element's slots: `children` = the default slot (the
|
|
41
|
+
* title), plus `leading`, `meta`, and `menu`. The `menu` region takes the
|
|
42
|
+
* consumer's OWN popover (rename / fork / archive live there); the component
|
|
43
|
+
* provides only the region plus focus and ARIA plumbing, never a declarative
|
|
44
|
+
* actions prop. Activation semantics live in the CONTAINER
|
|
45
|
+
* (`createConversationItemsController` in conversation-list.tsx): this row is
|
|
46
|
+
* presentational, and the `data-kai-item-menu` marker on the menu region is what
|
|
47
|
+
* the container's activation guard keys off so a click in the consumer's menu
|
|
48
|
+
* never also selects the row.
|
|
49
|
+
*
|
|
50
|
+
* ARIA contract for direct Solid use: the row renders `role="listitem"` holding
|
|
51
|
+
* a `role="button"` body (`aria-current` marks the active row, the same dialect
|
|
52
|
+
* as the batteries-mode row above) with the menu as the body's tabbable SIBLING
|
|
53
|
+
* (ratified 2026-08-20; axe `nested-interactive` bans focusable descendants of
|
|
54
|
+
* a control, and `aria-required-children` outlaws non-option content anywhere
|
|
55
|
+
* inside a listbox, which is why the vocabulary is not listbox/option). Render
|
|
56
|
+
* your loop's rows inside a `role="list"` ancestor; element mode has no such
|
|
57
|
+
* burden: `<kai-conversations>`' items region provides it for slotted items.
|
|
58
|
+
*/
|
|
59
|
+
export interface SlottedConversationItemProps {
|
|
60
|
+
/** The row's identity, handed to the container's selection contract. In the
|
|
61
|
+
* element this is the `conversation-id` attribute (host `id` is the fallback). */
|
|
62
|
+
conversationId?: string;
|
|
63
|
+
/** Selected state. Reflected as `aria-current` on the row body and a
|
|
64
|
+
* `data-active` styling hook on the row; the container drives it from its
|
|
65
|
+
* `activeId`. */
|
|
66
|
+
active?: boolean;
|
|
67
|
+
/** Dense single-line row padding. */
|
|
68
|
+
compact?: boolean;
|
|
69
|
+
/** Leading region before the title (an icon or avatar). */
|
|
70
|
+
leading?: JSX.Element;
|
|
71
|
+
/** Meta region under the title (a timestamp or status line). */
|
|
72
|
+
meta?: JSX.Element;
|
|
73
|
+
/** Your own row menu (a popover trigger). Never selects the row. */
|
|
74
|
+
menu?: JSX.Element;
|
|
75
|
+
/** The title (the element's default slot). */
|
|
76
|
+
children?: JSX.Element;
|
|
77
|
+
/** Set when the element HOST carries the row-group semantics: the inner row
|
|
78
|
+
* then renders `role="presentation"` so the accessibility tree sees one
|
|
79
|
+
* group (the host), never two. The `kai-conversation-item` facade sets it;
|
|
80
|
+
* Solid consumers rendering the component directly leave it off and the row
|
|
81
|
+
* itself is the group. */
|
|
82
|
+
hostSemantics?: boolean;
|
|
83
|
+
class?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function SlottedConversationItem(props: SlottedConversationItemProps) {
|
|
87
|
+
const [local] = splitProps(props, ['conversationId', 'active', 'compact', 'leading', 'meta', 'menu', 'children', 'hostSemantics', 'class']);
|
|
88
|
+
return (
|
|
89
|
+
// The sibling restructure (ratified 2026-08-20): axe nested-interactive
|
|
90
|
+
// bans focusable descendants of an activation control, so the control role
|
|
91
|
+
// sits on the row BODY and the consumer's menu is its tabbable SIBLING
|
|
92
|
+
// (the nav.tsx TrailingActions precedent). The vocabulary is
|
|
93
|
+
// list/listitem/button + aria-current — NOT listbox/option, because axe
|
|
94
|
+
// aria-required-children lets a listbox subtree own nothing but options
|
|
95
|
+
// (and groups of options), which outlaws a sibling menu ANYWHERE inside
|
|
96
|
+
// it; button + aria-current is also exactly what the batteries-mode
|
|
97
|
+
// ConversationItem row above renders, so the two modes speak one dialect.
|
|
98
|
+
<div
|
|
99
|
+
part="row"
|
|
100
|
+
role={local.hostSemantics ? 'presentation' : 'listitem'}
|
|
101
|
+
data-active={local.active ? '' : undefined}
|
|
102
|
+
data-conversation-id={local.conversationId}
|
|
103
|
+
class={cn(
|
|
104
|
+
'flex w-full items-center gap-2.5 rounded-lg text-left transition-colors',
|
|
105
|
+
local.compact ? 'px-2.5 py-1.5' : 'px-2.5 py-2',
|
|
106
|
+
local.active ? 'bg-muted' : 'hover:bg-muted/50',
|
|
107
|
+
local.class,
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
<div
|
|
111
|
+
part="body"
|
|
112
|
+
role="button"
|
|
113
|
+
data-kai-item-body
|
|
114
|
+
aria-current={local.active ? 'true' : 'false'}
|
|
115
|
+
class="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
116
|
+
>
|
|
117
|
+
<Show when={local.leading}>
|
|
118
|
+
<span part="leading" class="flex shrink-0 items-center text-muted-foreground">{local.leading}</span>
|
|
119
|
+
</Show>
|
|
120
|
+
<div class="min-w-0 flex-1">
|
|
121
|
+
<div part="title" class={cn('truncate text-sm', local.active ? 'font-medium text-foreground' : 'text-foreground/80')}>
|
|
122
|
+
{local.children}
|
|
123
|
+
</div>
|
|
124
|
+
<Show when={local.meta}>
|
|
125
|
+
<div part="meta" class="mt-0.5 truncate text-xs text-muted-foreground">{local.meta}</div>
|
|
126
|
+
</Show>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
<Show when={local.menu}>
|
|
130
|
+
<span part="menu" data-kai-item-menu class="ml-auto flex shrink-0 items-center">{local.menu}</span>
|
|
131
|
+
</Show>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
34
136
|
export function ConversationItem(props: ConversationItemProps) {
|
|
35
137
|
const [local] = splitProps(props, ['conversation', 'isActive', 'onSelect', 'compact', 'class']);
|
|
36
138
|
// The trailing text: the consumer's own `trailing` field, else an auto relative
|
|
37
|
-
// time from updatedAt (fallback lastMessageAt).
|
|
38
|
-
// time
|
|
39
|
-
//
|
|
139
|
+
// time from updatedAt (fallback lastMessageAt). Never an internal clock — it is a
|
|
140
|
+
// render-time snapshot.
|
|
141
|
+
//
|
|
142
|
+
// REACTIVITY, and the weaker version of this note is what #224 was filed against:
|
|
143
|
+
// a new `conversations` array reference is NOT sufficient. `ConversationList` renders
|
|
144
|
+
// these rows through a reference-keyed `<For>` that captures `conv` as a VALUE, so a
|
|
145
|
+
// row whose item object is unchanged is never re-invoked and never re-reads anything
|
|
146
|
+
// below. The consumer needs BOTH — a new array (which is what notifies at all) and a
|
|
147
|
+
// new object for the item that changed (which is what this row can see). Adds,
|
|
148
|
+
// removes and reorders are fine on a fresh array alone, since those rows' identities
|
|
149
|
+
// already differ. Pinned by `src/components/reactivity-contract.test.tsx`.
|
|
40
150
|
const trailing = createMemo(
|
|
41
151
|
() => local.conversation.trailing ?? relativeTimeShort(local.conversation.updatedAt ?? local.conversation.lastMessageAt),
|
|
42
152
|
);
|