@kitn.ai/ui 0.25.1 → 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 +5 -7
- 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 +27 -7
- 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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createSignal, onCleanup, onMount } from 'solid-js';
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
|
+
import { readSlots, CONVERSATION_ITEM_SLOTS } from './slots';
|
|
4
|
+
import { SlottedConversationItem } from '../components/conversation-item';
|
|
5
|
+
|
|
6
|
+
interface Props extends Record<string, unknown> {
|
|
7
|
+
/** The row's identity, handed to the container's selection contract. In the
|
|
8
|
+
* element this is the `conversation-id` attribute (host `id` is the fallback). */
|
|
9
|
+
conversationId?: string;
|
|
10
|
+
/** Selected state. Reflected as `aria-current` on the row body and a
|
|
11
|
+
* `data-active` styling hook on the row; the container drives it from its
|
|
12
|
+
* `activeId`. */
|
|
13
|
+
active?: boolean;
|
|
14
|
+
/** Dense single-line row padding. */
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `<kai-conversation-item>` — one composed row of a consumer-owned conversation
|
|
20
|
+
* loop, slotted into `<kai-conversations>`' light DOM. The consumer owns the
|
|
21
|
+
* loop (framework-native `map`, `<For>`, `v-for`); the container detects these
|
|
22
|
+
* children, skips its data rendering, and runs the parent-item contract over
|
|
23
|
+
* them: selection state flowing container to item, roving tabindex, and the
|
|
24
|
+
* accessible list/row relationship (list rows with button bodies and
|
|
25
|
+
* `aria-current` marking the active one; ratified 2026-08-20 — axe's
|
|
26
|
+
* nested-interactive and aria-required-children rules are why this is not
|
|
27
|
+
* listbox/option).
|
|
28
|
+
*
|
|
29
|
+
* Slots: the default slot is the title; `leading`, `meta` and `menu` are the
|
|
30
|
+
* named regions. The `menu` slot takes your OWN popover (rename, fork, archive
|
|
31
|
+
* live there); the element provides only the region plus focus and ARIA
|
|
32
|
+
* plumbing, never a declarative actions prop, and a click inside it never
|
|
33
|
+
* selects the row.
|
|
34
|
+
*
|
|
35
|
+
* The item is presentational on its own: activation (click, Enter, Space)
|
|
36
|
+
* surfaces as `kai-conversation-select` on the surrounding `<kai-conversations>`,
|
|
37
|
+
* never as an event of this element.
|
|
38
|
+
*/
|
|
39
|
+
defineWebComponent<Props>('kai-conversation-item', {
|
|
40
|
+
conversationId: undefined,
|
|
41
|
+
active: undefined,
|
|
42
|
+
compact: undefined,
|
|
43
|
+
}, (props, { element, flag, reflectFlag }) => {
|
|
44
|
+
// Which named regions the consumer has filled; drives the conditional
|
|
45
|
+
// wrappers so an empty region leaves no stray box behind.
|
|
46
|
+
const [slots, setSlots] = createSignal<Record<string, boolean>>({});
|
|
47
|
+
onMount(() => {
|
|
48
|
+
const read = () => setSlots(readSlots(element, CONVERSATION_ITEM_SLOTS));
|
|
49
|
+
read();
|
|
50
|
+
const observer = new MutationObserver(read);
|
|
51
|
+
observer.observe(element, { childList: true, attributes: true, subtree: true });
|
|
52
|
+
onCleanup(() => observer.disconnect());
|
|
53
|
+
|
|
54
|
+
// The HOST is the row LISTITEM (sibling restructure, ratified 2026-08-20):
|
|
55
|
+
// it wraps the activation body AND the consumer's tabbable menu, so it must
|
|
56
|
+
// never be the activation control itself — axe nested-interactive bans
|
|
57
|
+
// focusable descendants of a control. The button role, aria-current and the
|
|
58
|
+
// roving tabindex live on the shadow BODY (`data-kai-item-body`), which the
|
|
59
|
+
// container's controller targets. An authored role wins.
|
|
60
|
+
if (!element.hasAttribute('role')) element.setAttribute('role', 'listitem');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Property and attribute stay in agreement for `active`; the body's
|
|
64
|
+
// aria-current follows it reactively (nothing is written to the host).
|
|
65
|
+
reflectFlag('active');
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<SlottedConversationItem
|
|
69
|
+
conversationId={props.conversationId as string | undefined}
|
|
70
|
+
active={flag('active')}
|
|
71
|
+
compact={flag('compact')}
|
|
72
|
+
hostSemantics
|
|
73
|
+
leading={slots().leading ? <slot name="leading" /> : undefined}
|
|
74
|
+
meta={slots().meta ? <slot name="meta" /> : undefined}
|
|
75
|
+
menu={slots().menu ? <slot name="menu" /> : undefined}
|
|
76
|
+
>
|
|
77
|
+
<slot />
|
|
78
|
+
</SlottedConversationItem>
|
|
79
|
+
);
|
|
80
|
+
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { createSignal, onMount, onCleanup, Show } from 'solid-js';
|
|
1
|
+
import { createSignal, createEffect, onMount, onCleanup, Show } from 'solid-js';
|
|
2
2
|
import { defineWebComponent } from './define';
|
|
3
3
|
import { createControllableSignal } from '../primitives/controllable';
|
|
4
4
|
import { readSlots, CONVERSATIONS_SLOTS } from './slots';
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
ConversationList, CollapsedRail, createConversationItemsController,
|
|
7
|
+
type ConversationListController,
|
|
8
|
+
} from '../components/conversation-list';
|
|
9
|
+
import type { ConversationGroup, ConversationSummary } from '../types';
|
|
7
10
|
|
|
8
11
|
interface Props extends Record<string, unknown> {
|
|
9
12
|
/** The list's section headers (`{ id, name, sortOrder, createdAt }`), rendered
|
|
@@ -16,7 +19,11 @@ interface Props extends Record<string, unknown> {
|
|
|
16
19
|
* matching no entry in `groups`, falls into a trailing "Ungrouped" section, so
|
|
17
20
|
* nothing you pass in is ever dropped. There is no recency bucketing. Set as a
|
|
18
21
|
* JS property. Omit to supply them as `<kai-conversation>` light-DOM children
|
|
19
|
-
* instead, or for the empty state.
|
|
22
|
+
* instead, or for the empty state. A search query that matches nothing shows a
|
|
23
|
+
* visible "No conversations match your search" state, distinct from the
|
|
24
|
+
* zero-conversations empty state. Slotted `<kai-conversation-item>` children
|
|
25
|
+
* switch the list into item mode instead: your own rows win and this array is
|
|
26
|
+
* not rendered. */
|
|
20
27
|
conversations?: ConversationSummary[];
|
|
21
28
|
/** The id of the currently-open conversation, highlighted in the list. */
|
|
22
29
|
activeId?: string;
|
|
@@ -28,10 +35,14 @@ interface Props extends Record<string, unknown> {
|
|
|
28
35
|
/** Initial collapsed state when uncontrolled (default false). Use the
|
|
29
36
|
* `default-collapsed` attribute to start collapsed in plain HTML. */
|
|
30
37
|
defaultCollapsed?: boolean;
|
|
38
|
+
/** Dense single-line rows (a leading dot + title, no message count). */
|
|
39
|
+
compact?: boolean;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
interface Events {
|
|
34
|
-
/** A conversation was selected.
|
|
43
|
+
/** A conversation was selected. The selection event in BOTH modes: a
|
|
44
|
+
* batteries data row, or an activated `<kai-conversation-item>` child
|
|
45
|
+
* (click, Enter or Space). */
|
|
35
46
|
'kai-conversation-select': { id: string };
|
|
36
47
|
/** The "New chat" button was clicked. */
|
|
37
48
|
'kai-new-chat': Record<string, never>;
|
|
@@ -50,19 +61,19 @@ interface Events {
|
|
|
50
61
|
* - `id` → ConversationSummary.id
|
|
51
62
|
* - `group-id` → ConversationSummary.groupId (optional)
|
|
52
63
|
* - textContent → ConversationSummary.title
|
|
53
|
-
*
|
|
54
|
-
* `
|
|
55
|
-
*
|
|
64
|
+
* Fields not expressible as HTML attributes are NOT fabricated (F-10): the
|
|
65
|
+
* optional `scope` and `lastMessageAt` stay absent, and the required
|
|
66
|
+
* `messageCount`/`updatedAt` get honest defaults — zero messages, and an empty
|
|
67
|
+
* `updatedAt` from which no trailing relative time is derived (the epoch it
|
|
68
|
+
* used to fabricate rendered a bogus "many days ago" on every declarative row).
|
|
56
69
|
*/
|
|
57
70
|
export function parseKaiConversationElement(n: Element): ConversationSummary {
|
|
58
71
|
return {
|
|
59
72
|
id: n.getAttribute('id') ?? '',
|
|
60
73
|
title: n.textContent?.trim() ?? '',
|
|
61
74
|
groupId: n.getAttribute('group-id') ?? undefined,
|
|
62
|
-
scope: { type: 'collection' } as ConversationScope,
|
|
63
75
|
messageCount: 0,
|
|
64
|
-
|
|
65
|
-
updatedAt: new Date(0).toISOString(),
|
|
76
|
+
updatedAt: '',
|
|
66
77
|
};
|
|
67
78
|
}
|
|
68
79
|
|
|
@@ -72,17 +83,34 @@ defineWebComponent<Props, Events>('kai-conversations', {
|
|
|
72
83
|
activeId: undefined,
|
|
73
84
|
collapsed: undefined,
|
|
74
85
|
defaultCollapsed: undefined,
|
|
86
|
+
compact: undefined,
|
|
75
87
|
}, (props, { dispatch, element, expose, flag }) => {
|
|
76
88
|
// Read declarative <kai-conversation> children from light DOM.
|
|
77
89
|
// Shadow DOM with no <slot> suppresses them visually — they're invisible data carriers.
|
|
78
90
|
const [slottedConversations, setSlottedConversations] = createSignal<ConversationSummary[]>([]);
|
|
91
|
+
// Item mode (spec 2026-08-20 § 2a): light-DOM <kai-conversation-item> children
|
|
92
|
+
// mean the CONSUMER owns the loop — the container skips its data rendering and
|
|
93
|
+
// runs the parent-item contract over the hosts instead.
|
|
94
|
+
const [itemHosts, setItemHosts] = createSignal<HTMLElement[]>([]);
|
|
79
95
|
// Which composition slots (header/empty/footer) the consumer has filled.
|
|
80
96
|
const [slots, setSlots] = createSignal<Record<string, boolean>>({});
|
|
81
97
|
onMount(() => {
|
|
82
98
|
const read = () => {
|
|
83
99
|
const nodes = [...element.querySelectorAll('kai-conversation')];
|
|
84
100
|
setSlottedConversations(nodes.map(parseKaiConversationElement));
|
|
101
|
+
// Reference-stable: a fresh array every observer tick would re-run the
|
|
102
|
+
// sync effect (whose writes the observer sees) in a feedback loop.
|
|
103
|
+
const hosts = [...element.querySelectorAll<HTMLElement>(':scope > kai-conversation-item')];
|
|
104
|
+
setItemHosts((prev) =>
|
|
105
|
+
prev.length === hosts.length && hosts.every((h, i) => h === prev[i]) ? prev : hosts,
|
|
106
|
+
);
|
|
85
107
|
setSlots(readSlots(element, CONVERSATIONS_SLOTS));
|
|
108
|
+
// Re-sync on EVERY mutation, not only membership changes: an item host
|
|
109
|
+
// that upgrades after the first sync mutates its own attributes (the
|
|
110
|
+
// facade's mount), which lands here — and its shadow body needs stamping
|
|
111
|
+
// even though the hosts array is reference-stable. sync() writes
|
|
112
|
+
// on-change only, so this cannot feed the observer a loop.
|
|
113
|
+
if (hosts.length) itemsController.sync();
|
|
86
114
|
};
|
|
87
115
|
read();
|
|
88
116
|
const observer = new MutationObserver(read);
|
|
@@ -90,9 +118,26 @@ defineWebComponent<Props, Events>('kai-conversations', {
|
|
|
90
118
|
onCleanup(() => observer.disconnect());
|
|
91
119
|
});
|
|
92
120
|
|
|
121
|
+
const itemMode = () => itemHosts().length > 0;
|
|
122
|
+
|
|
93
123
|
// Prop conversations take precedence; slotted children are appended after.
|
|
94
124
|
const allConversations = () => [...(props.conversations ?? []), ...slottedConversations()];
|
|
95
125
|
|
|
126
|
+
// The parent-item contract: selection flowing container→item, roving tabindex,
|
|
127
|
+
// ARIA list-row bookkeeping — pure DOM, host-agnostic (see the controller's JSDoc in
|
|
128
|
+
// components/conversation-list.tsx). Solid context cannot cross the element
|
|
129
|
+
// boundary, so the channel is DOM traversal by construction.
|
|
130
|
+
const itemsController = createConversationItemsController({
|
|
131
|
+
getItems: itemHosts,
|
|
132
|
+
getActiveId: () => props.activeId as string | undefined,
|
|
133
|
+
onSelect: (id) => dispatch('kai-conversation-select', { id }),
|
|
134
|
+
});
|
|
135
|
+
// Re-derive the bookkeeping whenever the children change (the MutationObserver
|
|
136
|
+
// and the shadow slot's slotchange both funnel into itemHosts) or activeId moves.
|
|
137
|
+
createEffect(() => {
|
|
138
|
+
if (itemMode()) itemsController.sync();
|
|
139
|
+
});
|
|
140
|
+
|
|
96
141
|
// ── Rail collapse (controlled/uncontrolled, same pattern as kai-workspace) ──
|
|
97
142
|
// `collapsed` (when set) wins; otherwise the element manages its own state,
|
|
98
143
|
// seeded from `defaultCollapsed`. `setCollapsedTo` always writes the internal
|
|
@@ -143,8 +188,12 @@ defineWebComponent<Props, Events>('kai-conversations', {
|
|
|
143
188
|
onSelect={(id) => dispatch('kai-conversation-select', { id })}
|
|
144
189
|
onNewChat={() => dispatch('kai-new-chat')}
|
|
145
190
|
onToggleSidebar={() => { dispatch('kai-toggle-sidebar'); setCollapsedTo(true); }}
|
|
191
|
+
compact={flag('compact')}
|
|
146
192
|
onSearchChange={(query) => dispatch('kai-search', { query })}
|
|
147
193
|
controllerRef={(c) => (controller = c)}
|
|
194
|
+
items={itemMode() ? <slot /> : undefined}
|
|
195
|
+
itemsKeyDown={itemsController.handleKeyDown}
|
|
196
|
+
itemsClick={itemsController.handleClick}
|
|
148
197
|
header={slots().header ? <slot name="header" /> : undefined}
|
|
149
198
|
footer={slots().footer ? <slot name="footer" /> : undefined}
|
|
150
199
|
empty={slots().empty ? <slot name="empty" /> : undefined}
|
|
@@ -70,8 +70,8 @@ export interface DefaultPromptInputProps {
|
|
|
70
70
|
* (name, media type, reason) and renders nothing itself: what the user should
|
|
71
71
|
* see is the application's call, not the kit's. */
|
|
72
72
|
onAttachmentsRejected?: (rejected: RejectedAttachment[]) => void;
|
|
73
|
-
/** Show a
|
|
74
|
-
|
|
73
|
+
/** Show a web-search (Globe) button in the left toolbar; calls `onWebSearch`. */
|
|
74
|
+
webSearch?: boolean;
|
|
75
75
|
/** Show a Voice (Mic) button in the left toolbar; calls `onVoice`. */
|
|
76
76
|
voice?: boolean;
|
|
77
77
|
/** Send-button visibility. `'always'` (default) always shows it; `'auto'` shows
|
|
@@ -84,7 +84,7 @@ export interface DefaultPromptInputProps {
|
|
|
84
84
|
onSubmit: () => void;
|
|
85
85
|
onSuggestionClick: (v: string) => void;
|
|
86
86
|
onAttachmentsChange?: (attachments: AttachmentData[]) => void;
|
|
87
|
-
|
|
87
|
+
onWebSearch?: () => void;
|
|
88
88
|
onVoice?: () => void;
|
|
89
89
|
/** When `true` and `loading` is also `true`, the send button is replaced by
|
|
90
90
|
* a Stop button that calls `onStop`. */
|
|
@@ -296,7 +296,7 @@ export function DefaultPromptInput(props: DefaultPromptInputProps) {
|
|
|
296
296
|
<Paperclip class="size-4" />
|
|
297
297
|
</Button>
|
|
298
298
|
</Show>
|
|
299
|
-
<Show when={props.
|
|
299
|
+
<Show when={props.webSearch}>
|
|
300
300
|
<Button
|
|
301
301
|
type="button"
|
|
302
302
|
variant="outline"
|
|
@@ -304,7 +304,7 @@ export function DefaultPromptInput(props: DefaultPromptInputProps) {
|
|
|
304
304
|
class="rounded-full gap-1"
|
|
305
305
|
aria-label="Search the web"
|
|
306
306
|
disabled={props.disabled}
|
|
307
|
-
onClick={() => props.
|
|
307
|
+
onClick={() => props.onWebSearch?.()}
|
|
308
308
|
>
|
|
309
309
|
<Globe class="size-4" />
|
|
310
310
|
Search
|
package/src/elements/define.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { customElement } from 'solid-element';
|
|
2
2
|
import { ChatConfig } from '../primitives/chat-config';
|
|
3
3
|
import { ELEMENT_CSS } from './css';
|
|
4
|
-
import {
|
|
4
|
+
import { elementDiagnosticsWanted, installElementDiagnostics } from './element-diagnostics';
|
|
5
|
+
import { createEffect, createSignal, onCleanup, Show, type JSX } from 'solid-js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Shared constructable stylesheet, built once and adopted by every element's
|
|
@@ -78,6 +79,33 @@ export interface WebComponentContext<E = Record<string, unknown>> {
|
|
|
78
79
|
* convention). Methods are attached when the facade renders (on element upgrade).
|
|
79
80
|
*/
|
|
80
81
|
expose: (methods: Record<string, (...args: never[]) => unknown>) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Reflect a boolean prop to its host attribute AND make that prop read back what
|
|
84
|
+
* was set. Use this instead of hand-rolling
|
|
85
|
+
* `createEffect(() => element.toggleAttribute('x', flag('x')))`.
|
|
86
|
+
*
|
|
87
|
+
* WHY IT IS ONE CALL AND NOT TWO. The reflection is what BREAKS the read-back, so
|
|
88
|
+
* the fix has to be attached to it or it gets forgotten — and it was, three times.
|
|
89
|
+
* `toggleAttribute(name, true)` sets the attribute to the EMPTY STRING;
|
|
90
|
+
* component-register's `attributeChangedCallback` then writes the prop back as
|
|
91
|
+
* `this[name] = parseAttributeValue("")`, and `parseAttributeValue` returns
|
|
92
|
+
* `undefined` for the empty string. So a reflected flag became write-only:
|
|
93
|
+
* `el.loading = true` left `el.loading === undefined` while `[loading]` was on the
|
|
94
|
+
* host. The element kept BEHAVING correctly, because `flag()` reads the attribute —
|
|
95
|
+
* which is precisely why nobody noticed until a consumer tried to read the property
|
|
96
|
+
* back (findings G-05).
|
|
97
|
+
*
|
|
98
|
+
* What this installs is an instance-level wrapper around the accessor
|
|
99
|
+
* component-register already created, coercing every incoming value through the
|
|
100
|
+
* same `flag()` policy (`resolveFlag`) — so the `undefined` write-back resolves to
|
|
101
|
+
* the attribute that caused it, and the property and the attribute can no longer
|
|
102
|
+
* disagree. It delegates to the underlying setter, so reactivity is untouched.
|
|
103
|
+
*
|
|
104
|
+
* `source` overrides where the ON/OFF value comes from, for an element whose truth
|
|
105
|
+
* is an internal controller rather than the prop (see `wireDisclosure`). Returning
|
|
106
|
+
* `undefined` from it means "not ready, leave the attribute alone".
|
|
107
|
+
*/
|
|
108
|
+
reflectFlag: (name: string, source?: () => boolean | undefined) => void;
|
|
81
109
|
/**
|
|
82
110
|
* Whether the element should currently render its dark-mode look --
|
|
83
111
|
* exactly the SAME resolved value that already drives the `.dark` class
|
|
@@ -197,10 +225,22 @@ function defineWithNonReflectingProps<T>(
|
|
|
197
225
|
tag: string,
|
|
198
226
|
keys: readonly string[],
|
|
199
227
|
define: () => T,
|
|
228
|
+
/**
|
|
229
|
+
* Anything else that must be on the prototype BEFORE the registry sees the
|
|
230
|
+
* class. Today that is the element-layer diagnostics, and it needs this seam
|
|
231
|
+
* for a reason that is adjacent to the one above but not the same: `define()`
|
|
232
|
+
* SNAPSHOTS the lifecycle callbacks (`connectedCallback`,
|
|
233
|
+
* `attributeChangedCallback`, …) off the prototype into the custom-element
|
|
234
|
+
* definition, and the browser invokes the snapshot. A wrapper installed
|
|
235
|
+
* afterwards is present on the prototype, passes `hasOwnProperty`, and is
|
|
236
|
+
* never called. See `installElementDiagnostics`.
|
|
237
|
+
*/
|
|
238
|
+
alsoPatch?: (proto: object) => void,
|
|
200
239
|
): { result: T; handled: boolean } {
|
|
201
|
-
// `handled` = nothing more for the caller to do. With no colliding props
|
|
202
|
-
// nothing to install at all, so that case is
|
|
203
|
-
|
|
240
|
+
// `handled` = nothing more for the caller to do. With no colliding props and
|
|
241
|
+
// nothing else to patch there is nothing to install at all, so that case is
|
|
242
|
+
// trivially handled.
|
|
243
|
+
if (keys.length === 0 && !alsoPatch) return { result: define(), handled: true };
|
|
204
244
|
const registry = customElements;
|
|
205
245
|
const hadOwnDefine = Object.prototype.hasOwnProperty.call(registry, 'define');
|
|
206
246
|
const inner = registry.define;
|
|
@@ -214,7 +254,8 @@ function defineWithNonReflectingProps<T>(
|
|
|
214
254
|
options?: ElementDefinitionOptions,
|
|
215
255
|
) {
|
|
216
256
|
if (name === tag) {
|
|
217
|
-
installNonReflectingProps(constructor.prototype, keys);
|
|
257
|
+
if (keys.length) installNonReflectingProps(constructor.prototype, keys);
|
|
258
|
+
alsoPatch?.(constructor.prototype);
|
|
218
259
|
handled = true;
|
|
219
260
|
}
|
|
220
261
|
return inner.call(this, name, constructor, options);
|
|
@@ -237,6 +278,76 @@ function defineWithNonReflectingProps<T>(
|
|
|
237
278
|
}
|
|
238
279
|
}
|
|
239
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Marks a setter as one of ours, so the wrap is idempotent WITHOUT being permanent.
|
|
283
|
+
*
|
|
284
|
+
* The mark goes on the setter function rather than on the element, and that is the
|
|
285
|
+
* whole subtlety: component-register installs a FRESH instance accessor from
|
|
286
|
+
* `initializeProps` on every (re)connect after a release, which throws our wrapper
|
|
287
|
+
* away. An element-level "already done" flag would then skip re-wrapping and the
|
|
288
|
+
* property would quietly go write-only again on the second mount. An unmarked setter
|
|
289
|
+
* is by definition a new one, so it gets wrapped again.
|
|
290
|
+
*/
|
|
291
|
+
const FLAG_READ_BACK = Symbol('kai-flag-read-back');
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Wrap the instance accessor component-register created for `name` so every value
|
|
295
|
+
* written to it — a consumer's assignment, or the `undefined` write-back that
|
|
296
|
+
* `attributeChangedCallback` performs after a `toggleAttribute` — is resolved through
|
|
297
|
+
* the same policy `flag()` uses.
|
|
298
|
+
*
|
|
299
|
+
* TIMING. This must run from the facade body, i.e. during `connectedCallback` AFTER
|
|
300
|
+
* `initializeProps`, and that is not a detail: `initializeProps` harvests any value
|
|
301
|
+
* set on the element BEFORE upgrade (`value = element[key]`) and only then installs
|
|
302
|
+
* the accessor. Wrapping earlier (on the prototype, the way `installNonReflectingProps`
|
|
303
|
+
* does) would be shadowed by that accessor and would also sit in front of the harvest —
|
|
304
|
+
* the same class of defect `define-upgrade-ordering.test.tsx` pins for attributes.
|
|
305
|
+
*/
|
|
306
|
+
function installFlagReadBack(element: HTMLElement, name: string, attribute: string): void {
|
|
307
|
+
const inner = Object.getOwnPropertyDescriptor(element, name);
|
|
308
|
+
if (!inner?.get || !inner.set) {
|
|
309
|
+
// Reached only if `name` is not a declared prop of this element — a typo, or a
|
|
310
|
+
// facade reflecting something it never declared. Warn rather than no-op silently;
|
|
311
|
+
// a silent skip here would look exactly like a working read-back.
|
|
312
|
+
console.warn(
|
|
313
|
+
`reflectFlag("${name}"): no such declared prop on <${element.localName}> — ` +
|
|
314
|
+
`the attribute will still reflect, but the property will not read back.`,
|
|
315
|
+
);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const { get, set } = inner;
|
|
319
|
+
if ((set as { [FLAG_READ_BACK]?: true })[FLAG_READ_BACK]) return;
|
|
320
|
+
|
|
321
|
+
const wrapped = function (this: HTMLElement, value: unknown) {
|
|
322
|
+
set.call(this, resolveFlag(this, value, attribute));
|
|
323
|
+
} as ((value: unknown) => void) & { [FLAG_READ_BACK]?: true };
|
|
324
|
+
wrapped[FLAG_READ_BACK] = true;
|
|
325
|
+
|
|
326
|
+
Object.defineProperty(element, name, {
|
|
327
|
+
get,
|
|
328
|
+
set: wrapped,
|
|
329
|
+
enumerable: inner.enumerable ?? true,
|
|
330
|
+
configurable: true,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Seed, but ONLY upwards. The value sitting in the prop store right now may already
|
|
334
|
+
// be the broken one: `<el loading>` parses to `undefined` in `initializeProps`
|
|
335
|
+
// exactly as it does in the write-back, so without this the bare-attribute case
|
|
336
|
+
// would still read back `undefined` until something else wrote to the prop.
|
|
337
|
+
//
|
|
338
|
+
// WHY NOT SEED `false` TOO. Writing `false` over an `undefined` would look tidier
|
|
339
|
+
// and is wrong: for props declared with an `undefined` default, `undefined` MEANS
|
|
340
|
+
// "the consumer has not set this", and at least one caller depends on telling that
|
|
341
|
+
// apart from an explicit `false` — `wireDisclosure` seeds from `defaultOpen` only
|
|
342
|
+
// while `open` is unset, so a blanket `false` seed would clobber
|
|
343
|
+
// `<el default-open>` on every mount. Promoting only a present attribute to `true`
|
|
344
|
+
// fixes the read-back without inventing an opinion the author never expressed.
|
|
345
|
+
const current = get.call(element);
|
|
346
|
+
if (typeof current !== 'boolean' && resolveFlag(element, current, attribute)) {
|
|
347
|
+
wrapped.call(element, true);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
240
351
|
/** Underlying flag resolution; see `WebComponentContext.flag`. */
|
|
241
352
|
function resolveFlag(element: HTMLElement, value: unknown, attribute: string): boolean {
|
|
242
353
|
if (value === true) return true;
|
|
@@ -329,6 +440,21 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
|
|
|
329
440
|
const flag = (name: string) =>
|
|
330
441
|
resolveFlag(element, (props as Record<string, unknown>)[name], toAttr(name));
|
|
331
442
|
|
|
443
|
+
// Reflect a boolean prop to its attribute and keep the property readable. See
|
|
444
|
+
// WebComponentContext.reflectFlag for the defect this exists to close.
|
|
445
|
+
const reflectFlag: WebComponentContext<E>['reflectFlag'] = (name, source) => {
|
|
446
|
+
const attribute = toAttr(name);
|
|
447
|
+
installFlagReadBack(element, name, attribute);
|
|
448
|
+
createEffect(() => {
|
|
449
|
+
const on = source ? source() : flag(name);
|
|
450
|
+
// `undefined` means the caller's source is not ready yet (a controller a
|
|
451
|
+
// primitive has not handed up). Leave the author's attribute alone rather
|
|
452
|
+
// than writing a guessed OFF over it.
|
|
453
|
+
if (on === undefined) return;
|
|
454
|
+
element.toggleAttribute(attribute, on);
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
|
|
332
458
|
// Attach imperative methods onto the host instance. See WebComponentContext.expose.
|
|
333
459
|
// Uses defineProperty so a method can shadow an inherited getter-only accessor
|
|
334
460
|
// (e.g. `focus`/`blur`) which a plain assignment would throw on. A method name
|
|
@@ -381,15 +507,39 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
|
|
|
381
507
|
<div classList={{ dark: isDark() }} style={{ display: 'contents', color: 'var(--color-foreground)' }}>
|
|
382
508
|
<div ref={portalNode} />
|
|
383
509
|
<ChatConfig portalMount={portalNode}>
|
|
384
|
-
{Facade(props as unknown as P, {
|
|
510
|
+
{Facade(props as unknown as P, {
|
|
511
|
+
element,
|
|
512
|
+
dispatch,
|
|
513
|
+
flag,
|
|
514
|
+
reflectFlag,
|
|
515
|
+
expose,
|
|
516
|
+
dark: isDark,
|
|
517
|
+
})}
|
|
385
518
|
</ChatConfig>
|
|
386
519
|
</div>
|
|
387
520
|
</>
|
|
388
521
|
);
|
|
389
522
|
};
|
|
390
523
|
|
|
391
|
-
|
|
392
|
-
|
|
524
|
+
// Element-layer diagnostics: report the three `kai-` contract violations that
|
|
525
|
+
// are otherwise silent (an array or object prop set as an HTML attribute, the
|
|
526
|
+
// same array reference handed back, a new array of the same item objects).
|
|
527
|
+
//
|
|
528
|
+
// HERE because this function is the single choke point — every kai-* element
|
|
529
|
+
// registers through it, which `elements/slots.test.ts` already enforces — so
|
|
530
|
+
// one call covers every element with no per-element work and no second list.
|
|
531
|
+
//
|
|
532
|
+
// Through the pre-define seam, not after the fact, because the registry
|
|
533
|
+
// snapshots lifecycle callbacks at definition time; see `alsoPatch` above.
|
|
534
|
+
// `elementDiagnosticsWanted` is asked first so a tag with no non-scalar prop
|
|
535
|
+
// (37 of the 80) does not even pay for the registry interception.
|
|
536
|
+
const wantsDiagnostics = elementDiagnosticsWanted(tag);
|
|
537
|
+
|
|
538
|
+
const { result: Ctor, handled } = defineWithNonReflectingProps(
|
|
539
|
+
tag,
|
|
540
|
+
shadowedProps,
|
|
541
|
+
() => customElement(tag, defaults, renderFacade),
|
|
542
|
+
wantsDiagnostics ? (proto) => installElementDiagnostics(tag, proto) : undefined,
|
|
393
543
|
);
|
|
394
544
|
|
|
395
545
|
// Belt and braces for the one path the wrap cannot see: if `customElements.define`
|
|
@@ -400,4 +550,14 @@ export function defineWebComponent<P extends Record<string, unknown>, E = Record
|
|
|
400
550
|
// in the normal case, where the accessors are already on this same prototype.
|
|
401
551
|
const proto = (Ctor as unknown as { prototype?: object } | undefined)?.prototype;
|
|
402
552
|
if (!handled && proto) installNonReflectingProps(proto, shadowedProps);
|
|
553
|
+
|
|
554
|
+
// NOTE the diagnostics have no equivalent late fallback, and must not get one.
|
|
555
|
+
// The non-reflecting props still work when installed late (they only have to
|
|
556
|
+
// beat the NEXT constructor); the diagnostics do not, because the registry has
|
|
557
|
+
// already snapshotted the lifecycle callbacks it will invoke. Installing them
|
|
558
|
+
// here would produce a wrapper that is visibly present and never called —
|
|
559
|
+
// which is worse than not installing it, since it would read as coverage. So
|
|
560
|
+
// in the one environment the pre-define seam cannot reach (a frozen registry:
|
|
561
|
+
// SES lockdown, a sealed polyfill), element diagnostics are simply absent, and
|
|
562
|
+
// every other element behaviour is unaffected.
|
|
403
563
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// The ELEMENT-layer diagnostic events: what the `kai-*` custom elements SAW a
|
|
2
|
+
// consumer do to them.
|
|
3
|
+
//
|
|
4
|
+
// The wire events answer "did the data arrive". These answer the other half —
|
|
5
|
+
// "the data is right and the UI still is not updating" — which is a different
|
|
6
|
+
// failure with a different cause and, until now, no evidence at all. All three
|
|
7
|
+
// causes are documented in root CLAUDE.md's `kai-` contract section, and every
|
|
8
|
+
// one of them is silent by construction:
|
|
9
|
+
//
|
|
10
|
+
// 1. An array or object prop set as an HTML ATTRIBUTE. `component-register`
|
|
11
|
+
// parses attributes with a `JSON.parse` whose `catch` returns the raw
|
|
12
|
+
// string, so `el.setAttribute('messages', arr)` assigns the literal text
|
|
13
|
+
// `"[object Object],[object Object]"` to the property and pushes THAT into
|
|
14
|
+
// the Solid signal. Nothing throws, nothing logs, nothing renders.
|
|
15
|
+
// 2. The SAME array reference handed back. solid-element creates one signal
|
|
16
|
+
// per prop and sets it with `set(() => v)`; Solid's default equality is
|
|
17
|
+
// `===`, so re-assigning the identical reference notifies nothing.
|
|
18
|
+
// 3. A NEW array whose items are all the previous item objects. The array
|
|
19
|
+
// notifies, but the lists render through reference-keyed `<For>`s, so no
|
|
20
|
+
// row is re-invoked and nothing on screen changes.
|
|
21
|
+
//
|
|
22
|
+
// ZERO IMPORTS, DELIBERATELY. These are type declarations only, so this file is
|
|
23
|
+
// erased at build. That is what lets `wire/diagnostics.ts` name them in its
|
|
24
|
+
// union without `wire/` acquiring a runtime dependency on `elements/` — which
|
|
25
|
+
// would be a real layering inversion, and would drag the element bundle into
|
|
26
|
+
// every consumer that only parses streams.
|
|
27
|
+
//
|
|
28
|
+
// METADATA ONLY, by the same rule the wire events follow: if a value comes from
|
|
29
|
+
// the model, the end user, or the app's data it is PAYLOAD; if it describes the
|
|
30
|
+
// shape, size, timing or identity of that value it is METADATA. A tag name, a
|
|
31
|
+
// prop name, a kind, a count and a length are all identity or shape. A prop's
|
|
32
|
+
// VALUE never appears in any field below — `valuePreview` is a shape
|
|
33
|
+
// description built from a closed vocabulary, never the text itself. Pinned by
|
|
34
|
+
// `element-diagnostics-payload.test.ts`, which plants a sentinel inside every
|
|
35
|
+
// prop value it sets and asserts it never reaches a serialized event.
|
|
36
|
+
|
|
37
|
+
/** The envelope, structurally identical to `WireDiagnosticBase`. Restated here
|
|
38
|
+
* rather than imported so this file keeps its zero-import property; the two are
|
|
39
|
+
* kept in step by `element-diagnostics.test.ts`, which asserts every emitted
|
|
40
|
+
* element event carries a `type` and a numeric `t`. */
|
|
41
|
+
export interface ElementDiagnosticBase {
|
|
42
|
+
type: string;
|
|
43
|
+
/** `Date.now()` at emission. */
|
|
44
|
+
t: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Which contract was broken. A closed vocabulary, so a panel keys its own
|
|
48
|
+
* explanation off the kind and never needs prose from the kit. */
|
|
49
|
+
export type ElementViolationKind =
|
|
50
|
+
/** A non-scalar prop received a value through the ATTRIBUTE channel. The
|
|
51
|
+
* attribute cannot carry an array or an object, so the property now holds a
|
|
52
|
+
* string and the element renders nothing. */
|
|
53
|
+
| 'array-prop-as-attribute'
|
|
54
|
+
/** A list prop was set to the array reference it already held. Solid compares
|
|
55
|
+
* with `===`, so this notified nothing — a no-op write. */
|
|
56
|
+
| 'same-array-reference'
|
|
57
|
+
/** A list prop received a NEW array of the SAME length whose every item is
|
|
58
|
+
* reference-identical to the previous array's. The array notified, but every
|
|
59
|
+
* row's identity is unchanged, so a reference-keyed `<For>` re-invokes none
|
|
60
|
+
* of them. The classic "I mutated the item and copied the array" case.
|
|
61
|
+
*
|
|
62
|
+
* NOTE what this deliberately does NOT claim: the kit cannot know an item
|
|
63
|
+
* "changed" without comparing its CONTENT, and content is payload. So this
|
|
64
|
+
* reports the structurally knowable fact — an update that cannot render —
|
|
65
|
+
* and leaves the question of whether the consumer meant anything by it to
|
|
66
|
+
* the consumer, who is the only one who can answer it. */
|
|
67
|
+
| 'mutated-in-place';
|
|
68
|
+
|
|
69
|
+
/** A consumer broke one of the three `kai-` contract rules on a live element. */
|
|
70
|
+
export interface ElementViolationEvent extends ElementDiagnosticBase {
|
|
71
|
+
type: 'element.violation';
|
|
72
|
+
kind: ElementViolationKind;
|
|
73
|
+
/** The custom-element tag, e.g. `kai-chat`. */
|
|
74
|
+
tag: string;
|
|
75
|
+
/** The camelCase prop name, e.g. `messages`. */
|
|
76
|
+
prop: string;
|
|
77
|
+
/**
|
|
78
|
+
* SHAPE of the offending value, never its content. `array-prop-as-attribute`
|
|
79
|
+
* only. A closed vocabulary:
|
|
80
|
+
*
|
|
81
|
+
* `[object Object]` one object stringified into the attribute
|
|
82
|
+
* `[object Object] x N` an array of N objects stringified — the common case
|
|
83
|
+
* `json:number` the attribute held valid JSON, but a scalar
|
|
84
|
+
* `json:boolean` "
|
|
85
|
+
* `json:null` "
|
|
86
|
+
* `string(len=N)` anything else: the LENGTH of the text, never the text
|
|
87
|
+
* `empty-attribute` a bare attribute on a prop that needs a value
|
|
88
|
+
*/
|
|
89
|
+
valuePreview?: string;
|
|
90
|
+
/** Element count of the array involved. `same-array-reference` and
|
|
91
|
+
* `mutated-in-place` only. A length is shape, not content. */
|
|
92
|
+
length?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Which `kai-*` elements are DEFINED in this realm right now.
|
|
96
|
+
*
|
|
97
|
+
* The question behind it is hydration: an SSR page whose markup contains
|
|
98
|
+
* `<kai-chat>` but whose element bundle never loaded shows empty boxes and no
|
|
99
|
+
* error, because an undefined custom element is a valid, inert HTMLElement.
|
|
100
|
+
* The SSR starters already answer this by hand for a hard-coded handful
|
|
101
|
+
* (`HydrationBadge.tsx`); this is the same answer over the whole manifest. */
|
|
102
|
+
export interface ElementRegistryEvent extends ElementDiagnosticBase {
|
|
103
|
+
type: 'element.registry';
|
|
104
|
+
/** Tags `customElements.get()` resolved. */
|
|
105
|
+
defined: string[];
|
|
106
|
+
/** Tags in the manifest that are NOT defined in this realm. Named for what it
|
|
107
|
+
* is rather than `undefined`, which is a miserable key to read in a panel. */
|
|
108
|
+
notDefined: string[];
|
|
109
|
+
/** `defined.length + notDefined.length`. Present so a panel can show "62 of
|
|
110
|
+
* 79" without trusting itself to add up two arrays it may have truncated. */
|
|
111
|
+
total: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type ElementDiagnosticEvent = ElementViolationEvent | ElementRegistryEvent;
|