@kitn.ai/ui 0.20.0 → 0.21.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 +44 -45
- package/dist/components/artifact-card.d.ts +51 -0
- package/dist/components/artifact.d.ts +1 -1
- package/dist/components/attachments.d.ts +2 -2
- package/dist/components/audio-visualizer/index.d.ts +3 -3
- package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
- package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
- package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
- package/dist/components/card-fallback.d.ts +12 -2
- package/dist/components/card-renderer.d.ts +63 -2
- package/dist/components/chat-container.d.ts +3 -0
- package/dist/components/chat-scope-picker.d.ts +2 -2
- package/dist/components/chat-thread.d.ts +68 -18
- package/dist/components/choice-card.d.ts +3 -28
- package/dist/components/composer-dom.d.ts +1 -1
- package/dist/components/composer-highlight.d.ts +1 -1
- package/dist/components/composer-history.d.ts +1 -1
- package/dist/components/composer.d.ts +1 -1
- package/dist/components/confirm-card.d.ts +3 -18
- package/dist/components/context.d.ts +8 -4
- package/dist/components/conversation-item.d.ts +1 -1
- package/dist/components/conversation-list.d.ts +4 -3
- package/dist/components/embed.d.ts +3 -3
- package/dist/components/file-tree.d.ts +11 -22
- package/dist/components/form-widgets.d.ts +1 -1
- package/dist/components/form.d.ts +3 -44
- package/dist/components/link-preview.d.ts +3 -3
- package/dist/components/loader.d.ts +30 -48
- package/dist/components/message-skills.d.ts +4 -0
- package/dist/components/message.d.ts +84 -21
- package/dist/components/model-switcher.d.ts +2 -2
- package/dist/components/prompt-input.d.ts +2 -2
- package/dist/components/response-compare-types.d.ts +11 -2
- package/dist/components/response-compare.d.ts +3 -3
- package/dist/components/source.d.ts +12 -3
- package/dist/components/tasks-card.d.ts +3 -31
- package/dist/components/thread.d.ts +17 -5
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/tool-classify.d.ts +8 -0
- package/dist/components/tool-types.d.ts +29 -1
- package/dist/components/tool.d.ts +3 -3
- package/dist/components/use-card-resolution.d.ts +1 -1
- package/dist/create-tween-D3Y5qiij.js +338 -0
- package/dist/create-tween-DGKY1FDY.js +341 -0
- package/dist/create-tween-paho5Rh-.js +7 -0
- package/dist/custom-elements.json +1681 -454
- 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/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-types.d.ts +70 -10
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-Tj58ES_d.js → Icon-C-2LvGDG.js} +1 -1
- package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-CaYoImEk.js} +1 -1
- package/dist/elements/chunks/arrow-left-Bb46H7w9.js +1 -0
- package/dist/elements/chunks/artifact-BBIscOxJ.js +1 -0
- package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-BB56zoji.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-BxOx4THz.js +1 -0
- package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-SxLSZ19R.js} +1 -1
- package/dist/elements/chunks/{button-ZWNif3--.js → button-CmkUkz6c.js} +1 -1
- package/dist/elements/chunks/card-renderer-Db4ffkWv.js +1 -0
- package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
- package/dist/elements/chunks/chat-thread-B3Ks-cKr.js +1 -0
- package/dist/elements/chunks/check-C8desc_h.js +1 -0
- package/dist/elements/chunks/chevron-down-DKtc2HNB.js +1 -0
- package/dist/elements/chunks/chevron-right-Dw4GPhwL.js +1 -0
- package/dist/elements/chunks/choice-card-Q9cYYDyM.js +1 -0
- package/dist/elements/chunks/circle-BeokBsvB.js +1 -0
- package/dist/elements/chunks/circle-check-wJSqt859.js +1 -0
- package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-D0MEfxOM.js} +1 -1
- package/dist/elements/chunks/code-block-C8VzAraE.js +1 -0
- package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-Cvj61obE.js} +1 -1
- package/dist/elements/chunks/{composer-BguS0QL_.js → composer-D86ztyL0.js} +2 -2
- package/dist/elements/chunks/confirm-card-BdyDAGLw.js +1 -0
- package/dist/elements/chunks/context-WHDl-xSx.js +1 -0
- package/dist/elements/chunks/conversation-list-CdR14_Y4.js +1 -0
- package/dist/elements/chunks/create-tween-C_A_B638.js +7 -0
- package/dist/elements/chunks/{default-input-Cqqilg9t.js → default-input-CSZqUM81.js} +1 -1
- package/dist/elements/chunks/define-BzPnNsXg.js +1 -0
- package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DVKsoc1F.js} +1 -1
- package/dist/elements/chunks/{download-Dsbg3ste.js → download-BjA54IMJ.js} +1 -1
- package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-BjVVHWXH.js} +1 -1
- package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BNedmqfi.js} +1 -1
- package/dist/elements/chunks/embed-DQsl4XEq.js +1 -0
- package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-Di-dw2CU.js} +1 -1
- package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-B3sAODCV.js} +1 -1
- package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-Dgto-bMn.js} +1 -1
- package/dist/elements/chunks/{folder-glP5rha4.js → folder-D6rMadFV.js} +1 -1
- package/dist/elements/chunks/form-CILxvhQ7.js +1 -0
- package/dist/elements/chunks/hover-card-BtiF4XiU.js +1 -0
- package/dist/elements/chunks/{icon-BLb8beoi.js → icon-DKkmze3y.js} +1 -1
- package/dist/elements/chunks/{info-CIsv92JK.js → info-CWRjZkuy.js} +1 -1
- package/dist/elements/chunks/{input-Dds1kFyt.js → input--QJBK6BA.js} +1 -1
- package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-C3zuKh8R.js} +1 -1
- package/dist/elements/chunks/{link-i4r_k7z4.js → link-BL5JA7Q0.js} +1 -1
- package/dist/elements/chunks/link-preview-CU37bbXh.js +1 -0
- package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-XmpyxyRH.js} +1 -1
- package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-3Gv5UZ_o.js} +1 -1
- package/dist/elements/chunks/message-CnBA2OUt.js +1 -0
- package/dist/elements/chunks/message-X5NZbq-W.js +1 -0
- package/dist/elements/chunks/message-square-CnpAm_P0.js +1 -0
- package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-DwyrUTcM.js} +1 -1
- package/dist/elements/chunks/{model-switcher-B94YAKsM.js → model-switcher-CclaJEHt.js} +1 -1
- package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-Cepd-KcS.js} +1 -1
- package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CdwKjrlu.js} +1 -1
- package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-DMVXBD3b.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-Di_WZE_5.js} +1 -1
- package/dist/elements/chunks/reasoning-D0jMch5u.js +1 -0
- package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-1znbL1Gv.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-DVhhgx8n.js} +1 -1
- package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-Bs4bFS1P.js} +1 -1
- package/dist/elements/chunks/scroll-button-Bet5NLc4.js +1 -0
- package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-C5Gh8qGC.js} +1 -1
- package/dist/elements/chunks/{settings-B8lmec4D.js → settings-5vmEFg0K.js} +1 -1
- package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-BWLB38kz.js} +1 -1
- package/dist/elements/chunks/{share-C3lTiYGt.js → share-BZ3G_DaB.js} +1 -1
- package/dist/elements/chunks/skeleton-DafPJQTp.js +1 -0
- package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
- package/dist/elements/chunks/source-D_qj_6uJ.js +1 -0
- package/dist/elements/chunks/{star-CSJtGxgv.js → star-Cuv7j2IE.js} +1 -1
- package/dist/elements/chunks/{store-CHyW95TP.js → store-C1StPHfW.js} +1 -1
- package/dist/elements/chunks/tasks-card-EyoBZfpA.js +1 -0
- package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D9gRSh34.js} +1 -1
- package/dist/elements/chunks/textarea-DuhtHfcm.js +1 -0
- package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-B9hwv43w.js} +1 -1
- package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-Bm3DUEDe.js} +1 -1
- package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-CDtW7L0z.js} +1 -1
- package/dist/elements/chunks/tooltip-eB226mGK.js +1 -0
- package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C1SNzTqK.js} +1 -1
- package/dist/elements/chunks/use-card-resolution-YZh2MvI5.js +1 -0
- package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Cj1F7D0l.js} +2 -2
- package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-T9gpt7QE.js} +1 -1
- package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-CxU9z-TE.js} +2 -2
- package/dist/elements/chunks/{video-B7vmenoC.js → video-CK4R7Rus.js} +1 -1
- package/dist/elements/chunks/x-D1qx0aLd.js +1 -0
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +7 -7
- package/dist/elements/define.d.ts +1 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/disclosure.d.ts +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +107 -0
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register.d.ts +8 -6
- 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/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slots.d.ts +16 -0
- 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/validate-messages.d.ts +25 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +2725 -214
- package/dist/index.d.ts +163 -125
- package/dist/index.js +6336 -5802
- package/dist/index.server.d.ts +5 -0
- package/dist/index.server.js +16913 -0
- package/dist/kai-provider.es.d.ts +5 -0
- package/dist/kai-provider.es.js +79 -77
- package/dist/kai.es.d.ts +5 -0
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +762 -148
- package/dist/llms/llms.txt +57 -5
- package/dist/mcp.es.js +4010 -770
- package/dist/primitives/card-component-types.d.ts +11 -0
- package/dist/primitives/card-data-types.d.ts +196 -0
- package/dist/primitives/card-host.d.ts +1 -1
- package/dist/primitives/card-recovery.d.ts +3 -3
- package/dist/primitives/card-registry.d.ts +4 -14
- package/dist/primitives/card-resolution.d.ts +1 -1
- package/dist/primitives/card-routing.d.ts +1 -1
- package/dist/primitives/card-tags.d.ts +11 -0
- package/dist/primitives/card-validate-cards.d.ts +57 -0
- package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
- package/dist/primitives/card-validate-schemas.d.ts +17 -0
- package/dist/primitives/card-validate.d.ts +24 -0
- package/dist/primitives/create-kai-chat.d.ts +3 -3
- package/dist/primitives/embed-providers.d.ts +1 -1
- package/dist/primitives/link-preview.d.ts +1 -1
- package/dist/primitives/message-feedback.d.ts +2 -2
- package/dist/primitives/toast-store.d.ts +1 -1
- package/dist/react/index.d.ts +717 -299
- package/dist/react/use-kai-chat.d.ts +8 -4
- package/dist/react.d.ts +5 -0
- package/dist/react.js +417 -316
- package/dist/register-impl-bzrZ44ft.js +145 -0
- package/dist/remote/host-embed.d.ts +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/provider-runtime.d.ts +2 -2
- package/dist/remote/provider.d.ts +2 -2
- package/dist/remote/validate.d.ts +1 -1
- package/dist/remote/wire.d.ts +1 -1
- package/dist/schemas/artifact.schema.json +92 -0
- package/dist/schemas/embed.schema.json +0 -2
- package/dist/schemas/from-tool-call.d.ts +48 -0
- package/dist/schemas/index.d.ts +48 -0
- package/dist/schemas/link.schema.json +0 -2
- package/dist/schemas/provider-subsets.d.ts +136 -0
- package/dist/schemas/registry.d.ts +140 -0
- package/dist/schemas/tool-defs.d.ts +121 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +717 -0
- package/dist/solid-BCJUQqwS.js +22750 -0
- package/dist/solid-BdENBcEq.js +20108 -0
- package/dist/solid.d.ts +66 -0
- package/dist/solid.js +261 -0
- package/dist/solid.server.d.ts +5 -0
- package/dist/solid.server.js +261 -0
- package/dist/state/index.d.ts +14 -4
- package/dist/state/messages.d.ts +8 -3
- package/dist/state/mock.d.ts +46 -0
- package/dist/state/parts.d.ts +85 -0
- package/dist/state/stream.d.ts +15 -9
- package/dist/state.d.ts +5 -0
- package/dist/state.js +253 -84
- package/dist/ui/action-icons.d.ts +1 -1
- package/dist/ui/collapsible.d.ts +11 -6
- package/dist/ui/dropdown.d.ts +47 -42
- package/dist/ui/pane-group.d.ts +1 -1
- package/dist/variant-aurora-1wMcXRUS.js +381 -0
- package/dist/variant-aurora-BKHHiOgL.js +391 -0
- package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-DOb_o4pC.js} +1 -1
- package/dist/{variant-custom-Dif-IV4u.js → variant-custom-CJGB0Kbw.js} +1 -1
- package/dist/variant-custom-Co0CkXJF.js +200 -0
- package/dist/variant-custom-DWSSgmd2.js +170 -0
- package/dist/variant-wave-C3-45cL6.js +218 -0
- package/dist/{variant-wave-DbfNdvSi.js → variant-wave-CEODq-9q.js} +1 -1
- package/dist/variant-wave-jdXBnCUZ.js +208 -0
- package/dist/wire/chunk.d.ts +171 -0
- package/dist/wire/consume.d.ts +26 -0
- package/dist/wire/encode.d.ts +148 -0
- package/dist/wire/formats/anthropic.d.ts +2 -0
- package/dist/wire/formats/openai.d.ts +5 -0
- package/dist/wire/index.d.ts +14 -0
- package/dist/wire/read.d.ts +26 -0
- package/dist/wire/sink-helpers.d.ts +21 -0
- package/dist/wire/sse.d.ts +33 -0
- package/dist/wire.d.ts +5 -53
- package/dist/wire.js +898 -0
- package/frameworks/react/index.tsx +183 -154
- package/frameworks/react/runtime.tsx +21 -1
- package/frameworks/react/use-kai-chat.tsx +11 -1
- package/llms-full.txt +762 -148
- package/llms.txt +57 -5
- package/package.json +65 -7
- package/src/agent-tooling/archetypes.ts +96 -0
- package/src/agent-tooling/integrations/anthropic.ts +412 -0
- package/src/agent-tooling/integrations/cloudflare.ts +136 -52
- package/src/agent-tooling/integrations/langgraph.ts +67 -13
- package/src/agent-tooling/integrations/mastra.ts +120 -12
- package/src/agent-tooling/integrations/mock.ts +43 -8
- package/src/agent-tooling/integrations/ollama.ts +61 -24
- package/src/agent-tooling/integrations/openai.ts +87 -0
- package/src/agent-tooling/integrations/openrouter.ts +56 -8
- package/src/agent-tooling/integrations/pi.ts +79 -23
- package/src/agent-tooling/integrations/pydantic-ai.ts +24 -2
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +84 -13
- package/src/agent-tooling/mcp/manifest.ts +206 -25
- package/src/agent-tooling/mcp/tools/debug.ts +13 -7
- package/src/agent-tooling/mcp/tools/reference.ts +285 -6
- package/src/agent-tooling/mcp/tools/scaffold.ts +3345 -675
- package/src/agent-tooling/registry.ts +17 -0
- package/src/agent-tooling/types.ts +229 -1
- package/src/components/artifact-card.tsx +120 -0
- package/src/components/audio-visualizer/index.tsx +9 -0
- package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
- package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
- package/src/components/card-fallback.tsx +42 -10
- package/src/components/card-renderer.tsx +146 -9
- package/src/components/chat-container.tsx +4 -0
- package/src/components/chat-thread.tsx +118 -51
- package/src/components/choice-card.tsx +17 -29
- package/src/components/composer.tsx +12 -2
- package/src/components/confirm-card.tsx +24 -20
- package/src/components/context.tsx +12 -4
- package/src/components/conversation-list.tsx +18 -2
- package/src/components/file-tree.tsx +11 -22
- package/src/components/file-upload.tsx +14 -8
- package/src/components/form.tsx +14 -47
- package/src/components/loader.tsx +39 -12
- package/src/components/message-skills.tsx +4 -0
- package/src/components/message.tsx +294 -46
- package/src/components/reasoning.tsx +92 -5
- package/src/components/response-compare-types.ts +25 -0
- package/src/components/response-compare.tsx +4 -9
- package/src/components/source.tsx +50 -24
- package/src/components/tasks-card.tsx +10 -33
- package/src/components/thread.tsx +84 -42
- package/src/components/toast.tsx +11 -5
- package/src/components/tool-classify.ts +26 -0
- package/src/components/tool-types.ts +31 -1
- package/src/elements/agent-card.tsx +11 -11
- package/src/elements/artifact.tsx +15 -7
- package/src/elements/attachments.tsx +6 -4
- package/src/elements/avatar.tsx +1 -1
- package/src/elements/button.tsx +3 -3
- package/src/elements/card.tsx +2 -2
- package/src/elements/cards.tsx +109 -14
- package/src/elements/chain-of-thought.tsx +14 -10
- package/src/elements/chat-scope-picker.tsx +9 -7
- package/src/elements/chat-types.ts +59 -6
- package/src/elements/chat-workspace.tsx +50 -9
- package/src/elements/chat.tsx +64 -3
- package/src/elements/choice.tsx +8 -8
- package/src/elements/coachmark.tsx +1 -1
- package/src/elements/command.tsx +8 -23
- package/src/elements/compare.tsx +7 -6
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +8 -8
- package/src/elements/confirm-card.tsx +9 -9
- package/src/elements/context-meter.tsx +4 -18
- package/src/elements/conversation-list.tsx +14 -9
- package/src/elements/default-input.tsx +3 -3
- package/src/elements/define.tsx +178 -43
- package/src/elements/editable-label.tsx +17 -2
- package/src/elements/element-data-types.ts +112 -0
- package/src/elements/element-meta.json +1596 -304
- package/src/elements/element-types.d.ts +2636 -211
- package/src/elements/feedback-bar.tsx +1 -1
- package/src/elements/file-tree.tsx +1 -1
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +14 -6
- package/src/elements/input.tsx +1 -1
- package/src/elements/kbd.tsx +1 -1
- package/src/elements/menu.tsx +10 -29
- package/src/elements/message-skills.tsx +7 -9
- package/src/elements/message.tsx +200 -32
- package/src/elements/model-switcher.tsx +5 -3
- package/src/elements/pane.tsx +1 -1
- package/src/elements/prompt-dock.tsx +8 -8
- package/src/elements/prompt-input.tsx +10 -10
- package/src/elements/prompt-suggestions.tsx +7 -4
- package/src/elements/reasoning.tsx +1 -1
- package/src/elements/register.ts +9 -0
- package/src/elements/resizable.globals.d.ts +36 -0
- package/src/elements/resizable.tsx +24 -13
- package/src/elements/response-stream.tsx +1 -1
- package/src/elements/scroll-button.tsx +1 -1
- package/src/elements/search.tsx +6 -1
- package/src/elements/segmented.tsx +5 -11
- package/src/elements/separator.tsx +1 -1
- package/src/elements/slots.ts +57 -24
- package/src/elements/source.tsx +21 -22
- package/src/elements/switch.tsx +3 -3
- package/src/elements/tasks.tsx +5 -4
- package/src/elements/thread.tsx +36 -4
- package/src/elements/toast.tsx +3 -2
- package/src/elements/tool.tsx +1 -1
- package/src/elements/validate-messages.ts +76 -0
- package/src/elements/voice-input.tsx +7 -7
- package/src/elements/voice-output.tsx +4 -4
- package/src/index.ts +141 -6
- package/src/primitives/card-component-types.ts +63 -0
- package/src/primitives/card-data-types.ts +312 -0
- package/src/primitives/card-recovery.ts +13 -2
- package/src/primitives/card-registry.tsx +34 -23
- package/src/primitives/card-schemas/artifact.schema.json +92 -0
- package/src/primitives/card-schemas/embed.schema.json +0 -2
- package/src/primitives/card-schemas/link.schema.json +0 -2
- package/src/primitives/card-tags.ts +61 -0
- package/src/primitives/card-validate-cards.ts +255 -0
- package/src/primitives/card-validate-generator.testlib.ts +38 -0
- package/src/primitives/card-validate-schemas.ts +58 -0
- package/src/primitives/card-validate.ts +48 -20
- package/src/primitives/create-tween.ts +54 -2
- package/src/primitives/message-feedback.ts +3 -2
- package/src/primitives/use-audio-analysis.ts +20 -1
- package/src/primitives/use-sequencer.ts +20 -1
- package/src/schemas/from-tool-call.ts +151 -0
- package/src/schemas/index.ts +340 -0
- package/src/schemas/provider-subsets.ts +538 -0
- package/src/schemas/registry.ts +355 -0
- package/src/schemas/tool-defs.ts +492 -0
- package/src/solid.ts +150 -0
- package/src/state/index.ts +27 -1
- package/src/state/messages.ts +21 -4
- package/src/state/mock.ts +211 -0
- package/src/state/parts.ts +295 -0
- package/src/state/stream.ts +56 -58
- package/src/ui/collapsible.tsx +17 -5
- package/src/ui/dropdown.tsx +46 -12
- package/src/ui/overlay.tsx +10 -4
- package/src/wire/chunk.ts +201 -0
- package/src/wire/consume.ts +440 -0
- package/src/wire/encode.ts +474 -0
- package/src/wire/formats/anthropic.ts +267 -0
- package/src/wire/formats/openai.ts +187 -0
- package/src/wire/index.ts +52 -0
- package/src/wire/read.ts +152 -0
- package/src/wire/sink-helpers.ts +50 -0
- package/src/wire/sse.ts +140 -0
- package/dist/create-tween-hvpmSuwW.js +0 -7
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
- package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
- package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
- package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
- package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
- package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
- package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
- package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
- package/dist/elements/chunks/define-D1din7Ld.js +0 -1
- package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
- package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
- package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
- package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
- package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
- package/dist/elements/chunks/slots--V19HwUb.js +0 -1
- package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
- package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
- package/dist/host-embed.d.ts +0 -33
- package/dist/kai-provider.d.ts +0 -2
- package/dist/origin.d.ts +0 -5
- package/dist/provider-runtime.d.ts +0 -27
- package/dist/register-impl-DQyejdfb.js +0 -145
- package/dist/validate.d.ts +0 -4
- package/dist/version.d.ts +0 -4
- package/src/components/component-meta.json +0 -4550
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/resizable.d.ts +0 -27
- package/src/elements/video-poster.jpg +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/primitives/card-validate-cards.ts
|
|
2
|
+
// Two-tier validation of a CardEnvelope's `data` against the card schema for its
|
|
3
|
+
// type, for the NATIVE dispatchers.
|
|
4
|
+
//
|
|
5
|
+
// "The card schema for its type" means one of the seven built-ins, OR a schema the
|
|
6
|
+
// app registered for a card type of its own. The second half arrived with
|
|
7
|
+
// `createCardRegistry` (src/schemas/registry.ts) and is the optional third argument
|
|
8
|
+
// to `validateCardData`. It exists because a developer's real generative UI is THEIR
|
|
9
|
+
// pricing table, not our confirm card, and a validator that covers only the built-ins
|
|
10
|
+
// leaves the one card the app actually cares about as the only unchecked one.
|
|
11
|
+
//
|
|
12
|
+
// THIS MIRRORS src/remote/provider-runtime.ts:139-147, DELIBERATELY.
|
|
13
|
+
// -----------------------------------------------------------------
|
|
14
|
+
// The remote (iframe) transport has validated every incoming envelope since the card
|
|
15
|
+
// contract landed: `validateAgainstSchema(renderer.schema, envelope.data)`, and on
|
|
16
|
+
// failure it renders a placeholder and emits `{ kind: 'error', cardId, message }`.
|
|
17
|
+
// The native path (the Solid `<CardRenderer>` and `<kai-cards>`) checked only that
|
|
18
|
+
// the TYPE was registered, so the same bad payload was caught in an iframe and
|
|
19
|
+
// rendered silently in-process. That asymmetry is what this file closes. The two
|
|
20
|
+
// transports are one behaviour; if you change the shape of the failure here, change
|
|
21
|
+
// it there too, and say so.
|
|
22
|
+
//
|
|
23
|
+
// WHY THIS IS ENFORCEMENT AND NOT A SAFETY NET
|
|
24
|
+
// --------------------------------------------
|
|
25
|
+
// The intended design was that `strict: true` tool definitions would constrain the
|
|
26
|
+
// model so envelopes arrived valid by construction. Measured against both providers'
|
|
27
|
+
// current strict subsets (see src/schemas/provider-subsets.ts), strict mode cannot
|
|
28
|
+
// express ANY of the seven card types today: `form`'s free-form `properties` object
|
|
29
|
+
// collapses to `{}` under the mandatory `additionalProperties: false`, and the other
|
|
30
|
+
// six trip `minLength`/`maxLength`/`format: "uri"`/`allOf`. So the schema guides the
|
|
31
|
+
// model and does not constrain it, and this check is the only thing standing between
|
|
32
|
+
// a wrong shape and the user's screen.
|
|
33
|
+
//
|
|
34
|
+
// WHY THE TIERING IS NOT POLITENESS
|
|
35
|
+
// ---------------------------------
|
|
36
|
+
// This library is pre-1.0 and already in use. Cards that render acceptably today
|
|
37
|
+
// must keep rendering, or turning validation on is a behaviour break dressed up as a
|
|
38
|
+
// fix. So a failure that means the card has NOTHING to render replaces the card with
|
|
39
|
+
// a diagnostic (hard), and a failure that means the card has content which is merely
|
|
40
|
+
// out of bounds renders the card and reports (soft). Both emit `{ kind: 'error' }`,
|
|
41
|
+
// because the developer needs to hear about both; only one changes what the user sees.
|
|
42
|
+
//
|
|
43
|
+
// THE SOFT TIER IS ON BY EVIDENCE, NOT BY CONFIDENCE
|
|
44
|
+
// --------------------------------------------------
|
|
45
|
+
// The plan (2026-08-11-emit-contract.md §9 risk 4) said validation is net-negative if
|
|
46
|
+
// it fires on cards that render fine today, and set the bar at ZERO before the soft
|
|
47
|
+
// tier could default on. That was measured, not assumed:
|
|
48
|
+
// docs/superpowers/specs/2026-08-12-soft-tier-corpus.md.
|
|
49
|
+
//
|
|
50
|
+
// 35 card envelopes recorded from five model configurations across both wires, six
|
|
51
|
+
// card types. ZERO trip the soft tier; zero trip the hard tier. The check was proven
|
|
52
|
+
// live on that same path by seven negative controls (real envelopes mutated into each
|
|
53
|
+
// tier's named failure, each required to trip), and a fifth of the envelopes were
|
|
54
|
+
// confirmed by replaying them through the real app and reading them back off
|
|
55
|
+
// `<kai-thread>.messages`. Margins were wide, not marginal: the worst `link.title`
|
|
56
|
+
// observed was 21 characters against a 300 limit.
|
|
57
|
+
//
|
|
58
|
+
// What that corpus does NOT cover, so it is not claimed: the structured-output path
|
|
59
|
+
// (model-authored envelope JSON, which nothing clamps), live `artifact` cards, and
|
|
60
|
+
// the soft constraints the spike's own tool projection clamps before they can be hit
|
|
61
|
+
// (`tone`/`provider` enums, the generated `id` fields, every `minItems`). If the soft
|
|
62
|
+
// tier ever does turn out to be noisy, that is where to look first, and the fix is the
|
|
63
|
+
// projection rather than the tier.
|
|
64
|
+
//
|
|
65
|
+
// WHAT IS ACTUALLY CHECKED
|
|
66
|
+
// ------------------------
|
|
67
|
+
// The keyword coverage is `validateAgainstSchema`'s, which is a lean subset: `type`,
|
|
68
|
+
// `const`, `enum`, `required`, `properties`, `items`, the numeric bounds,
|
|
69
|
+
// `minLength`/`maxLength`/`pattern`, `minItems`/`maxItems`/`uniqueItems`. It has no
|
|
70
|
+
// applicators and no `additionalProperties`. The consequences are written out per
|
|
71
|
+
// keyword in scripts/gen-card-validation-schemas.mjs's NOT_ENFORCED table, and the
|
|
72
|
+
// generated projection repeats them at the top of card-validate-schemas.ts. The
|
|
73
|
+
// short version, because it bears on what may be claimed: an `embed` missing both
|
|
74
|
+
// `id` and `url`, an `artifact` with neither `src` nor `files`, a `link.url` that is
|
|
75
|
+
// not a URL, and an undeclared extra property are all NOT caught here.
|
|
76
|
+
|
|
77
|
+
import { validateAgainstSchema, type JsonSchema, type ValidationIssue } from './card-validate';
|
|
78
|
+
import { CARD_VALIDATION_SCHEMAS, type ValidatedCardType } from './card-validate-schemas';
|
|
79
|
+
|
|
80
|
+
export type { ValidatedCardType } from './card-validate-schemas';
|
|
81
|
+
export { CARD_VALIDATION_SCHEMAS } from './card-validate-schemas';
|
|
82
|
+
|
|
83
|
+
/** How badly a failed constraint hurts the rendered card. */
|
|
84
|
+
export type CardValidationTier = 'hard' | 'soft';
|
|
85
|
+
|
|
86
|
+
export interface CardValidationIssue extends ValidationIssue {
|
|
87
|
+
tier: CardValidationTier;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CardValidationReport {
|
|
91
|
+
/** True when nothing failed. A SOFT-only report is not `ok`. */
|
|
92
|
+
ok: boolean;
|
|
93
|
+
/** `hard` if any issue is hard, else `soft` if any issue is soft, else `ok`. */
|
|
94
|
+
tier: CardValidationTier | 'ok';
|
|
95
|
+
/** Every failure, hard and soft, in the validator's order. */
|
|
96
|
+
issues: CardValidationIssue[];
|
|
97
|
+
/** The hard issues only. Non-empty exactly when `tier === 'hard'`. */
|
|
98
|
+
hard: CardValidationIssue[];
|
|
99
|
+
/** One line naming the failing field paths. Safe to render and to log. */
|
|
100
|
+
summary: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Keyword to tier.
|
|
105
|
+
*
|
|
106
|
+
* The line is PRESENCE versus BOUNDS, and it is drawn per keyword rather than per
|
|
107
|
+
* card because the dispatcher does not know what any given card component does with
|
|
108
|
+
* a field.
|
|
109
|
+
*
|
|
110
|
+
* hard the field the card is built around is absent, or is a different kind of
|
|
111
|
+
* thing entirely. There is nothing to draw.
|
|
112
|
+
* soft the field is present and of the right kind; there is just too much of it,
|
|
113
|
+
* too little, or it is outside a declared range. Something renders.
|
|
114
|
+
*
|
|
115
|
+
* THE TWO ENTRIES THAT ARE JUDGEMENT CALLS, RECORDED BECAUSE THEY ARE ARGUABLE:
|
|
116
|
+
*
|
|
117
|
+
* `minItems` is HARD while `minLength` is SOFT, and they look like the same shape.
|
|
118
|
+
* The difference is what is lost. `confirm.actions` has `minItems: 1`, so violating
|
|
119
|
+
* it means a decision card with no decisions: the entire interactive surface is
|
|
120
|
+
* gone, and what renders is empty chrome that looks like a bug in this library. The
|
|
121
|
+
* plan names that case specifically as the one the diagnostic exists for. Violating
|
|
122
|
+
* `minLength: 1` on `actions[].label` means ONE blank button label; the button is
|
|
123
|
+
* still there, still focusable, and still emits the correct `id`. Replacing a whole
|
|
124
|
+
* card with a diagnostic over one blank label is the worse outcome for the user.
|
|
125
|
+
*
|
|
126
|
+
* `type` is HARD everywhere, including on optional scalars where a truthy wrong type
|
|
127
|
+
* would have rendered fine (`dismissible: "yes"`). That is deliberately blunt. A
|
|
128
|
+
* per-field carve-out would need the dispatcher to know which fields each card
|
|
129
|
+
* component treats loosely, which it cannot, and a wrong `type` is the single
|
|
130
|
+
* strongest signal that the model produced something other than what was asked for.
|
|
131
|
+
*/
|
|
132
|
+
const TIER_BY_KEYWORD: Readonly<Record<string, CardValidationTier>> = Object.freeze({
|
|
133
|
+
// hard: nothing to render
|
|
134
|
+
type: 'hard',
|
|
135
|
+
required: 'hard',
|
|
136
|
+
const: 'hard', // pins a discriminator (`form.data.type` must be `"object"`)
|
|
137
|
+
minItems: 'hard',
|
|
138
|
+
// soft: content is present, bounds are not respected
|
|
139
|
+
enum: 'soft', // cards fall back to their default variant for an unknown value
|
|
140
|
+
maxItems: 'soft',
|
|
141
|
+
minLength: 'soft',
|
|
142
|
+
maxLength: 'soft',
|
|
143
|
+
pattern: 'soft',
|
|
144
|
+
minimum: 'soft',
|
|
145
|
+
maximum: 'soft',
|
|
146
|
+
exclusiveMinimum: 'soft',
|
|
147
|
+
exclusiveMaximum: 'soft',
|
|
148
|
+
uniqueItems: 'soft',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The tier for a keyword the table does not list.
|
|
153
|
+
*
|
|
154
|
+
* SOFT, on purpose. An unlisted keyword can only appear by someone adding a check to
|
|
155
|
+
* `validateAgainstSchema` and not tiering it, and the failure mode of guessing wrong
|
|
156
|
+
* is asymmetric: guessing soft under-reacts on one new keyword until someone tiers
|
|
157
|
+
* it, while guessing hard would start blanking cards that render fine today, which
|
|
158
|
+
* is precisely the regression this design exists to prevent. `card-validate-cards.test.ts`
|
|
159
|
+
* asserts the table covers every keyword the validator implements, so this default
|
|
160
|
+
* is a fallback for a tree that is already failing its own test, not a resting state.
|
|
161
|
+
*/
|
|
162
|
+
const DEFAULT_TIER: CardValidationTier = 'soft';
|
|
163
|
+
|
|
164
|
+
/** True when this envelope type has a BUILT-IN projected schema to validate against. */
|
|
165
|
+
export function isValidatedCardType(type: string): type is ValidatedCardType {
|
|
166
|
+
return Object.prototype.hasOwnProperty.call(CARD_VALIDATION_SCHEMAS, type);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The schema to check `type` against, consumer-supplied first.
|
|
171
|
+
*
|
|
172
|
+
* A consumer schema WINS over a built-in of the same name, matching
|
|
173
|
+
* `mergeCardTags`/`mergeCardComponents`, where the consumer's entry is spread over
|
|
174
|
+
* ours. Anything else would mean a developer who replaced our confirm card with their
|
|
175
|
+
* own still had their data checked against our shape.
|
|
176
|
+
*/
|
|
177
|
+
function schemaFor(
|
|
178
|
+
type: string,
|
|
179
|
+
extra: Readonly<Record<string, JsonSchema>> | undefined,
|
|
180
|
+
): JsonSchema | null {
|
|
181
|
+
if (extra && Object.prototype.hasOwnProperty.call(extra, type)) {
|
|
182
|
+
const custom = extra[type];
|
|
183
|
+
if (custom && typeof custom === 'object') return custom;
|
|
184
|
+
}
|
|
185
|
+
return isValidatedCardType(type) ? CARD_VALIDATION_SCHEMAS[type] : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function summarize(issues: CardValidationIssue[]): string {
|
|
189
|
+
// Name the FIELD PATHS. "invalid card data" on its own sends a developer to a
|
|
190
|
+
// debugger; "(root).actions: fewer than minItems 1" sends them to the line.
|
|
191
|
+
const shown = issues.slice(0, 3).map((i) => i.message);
|
|
192
|
+
const rest = issues.length - shown.length;
|
|
193
|
+
return shown.join('; ') + (rest > 0 ? `; and ${rest} more` : '');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Validate a card envelope's `data` against the schema for `type`.
|
|
198
|
+
*
|
|
199
|
+
* Returns `null` when there is no schema for the type at all. Returning `null` rather
|
|
200
|
+
* than an empty pass keeps "we checked and it was fine" distinguishable from "there
|
|
201
|
+
* was nothing to check", so a caller cannot report the second as the first.
|
|
202
|
+
*
|
|
203
|
+
* @param schemas consumer-registered schemas by card type, normally
|
|
204
|
+
* `registry.validationSchemas` from `createCardRegistry`. Without it this function
|
|
205
|
+
* knows only the seven built-ins, which would mean a developer's own pricing-table
|
|
206
|
+
* card, registered perfectly legally through `cardTypes`, is the one card in the app
|
|
207
|
+
* nothing checks. The custom entry wins over a built-in of the same name; see
|
|
208
|
+
* {@link schemaFor}.
|
|
209
|
+
*
|
|
210
|
+
* A consumer schema is used AS AUTHORED rather than through the build-time lean
|
|
211
|
+
* projection the built-ins get, because that projection is a bundle-size measure and
|
|
212
|
+
* cannot run over a schema this package has never seen. The practical difference is
|
|
213
|
+
* only weight: `validateAgainstSchema` ignores every keyword it does not implement, so
|
|
214
|
+
* a custom schema is checked to exactly the same depth as a built-in, with the same
|
|
215
|
+
* NOT_ENFORCED gaps (no `allOf`/`anyOf`/`oneOf`/`if`, no `$ref`, no
|
|
216
|
+
* `additionalProperties`, no `format`).
|
|
217
|
+
*/
|
|
218
|
+
export function validateCardData(
|
|
219
|
+
type: string,
|
|
220
|
+
data: unknown,
|
|
221
|
+
schemas?: Readonly<Record<string, JsonSchema>>,
|
|
222
|
+
): CardValidationReport | null {
|
|
223
|
+
const schema = schemaFor(type, schemas);
|
|
224
|
+
if (schema === null) return null;
|
|
225
|
+
const result = validateAgainstSchema(schema, data);
|
|
226
|
+
if (result.valid) return { ok: true, tier: 'ok', issues: [], hard: [], summary: '' };
|
|
227
|
+
|
|
228
|
+
const issues: CardValidationIssue[] = result.issues.map((issue) => ({
|
|
229
|
+
...issue,
|
|
230
|
+
tier: TIER_BY_KEYWORD[issue.keyword] ?? DEFAULT_TIER,
|
|
231
|
+
}));
|
|
232
|
+
const hard = issues.filter((i) => i.tier === 'hard');
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
tier: hard.length > 0 ? 'hard' : 'soft',
|
|
236
|
+
issues,
|
|
237
|
+
hard,
|
|
238
|
+
summary: summarize(hard.length > 0 ? hard : issues),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The message both native dispatchers put on the `error` CardEvent.
|
|
244
|
+
*
|
|
245
|
+
* Spelled once, here, and prefixed `invalid card data:` to match what
|
|
246
|
+
* provider-runtime.ts already emits for the remote transport, so a consumer's
|
|
247
|
+
* `policy.onError` sees the same string shape regardless of which transport the card
|
|
248
|
+
* arrived through.
|
|
249
|
+
*/
|
|
250
|
+
export function cardValidationMessage(report: CardValidationReport): string {
|
|
251
|
+
return `invalid card data (${report.tier}): ${report.summary}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Exposed for the tier-coverage test, which asserts it against the validator. */
|
|
255
|
+
export const TIERS_BY_KEYWORD = TIER_BY_KEYWORD;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Test-only loader for scripts/gen-card-validation-schemas.mjs.
|
|
2
|
+
//
|
|
3
|
+
// The specifier is a runtime variable on purpose, the same way
|
|
4
|
+
// src/elements/inline-element-types.test.ts loads gen-element-types.mjs: the script
|
|
5
|
+
// is plain ESM outside `include`, and a static import would need `allowJs`.
|
|
6
|
+
//
|
|
7
|
+
// The interface below is a hand-written type for a JavaScript module, which is a
|
|
8
|
+
// restatement and therefore a drift risk. It is kept as narrow as the tests need,
|
|
9
|
+
// and the drift it could hide is the harmless direction: a wrong shape here fails at
|
|
10
|
+
// runtime in the test that uses it, immediately, rather than passing while covering
|
|
11
|
+
// nothing. The alternative (a sibling `.d.mts`) would be a WIDER restatement of the
|
|
12
|
+
// same module with the same risk.
|
|
13
|
+
import { resolve, dirname } from 'node:path';
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
|
+
|
|
16
|
+
/** One keyword the projection could not account for, with where it was found. */
|
|
17
|
+
export interface UnclassifiedKeyword {
|
|
18
|
+
path: string;
|
|
19
|
+
keyword: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CardValidationGenerator {
|
|
23
|
+
CARD_TYPES: string[];
|
|
24
|
+
STRIPPED: Readonly<Record<string, string>>;
|
|
25
|
+
NOT_ENFORCED: Readonly<Record<string, string>>;
|
|
26
|
+
OUT_FILE: string;
|
|
27
|
+
enforcedKeywords(source?: string): Set<string>;
|
|
28
|
+
scanUnclassified(doc: unknown, enforced: Set<string>): UnclassifiedKeyword[];
|
|
29
|
+
projectSchema(doc: unknown, enforced: Set<string>): Record<string, unknown> | undefined;
|
|
30
|
+
build(): { enforced: Set<string>; projections: Record<string, unknown>; source: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
34
|
+
|
|
35
|
+
export async function loadGenerator(): Promise<CardValidationGenerator> {
|
|
36
|
+
const url = pathToFileURL(resolve(PKG_ROOT, 'scripts/gen-card-validation-schemas.mjs')).href;
|
|
37
|
+
return (await import(/* @vite-ignore */ url)) as CardValidationGenerator;
|
|
38
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-card-validation-schemas.mjs. Do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// Run `node scripts/gen-card-validation-schemas.mjs` (or any build, which does it in
|
|
4
|
+
// `prebuild`) to regenerate. `card-validate-schemas.sync.test.ts` fails if this file
|
|
5
|
+
// and the authored schemas in src/primitives/card-schemas/ disagree.
|
|
6
|
+
//
|
|
7
|
+
// WHAT THIS IS
|
|
8
|
+
// ------------
|
|
9
|
+
// The 7 card-data schemas with everything the browser cannot act on removed:
|
|
10
|
+
// `$schema`, `$id`, `title`, `description`, `default`, every `x-*` hint, and every
|
|
11
|
+
// keyword `validateAgainstSchema` does not implement. The authored documents remain
|
|
12
|
+
// the source of truth and ship unchanged at `@kitn.ai/ui/schemas` for the server.
|
|
13
|
+
//
|
|
14
|
+
// ENFORCED here, read out of validateAgainstSchema's body by the generator:
|
|
15
|
+
// const, enum, exclusiveMaximum, exclusiveMinimum, items, maxItems, maxLength, maximum, minItems, minLength, minimum, pattern, properties, required, type, uniqueItems
|
|
16
|
+
//
|
|
17
|
+
// NOT ENFORCED, and therefore NOT in this projection. Each is a real constraint the
|
|
18
|
+
// authored schema makes that nothing checks at render time:
|
|
19
|
+
// `additionalProperties`: unknown properties are NOT rejected. `artifact`, `embed` and `link` set `additionalProperties: false`; a model adding a field they do not declare renders exactly as it does today. Implementing it would make every extra field a validation failure on cards that render fine, which is the regression the tiering exists to avoid.
|
|
20
|
+
// `allOf`: conditional requirements are NOT checked. This is `embed`'s entire provider/url rule (generic needs `url`; youtube/vimeo need `id` or `url`), so an embed card missing both validates here and renders an empty player. Fixing it means implementing applicators, not widening this table.
|
|
21
|
+
// `anyOf`: branch alternatives are NOT checked. This is `artifact`'s root `[{required:["src"]},{required:["files"]}]` rule, so an artifact card carrying NEITHER a `src` nor a `files` list validates here and renders empty chrome. It is also half of `embed`'s youtube/vimeo `id`-or-`url` rule.
|
|
22
|
+
// `oneOf`: exactly-one-of is NOT checked. `artifact.height` is `oneOf: [number, string]`, and because that subschema has no other keyword it drops out of the projection entirely: a `height` of `true` or `{}` is not caught.
|
|
23
|
+
// `if`: the conditional applicator is NOT evaluated; see `allOf`.
|
|
24
|
+
// `then`: the conditional consequent is NOT evaluated; see `allOf`.
|
|
25
|
+
// `else`: the conditional alternative is NOT evaluated; see `allOf`.
|
|
26
|
+
// `not`: negation is NOT evaluated; a value the schema explicitly forbids validates here. Unused by any card schema today.
|
|
27
|
+
// `format`: string formats are NOT checked. `link`, `embed` and `artifact` use `format: "uri"`; a non-URL string in `link.url` validates here. The cards themselves guard what they will actually navigate to or frame, which is where a URL check belongs (a validator that only warns cannot stop a bad origin loading).
|
|
28
|
+
// `$ref`: references are NOT resolved; a `$ref`-ed subschema is not checked at all. No card schema uses one today.
|
|
29
|
+
// `$defs`: definition blocks are NOT reachable without `$ref`, so their contents are never checked.
|
|
30
|
+
// `definitions`: the draft-07 spelling of `$defs`; its contents are NOT checked either, for the same reason. Listed separately so a schema written in the older dialect cannot slip past this table.
|
|
31
|
+
import type { JsonSchema } from './card-validate';
|
|
32
|
+
|
|
33
|
+
/** The built-in `CardEnvelope.type` values that have a schema to validate against. */
|
|
34
|
+
export type ValidatedCardType = "artifact" | "choice" | "confirm" | "embed" | "form" | "link" | "tasks";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* One widening cast, at the module boundary, for the same reason
|
|
38
|
+
* src/schemas/index.ts has one: TypeScript infers `string` for a `"type"` member
|
|
39
|
+
* written as a literal in an object literal, while `JsonSchema['type']` is a union
|
|
40
|
+
* of string literals, so these documents are not assignable without it. The
|
|
41
|
+
* alternative (`as const` throughout) makes every `required` array `readonly`,
|
|
42
|
+
* which `JsonSchema` does not accept either.
|
|
43
|
+
*
|
|
44
|
+
* The cast is not a claim about the contents. What it would otherwise hide is
|
|
45
|
+
* checked by the sync test, which re-runs this generator and compares byte for byte.
|
|
46
|
+
*/
|
|
47
|
+
export const CARD_VALIDATION_SCHEMAS = {
|
|
48
|
+
"artifact": {"type":"object","properties":{"src":{"type":"string"},"files":{"type":"array","maxItems":500,"items":{"type":"object","required":["path"],"properties":{"path":{"type":"string","maxLength":1024},"url":{"type":"string"},"code":{"type":"string"},"language":{"type":"string","maxLength":40},"type":{"type":"string","enum":["html","pdf","image","other"]},"additions":{"type":"integer","minimum":0},"deletions":{"type":"integer","minimum":0},"status":{"type":"string","enum":["added","modified","deleted","renamed","untracked"]}}}},"tab":{"type":"string","enum":["preview","code"]},"activeFile":{"type":"string","maxLength":1024},"displayUrl":{"type":"string","maxLength":2048}}},
|
|
49
|
+
"choice": {"type":"object","required":["options"],"properties":{"prompt":{"type":"string"},"submitLabel":{"type":"string"},"options":{"type":"array","minItems":1,"items":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"description":{"type":"string"},"media":{"type":"object","properties":{"image":{"type":"string"},"imageAlt":{"type":"string"},"icon":{"type":"string"}}},"meta":{"type":"string"},"recommended":{"type":"boolean"},"disabled":{"type":"boolean"}}}},"dismissible":{"type":"boolean"}}},
|
|
50
|
+
"confirm": {"type":"object","required":["actions"],"properties":{"heading":{"type":"string"},"body":{"type":"string"},"tone":{"type":"string","enum":["default","warning","danger"]},"actions":{"type":"array","minItems":1,"maxItems":4,"items":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"style":{"type":"string","enum":["primary","default","destructive"]},"default":{"type":"boolean"}}}},"dismissible":{"type":"boolean"}}},
|
|
51
|
+
"embed": {"type":"object","required":["provider"],"properties":{"provider":{"type":"string","enum":["youtube","vimeo","generic"]},"id":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"url":{"type":"string"},"title":{"type":"string","maxLength":300},"poster":{"type":"string"},"start":{"type":"integer","minimum":0},"aspectRatio":{"type":"string","enum":["16:9","4:3","1:1","9:16"]}}},
|
|
52
|
+
"form": {"type":"object","required":["type","properties"],"properties":{"type":{"const":"object"},"title":{"type":"string"},"description":{"type":"string"},"required":{"type":"array","items":{"type":"string"}},"properties":{"type":"object"},"x-kai-order":{"type":"array","items":{"type":"string"}},"x-kai-inlineMax":{"type":"integer","minimum":1},"x-kai-submitLabel":{"type":"string"},"x-kai-dismissible":{"type":"boolean"},"x-kai-actions":{"type":"array","items":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"variant":{"enum":["default","ghost","outline"]}}}}}},
|
|
53
|
+
"link": {"type":"object","required":["url"],"properties":{"url":{"type":"string"},"title":{"type":"string","maxLength":300},"description":{"type":"string","maxLength":1000},"image":{"type":"string"},"imageAlt":{"type":"string","maxLength":300},"favicon":{"type":"string"},"domain":{"type":"string","maxLength":253},"siteName":{"type":"string","maxLength":200}}},
|
|
54
|
+
"tasks": {"type":"object","required":["tasks"],"properties":{"mode":{"type":"string","enum":["select","progress"]},"heading":{"type":"string"},"tasks":{"type":"array","minItems":1,"items":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"description":{"type":"string"},"checked":{"type":"boolean"},"disabled":{"type":"boolean"}}}},"selectAll":{"type":"boolean"},"confirmLabel":{"type":"string"},"allowEmpty":{"type":"boolean"},"min":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":1},"dismissible":{"type":"boolean"}}},
|
|
55
|
+
} as unknown as Readonly<Record<ValidatedCardType, JsonSchema>>;
|
|
56
|
+
|
|
57
|
+
/** The card types this projection covers, in a stable order. */
|
|
58
|
+
export const VALIDATED_CARD_TYPES: readonly ValidatedCardType[] = ["artifact","choice","confirm","embed","form","link","tasks"];
|
|
@@ -20,9 +20,34 @@ export interface JsonSchema {
|
|
|
20
20
|
[key: `x-${string}`]: unknown;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* One failed constraint, with the KEYWORD that failed kept separate from the prose.
|
|
25
|
+
*
|
|
26
|
+
* `errors` (the string form) is what every existing caller reads and it is unchanged.
|
|
27
|
+
* This structured twin exists because the native card dispatcher has to TIER a
|
|
28
|
+
* failure (a missing `required` field means the card cannot render, a `maxItems`
|
|
29
|
+
* overrun means it renders five buttons instead of four), and the only honest way to
|
|
30
|
+
* tell those apart is the keyword. Re-parsing the prose to recover it would be a
|
|
31
|
+
* second, silently-drifting copy of the same fact.
|
|
32
|
+
*/
|
|
33
|
+
export interface ValidationIssue {
|
|
34
|
+
/** Where, in readable form: `(root)`, `(root).actions`, `(root).actions[0].id`. */
|
|
35
|
+
path: string;
|
|
36
|
+
/** The JSON Schema keyword that failed. `required` for a missing property. */
|
|
37
|
+
keyword: string;
|
|
38
|
+
/** The human-readable message; identical to the matching entry in `errors`. */
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
export interface ValidationResult {
|
|
24
43
|
valid: boolean;
|
|
25
44
|
errors: string[];
|
|
45
|
+
/**
|
|
46
|
+
* The same failures as `errors`, one-for-one and in the same order, with the
|
|
47
|
+
* keyword and path kept as data. Added alongside `errors` rather than replacing
|
|
48
|
+
* it so no existing caller changes.
|
|
49
|
+
*/
|
|
50
|
+
issues: ValidationIssue[];
|
|
26
51
|
}
|
|
27
52
|
|
|
28
53
|
function typeOf(v: unknown): string {
|
|
@@ -42,46 +67,49 @@ function matchesType(v: unknown, t: NonNullable<JsonSchema['type']>): boolean {
|
|
|
42
67
|
}
|
|
43
68
|
}
|
|
44
69
|
|
|
45
|
-
function walk(schema: JsonSchema, value: unknown, path: string,
|
|
70
|
+
function walk(schema: JsonSchema, value: unknown, path: string, issues: ValidationIssue[]): void {
|
|
46
71
|
const at = path || '(root)';
|
|
72
|
+
const fail = (keyword: string, message: string, where = at): void => {
|
|
73
|
+
issues.push({ path: where, keyword, message: `${where}: ${message}` });
|
|
74
|
+
};
|
|
47
75
|
if (schema.type && !matchesType(value, schema.type)) {
|
|
48
|
-
|
|
76
|
+
fail('type', `expected ${schema.type}, got ${typeOf(value)}`);
|
|
49
77
|
return; // type wrong → downstream checks are meaningless
|
|
50
78
|
}
|
|
51
79
|
if ('const' in schema && JSON.stringify(value) !== JSON.stringify(schema.const)) {
|
|
52
|
-
|
|
80
|
+
fail('const', `must equal const ${JSON.stringify(schema.const)}`);
|
|
53
81
|
}
|
|
54
82
|
if (schema.enum && !schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))) {
|
|
55
|
-
|
|
83
|
+
fail('enum', `must be one of ${JSON.stringify(schema.enum)}`);
|
|
56
84
|
}
|
|
57
85
|
if (typeof value === 'number') {
|
|
58
|
-
if (schema.minimum !== undefined && value < schema.minimum)
|
|
59
|
-
if (schema.maximum !== undefined && value > schema.maximum)
|
|
60
|
-
if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)
|
|
61
|
-
if (schema.exclusiveMaximum !== undefined && value >= schema.exclusiveMaximum)
|
|
86
|
+
if (schema.minimum !== undefined && value < schema.minimum) fail('minimum', `< minimum ${schema.minimum}`);
|
|
87
|
+
if (schema.maximum !== undefined && value > schema.maximum) fail('maximum', `> maximum ${schema.maximum}`);
|
|
88
|
+
if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum) fail('exclusiveMinimum', '<= exclusiveMinimum');
|
|
89
|
+
if (schema.exclusiveMaximum !== undefined && value >= schema.exclusiveMaximum) fail('exclusiveMaximum', '>= exclusiveMaximum');
|
|
62
90
|
}
|
|
63
91
|
if (typeof value === 'string') {
|
|
64
|
-
if (schema.minLength !== undefined && value.length < schema.minLength)
|
|
65
|
-
if (schema.maxLength !== undefined && value.length > schema.maxLength)
|
|
66
|
-
if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value))
|
|
92
|
+
if (schema.minLength !== undefined && value.length < schema.minLength) fail('minLength', `shorter than minLength ${schema.minLength}`);
|
|
93
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength) fail('maxLength', `longer than maxLength ${schema.maxLength}`);
|
|
94
|
+
if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) fail('pattern', 'does not match pattern');
|
|
67
95
|
}
|
|
68
96
|
if (Array.isArray(value)) {
|
|
69
|
-
if (schema.minItems !== undefined && value.length < schema.minItems)
|
|
70
|
-
if (schema.maxItems !== undefined && value.length > schema.maxItems)
|
|
97
|
+
if (schema.minItems !== undefined && value.length < schema.minItems) fail('minItems', `fewer than minItems ${schema.minItems}`);
|
|
98
|
+
if (schema.maxItems !== undefined && value.length > schema.maxItems) fail('maxItems', `more than maxItems ${schema.maxItems}`);
|
|
71
99
|
if (schema.uniqueItems) {
|
|
72
100
|
const seen = new Set(value.map((v) => JSON.stringify(v)));
|
|
73
|
-
if (seen.size !== value.length)
|
|
101
|
+
if (seen.size !== value.length) fail('uniqueItems', 'items not unique');
|
|
74
102
|
}
|
|
75
|
-
if (schema.items) value.forEach((v, i) => walk(schema.items!, v, `${at}[${i}]`,
|
|
103
|
+
if (schema.items) value.forEach((v, i) => walk(schema.items!, v, `${at}[${i}]`, issues));
|
|
76
104
|
}
|
|
77
105
|
if (typeOf(value) === 'object') {
|
|
78
106
|
const obj = value as Record<string, unknown>;
|
|
79
107
|
for (const key of schema.required ?? []) {
|
|
80
|
-
if (!(key in obj) || obj[key] === undefined)
|
|
108
|
+
if (!(key in obj) || obj[key] === undefined) fail('required', 'required', `${at}.${key}`);
|
|
81
109
|
}
|
|
82
110
|
if (schema.properties) {
|
|
83
111
|
for (const [key, sub] of Object.entries(schema.properties)) {
|
|
84
|
-
if (key in obj && obj[key] !== undefined) walk(sub, obj[key], `${at}.${key}`,
|
|
112
|
+
if (key in obj && obj[key] !== undefined) walk(sub, obj[key], `${at}.${key}`, issues);
|
|
85
113
|
}
|
|
86
114
|
}
|
|
87
115
|
}
|
|
@@ -89,7 +117,7 @@ function walk(schema: JsonSchema, value: unknown, path: string, errors: string[]
|
|
|
89
117
|
|
|
90
118
|
/** Validate `value` against the lean JSON-Schema subset. */
|
|
91
119
|
export function validateAgainstSchema(schema: JsonSchema, value: unknown): ValidationResult {
|
|
92
|
-
const
|
|
93
|
-
walk(schema, value, '',
|
|
94
|
-
return { valid:
|
|
120
|
+
const issues: ValidationIssue[] = [];
|
|
121
|
+
walk(schema, value, '', issues);
|
|
122
|
+
return { valid: issues.length === 0, errors: issues.map((i) => i.message), issues };
|
|
95
123
|
}
|
|
@@ -50,8 +50,22 @@ function cubicBezier(x1: number, y1: number, x2: number, y2: number): (t: number
|
|
|
50
50
|
|
|
51
51
|
// Matches motion's named curves so the visualizers feel like their LiveKit
|
|
52
52
|
// counterparts rather than a plausible-looking approximation.
|
|
53
|
+
//
|
|
54
|
+
// Every easing here is TOTAL: it clamps `t` into [0, 1] itself rather than
|
|
55
|
+
// trusting its caller. `cubicBezier` already did (its Newton solve needs `t`
|
|
56
|
+
// in range to converge); `linear` was the bare identity and passed anything
|
|
57
|
+
// straight through. `step()` below clamps the top but not the bottom, so a
|
|
58
|
+
// frame timestamp EARLIER than the origin `to()` captured yields a negative
|
|
59
|
+
// `t` and, through an unclamped easing, a tween that runs backwards.
|
|
60
|
+
//
|
|
61
|
+
// That timestamp cannot occur in a browser -- requestAnimationFrame's
|
|
62
|
+
// timestamp and `performance.now()` share a time origin per spec -- so this is
|
|
63
|
+
// robustness, not a live bug. It does occur under vitest + jsdom, where the
|
|
64
|
+
// two do not share an origin. The clamp lives in the easings rather than at
|
|
65
|
+
// the `step()` call site so the tests can drive a negative `t` through each
|
|
66
|
+
// curve individually and prove which ones guard.
|
|
53
67
|
const EASINGS = {
|
|
54
|
-
linear: (t: number) => t,
|
|
68
|
+
linear: (t: number) => Math.min(1, Math.max(0, t)),
|
|
55
69
|
easeIn: cubicBezier(0.42, 0, 1, 1),
|
|
56
70
|
easeOut: cubicBezier(0, 0, 0.58, 1),
|
|
57
71
|
easeInOut: cubicBezier(0.42, 0, 0.58, 1),
|
|
@@ -63,8 +77,15 @@ const EASINGS = {
|
|
|
63
77
|
* `bounce` maps to the damping ratio: 0 is critically damped (no overshoot),
|
|
64
78
|
* higher values overshoot more. Matches the feel of motion's spring defaults
|
|
65
79
|
* closely enough for shader uniforms, without the dependency.
|
|
80
|
+
*
|
|
81
|
+
* `t` is clamped at 0 for the same reason the named easings are (see EASINGS),
|
|
82
|
+
* and the failure here is far louder than theirs: the decay term is
|
|
83
|
+
* `Math.exp(-zeta * omega * t)`, so a negative `t` makes it GROW rather than
|
|
84
|
+
* decay. At t = -0.25 with bounce 0.4 this returned ~5.9 instead of something
|
|
85
|
+
* in [0, ~1.3]. No top clamp: overshooting past 1 is what a spring is for.
|
|
66
86
|
*/
|
|
67
87
|
function spring(t: number, bounce: number): number {
|
|
88
|
+
t = Math.max(0, t);
|
|
68
89
|
const zeta = Math.max(0.05, 1 - Math.min(0.95, bounce));
|
|
69
90
|
const omega = 10;
|
|
70
91
|
const damped = omega * Math.sqrt(Math.max(0, 1 - zeta * zeta));
|
|
@@ -108,8 +129,39 @@ export function createTween(initial: number): {
|
|
|
108
129
|
let raf = 0;
|
|
109
130
|
let disposed = false;
|
|
110
131
|
|
|
132
|
+
/**
|
|
133
|
+
* `cancelAnimationFrame`, captured at SETUP.
|
|
134
|
+
*
|
|
135
|
+
* `stop()` below runs at dispose, and dispose is not guaranteed to happen
|
|
136
|
+
* while the page that mounted this tween is still standing -- a host can tear
|
|
137
|
+
* its DOM globals down in between (`component-register`'s
|
|
138
|
+
* `disconnectedCallback` defers a microtask; a test environment deletes the
|
|
139
|
+
* globals synchronously right after detaching). A bare `cancelAnimationFrame`
|
|
140
|
+
* there throws from a promise nobody holds, so it surfaces as an unhandled
|
|
141
|
+
* rejection that fails a run in which every test passed. See
|
|
142
|
+
* tests/components/teardown-without-dom-globals.test.tsx.
|
|
143
|
+
*
|
|
144
|
+
* The FUNCTION, not the view. The `const win = window` capture that fixes a
|
|
145
|
+
* bare `document` does nothing here: `window === globalThis` -- measured, in
|
|
146
|
+
* jsdom and in real Chromium/WebKit alike -- and the teardown deletes these
|
|
147
|
+
* keys off that very object, so `win.cancelAnimationFrame` is undefined by
|
|
148
|
+
* the time cleanup runs. It only trades the ReferenceError for a TypeError.
|
|
149
|
+
* `.bind` pins the receiver the WebIDL operation is specified on; Chromium
|
|
150
|
+
* and WebKit both accept a detached call (measured), so the bind is belt and
|
|
151
|
+
* braces against an engine that does not, at zero cost.
|
|
152
|
+
*
|
|
153
|
+
* GUARDED because "setup" for this primitive is the component body, and a
|
|
154
|
+
* server render executes component bodies. Node has no `cancelAnimationFrame`
|
|
155
|
+
* at all, so an unguarded capture here would trade the disposal crash for an
|
|
156
|
+
* SSR crash -- measured, not hypothesised. Nothing can be scheduled without
|
|
157
|
+
* `requestAnimationFrame` either, so the no-op fallback is exactly right.
|
|
158
|
+
*/
|
|
159
|
+
const cancelFrame = typeof cancelAnimationFrame === 'function'
|
|
160
|
+
? cancelAnimationFrame.bind(globalThis)
|
|
161
|
+
: () => {};
|
|
162
|
+
|
|
111
163
|
const stop = () => {
|
|
112
|
-
if (raf)
|
|
164
|
+
if (raf) cancelFrame(raf);
|
|
113
165
|
raf = 0;
|
|
114
166
|
};
|
|
115
167
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { createSignal, onCleanup } from 'solid-js';
|
|
11
11
|
import type { ChatMessage, FeedbackVote } from '../elements/chat-types';
|
|
12
|
+
import { partsToText } from '../state';
|
|
12
13
|
import { toast } from './toast-store';
|
|
13
14
|
|
|
14
15
|
/** Detail shape emitted by the action row. `state` is present only for the
|
|
@@ -38,7 +39,7 @@ export interface MessageFeedback {
|
|
|
38
39
|
/** Whether a message's copy button should currently show its check. */
|
|
39
40
|
isCopied: (id: string) => boolean;
|
|
40
41
|
/** Route a clicked action through copy / vote / passthrough handling. */
|
|
41
|
-
handleAction: (m: Pick<ChatMessage, 'id' | '
|
|
42
|
+
handleAction: (m: Pick<ChatMessage, 'id' | 'parts' | 'feedback'>, action: string) => void;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const DEFAULT_COPIED_DURATION = 2000;
|
|
@@ -96,7 +97,7 @@ export function createMessageFeedback(opts: MessageFeedbackOptions): MessageFeed
|
|
|
96
97
|
const handleAction: MessageFeedback['handleAction'] = (m, action) => {
|
|
97
98
|
if (action === 'copy') {
|
|
98
99
|
// Clipboard may be unavailable (insecure context / jsdom); fail soft.
|
|
99
|
-
try { navigator.clipboard?.writeText(m.
|
|
100
|
+
try { navigator.clipboard?.writeText(partsToText(m.parts)); } catch { /* ignore */ }
|
|
100
101
|
markCopied(m.id);
|
|
101
102
|
toast('Copied to clipboard', { target: opts.target?.() });
|
|
102
103
|
opts.emit({ messageId: m.id, action });
|
|
@@ -237,6 +237,25 @@ export function useAudioAnalysis(
|
|
|
237
237
|
const ctx = getContext();
|
|
238
238
|
if (!ctx) return; // SSR, or a browser without Web Audio.
|
|
239
239
|
if (typeof requestAnimationFrame === 'undefined') return;
|
|
240
|
+
if (typeof cancelAnimationFrame === 'undefined') return;
|
|
241
|
+
|
|
242
|
+
// Captured at SETUP and closed over, never re-resolved as a global inside
|
|
243
|
+
// `onCleanup`: cleanup can run after the host removed the DOM globals (a
|
|
244
|
+
// `kai-*` release is deferred one microtask past detachment, so an
|
|
245
|
+
// environment teardown gets in between), and a bare `cancelAnimationFrame`
|
|
246
|
+
// there throws -- from a promise nobody holds, so it lands as an unhandled
|
|
247
|
+
// rejection that fails the run while every test passes.
|
|
248
|
+
// See tests/components/teardown-without-dom-globals.test.tsx.
|
|
249
|
+
//
|
|
250
|
+
// The FUNCTION, not the view. The `const win = window` capture that fixes a
|
|
251
|
+
// bare `document` does nothing here: `window === globalThis` -- measured, in
|
|
252
|
+
// jsdom and in real Chromium/WebKit alike -- and the teardown deletes these
|
|
253
|
+
// keys off that very object, so `win.cancelAnimationFrame` is undefined by
|
|
254
|
+
// the time cleanup runs. It only trades the ReferenceError for a TypeError.
|
|
255
|
+
// `.bind` pins the receiver the WebIDL operation is specified on; Chromium
|
|
256
|
+
// and WebKit both accept a detached call (measured), so the bind is belt and
|
|
257
|
+
// braces against an engine that does not, at zero cost.
|
|
258
|
+
const cancelFrame = cancelAnimationFrame.bind(globalThis);
|
|
240
259
|
|
|
241
260
|
const stopResume = resumeOnGesture(ctx);
|
|
242
261
|
|
|
@@ -302,7 +321,7 @@ export function useAudioAnalysis(
|
|
|
302
321
|
raf = requestAnimationFrame(step);
|
|
303
322
|
|
|
304
323
|
onCleanup(() => {
|
|
305
|
-
|
|
324
|
+
cancelFrame(raf);
|
|
306
325
|
stopResume();
|
|
307
326
|
bandsAnalyser.disconnect();
|
|
308
327
|
volumeAnalyser.disconnect();
|