@jxsuite/studio 2.0.1 → 2.1.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/canvas.html +21 -0
- package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
- package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
- package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
- package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
- package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
- package/dist/chunks/content-source-34gnkbnn.js +38 -0
- package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
- package/dist/chunks/extension-sections-6mqydz35.js +301 -0
- package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
- package/dist/chunks/files-8kn321h1.js +70 -0
- package/dist/chunks/files-8kn321h1.js.map +9 -0
- package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
- package/dist/chunks/git-panel-tjbr191d.js +51 -0
- package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
- package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
- package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
- package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
- package/dist/chunks/layers-panel-87pb25km.js +58 -0
- package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
- package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
- package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
- package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
- package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
- package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
- package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
- package/dist/chunks/right-panel-zpdxd98t.js +37 -0
- package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
- package/dist/chunks/section-registry-zmckaxf4.js +30 -0
- package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
- package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
- package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
- package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
- package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
- package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
- package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
- package/dist/chunks/studio-41exgzhq.js +6690 -0
- package/dist/chunks/studio-41exgzhq.js.map +38 -0
- package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
- package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
- package/dist/chunks/studio-50k77cca.js +1787 -0
- package/dist/chunks/studio-50k77cca.js.map +18 -0
- package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
- package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
- package/dist/chunks/studio-6f0am8f2.js +53 -0
- package/dist/chunks/studio-6f0am8f2.js.map +10 -0
- package/dist/chunks/studio-6nmjc5qk.js +7002 -0
- package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
- package/dist/chunks/studio-7afd21nw.js +67 -0
- package/dist/chunks/studio-7afd21nw.js.map +10 -0
- package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
- package/dist/chunks/studio-7bmdvzne.js.map +10 -0
- package/dist/chunks/studio-8jpzqk5s.js +128 -0
- package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
- package/dist/chunks/studio-94m77tgk.js +1482 -0
- package/dist/chunks/studio-94m77tgk.js.map +13 -0
- package/dist/chunks/studio-9je6w5sq.js +1000 -0
- package/dist/chunks/studio-9je6w5sq.js.map +16 -0
- package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
- package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
- package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
- package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
- package/dist/chunks/studio-bw5dz6km.js +608 -0
- package/dist/chunks/studio-bw5dz6km.js.map +21 -0
- package/dist/chunks/studio-c6s5getc.js +4232 -0
- package/dist/chunks/studio-c6s5getc.js.map +39 -0
- package/dist/chunks/studio-cjm7fdjf.js +451 -0
- package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
- package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
- package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
- package/dist/chunks/studio-dg7zyt5x.js +28 -0
- package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
- package/dist/chunks/studio-dzcyvvvz.js +166 -0
- package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
- package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
- package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
- package/dist/chunks/studio-f12h6177.js +411 -0
- package/dist/chunks/studio-f12h6177.js.map +13 -0
- package/dist/chunks/studio-hbp1s8m9.js +1817 -0
- package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
- package/dist/chunks/studio-k2sxjph5.js +15295 -0
- package/dist/chunks/studio-k2sxjph5.js.map +235 -0
- package/dist/chunks/studio-myje51t3.js +385 -0
- package/dist/chunks/studio-myje51t3.js.map +12 -0
- package/dist/chunks/studio-na6rwzhw.js +353 -0
- package/dist/chunks/studio-na6rwzhw.js.map +11 -0
- package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
- package/dist/chunks/studio-p471gg5n.js.map +14 -0
- package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
- package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
- package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
- package/dist/chunks/studio-sas7mt3k.js +75 -0
- package/dist/chunks/studio-sas7mt3k.js.map +10 -0
- package/dist/chunks/studio-t5wqwzdh.js +74 -0
- package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
- package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
- package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
- package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
- package/dist/chunks/studio-tr20m8g8.js.map +9 -0
- package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
- package/dist/chunks/studio-v96qfnww.js.map +54 -0
- package/dist/chunks/studio-vcetc91k.js +659 -0
- package/dist/chunks/studio-vcetc91k.js.map +10 -0
- package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
- package/dist/chunks/studio-vmymjefp.js.map +98 -0
- package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
- package/dist/chunks/studio-x9hmy5kr.js +390 -0
- package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
- package/dist/chunks/studio-xa0ce0n7.js +335 -0
- package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
- package/dist/chunks/studio-xm3dbxvn.js +378 -0
- package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
- package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
- package/dist/chunks/studio-zv37yrfa.js +1054 -0
- package/dist/chunks/studio-zv37yrfa.js.map +13 -0
- package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
- package/dist/chunks/ws-client-654ts66j.js.map +11 -0
- package/dist/iframe-entry.js +668 -230
- package/dist/studio.js +21080 -32585
- package/index.html +35 -4685
- package/package.json +17 -15
- package/src/about/about-modal.ts +42 -11
- package/src/browse/library-commands.ts +240 -0
- package/src/browse/library-layouts.ts +369 -0
- package/src/browse/library-model.ts +396 -0
- package/src/browse/library-pane.ts +1049 -0
- package/src/browse/library-preview.ts +252 -0
- package/src/browse/library-source.ts +150 -0
- package/src/canvas/canvas-helpers.ts +33 -9
- package/src/canvas/canvas-live-render.ts +45 -29
- package/src/canvas/canvas-patcher.ts +151 -31
- package/src/canvas/canvas-perf.ts +46 -7
- package/src/canvas/canvas-render.ts +1092 -220
- package/src/canvas/canvas-surface.ts +240 -0
- package/src/canvas/canvas-utils.ts +1166 -124
- package/src/canvas/iframe-drop.ts +2 -1
- package/src/canvas/iframe-entry.ts +241 -4
- package/src/canvas/iframe-host.ts +1226 -143
- package/src/canvas/iframe-inline-edit.ts +31 -0
- package/src/canvas/iframe-interaction.ts +76 -16
- package/src/canvas/iframe-keys.ts +107 -45
- package/src/canvas/iframe-overlay.ts +38 -1
- package/src/canvas/iframe-protocol.ts +137 -2
- package/src/canvas/iframe-render.ts +70 -2
- package/src/canvas/iframe-slash.ts +6 -0
- package/src/canvas/path-mapping.ts +56 -6
- package/src/canvas/surface-registry.ts +292 -0
- package/src/collab/collab-commands.ts +244 -0
- package/src/collab/collab-session.ts +198 -32
- package/src/collab/collab-state.ts +28 -2
- package/src/collab/presence-chips.ts +100 -9
- package/src/commands/active-registry.ts +42 -0
- package/src/commands/app-commands.ts +176 -0
- package/src/commands/budget.ts +158 -0
- package/src/commands/command-args.ts +253 -0
- package/src/commands/context.ts +387 -0
- package/src/commands/defaults.ts +580 -0
- package/src/commands/keymap.ts +571 -0
- package/src/commands/levels.ts +345 -0
- package/src/commands/live-context.ts +193 -0
- package/src/commands/reference.ts +227 -0
- package/src/commands/registry.ts +293 -0
- package/src/component-props.ts +3 -2
- package/src/content/draft-state.ts +106 -0
- package/src/content/entry-commands.ts +282 -0
- package/src/content/entry-editor.ts +327 -0
- package/src/content/entry-fields.ts +116 -0
- package/src/content/entry-model.ts +175 -0
- package/src/editor/canvas-context-menu.ts +17 -11
- package/src/editor/canvas-slash-bridge.ts +11 -4
- package/src/editor/context-menu.ts +804 -204
- package/src/editor/convert-to-component.ts +31 -10
- package/src/editor/convert-to-repeater.ts +3 -2
- package/src/editor/inline-edit-apply.ts +5 -5
- package/src/editor/inline-edit.ts +86 -50
- package/src/editor/insert-zone-action.ts +1 -1
- package/src/editor/shortcuts.ts +1043 -329
- package/src/editor/slash-menu.ts +1 -0
- package/src/files/elements.ts +166 -0
- package/src/files/file-ops.ts +178 -31
- package/src/files/files.ts +898 -268
- package/src/files/fs-events.ts +21 -0
- package/src/files/media-meta.ts +203 -0
- package/src/files/media-paths.ts +141 -0
- package/src/files/media-upload.ts +7 -3
- package/src/files/media-usage.ts +189 -0
- package/src/github/github-auth.ts +235 -33
- package/src/github/github-publish.ts +80 -21
- package/src/grid/grid-controller.ts +152 -12
- package/src/grid/grid-layout.ts +225 -20
- package/src/grid/grid-open.ts +120 -0
- package/src/grid/grid-panel.ts +554 -39
- package/src/grid/grid-source.ts +27 -1
- package/src/grid/redirects-grid.ts +599 -0
- package/src/grid/redirects.ts +398 -0
- package/src/grid/sources/content-source.ts +108 -2
- package/src/i18n/i18n-commands.ts +322 -0
- package/src/new-project/add-repo-modal.ts +8 -10
- package/src/new-project/import-tab.ts +7 -0
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +221 -245
- package/src/packages/jxsuite-update.ts +97 -48
- package/src/packages/pull-package-sync.ts +18 -9
- package/src/page-params.ts +2 -1
- package/src/panels/activity-bar.ts +152 -91
- package/src/panels/activity-panel.ts +481 -0
- package/src/panels/ai-chat/chat-markdown.ts +8 -3
- package/src/panels/ai-chat/chat-view.ts +245 -18
- package/src/panels/ai-chat/composer.ts +31 -5
- package/src/panels/ai-chat/sessions-view.ts +17 -10
- package/src/panels/ai-panel.ts +279 -53
- package/src/panels/block-action-bar.ts +1115 -224
- package/src/panels/bottom-dock.ts +337 -0
- package/src/panels/canvas-dnd-bridge.ts +37 -8
- package/src/panels/chat-panel.ts +29 -12
- package/src/panels/data-explorer.ts +254 -112
- package/src/panels/data-grid.ts +10 -4
- package/src/panels/dnd.ts +28 -51
- package/src/panels/editors.ts +434 -107
- package/src/panels/elements-panel.ts +66 -2
- package/src/panels/empty-state.ts +112 -0
- package/src/panels/events-panel.ts +596 -82
- package/src/panels/formula-workspace.ts +408 -218
- package/src/panels/frontmatter-fields.ts +51 -10
- package/src/panels/frontmatter-panel.ts +357 -79
- package/src/panels/git-panel.ts +375 -164
- package/src/panels/head-panel.ts +791 -85
- package/src/panels/i18n-panel.ts +592 -0
- package/src/panels/imports-panel.ts +95 -85
- package/src/panels/jump-bar.ts +584 -0
- package/src/panels/layers-panel.ts +1106 -328
- package/src/panels/left-panel.ts +109 -204
- package/src/panels/navigator-panels.ts +131 -0
- package/src/panels/overlays.ts +7 -2
- package/src/panels/pane-context.ts +1477 -0
- package/src/panels/pane-grid.ts +406 -0
- package/src/panels/panel-registry.ts +340 -0
- package/src/panels/panel-scheduler.ts +75 -0
- package/src/panels/problems-panel.ts +234 -0
- package/src/panels/properties-panel.ts +946 -1139
- package/src/panels/provenance.ts +258 -0
- package/src/panels/quick-search.ts +791 -122
- package/src/panels/right-panel.ts +228 -102
- package/src/panels/seo-modal.ts +395 -0
- package/src/panels/settings-pane.ts +203 -0
- package/src/panels/signals-panel.ts +354 -137
- package/src/panels/statement-editor.ts +41 -25
- package/src/panels/statusbar.ts +342 -106
- package/src/panels/style-panel.ts +974 -463
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-doc.ts +4 -2
- package/src/panels/stylebook-layers-panel.ts +3 -2
- package/src/panels/stylebook-panel.ts +51 -26
- package/src/panels/tab-strip.ts +1058 -78
- package/src/panels/target-line.ts +274 -0
- package/src/panels/toolbar.ts +631 -484
- package/src/panels/welcome-screen.ts +301 -198
- package/src/platform-errors.ts +26 -3
- package/src/platform.ts +96 -2
- package/src/platforms/cloud.ts +71 -8
- package/src/platforms/devserver.ts +92 -29
- package/src/publish/deploy-checklist.ts +383 -0
- package/src/publish/pages-service.ts +10 -1
- package/src/publish/publish-commands.ts +218 -0
- package/src/publish/publish-panel.ts +110 -16
- package/src/recent-projects.ts +3 -2
- package/src/services/a11y-report.ts +505 -0
- package/src/services/ai-models.ts +30 -1
- package/src/services/ai-project-tools.ts +29 -10
- package/src/services/ai-system-prompt.ts +53 -14
- package/src/services/ai-tools.ts +39 -3
- package/src/services/ai-writes.ts +154 -0
- package/src/services/announce.ts +97 -0
- package/src/services/automation.ts +424 -175
- package/src/services/cem-export.ts +9 -4
- package/src/services/clock.ts +61 -0
- package/src/services/document-assistant.ts +36 -8
- package/src/services/idle.ts +179 -0
- package/src/services/import-client.ts +26 -0
- package/src/services/monaco-buffer.ts +627 -0
- package/src/services/monaco-lazy.ts +36 -0
- package/src/services/notify.ts +271 -0
- package/src/services/profile.ts +155 -0
- package/src/services/references.ts +0 -0
- package/src/services/token-lint.ts +3 -2
- package/src/services/tool-executor.ts +53 -7
- package/src/services/trusted-types.ts +123 -0
- package/src/settings/contexts-section.ts +427 -0
- package/src/settings/contributed-section.ts +149 -11
- package/src/settings/css-vars-editor.ts +306 -349
- package/src/settings/defs-editor.ts +93 -34
- package/src/settings/dependencies-editor.ts +7 -7
- package/src/settings/extension-sections.ts +37 -5
- package/src/settings/general-settings.ts +204 -121
- package/src/settings/locales-section.ts +347 -0
- package/src/settings/preferences-accounts.ts +140 -0
- package/src/settings/preferences-dialog.ts +620 -0
- package/src/settings/preferences-keymap.ts +270 -0
- package/src/settings/project-sections.ts +260 -0
- package/src/settings/section-registry.ts +169 -0
- package/src/settings/settings-document.ts +385 -0
- package/src/shell.ts +1549 -0
- package/src/site-context.ts +45 -20
- package/src/state.ts +14 -275
- package/src/store.ts +88 -43
- package/src/studio.ts +790 -346
- package/src/style/live-preview.ts +36 -0
- package/src/style/project-styles.ts +349 -0
- package/src/style/token-ref.ts +123 -0
- package/src/tabs/patch-ops.ts +16 -4
- package/src/tabs/project-config.ts +416 -0
- package/src/tabs/selection.ts +323 -0
- package/src/tabs/tab.ts +182 -47
- package/src/tabs/transact.ts +209 -28
- package/src/types.ts +68 -13
- package/src/ui/ai-credentials-form.ts +39 -35
- package/src/ui/ai-managed-connect.ts +7 -1
- package/src/ui/dynamic-slot.ts +224 -81
- package/src/ui/expression-editor.ts +71 -63
- package/src/ui/field-input.ts +4 -6
- package/src/ui/field-row.ts +215 -38
- package/src/ui/form-controls.ts +194 -7
- package/src/ui/layers.ts +492 -69
- package/src/ui/media-picker.ts +115 -25
- package/src/ui/panel-resize.ts +109 -91
- package/src/ui/progress-modal.ts +131 -50
- package/src/ui/regions.ts +462 -0
- package/src/ui/schema-form.ts +229 -45
- package/src/ui/spectrum.ts +24 -8
- package/src/ui/timing.ts +49 -0
- package/src/ui/value-selector.ts +20 -0
- package/src/ui/value-source.ts +348 -0
- package/src/ui/virtual-window.ts +400 -0
- package/src/utils/edit-display.ts +3 -2
- package/src/utils/inherited-style.ts +67 -37
- package/src/view.ts +64 -78
- package/src/workspace/pane-derive.ts +1258 -0
- package/src/workspace/session.ts +317 -0
- package/src/workspace/workspace.ts +1345 -23
- package/styles/canvas.css +653 -0
- package/styles/forced-colors.css +106 -0
- package/styles/inspector.css +1454 -0
- package/styles/overlays.css +2704 -0
- package/styles/panels.css +1701 -0
- package/styles/shell.css +1423 -0
- package/styles/tokens.css +151 -0
- package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
- package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
- package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
- package/dist/chunks/files-j8a1t06k.js +0 -50
- package/dist/chunks/files-j8a1t06k.js.map +0 -9
- package/dist/chunks/git-panel-xtq5z81x.js +0 -28
- package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
- package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
- package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
- package/dist/chunks/settings-modal-17ttr96g.js +0 -29
- package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
- package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
- package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
- package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
- package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
- package/dist/chunks/studio-0xgs7xrs.js +0 -429
- package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
- package/dist/chunks/studio-4d8hzcz5.js +0 -3427
- package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
- package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-jm0z592w.js.map +0 -115
- package/dist/chunks/studio-m2t4mdg5.js +0 -1854
- package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
- package/dist/chunks/studio-n4p11dfy.js +0 -853
- package/dist/chunks/studio-n4p11dfy.js.map +0 -12
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-sjh1wt1a.js +0 -3744
- package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
- package/dist/chunks/studio-v7jkjqng.js.map +0 -46
- package/dist/chunks/studio-xq0t7n5t.js +0 -140
- package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
- package/dist/chunks/studio-xze4zrs1.js.map +0 -10
- package/dist/chunks/studio-yfcstdzy.js +0 -399
- package/dist/chunks/studio-yfcstdzy.js.map +0 -12
- package/dist/chunks/studio-zp7jncs8.js +0 -469
- package/dist/chunks/studio-zp7jncs8.js.map +0 -14
- package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
- package/src/browse/browse-modal.ts +0 -69
- package/src/browse/browse.ts +0 -904
- package/src/canvas/canvas-diff.ts +0 -216
- package/src/canvas/nested-site-style.ts +0 -58
- package/src/new-project/design-fields.ts +0 -264
- package/src/new-project/templates.ts +0 -61
- package/src/panels/tab-bar.ts +0 -548
- package/src/settings/settings-modal.ts +0 -203
- /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
- /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
- /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
- /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
- /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
- /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
- /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
- /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
|
@@ -24,6 +24,11 @@ interface BuildSystemPromptOptions {
|
|
|
24
24
|
projectRoot?: string | undefined;
|
|
25
25
|
/** Whether a project is open. Defaults to `!!projectRoot` (kept explicit for tests). */
|
|
26
26
|
hasProject?: boolean | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* See {@link AiToolState.treeEditable}. Defaults to true so a caller with no registry — the
|
|
29
|
+
* prompt-shape tests — advertises the same list it always did.
|
|
30
|
+
*/
|
|
31
|
+
treeEditable?: boolean | undefined;
|
|
27
32
|
/** Project-relative file paths for the inventory section (project modes; capped). */
|
|
28
33
|
fileInventory?: string[] | undefined;
|
|
29
34
|
}
|
|
@@ -34,7 +39,20 @@ const FILE_INVENTORY_CAP = 100;
|
|
|
34
39
|
// ─── Tool tiers (single source of truth for prompt AND gating) ──────────────
|
|
35
40
|
|
|
36
41
|
/** Studio state a tool tier requires. Used by the gating registry AND the prompt tool list. */
|
|
37
|
-
|
|
42
|
+
/**
|
|
43
|
+
* `document-tree` is `document` plus the condition the HUMAN's equivalent verbs already carry.
|
|
44
|
+
*
|
|
45
|
+
* `Command.aiTool`'s contract is "the human's gate and the agent's gate stay one predicate", and
|
|
46
|
+
* these two gates were not one. The registry's `selection.delete` / `duplicate` / `moveUp` require
|
|
47
|
+
* `editor.kind === "canvas"` because the Outline renders whatever the active tab's document is —
|
|
48
|
+
* with Project Settings open, `project.json` drawn as a layer tree. The assistant's tier asked only
|
|
49
|
+
* whether a tab existed, so in that exact state the agent was advertised `remove_node` and
|
|
50
|
+
* `move_node` and executed them against the file that defines the project, while the human's
|
|
51
|
+
* `delete_node` was refused. `remove_node` self-refuses only the document root (`path.length < 2`),
|
|
52
|
+
* a weaker test than `structurallyEditable`, so a repeater template or `$switch` case was removable
|
|
53
|
+
* by the agent and not by the person.
|
|
54
|
+
*/
|
|
55
|
+
export type AiToolTier = "no-project" | "project" | "document" | "document-tree";
|
|
38
56
|
|
|
39
57
|
export interface AiToolInfo {
|
|
40
58
|
name: string;
|
|
@@ -108,63 +126,79 @@ export const AI_TOOL_TIERS: AiToolInfo[] = [
|
|
|
108
126
|
},
|
|
109
127
|
{
|
|
110
128
|
name: "set_property",
|
|
111
|
-
tier: "document",
|
|
129
|
+
tier: "document-tree",
|
|
112
130
|
blurb:
|
|
113
131
|
"set_property(path, key, value) — set or remove a property on the node at path (tagName, textContent, className, style, attributes, $props…). Pass value: null to remove.",
|
|
114
132
|
},
|
|
115
133
|
{
|
|
116
134
|
name: "set_style",
|
|
117
|
-
tier: "document",
|
|
135
|
+
tier: "document-tree",
|
|
118
136
|
blurb:
|
|
119
137
|
'set_style(path, property, value) — set or remove a CSS style property (camelCase) on a node. Values as strings: "10px", "var(--color-accent)". Pass value: null to remove.',
|
|
120
138
|
},
|
|
121
139
|
{
|
|
122
140
|
name: "set_text",
|
|
123
|
-
tier: "document",
|
|
141
|
+
tier: "document-tree",
|
|
124
142
|
blurb: 'set_text(path, value) — convenient alias for set_property with key: "textContent".',
|
|
125
143
|
},
|
|
126
144
|
{
|
|
127
145
|
name: "add_child",
|
|
128
|
-
tier: "document",
|
|
146
|
+
tier: "document-tree",
|
|
129
147
|
blurb:
|
|
130
148
|
"add_child(parentPath, index, node) — insert a new node into the children of parentPath at index.",
|
|
131
149
|
},
|
|
132
150
|
{
|
|
133
151
|
name: "remove_node",
|
|
134
|
-
tier: "document",
|
|
152
|
+
tier: "document-tree",
|
|
135
153
|
blurb: "remove_node(path) — remove the node at path.",
|
|
136
154
|
},
|
|
137
155
|
{
|
|
138
156
|
name: "move_node",
|
|
139
|
-
tier: "document",
|
|
157
|
+
tier: "document-tree",
|
|
140
158
|
blurb: "move_node(fromPath, toParentPath, toIndex) — move a node from one location to another.",
|
|
141
159
|
},
|
|
142
160
|
{
|
|
143
161
|
name: "add_state",
|
|
144
|
-
tier: "document",
|
|
162
|
+
tier: "document-tree",
|
|
145
163
|
blurb:
|
|
146
164
|
"add_state(key, value) — add a reactive state variable under the document's 'state' object. Value can be scalar, typed, computed, function, or data source.",
|
|
147
165
|
},
|
|
148
166
|
{
|
|
149
167
|
name: "update_state",
|
|
150
|
-
tier: "document",
|
|
168
|
+
tier: "document-tree",
|
|
151
169
|
blurb: "update_state(key, value) — update or remove (value: null) an existing state variable.",
|
|
152
170
|
},
|
|
153
171
|
];
|
|
154
172
|
|
|
173
|
+
/** The studio facts a tier is decided by. An object because four positional booleans is a trap. */
|
|
174
|
+
export interface AiToolState {
|
|
175
|
+
hasProject: boolean;
|
|
176
|
+
hasDocument: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* The open document is an element tree the canvas is editing — `editor.kind === "canvas"`.
|
|
179
|
+
*
|
|
180
|
+
* Read from the COMMAND CONTEXT the human's verbs are evaluated against, not recomputed here, so
|
|
181
|
+
* the two gates are one predicate rather than two that agree until someone edits one of them.
|
|
182
|
+
*/
|
|
183
|
+
treeEditable: boolean;
|
|
184
|
+
}
|
|
185
|
+
|
|
155
186
|
/**
|
|
156
187
|
* Whether a tier's tools are active for the given studio state. Shared semantics with the gating
|
|
157
188
|
* registry: bootstrap tools vanish once a project opens; project tools need a project; document
|
|
158
189
|
* tools need an active document (even in single-file mode without a project).
|
|
159
190
|
*/
|
|
160
|
-
export function tierActive(tier: AiToolTier,
|
|
191
|
+
export function tierActive(tier: AiToolTier, state: AiToolState): boolean {
|
|
161
192
|
if (tier === "no-project") {
|
|
162
|
-
return !hasProject;
|
|
193
|
+
return !state.hasProject;
|
|
163
194
|
}
|
|
164
195
|
if (tier === "project") {
|
|
165
|
-
return hasProject;
|
|
196
|
+
return state.hasProject;
|
|
197
|
+
}
|
|
198
|
+
if (tier === "document-tree") {
|
|
199
|
+
return state.hasDocument && state.treeEditable;
|
|
166
200
|
}
|
|
167
|
-
return hasDocument;
|
|
201
|
+
return state.hasDocument;
|
|
168
202
|
}
|
|
169
203
|
|
|
170
204
|
// ─── Jx Schema Reference (condensed) ────────────────────────────────────────
|
|
@@ -525,12 +559,17 @@ export function buildSystemPrompt({
|
|
|
525
559
|
components,
|
|
526
560
|
projectRoot,
|
|
527
561
|
hasProject = Boolean(projectRoot),
|
|
562
|
+
treeEditable = true,
|
|
528
563
|
fileInventory,
|
|
529
564
|
}: BuildSystemPromptOptions = {}) {
|
|
530
565
|
const hasDocument = Boolean(document);
|
|
531
566
|
|
|
532
567
|
// 1. Role, state-appropriate workflow, and the tool list for the current state.
|
|
533
|
-
|
|
568
|
+
// The list the model is TOLD about and the list the gate will honour are the same filter, so a
|
|
569
|
+
// Refusal is never a surprise to it.
|
|
570
|
+
const toolList = AI_TOOL_TIERS.filter((t) =>
|
|
571
|
+
tierActive(t.tier, { hasDocument, hasProject, treeEditable }),
|
|
572
|
+
)
|
|
534
573
|
.map((t) => `- ${t.blurb}`)
|
|
535
574
|
.join("\n");
|
|
536
575
|
|
package/src/services/ai-tools.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "../tabs/transact";
|
|
28
28
|
import type { JxNodeValue } from "../tabs/transact";
|
|
29
29
|
import { validateDoc } from "./jx-validate";
|
|
30
|
+
import { recordWrite } from "./ai-writes";
|
|
30
31
|
import { flagHardcodedTokens, formatTokenHints } from "./token-lint";
|
|
31
32
|
|
|
32
33
|
const PATH_DESCRIPTION =
|
|
@@ -127,7 +128,24 @@ async function applyAndValidate(
|
|
|
127
128
|
const before = new Set(await validate(rawBefore));
|
|
128
129
|
const renderOkBefore = renderCheck ? await renderCheck(rawBefore) : { ok: true };
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
/* AND THE WRITE CAN BE REFUSED, in which case the model must be told rather than congratulated.
|
|
132
|
+
The collab gate pauses structural editing while source is canonical, and this went on to
|
|
133
|
+
record a ledger entry ("Changed N files") and validate the UNCHANGED document — which produces
|
|
134
|
+
no new errors, so the tool answered `success: true`. The model then built its next three edits
|
|
135
|
+
on a change that never existed. A refusal is an ordinary state of the room, so it is an error
|
|
136
|
+
result with a reason the model can act on (wait, or ask the author), not a thrown exception. */
|
|
137
|
+
if (!transactDoc(tab, mutationFn)) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error:
|
|
141
|
+
"The document is frozen: a collaborator is editing its source, so structural edits are " +
|
|
142
|
+
"paused. Nothing was changed. Try again once source editing ends.",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/* One ledger entry per mutation, so the panel's "Changed N files" counts documents the model
|
|
146
|
+
touched rather than sentences it wrote (§7.4). `disk: false` is the load-bearing half: this
|
|
147
|
+
went through transactDoc, so the tab's history — and "Restore to here" — can reach it. */
|
|
148
|
+
recordWrite({ disk: false, ok: true, path: tab.documentPath ?? "(untitled)", tool: summary });
|
|
131
149
|
|
|
132
150
|
const rawAfter = toRaw(tab.doc.document);
|
|
133
151
|
const after = await validate(rawAfter);
|
|
@@ -734,14 +752,23 @@ export function registerAiTools(
|
|
|
734
752
|
}
|
|
735
753
|
try {
|
|
736
754
|
await saveFile(relPath, JSON.stringify(content, null, 2));
|
|
755
|
+
recordWrite({ disk: true, ok: true, path: relPath, tool: "create_component" });
|
|
737
756
|
return {
|
|
738
757
|
success: true,
|
|
739
758
|
summary: await reconcileAfterWrite(relPath, `Created component at "${relPath}".`),
|
|
740
759
|
};
|
|
741
760
|
} catch (error) {
|
|
761
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
762
|
+
recordWrite({
|
|
763
|
+
disk: true,
|
|
764
|
+
error: message,
|
|
765
|
+
ok: false,
|
|
766
|
+
path: relPath,
|
|
767
|
+
tool: "create_component",
|
|
768
|
+
});
|
|
742
769
|
return {
|
|
743
770
|
success: false,
|
|
744
|
-
error: `Failed to write file: ${
|
|
771
|
+
error: `Failed to write file: ${message}`,
|
|
745
772
|
};
|
|
746
773
|
}
|
|
747
774
|
},
|
|
@@ -802,14 +829,23 @@ export function registerAiTools(
|
|
|
802
829
|
}
|
|
803
830
|
try {
|
|
804
831
|
await saveFile(relPath, JSON.stringify(content, null, 2));
|
|
832
|
+
recordWrite({ disk: true, ok: true, path: relPath, tool: "create_page" });
|
|
805
833
|
return {
|
|
806
834
|
success: true,
|
|
807
835
|
summary: await reconcileAfterWrite(relPath, `Created page at "${relPath}".`),
|
|
808
836
|
};
|
|
809
837
|
} catch (error) {
|
|
838
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
839
|
+
recordWrite({
|
|
840
|
+
disk: true,
|
|
841
|
+
error: message,
|
|
842
|
+
ok: false,
|
|
843
|
+
path: relPath,
|
|
844
|
+
tool: "create_page",
|
|
845
|
+
});
|
|
810
846
|
return {
|
|
811
847
|
success: false,
|
|
812
|
-
error: `Failed to write file: ${
|
|
848
|
+
error: `Failed to write file: ${message}`,
|
|
813
849
|
};
|
|
814
850
|
}
|
|
815
851
|
},
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ai-writes.ts — what the assistant changed, per turn, and whether you can take it back.
|
|
3
|
+
*
|
|
4
|
+
* §7.4 (AI honesty). Two facts about an assistant turn were legible only to the model:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Which files it changed.** The loop collected `result.summary` strings and fed them back into
|
|
7
|
+
* the conversation; the human got a paragraph of prose in which "I updated the hero" and "I
|
|
8
|
+
* rewrote layouts/base.json" are the same shape of sentence.
|
|
9
|
+
* 2. **Which of those changes undo can reach.** Document tools mutate through `transactDoc`, so ⌘Z
|
|
10
|
+
* covers them. `write_file` and `write_project_config` go straight to disk through the platform,
|
|
11
|
+
* where there is no history and never was — and the caveat was appended to the MODEL-facing tool
|
|
12
|
+
* summary, telling the party that cannot press ⌘Z and not the party that can.
|
|
13
|
+
*
|
|
14
|
+
* A ledger rather than a parse of the summaries, because a summary is prose the model reads and a
|
|
15
|
+
* ledger is a record the UI renders — deriving one from the other would make the panel's honesty
|
|
16
|
+
* depend on the wording of a sentence written for somebody else.
|
|
17
|
+
*
|
|
18
|
+
* The tools record; the loop bounds the turn; the panel renders. Nothing here knows about lit, and
|
|
19
|
+
* nothing here imports the loop, so a tool can be honest without either.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** One change the assistant made, as recorded by the tool that made it. */
|
|
23
|
+
export interface AiWrite {
|
|
24
|
+
/** Project-relative path, or the document path for an in-editor mutation. */
|
|
25
|
+
path: string;
|
|
26
|
+
/** The tool that did it — the command title the chip renders. */
|
|
27
|
+
tool: string;
|
|
28
|
+
/**
|
|
29
|
+
* True when the change went to disk with no transaction behind it.
|
|
30
|
+
*
|
|
31
|
+
* This is the undo caveat, as a fact rather than a sentence: a `disk` write is NOT reachable by
|
|
32
|
+
* ⌘Z, by the tab's history, or by "Restore to here". The panel says so, in those words, to the
|
|
33
|
+
* person who would otherwise press ⌘Z and believe it worked.
|
|
34
|
+
*/
|
|
35
|
+
disk: boolean;
|
|
36
|
+
/** False when the tool reported a failure — a listed attempt that changed nothing. */
|
|
37
|
+
ok: boolean;
|
|
38
|
+
/** Why it failed, when it did. */
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Every write recorded during one assistant turn, in the order the tools made them. */
|
|
43
|
+
export interface AiTurn {
|
|
44
|
+
/** The id of the assistant message the turn produced — how the panel finds its own ledger. */
|
|
45
|
+
id: string;
|
|
46
|
+
writes: AiWrite[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Completed turns, oldest first. Bounded: a session's history is the session store's job. */
|
|
50
|
+
const turns: AiTurn[] = [];
|
|
51
|
+
|
|
52
|
+
/** How many turns of ledger are kept. Older ones drop their summary; the messages remain. */
|
|
53
|
+
export const MAX_TURNS = 50;
|
|
54
|
+
|
|
55
|
+
let open: AiTurn | null = null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Start recording. Called by the agent loop once per user turn.
|
|
59
|
+
*
|
|
60
|
+
* Idempotent on the same id so a nested or re-entered loop cannot split one turn's writes across
|
|
61
|
+
* two ledgers — which is the same class of bug as the batch that was opened against whichever tab
|
|
62
|
+
* happened to be active.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} id
|
|
65
|
+
*/
|
|
66
|
+
export function beginTurn(id: string): void {
|
|
67
|
+
if (open?.id === id) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
open = { id, writes: [] };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Record one change. A no-op when no turn is open, so a tool invoked outside the loop — a command,
|
|
75
|
+
* a test — costs nothing and reports nothing.
|
|
76
|
+
*
|
|
77
|
+
* @param {AiWrite} write
|
|
78
|
+
*/
|
|
79
|
+
export function recordWrite(write: AiWrite): void {
|
|
80
|
+
open?.writes.push(write);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Close the open turn and file it under `id`.
|
|
85
|
+
*
|
|
86
|
+
* The id arrives at the END because it is the assistant message's id, which does not exist when the
|
|
87
|
+
* turn starts — the loop opens the turn before it knows what the model will say.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} id - The assistant message the turn produced
|
|
90
|
+
* @returns {AiWrite[]} What was recorded
|
|
91
|
+
*/
|
|
92
|
+
export function endTurn(id: string): AiWrite[] {
|
|
93
|
+
if (!open) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const turn: AiTurn = { id, writes: open.writes };
|
|
97
|
+
open = null;
|
|
98
|
+
if (turn.writes.length === 0) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
turns.push(turn);
|
|
102
|
+
while (turns.length > MAX_TURNS) {
|
|
103
|
+
turns.shift();
|
|
104
|
+
}
|
|
105
|
+
return turn.writes;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* What the turn that produced this assistant message changed. Empty for a turn that changed
|
|
110
|
+
* nothing, which is why the panel renders no summary at all rather than "Changed 0 files".
|
|
111
|
+
*
|
|
112
|
+
* @param {string} id
|
|
113
|
+
* @returns {AiWrite[]}
|
|
114
|
+
*/
|
|
115
|
+
export function writesForTurn(id: string): AiWrite[] {
|
|
116
|
+
return turns.find((turn) => turn.id === id)?.writes ?? [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The one-line summary the panel puts above the expander.
|
|
121
|
+
*
|
|
122
|
+
* Counts DISTINCT paths, not writes: a turn that edits the same document six times changed one
|
|
123
|
+
* file, and saying "Changed 6 files" would be the same dishonesty in the other direction. Disk
|
|
124
|
+
* writes are called out separately because they are the ones undo cannot reach.
|
|
125
|
+
*
|
|
126
|
+
* @param {AiWrite[]} writes
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
export function summarizeWrites(writes: AiWrite[]): string {
|
|
130
|
+
const applied = writes.filter((w) => w.ok);
|
|
131
|
+
const paths = new Set(applied.map((w) => w.path));
|
|
132
|
+
const failed = writes.length - applied.length;
|
|
133
|
+
if (paths.size === 0) {
|
|
134
|
+
return failed > 0 ? `${failed} change${failed === 1 ? "" : "s"} failed` : "";
|
|
135
|
+
}
|
|
136
|
+
const files = `Changed ${paths.size} file${paths.size === 1 ? "" : "s"}`;
|
|
137
|
+
const diskPaths = new Set(applied.filter((w) => w.disk).map((w) => w.path));
|
|
138
|
+
const parts = [files];
|
|
139
|
+
if (diskPaths.size > 0) {
|
|
140
|
+
parts.push(
|
|
141
|
+
`${diskPaths.size} written to disk — undo cannot reach ${diskPaths.size === 1 ? "it" : "them"}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (failed > 0) {
|
|
145
|
+
parts.push(`${failed} failed`);
|
|
146
|
+
}
|
|
147
|
+
return parts.join(" · ");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Drop every ledger. For tests and for the "new chat" / "close project" paths. */
|
|
151
|
+
export function resetAiWrites(): void {
|
|
152
|
+
turns.splice(0);
|
|
153
|
+
open = null;
|
|
154
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app's live region: one element, one call site, everything the app says out loud.
|
|
3
|
+
*
|
|
4
|
+
* **Why this exists.** `notify.ts` is the app's only way to say what happened, and its default tier
|
|
5
|
+
* routes **error ⇒ problem**. But the `role="status"` region lived on the _toast_ host, and the
|
|
6
|
+
* Problems panel had none at all — so a **failure reached no live region whatsoever**. The app
|
|
7
|
+
* would post "Save failed", show it in a panel, and a screen-reader user would be told nothing. A
|
|
8
|
+
* panel-local region would not have fixed it either: the Problems list lives in the Bottom dock,
|
|
9
|
+
* and a region inside a hidden tab announces nothing.
|
|
10
|
+
*
|
|
11
|
+
* So the announcer is not a panel's concern. It is called from {@link notify} itself — one call
|
|
12
|
+
* site, so a record cannot be posted without being announced, and a future host gets announcements
|
|
13
|
+
* for free rather than having to remember.
|
|
14
|
+
*
|
|
15
|
+
* **Two regions, because politeness is not a style choice** (WAI-ARIA §5.2.9). `assertive`
|
|
16
|
+
* interrupts whatever the reader is listening to, which is right for a failure and rude for a save.
|
|
17
|
+
* `polite` waits for a pause. Mixing both in one region does not work — the attribute is read when
|
|
18
|
+
* the region is created, not when the text changes — so there are two, and the severity picks.
|
|
19
|
+
*
|
|
20
|
+
* **The text is cleared, then set on a later frame.** A live region announces a _change_; writing
|
|
21
|
+
* the same string twice is not one, so a repeated failure would be silent. Clearing first makes
|
|
22
|
+
* every post a change.
|
|
23
|
+
*
|
|
24
|
+
* @docs studio/interface/problems-and-progress
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** How urgently a message interrupts. Errors interrupt; everything else waits for a pause. */
|
|
28
|
+
export type Politeness = "assertive" | "polite";
|
|
29
|
+
|
|
30
|
+
const REGION_ID: Readonly<Record<Politeness, string>> = {
|
|
31
|
+
assertive: "jx-live-assertive",
|
|
32
|
+
polite: "jx-live-polite",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Find (or create) a live region.
|
|
37
|
+
*
|
|
38
|
+
* It is appended to `<body>` rather than to any panel, and it is visually hidden with the clip-rect
|
|
39
|
+
* idiom rather than `display: none` — a display-none region is removed from the accessibility tree
|
|
40
|
+
* entirely and announces nothing, which is the classic way to ship a live region that does not
|
|
41
|
+
* work.
|
|
42
|
+
*/
|
|
43
|
+
function region(politeness: Politeness): HTMLElement | null {
|
|
44
|
+
if (typeof document === "undefined") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const id = REGION_ID[politeness];
|
|
48
|
+
const existing = document.querySelector<HTMLElement>(`#${id}`);
|
|
49
|
+
if (existing) {
|
|
50
|
+
return existing;
|
|
51
|
+
}
|
|
52
|
+
const node = document.createElement("div");
|
|
53
|
+
node.id = id;
|
|
54
|
+
node.setAttribute("aria-live", politeness);
|
|
55
|
+
/* `status` for polite and `alert` for assertive: the implicit roles, so an assistive technology
|
|
56
|
+
that reads roles rather than the attribute agrees with one that does the opposite. */
|
|
57
|
+
node.setAttribute("role", politeness === "assertive" ? "alert" : "status");
|
|
58
|
+
node.setAttribute("aria-atomic", "true");
|
|
59
|
+
node.style.cssText =
|
|
60
|
+
"position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;" +
|
|
61
|
+
"clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0";
|
|
62
|
+
document.body.append(node);
|
|
63
|
+
return node;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Say something out loud.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} message What to announce. Empty strings are ignored — a live region that receives
|
|
70
|
+
* one announces nothing, so posting it only hides a caller's bug.
|
|
71
|
+
* @param {Politeness} [politeness] Defaults to `polite`.
|
|
72
|
+
*/
|
|
73
|
+
export function announce(message: string, politeness: Politeness = "polite"): void {
|
|
74
|
+
if (message === "") {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const node = region(politeness);
|
|
78
|
+
if (!node) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
node.textContent = "";
|
|
82
|
+
/* A live region announces a CHANGE. Writing the same string twice is not one, so a second
|
|
83
|
+
identical failure would be silent without the clear-then-set on a later turn. */
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
node.textContent = message;
|
|
86
|
+
}, 0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Remove both regions. Tests only — a leaked region makes the next test's assertions ambiguous. */
|
|
90
|
+
export function resetAnnouncer(): void {
|
|
91
|
+
if (typeof document === "undefined") {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const id of Object.values(REGION_ID)) {
|
|
95
|
+
document.querySelector(`#${id}`)?.remove();
|
|
96
|
+
}
|
|
97
|
+
}
|