@kitn.ai/ui 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/{Icon-BnFKqqtY.js → Icon-DObhsRtm.js} +1 -1
- package/dist/{action-icons-CBskfBMz.js → action-icons-CcgNN2oT.js} +1 -1
- package/dist/arrow-down-CjRU3JgV.js +1 -0
- package/dist/arrow-left-Bnpb1UEW.js +1 -0
- package/dist/{artifact-CvXEm1lS.js → artifact-Bwt7OgcR.js} +1 -1
- package/dist/{attachments-C43ZegEq.js → attachments-Qr4kg-2b.js} +1 -1
- package/dist/{audio-visualizer-B-79y1Tj.js → audio-visualizer-DToYdfSc.js} +1 -1
- package/dist/{badge-BpUbq8c0.js → badge-CdEu_jn2.js} +1 -1
- package/dist/{bell-sn17Ip9v.js → bell-DQAAOpDz.js} +1 -1
- package/dist/{button-BH6rN0U-.js → button-DkuOnGLy.js} +1 -1
- package/dist/{card-renderer-Cb1gQmth.js → card-renderer-Jx70m6_l.js} +1 -1
- package/dist/{card-routing-DifQgS7n.js → card-routing-0YidER_3.js} +1 -1
- package/dist/check-njkVdZu2.js +1 -0
- package/dist/{checkbox-Dc9Yov_B.js → checkbox-D7iRB3nL.js} +1 -1
- package/dist/{checkbox-group-B1kDLLYk.js → checkbox-group-B2_rpoPM.js} +1 -1
- package/dist/chevron-down-B72s6PXR.js +1 -0
- package/dist/chevron-right-BatuHzUm.js +1 -0
- package/dist/{choice-card-BT3c1S8g.js → choice-card-m0BYqqOK.js} +1 -1
- package/dist/circle-check-B62pODB3.js +1 -0
- package/dist/{circle-x-wobWkoSx.js → circle-x-BPG0iPst.js} +1 -1
- package/dist/circle-zx0ByzY9.js +1 -0
- package/dist/code-block-ClMLKb7C.js +1 -0
- package/dist/{collapsible-CPJbf6FM.js → collapsible-B4DlWicL.js} +1 -1
- package/dist/components/chat-thread.d.ts +89 -1
- package/dist/components/conversation-item.d.ts +16 -0
- package/dist/components/conversation-panel.d.ts +29 -0
- package/dist/components/home-panel.d.ts +34 -0
- package/dist/components/widget-tab-bar.d.ts +16 -0
- package/dist/{composer-CjngLzal.js → composer-sQ0HcvyG.js} +3 -3
- package/dist/{confirm-card-DCx_A4bU.js → confirm-card-rQWDQsnC.js} +1 -1
- package/dist/construct-cli.es.js +182 -14
- package/dist/{context-BY8V-Nlp.js → context-0kg4ZX1J.js} +1 -1
- package/dist/controllable-CnGpMF6r.js +1 -0
- package/dist/conversation-item-BxwQLaFJ.js +1 -0
- package/dist/conversation-list-BCfGN1JU.js +1 -0
- package/dist/{copy-B6DO4PqB.js → copy-n9u9rqpi.js} +1 -1
- package/dist/{create-tween-CnMHviKB.js → create-tween-C-q-JCM-.js} +1 -1
- package/dist/{create-tween-D1aiGZZh.js → create-tween-CRrJXxBd.js} +1 -1
- package/dist/{create-tween-BPzaefTp.js → create-tween-DZ3RRDHD.js} +1 -1
- package/dist/{create-tween-DUSQUKpr.js → create-tween-uPkArjIG.js} +1 -1
- package/dist/custom-elements.json +51 -2
- package/dist/{default-input-CucQEOqH.js → default-input-CaMrJkgH.js} +1 -1
- package/dist/define-By-uL588.js +1 -0
- package/dist/define.js +2 -2
- package/dist/define.server.js +2 -2
- package/dist/{disclosure-CbLtajUQ.js → disclosure-WUImBbDv.js} +1 -1
- package/dist/{dropdown-BwhViJFd.js → dropdown-wZVeJOMd.js} +1 -1
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/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.js +1 -1
- package/dist/elements/checkbox.js +1 -1
- 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/dialog.js +1 -1
- package/dist/elements/dock.js +3 -3
- package/dist/elements/dropdown.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- 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.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/radio-group.js +1 -1
- 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 +1 -1
- 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.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/slider.js +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 +20 -4
- package/dist/{ellipsis-DFr4Vqr3.js → ellipsis-CbOpJnU3.js} +1 -1
- package/dist/{embed-5s_vU4J0.js → embed-CrfjkbUG.js} +1 -1
- package/dist/{external-link-u9d85ORQ.js → external-link-ByXAwBBQ.js} +1 -1
- package/dist/{file-text-CX7_x42o.js → file-text-DMib2qwt.js} +1 -1
- package/dist/{file-tree-D3kBjXI5.js → file-tree-CJqM2aP7.js} +1 -1
- package/dist/{folder-V-YdIL8f.js → folder-_BAb929C.js} +1 -1
- package/dist/{form-BRNUMCN2.js → form-DSZgZQZk.js} +1 -1
- package/dist/{hover-card-DaeiYrTA.js → hover-card-BQbul9eq.js} +1 -1
- package/dist/{icon-BpixdZNq.js → icon-CdcfRhYI.js} +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2842 -2703
- package/dist/index.server.js +2820 -2681
- package/dist/{info-8mFsIjS3.js → info-DsFjGuI_.js} +1 -1
- package/dist/{input-d1Yuu-hz.js → input-DKbmkMos.js} +1 -1
- package/dist/kai.es.js +1 -1
- package/dist/{kbd-961zMPFB.js → kbd-PvdGhX4s.js} +1 -1
- package/dist/{link-preview-DIIbV2Sd.js → link-preview-DbM1866N.js} +1 -1
- package/dist/{link-DlZZBGaR.js → link-zF8PzWfF.js} +1 -1
- package/dist/{loader-YQszHFaW.js → loader-BM4CEz4G.js} +1 -1
- package/dist/{markdown-CVk1MPsj.js → markdown-LMvmAyBk.js} +7 -7
- package/dist/mcp.es.js +78 -4
- package/dist/{message-CwKVEdZg.js → message-BSsgvHvf.js} +1 -1
- package/dist/{message-DS8lzHc1.js → message-BYZECyek.js} +1 -1
- package/dist/message-circle-DruwObfX.js +1 -0
- package/dist/message-square-BG6q6mlm.js +1 -0
- package/dist/{minimize-2-UEZmP2QT.js → minimize-2-BQRoBV1V.js} +1 -1
- package/dist/{model-switcher-Kk1jEQ5t.js → model-switcher-CMpCOYcB.js} +1 -1
- package/dist/{overlay-C1od6LOz.js → overlay-Cb-MkyDa.js} +1 -1
- package/dist/{panel-right-ryET0yyn.js → panel-right-i8warFxT.js} +1 -1
- package/dist/{paperclip-BDNoZFJP.js → paperclip-ByY3UVO6.js} +1 -1
- package/dist/play-BvKKq3fX.js +1 -0
- package/dist/primitives/conversation-store.d.ts +54 -0
- package/dist/{progress-bar-BvYX0aoy.js → progress-bar-jVAHvMUf.js} +1 -1
- package/dist/{prompt-suggestion-BRFu2lcu.js → prompt-suggestion-CD-qtNgC.js} +1 -1
- package/dist/{radio-DcL23cNJ.js → radio-BsoBMhkF.js} +1 -1
- package/dist/react/index.d.ts +393 -0
- package/dist/react.js +20 -20
- package/dist/{reasoning-Cackq4Rh.js → reasoning-4kBPtlTx.js} +1 -1
- package/dist/register-impl-B1hLkrI8.js +424 -0
- package/dist/{resizable-Cp9HUYih.js → resizable-uPmY9VY-.js} +1 -1
- package/dist/{rotate-ccw-0DLRDHH5.js → rotate-ccw-B2xn93py.js} +1 -1
- package/dist/{rotate-cw-DOB9IGcU.js → rotate-cw-CMVLoCyH.js} +1 -1
- package/dist/{scroll-area-emVmWP4K.js → scroll-area-BqLUo8ZZ.js} +1 -1
- package/dist/{scroll-button-CeP_gv1x.js → scroll-button-DslXiJyL.js} +1 -1
- package/dist/{select-CWmu00tS.js → select-2cLRtPGT.js} +1 -1
- package/dist/{separator-CE6vly6z.js → separator-BWQtNJcM.js} +1 -1
- package/dist/{settings-CE-78SvZ.js → settings-BBR63jLg.js} +1 -1
- package/dist/{settings-group-ZFWNxFQR.js → settings-group-B2ciy-Iw.js} +1 -1
- package/dist/{skeleton-BebdP4mf.js → skeleton-CkHI-HgC.js} +1 -1
- package/dist/{slider-BAKRYUlz.js → slider-DDR9U-i0.js} +1 -1
- package/dist/{solid-BE5ui6sy.js → solid-BzCFdL25.js} +7720 -7058
- package/dist/{solid-DFhsce3O.js → solid-DruVOXnf.js} +5864 -5283
- package/dist/solid.d.ts +6 -2
- package/dist/solid.js +217 -210
- package/dist/solid.server.js +217 -210
- package/dist/{source-B0FlguSx.js → source-GEnFF528.js} +1 -1
- package/dist/{star-CBMZvLuN.js → star-CilHq8GP.js} +1 -1
- package/dist/store-Ciyk6eZG.js +1 -0
- package/dist/{switch-CViARZCy.js → switch-KkeQIbuH.js} +1 -1
- package/dist/{tasks-card-MjCcOhR_.js → tasks-card-BrN4bggJ.js} +1 -1
- package/dist/{text-shimmer-DKlwRF2G.js → text-shimmer-Dcj1MHuq.js} +1 -1
- package/dist/{textarea-3ay2Me2-.js → textarea-Dpvbngbg.js} +1 -1
- package/dist/theme.tokens.css +28 -0
- package/dist/{thumbs-up-DQcu2HMO.js → thumbs-up-CsT9Wzb9.js} +1 -1
- package/dist/{toast-store-C6yKOhbk.js → toast-store-tgK6MuMv.js} +1 -1
- package/dist/{tool-BCYNlfKy.js → tool-DzUjIBT8.js} +1 -1
- package/dist/{tooltip-05zI4hjs.js → tooltip-Bkkg_4t0.js} +1 -1
- package/dist/{trash-2-BR1RxGky.js → trash-2-jBKxjGGQ.js} +1 -1
- package/dist/{triangle-alert-DHohDOCy.js → triangle-alert-DlQ68ppf.js} +1 -1
- package/dist/types.d.ts +48 -3
- package/dist/ui/dock.d.ts +29 -0
- package/dist/{upload-BbGW6A_u.js → upload-TcAHXv9W.js} +1 -1
- package/dist/{url-scheme-policy-DHpoTJwB.js → url-scheme-policy-DR5DgvkM.js} +1 -1
- package/dist/{use-card-resolution-CJW4grFa.js → use-card-resolution-DaZqAdWf.js} +1 -1
- package/dist/{variant-aurora-xiMPty8f.js → variant-aurora-BhLuYxJR.js} +1 -1
- package/dist/{variant-aurora-BUOVgPOB.js → variant-aurora-CNmfHhcx.js} +2 -2
- package/dist/{variant-aurora-CUsUJvFI.js → variant-aurora-D6GsdTPA.js} +2 -2
- package/dist/{variant-aurora-CW2i4bfr.js → variant-aurora-DbW_OWL2.js} +1 -1
- package/dist/{variant-custom-D91HjAKz.js → variant-custom-BRvaNHSi.js} +1 -1
- package/dist/{variant-custom-BQtOnmeO.js → variant-custom-BvYHR62b.js} +1 -1
- package/dist/{variant-custom-CAnzzppW.js → variant-custom-DMTrEO5w.js} +2 -2
- package/dist/{variant-custom-CAm9L-E5.js → variant-custom-DV2x254s.js} +2 -2
- package/dist/{variant-wave-Ca1Ls4_y.js → variant-wave-BNMGrDfj.js} +2 -2
- package/dist/{variant-wave-uY5crU_K.js → variant-wave-BpDd8t3Z.js} +1 -1
- package/dist/{variant-wave-CxX39DKd.js → variant-wave-CyBueXj9.js} +2 -2
- package/dist/{variant-wave-DYyzIIZv.js → variant-wave-sp-qwsdI.js} +1 -1
- package/dist/x-Z5UntkIJ.js +1 -0
- package/frameworks/react/index.tsx +14 -4
- package/llms-full.txt +7 -2
- package/package.json +1 -1
- package/src/agent-tooling/README.md +192 -0
- package/src/agent-tooling/catalog/invariants.ts +2 -2
- package/src/agent-tooling/construct/cli.ts +16 -0
- package/src/agent-tooling/construct/codegen.ts +309 -27
- package/src/agent-tooling/construct/construct.v1.schema.json +69 -0
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +11 -2
- package/src/agent-tooling/construct/schema.ts +81 -0
- package/src/components/chat-thread.tsx +532 -36
- package/src/components/conversation-item.tsx +23 -0
- package/src/components/conversation-panel.tsx +118 -0
- package/src/components/home-panel.tsx +159 -0
- package/src/components/widget-tab-bar.tsx +73 -0
- package/src/elements/chat.tsx +94 -2
- package/src/elements/compiled.css +1 -1
- package/src/elements/element-meta.json +49 -6
- package/src/elements/element-nonscalar.json +2 -0
- package/src/elements/element-types.d.ts +20 -4
- package/src/index.ts +3 -1
- package/src/primitives/conversation-store.ts +248 -0
- package/src/solid.ts +10 -2
- package/src/types.ts +45 -3
- package/src/ui/dock.tsx +47 -1
- package/theme.css +28 -0
- package/dist/arrow-down-Br0qtpsz.js +0 -1
- package/dist/arrow-left-Dj7xgE12.js +0 -1
- package/dist/check-DPeeMUYx.js +0 -1
- package/dist/chevron-down-CHSWk7ZT.js +0 -1
- package/dist/chevron-right-B7l_fMgx.js +0 -1
- package/dist/circle-Bb0yOpqM.js +0 -1
- package/dist/circle-check-BGZpCiz7.js +0 -1
- package/dist/code-block-CUQ5mtv0.js +0 -1
- package/dist/controllable-D43BKmtX.js +0 -1
- package/dist/conversation-list-D4lLU_yS.js +0 -1
- package/dist/define-CGGX-7Vg.js +0 -1
- package/dist/message-circle-Xf8Q-vZp.js +0 -1
- package/dist/message-square-Cxnwgkns.js +0 -1
- package/dist/play-OFIvYXUQ.js +0 -1
- package/dist/register-impl-C7rENvPK.js +0 -424
- package/dist/store-7eoadydQ.js +0 -1
- package/dist/x-C5m1JFgi.js +0 -1
|
@@ -493,7 +493,8 @@ function emitIndexHtml(c: Construct): string {
|
|
|
493
493
|
|
|
494
494
|
function emitElement(c: Construct): string {
|
|
495
495
|
const accent = c.theme?.accent;
|
|
496
|
-
|
|
496
|
+
const unreadColor = c.theme?.unreadColor;
|
|
497
|
+
if (!accent && !unreadColor) {
|
|
497
498
|
// `empty` (Task 14) composes straight into ChatThread's own `emptyContent`
|
|
498
499
|
// prop now (see emitEmptyContentProp's doc) — a plain JSX value passed down
|
|
499
500
|
// through App, not a Portal onto the host — so the facade needs no `ctx` and
|
|
@@ -503,6 +504,35 @@ function emitElement(c: Construct): string {
|
|
|
503
504
|
return `import { defineWebComponent } from '@kitn.ai/ui/define';
|
|
504
505
|
import { App } from './App';
|
|
505
506
|
|
|
507
|
+
// The one facade. Interior stays pure Solid (no nested element registrations);
|
|
508
|
+
// the kit CSS is injected into the shadow root by defineWebComponent itself.
|
|
509
|
+
defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' | 'auto' }, ${facade});
|
|
510
|
+
`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// `unreadColor` (owner ruling, 2026-08-26 — unread-indicator round) gets the
|
|
514
|
+
// exact same setProperty treatment as `accent` below — construct-authored/
|
|
515
|
+
// untrusted text, JSON.stringify'd at its one interpolation site, carried via
|
|
516
|
+
// `ctx.element.style.setProperty` rather than interpolated into CSS text (so
|
|
517
|
+
// it can never break out into a new declaration/rule) — but with NO paired
|
|
518
|
+
// -foreground computation: unlike the primary accent, --color-unread never
|
|
519
|
+
// sits behind text, only inside small filled dots (list-row/header/Dock
|
|
520
|
+
// badge), so there is nothing to contrast-pair against. Emitted
|
|
521
|
+
// unconditionally alongside accent's setProperty call when present, even if
|
|
522
|
+
// `accent` itself is absent — this branch is reached whenever EITHER is set.
|
|
523
|
+
const unreadColorSetProperty = unreadColor
|
|
524
|
+
? `\n ctx.element.style.setProperty('--kai-color-unread', ${JSON.stringify(unreadColor)});`
|
|
525
|
+
: '';
|
|
526
|
+
|
|
527
|
+
if (!accent) {
|
|
528
|
+
// unreadColor-only construct: no accent, so none of the contrast-pairing
|
|
529
|
+
// machinery below applies — just the one setProperty call and `ctx`.
|
|
530
|
+
const facade = `(_props, ctx) => {${unreadColorSetProperty}
|
|
531
|
+
return <App />;
|
|
532
|
+
}`;
|
|
533
|
+
return `import { defineWebComponent } from '@kitn.ai/ui/define';
|
|
534
|
+
import { App } from './App';
|
|
535
|
+
|
|
506
536
|
// The one facade. Interior stays pure Solid (no nested element registrations);
|
|
507
537
|
// the kit CSS is injected into the shadow root by defineWebComponent itself.
|
|
508
538
|
defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' | 'auto' }, ${facade});
|
|
@@ -557,7 +587,7 @@ defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' |
|
|
|
557
587
|
foregroundCss + `${CONTRAST_COLOR_SUPPORTS} {\n :host { --kai-color-primary-foreground: contrast-color(var(--kai-color-primary)); }\n}`;
|
|
558
588
|
|
|
559
589
|
const facade = `(_props, ctx) => {
|
|
560
|
-
ctx.element.style.setProperty('--kai-color-primary', ${JSON.stringify(accent)})
|
|
590
|
+
ctx.element.style.setProperty('--kai-color-primary', ${JSON.stringify(accent)});${unreadColorSetProperty}
|
|
561
591
|
return (
|
|
562
592
|
<>
|
|
563
593
|
<style>{${JSON.stringify(styleText)}}</style>
|
|
@@ -582,10 +612,11 @@ defineWebComponent('${c.name}', { theme: '${themeMode(c)}' as 'light' | 'dark' |
|
|
|
582
612
|
|
|
583
613
|
function emitApp(c: Construct): string {
|
|
584
614
|
if (c.layout === 'custom') return emitCustomApp(c);
|
|
585
|
-
return `${emitSolidJsImport(c)}import { ChatThread, createKaiChat${emitLayoutImport(c)}${emitCardComponentImport(c)}${emitEmptyComponentImport(c)}${emitHeaderCloseImport(c)} } from '@kitn.ai/ui/solid';
|
|
586
|
-
import type { AttachmentData${emitHistoryTypeImport(c)} } from '@kitn.ai/ui/solid';
|
|
615
|
+
return `${emitSolidJsImport(c)}${emitConversationsSignalsImport(c)}import { ChatThread, createKaiChat${emitLayoutImport(c)}${emitCardComponentImport(c)}${emitEmptyComponentImport(c)}${emitHeaderCloseImport(c)} } from '@kitn.ai/ui/solid';
|
|
616
|
+
import type { AttachmentData${emitHistoryTypeImport(c)}${emitConversationsResetTypeImport(c)} } from '@kitn.ai/ui/solid';
|
|
587
617
|
${emitProviderImports(c)}
|
|
588
618
|
${emitCardsImport(c)}
|
|
619
|
+
${emitConversationsImport(c)}
|
|
589
620
|
|
|
590
621
|
${emitProviderSetup(c)}
|
|
591
622
|
${emitHistorySetup(c)}
|
|
@@ -654,9 +685,26 @@ ${emitHistorySetup(c)}
|
|
|
654
685
|
// emitDockCloseVar/emitHeaderEndContentProp's docs. No header means no
|
|
655
686
|
// row for it to sit in, so that case is untouched and Dock's own built-in
|
|
656
687
|
// mobile X keeps covering it.
|
|
688
|
+
// - conversations (Task 5): gated via ChatThread's own \`conversations\`/
|
|
689
|
+
// \`store\` props (kit-owned end to end — the prior-conversations list,
|
|
690
|
+
// list/load/save, autosave on every \`chat.messages()\` change). Requires
|
|
691
|
+
// capabilities.history persistence \`local\` or \`endpoint\` (schema
|
|
692
|
+
// superRefine, C-4) and SUBSUMES this file's hand-rolled history effect
|
|
693
|
+
// when on — see emitHistorySetup's own doc for the persistence-ownership
|
|
694
|
+
// decision: the store prop is the ONLY persistence mechanism emitted,
|
|
695
|
+
// never both. ChatThread never mutates \`messages\` itself, so
|
|
696
|
+
// \`onConversationLoad\` is ALSO wired here (\`chat.setMessages(() =>
|
|
697
|
+
// messages)\`) — without it, select/new/mount-restore all update
|
|
698
|
+
// ChatThread's own internal view/list state while the rendered thread
|
|
699
|
+
// never changes (see emitConversationsProps's own doc for the Task 6
|
|
700
|
+
// live-browser bug this fixes).
|
|
701
|
+
// - conversations + widget (owner follow-up): closing the widget while its
|
|
702
|
+
// list view is open must not leave it there for the next open — see
|
|
703
|
+
// widgetHasConversationsChrome/emitDockOnOpenChangeProp's docs. Wired only
|
|
704
|
+
// for \`widget\`, the one layout with something that closes/reopens at all.
|
|
657
705
|
export function App() {
|
|
658
|
-
${emitDockCloseVar(c, ' ')} return (
|
|
659
|
-
${emitLayoutOpen(c)}${emitSlots(c.slots, ' ')} <ChatThread messages={chat.messages()} loading={chat.loading()} placeholder="Ask anything" onSubmit={submit} webSearch={false} voice={false}${emitHeaderProp(c)}${emitHeaderEndContentProp(c)}${emitAttachProps(c)}${emitStartersProp(c)}${emitReasoningProp(c)}${emitReasoningOpenProp(c)}${emitEmptyContentProp(c)}${emitCardTypesProp(c)} />
|
|
706
|
+
${emitDockCloseVar(c, ' ')}${emitChatControllerVar(c, ' ')}${emitConversationsSignalsVar(c, ' ')} return (
|
|
707
|
+
${emitLayoutOpen(c)}${emitSlots(c.slots, ' ')} <ChatThread messages={chat.messages()} loading={chat.loading()} placeholder="Ask anything" onSubmit={submit} webSearch={false} voice={false}${emitHeaderProp(c)}${emitHeaderEndContentProp(c)}${emitAttachProps(c)}${emitStartersProp(c)}${emitReasoningProp(c)}${emitReasoningOpenProp(c)}${emitEmptyContentProp(c)}${emitCardTypesProp(c)}${emitHomeProp(c)}${emitConversationsProps(c)}${emitChatControllerRefProp(c)}${emitChatThreadUnreadProps(c)} />
|
|
660
708
|
${emitLayoutClose(c)} );
|
|
661
709
|
}
|
|
662
710
|
`;
|
|
@@ -683,13 +731,18 @@ ${emitLayoutClose(c)} );
|
|
|
683
731
|
* hand, which this format's own rule already commits to (no code-in-JSON).
|
|
684
732
|
*
|
|
685
733
|
* Capability gating: only cards are wired here (`Thread` accepts `cardTypes`
|
|
686
|
-
* natively, same as `ChatThread`). starters/attachments/reasoning
|
|
687
|
-
* wired for `custom`
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
734
|
+
* natively, same as `ChatThread`). starters/attachments/reasoning/
|
|
735
|
+
* reasoningOpen/header.title/empty/conversations are NOT wired for `custom`
|
|
736
|
+
* in v1 — `Thread`/`PromptInput` don't carry the kit's own plumbing for
|
|
737
|
+
* those (they live inside `ChatThread`'s composer, or — for conversations —
|
|
738
|
+
* ChatThread itself owns the list/load/save wiring), and hand-rolling a
|
|
739
|
+
* second copy here is exactly the restatement this file avoids everywhere
|
|
740
|
+
* else. Decided loudly in the emitted comment below, not silently: this is
|
|
741
|
+
* the eject artifact, so a construct author who needs one of them on
|
|
742
|
+
* `custom` adds it directly to the plain Solid file they now own.
|
|
743
|
+
* capabilities.history persistence itself IS still honored on `custom` (the
|
|
744
|
+
* hand-rolled effect below), independent of whether conversations is also
|
|
745
|
+
* set — see emitHistorySetup's own doc for why `custom` keeps it.
|
|
693
746
|
*/
|
|
694
747
|
function emitCustomApp(c: Construct): string {
|
|
695
748
|
const slots = c.slots ?? [];
|
|
@@ -708,8 +761,8 @@ ${emitHistorySetup(c)}
|
|
|
708
761
|
// layout: custom — minimal chrome, no ChatThread/Dock/PaneGroup. The bare
|
|
709
762
|
// spine (Thread + PromptInput) plus the declared slots, positioned by hand so
|
|
710
763
|
// YOU own the surrounding DOM. Capabilities beyond the spine (starters,
|
|
711
|
-
// attachments, reasoning display-mode, reasoningOpen, header.title, empty
|
|
712
|
-
// are NOT wired here in v1 — this file is the eject artifact; add them the
|
|
764
|
+
// attachments, reasoning display-mode, reasoningOpen, header.title, empty,
|
|
765
|
+
// conversations) are NOT wired here in v1 — this file is the eject artifact; add them the
|
|
713
766
|
// way ChatThread composes them (components/chat-thread.tsx in the kit's own
|
|
714
767
|
// source) if this construct needs them on a custom layout.
|
|
715
768
|
export function App() {
|
|
@@ -820,6 +873,116 @@ function widgetHasHeaderClose(c: Construct): boolean {
|
|
|
820
873
|
return c.layout === 'widget' && !!c.header?.title;
|
|
821
874
|
}
|
|
822
875
|
|
|
876
|
+
/** `widget` layout with `capabilities.conversations` on: whether the widget
|
|
877
|
+
* needs the close-resets-the-list-view wiring below (owner follow-up,
|
|
878
|
+
* 2026-08-26 — closing the widget while its conversations list was open
|
|
879
|
+
* left `ChatThread`'s internal `view` state at `'list'`, so the NEXT open
|
|
880
|
+
* landed back on the list instead of the default chat screen). Only
|
|
881
|
+
* `widget` has a `Dock` to close/reopen at all; every other layout renders
|
|
882
|
+
* ChatThread inline with nothing that ever hides it, so the regression
|
|
883
|
+
* can't occur there and nothing is emitted for them.
|
|
884
|
+
*
|
|
885
|
+
* ALSO gates unread indicators (owner round, 2026-08-26): the FAB's `Dock
|
|
886
|
+
* unread` badge and `ChatThread`'s own `hostOpen` prop both need the exact
|
|
887
|
+
* same `dockOpen` tracking this reset wiring already needs, and both need
|
|
888
|
+
* the same "is there even a Dock to reflect this on" condition — one gate,
|
|
889
|
+
* reused, rather than two nearly-identical predicates drifting apart.
|
|
890
|
+
*
|
|
891
|
+
* Widened (Task 5) to also fire on `home` alone, with no `conversations`
|
|
892
|
+
* capability: closing the widget must return the view to Home exactly like
|
|
893
|
+
* it must return to the default chat screen — same regression class, same
|
|
894
|
+
* reset wiring, and `closeConversationsList()` is a safe no-op when there
|
|
895
|
+
* never was a conversations list to close. */
|
|
896
|
+
function widgetHasConversationsChrome(c: Construct): boolean {
|
|
897
|
+
return c.layout === 'widget' && (!!c.capabilities?.conversations || !!c.home);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/** Declares the closure `emitChatControllerRefProp`/`emitDockOnOpenChangeProp`
|
|
901
|
+
* share: `ChatThread`'s own `controllerRef` (chat-thread.tsx) hands back a
|
|
902
|
+
* `ChatThreadController` — the existing imperative seam, not a new one — and
|
|
903
|
+
* this captures it so `Dock`'s `onOpenChange` (a sibling prop on a sibling
|
|
904
|
+
* element, not a ChatThread descendant) can call
|
|
905
|
+
* `closeConversationsList()` on it. Declared inside `App()`, not at module
|
|
906
|
+
* scope, for the same instance-isolation reason as `emitDockCloseVar`. */
|
|
907
|
+
function emitChatControllerVar(c: Construct, indent: string): string {
|
|
908
|
+
return widgetHasConversationsChrome(c) ? `${indent}let chatController: ChatThreadController | undefined;\n` : '';
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/** Threads `emitChatControllerVar`'s closure onto `<ChatThread controllerRef>`. */
|
|
912
|
+
function emitChatControllerRefProp(c: Construct): string {
|
|
913
|
+
return widgetHasConversationsChrome(c) ? ' controllerRef={(api) => (chatController = api)}' : '';
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** `Dock`'s own `onOpenChange` (ui/dock.tsx) already fires on EVERY close
|
|
917
|
+
* path — the header X, the launcher toggle, and Escape all resolve through
|
|
918
|
+
* its single `setOpen` — so wiring it once here covers all three with no
|
|
919
|
+
* per-path duplication (the alternative `emitDockCloseVar`/`dockClose`
|
|
920
|
+
* closure above only covers the header-X path, which is why that one isn't
|
|
921
|
+
* reused for this). The reset half fires ONLY on the close transition
|
|
922
|
+
* (`!open`) — opening needs no action there, the list stays gone until the
|
|
923
|
+
* visitor taps the toggle again, and a mid-open reset call would fight
|
|
924
|
+
* anyone still viewing the list. `setDockOpen(open)` fires on BOTH
|
|
925
|
+
* transitions unconditionally — it's a plain mirror of Dock's own state
|
|
926
|
+
* into a signal `ChatThread`'s `hostOpen` prop can read (see
|
|
927
|
+
* `emitConversationsSignalsVar`'s doc for why a signal, not a variable). */
|
|
928
|
+
function emitDockOnOpenChangeProp(c: Construct): string {
|
|
929
|
+
return widgetHasConversationsChrome(c)
|
|
930
|
+
? ' onOpenChange={(open) => { setDockOpen(open); if (!open) chatController?.closeConversationsList(); }}'
|
|
931
|
+
: '';
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/** `ChatThreadController` is needed only when `emitChatControllerVar` above
|
|
935
|
+
* declares a variable of that type — appended onto the same `import type`
|
|
936
|
+
* statement as `AttachmentData`/`ChatMessage`, never a second import
|
|
937
|
+
* statement for the module (same convention as `emitHistoryTypeImport`). */
|
|
938
|
+
function emitConversationsResetTypeImport(c: Construct): string {
|
|
939
|
+
return widgetHasConversationsChrome(c) ? ', ChatThreadController' : '';
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/** `createSignal` import for `emitConversationsSignalsVar`'s two signals.
|
|
943
|
+
* Deliberately its OWN `import { createSignal } from 'solid-js'` line, not
|
|
944
|
+
* folded onto `emitSolidJsImport`'s: that one imports `createEffect` for the
|
|
945
|
+
* hand-rolled history-restore effect, which is UNEMITTED whenever
|
|
946
|
+
* `capabilities.conversations` is on (conversations subsumes persistence —
|
|
947
|
+
* see that function's own doc) — so for every construct this function
|
|
948
|
+
* actually fires for, `emitSolidJsImport` is already returning `''` and
|
|
949
|
+
* there's nothing to fold onto. */
|
|
950
|
+
function emitConversationsSignalsImport(c: Construct): string {
|
|
951
|
+
return widgetHasConversationsChrome(c) ? `import { createSignal } from 'solid-js';\n` : '';
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/** Declares the two signals `Dock`'s `onOpenChange` (above) writes and
|
|
955
|
+
* `ChatThread`'s `hostOpen`/`onUnreadChange` props (below) read/write:
|
|
956
|
+
* `dockOpen` mirrors whether the panel is currently open — a SIGNAL, not a
|
|
957
|
+
* plain closure variable like `dockClose`/`chatController` above, because
|
|
958
|
+
* `ChatThread` reads it REACTIVELY every render (`hostOpen={dockOpen()}`),
|
|
959
|
+
* not just calls it imperatively on an event; `anyUnread` is the reverse
|
|
960
|
+
* direction, `ChatThread` writing outward via `onUnreadChange={setAnyUnread}`
|
|
961
|
+
* so `Dock`'s own `unread` prop (ui/dock.tsx — already exists, already
|
|
962
|
+
* tested, no change needed there) can mirror it onto the FAB.
|
|
963
|
+
* `dockOpen`'s initial value matches `widget.defaultOpen` (mirroring
|
|
964
|
+
* `emitDockDefaultOpen`'s own read of the same field) — a widget that opens
|
|
965
|
+
* by default has to start "seen", or a message arriving before the first
|
|
966
|
+
* `onOpenChange` fire would look like it happened while closed. */
|
|
967
|
+
function emitConversationsSignalsVar(c: Construct, indent: string): string {
|
|
968
|
+
if (!widgetHasConversationsChrome(c)) return '';
|
|
969
|
+
const w = c.layout === 'widget' ? c.widget : undefined;
|
|
970
|
+
const defaultOpen = w?.defaultOpen === true ? 'true' : 'false';
|
|
971
|
+
return `${indent}const [dockOpen, setDockOpen] = createSignal(${defaultOpen});\n${indent}const [anyUnread, setAnyUnread] = createSignal(false);\n`;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/** Threads the two signals above onto `<ChatThread hostOpen>`/
|
|
975
|
+
* `<ChatThread onUnreadChange>`. */
|
|
976
|
+
function emitChatThreadUnreadProps(c: Construct): string {
|
|
977
|
+
return widgetHasConversationsChrome(c) ? ' hostOpen={dockOpen()} onUnreadChange={setAnyUnread}' : '';
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/** Threads `anyUnread` onto `Dock`'s own (pre-existing, unchanged) `unread`
|
|
981
|
+
* prop — the FAB's badge while closed. */
|
|
982
|
+
function emitDockUnreadProp(c: Construct): string {
|
|
983
|
+
return widgetHasConversationsChrome(c) ? ' unread={anyUnread()}' : '';
|
|
984
|
+
}
|
|
985
|
+
|
|
823
986
|
/** The local closure `emitHeaderEndContentProp`/`emitDockControllerRef` share:
|
|
824
987
|
* Dock's `controllerRef` hands back `{ open, setOpen }` (ui/dock.tsx) — the
|
|
825
988
|
* existing imperative seam, not a new one — and this captures `setOpen`
|
|
@@ -916,22 +1079,106 @@ function emitReasoningOpenProp(c: Construct): string {
|
|
|
916
1079
|
return c.capabilities?.reasoningOpen === true ? ' reasoningOpen={true}' : '';
|
|
917
1080
|
}
|
|
918
1081
|
|
|
1082
|
+
/** capabilities.conversations -> ChatThread's own `conversations`/`store`
|
|
1083
|
+
* props (C-8: this is the ONLY logic codegen contributes — everything else
|
|
1084
|
+
* is kit code behind ChatThread). `local` persistence wires
|
|
1085
|
+
* localStorageStore(name[, userId]); `endpoint` wires fetchStore(url[,
|
|
1086
|
+
* userId]) — both re-exported from @kitn.ai/ui/solid, never hand-rolled
|
|
1087
|
+
* here (composition-over-reauthoring, same discipline as every other
|
|
1088
|
+
* capability in this file). `c.name` is validated against TAG_RE
|
|
1089
|
+
* (schema.ts — lowercase, hyphenated, a valid custom-element tag), so like
|
|
1090
|
+
* the other `'${c.name}'` interpolation sites in this file (defineWebComponent,
|
|
1091
|
+
* the preview HTML's element tag) it is embedded directly rather than
|
|
1092
|
+
* JSON.stringify'd. userId/url ARE construct-authored/untrusted free text,
|
|
1093
|
+
* JSON.stringify'd at their one interpolation site like theme.accent. The
|
|
1094
|
+
* schema's superRefine (C-4) guarantees history is present with persistence
|
|
1095
|
+
* 'local' or 'endpoint' whenever conversations is true, so no further guard
|
|
1096
|
+
* is needed here.
|
|
1097
|
+
*
|
|
1098
|
+
* BUG FIX (Task 6 live-browser demo, both defects traced to one root cause):
|
|
1099
|
+
* `conversations`/`store` alone only drive ChatThread's OWN internal
|
|
1100
|
+
* view/list/activeId state machine — select/new/mount-restore all resolve
|
|
1101
|
+
* through `store.load()`/a cleared array, but ChatThread never mutates
|
|
1102
|
+
* `props.messages` itself (doc comment on `onConversationLoad`, chat-thread.tsx
|
|
1103
|
+
* line ~144: "this component does not mutate props.messages itself"). Without
|
|
1104
|
+
* `onConversationLoad` wired back to this app's own `chat` store, every
|
|
1105
|
+
* load/new/restore updated ChatThread's internal bookkeeping (active id, list
|
|
1106
|
+
* entry) while the actually-rendered `chat.messages()` never changed — the
|
|
1107
|
+
* exact silent-drop class this codebase's CLAUDE.md calls out ("decide
|
|
1108
|
+
* loudly"), except here the drop was an OMITTED wire, not a decision: "+ New
|
|
1109
|
+
* conversation" appeared to do nothing (same messages kept rendering) and a
|
|
1110
|
+
* reload's mount-time auto-restore updated the list's active row but left the
|
|
1111
|
+
* chat view on the empty/welcome screen. `chat.setMessages(() => messages)`
|
|
1112
|
+
* closes the loop — ChatThread already hands back a FRESH array reference on
|
|
1113
|
+
* every call (`[...messages]` at both call sites in chat-thread.tsx), so no
|
|
1114
|
+
* extra clone is needed here; the updater-returns-new-array form is what the
|
|
1115
|
+
* reactivity contract (CLAUDE.md) requires and is what emitHistorySetup's
|
|
1116
|
+
* own hand-rolled local/endpoint restore already did before conversations
|
|
1117
|
+
* subsumed it. */
|
|
1118
|
+
/** `home` -> ChatThread's own `home` prop (chat-thread.tsx, Task 1-4), plain
|
|
1119
|
+
* data threaded straight through as ONE JSON.stringify'd object literal —
|
|
1120
|
+
* vocabulary-never-logic (this format's binding rule): the construct only
|
|
1121
|
+
* ever DECLARES the home screen's content, never wires a handler for it.
|
|
1122
|
+
* `<kai-chat>`'s own `kai-home-link` CustomEvent (Task 2-3) is how a
|
|
1123
|
+
* consumer of the EMITTED app would react to a link click; codegen never
|
|
1124
|
+
* emits an `onHomeLink` listener here for the same reason it never emits
|
|
1125
|
+
* handlers for any other capability in this file. Omitted entirely when no
|
|
1126
|
+
* `home` is declared — the same off-by-default gating as `emitHeaderProp`/
|
|
1127
|
+
* `emitEmptyContentProp` above. */
|
|
1128
|
+
function emitHomeProp(c: Construct): string {
|
|
1129
|
+
if (!c.home) return '';
|
|
1130
|
+
return ` home={${JSON.stringify(c.home)}}`;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function emitConversationsProps(c: Construct): string {
|
|
1134
|
+
if (!c.capabilities?.conversations) return '';
|
|
1135
|
+
const history = c.capabilities.history;
|
|
1136
|
+
const storeCall =
|
|
1137
|
+
history?.persistence === 'endpoint'
|
|
1138
|
+
? `fetchStore(${JSON.stringify(history.url)}${c.userId ? `, ${JSON.stringify(c.userId)}` : ''})`
|
|
1139
|
+
: `localStorageStore('${c.name}'${c.userId ? `, ${JSON.stringify(c.userId)}` : ''})`;
|
|
1140
|
+
return ` conversations={true} store={${storeCall}} onConversationLoad={(messages) => chat.setMessages(() => messages)}`;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/** A dedicated named import for whichever store constructor
|
|
1144
|
+
* emitConversationsProps used — its own statement (mirrors emitCardsImport's
|
|
1145
|
+
* own `import { cards } from './cards'` line) rather than spliced onto the
|
|
1146
|
+
* ChatThread import list, so noUnusedLocals never trips when conversations
|
|
1147
|
+
* is absent. */
|
|
1148
|
+
function emitConversationsImport(c: Construct): string {
|
|
1149
|
+
if (!c.capabilities?.conversations) return '';
|
|
1150
|
+
const name = c.capabilities.history?.persistence === 'endpoint' ? 'fetchStore' : 'localStorageStore';
|
|
1151
|
+
return `import { ${name} } from '@kitn.ai/ui/solid';`;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
919
1154
|
/** capabilities.history -> whether the App module needs `createEffect`
|
|
920
1155
|
* (both persisted variants react to `chat.messages()` changing; `none`/
|
|
921
1156
|
* absent needs no extra Solid import at all, matching the off-by-default
|
|
922
|
-
* gating everywhere else in this file).
|
|
1157
|
+
* gating everywhere else in this file).
|
|
1158
|
+
*
|
|
1159
|
+
* capabilities.conversations SUBSUMES this on every layout EXCEPT `custom`
|
|
1160
|
+
* (CU-1: conversations is one of the capabilities excluded from the custom
|
|
1161
|
+
* layout's escape hatch, so custom never wires the store-based path and
|
|
1162
|
+
* must keep this hand-rolled one). Where it applies, ChatThread's own
|
|
1163
|
+
* conversations feature owns persistence entirely through the `store` prop
|
|
1164
|
+
* (see emitHistorySetup's doc for the full decision) and this file's
|
|
1165
|
+
* hand-rolled effect is never emitted, so it needs no `createEffect` import
|
|
1166
|
+
* either — gate the same way. */
|
|
923
1167
|
function emitSolidJsImport(c: Construct): string {
|
|
924
1168
|
const history = c.capabilities?.history;
|
|
925
|
-
if (!history || history.persistence === 'none') return '';
|
|
1169
|
+
if (!history || history.persistence === 'none' || (c.layout !== 'custom' && c.capabilities?.conversations)) return '';
|
|
926
1170
|
return `import { createEffect } from 'solid-js';\n`;
|
|
927
1171
|
}
|
|
928
1172
|
|
|
929
1173
|
/** capabilities.history -> whether the AttachmentData type import also needs
|
|
930
1174
|
* ChatMessage (only the persisted variants read/write full ChatMessage[]
|
|
931
|
-
* arrays).
|
|
1175
|
+
* arrays). Gated off when capabilities.conversations subsumes persistence
|
|
1176
|
+
* (see emitSolidJsImport's doc — same `layout !== 'custom'` condition) for
|
|
1177
|
+
* the same reason: the hand-rolled block that used this type is never
|
|
1178
|
+
* emitted there. */
|
|
932
1179
|
function emitHistoryTypeImport(c: Construct): string {
|
|
933
1180
|
const history = c.capabilities?.history;
|
|
934
|
-
if (!history || history.persistence === 'none') return '';
|
|
1181
|
+
if (!history || history.persistence === 'none' || (c.layout !== 'custom' && c.capabilities?.conversations)) return '';
|
|
935
1182
|
// The enclosing statement is already `import type { ... }` (AttachmentData),
|
|
936
1183
|
// so this must NOT repeat the `type` modifier inside the braces — `import
|
|
937
1184
|
// type { AttachmentData, type ChatMessage }` is a TS syntax error.
|
|
@@ -992,6 +1239,28 @@ function emitHistorySetup(c: Construct): string {
|
|
|
992
1239
|
const history = c.capabilities?.history;
|
|
993
1240
|
if (!history || history.persistence === 'none') return '';
|
|
994
1241
|
|
|
1242
|
+
// PERSISTENCE-OWNERSHIP DECISION (Task 5): capabilities.conversations
|
|
1243
|
+
// SUBSUMES this hand-rolled block entirely. ChatThread's own conversations
|
|
1244
|
+
// feature (chat-thread.tsx) already autosaves the current thread on every
|
|
1245
|
+
// `chat.messages()` change through whatever ConversationStore its `store`
|
|
1246
|
+
// prop wires — localStorageStore(name) for `local`, fetchStore(url) for
|
|
1247
|
+
// `endpoint` (see emitConversationsProps below), the SAME "keyed by
|
|
1248
|
+
// construct name, local browser storage or a fetch endpoint" mechanism this
|
|
1249
|
+
// block hand-rolls for a single anonymous thread via THREAD_KEY/fetch. With
|
|
1250
|
+
// conversations on there is a real conversation id to save under (the store
|
|
1251
|
+
// contract), so the store path is strictly more correct, not just
|
|
1252
|
+
// redundant. Emitting both would persist through two independent
|
|
1253
|
+
// mechanisms on every change — not corrupting, but wasteful, confusing to
|
|
1254
|
+
// read, and exactly the kind of restatement this file avoids elsewhere
|
|
1255
|
+
// (see emitApp's header comment) — so this function contributes NOTHING
|
|
1256
|
+
// when capabilities.conversations is set: the store prop is the ONE
|
|
1257
|
+
// persistence mechanism in the emitted App. This does NOT apply to the
|
|
1258
|
+
// `custom` layout — CU-1 excludes conversations from custom's escape
|
|
1259
|
+
// hatch (emitCustomApp never calls emitConversationsProps), so custom
|
|
1260
|
+
// must keep this hand-rolled block as its only persistence mechanism even
|
|
1261
|
+
// when capabilities.conversations is set on the construct.
|
|
1262
|
+
if (c.layout !== 'custom' && c.capabilities?.conversations) return '';
|
|
1263
|
+
|
|
995
1264
|
if (history.persistence === 'local') {
|
|
996
1265
|
const key = JSON.stringify(c.userId ? `kai:${c.name}:${c.userId}:thread` : `kai:${c.name}:thread`);
|
|
997
1266
|
return `
|
|
@@ -1159,7 +1428,7 @@ async function submit(detail: { value: string; attachments: AttachmentData[] })
|
|
|
1159
1428
|
function emitLayoutImport(c: Construct): string {
|
|
1160
1429
|
switch (c.layout) {
|
|
1161
1430
|
case 'widget':
|
|
1162
|
-
return ',
|
|
1431
|
+
return `, Dock${hasLauncherIcon(c) ? ', DockLauncherImage' : ''}`;
|
|
1163
1432
|
case 'split':
|
|
1164
1433
|
return ', WorkspaceShell';
|
|
1165
1434
|
case 'fullscreen':
|
|
@@ -1240,15 +1509,28 @@ function emitDockPosition(c: Construct): string {
|
|
|
1240
1509
|
return w?.position ? ` position="${w.position}"` : '';
|
|
1241
1510
|
}
|
|
1242
1511
|
|
|
1243
|
-
/**
|
|
1244
|
-
*
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1512
|
+
/** Whether `widgetHasLauncherIcon` (see below) needs the extra import — its
|
|
1513
|
+
* own named predicate so `emitLayoutImport`'s `widget` case reads plainly. */
|
|
1514
|
+
function hasLauncherIcon(c: Construct): boolean {
|
|
1515
|
+
return c.layout === 'widget' && !!c.widget?.launcherIcon;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/** widget.launcherIcon -> Dock's `launcher` prop, via `DockLauncherImage`
|
|
1519
|
+
* (ui/dock.tsx) rather than a hand-rolled `<img>`: a construct-authored URL
|
|
1520
|
+
* is exactly as capable of 404ing as any other network fetch (this is what
|
|
1521
|
+
* the fix report found — `kai dev`'s own `owner-widget` fixture pinned a
|
|
1522
|
+
* `https://example.com/logo.png` placeholder that never resolved, so the
|
|
1523
|
+
* FAB rendered a permanently broken image), and `DockLauncherImage` is the
|
|
1524
|
+
* kit's own tested graceful-degradation component for exactly that,
|
|
1525
|
+
* falling back to the built-in glyph on load failure. launcherIcon is
|
|
1526
|
+
* construct-authored/untrusted text (like theme.accent/provider.url
|
|
1527
|
+
* elsewhere in this file), so it is JSON.stringify'd into a real JS
|
|
1528
|
+
* string-literal expression, never interpolated into a raw JSX attribute
|
|
1529
|
+
* string. */
|
|
1248
1530
|
function emitDockLauncher(c: Construct): string {
|
|
1249
1531
|
const w = c.layout === 'widget' ? c.widget : undefined;
|
|
1250
1532
|
if (!w?.launcherIcon) return '';
|
|
1251
|
-
return ` launcher={<
|
|
1533
|
+
return ` launcher={<DockLauncherImage src={${JSON.stringify(w.launcherIcon)}} />}`;
|
|
1252
1534
|
}
|
|
1253
1535
|
|
|
1254
1536
|
/** widget.defaultOpen -> Dock's own `defaultOpen` prop. Only `true` costs a
|
|
@@ -1262,7 +1544,7 @@ function emitDockDefaultOpen(c: Construct): string {
|
|
|
1262
1544
|
function emitLayoutOpen(c: Construct): string {
|
|
1263
1545
|
switch (c.layout) {
|
|
1264
1546
|
case 'widget':
|
|
1265
|
-
return ` <Dock label="${c.name}"${emitDockPosition(c)}${emitDockLauncher(c)}${emitDockDefaultOpen(c)}${emitDockHideClose(c)}${emitDockControllerRef(c)}>\n`;
|
|
1547
|
+
return ` <Dock label="${c.name}"${emitDockPosition(c)}${emitDockLauncher(c)}${emitDockDefaultOpen(c)}${emitDockHideClose(c)}${emitDockControllerRef(c)}${emitDockOnOpenChangeProp(c)}${emitDockUnreadProp(c)}>\n`;
|
|
1266
1548
|
case 'fullscreen':
|
|
1267
1549
|
return ` <div style={{ height: '100dvh', display: 'flex', 'flex-direction': 'column' }}>\n`;
|
|
1268
1550
|
case 'aside':
|
|
@@ -74,6 +74,9 @@
|
|
|
74
74
|
"accent": {
|
|
75
75
|
"type": "string"
|
|
76
76
|
},
|
|
77
|
+
"unreadColor": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
},
|
|
77
80
|
"mode": {
|
|
78
81
|
"default": "system",
|
|
79
82
|
"type": "string",
|
|
@@ -120,6 +123,68 @@
|
|
|
120
123
|
],
|
|
121
124
|
"additionalProperties": false
|
|
122
125
|
},
|
|
126
|
+
"home": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"properties": {
|
|
129
|
+
"greeting": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"properties": {
|
|
132
|
+
"title": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"minLength": 1
|
|
135
|
+
},
|
|
136
|
+
"subtitle": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"minLength": 1
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"additionalProperties": false
|
|
142
|
+
},
|
|
143
|
+
"recentConversation": {
|
|
144
|
+
"type": "boolean",
|
|
145
|
+
"const": true
|
|
146
|
+
},
|
|
147
|
+
"newConversation": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"label": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"minLength": 1
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"additionalProperties": false
|
|
156
|
+
},
|
|
157
|
+
"links": {
|
|
158
|
+
"type": "array",
|
|
159
|
+
"items": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"properties": {
|
|
162
|
+
"label": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"minLength": 1
|
|
165
|
+
},
|
|
166
|
+
"href": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"minLength": 1
|
|
169
|
+
},
|
|
170
|
+
"description": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"minLength": 1
|
|
173
|
+
},
|
|
174
|
+
"icon": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"minLength": 1
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"required": [
|
|
180
|
+
"label"
|
|
181
|
+
],
|
|
182
|
+
"additionalProperties": false
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"additionalProperties": false
|
|
187
|
+
},
|
|
123
188
|
"capabilities": {
|
|
124
189
|
"type": "object",
|
|
125
190
|
"properties": {
|
|
@@ -180,6 +245,10 @@
|
|
|
180
245
|
},
|
|
181
246
|
"reasoningOpen": {
|
|
182
247
|
"type": "boolean"
|
|
248
|
+
},
|
|
249
|
+
"conversations": {
|
|
250
|
+
"type": "boolean",
|
|
251
|
+
"const": true
|
|
183
252
|
}
|
|
184
253
|
},
|
|
185
254
|
"additionalProperties": false
|
|
@@ -5,19 +5,28 @@
|
|
|
5
5
|
"provider": { "mode": "mock" },
|
|
6
6
|
"userId": "user_123",
|
|
7
7
|
"header": { "title": "Acme Support" },
|
|
8
|
+
"theme": { "unreadColor": "#38BDF8" },
|
|
8
9
|
"empty": {
|
|
9
10
|
"title": "Hi, we're Acme Support",
|
|
10
11
|
"description": "Ask us about orders, refunds, and more."
|
|
11
12
|
},
|
|
13
|
+
"home": {
|
|
14
|
+
"greeting": { "title": "Hi from Acme 👋", "subtitle": "Orders, refunds, anything." },
|
|
15
|
+
"recentConversation": true,
|
|
16
|
+
"links": [
|
|
17
|
+
{ "label": "Help center", "href": "https://ui.kitn.ai", "description": "Guides and FAQs", "icon": "book-open" },
|
|
18
|
+
{ "label": "Talk to sales", "description": "We reply fast", "icon": "message-circle" }
|
|
19
|
+
]
|
|
20
|
+
},
|
|
12
21
|
"widget": {
|
|
13
22
|
"position": "top-start",
|
|
14
|
-
"launcherIcon": "https://example.com/logo.png",
|
|
15
23
|
"defaultOpen": true
|
|
16
24
|
},
|
|
17
25
|
"capabilities": {
|
|
18
26
|
"attachments": { "accept": ["image/*", "application/pdf"] },
|
|
19
27
|
"history": { "persistence": "local" },
|
|
20
28
|
"starters": ["Where's my order?", "Request a refund"],
|
|
21
|
-
"reasoningOpen": true
|
|
29
|
+
"reasoningOpen": true,
|
|
30
|
+
"conversations": true
|
|
22
31
|
}
|
|
23
32
|
}
|