@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
package/src/elements/menu.tsx
CHANGED
|
@@ -51,6 +51,10 @@ interface Props extends Record<string, unknown> {
|
|
|
51
51
|
* and an accessible name has to contain the visible text. Then either drop
|
|
52
52
|
* `label` or make it contain the word you slotted. */
|
|
53
53
|
label?: string;
|
|
54
|
+
/** Stretch the trigger to the full width of the menu's container (a block
|
|
55
|
+
* row), e.g. a sidebar-footer account row. Same affordance as
|
|
56
|
+
* `<kai-button full>`. Attribute: `full`. */
|
|
57
|
+
full?: boolean;
|
|
54
58
|
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open`
|
|
55
59
|
* attribute, the menu still self-manages on click/keyboard). Set `el.open = true`,
|
|
56
60
|
* or `<kai-menu open>`; listen for `kai-open-change`. */
|
|
@@ -105,6 +109,7 @@ defineWebComponent<Props, Events>('kai-menu', {
|
|
|
105
109
|
triggerLabel: undefined,
|
|
106
110
|
triggerIconTrailing: undefined,
|
|
107
111
|
label: undefined,
|
|
112
|
+
full: false,
|
|
108
113
|
open: undefined,
|
|
109
114
|
defaultOpen: undefined,
|
|
110
115
|
disabled: undefined,
|
|
@@ -187,6 +192,12 @@ defineWebComponent<Props, Events>('kai-menu', {
|
|
|
187
192
|
}
|
|
188
193
|
|
|
189
194
|
return (
|
|
195
|
+
<>
|
|
196
|
+
{/* The host shrinks to the trigger by default (UA inline); `full` makes it
|
|
197
|
+
a block whose trigger button fills the width — the kai-button pattern
|
|
198
|
+
(`:host([full])`), for sidebar-footer account rows and other stretched
|
|
199
|
+
placements. */}
|
|
200
|
+
<style>{':host([full]){display:block}'}</style>
|
|
190
201
|
<Dropdown
|
|
191
202
|
defaultOpen={flag('defaultOpen')}
|
|
192
203
|
disabled={flag('disabled')}
|
|
@@ -198,6 +209,7 @@ defineWebComponent<Props, Events>('kai-menu', {
|
|
|
198
209
|
props.triggerLabel
|
|
199
210
|
? 'gap-1.5 px-2 py-1.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground'
|
|
200
211
|
: 'justify-center p-1.5 text-foreground hover:bg-muted',
|
|
212
|
+
flag('full') && 'w-full',
|
|
201
213
|
)}
|
|
202
214
|
// A visible `triggerLabel` IS the name (name-from-contents reaches the
|
|
203
215
|
// slotted/shadow text), so `label` must not be layered over it: aria-label
|
|
@@ -224,5 +236,6 @@ defineWebComponent<Props, Events>('kai-menu', {
|
|
|
224
236
|
{renderItems((props.items as KaiMenuItem[] | undefined) ?? [])}
|
|
225
237
|
</DropdownContent>
|
|
226
238
|
</Dropdown>
|
|
239
|
+
</>
|
|
227
240
|
);
|
|
228
241
|
});
|
|
@@ -23,7 +23,8 @@ type SlotName = (typeof SLOT_NAMES)[number];
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* `<kai-prompt-dock>` — a recessed tray that frames a prompt input and can extend
|
|
26
|
-
* with optional "lip" regions above and/or below it.
|
|
26
|
+
* with optional "lip" regions above and/or below it. It sits in the page flow and
|
|
27
|
+
* launches nothing; the floating corner launcher is `<kai-dock>`. The input is the raised card
|
|
27
28
|
* that floats on the tray; the lips sit in a slightly darker recessed band sharing
|
|
28
29
|
* the tray's rounding, so the whole thing reads as one cohesive control.
|
|
29
30
|
*
|
|
@@ -27,9 +27,9 @@ interface Props extends Record<string, unknown> {
|
|
|
27
27
|
/** What clicking a suggestion does: `'submit'` (default) sends it immediately
|
|
28
28
|
* as if typed and submitted; `'fill'` just places it in the input. */
|
|
29
29
|
suggestionMode?: 'submit' | 'fill';
|
|
30
|
-
/** Show a
|
|
31
|
-
* `search` event. */
|
|
32
|
-
|
|
30
|
+
/** Show a web-search (Globe) button in the left toolbar; clicking it fires a
|
|
31
|
+
* `kai-web-search` event. Attribute: `web-search`. */
|
|
32
|
+
webSearch?: boolean;
|
|
33
33
|
/** Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice`
|
|
34
34
|
* event. */
|
|
35
35
|
voice?: boolean;
|
|
@@ -75,8 +75,8 @@ interface Events {
|
|
|
75
75
|
'kai-attachments-change': { attachments: AttachmentData[] };
|
|
76
76
|
/** A suggestion was clicked while `suggestion-mode="fill"`. */
|
|
77
77
|
'kai-suggestion-click': { value: string };
|
|
78
|
-
/** The
|
|
79
|
-
'kai-search': Record<string, never>;
|
|
78
|
+
/** The web-search (Globe) toolbar button was clicked. */
|
|
79
|
+
'kai-web-search': Record<string, never>;
|
|
80
80
|
/** The Voice (Mic) toolbar button was clicked. */
|
|
81
81
|
'kai-voice': Record<string, never>;
|
|
82
82
|
/** The Stop button was clicked while `stoppable` and `loading` are both true. */
|
|
@@ -93,7 +93,7 @@ defineWebComponent<Props, Events>('kai-prompt-input', {
|
|
|
93
93
|
loading: false,
|
|
94
94
|
suggestions: undefined,
|
|
95
95
|
suggestionMode: 'submit',
|
|
96
|
-
|
|
96
|
+
webSearch: false,
|
|
97
97
|
voice: false,
|
|
98
98
|
stoppable: false,
|
|
99
99
|
submit: 'always',
|
|
@@ -215,7 +215,7 @@ defineWebComponent<Props, Events>('kai-prompt-input', {
|
|
|
215
215
|
attach={flag('attach')}
|
|
216
216
|
suggestions={props.suggestions}
|
|
217
217
|
attachments={attachments()}
|
|
218
|
-
|
|
218
|
+
webSearch={flag('webSearch')}
|
|
219
219
|
voice={flag('voice')}
|
|
220
220
|
toolbarActions={toolbarActions()}
|
|
221
221
|
triggers={props.triggers}
|
|
@@ -225,7 +225,7 @@ defineWebComponent<Props, Events>('kai-prompt-input', {
|
|
|
225
225
|
onSubmit={handleSubmit}
|
|
226
226
|
onSuggestionClick={handleSuggestionClick}
|
|
227
227
|
onAttachmentsChange={(a) => { setAttachments(a); dispatch('kai-attachments-change', { attachments: a }); }}
|
|
228
|
-
|
|
228
|
+
onWebSearch={() => dispatch('kai-web-search')}
|
|
229
229
|
onVoice={() => dispatch('kai-voice')}
|
|
230
230
|
onStop={() => dispatch('kai-stop')}
|
|
231
231
|
onAction={(id) => dispatch('kai-toolbar-action', { action: id })}
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
// from ./register.ts so the elements entry is SSR-import-safe — see the comment
|
|
4
4
|
// there. This file is hand-maintained (the generator gen-element-api.mjs SKIPs
|
|
5
5
|
// register.ts AND this file); keep the component import list here in sync.
|
|
6
|
+
import { installKaiDevtoolsHook } from '../diagnostics/hook';
|
|
7
|
+
import { emitElementRegistry } from './element-diagnostics';
|
|
6
8
|
import './conversation-list';
|
|
9
|
+
import './conversation-item';
|
|
7
10
|
import './prompt-input';
|
|
8
11
|
import './chat';
|
|
9
12
|
import './chat-workspace';
|
|
@@ -77,6 +80,10 @@ import './compare';
|
|
|
77
80
|
import './composer';
|
|
78
81
|
// W3 phase 2: cascading action menu from a JSON items-tree
|
|
79
82
|
import './menu';
|
|
83
|
+
// The same Dropdown surface with a consumer-owned body: you slot the rows
|
|
84
|
+
// instead of handing over an items tree (the shape a framework consumer needs
|
|
85
|
+
// when the rows are their own components).
|
|
86
|
+
import './dropdown';
|
|
80
87
|
// W4 phase 1: grouped filterable command/mention palette
|
|
81
88
|
import './command';
|
|
82
89
|
// Prompt dock + settings building blocks (graduated from SolidJS prototypes)
|
|
@@ -89,8 +96,47 @@ import './pane';
|
|
|
89
96
|
import './pane-group';
|
|
90
97
|
import './agent-card';
|
|
91
98
|
import './dialog';
|
|
99
|
+
import './dock';
|
|
92
100
|
// Input & search field family
|
|
93
101
|
import './input';
|
|
94
102
|
import './search';
|
|
95
103
|
import './kbd';
|
|
96
104
|
import './editable-label';
|
|
105
|
+
|
|
106
|
+
// The devtools recorder hook, installed HERE because this file is already the
|
|
107
|
+
// browser-only half of the elements entry (register.ts gates it behind a window
|
|
108
|
+
// check and a dynamic import). So an app that registers the kai-* elements gets
|
|
109
|
+
// the hook with no work, while an SSR import of the entry still touches no
|
|
110
|
+
// global.
|
|
111
|
+
//
|
|
112
|
+
// At the BOTTOM, and it makes no difference: `import` declarations are hoisted,
|
|
113
|
+
// so every element module above has already evaluated by the time any statement
|
|
114
|
+
// in this file runs. Written here anyway so the order you read is the order that
|
|
115
|
+
// happens. Nothing above emits a diagnostic event, so there is nothing to miss.
|
|
116
|
+
//
|
|
117
|
+
// Idempotent and near-free: with no activation signal it allocates no buffer,
|
|
118
|
+
// makes no subscription, and leaves emission a guarded no-op. An app importing
|
|
119
|
+
// the SolidJS components directly never runs this file and must call
|
|
120
|
+
// `installKaiDevtoolsHook()` itself -- see the docblock in ../diagnostics/index.
|
|
121
|
+
installKaiDevtoolsHook();
|
|
122
|
+
|
|
123
|
+
// One registry snapshot, right after the hook installs and therefore after every
|
|
124
|
+
// element above has registered (import declarations are hoisted, so they all ran
|
|
125
|
+
// before this line).
|
|
126
|
+
//
|
|
127
|
+
// WHY HERE AND NOT INSIDE THE HOOK. The hook lives in ../diagnostics, which is
|
|
128
|
+
// its own rollup bundle and must stay free of any element import -- pulling the
|
|
129
|
+
// manifest in there would drag element bytes into a consumer who only parses
|
|
130
|
+
// streams. The dependency goes elements -> diagnostics, never back.
|
|
131
|
+
//
|
|
132
|
+
// WHY EMIT AT ALL WHEN A PANEL IS USUALLY NOT THERE YET. On the recording branch
|
|
133
|
+
// the hook has already subscribed by this point, so this snapshot lands in the
|
|
134
|
+
// history buffer and a panel attaching seconds later still sees the state at
|
|
135
|
+
// load -- which is the state that answers the hydration question. On the dormant
|
|
136
|
+
// branch `emitElementRegistry()` short-circuits on the active check and costs a
|
|
137
|
+
// symbol read.
|
|
138
|
+
//
|
|
139
|
+
// A panel attaching mid-session with no history gets no snapshot from here and
|
|
140
|
+
// should ask for a fresh one: `emitElementRegistry()` is exported from
|
|
141
|
+
// @kitn.ai/ui/elements for exactly that.
|
|
142
|
+
emitElementRegistry();
|
package/src/elements/register.ts
CHANGED
|
@@ -38,6 +38,34 @@ export type { CodeHighlightingOptions } from '../primitives/highlighter';
|
|
|
38
38
|
export { classifyTool } from '../components/tool-classify';
|
|
39
39
|
export type { ToolKind } from '../components/tool-classify';
|
|
40
40
|
|
|
41
|
+
// Element-layer diagnostics. `emitElementRegistry()` re-emits the which-elements-
|
|
42
|
+
// are-defined snapshot on demand, for a panel that attached mid-session and so
|
|
43
|
+
// missed the one register-impl emits at load. SSR-safe and a no-op with no
|
|
44
|
+
// subscriber, so calling it unconditionally is fine.
|
|
45
|
+
//
|
|
46
|
+
// WHY THIS ONE PRODUCER IS PUBLIC while `@kitn.ai/ui/diagnostics` deliberately
|
|
47
|
+
// keeps `emitWireDiagnostic`, `nextStreamId` and `setWirePayloadCapture`
|
|
48
|
+
// internal. That rule exists because a consumer able to forge events or reset
|
|
49
|
+
// the stream counter can make a panel lie about a stream that never happened,
|
|
50
|
+
// and one able to flip payload capture can start recording an app's users'
|
|
51
|
+
// conversations without the app deciding to. This function takes no arguments
|
|
52
|
+
// and reports nothing but `customElements.get()` over the manifest: there is no
|
|
53
|
+
// value to forge, no counter to move and no capture to arm. It can only tell the
|
|
54
|
+
// truth about the registry, which is the one thing a late-attaching panel cannot
|
|
55
|
+
// find out for itself.
|
|
56
|
+
//
|
|
57
|
+
// The event TYPES are re-exported here as well as from @kitn.ai/ui/diagnostics:
|
|
58
|
+
// a consumer already importing the elements entry should not have to add a
|
|
59
|
+
// second specifier to name the events it is handed.
|
|
60
|
+
export { emitElementRegistry } from './element-diagnostics';
|
|
61
|
+
export type {
|
|
62
|
+
ElementDiagnosticBase,
|
|
63
|
+
ElementDiagnosticEvent,
|
|
64
|
+
ElementRegistryEvent,
|
|
65
|
+
ElementViolationEvent,
|
|
66
|
+
ElementViolationKind,
|
|
67
|
+
} from './diagnostic-events';
|
|
68
|
+
|
|
41
69
|
// Imperative toast API — usable directly from the elements entry so consumers
|
|
42
70
|
// who only import the web-components bundle still get `toast()`. The store is
|
|
43
71
|
// SSR-safe (no DOM touched until the first toast is raised on the client).
|
|
@@ -129,9 +129,16 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
|
|
|
129
129
|
min: el.getAttribute('min') ?? undefined,
|
|
130
130
|
max: el.getAttribute('max') ?? undefined,
|
|
131
131
|
locked: el.hasAttribute('locked') && el.getAttribute('locked') !== 'false',
|
|
132
|
-
// Honour both the `hidden`
|
|
133
|
-
//
|
|
134
|
-
//
|
|
132
|
+
// Honour both the `hidden` attribute and the property, because on THIS element
|
|
133
|
+
// they are two independent halves of the state and either can be the only one
|
|
134
|
+
// set. Not a fact about custom elements in general — a custom element that does
|
|
135
|
+
// not declare a `hidden` prop keeps the native reflecting accessor. It is a fact
|
|
136
|
+
// about this one: `<kai-resizable-item>` declares `hidden: false`, so
|
|
137
|
+
// component-register owns the property and nothing reflects. Measured:
|
|
138
|
+
// `el.hidden = true` writes no attribute, and after maximize writes the
|
|
139
|
+
// attribute `el.hidden` reads `undefined`. This union is what absorbs that, and
|
|
140
|
+
// it is why `hidden` is exempt from the reflectFlag migration rather than broken
|
|
141
|
+
// by it — see reflectItemConfig's doc below.
|
|
135
142
|
hidden: el.hidden || (el.hasAttribute('hidden') && el.getAttribute('hidden') !== 'false'),
|
|
136
143
|
collapsed,
|
|
137
144
|
};
|
|
@@ -543,10 +550,22 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
|
|
|
543
550
|
* *properties* — `el.size = "280px"` — but component-register only mirrors
|
|
544
551
|
* attribute→property, never the reverse, so a property-set size never becomes an
|
|
545
552
|
* attribute and the parent silently falls back to a flexible split. This is the
|
|
546
|
-
* exact class of bug the `collapsed` reflection already fixed;
|
|
547
|
-
* `size`/`min`/`max` (
|
|
548
|
-
*
|
|
549
|
-
*
|
|
553
|
+
* exact class of bug the `collapsed` reflection already fixed; handled here for
|
|
554
|
+
* `size`/`min`/`max` (the STRINGS). The booleans — `collapsed` and `locked` — go
|
|
555
|
+
* through `reflectFlag` in the facade body instead, which reflects AND keeps the
|
|
556
|
+
* property readable; see WebComponentContext.reflectFlag.
|
|
557
|
+
*
|
|
558
|
+
* `hidden` is deliberately NOT reflected, and NOT because it is already fine — it is
|
|
559
|
+
* split exactly like the others, and measurably so: `el.hidden = true` leaves no
|
|
560
|
+
* attribute (a plain `<div>` gets one), and after maximize writes the attribute
|
|
561
|
+
* `el.hidden` reads `undefined`. Declaring `hidden: false` is what does it — it makes
|
|
562
|
+
* `hidden` a component-register prop, whose non-reflecting accessor shadows the native
|
|
563
|
+
* reflecting one. What makes the split HARMLESS here is that both readers take the
|
|
564
|
+
* union, `el.hidden || el.hasAttribute('hidden')`, so neither half can be missed.
|
|
565
|
+
* Routing it through `reflectFlag` would put a second writer on an attribute the
|
|
566
|
+
* maximize/restore path already owns, so it is a behaviour decision of its own rather
|
|
567
|
+
* than part of the read-back fix. See the exemption in
|
|
568
|
+
* tests/elements/reflected-boolean-coverage.test.ts.
|
|
550
569
|
*
|
|
551
570
|
* Durability (don't fight drags): the parent WRITES `size` back to the live percent
|
|
552
571
|
* during a drag (`persistSizes` → `setAttribute('size', …)`), and that attribute
|
|
@@ -568,7 +587,6 @@ defineWebComponent<GroupProps, GroupEvents>('kai-resizable', {
|
|
|
568
587
|
export function reflectItemConfig(
|
|
569
588
|
element: HTMLElement,
|
|
570
589
|
props: { readonly size?: string; readonly min?: string; readonly max?: string },
|
|
571
|
-
flag: (name: string) => boolean,
|
|
572
590
|
) {
|
|
573
591
|
const norm = (v: unknown): string | null => (v == null || v === '' ? null : String(v));
|
|
574
592
|
const reflectString = (name: 'size' | 'min' | 'max') => {
|
|
@@ -591,11 +609,11 @@ export function reflectItemConfig(
|
|
|
591
609
|
reflectString('size');
|
|
592
610
|
reflectString('min');
|
|
593
611
|
reflectString('max');
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
// `
|
|
597
|
-
//
|
|
598
|
-
|
|
612
|
+
// `locked` used to be reflected here too. It is a BOOLEAN, so it now goes through
|
|
613
|
+
// `reflectFlag` in the facade body next to `collapsed` — a boolean reflected with a
|
|
614
|
+
// bare `toggleAttribute` reads back `undefined` (see WebComponentContext.reflectFlag),
|
|
615
|
+
// and `reflectFlag` is only reachable from the facade's context. What is left here is
|
|
616
|
+
// the string props, which is this helper's real subject: the drag-durability guard.
|
|
599
617
|
}
|
|
600
618
|
|
|
601
619
|
interface ItemProps extends Record<string, unknown> {
|
|
@@ -633,23 +651,32 @@ defineWebComponent<ItemProps>('kai-resizable-item', {
|
|
|
633
651
|
locked: false,
|
|
634
652
|
hidden: false,
|
|
635
653
|
collapsed: false,
|
|
636
|
-
}, (props, { element,
|
|
654
|
+
}, (props, { element, reflectFlag }) => {
|
|
637
655
|
// Reflect the reactive `collapsed` prop to the `collapsed` ATTRIBUTE the parent
|
|
638
656
|
// <kai-resizable> reads (and observes). This is what makes a bare JSX boolean
|
|
639
657
|
// collapse the panel at INITIAL render from any framework: component-register
|
|
640
658
|
// parses `<kai-resizable-item collapsed>` to `undefined` (not `true`), and a JSX
|
|
641
|
-
// boolean sets neither `hidden` nor the IDL property — so
|
|
642
|
-
// (which also honours the attribute) is the reliable source, and
|
|
643
|
-
//
|
|
644
|
-
// pattern as <kai-chat>'s `loading` reflection
|
|
645
|
-
// `
|
|
646
|
-
//
|
|
647
|
-
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
//
|
|
659
|
+
// boolean sets neither `hidden` nor the IDL property — so the flag resolution
|
|
660
|
+
// (which also honours the attribute) is the reliable source, and the attribute is
|
|
661
|
+
// mirrored back so the parent's readItems()/MutationObserver lay it out. Same
|
|
662
|
+
// pattern as <kai-chat>'s `loading` reflection, and now literally the same code:
|
|
663
|
+
// `reflectFlag` also makes `el.collapsed` READ BACK, which a hand-rolled
|
|
664
|
+
// toggleAttribute effect does not (WebComponentContext.reflectFlag).
|
|
665
|
+
//
|
|
666
|
+
// We touch only `collapsed` and `locked`, never `hidden`, so the back-compat hidden
|
|
667
|
+
// path and maximize (which drive `hidden`) are untouched — see the named exemption
|
|
668
|
+
// in tests/elements/reflected-boolean-coverage.test.ts for why `hidden` is a
|
|
669
|
+
// separate decision rather than an oversight.
|
|
670
|
+
reflectFlag('collapsed');
|
|
671
|
+
// `locked` is the parent's other boolean input; the parent also writes it during
|
|
672
|
+
// maximize/restore, and because the flag resolution re-derives from that same
|
|
673
|
+
// attribute state the reflection stays self-consistent (no fight).
|
|
674
|
+
reflectFlag('locked');
|
|
675
|
+
// Reflect the STRING config props (`size`/`min`/`max`) property→attribute for the
|
|
676
|
+
// SAME reason — a framework that sets them as DOM properties (React et al.) would
|
|
677
|
+
// otherwise be invisible to the parent. See reflectItemConfig for the
|
|
651
678
|
// drag-durability guard on `size`.
|
|
652
|
-
reflectItemConfig(element, props
|
|
679
|
+
reflectItemConfig(element, props);
|
|
653
680
|
return (
|
|
654
681
|
<>
|
|
655
682
|
{/* The item host fills the panel's single grid cell (the panel stretches it on
|
|
@@ -109,6 +109,11 @@ defineWebComponent<Props, Events>('kai-scroll-button', {
|
|
|
109
109
|
variant={props.variant ?? 'outline'}
|
|
110
110
|
size={props.size ?? 'icon'}
|
|
111
111
|
aria-label="Scroll to bottom"
|
|
112
|
+
// While at the bottom this button is fully transparent and pointer-inert,
|
|
113
|
+
// but it stayed in the tab order — a keyboard user landed on a control
|
|
114
|
+
// they could not see. Mirrors ScrollButton in components/scroll-button.tsx.
|
|
115
|
+
tabindex={isAtBottom() ? -1 : 0}
|
|
116
|
+
aria-hidden={isAtBottom() ? 'true' : undefined}
|
|
112
117
|
class={cn(
|
|
113
118
|
'rounded-full transition-all duration-150 ease-out',
|
|
114
119
|
!isAtBottom()
|
package/src/elements/slots.ts
CHANGED
|
@@ -62,6 +62,39 @@ export const CONVERSATIONS_PARTS: PartDef[] = [
|
|
|
62
62
|
doc: 'The right-aligned trailing text on each conversation row (a count, status, or relative time). Set it per item via the `trailing` field; otherwise a short auto relative time is derived from `updatedAt`. Recolor or resize it from outside.',
|
|
63
63
|
recipe: 'kai-conversations::part(trailing) { color: var(--color-primary); font-variant-numeric: tabular-nums }',
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
name: 'items',
|
|
67
|
+
doc: 'The item-mode listbox region wrapping your slotted `<kai-conversation-item>` children.',
|
|
68
|
+
recipe: 'kai-conversations::part(items) { gap: 2px }',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/** Slots of `<kai-conversation-item>` — one composed row of a consumer-owned
|
|
73
|
+
* conversation loop (the default slot is the title). */
|
|
74
|
+
export const CONVERSATION_ITEM_SLOTS: SlotDef[] = [
|
|
75
|
+
{ name: 'leading', mode: 'inject', part: true, doc: 'Leading region before the title (an icon or avatar).' },
|
|
76
|
+
{ name: 'meta', mode: 'inject', part: true, doc: 'Meta region under the title (a timestamp or status line).' },
|
|
77
|
+
{ name: 'menu', mode: 'inject', part: true, doc: 'Your own row menu (a popover trigger). Never selects the row.' },
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/** Styleable `::part`s of `<kai-conversation-item>`. (`leading`/`meta`/`menu`
|
|
81
|
+
* are covered by their slot defs' `part: true` flags.) */
|
|
82
|
+
export const CONVERSATION_ITEM_PARTS: PartDef[] = [
|
|
83
|
+
{
|
|
84
|
+
name: 'body',
|
|
85
|
+
doc: 'The activation surface inside the row (button role; carries aria-current and the roving tabindex). The focus ring paints here.',
|
|
86
|
+
recipe: 'kai-conversation-item::part(body) { outline-offset: 2px }',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'row',
|
|
90
|
+
doc: 'The whole row surface. Carries `data-active` while selected.',
|
|
91
|
+
recipe: 'kai-conversation-item::part(row) { border-radius: 0.5rem }',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'title',
|
|
95
|
+
doc: 'The title line (the default slot renders inside it).',
|
|
96
|
+
recipe: 'kai-conversation-item::part(title) { font-weight: 600 }',
|
|
97
|
+
},
|
|
65
98
|
];
|
|
66
99
|
|
|
67
100
|
/** Slots of `<kai-message>` — per-message composition seams. `before-body` and
|
|
@@ -183,6 +216,33 @@ export const SKELETON_PARTS: PartDef[] = [
|
|
|
183
216
|
},
|
|
184
217
|
];
|
|
185
218
|
|
|
219
|
+
/**
|
|
220
|
+
* The parts of ONE attachment item, declared by `components/attachments.tsx`
|
|
221
|
+
* and therefore surfaced by every element that renders an attachment — the
|
|
222
|
+
* standalone `<kai-attachments>` AND the thread inside `<kai-message>` /
|
|
223
|
+
* `<kai-chat>`. Shared rather than duplicated so the two can never document
|
|
224
|
+
* different names for the same node.
|
|
225
|
+
*
|
|
226
|
+
* These are a styling seam and a TESTING seam, and the second is why they exist
|
|
227
|
+
* at all. `examples/internal/openrouter-spike` proves "a user can see which file
|
|
228
|
+
* was attached" by locating these; before they existed it pinned the chip by
|
|
229
|
+
* `span.truncate` and broke the day the thread moved to the grid tile, while the
|
|
230
|
+
* filename was still plainly on screen. A published part is what lets an
|
|
231
|
+
* assertion name the thing rather than its current markup.
|
|
232
|
+
*/
|
|
233
|
+
export const ATTACHMENT_ITEM_PARTS: PartDef[] = [
|
|
234
|
+
{
|
|
235
|
+
name: 'attachment',
|
|
236
|
+
doc: 'One attachment item: the chip, row or tile, whichever variant is rendering. Restyle its background, radius or border from outside without caring which layout it is.',
|
|
237
|
+
recipe: 'kai-chat::part(attachment) { border-radius: 0.25rem }',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'attachment-name',
|
|
241
|
+
doc: 'The attachment’s filename label. Present in every variant that shows one (a grid tile omits it for an image, which is its own label). Retune its type or hide it entirely.',
|
|
242
|
+
recipe: 'kai-chat::part(attachment-name) { font-size: 0.75rem }',
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
|
|
186
246
|
/** Styleable `::part`s of `<kai-message>`. (The `avatar` part is contributed by
|
|
187
247
|
* the `avatar` slot's `part: true` flag, so it is not repeated here.) */
|
|
188
248
|
export const MESSAGE_PARTS: PartDef[] = [
|
|
@@ -211,6 +271,10 @@ export const MESSAGE_PARTS: PartDef[] = [
|
|
|
211
271
|
doc: 'The citation row rendered from the message’s `source` parts: a wrapped row of chips below the bubble, never inside it. Restyle its spacing or hide it entirely from outside.',
|
|
212
272
|
recipe: 'kai-message::part(citations) { gap: 0.5rem }',
|
|
213
273
|
},
|
|
274
|
+
// A message renders its `file` parts through the same Attachment chrome
|
|
275
|
+
// `<kai-attachments>` uses, so it exposes the same handles. Shared, not
|
|
276
|
+
// restated — see ATTACHMENT_ITEM_PARTS.
|
|
277
|
+
...ATTACHMENT_ITEM_PARTS,
|
|
214
278
|
];
|
|
215
279
|
|
|
216
280
|
/** Styleable `::part`s of `<kai-attachments>`. */
|
|
@@ -220,6 +284,7 @@ export const ATTACHMENTS_PARTS: PartDef[] = [
|
|
|
220
284
|
doc: 'The image shown in an attachment’s hover-card preview. Bounded by default (max ~320×256, aspect preserved) so a large image never blows up the card. Raise or lower the cap from outside.',
|
|
221
285
|
recipe: 'kai-attachments::part(preview) { max-width: 32rem; max-height: 24rem }',
|
|
222
286
|
},
|
|
287
|
+
...ATTACHMENT_ITEM_PARTS,
|
|
223
288
|
];
|
|
224
289
|
|
|
225
290
|
/** Styleable `::part`s of `<kai-status>`. */
|
|
@@ -323,21 +388,21 @@ export const CARD_PARTS: PartDef[] = [
|
|
|
323
388
|
},
|
|
324
389
|
];
|
|
325
390
|
|
|
326
|
-
/**
|
|
327
|
-
* content into: brand/tabs, the upgrade/Design/user-menu cluster, a top banner). */
|
|
391
|
+
/** Slots of `<kai-workspace>` (the five shell regions; main also takes the default slot). */
|
|
328
392
|
export const WORKSPACE_SLOTS: SlotDef[] = [
|
|
329
|
-
{ name: '
|
|
330
|
-
{ name: '
|
|
331
|
-
{ name: 'main
|
|
332
|
-
{ name: '
|
|
393
|
+
{ name: 'header', mode: 'inject', part: true, doc: 'The top band across the full shell width (app bar, tabs, breadcrumbs).' },
|
|
394
|
+
{ name: 'start', mode: 'inject', part: true, doc: 'The inline-start aside column (a conversation rail, a nav, a file tree). Resizable and collapsible.' },
|
|
395
|
+
{ name: 'main', mode: 'inject', part: true, doc: 'The main region. Unnamed children project here too, via the default slot.' },
|
|
396
|
+
{ name: 'end', mode: 'inject', part: true, doc: 'The inline-end aside column (inspector, notes, preview). Resizable and collapsible.' },
|
|
397
|
+
{ name: 'footer', mode: 'inject', part: true, doc: 'The bottom band across the full shell width (status bar, disclaimers).' },
|
|
333
398
|
];
|
|
334
399
|
|
|
335
|
-
/** Styleable `::part`s of `<kai-workspace
|
|
400
|
+
/** Styleable `::part`s of `<kai-workspace>` (beyond the slot-backed header/start/main/end/footer parts). */
|
|
336
401
|
export const WORKSPACE_PARTS: PartDef[] = [
|
|
337
402
|
{
|
|
338
|
-
name: '
|
|
339
|
-
doc: '
|
|
340
|
-
recipe: 'kai-workspace::part(
|
|
403
|
+
name: 'aside',
|
|
404
|
+
doc: 'Both aside columns match this part (each also matches its own start/end part). Restyle the shared aside surface or border from outside; the --kai-workspace-start-* and --kai-workspace-end-* custom properties set the widths.',
|
|
405
|
+
recipe: 'kai-workspace::part(aside) { background: var(--color-card) }',
|
|
341
406
|
},
|
|
342
407
|
];
|
|
343
408
|
|
|
@@ -474,6 +539,18 @@ export const PROMPT_DOCK_PARTS: PartDef[] = [
|
|
|
474
539
|
},
|
|
475
540
|
];
|
|
476
541
|
|
|
542
|
+
/** Slots + styleable `::part`s of `<kai-dock>` (the floating corner launcher — not
|
|
543
|
+
* `<kai-prompt-dock>`, which is an in-flow tray around a prompt input). */
|
|
544
|
+
export const DOCK_SLOTS: SlotDef[] = [
|
|
545
|
+
{ name: 'panel', mode: 'replace', part: true, doc: 'The panel body. ANY element: a `<kai-chat>`, a form, your own component. The dock never reads or types it, and the default slot is the same region.' },
|
|
546
|
+
{ name: 'launcher', mode: 'inject', doc: 'Content inside the built-in button while CLOSED; defaults to a chat glyph. Text works as well as an icon: the button keeps its height and grows sideways into a pill, so a label like "Support" is not clipped. The BUTTON is never slotted away, because it owns aria-expanded, aria-controls, the toggle wiring and the focus return.' },
|
|
547
|
+
{ name: 'launcher-open', mode: 'inject', doc: 'Content inside the button while OPEN; defaults to a ✕. Fill only `launcher` and that glyph stays while open rather than morphing into a built-in that clashes with it.' },
|
|
548
|
+
];
|
|
549
|
+
export const DOCK_PARTS: PartDef[] = [
|
|
550
|
+
{ name: 'launcher', doc: 'The launcher button pinned to the corner: a disc by default, a pill once you slot a text label. Restyle its surface or shadow; --kai-dock-launcher-size sets its height and its minimum width.', recipe: 'kai-dock::part(launcher) { background: var(--color-info) }' },
|
|
551
|
+
{ name: 'badge', doc: 'The unread dot on the launcher, rendered only while closed and only when `unread` is set. Restyle its color or size.', recipe: 'kai-dock::part(badge) { background: var(--color-success) }' },
|
|
552
|
+
];
|
|
553
|
+
|
|
477
554
|
/** Styleable `::part`s of `<kai-segmented>`. */
|
|
478
555
|
export const SEGMENTED_PARTS: PartDef[] = [
|
|
479
556
|
{ name: 'track', doc: 'The segmented track (the pill container holding the segments). Restyle its background, radius, or padding.', recipe: 'kai-segmented::part(track) { border-radius: 9999px }' },
|
|
@@ -512,6 +589,13 @@ export const AGENT_CARD_PARTS: PartDef[] = [
|
|
|
512
589
|
{ name: 'menu', doc: 'The trailing overflow ("...") menu button.', recipe: 'kai-agent-card::part(menu) { opacity: 1 }' },
|
|
513
590
|
];
|
|
514
591
|
|
|
592
|
+
/** Styleable `::part`s of `<kai-code-block>` (one syntax-highlighted code block).
|
|
593
|
+
* The code region itself is not a part — it is rebuilt when the highlight lands,
|
|
594
|
+
* and its look is owned by the Shiki theme (`code-theme`). */
|
|
595
|
+
export const CODE_BLOCK_PARTS: PartDef[] = [
|
|
596
|
+
{ name: 'copy', doc: 'The copy-to-clipboard button in the header row. Hide it with `copy="false"` rather than CSS.', recipe: 'kai-code-block::part(copy) { color: var(--color-primary) }' },
|
|
597
|
+
];
|
|
598
|
+
|
|
515
599
|
/** Slots + styleable `::part`s of `<kai-dialog>` (the centered modal). */
|
|
516
600
|
export const DIALOG_SLOTS: SlotDef[] = [
|
|
517
601
|
{ name: 'header', mode: 'inject', part: true, doc: 'Optional title region at the top of the panel.' },
|
|
@@ -594,6 +678,13 @@ export const POPOVER_SLOTS: SlotDef[] = [
|
|
|
594
678
|
{ name: 'trigger', mode: 'replace', doc: 'The control that opens the popover (a button, an avatar, …). The panel anchors to it.' },
|
|
595
679
|
];
|
|
596
680
|
|
|
681
|
+
/** Slots of `<kai-dropdown>`. The menu body is the default slot; `trigger` is the
|
|
682
|
+
* VISUAL content of the built-in trigger button (never your own `<button>`/`<a>`,
|
|
683
|
+
* which would nest interactive elements — the `kai-menu` rule). */
|
|
684
|
+
export const DROPDOWN_SLOTS: SlotDef[] = [
|
|
685
|
+
{ name: 'trigger', mode: 'replace', doc: 'Visual content of the trigger button (an icon, text, an `<svg>`). Replaces the built-in trigger* content; name it with `label`.' },
|
|
686
|
+
];
|
|
687
|
+
|
|
597
688
|
/** Styleable `::part`s of `<kai-audio-visualizer>`. Every DOM variant (`bar`,
|
|
598
689
|
* `grid`, `radial`) shares the `bar`/`cell` markup pattern; the shader variants
|
|
599
690
|
* (`wave`, `aurora`) share the `canvas` part. Lit items ALSO carry the
|
|
@@ -631,7 +722,8 @@ export const AUDIO_VISUALIZER_PARTS: PartDef[] = [
|
|
|
631
722
|
export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
|
|
632
723
|
'kai-chat': { slots: CHAT_SLOTS, parts: CHAT_PARTS },
|
|
633
724
|
'kai-command': { parts: COMMAND_PARTS },
|
|
634
|
-
'kai-conversations': { slots: CONVERSATIONS_SLOTS, parts: CONVERSATIONS_PARTS },
|
|
725
|
+
'kai-conversations': { slots: CONVERSATIONS_SLOTS, parts: CONVERSATIONS_PARTS, children: 'Your own `<kai-conversation-item>` rows (item mode: the consumer-owned loop). Data rows do not render while any are present.' },
|
|
726
|
+
'kai-conversation-item': { slots: CONVERSATION_ITEM_SLOTS, parts: CONVERSATION_ITEM_PARTS, children: 'The row title. `leading`, `meta` and `menu` are the named regions around it.' },
|
|
635
727
|
'kai-message': { slots: MESSAGE_SLOTS, parts: MESSAGE_PARTS },
|
|
636
728
|
'kai-thread': { slots: THREAD_SLOTS },
|
|
637
729
|
'kai-prompt-input': { slots: PROMPT_INPUT_SLOTS, parts: PROMPT_INPUT_PARTS },
|
|
@@ -650,7 +742,7 @@ export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
|
|
|
650
742
|
'kai-voice-output': { parts: VOICE_OUTPUT_PARTS },
|
|
651
743
|
'kai-screen': { slots: SCREEN_SLOTS, parts: SCREEN_PARTS, children: 'The screen body, below the title bar.' },
|
|
652
744
|
'kai-card': { slots: CARD_SLOTS, parts: CARD_PARTS, children: 'The card body, below the header/media regions.' },
|
|
653
|
-
'kai-workspace': { slots: WORKSPACE_SLOTS, parts: WORKSPACE_PARTS },
|
|
745
|
+
'kai-workspace': { slots: WORKSPACE_SLOTS, parts: WORKSPACE_PARTS, children: 'The main region content (same region as the `main` slot): your `<kai-chat>`, or any app view.' },
|
|
654
746
|
'kai-nav': { parts: NAV_PARTS },
|
|
655
747
|
'kai-coachmark': { slots: COACHMARK_SLOTS, parts: COACHMARK_PARTS, children: 'The ANCHOR the coachmark points at: the element it attaches to and positions against. The bubble body is the `content` slot.' },
|
|
656
748
|
'kai-progress-bar': { parts: PROGRESS_BAR_PARTS },
|
|
@@ -664,6 +756,7 @@ export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
|
|
|
664
756
|
'kai-pane-group': { parts: PANE_GROUP_PARTS, children: 'Content shown for every tab. Use it INSTEAD of the per-tab `slot="<tab id>"` seams when you swap the content yourself.' },
|
|
665
757
|
'kai-agent-card': { parts: AGENT_CARD_PARTS },
|
|
666
758
|
'kai-dialog': { slots: DIALOG_SLOTS, parts: DIALOG_PARTS, children: 'The dialog body, between the `header` and `footer` slots.' },
|
|
759
|
+
'kai-dock': { slots: DOCK_SLOTS, parts: DOCK_PARTS, children: 'The panel body, the same region as `slot="panel"`.' },
|
|
667
760
|
'kai-input': { slots: INPUT_SLOTS, parts: INPUT_PARTS },
|
|
668
761
|
'kai-search': { parts: SEARCH_PARTS },
|
|
669
762
|
'kai-kbd': { parts: KBD_PARTS, children: 'Literal key text, when you are not using the `keys` prop to render key caps.' },
|
|
@@ -671,9 +764,11 @@ export const ELEMENT_COMPOSITION: Record<string, ElementComposition> = {
|
|
|
671
764
|
'kai-empty': { slots: EMPTY_SLOTS, children: 'The empty-state body below the title/description, usually the call to action.' },
|
|
672
765
|
'kai-file-upload': { children: 'Custom dropzone content, replacing the default label (the `label` prop is the fallback).' },
|
|
673
766
|
'kai-popover': { slots: POPOVER_SLOTS, children: 'The popover panel body. The control that opens it is the `trigger` slot.' },
|
|
767
|
+
'kai-dropdown': { slots: DROPDOWN_SLOTS, children: 'The menu body: your own rows. Give each `role="menuitem"`. The control that opens it is the `trigger` slot.' },
|
|
674
768
|
'kai-resizable': { children: 'The `<kai-resizable-item>` panels, in order. Dividers are inserted between them.' },
|
|
675
769
|
'kai-resizable-item': { children: 'This panel\'s content.' },
|
|
676
770
|
'kai-tooltip': { children: 'The TRIGGER the tooltip describes. The tip text is the `text` prop.' },
|
|
771
|
+
'kai-code-block': { parts: CODE_BLOCK_PARTS },
|
|
677
772
|
'kai-audio-visualizer': { parts: AUDIO_VISUALIZER_PARTS },
|
|
678
773
|
};
|
|
679
774
|
|
package/src/elements/styles.css
CHANGED
|
@@ -40,12 +40,23 @@
|
|
|
40
40
|
/* One token-driven focus ring for EVERY focusable element in the shadow root.
|
|
41
41
|
Blue by default (via --color-ring), consistent in light + dark, and
|
|
42
42
|
overridable through --kai-color-ring. Uses `outline` (not a box-shadow
|
|
43
|
-
ring)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
the
|
|
43
|
+
ring) because it follows the element's radius and — unlike a `ring-*`
|
|
44
|
+
utility — is written in literal values, so it does not depend on any
|
|
45
|
+
`--tw-*` custom property. That matters more than it looks: `@property`
|
|
46
|
+
registration is document-global and is ignored inside a shadow root, so
|
|
47
|
+
every `--tw-*` here is unset and every `ring-*` utility computes to
|
|
48
|
+
nothing. This literal rule is the only reason most of the kit had a focus
|
|
49
|
+
indicator at all. `:focus-visible` so it only appears for keyboard
|
|
50
|
+
navigation, never on a mouse click. Components that render their own ring
|
|
51
|
+
set `focus-visible:outline-none` (a utility in a later cascade layer) and
|
|
52
|
+
win over this — so there's no double ring, just a guaranteed blue fallback
|
|
53
|
+
for the many controls that have no explicit focus style.
|
|
54
|
+
|
|
55
|
+
CAVEAT, learned the hard way: an outline is NOT immune to clipping. It is
|
|
56
|
+
unaffected by the element's OWN overflow, but `outline-offset: 2px` draws
|
|
57
|
+
it entirely outside the border box, where an ANCESTOR with
|
|
58
|
+
`overflow: clip`/`hidden` erases it completely. Controls that sit flush
|
|
59
|
+
inside such a container need `.kai-focus-inset` below. */
|
|
49
60
|
:focus-visible {
|
|
50
61
|
outline: 2px solid var(--color-ring);
|
|
51
62
|
outline-offset: 2px;
|
|
@@ -87,6 +98,19 @@
|
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
@layer components {
|
|
101
|
+
/* Focus ring drawn INSIDE the element's border box, for focusable regions
|
|
102
|
+
that sit flush against a clipping ancestor. The scrollable code-block body
|
|
103
|
+
and the message log are both `overflow-*` regions filling a rounded
|
|
104
|
+
`overflow: clip` container, so the default `outline-offset: 2px` put the
|
|
105
|
+
whole ring outside the clip and it never painted — the outline computed
|
|
106
|
+
correctly, matched `:focus-visible`, and was erased before it reached the
|
|
107
|
+
screen. A negative offset keeps it clear of the boundary. Same token and
|
|
108
|
+
width as the base ring, so it reads identically. */
|
|
109
|
+
.kai-focus-inset:focus-visible {
|
|
110
|
+
outline: 2px solid var(--color-ring);
|
|
111
|
+
outline-offset: -2px;
|
|
112
|
+
}
|
|
113
|
+
|
|
90
114
|
/* Designed radio + checkbox (appearance-none) so the form controls read as
|
|
91
115
|
intentional, theme correctly in light/dark, and have comfortable targets —
|
|
92
116
|
replacing the weak native defaults. Used via `.kai-radio` / `.kai-checkbox`. */
|