@kitn.ai/ui 0.26.0 → 0.28.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 +25 -6
- package/bin/mcp.js +16 -3
- package/bin/route.js +23 -0
- package/bin/route.test.js +58 -0
- package/dist/{elements/chunks/Icon--Y8vsFal.js → Icon-DObhsRtm.js} +1 -1
- package/dist/action-icons-CcgNN2oT.js +1 -0
- package/dist/arrow-down-CjRU3JgV.js +1 -0
- package/dist/arrow-left-Bnpb1UEW.js +1 -0
- package/dist/artifact-Bwt7OgcR.js +1 -0
- package/dist/assets/dev-5XzXYR21.js +91 -0
- package/dist/attachments-Qr4kg-2b.js +1 -0
- package/dist/{elements/chunks/audio-visualizer-D7lB6mvR.js → audio-visualizer-DToYdfSc.js} +1 -1
- package/dist/badge-CdEu_jn2.js +1 -0
- package/dist/bash-InADTalH.js +1 -6
- package/dist/bell-DQAAOpDz.js +1 -0
- package/dist/{elements/chunks/button-flUbeufF.js → button-DkuOnGLy.js} +1 -1
- package/dist/card-renderer-Jx70m6_l.js +1 -0
- package/dist/card-routing-0YidER_3.js +1 -0
- package/dist/check-njkVdZu2.js +1 -0
- package/dist/checkbox-D7iRB3nL.js +1 -0
- package/dist/checkbox-group-B2_rpoPM.js +1 -0
- package/dist/chevron-down-B72s6PXR.js +1 -0
- package/dist/chevron-right-BatuHzUm.js +1 -0
- package/dist/choice-card-m0BYqqOK.js +1 -0
- package/dist/circle-check-B62pODB3.js +1 -0
- package/dist/circle-x-BPG0iPst.js +1 -0
- package/dist/circle-zx0ByzY9.js +1 -0
- package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
- package/dist/code-block-ClMLKb7C.js +1 -0
- package/dist/{elements/chunks/collapsible-BzbXpPzr.js → collapsible-B4DlWicL.js} +1 -1
- package/dist/components/attachment-types.d.ts +7 -0
- package/dist/components/audio-visualizer/fit-scale.d.ts +1 -1
- package/dist/components/card-renderer.d.ts +13 -0
- package/dist/components/card.d.ts +4 -0
- package/dist/components/chat-thread.d.ts +135 -2
- package/dist/components/conversation-item.d.ts +35 -7
- package/dist/components/conversation-list.d.ts +17 -5
- package/dist/components/conversation-panel.d.ts +29 -0
- package/dist/components/form-widgets.d.ts +67 -2
- package/dist/components/form.d.ts +48 -1
- package/dist/components/home-panel.d.ts +34 -0
- package/dist/components/message.d.ts +25 -3
- package/dist/components/reasoning.d.ts +6 -0
- package/dist/components/scroll-button.d.ts +11 -0
- package/dist/components/thread.d.ts +5 -0
- package/dist/components/tool-types.d.ts +9 -0
- package/dist/components/widget-tab-bar.d.ts +16 -0
- package/dist/composer-sQ0HcvyG.js +106 -0
- package/dist/confirm-card-rQWDQsnC.js +1 -0
- package/dist/construct-cli.es.js +1337 -0
- package/dist/{elements/chunks/context-VLIL3sJ-.js → context-0kg4ZX1J.js} +1 -1
- package/dist/controllable-CnGpMF6r.js +1 -0
- package/dist/conversation-item-BxwQLaFJ.js +1 -0
- package/dist/conversation-list-BCfGN1JU.js +1 -0
- package/dist/copy-n9u9rqpi.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/{create-tween-BFT6c-Y8.js → create-tween-C-q-JCM-.js} +1 -1
- package/dist/{create-tween-CJNlzoaO.js → create-tween-CRrJXxBd.js} +1 -1
- package/dist/{create-tween-CQED4T0g.js → create-tween-DZ3RRDHD.js} +1 -1
- package/dist/{elements/chunks/create-tween-c4Z8TDYz.js → create-tween-uPkArjIG.js} +2 -2
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/custom-elements.json +1433 -202
- package/dist/default-input-CaMrJkgH.js +1 -0
- package/dist/define-By-uL588.js +1 -0
- package/dist/define.d.ts +4 -0
- package/dist/define.js +510 -0
- package/dist/define.server.d.ts +5 -0
- package/dist/define.server.js +506 -0
- package/dist/{elements/chunks/disclosure-CcNbyLy0.js → disclosure-WUImBbDv.js} +1 -1
- package/dist/{elements/chunks/dropdown-BgWTthUx.js → dropdown-wZVeJOMd.js} +1 -1
- 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/checkbox-group.d.ts +1 -0
- package/dist/elements/checkbox-group.js +1 -0
- package/dist/elements/checkbox.d.ts +1 -0
- package/dist/elements/checkbox.js +1 -0
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- 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.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/define-entry.d.ts +12 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.js +102 -8
- package/dist/elements/dropdown.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +37 -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-grid.d.ts +1 -0
- package/dist/elements/pane-grid.js +1 -0
- 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/radio-group.d.ts +1 -0
- package/dist/elements/radio-group.js +1 -0
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- 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/select.d.ts +1 -0
- package/dist/elements/select.js +1 -0
- 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/slider.d.ts +1 -0
- package/dist/elements/slider.js +1 -0
- package/dist/elements/slots.d.ts +1 -1
- 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 +380 -26
- package/dist/{elements/chunks/ellipsis-7gU33mL2.js → ellipsis-CbOpJnU3.js} +1 -1
- package/dist/embed-CrfjkbUG.js +1 -0
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/{elements/chunks/external-link-CSOEsNlx.js → external-link-ByXAwBBQ.js} +1 -1
- package/dist/file-text-DMib2qwt.js +1 -0
- package/dist/{elements/chunks/file-tree-zvB2i7P9.js → file-tree-CJqM2aP7.js} +1 -1
- package/dist/folder-_BAb929C.js +1 -0
- package/dist/form-DSZgZQZk.js +1 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/hover-card-BQbul9eq.js +1 -0
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/icon-CdcfRhYI.js +1 -0
- package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7474 -5857
- package/dist/index.server.js +5965 -4762
- package/dist/info-DsFjGuI_.js +1 -0
- package/dist/input-DKbmkMos.js +1 -0
- package/dist/javascript-C25yR2R2.js +1 -6
- package/dist/json-DxJze_jm.js +1 -6
- package/dist/kai.es.js +1 -1
- package/dist/kbd-PvdGhX4s.js +1 -0
- package/dist/{elements/chunks/link-preview-OQ9IEq0f.js → link-preview-DbM1866N.js} +1 -1
- package/dist/link-zF8PzWfF.js +1 -0
- package/dist/loader-BM4CEz4G.js +1 -0
- package/dist/{elements/chunks/markdown-DgBFaGAz.js → markdown-LMvmAyBk.js} +7 -7
- package/dist/mcp.es.js +1361 -81
- package/dist/message-BSsgvHvf.js +1 -0
- package/dist/message-BYZECyek.js +1 -0
- package/dist/message-circle-DruwObfX.js +1 -0
- package/dist/message-square-BG6q6mlm.js +1 -0
- package/dist/{elements/chunks/minimize-2-DPIUKTrT.js → minimize-2-BQRoBV1V.js} +1 -1
- package/dist/{elements/chunks/model-switcher-DsT8tFB0.js → model-switcher-CMpCOYcB.js} +1 -1
- package/dist/{elements/chunks/overlay-DuiwdXjm.js → overlay-Cb-MkyDa.js} +1 -1
- package/dist/panel-right-i8warFxT.js +1 -0
- package/dist/paperclip-ByY3UVO6.js +1 -0
- package/dist/play-BvKKq3fX.js +1 -0
- package/dist/primitives/card-data-types.d.ts +49 -0
- package/dist/primitives/card-routing.d.ts +1 -33
- package/dist/primitives/card-validate.d.ts +2 -2
- package/dist/primitives/chat-config.d.ts +29 -2
- package/dist/primitives/conversation-store.d.ts +54 -0
- package/dist/primitives/field-mask.d.ts +96 -0
- package/dist/primitives/field-semantics.d.ts +36 -0
- package/dist/primitives/input-mask.d.ts +46 -0
- package/dist/primitives/url-scheme-policy.d.ts +34 -0
- package/dist/primitives/use-resize-observer.d.ts +2 -2
- package/dist/progress-bar-jVAHvMUf.js +1 -0
- package/dist/prompt-suggestion-CD-qtNgC.js +1 -0
- package/dist/radio-BsoBMhkF.js +1 -0
- package/dist/react/index.d.ts +586 -19
- package/dist/react.js +223 -187
- package/dist/reasoning-4kBPtlTx.js +1 -0
- package/dist/register-impl-B1hLkrI8.js +424 -0
- package/dist/{elements/chunks/resizable-D181b4fM.js → resizable-uPmY9VY-.js} +1 -1
- package/dist/rotate-ccw-B2xn93py.js +1 -0
- package/dist/rotate-cw-CMVLoCyH.js +1 -0
- package/dist/schemas/form.schema.json +21 -1
- package/dist/schemas/tool-defs.d.ts +11 -1
- package/dist/schemas.js +81 -81
- package/dist/scroll-area-BqLUo8ZZ.js +1 -0
- package/dist/scroll-button-DslXiJyL.js +1 -0
- package/dist/select-2cLRtPGT.js +1 -0
- package/dist/{elements/chunks/separator-CeJHQqLW.js → separator-BWQtNJcM.js} +1 -1
- package/dist/{elements/chunks/settings-CqaHgJF9.js → settings-BBR63jLg.js} +1 -1
- package/dist/{elements/chunks/settings-group-CjqjLCOn.js → settings-group-B2ciy-Iw.js} +1 -1
- package/dist/{elements/chunks/skeleton-D8akBnKS.js → skeleton-CkHI-HgC.js} +1 -1
- package/dist/slider-DDR9U-i0.js +1 -0
- package/dist/{solid-BtdqNZI7.js → solid-BzCFdL25.js} +9771 -7594
- package/dist/{solid-CnavwBye.js → solid-DruVOXnf.js} +7893 -6183
- package/dist/solid.d.ts +16 -2
- package/dist/solid.js +256 -243
- package/dist/solid.server.js +256 -243
- package/dist/source-GEnFF528.js +1 -0
- package/dist/star-CilHq8GP.js +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/mock.d.ts +32 -2
- package/dist/state.js +133 -108
- package/dist/store-Ciyk6eZG.js +1 -0
- package/dist/svelte--5p79yCD.js +1 -15
- package/dist/switch-KkeQIbuH.js +1 -0
- package/dist/tasks-card-BrN4bggJ.js +1 -0
- package/dist/text-shimmer-Dcj1MHuq.js +1 -0
- package/dist/textarea-Dpvbngbg.js +1 -0
- package/dist/theme.tokens.css +110 -13
- package/dist/{elements/chunks/thumbs-up-DX2Ijk3H.js → thumbs-up-CsT9Wzb9.js} +1 -1
- package/dist/toast-store-tgK6MuMv.js +1 -0
- package/dist/{elements/chunks/tool-DKQ2D1DP.js → tool-DzUjIBT8.js} +1 -1
- package/dist/{elements/chunks/tooltip-B0G99y8w.js → tooltip-Bkkg_4t0.js} +1 -1
- package/dist/trash-2-jBKxjGGQ.js +1 -0
- package/dist/{elements/chunks/triangle-alert-F9cyUEyy.js → triangle-alert-DlQ68ppf.js} +1 -1
- package/dist/tsx-B8rCNbgL.js +1 -6
- package/dist/types.d.ts +48 -3
- package/dist/typescript-RycA9KXf.js +1 -6
- package/dist/ui/checkbox-group.d.ts +83 -0
- package/dist/ui/checkbox.d.ts +40 -0
- package/dist/ui/dock.d.ts +51 -0
- package/dist/ui/icon.d.ts +3 -0
- package/dist/ui/input.d.ts +48 -2
- package/dist/ui/radio.d.ts +85 -0
- package/dist/ui/select.d.ts +71 -0
- package/dist/ui/slider.d.ts +65 -0
- package/dist/ui/switch.d.ts +9 -2
- package/dist/upload-TcAHXv9W.js +1 -0
- package/dist/url-scheme-policy-DR5DgvkM.js +1 -0
- package/dist/use-card-resolution-DaZqAdWf.js +1 -0
- package/dist/{elements/chunks/variant-aurora-qt6Zct8M.js → variant-aurora-BhLuYxJR.js} +1 -1
- package/dist/{variant-aurora-B8II3-I1.js → variant-aurora-CNmfHhcx.js} +2 -2
- package/dist/{variant-aurora-CMNWOSJR.js → variant-aurora-D6GsdTPA.js} +2 -2
- package/dist/{variant-aurora-CcuDMWwQ.js → variant-aurora-DbW_OWL2.js} +1 -1
- package/dist/{elements/chunks/variant-custom-tJp22_BQ.js → variant-custom-BRvaNHSi.js} +1 -1
- package/dist/{variant-custom-L16JLLZy.js → variant-custom-BvYHR62b.js} +1 -1
- package/dist/{variant-custom-BdvYC7af.js → variant-custom-DMTrEO5w.js} +2 -2
- package/dist/{variant-custom-C714mqKl.js → variant-custom-DV2x254s.js} +2 -2
- package/dist/{variant-wave-ecwykeyL.js → variant-wave-BNMGrDfj.js} +2 -2
- package/dist/{variant-wave-2S4La2Ka.js → variant-wave-BpDd8t3Z.js} +1 -1
- package/dist/{variant-wave-D7ruiA1W.js → variant-wave-CyBueXj9.js} +2 -2
- package/dist/{elements/chunks/variant-wave-CaQ5ti39.js → variant-wave-sp-qwsdI.js} +1 -1
- package/dist/vue-BmIZj4XD.js +1 -33
- package/dist/x-Z5UntkIJ.js +1 -0
- package/frameworks/react/index.tsx +210 -22
- package/llms-full.txt +1184 -22
- package/llms.txt +6 -3
- package/package.json +17 -4
- package/src/agent-tooling/README.md +192 -0
- package/src/agent-tooling/catalog/invariants.ts +3 -3
- package/src/agent-tooling/catalog/surfaces.ts +72 -0
- package/src/agent-tooling/construct/cli-entry.ts +8 -0
- package/src/agent-tooling/construct/cli.ts +147 -0
- package/src/agent-tooling/construct/codegen.ts +1635 -0
- package/src/agent-tooling/construct/construct.v1.schema.json +319 -0
- package/src/agent-tooling/construct/dev.ts +148 -0
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +32 -0
- package/src/agent-tooling/construct/schema.ts +452 -0
- package/src/agent-tooling/mcp/server.ts +2 -1
- package/src/agent-tooling/mcp/tools/construct.ts +130 -0
- package/src/agent-tooling/mcp/tools/debug.ts +126 -4
- package/src/agent-tooling/mcp/tools/reference.ts +104 -3
- package/src/agent-tooling/recipes/composed-thread.ts +714 -0
- package/src/agent-tooling/recipes/index.ts +22 -0
- package/src/agent-tooling/recipes/types.ts +32 -0
- package/src/agent-tooling/registry.ts +5 -0
- package/src/components/attachment-types.ts +7 -0
- package/src/components/audio-visualizer/fit-scale.ts +1 -1
- package/src/components/card-renderer.tsx +39 -8
- package/src/components/card.tsx +10 -2
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/chat-thread.tsx +602 -43
- package/src/components/choice-card.tsx +102 -37
- package/src/components/coachmark.tsx +1 -1
- package/src/components/code-block.tsx +2 -2
- package/src/components/composer.tsx +89 -15
- package/src/components/confirm-card.tsx +20 -4
- package/src/components/conversation-item.tsx +56 -9
- package/src/components/conversation-list.tsx +25 -10
- package/src/components/conversation-panel.tsx +118 -0
- package/src/components/empty.tsx +14 -3
- package/src/components/file-tree.tsx +5 -1
- package/src/components/form-widgets.tsx +257 -195
- package/src/components/form.tsx +297 -9
- package/src/components/home-panel.tsx +159 -0
- package/src/components/loader.tsx +12 -5
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +71 -11
- package/src/components/prompt-input.tsx +7 -1
- package/src/components/prompt-suggestion.tsx +4 -1
- package/src/components/reasoning.tsx +30 -3
- package/src/components/response-compare.tsx +2 -2
- package/src/components/scroll-button.tsx +56 -4
- package/src/components/source.tsx +6 -1
- package/src/components/tasks-card.tsx +58 -16
- package/src/components/thread.tsx +10 -1
- package/src/components/toast.tsx +1 -1
- package/src/components/tool-types.ts +9 -0
- package/src/components/widget-tab-bar.tsx +73 -0
- package/src/elements/attachments.tsx +3 -1
- package/src/elements/chat-workspace.tsx +1 -1
- package/src/elements/chat.tsx +115 -5
- package/src/elements/checkbox-group.tsx +164 -0
- package/src/elements/checkbox.tsx +134 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +12 -1
- package/src/elements/conversation-item.tsx +62 -22
- package/src/elements/conversation-list.tsx +2 -2
- package/src/elements/define-entry.ts +12 -0
- package/src/elements/dock.tsx +11 -0
- package/src/elements/element-data-types.ts +40 -0
- package/src/elements/element-manifest.json +24 -0
- package/src/elements/element-meta.json +767 -34
- package/src/elements/element-nonscalar.json +17 -0
- package/src/elements/element-types.d.ts +380 -26
- package/src/elements/feedback-bar.tsx +5 -0
- package/src/elements/icon-names.json +29 -0
- package/src/elements/input.tsx +312 -23
- package/src/elements/message.tsx +2 -0
- package/src/elements/pane-grid.tsx +111 -0
- package/src/elements/prompt-input.tsx +8 -2
- package/src/elements/radio-group.tsx +129 -0
- package/src/elements/register-impl.ts +6 -0
- package/src/elements/scroll-button.tsx +54 -6
- package/src/elements/select.tsx +165 -0
- package/src/elements/slider.tsx +171 -0
- package/src/elements/slots.ts +1 -1
- package/src/elements/styles.css +89 -3
- package/src/elements/thread.tsx +2 -0
- package/src/elements/toast.tsx +14 -1
- package/src/index.ts +5 -2
- package/src/primitives/card-data-types.ts +49 -0
- package/src/primitives/card-host.tsx +4 -2
- package/src/primitives/card-routing.ts +7 -56
- package/src/primitives/card-schemas/form.schema.json +21 -1
- package/src/primitives/card-validate.ts +10 -2
- package/src/primitives/chat-config.tsx +33 -6
- package/src/primitives/conversation-store.ts +248 -0
- package/src/primitives/field-mask.ts +256 -0
- package/src/primitives/field-semantics.ts +115 -0
- package/src/primitives/input-mask.ts +853 -0
- package/src/primitives/toast-store.ts +66 -4
- package/src/primitives/url-scheme-policy.ts +70 -0
- package/src/primitives/use-resize-observer.ts +2 -2
- package/src/schemas/tool-defs.ts +12 -2
- package/src/solid.ts +20 -2
- package/src/state/index.ts +1 -1
- package/src/state/mock.ts +92 -6
- package/src/types.ts +45 -3
- package/src/ui/agent-card.tsx +2 -2
- package/src/ui/avatar.tsx +1 -1
- package/src/ui/checkbox-group.tsx +153 -0
- package/src/ui/checkbox.tsx +63 -0
- package/src/ui/dock.tsx +192 -7
- package/src/ui/icon.tsx +53 -0
- package/src/ui/input.tsx +249 -9
- package/src/ui/kbd.tsx +1 -1
- package/src/ui/nav.tsx +1 -1
- package/src/ui/pane-group.tsx +2 -2
- package/src/ui/radio.tsx +150 -0
- package/src/ui/select.tsx +168 -0
- package/src/ui/slider.tsx +178 -0
- package/src/ui/switch.tsx +49 -18
- package/src/utils/cn.ts +11 -5
- package/theme.css +112 -13
- package/dist/elements/chunks/action-icons-D9u3OzYW.js +0 -1
- package/dist/elements/chunks/arrow-left-CVh1TeN7.js +0 -1
- package/dist/elements/chunks/artifact-BkYcfIlZ.js +0 -1
- package/dist/elements/chunks/attachments-DwfNlubq.js +0 -1
- package/dist/elements/chunks/badge-Q0-Cmwzz.js +0 -1
- package/dist/elements/chunks/bash-InADTalH.js +0 -1
- package/dist/elements/chunks/card-renderer-3Dyl-FpN.js +0 -1
- package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
- package/dist/elements/chunks/check-B5kBPKeo.js +0 -1
- package/dist/elements/chunks/chevron-down-DZ1GS39h.js +0 -1
- package/dist/elements/chunks/chevron-right-CvP7OUd2.js +0 -1
- package/dist/elements/chunks/choice-card-D4QUIOWZ.js +0 -1
- package/dist/elements/chunks/circle-CDf9g6MQ.js +0 -1
- package/dist/elements/chunks/circle-check-CA47Nqbz.js +0 -1
- package/dist/elements/chunks/circle-x-CJ_HVbC8.js +0 -1
- package/dist/elements/chunks/code-block-BdLiTitd.js +0 -1
- package/dist/elements/chunks/composer-Byve-n8K.js +0 -69
- package/dist/elements/chunks/confirm-card-B0flE3MN.js +0 -1
- package/dist/elements/chunks/controllable-DTwTIlwp.js +0 -1
- package/dist/elements/chunks/conversation-item-BDztezOG.js +0 -1
- package/dist/elements/chunks/copy-Nf-kqxXH.js +0 -1
- package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
- package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
- package/dist/elements/chunks/default-input-C2iLFq0b.js +0 -1
- package/dist/elements/chunks/define-wjHR1MkU.js +0 -1
- package/dist/elements/chunks/download-C4V7tBdb.js +0 -1
- package/dist/elements/chunks/embed-D3KewsIJ.js +0 -1
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
- package/dist/elements/chunks/file-text-D1G8khle.js +0 -1
- package/dist/elements/chunks/folder-WW2LGLid.js +0 -1
- package/dist/elements/chunks/form-CQirdC4W.js +0 -1
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
- package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
- package/dist/elements/chunks/hover-card-CJJkfHTK.js +0 -1
- package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
- package/dist/elements/chunks/icon-Cfgmf4fa.js +0 -1
- package/dist/elements/chunks/info-DX3YMOt6.js +0 -1
- package/dist/elements/chunks/input-BtW9U6Sq.js +0 -1
- package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
- package/dist/elements/chunks/json-DxJze_jm.js +0 -1
- package/dist/elements/chunks/kbd-LGXwuT_V.js +0 -1
- package/dist/elements/chunks/link-2My69BqT.js +0 -1
- package/dist/elements/chunks/loader-iOxc_Tu-.js +0 -1
- package/dist/elements/chunks/message-VMZohrXx.js +0 -1
- package/dist/elements/chunks/message-circle-CpXMM-_n.js +0 -1
- package/dist/elements/chunks/message-square-DxcWtETQ.js +0 -1
- package/dist/elements/chunks/message-tZmV2A3p.js +0 -1
- package/dist/elements/chunks/paperclip-CRZVp4z_.js +0 -1
- package/dist/elements/chunks/progress-bar-DNy66e_E.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-Bn64Nl7L.js +0 -1
- package/dist/elements/chunks/reasoning-Emn9N4-T.js +0 -1
- package/dist/elements/chunks/rotate-cw-CPWe37D9.js +0 -1
- package/dist/elements/chunks/scroll-area-ClZkZxIk.js +0 -1
- package/dist/elements/chunks/scroll-button-DPE-t3x_.js +0 -1
- package/dist/elements/chunks/share-CyfnNEqp.js +0 -1
- package/dist/elements/chunks/source-UWCvmVak.js +0 -1
- package/dist/elements/chunks/star-DoHFAikQ.js +0 -1
- package/dist/elements/chunks/store-Cjq0qwYN.js +0 -1
- package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
- package/dist/elements/chunks/tasks-card-CmZ5xh6v.js +0 -1
- package/dist/elements/chunks/text-shimmer-DYn09zrz.js +0 -1
- package/dist/elements/chunks/textarea-skiGHURM.js +0 -1
- package/dist/elements/chunks/toast-store-CFnQpThX.js +0 -1
- package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
- package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
- package/dist/elements/chunks/use-card-resolution-DoA2f_EU.js +0 -1
- package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
- package/dist/elements/chunks/x-B6UnOiLw.js +0 -1
- package/dist/llms/llms-full.txt +0 -2949
- package/dist/llms/llms.txt +0 -156
- package/dist/register-impl-DpE7icIb.js +0 -293
- package/dist/ui/stat.d.ts +0 -16
- package/src/ui/stat.tsx +0 -41
- /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
- /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
- /package/dist/{elements/chunks/slots-DaNjX0rc.js → slots-DaNjX0rc.js} +0 -0
package/src/components/form.tsx
CHANGED
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
} from '../primitives/card-validate';
|
|
23
23
|
import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primitives/card-contract';
|
|
24
24
|
import { emitCardEvent } from '../primitives/card-routing';
|
|
25
|
+
import { compileMask, formatForDisplay, formatRaw, normalizeToRaw } from '../primitives/field-mask';
|
|
26
|
+
import { FIELD_SEMANTIC_TYPES, fieldSemantics, type FieldSemanticType } from '../primitives/field-semantics';
|
|
25
27
|
import { useCardHost } from '../primitives/card-host';
|
|
26
28
|
import { useCardResolution } from './use-card-resolution';
|
|
27
29
|
import { Check } from 'lucide-solid';
|
|
@@ -167,6 +169,233 @@ export function widgetFor(field: FormField, inlineMax: number): WidgetKind {
|
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
173
|
+
// Field formats (spec §7.3) — the model-facing half of masked fields.
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* What one field's `x-kai-*` format hints resolve to.
|
|
178
|
+
*
|
|
179
|
+
* The three props on the left are `Input`'s masking surface verbatim;
|
|
180
|
+
* `hint` is the TEXT statement of the expected format, which `FieldRow` renders and
|
|
181
|
+
* links through its EXISTING `aria-describedby` chain (no third channel).
|
|
182
|
+
* Every field is `undefined` when the field carries no hints, which is what "byte for
|
|
183
|
+
* byte the behavior of today" means here.
|
|
184
|
+
*/
|
|
185
|
+
export interface FieldMaskHint {
|
|
186
|
+
/** The compiled-clean mask pattern handed to `Input`'s `format`. */
|
|
187
|
+
format?: string;
|
|
188
|
+
/** An explicit, aligned display guide. Absent = the pattern derives its own. */
|
|
189
|
+
guide?: string;
|
|
190
|
+
/** The tier-1 semantic type: `inputmode`, `autocomplete`, and the canonical form. */
|
|
191
|
+
semantic?: FieldSemanticType;
|
|
192
|
+
/** The expected format, stated in text for the description chain. */
|
|
193
|
+
hint?: string;
|
|
194
|
+
/**
|
|
195
|
+
* Everything that was ignored and why, as DATA rather than as a side effect.
|
|
196
|
+
*
|
|
197
|
+
* Resolution runs inside a memo on a render path, so it must never throw and must
|
|
198
|
+
* not warn twice for the same reason on a re-render. Returning the messages lets
|
|
199
|
+
* `FieldRow` own the once-per-row discipline and lets the pure tests assert the
|
|
200
|
+
* wording without a console spy.
|
|
201
|
+
*/
|
|
202
|
+
warnings: string[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The resolution of a field with no format hints: shared, frozen, and the default
|
|
206
|
+
* every non-text widget carries so the prop bag has one shape. */
|
|
207
|
+
export const EMPTY_MASK_HINT: FieldMaskHint = Object.freeze({ warnings: [] as string[] });
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Messages already printed for a given field DEFINITION.
|
|
211
|
+
*
|
|
212
|
+
* Keyed on the field object, not on the row, and that is the fix rather than the
|
|
213
|
+
* convenience. A per-row `Set` looks right and does not work: `FieldRow`'s body runs
|
|
214
|
+
* TWICE for one visible row (measured — the parent rebuilds the `<For>` once at mount),
|
|
215
|
+
* so each build got its own empty Set and every bad hint printed twice. The reviewer
|
|
216
|
+
* caught that because the tests asserted `toHaveBeenCalled()` and never a count; they
|
|
217
|
+
* assert `toHaveBeenCalledTimes` now.
|
|
218
|
+
*
|
|
219
|
+
* A `WeakMap` because the key is model output: it must not pin a discarded card's field
|
|
220
|
+
* definitions in memory, and it must not dedupe across cards either. The repo's
|
|
221
|
+
* reactivity contract makes the identity exactly right — an edited field arrives as a
|
|
222
|
+
* NEW object (CLAUDE.md, the `kai-` contract), so a changed hint warns again, while the
|
|
223
|
+
* same definition rendered twice warns once.
|
|
224
|
+
*/
|
|
225
|
+
const WARNED_FIELDS = new WeakMap<FormField, Set<string>>();
|
|
226
|
+
|
|
227
|
+
function warnOncePerField(field: FormField, message: string): void {
|
|
228
|
+
let seen = WARNED_FIELDS.get(field);
|
|
229
|
+
if (seen === undefined) {
|
|
230
|
+
seen = new Set<string>();
|
|
231
|
+
WARNED_FIELDS.set(field, seen);
|
|
232
|
+
}
|
|
233
|
+
if (seen.has(message)) return;
|
|
234
|
+
seen.add(message);
|
|
235
|
+
// eslint-disable-next-line no-console
|
|
236
|
+
console.warn(message);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Clip a model-supplied string before it reaches a console warning. Not a security
|
|
240
|
+
* boundary — the console is not a sink — but a 500-character pattern printing itself
|
|
241
|
+
* on every render is noise that hides the sentence that matters (M4 precedent). */
|
|
242
|
+
function clipForWarning(text: string): string {
|
|
243
|
+
return text.length <= 32 ? text : `${text.slice(0, 32)}… (${text.length} chars)`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const SEMANTIC_TOKENS: readonly string[] = FIELD_SEMANTIC_TYPES;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Resolve a field's `x-kai-format` / `x-kai-mask` / `x-kai-mask-guide` into the
|
|
250
|
+
* masking props `Input` takes, or into nothing at all.
|
|
251
|
+
*
|
|
252
|
+
* EVERY INPUT HERE IS MODEL OUTPUT and therefore untrusted. The hints are
|
|
253
|
+
* display-only — they produce text and caret positions, never HTML, a URL or an
|
|
254
|
+
* attribute on a navigable element — so this is about denial of service and
|
|
255
|
+
* confusion, not injection: an unknown token, a pattern the engine refuses and a
|
|
256
|
+
* misaligned guide each degrade to the largest thing that still works, and say so.
|
|
257
|
+
* A THROW here would take out the whole card, including the fields that were fine.
|
|
258
|
+
*/
|
|
259
|
+
export function resolveFieldMask(field: FormField, fieldKey = ''): FieldMaskHint {
|
|
260
|
+
const declared: unknown = field['x-kai-format'];
|
|
261
|
+
const mask: unknown = field['x-kai-mask'];
|
|
262
|
+
const guideIn: unknown = field['x-kai-mask-guide'];
|
|
263
|
+
const where = fieldKey ? ` on field "${clipForWarning(fieldKey)}"` : '';
|
|
264
|
+
|
|
265
|
+
if (declared === undefined) {
|
|
266
|
+
// A pattern with nothing to attach it to. Silence here would leave a model
|
|
267
|
+
// convinced it had asked for a mask and a developer looking at a plain field.
|
|
268
|
+
if (typeof mask === 'string' || typeof guideIn === 'string') {
|
|
269
|
+
return {
|
|
270
|
+
warnings: [
|
|
271
|
+
`kai-form: x-kai-mask${where} has no x-kai-format and is ignored. ` +
|
|
272
|
+
`Set "x-kai-format": "custom" to mask with it.`,
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return { warnings: [] };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (typeof declared !== 'string' || !SEMANTIC_TOKENS.includes(declared)) {
|
|
280
|
+
return {
|
|
281
|
+
warnings: [
|
|
282
|
+
`kai-form: x-kai-format "${clipForWarning(String(declared))}"${where} is not one of ` +
|
|
283
|
+
`${SEMANTIC_TOKENS.join(', ')}. Rendering an unmasked text field.`,
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const semantic = declared as FieldSemanticType;
|
|
289
|
+
const warnings: string[] = [];
|
|
290
|
+
const custom = semantic === 'custom';
|
|
291
|
+
|
|
292
|
+
if (!custom && typeof mask === 'string') {
|
|
293
|
+
warnings.push(
|
|
294
|
+
`kai-form: x-kai-mask${where} is read only with x-kai-format "custom"; ` +
|
|
295
|
+
`"${semantic}" brings its own format. The mask is ignored.`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const pattern = custom ? mask : fieldSemantics(semantic).defaultFormat;
|
|
300
|
+
if (typeof pattern !== 'string' || pattern === '') {
|
|
301
|
+
warnings.push(
|
|
302
|
+
`kai-form: x-kai-format "custom"${where} needs an x-kai-mask pattern. ` +
|
|
303
|
+
`Rendering an unmasked text field.`,
|
|
304
|
+
);
|
|
305
|
+
return { warnings };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Compile the PATTERN first, alone. Compiling it with a bad guide would report the
|
|
309
|
+
// guide's failure and the pattern's as one event, and the two have different
|
|
310
|
+
// fallbacks: a refused pattern means no mask at all, a refused guide means this
|
|
311
|
+
// mask with its own derived guide.
|
|
312
|
+
let compiled;
|
|
313
|
+
try {
|
|
314
|
+
compiled = compileMask(pattern);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
warnings.push(
|
|
317
|
+
`kai-form: x-kai-mask "${clipForWarning(pattern)}"${where} did not compile ` +
|
|
318
|
+
`(${err instanceof Error ? err.message : String(err)}). Rendering an unmasked text field.`,
|
|
319
|
+
);
|
|
320
|
+
return { warnings };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// A pattern with no `#`/`@`/`*` COMPILES — unknown characters are literals by
|
|
324
|
+
// position, which is the rule that makes `V-***` work and is not negotiable. But it
|
|
325
|
+
// compiles to capacity 0: a field that accepts nothing, shows nothing and submits
|
|
326
|
+
// nothing. Left alone that is the quietest failure in the whole feature, and the one
|
|
327
|
+
// a model is likeliest to reach — `mm/dd/yyyy` and `CHG-1234` are the strings this
|
|
328
|
+
// schema's own descriptions put in front of it, one free-form key away from where
|
|
329
|
+
// they belong. So it is refused here, out loud, on the same path as a pattern the
|
|
330
|
+
// engine rejects outright. `compileMask` cannot make this call for both callers: a
|
|
331
|
+
// zero-capacity pattern is a legitimate compile, it is only a nonsense FIELD.
|
|
332
|
+
if (compiled.capacity === 0) {
|
|
333
|
+
warnings.push(
|
|
334
|
+
`kai-form: x-kai-mask "${clipForWarning(pattern)}"${where} has no fill positions, so nothing ` +
|
|
335
|
+
`could ever be typed into it. Use # for a digit, @ for a letter or digit, * for an ` +
|
|
336
|
+
`obscurable one (e.g. "##/##/####", with "mm/dd/yyyy" as x-kai-mask-guide). ` +
|
|
337
|
+
`Rendering an unmasked text field.`,
|
|
338
|
+
);
|
|
339
|
+
return { warnings };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let guide = typeof guideIn === 'string' ? guideIn : undefined;
|
|
343
|
+
if (guide !== undefined) {
|
|
344
|
+
try {
|
|
345
|
+
compileMask(pattern, guide);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
warnings.push(
|
|
348
|
+
`kai-form: x-kai-mask-guide "${clipForWarning(guide)}"${where} does not align with the ` +
|
|
349
|
+
`pattern (${err instanceof Error ? err.message : String(err)}). Using the pattern's own guide.`,
|
|
350
|
+
);
|
|
351
|
+
guide = undefined;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
format: pattern,
|
|
357
|
+
guide,
|
|
358
|
+
semantic,
|
|
359
|
+
// The guide when there is one (`mm/dd/yyyy` reads as a format to a person); the
|
|
360
|
+
// pattern otherwise, because a DERIVED guide is spaces at every fill position and
|
|
361
|
+
// says nothing out loud. A visual guide is not a description either way (spec §6).
|
|
362
|
+
hint: `Format: ${guide ?? pattern}`,
|
|
363
|
+
warnings,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The text a masked control SHOWS for a stored (canonical) value.
|
|
369
|
+
*
|
|
370
|
+
* The store holds one value per field and it is the canonical one: digits for
|
|
371
|
+
* `tel`/`ssn`/`credit-card`. That value is also what flows back into the control as its
|
|
372
|
+
* `value` prop — and writing `5550101234` over a field the masker just wrote
|
|
373
|
+
* `555-010-1234` into un-formats it one keystroke behind the user, with the caret
|
|
374
|
+
* jumping to the end. `<kai-input>` hit exactly this and split display from canonical;
|
|
375
|
+
* this is the same split, one layer up, for the Solid `Form`.
|
|
376
|
+
*
|
|
377
|
+
* Re-formatting through the SAME pure engine means the string handed back is the string
|
|
378
|
+
* already in the field, which the HTML value setter treats as a no-op (caret included).
|
|
379
|
+
*
|
|
380
|
+
* THE GUIDE BRANCH IS NOT A CHOICE MADE HERE — it mirrors `display()` in
|
|
381
|
+
* `primitives/input-mask.ts`: with an explicit guide the field is always the pattern's
|
|
382
|
+
* full length, without one it shows up to the last typed character. Disagreeing with the
|
|
383
|
+
* masker about that would put the fight straight back.
|
|
384
|
+
*/
|
|
385
|
+
function maskedDisplay(hint: FieldMaskHint, value: unknown): unknown {
|
|
386
|
+
if (hint.format === undefined || typeof value !== 'string' || value === '') return value;
|
|
387
|
+
try {
|
|
388
|
+
const pattern = compileMask(hint.format, hint.guide);
|
|
389
|
+
const raw = normalizeToRaw(pattern, value);
|
|
390
|
+
return hint.guide === undefined ? formatRaw(pattern, raw) : formatForDisplay(pattern, raw);
|
|
391
|
+
} catch {
|
|
392
|
+
// Unreachable in practice — `resolveFieldMask` only reports a `format` that already
|
|
393
|
+
// compiled — but this runs on a render path, so it degrades to the stored text
|
|
394
|
+
// rather than taking the card down if that ever stops being true.
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
170
399
|
/** Humanize a camelCase / snake_case property key into a label. */
|
|
171
400
|
export function humanize(key: string): string {
|
|
172
401
|
const spaced = key
|
|
@@ -626,6 +855,13 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
626
855
|
>
|
|
627
856
|
<div class="flex w-full flex-wrap items-center justify-between gap-2">
|
|
628
857
|
<Show when={dismissible()}>
|
|
858
|
+
{/* The contract `dismiss` verb — a footer ACTION (it emits
|
|
859
|
+
`{kind:'dismiss'}` and collapses the card to a re-openable
|
|
860
|
+
stub), not the chrome close in `Card`'s own `dismissible`
|
|
861
|
+
prop, which the contract cards deliberately never set.
|
|
862
|
+
confirm/choice/tasks were unlabelled ghost ✕ icons and now
|
|
863
|
+
match this one, so all four dismiss controls are the same
|
|
864
|
+
labelled ghost button. */}
|
|
629
865
|
<Button
|
|
630
866
|
type="button"
|
|
631
867
|
variant="ghost"
|
|
@@ -738,27 +974,59 @@ interface FieldRowProps {
|
|
|
738
974
|
|
|
739
975
|
function FieldRow(props: FieldRowProps): JSX.Element {
|
|
740
976
|
const id = `f-${props.fieldKey}-${Math.random().toString(36).slice(2, 8)}`;
|
|
977
|
+
const labelId = `${id}-label`;
|
|
741
978
|
const errorId = `${id}-err`;
|
|
742
979
|
const descId = `${id}-desc`;
|
|
980
|
+
const formatId = `${id}-fmt`;
|
|
743
981
|
const label = () => props.field.title ?? humanize(props.fieldKey);
|
|
744
982
|
const widget = createMemo(() => widgetFor(props.field, props.inlineMax));
|
|
745
983
|
const placeholder = () => props.field['x-kai-placeholder'];
|
|
984
|
+
|
|
985
|
+
// Field formats (spec §7.3). A memo, so a keystroke does not re-resolve: it reads
|
|
986
|
+
// `props.field` and nothing else, which is the subscription discipline the rest of
|
|
987
|
+
// this row is built on (K-D12b).
|
|
988
|
+
const maskHint = createMemo(() => {
|
|
989
|
+
const resolved = resolveFieldMask(props.field, props.fieldKey);
|
|
990
|
+
for (const message of resolved.warnings) warnOncePerField(props.field, message);
|
|
991
|
+
return resolved;
|
|
992
|
+
});
|
|
993
|
+
|
|
746
994
|
const describedBy = () =>
|
|
747
|
-
[
|
|
995
|
+
[
|
|
996
|
+
props.field.description ? descId : '',
|
|
997
|
+
maskHint().hint ? formatId : '',
|
|
998
|
+
props.error() ? errorId : '',
|
|
999
|
+
]
|
|
748
1000
|
.filter(Boolean)
|
|
749
1001
|
.join(' ') || undefined;
|
|
750
1002
|
|
|
751
|
-
|
|
1003
|
+
// `fieldset` and `repeater` render a REAL <fieldset><legend> holding this same text,
|
|
1004
|
+
// so the row's own label would print it twice. They are the only two kinds that
|
|
1005
|
+
// supply their own VISIBLE grouping label.
|
|
1006
|
+
//
|
|
1007
|
+
// The other four kinds this list used to hold — radio, checkbox-group, multiselect,
|
|
1008
|
+
// taglist — supplied an `aria-label` and nothing else, which is a name only a screen
|
|
1009
|
+
// reader can reach: "Severity", "Environments" and "Tags" were announced and INVISIBLE
|
|
1010
|
+
// on screen. The row now always renders the text for them and hands `labelId` to the
|
|
1011
|
+
// widget, which points its group at it with `aria-labelledby`. One string, visible,
|
|
1012
|
+
// correctly associated.
|
|
1013
|
+
const ownLegend = () => ['fieldset', 'repeater'].includes(widget());
|
|
1014
|
+
|
|
1015
|
+
// Which kinds have no single labelable control for `<label for>` to name. Their label
|
|
1016
|
+
// is still an element with an id, which is what `aria-labelledby` needs; `taglist` is
|
|
1017
|
+
// deliberately NOT here, because its draft <input> carries the row id and a real
|
|
1018
|
+
// `for` association is strictly better than an ARIA one.
|
|
1019
|
+
const groupOnly = () => ['radio', 'checkbox-group', 'multiselect'].includes(widget());
|
|
1020
|
+
|
|
1021
|
+
const common = fieldCommon(props, id, placeholder, label, describedBy, maskHint, () =>
|
|
1022
|
+
ownLegend() ? undefined : labelId,
|
|
1023
|
+
);
|
|
752
1024
|
|
|
753
|
-
// A nested fieldset / repeater / checkbox-group provide their own grouping
|
|
754
|
-
// label, so the row's <label> is only rendered for simple single controls.
|
|
755
|
-
const isGrouped = () =>
|
|
756
|
-
['fieldset', 'repeater', 'checkbox-group', 'multiselect', 'radio', 'taglist'].includes(widget());
|
|
757
1025
|
|
|
758
1026
|
return (
|
|
759
1027
|
<div class="flex flex-col gap-2 rounded-xl bg-surface p-3.5" data-field={props.fieldKey}>
|
|
760
|
-
<Show when={!
|
|
761
|
-
<label for={id} class="text-sm font-medium text-foreground">
|
|
1028
|
+
<Show when={!ownLegend()}>
|
|
1029
|
+
<label id={labelId} for={groupOnly() ? undefined : id} class="text-sm font-medium text-foreground">
|
|
762
1030
|
{label()}
|
|
763
1031
|
<Show when={props.required}>
|
|
764
1032
|
<span class="text-destructive dark:text-red-400" aria-hidden="true">{' *'}</span>
|
|
@@ -772,6 +1040,15 @@ function FieldRow(props: FieldRowProps): JSX.Element {
|
|
|
772
1040
|
</p>
|
|
773
1041
|
</Show>
|
|
774
1042
|
|
|
1043
|
+
{/* The expected format, as TEXT and in the same describedby chain as the
|
|
1044
|
+
description and the error (spec §6, SC 3.3.2). The visual guide inside the
|
|
1045
|
+
control is not a description and does not replace this. */}
|
|
1046
|
+
<Show when={maskHint().hint}>
|
|
1047
|
+
<p id={formatId} class="text-xs text-muted-foreground">
|
|
1048
|
+
{maskHint().hint}
|
|
1049
|
+
</p>
|
|
1050
|
+
</Show>
|
|
1051
|
+
|
|
775
1052
|
<WidgetSwitch widget={widget()} common={common} fieldKey={props.fieldKey} />
|
|
776
1053
|
|
|
777
1054
|
<Show when={props.error()}>
|
|
@@ -806,10 +1083,17 @@ export function fieldCommon(
|
|
|
806
1083
|
placeholder: () => string | undefined,
|
|
807
1084
|
label: () => string,
|
|
808
1085
|
describedBy: () => string | undefined = () => undefined,
|
|
1086
|
+
mask: () => FieldMaskHint = () => EMPTY_MASK_HINT,
|
|
1087
|
+
labelledBy: () => string | undefined = () => undefined,
|
|
809
1088
|
): ReturnType<FieldRowCommon> {
|
|
810
1089
|
return {
|
|
811
1090
|
id,
|
|
812
|
-
|
|
1091
|
+
// The DISPLAY text for a masked field, the stored value verbatim for every other
|
|
1092
|
+
// one (`maskedDisplay` is the identity when nothing resolved). What the form
|
|
1093
|
+
// SUBMITS is untouched by this: that comes off the store, which the control fills
|
|
1094
|
+
// with the canonical value through `onInput`.
|
|
1095
|
+
get value() { return maskedDisplay(mask(), props.value()); },
|
|
1096
|
+
get mask() { return mask(); },
|
|
813
1097
|
get field() { return props.field; },
|
|
814
1098
|
get disabled() { return props.disabled || props.field.readOnly === true; },
|
|
815
1099
|
get placeholder() { return placeholder(); },
|
|
@@ -817,6 +1101,7 @@ export function fieldCommon(
|
|
|
817
1101
|
get invalid() { return Boolean(props.error()); },
|
|
818
1102
|
get describedBy() { return describedBy(); },
|
|
819
1103
|
get label() { return label(); },
|
|
1104
|
+
get labelledBy() { return labelledBy(); },
|
|
820
1105
|
onInput: (v) => props.onInput(v),
|
|
821
1106
|
onBlur: () => props.onBlur(),
|
|
822
1107
|
};
|
|
@@ -830,6 +1115,7 @@ interface WidgetSwitchProps {
|
|
|
830
1115
|
type FieldRowCommon = () => {
|
|
831
1116
|
id: string;
|
|
832
1117
|
value: unknown;
|
|
1118
|
+
mask: FieldMaskHint;
|
|
833
1119
|
field: FormField;
|
|
834
1120
|
disabled: boolean;
|
|
835
1121
|
placeholder?: string;
|
|
@@ -837,6 +1123,8 @@ type FieldRowCommon = () => {
|
|
|
837
1123
|
invalid: boolean;
|
|
838
1124
|
describedBy?: string;
|
|
839
1125
|
label: string;
|
|
1126
|
+
/** The id of the row's visible <label>, for widgets that name a GROUP. */
|
|
1127
|
+
labelledBy?: string;
|
|
840
1128
|
onInput: (v: unknown) => void;
|
|
841
1129
|
onBlur: () => void;
|
|
842
1130
|
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { For, Show } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
import { Button } from '../ui/button';
|
|
4
|
+
import { renderIcon } from '../ui/icon';
|
|
5
|
+
import { isSafeUrl } from '../primitives/url-scheme-policy';
|
|
6
|
+
import { relativeTimeShort, isConversationUnread } from './conversation-item';
|
|
7
|
+
import type { ConversationSummary, HomeLinkEntry } from '../types';
|
|
8
|
+
|
|
9
|
+
export interface HomePanelProps {
|
|
10
|
+
greeting?: { title?: string; subtitle?: string };
|
|
11
|
+
/** The most-recent conversation, host-derived. `undefined` hides the card
|
|
12
|
+
* entirely — this component never fetches or picks one itself. */
|
|
13
|
+
recent?: ConversationSummary;
|
|
14
|
+
/** Defaults to `true`. */
|
|
15
|
+
showNewConversation?: boolean;
|
|
16
|
+
/** Defaults to `'Send us a message'`. */
|
|
17
|
+
newChatLabel?: string;
|
|
18
|
+
links?: HomeLinkEntry[];
|
|
19
|
+
onSelectRecent?: (id: string) => void;
|
|
20
|
+
onNewChat: () => void;
|
|
21
|
+
/** Fired only for href-less link entries — an entry with a safe `href`
|
|
22
|
+
* navigates as a real anchor instead. */
|
|
23
|
+
onLink?: (entry: HomeLinkEntry) => void;
|
|
24
|
+
class?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The widget home screen (Intercom-pattern, H-1): greeting, the most-recent
|
|
29
|
+
* conversation, a "start a new conversation" CTA, and a list of host-defined
|
|
30
|
+
* links. Pure props in, events out — no fetching, no routing; a later task
|
|
31
|
+
* wires this behind `ChatThread`'s Home/Messages tab bar.
|
|
32
|
+
*
|
|
33
|
+
* Unsafe-href rule: a `links` entry with an `href` that fails `isSafeUrl`
|
|
34
|
+
* (e.g. `javascript:`) is NOT silently promoted into a button/event-emitter —
|
|
35
|
+
* that would let a host-authored-but-attacker-influenced config still fire a
|
|
36
|
+
* handler off a scheme SAFE_SCHEMES rejects. It renders as a plain,
|
|
37
|
+
* non-interactive row: label visible, no anchor, no button, no click handler.
|
|
38
|
+
*/
|
|
39
|
+
export function HomePanel(props: HomePanelProps) {
|
|
40
|
+
const title = () => props.greeting?.title ?? 'Hi there 👋';
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-kai-home-panel
|
|
45
|
+
class={cn('flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-5', props.class)}
|
|
46
|
+
>
|
|
47
|
+
<div class="flex flex-col gap-1">
|
|
48
|
+
<h2 class="text-xl font-semibold text-foreground">{title()}</h2>
|
|
49
|
+
<Show when={props.greeting?.subtitle}>
|
|
50
|
+
<p class="text-sm text-muted-foreground">{props.greeting!.subtitle}</p>
|
|
51
|
+
</Show>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<Show when={props.recent}>
|
|
55
|
+
{(recent) => {
|
|
56
|
+
const time = () => relativeTimeShort(recent().updatedAt ?? recent().lastMessageAt);
|
|
57
|
+
const unread = () => isConversationUnread(recent());
|
|
58
|
+
return (
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
data-kai-home-recent
|
|
62
|
+
onClick={() => props.onSelectRecent?.(recent().id)}
|
|
63
|
+
class="flex flex-col gap-1 rounded-xl border border-border p-4 text-left transition-colors hover:bg-accent"
|
|
64
|
+
>
|
|
65
|
+
<span class="flex items-center gap-2 text-sm font-semibold text-foreground">
|
|
66
|
+
<span class="min-w-0 flex-1 truncate">{recent().title}</span>
|
|
67
|
+
<Show when={unread()}>
|
|
68
|
+
<span aria-hidden="true" class="size-1.5 shrink-0 rounded-full bg-unread" />
|
|
69
|
+
</Show>
|
|
70
|
+
<Show when={time()}>
|
|
71
|
+
<span class="shrink-0 text-xs font-normal text-muted-foreground">{time()}</span>
|
|
72
|
+
</Show>
|
|
73
|
+
</span>
|
|
74
|
+
<Show when={recent().trailing}>
|
|
75
|
+
<span class="line-clamp-1 text-sm text-muted-foreground">{recent().trailing}</span>
|
|
76
|
+
</Show>
|
|
77
|
+
</button>
|
|
78
|
+
);
|
|
79
|
+
}}
|
|
80
|
+
</Show>
|
|
81
|
+
|
|
82
|
+
<Show when={props.showNewConversation !== false}>
|
|
83
|
+
<Button
|
|
84
|
+
type="button"
|
|
85
|
+
data-kai-home-new
|
|
86
|
+
full
|
|
87
|
+
align="start"
|
|
88
|
+
onClick={() => props.onNewChat()}
|
|
89
|
+
class="justify-between"
|
|
90
|
+
>
|
|
91
|
+
<span>{props.newChatLabel ?? 'Send us a message'}</span>
|
|
92
|
+
{renderIcon('arrow-right', { class: 'size-4 shrink-0' })}
|
|
93
|
+
</Button>
|
|
94
|
+
</Show>
|
|
95
|
+
|
|
96
|
+
<Show when={props.links && props.links.length > 0}>
|
|
97
|
+
<div class="flex flex-col overflow-hidden rounded-xl border border-border">
|
|
98
|
+
<For each={props.links}>
|
|
99
|
+
{(entry) => {
|
|
100
|
+
const safeHref = () => (entry.href && isSafeUrl(entry.href) ? entry.href : undefined);
|
|
101
|
+
const inner = (
|
|
102
|
+
<>
|
|
103
|
+
<span class="flex min-w-0 items-center gap-2">
|
|
104
|
+
<Show when={entry.icon}>{renderIcon(entry.icon, { class: 'size-4 shrink-0 text-muted-foreground' })}</Show>
|
|
105
|
+
<span class="flex min-w-0 flex-col text-left">
|
|
106
|
+
<span class="truncate text-sm font-medium text-foreground">{entry.label}</span>
|
|
107
|
+
<Show when={entry.description}>
|
|
108
|
+
<span class="truncate text-xs text-muted-foreground">{entry.description}</span>
|
|
109
|
+
</Show>
|
|
110
|
+
</span>
|
|
111
|
+
</span>
|
|
112
|
+
{renderIcon('chevron-right', { class: 'size-4 shrink-0 text-muted-foreground' })}
|
|
113
|
+
</>
|
|
114
|
+
);
|
|
115
|
+
const rowClass = 'flex items-center justify-between gap-2 border-b border-border p-3 last:border-b-0';
|
|
116
|
+
return (
|
|
117
|
+
<Show
|
|
118
|
+
when={!entry.href}
|
|
119
|
+
fallback={
|
|
120
|
+
<Show
|
|
121
|
+
when={safeHref()}
|
|
122
|
+
fallback={
|
|
123
|
+
// Unsafe href: a non-interactive row. No anchor, no
|
|
124
|
+
// button, no handler — the label stays visible, it
|
|
125
|
+
// just never becomes clickable.
|
|
126
|
+
<div data-kai-home-link class={rowClass}>
|
|
127
|
+
{inner}
|
|
128
|
+
</div>
|
|
129
|
+
}
|
|
130
|
+
>
|
|
131
|
+
<a
|
|
132
|
+
data-kai-home-link
|
|
133
|
+
href={safeHref()}
|
|
134
|
+
target="_blank"
|
|
135
|
+
rel="noreferrer noopener"
|
|
136
|
+
class={cn(rowClass, 'transition-colors hover:bg-accent')}
|
|
137
|
+
>
|
|
138
|
+
{inner}
|
|
139
|
+
</a>
|
|
140
|
+
</Show>
|
|
141
|
+
}
|
|
142
|
+
>
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
data-kai-home-link
|
|
146
|
+
onClick={() => props.onLink?.(entry)}
|
|
147
|
+
class={cn(rowClass, 'w-full text-left transition-colors hover:bg-accent')}
|
|
148
|
+
>
|
|
149
|
+
{inner}
|
|
150
|
+
</button>
|
|
151
|
+
</Show>
|
|
152
|
+
);
|
|
153
|
+
}}
|
|
154
|
+
</For>
|
|
155
|
+
</div>
|
|
156
|
+
</Show>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -250,7 +250,11 @@ export function TerminalLoader(props: TerminalLoaderProps) {
|
|
|
250
250
|
const containerSizes = { sm: 'h-4', md: 'h-5', lg: 'h-6' };
|
|
251
251
|
return (
|
|
252
252
|
<div class={cn('flex items-center space-x-1', containerSizes[size()], props.class)}>
|
|
253
|
-
|
|
253
|
+
{/* Decorative status chrome, not a control — was the BRAND token
|
|
254
|
+
(text-primary). The sibling TextShimmerLoader below already renders
|
|
255
|
+
its "Thinking" text purely in --color-foreground/--color-muted-foreground,
|
|
256
|
+
with no brand color at all; matches that precedent. */}
|
|
257
|
+
<span class={cn('text-foreground font-mono', textSizes[size()])}>{'>'}</span>
|
|
254
258
|
<div class={cn('bg-primary animate-[blink_1s_step-end_infinite]', cursorSizes[size()])} />
|
|
255
259
|
<span class="sr-only">Loading</span>
|
|
256
260
|
</div>
|
|
@@ -302,13 +306,16 @@ export function TextDotsLoader(props: TextDotsLoaderProps) {
|
|
|
302
306
|
const textSizes = { sm: 'text-xs', md: 'text-sm', lg: 'text-base' };
|
|
303
307
|
return (
|
|
304
308
|
<div class={cn('inline-flex items-center', props.class)}>
|
|
305
|
-
|
|
309
|
+
{/* Content/chrome, not a control — was `text-primary` on the label AND
|
|
310
|
+
the dots (both must match: one phrase, "Thinking..."). Matches
|
|
311
|
+
TextShimmerLoader's neutral precedent above. */}
|
|
312
|
+
<span class={cn('text-foreground font-medium', textSizes[size()])}>
|
|
306
313
|
{props.text ?? 'Thinking'}
|
|
307
314
|
</span>
|
|
308
315
|
<span class="inline-flex">
|
|
309
|
-
<span class="text-
|
|
310
|
-
<span class="text-
|
|
311
|
-
<span class="text-
|
|
316
|
+
<span class="text-foreground animate-[loading-dots_1.4s_infinite_0.2s]">.</span>
|
|
317
|
+
<span class="text-foreground animate-[loading-dots_1.4s_infinite_0.4s]">.</span>
|
|
318
|
+
<span class="text-foreground animate-[loading-dots_1.4s_infinite_0.6s]">.</span>
|
|
312
319
|
</span>
|
|
313
320
|
</div>
|
|
314
321
|
);
|
|
@@ -27,7 +27,7 @@ function MessageSkills(props: MessageSkillsProps) {
|
|
|
27
27
|
<div class={cn("flex items-center gap-1 flex-wrap", props.class)}>
|
|
28
28
|
<For each={props.skills}>
|
|
29
29
|
{(skill) => (
|
|
30
|
-
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-
|
|
30
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-micro font-medium bg-violet-400/10 text-violet-600 dark:text-violet-400">
|
|
31
31
|
{skill.name}
|
|
32
32
|
</span>
|
|
33
33
|
)}
|