@kitn.ai/ui 0.26.0 → 0.27.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 +16 -5
- 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-BnFKqqtY.js} +1 -1
- package/dist/action-icons-CBskfBMz.js +1 -0
- package/dist/arrow-down-Br0qtpsz.js +1 -0
- package/dist/arrow-left-Dj7xgE12.js +1 -0
- package/dist/artifact-CvXEm1lS.js +1 -0
- package/dist/assets/dev-5XzXYR21.js +91 -0
- package/dist/attachments-C43ZegEq.js +1 -0
- package/dist/{elements/chunks/audio-visualizer-D7lB6mvR.js → audio-visualizer-B-79y1Tj.js} +1 -1
- package/dist/badge-BpUbq8c0.js +1 -0
- package/dist/bash-InADTalH.js +1 -6
- package/dist/bell-sn17Ip9v.js +1 -0
- package/dist/{elements/chunks/button-flUbeufF.js → button-BH6rN0U-.js} +1 -1
- package/dist/card-renderer-Cb1gQmth.js +1 -0
- package/dist/card-routing-DifQgS7n.js +1 -0
- package/dist/check-DPeeMUYx.js +1 -0
- package/dist/checkbox-Dc9Yov_B.js +1 -0
- package/dist/checkbox-group-B1kDLLYk.js +1 -0
- package/dist/chevron-down-CHSWk7ZT.js +1 -0
- package/dist/chevron-right-B7l_fMgx.js +1 -0
- package/dist/choice-card-BT3c1S8g.js +1 -0
- package/dist/circle-Bb0yOpqM.js +1 -0
- package/dist/circle-check-BGZpCiz7.js +1 -0
- package/dist/circle-x-wobWkoSx.js +1 -0
- package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
- package/dist/{elements/chunks/code-block-BdLiTitd.js → code-block-CUQ5mtv0.js} +1 -1
- package/dist/{elements/chunks/collapsible-BzbXpPzr.js → collapsible-CPJbf6FM.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 +46 -1
- package/dist/components/conversation-item.d.ts +19 -7
- package/dist/components/conversation-list.d.ts +17 -5
- package/dist/components/form-widgets.d.ts +67 -2
- package/dist/components/form.d.ts +48 -1
- 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/composer-CjngLzal.js +106 -0
- package/dist/confirm-card-DCx_A4bU.js +1 -0
- package/dist/construct-cli.es.js +1169 -0
- package/dist/{elements/chunks/context-VLIL3sJ-.js → context-BY8V-Nlp.js} +1 -1
- package/dist/controllable-D43BKmtX.js +1 -0
- package/dist/conversation-list-D4lLU_yS.js +1 -0
- package/dist/copy-B6DO4PqB.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/{create-tween-CQED4T0g.js → create-tween-BPzaefTp.js} +1 -1
- package/dist/{create-tween-BFT6c-Y8.js → create-tween-CnMHviKB.js} +1 -1
- package/dist/{create-tween-CJNlzoaO.js → create-tween-D1aiGZZh.js} +1 -1
- package/dist/{elements/chunks/create-tween-c4Z8TDYz.js → create-tween-DUSQUKpr.js} +2 -2
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/custom-elements.json +1400 -218
- package/dist/default-input-CucQEOqH.js +1 -0
- package/dist/define-CGGX-7Vg.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-CbLtajUQ.js} +1 -1
- package/dist/{elements/chunks/dropdown-BgWTthUx.js → dropdown-BwhViJFd.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 +101 -7
- 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 +362 -24
- package/dist/{elements/chunks/ellipsis-7gU33mL2.js → ellipsis-DFr4Vqr3.js} +1 -1
- package/dist/embed-5s_vU4J0.js +1 -0
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/{elements/chunks/external-link-CSOEsNlx.js → external-link-u9d85ORQ.js} +1 -1
- package/dist/file-text-CX7_x42o.js +1 -0
- package/dist/{elements/chunks/file-tree-zvB2i7P9.js → file-tree-D3kBjXI5.js} +1 -1
- package/dist/folder-V-YdIL8f.js +1 -0
- package/dist/form-BRNUMCN2.js +1 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/hover-card-DaeiYrTA.js +1 -0
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/icon-BpixdZNq.js +1 -0
- package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7330 -5852
- package/dist/index.server.js +5823 -4759
- package/dist/info-8mFsIjS3.js +1 -0
- package/dist/input-d1Yuu-hz.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-961zMPFB.js +1 -0
- package/dist/link-DlZZBGaR.js +1 -0
- package/dist/{elements/chunks/link-preview-OQ9IEq0f.js → link-preview-DIIbV2Sd.js} +1 -1
- package/dist/loader-YQszHFaW.js +1 -0
- package/dist/{elements/chunks/markdown-DgBFaGAz.js → markdown-CVk1MPsj.js} +1 -1
- package/dist/mcp.es.js +1285 -79
- package/dist/message-CwKVEdZg.js +1 -0
- package/dist/message-DS8lzHc1.js +1 -0
- package/dist/message-circle-Xf8Q-vZp.js +1 -0
- package/dist/message-square-Cxnwgkns.js +1 -0
- package/dist/{elements/chunks/minimize-2-DPIUKTrT.js → minimize-2-UEZmP2QT.js} +1 -1
- package/dist/{elements/chunks/model-switcher-DsT8tFB0.js → model-switcher-Kk1jEQ5t.js} +1 -1
- package/dist/{elements/chunks/overlay-DuiwdXjm.js → overlay-C1od6LOz.js} +1 -1
- package/dist/panel-right-ryET0yyn.js +1 -0
- package/dist/paperclip-BDNoZFJP.js +1 -0
- package/dist/play-OFIvYXUQ.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/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-BvYX0aoy.js +1 -0
- package/dist/prompt-suggestion-BRFu2lcu.js +1 -0
- package/dist/radio-DcL23cNJ.js +1 -0
- package/dist/react/index.d.ts +193 -19
- package/dist/react.js +216 -180
- package/dist/reasoning-Cackq4Rh.js +1 -0
- package/dist/register-impl-C7rENvPK.js +424 -0
- package/dist/{elements/chunks/resizable-D181b4fM.js → resizable-Cp9HUYih.js} +1 -1
- package/dist/rotate-ccw-0DLRDHH5.js +1 -0
- package/dist/rotate-cw-DOB9IGcU.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-emVmWP4K.js +1 -0
- package/dist/scroll-button-CeP_gv1x.js +1 -0
- package/dist/select-CWmu00tS.js +1 -0
- package/dist/{elements/chunks/separator-CeJHQqLW.js → separator-CE6vly6z.js} +1 -1
- package/dist/{elements/chunks/settings-CqaHgJF9.js → settings-CE-78SvZ.js} +1 -1
- package/dist/{elements/chunks/settings-group-CjqjLCOn.js → settings-group-ZFWNxFQR.js} +1 -1
- package/dist/{elements/chunks/skeleton-D8akBnKS.js → skeleton-BebdP4mf.js} +1 -1
- package/dist/slider-BAKRYUlz.js +1 -0
- package/dist/{solid-BtdqNZI7.js → solid-BE5ui6sy.js} +8727 -7212
- package/dist/{solid-CnavwBye.js → solid-DFhsce3O.js} +7015 -5886
- package/dist/solid.d.ts +10 -0
- package/dist/solid.js +240 -234
- package/dist/solid.server.js +240 -234
- package/dist/source-B0FlguSx.js +1 -0
- package/dist/star-CBMZvLuN.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/{elements/chunks/store-Cjq0qwYN.js → store-7eoadydQ.js} +1 -1
- package/dist/svelte--5p79yCD.js +1 -15
- package/dist/switch-CViARZCy.js +1 -0
- package/dist/tasks-card-MjCcOhR_.js +1 -0
- package/dist/text-shimmer-DKlwRF2G.js +1 -0
- package/dist/textarea-3ay2Me2-.js +1 -0
- package/dist/theme.tokens.css +82 -13
- package/dist/{elements/chunks/thumbs-up-DX2Ijk3H.js → thumbs-up-DQcu2HMO.js} +1 -1
- package/dist/toast-store-C6yKOhbk.js +1 -0
- package/dist/{elements/chunks/tool-DKQ2D1DP.js → tool-BCYNlfKy.js} +1 -1
- package/dist/{elements/chunks/tooltip-B0G99y8w.js → tooltip-05zI4hjs.js} +1 -1
- package/dist/trash-2-BR1RxGky.js +1 -0
- package/dist/{elements/chunks/triangle-alert-F9cyUEyy.js → triangle-alert-DHohDOCy.js} +1 -1
- package/dist/tsx-B8rCNbgL.js +1 -6
- 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 +22 -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-BbGW6A_u.js +1 -0
- package/dist/url-scheme-policy-DHpoTJwB.js +1 -0
- package/dist/use-card-resolution-CJW4grFa.js +1 -0
- package/dist/{variant-aurora-B8II3-I1.js → variant-aurora-BUOVgPOB.js} +2 -2
- package/dist/{variant-aurora-CMNWOSJR.js → variant-aurora-CUsUJvFI.js} +2 -2
- package/dist/{variant-aurora-CcuDMWwQ.js → variant-aurora-CW2i4bfr.js} +1 -1
- package/dist/{elements/chunks/variant-aurora-qt6Zct8M.js → variant-aurora-xiMPty8f.js} +1 -1
- package/dist/{variant-custom-L16JLLZy.js → variant-custom-BQtOnmeO.js} +1 -1
- package/dist/{variant-custom-C714mqKl.js → variant-custom-CAm9L-E5.js} +2 -2
- package/dist/{variant-custom-BdvYC7af.js → variant-custom-CAnzzppW.js} +2 -2
- package/dist/{elements/chunks/variant-custom-tJp22_BQ.js → variant-custom-D91HjAKz.js} +1 -1
- package/dist/{variant-wave-ecwykeyL.js → variant-wave-Ca1Ls4_y.js} +2 -2
- package/dist/{variant-wave-D7ruiA1W.js → variant-wave-CxX39DKd.js} +2 -2
- package/dist/{elements/chunks/variant-wave-CaQ5ti39.js → variant-wave-DYyzIIZv.js} +1 -1
- package/dist/{variant-wave-2S4La2Ka.js → variant-wave-uY5crU_K.js} +1 -1
- package/dist/vue-BmIZj4XD.js +1 -33
- package/dist/x-C5m1JFgi.js +1 -0
- package/frameworks/react/index.tsx +198 -20
- package/llms-full.txt +1178 -21
- package/llms.txt +6 -3
- package/package.json +17 -4
- package/src/agent-tooling/catalog/invariants.ts +1 -1
- 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 +131 -0
- package/src/agent-tooling/construct/codegen.ts +1353 -0
- package/src/agent-tooling/construct/construct.v1.schema.json +250 -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 +23 -0
- package/src/agent-tooling/construct/schema.ts +371 -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 +72 -9
- 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 +33 -9
- package/src/components/conversation-list.tsx +25 -10
- 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/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/elements/attachments.tsx +3 -1
- package/src/elements/chat-workspace.tsx +1 -1
- package/src/elements/chat.tsx +22 -4
- 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 +736 -46
- package/src/elements/element-nonscalar.json +15 -0
- package/src/elements/element-types.d.ts +362 -24
- 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 +2 -1
- 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/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 +10 -0
- package/src/state/index.ts +1 -1
- package/src/state/mock.ts +92 -6
- 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 +145 -6
- 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 +84 -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/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/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
|
};
|
|
@@ -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
|
)}
|
|
@@ -8,6 +8,7 @@ import { actionIcon, BUILTIN_ACTION_LABEL } from "../ui/action-icons";
|
|
|
8
8
|
import type { ChatMessageAction, CustomAction, FeedbackVote, MessagePart, MessageSource } from "../elements/chat-types";
|
|
9
9
|
import { useChatConfig, textClass } from "../primitives/chat-config";
|
|
10
10
|
import { Reasoning, ReasoningTrigger, ReasoningContent } from "./reasoning";
|
|
11
|
+
import { Loader } from "./loader";
|
|
11
12
|
import { Tool } from "./tool";
|
|
12
13
|
import {
|
|
13
14
|
Attachments,
|
|
@@ -328,6 +329,12 @@ export interface MessageBodyProps {
|
|
|
328
329
|
* the kit checks its own seven built-ins and leaves your own card type
|
|
329
330
|
* unvalidated. A schema here WINS over a built-in of the same name. */
|
|
330
331
|
cardSchemas?: CardSchemaMap;
|
|
332
|
+
/** The custom-element host node to emit card events off when no `CardProvider`
|
|
333
|
+
* is above this body, forwarded to `CardRenderer` as `hostElement`. The
|
|
334
|
+
* `<kai-chat>`/`<kai-message>`/`<kai-thread>` facades pass their own element,
|
|
335
|
+
* so a `card` part's events leave as the bubbling `kai-card` CustomEvent
|
|
336
|
+
* instead of being silently discarded. */
|
|
337
|
+
cardHostElement?: HTMLElement;
|
|
331
338
|
/** Whether this is a user message (right-aligned bubble) vs an assistant
|
|
332
339
|
* message (full-width transparent). */
|
|
333
340
|
isUser: boolean;
|
|
@@ -348,11 +355,27 @@ export interface MessageBodyProps {
|
|
|
348
355
|
copied?: boolean;
|
|
349
356
|
/** Whether this message is the one currently streaming. Forwarded to each
|
|
350
357
|
* reasoning part's `<Reasoning>` so the disclosure auto-opens while the
|
|
351
|
-
* model is thinking and settles back once the stream ends
|
|
352
|
-
*
|
|
353
|
-
*
|
|
358
|
+
* model is thinking and settles back once the stream ends. Without it the
|
|
359
|
+
* user watches a static collapsed "Reasoning" label for the whole thinking
|
|
360
|
+
* window. The caller owns the definition of "streaming" — for
|
|
354
361
|
* `ChatThread` that is `loading` + being the last assistant message. */
|
|
355
362
|
isStreaming?: boolean;
|
|
363
|
+
/** How a `reasoning` part renders. `'full'` (default) is the current
|
|
364
|
+
* behavior: the collapsible `<Reasoning>` disclosure, with the "Thinking…"
|
|
365
|
+
* shimmer on its trigger while `isStreaming`. `'compact'` drops the
|
|
366
|
+
* disclosure entirely and shows only a shimmer loader while the part is
|
|
367
|
+
* streaming — nothing once it settles, so there is no expandable detail to
|
|
368
|
+
* open. `'off'` renders the part not at all, in either state. Kit-decides-
|
|
369
|
+
* HOW: this is a display-mode fact about the medium, not a per-message
|
|
370
|
+
* toggle, so it applies uniformly to every reasoning part in the body. */
|
|
371
|
+
reasoningMode?: 'full' | 'compact' | 'off';
|
|
372
|
+
/** Seeds the reasoning disclosure open AND keeps it tracking the stream
|
|
373
|
+
* (open while streaming, closes when it settles): the pre-Task-19f `full`
|
|
374
|
+
* behavior. Default false/absent: the panel starts closed (just the
|
|
375
|
+
* "Thinking" shimmer chip) and only opens on click, the current default
|
|
376
|
+
* (owner ruling, 2026-08-26). Meaningless when `reasoningMode` is
|
|
377
|
+
* `'compact'`/`'off'` (no disclosure exists to open). */
|
|
378
|
+
reasoningDefaultOpen?: boolean;
|
|
356
379
|
/** INJECT slot projected at the TOP of the body — above the reasoning / tools /
|
|
357
380
|
* content. A per-message header: a model-name label, a role + timestamp line.
|
|
358
381
|
* In the `<kai-message>` shadow this is `<slot name="before-body" />`. */
|
|
@@ -613,7 +636,17 @@ function MessageBody(props: MessageBodyProps) {
|
|
|
613
636
|
part="bubble content"
|
|
614
637
|
markdown={props.markdown}
|
|
615
638
|
class={props.isUser
|
|
616
|
-
|
|
639
|
+
// Content token, not the brand token: `--color-primary`
|
|
640
|
+
// is the documented consumer brand override
|
|
641
|
+
// (theme.css `--kai-color-primary`), so toking message
|
|
642
|
+
// TEXT to it means every consumer that brands primary
|
|
643
|
+
// gets brand-colored message text. `text-foreground` is
|
|
644
|
+
// already `MessageContent`'s base color (matches the
|
|
645
|
+
// assistant path's `bg-transparent p-0`, which carries
|
|
646
|
+
// no color override and falls through to the same
|
|
647
|
+
// base) — dropping `text-primary` here just lets that
|
|
648
|
+
// base apply on the user bubble too.
|
|
649
|
+
? 'bg-muted max-w-[85%] rounded-2xl px-4 py-2'
|
|
617
650
|
: 'bg-transparent p-0'}
|
|
618
651
|
>
|
|
619
652
|
{p().text}
|
|
@@ -622,18 +655,45 @@ function MessageBody(props: MessageBodyProps) {
|
|
|
622
655
|
</Match>
|
|
623
656
|
<Match when={carrierOnly()}>{null}</Match>
|
|
624
657
|
<Match when={shownReasoning()}>
|
|
625
|
-
{(p) =>
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
658
|
+
{(p) => {
|
|
659
|
+
// Default 'full' is the pre-existing DISPLAY MODE byte
|
|
660
|
+
// for byte; the OPEN behavior changed under Task 19f —
|
|
661
|
+
// it no longer auto-opens while streaming by default
|
|
662
|
+
// (owner ruling 2026-08-26). `reasoningDefaultOpen`
|
|
663
|
+
// reproduces the old always-auto-opens behavior when set.
|
|
664
|
+
const mode = () => props.reasoningMode ?? 'full';
|
|
665
|
+
return (
|
|
666
|
+
<Switch fallback={null}>
|
|
667
|
+
<Match when={mode() === 'full'}>
|
|
668
|
+
<Reasoning
|
|
669
|
+
class="mb-2 w-full"
|
|
670
|
+
isStreaming={props.isStreaming}
|
|
671
|
+
defaultOpen={props.reasoningDefaultOpen}
|
|
672
|
+
openOnStream={props.reasoningDefaultOpen}
|
|
673
|
+
>
|
|
674
|
+
<ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
|
|
675
|
+
<ReasoningContent markdown>{p().text}</ReasoningContent>
|
|
676
|
+
</Reasoning>
|
|
677
|
+
</Match>
|
|
678
|
+
{/* 'compact': the same "Thinking…" shimmer the full
|
|
679
|
+
disclosure's trigger shows while streaming — no
|
|
680
|
+
<Reasoning>/<ReasoningContent>, so there is no
|
|
681
|
+
expandable detail and nothing left once the part
|
|
682
|
+
settles (isStreaming false → this Match doesn't
|
|
683
|
+
fire, matching 'off'). 'off' never reaches here:
|
|
684
|
+
its Match doesn't fire either. */}
|
|
685
|
+
<Match when={mode() === 'compact' && props.isStreaming}>
|
|
686
|
+
<Loader variant="text-shimmer" text={p().label ?? 'Reasoning'} class="mb-2" />
|
|
687
|
+
</Match>
|
|
688
|
+
</Switch>
|
|
689
|
+
);
|
|
690
|
+
}}
|
|
631
691
|
</Match>
|
|
632
692
|
<Match when={partAs(part(), 'tool')}>
|
|
633
693
|
{(p) => <Tool toolPart={p().tool} class="mb-2 w-full" />}
|
|
634
694
|
</Match>
|
|
635
695
|
<Match when={partAs(part(), 'card')}>
|
|
636
|
-
{(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} />}
|
|
696
|
+
{(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} hostElement={props.cardHostElement} />}
|
|
637
697
|
</Match>
|
|
638
698
|
{/* No `source` match here on purpose: source parts never
|
|
639
699
|
reach a 'single' group — they are collapsed into a
|
|
@@ -115,9 +115,15 @@ function PromptInputTextarea(props: PromptInputTextareaProps) {
|
|
|
115
115
|
// the contenteditable composer is visually identical. The frame (PromptInput
|
|
116
116
|
// root) still owns radius/bg/padding/focus-ring; auto-grow is native to a
|
|
117
117
|
// contenteditable block (capped via max-height on the editable).
|
|
118
|
+
// `text-start` is a PIN, not a style choice: `text-align` inherits, so any
|
|
119
|
+
// centered ancestor (`Empty`'s root did exactly this) reached in and centered
|
|
120
|
+
// the placeholder AND the typed text. An input control's text alignment is a
|
|
121
|
+
// fact about the control, so it states it rather than inheriting it. LOGICAL
|
|
122
|
+
// (`start`), not `text-left` — in RTL the correct edge is the right one, and
|
|
123
|
+
// pinning the physical value would be a worse bug than the one it fixes.
|
|
118
124
|
const editableClass = () =>
|
|
119
125
|
cn(
|
|
120
|
-
'text-foreground min-h-[44px] w-full bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 overflow-y-auto whitespace-pre-wrap break-words',
|
|
126
|
+
'text-foreground min-h-[44px] w-full bg-transparent text-start shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 overflow-y-auto whitespace-pre-wrap break-words',
|
|
121
127
|
textClass(config.proseSize()),
|
|
122
128
|
local.class,
|
|
123
129
|
);
|
|
@@ -136,7 +136,10 @@ function renderHighlighted(text: string, highlight: string) {
|
|
|
136
136
|
<Show when={before}>
|
|
137
137
|
<span class="text-foreground/70 whitespace-pre-wrap">{before}</span>
|
|
138
138
|
</Show>
|
|
139
|
-
|
|
139
|
+
{/* Matched-substring highlight is CONTENT, not a control — was the BRAND
|
|
140
|
+
token. Full-strength `text-foreground` against the `/70`-opacity
|
|
141
|
+
siblings above/below is what actually carries the highlight. */}
|
|
142
|
+
<span class="text-foreground font-medium whitespace-pre-wrap">{matched}</span>
|
|
140
143
|
<Show when={after}>
|
|
141
144
|
<span class="text-foreground/70 whitespace-pre-wrap">{after}</span>
|
|
142
145
|
</Show>
|