@jxsuite/studio 2.0.0 → 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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
- 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 +34 -9
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +270 -262
- 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 -120
- 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 +75 -2
- 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 +113 -0
- 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-qct0gt8f.js +0 -531
- package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
- package/dist/chunks/files-02zn5y24.js +0 -50
- package/dist/chunks/files-02zn5y24.js.map +0 -9
- package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
- package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
- package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
- package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
- package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
- package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-568nx4t5.js +0 -469
- package/dist/chunks/studio-568nx4t5.js.map +0 -14
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-asvf0j9e.js +0 -429
- package/dist/chunks/studio-asvf0j9e.js.map +0 -13
- package/dist/chunks/studio-d77qm3pk.js +0 -3427
- package/dist/chunks/studio-d77qm3pk.js.map +0 -26
- package/dist/chunks/studio-j6gqyjf4.js +0 -1854
- package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
- package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-rq0vxb19.js +0 -3744
- package/dist/chunks/studio-rq0vxb19.js.map +0 -22
- package/dist/chunks/studio-wd6ewdvd.js +0 -853
- package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
- 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-ygzav7z5.js.map +0 -46
- package/dist/chunks/studio-zmy7241e.js +0 -399
- package/dist/chunks/studio-zmy7241e.js.map +0 -12
- 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-2c188n41.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-hhj92gcr.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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notify.ts — the third record type (plan §1), and the app's only way to say what happened.
|
|
3
|
+
*
|
|
4
|
+
* What this replaces: 78 `statusMessage()` call sites — 26 failures and 52 successes — rendered in
|
|
5
|
+
* identical 11px grey text in a 24px strip and destroyed after 3000 ms. A failure and a save looked
|
|
6
|
+
* the same, neither could be acted on, and both were gone before a reader who had looked away could
|
|
7
|
+
* read them. `statusMessage` is deleted, not wrapped; `scripts/check-styles.ts` bans the name from
|
|
8
|
+
* `src/` so it cannot regrow.
|
|
9
|
+
*
|
|
10
|
+
* **A notification is a record, and its lifetime is chosen by the action it requires** (§7.1):
|
|
11
|
+
*
|
|
12
|
+
* | Tier | Host | Lifetime | Use when |
|
|
13
|
+
* | --------- | -------------------------- | ------------------ | ------------------------------ |
|
|
14
|
+
* | `toast` | the fourth overlay layer | timed, dismissible | reversible, or needs no action |
|
|
15
|
+
* | `problem` | the Bottom dock's Problems | until it is fixed | it must be fixed |
|
|
16
|
+
*
|
|
17
|
+
* §7.1 names a third tier — **inline**, the `error` slot on `ui/field-row.ts`'s `renderFieldRow`.
|
|
18
|
+
* It is deliberately NOT a member of {@link NotificationTier}: an inline error is a value a control
|
|
19
|
+
* renders next to the field the user is editing, not a record the app posts to a host, and it has
|
|
20
|
+
* no store to live in. Declaring a tier here whose only possible outcome is being dropped on the
|
|
21
|
+
* floor would be exactly the silence this module exists to end, so the union has two members and
|
|
22
|
+
* the third tier arrives as `renderFieldRow`'s own parameter (P4.4).
|
|
23
|
+
*
|
|
24
|
+
* **Recovery is a command id, not a per-call-site closure.** `notify.error("Save failed", { action:
|
|
25
|
+
* "file.save" })` gives the toast and the Problems row a Retry button whose label, availability,
|
|
26
|
+
* disabled reason and keyboard chord all come off the command record — which is why this lives
|
|
27
|
+
* beside the registry rather than inside any one surface. A call site that wanted a Retry used to
|
|
28
|
+
* have to invent a button; now it names a capability that already exists.
|
|
29
|
+
*
|
|
30
|
+
* Nothing here renders. `ui/layers.ts` hosts the toasts and the Bottom dock hosts the Problems
|
|
31
|
+
* list; both read the two reactive arrays below, so a new surface can render notifications without
|
|
32
|
+
* this module learning it exists.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { reactive } from "../reactivity";
|
|
36
|
+
import { announce } from "./announce";
|
|
37
|
+
import { now } from "./clock";
|
|
38
|
+
|
|
39
|
+
/** How bad it is. Chooses the icon, the colour, and the default tier. */
|
|
40
|
+
export const SEVERITIES = ["success", "info", "warn", "error"] as const;
|
|
41
|
+
|
|
42
|
+
export type Severity = (typeof SEVERITIES)[number];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The two hosted lifetimes.
|
|
46
|
+
*
|
|
47
|
+
* A tier is a HOST, not a rendering preference: a record whose tier is `problem` is one the app
|
|
48
|
+
* promises to keep until somebody fixes it, and a record whose tier is `toast` is one it promises
|
|
49
|
+
* to take away. See the module doc for why `inline` is not here.
|
|
50
|
+
*/
|
|
51
|
+
export const TIERS = ["toast", "problem"] as const;
|
|
52
|
+
|
|
53
|
+
export type NotificationTier = (typeof TIERS)[number];
|
|
54
|
+
|
|
55
|
+
/** The default tier per severity. A failure must be fixed; everything else may be taken away. */
|
|
56
|
+
const DEFAULT_TIER: Readonly<Record<Severity, NotificationTier>> = {
|
|
57
|
+
error: "problem",
|
|
58
|
+
info: "toast",
|
|
59
|
+
success: "toast",
|
|
60
|
+
warn: "toast",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* How long a toast rests before it is taken away, by severity.
|
|
65
|
+
*
|
|
66
|
+
* §7.1 caps the band at 4–8s. A warning that nobody has to act on still deserves the long end of
|
|
67
|
+
* it, because it is the one a reader is most likely to have looked away from.
|
|
68
|
+
*/
|
|
69
|
+
export const TOAST_LIFETIME_MS: Readonly<Record<Severity, number>> = {
|
|
70
|
+
error: 8000,
|
|
71
|
+
info: 4000,
|
|
72
|
+
success: 4000,
|
|
73
|
+
warn: 8000,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** What a caller may say about an outcome beyond its severity and its one line. */
|
|
77
|
+
export interface NotifyOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Command id offering recovery — the Retry / Fix button.
|
|
80
|
+
*
|
|
81
|
+
* A command id rather than a callback so the button's LABEL is the command's title, its enabled
|
|
82
|
+
* state is the command's `enablement`, and its refusal sentence is the command's `requires`. Four
|
|
83
|
+
* facts a closure could carry none of.
|
|
84
|
+
*/
|
|
85
|
+
action?: string;
|
|
86
|
+
/** Args passed to {@link NotifyOptions.action} when it runs. */
|
|
87
|
+
actionArgs?: Record<string, unknown>;
|
|
88
|
+
/** The long form — a stack, a captured log, a validator's output. Rendered in Problems only. */
|
|
89
|
+
detail?: string;
|
|
90
|
+
/** Who reported it: "Save", "Source Control", "Canvas", "Assistant". Groups the Problems list. */
|
|
91
|
+
source?: string;
|
|
92
|
+
/** The file or document the outcome is about, so Problems can click through to it. */
|
|
93
|
+
path?: string;
|
|
94
|
+
/** Override the tier {@link DEFAULT_TIER} would pick — a warning that MUST be fixed. */
|
|
95
|
+
tier?: NotificationTier;
|
|
96
|
+
/**
|
|
97
|
+
* Deduplication key. A second notification with the same key REPLACES the first rather than
|
|
98
|
+
* stacking beside it — the file watcher that fails once a second is one problem, not sixty.
|
|
99
|
+
*/
|
|
100
|
+
key?: string;
|
|
101
|
+
/** Override the toast's rest time. Ignored for the `problem` tier, which has no timer. */
|
|
102
|
+
timeoutMs?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One outcome, as recorded. Frozen: a host renders it, nothing edits it after the fact. */
|
|
106
|
+
export interface Notification {
|
|
107
|
+
readonly id: string;
|
|
108
|
+
readonly severity: Severity;
|
|
109
|
+
readonly message: string;
|
|
110
|
+
readonly tier: NotificationTier;
|
|
111
|
+
/** Epoch ms from {@link now}, so a pinned clock pins the "2m ago" a Problems row renders. */
|
|
112
|
+
readonly at: number;
|
|
113
|
+
readonly action?: string;
|
|
114
|
+
readonly actionArgs?: Record<string, unknown>;
|
|
115
|
+
readonly detail?: string;
|
|
116
|
+
readonly source?: string;
|
|
117
|
+
readonly path?: string;
|
|
118
|
+
readonly key?: string;
|
|
119
|
+
/** Rest time for a toast; `0` means "hold until dismissed". Absent on a problem. */
|
|
120
|
+
readonly timeoutMs?: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let _seq = 0;
|
|
124
|
+
|
|
125
|
+
/** Monotonic within a page load, and stable enough to be a lit `repeat` key. */
|
|
126
|
+
function nextId(): string {
|
|
127
|
+
_seq += 1;
|
|
128
|
+
return `n${_seq}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The live toast stack, oldest first. Reactive, so the host repaints by reading it.
|
|
133
|
+
*
|
|
134
|
+
* Exported as the array rather than behind a getter because that is what makes it trackable: a lit
|
|
135
|
+
* render effect that reads `toasts.length` re-runs when one arrives, with no subscription to
|
|
136
|
+
* remember to release.
|
|
137
|
+
*/
|
|
138
|
+
export const toasts: Notification[] = reactive([]);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Everything that must be fixed, oldest first — what the Bottom dock's Problems tab renders and
|
|
142
|
+
* what the rail badge and the status bar count.
|
|
143
|
+
*
|
|
144
|
+
* The shape is the contract for that surface: each entry has an `id` to key on, a `severity` to
|
|
145
|
+
* sort and colour by, a `source` to group by, a `path` to click through to, an optional `detail`
|
|
146
|
+
* for the expanded row, and an optional `action` command id for its Fix / Retry button.
|
|
147
|
+
*/
|
|
148
|
+
export const problems: Notification[] = reactive([]);
|
|
149
|
+
|
|
150
|
+
/** The most toasts on screen at once; the oldest is retired to make room. */
|
|
151
|
+
export const MAX_TOASTS = 4;
|
|
152
|
+
|
|
153
|
+
/** Which array a tier lives in. */
|
|
154
|
+
function storeFor(tier: NotificationTier): Notification[] {
|
|
155
|
+
return tier === "problem" ? problems : toasts;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Record an outcome and return it.
|
|
160
|
+
*
|
|
161
|
+
* The record is returned so a caller that owns the outcome's life — an operation that retries and
|
|
162
|
+
* succeeds — can {@link dismiss} its own problem instead of leaving a fixed thing on the list.
|
|
163
|
+
*/
|
|
164
|
+
export function notify(
|
|
165
|
+
severity: Severity,
|
|
166
|
+
message: string,
|
|
167
|
+
options: NotifyOptions = {},
|
|
168
|
+
): Notification {
|
|
169
|
+
const tier = options.tier ?? DEFAULT_TIER[severity];
|
|
170
|
+
const record: Notification = {
|
|
171
|
+
at: now(),
|
|
172
|
+
id: nextId(),
|
|
173
|
+
message,
|
|
174
|
+
severity,
|
|
175
|
+
tier,
|
|
176
|
+
...(options.action === undefined ? {} : { action: options.action }),
|
|
177
|
+
...(options.actionArgs === undefined ? {} : { actionArgs: options.actionArgs }),
|
|
178
|
+
...(options.detail === undefined ? {} : { detail: options.detail }),
|
|
179
|
+
...(options.source === undefined ? {} : { source: options.source }),
|
|
180
|
+
...(options.path === undefined ? {} : { path: options.path }),
|
|
181
|
+
...(options.key === undefined ? {} : { key: options.key }),
|
|
182
|
+
...(tier === "toast" ? { timeoutMs: options.timeoutMs ?? TOAST_LIFETIME_MS[severity] } : {}),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const store = storeFor(tier);
|
|
186
|
+
if (record.key !== undefined) {
|
|
187
|
+
const at = store.findIndex((existing) => existing.key === record.key);
|
|
188
|
+
if (at !== -1) {
|
|
189
|
+
store.splice(at, 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
store.push(record);
|
|
193
|
+
if (tier === "toast") {
|
|
194
|
+
while (toasts.length > MAX_TOASTS) {
|
|
195
|
+
toasts.shift();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/*
|
|
199
|
+
* Announce from HERE, not from a host.
|
|
200
|
+
*
|
|
201
|
+
* The `role="status"` region lived on the toast host, and `error` defaults to the `problem` tier —
|
|
202
|
+
* so a failure reached no live region at all, and a Problems-panel region would still announce
|
|
203
|
+
* nothing while another Bottom-dock tab was showing. One call site is what makes "posted" and
|
|
204
|
+
* "announced" the same event: a new host gets announcements without knowing this module exists.
|
|
205
|
+
*
|
|
206
|
+
* `source` is included because a screen-reader user has no visual grouping to tell them where the
|
|
207
|
+
* message came from, which is exactly what the panel's own column shows everyone else.
|
|
208
|
+
*/
|
|
209
|
+
announce(
|
|
210
|
+
record.source === undefined ? record.message : `${record.source}: ${record.message}`,
|
|
211
|
+
severity === "error" ? "assertive" : "polite",
|
|
212
|
+
);
|
|
213
|
+
return record;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** `notify.success(msg, opts)` and its three siblings — the shape §7.1 names. */
|
|
217
|
+
function severityHelper(severity: Severity) {
|
|
218
|
+
return (message: string, options: NotifyOptions = {}) => notify(severity, message, options);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
notify.success = severityHelper("success");
|
|
222
|
+
notify.info = severityHelper("info");
|
|
223
|
+
notify.warn = severityHelper("warn");
|
|
224
|
+
notify.error = severityHelper("error");
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Take one notification off whichever list holds it. Returns whether it was there.
|
|
228
|
+
*
|
|
229
|
+
* One function for both stores because a caller holding a record should not have to remember which
|
|
230
|
+
* tier it landed in — the record says so.
|
|
231
|
+
*/
|
|
232
|
+
export function dismiss(id: string): boolean {
|
|
233
|
+
for (const store of [toasts, problems]) {
|
|
234
|
+
const at = store.findIndex((record) => record.id === id);
|
|
235
|
+
if (at !== -1) {
|
|
236
|
+
store.splice(at, 1);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Drop every problem a predicate accepts (all of them with no predicate). Returns how many went.
|
|
245
|
+
*
|
|
246
|
+
* The predicate form is what lets an operation clear its own previous failures when it starts again
|
|
247
|
+
* — `clearProblems((p) => p.source === "Save")` — so a fixed problem stops being listed without the
|
|
248
|
+
* fixer having kept the record's id.
|
|
249
|
+
*/
|
|
250
|
+
export function clearProblems(match?: (record: Notification) => boolean): number {
|
|
251
|
+
const kept = match ? problems.filter((record) => !match(record)) : [];
|
|
252
|
+
const removed = problems.length - kept.length;
|
|
253
|
+
problems.splice(0, problems.length, ...kept);
|
|
254
|
+
return removed;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** How many problems are outstanding, optionally of one severity — the badge and the status bar. */
|
|
258
|
+
export function problemCount(severity?: Severity): number {
|
|
259
|
+
return severity === undefined
|
|
260
|
+
? problems.length
|
|
261
|
+
: problems.filter((record) => record.severity === severity).length;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Clear both stores. For tests and for the "close project" path — a problem is about a project, and
|
|
266
|
+
* carrying one across a project switch would attribute it to the wrong repository.
|
|
267
|
+
*/
|
|
268
|
+
export function resetNotifications(): void {
|
|
269
|
+
toasts.splice(0);
|
|
270
|
+
problems.splice(0);
|
|
271
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup profiles — a named, defined cold start.
|
|
3
|
+
*
|
|
4
|
+
* "What does Studio look like to someone who has never opened it?" currently has no answer inside
|
|
5
|
+
* the app. Every consumer of that question reaches _around_ it: the screenshot runner injects
|
|
6
|
+
* `localStorage.clear()` before the first navigation, the verify recipe says "use a fresh browser
|
|
7
|
+
* profile", and a support thread says "try a private window". All three clear the whole ORIGIN —
|
|
8
|
+
* including keys Studio does not own — and none of them can be invoked from inside the running
|
|
9
|
+
* app.
|
|
10
|
+
*
|
|
11
|
+
* Three shipped things need it to be a real, addressable state:
|
|
12
|
+
*
|
|
13
|
+
* - **Per-project layout and session persistence** cannot be tested without a defined starting point;
|
|
14
|
+
* "whatever was in storage" is not one.
|
|
15
|
+
* - **Preferences: Reset Layout** is `applyProfile("fresh")` scoped to the layout keys, and the plan
|
|
16
|
+
* promises it is "always one action away".
|
|
17
|
+
* - **Support** wants "reproduce with a clean profile" to be a URL, not an instruction.
|
|
18
|
+
*
|
|
19
|
+
* The profile is selected by `?profile=<id>` and applied ONCE, at import time, before anything
|
|
20
|
+
* reads persisted state — {@link import("../shell").shell} calls this above its own `localStorage`
|
|
21
|
+
* read for exactly that reason. `?clock=` rides alongside because it is the other boot-time knob:
|
|
22
|
+
* both describe the world the app wakes up in, and neither changes behaviour after.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { pinClock } from "./clock";
|
|
26
|
+
|
|
27
|
+
/** What a profile resets. */
|
|
28
|
+
export type ProfileReset = "none" | "app-state";
|
|
29
|
+
|
|
30
|
+
export interface StartupProfile {
|
|
31
|
+
id: string;
|
|
32
|
+
/** Shown wherever a profile is offered (Preferences, the palette). */
|
|
33
|
+
description: string;
|
|
34
|
+
reset: ProfileReset;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The profiles Studio declares.
|
|
39
|
+
*
|
|
40
|
+
* `default` is not a no-op by omission — it is a named state, so "the app started normally" and
|
|
41
|
+
* "the profile was unrecognised" are different outcomes and the second one is loud.
|
|
42
|
+
*/
|
|
43
|
+
export const STARTUP_PROFILES: Readonly<Record<string, StartupProfile>> = {
|
|
44
|
+
default: {
|
|
45
|
+
description: "Resume whatever was persisted — layout, recents, settings, chat history.",
|
|
46
|
+
id: "default",
|
|
47
|
+
reset: "none",
|
|
48
|
+
},
|
|
49
|
+
fresh: {
|
|
50
|
+
description: "A first-run app: no layout, no recents, no settings, no history.",
|
|
51
|
+
id: "fresh",
|
|
52
|
+
reset: "app-state",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const DEFAULT_PROFILE_ID = "default";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Studio's own persisted keys, by prefix.
|
|
60
|
+
*
|
|
61
|
+
* Scoped rather than `localStorage.clear()`: the dev server, the docs site and Studio share an
|
|
62
|
+
* origin, and a profile that wipes a neighbour's state is a worse bug than the one it fixes. Every
|
|
63
|
+
* key Studio writes begins with `jx` followed by one of these separators — the panel widths, the
|
|
64
|
+
* recents, the GitHub token, the AI and Cloudflare settings, the chat sessions, the update
|
|
65
|
+
* dismissals and the canvas debug flag.
|
|
66
|
+
*/
|
|
67
|
+
const OWNED_KEY_PATTERN = /^jx[-._:]/;
|
|
68
|
+
|
|
69
|
+
/** Whether a storage key belongs to Studio and is therefore a profile's to clear. */
|
|
70
|
+
export function isStudioStorageKey(key: string): boolean {
|
|
71
|
+
return OWNED_KEY_PATTERN.test(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Drop every Studio-owned key from `localStorage`.
|
|
76
|
+
*
|
|
77
|
+
* @returns The keys removed, so a caller (Preferences, a test) can report what it did.
|
|
78
|
+
*/
|
|
79
|
+
export function clearStudioStorage(): string[] {
|
|
80
|
+
const store = globalThis.localStorage as Storage | undefined;
|
|
81
|
+
if (!store) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const doomed: string[] = [];
|
|
85
|
+
try {
|
|
86
|
+
for (let i = 0; i < store.length; i++) {
|
|
87
|
+
const key = store.key(i);
|
|
88
|
+
if (key && isStudioStorageKey(key)) {
|
|
89
|
+
doomed.push(key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const key of doomed) {
|
|
93
|
+
store.removeItem(key);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Storage unavailable (private mode, disabled cookies) — there is nothing persisted to reset.
|
|
97
|
+
}
|
|
98
|
+
return doomed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Apply a profile by id.
|
|
103
|
+
*
|
|
104
|
+
* @throws {RangeError} When the id is not declared. A misspelled profile that silently fell back to
|
|
105
|
+
* `default` would start the app in the state the caller was trying to avoid.
|
|
106
|
+
*/
|
|
107
|
+
export function applyProfile(id: string): StartupProfile {
|
|
108
|
+
const profile = STARTUP_PROFILES[id];
|
|
109
|
+
if (!profile) {
|
|
110
|
+
throw new RangeError(
|
|
111
|
+
`unknown startup profile "${id}"; declared: ${Object.keys(STARTUP_PROFILES).join(", ")}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (profile.reset === "app-state") {
|
|
115
|
+
clearStudioStorage();
|
|
116
|
+
}
|
|
117
|
+
return profile;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** What {@link applyStartupProfile} resolved. */
|
|
121
|
+
export interface StartupState {
|
|
122
|
+
profile: StartupProfile;
|
|
123
|
+
/** The instant the clock was pinned to, or `null` when it follows the wall clock. */
|
|
124
|
+
clock: number | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let _startup: StartupState | null = null;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Read `?profile=` and `?clock=` and apply them. Idempotent — the first call wins, so importing
|
|
131
|
+
* this from a second module cannot re-clear storage the app has already written to.
|
|
132
|
+
*
|
|
133
|
+
* @param search The query string to read. Defaults to the live location, and is passed explicitly
|
|
134
|
+
* by the tests, which have no navigation.
|
|
135
|
+
*/
|
|
136
|
+
export function applyStartupProfile(search?: string): StartupState {
|
|
137
|
+
if (_startup) {
|
|
138
|
+
return _startup;
|
|
139
|
+
}
|
|
140
|
+
const query = new URLSearchParams(search ?? globalThis.location?.search ?? "");
|
|
141
|
+
const profile = applyProfile(query.get("profile") ?? DEFAULT_PROFILE_ID);
|
|
142
|
+
const clockParam = query.get("clock");
|
|
143
|
+
_startup = { clock: clockParam === null ? null : pinClock(clockParam), profile };
|
|
144
|
+
return _startup;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** What the app started as. `null` until {@link applyStartupProfile} has run. */
|
|
148
|
+
export function startupState(): StartupState | null {
|
|
149
|
+
return _startup;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Forget the resolved startup state, so the next call re-reads the query. Tests only. */
|
|
153
|
+
export function resetStartupProfile(): void {
|
|
154
|
+
_startup = null;
|
|
155
|
+
}
|
|
Binary file
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
9
|
+
import { displayTagName } from "@jxsuite/schema/guards";
|
|
9
10
|
|
|
10
11
|
interface TokenLintFinding {
|
|
11
12
|
path: string;
|
|
@@ -112,14 +113,14 @@ export function flagHardcodedTokens(
|
|
|
112
113
|
for (let i = 0; i < el.children.length; i++) {
|
|
113
114
|
const child = el.children[i];
|
|
114
115
|
if (child && typeof child === "object") {
|
|
115
|
-
walk(child, `${pathPrefix} > ${child.tagName || `[${i}]`}`);
|
|
116
|
+
walk(child, `${pathPrefix} > ${displayTagName(child.tagName) || `[${i}]`}`);
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
const tag = doc.tagName || "root";
|
|
122
|
-
walk(doc, tag);
|
|
123
|
+
walk(doc, displayTagName(tag));
|
|
123
124
|
return findings;
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -5,6 +5,19 @@
|
|
|
5
5
|
* `transactDoc()`), feeds the results back as `tool` messages, and re-streams — up to a capped
|
|
6
6
|
* number of rounds (spec §10.2, ADR docs/ai-assistant-decision.md §6a).
|
|
7
7
|
*
|
|
8
|
+
* Two §7.4 honesty rules live here rather than in the panel, because they are properties of the RUN
|
|
9
|
+
* and a panel can only render what the run recorded:
|
|
10
|
+
*
|
|
11
|
+
* - **A partial success is not a failure.** Running out of rounds called `setError`, which paints
|
|
12
|
+
* the turn red and — in `chat-state.ts` — DELETES the streaming message, so a turn that applied
|
|
13
|
+
* four edits and then hit the cap reported as an error that had also erased its own account of
|
|
14
|
+
* the four edits. The cap is now an ordinary assistant message whenever anything was applied, and
|
|
15
|
+
* an error only when nothing was.
|
|
16
|
+
* - **A batch belongs to the tab it edits.** `beginBatch(getTab())` ran once, against whichever tab
|
|
17
|
+
* happened to be active when the loop started. A turn that then moved to a second document closed
|
|
18
|
+
* its batch against the FIRST tab, so the second document's edits got neither a history snapshot
|
|
19
|
+
* nor a collab publish. The loop re-anchors the batch whenever the active tab changes under it.
|
|
20
|
+
*
|
|
8
21
|
* @license MIT
|
|
9
22
|
*/
|
|
10
23
|
|
|
@@ -13,7 +26,8 @@ import type { StreamingClient } from "@jxsuite/ai/streaming-client";
|
|
|
13
26
|
import type { ToolRegistry } from "@jxsuite/ai/tools";
|
|
14
27
|
|
|
15
28
|
import type { Tab } from "../tabs/tab";
|
|
16
|
-
import { beginBatch, endBatch } from "../tabs/transact";
|
|
29
|
+
import { batchTab, beginBatch, endBatch } from "../tabs/transact";
|
|
30
|
+
import { beginTurn, endTurn } from "./ai-writes";
|
|
17
31
|
|
|
18
32
|
const MAX_ROUNDS = 5;
|
|
19
33
|
|
|
@@ -41,11 +55,33 @@ export async function runAgentLoop({
|
|
|
41
55
|
const allErrors: string[] = [];
|
|
42
56
|
const appliedSummaries: string[] = [];
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
/** The ledger is filed under the assistant message the turn ends on — see services/ai-writes. */
|
|
59
|
+
const turnId = () => chatState.messages.at(-1)?.id ?? "";
|
|
60
|
+
beginTurn(`turn:${chatState.messages.length}`);
|
|
61
|
+
|
|
62
|
+
// Batch all tool-call mutations into a single undo step, anchored on the tab being edited.
|
|
45
63
|
if (getTab) {
|
|
46
64
|
beginBatch(getTab());
|
|
47
65
|
}
|
|
48
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Close the batch and re-open it when the tools have moved to a different document.
|
|
69
|
+
*
|
|
70
|
+
* Runs after every tool execution rather than only inside `open_document`, because a tool is not
|
|
71
|
+
* the only thing that can change the active tab: project adoption replaces every tab in the
|
|
72
|
+
* workspace, and the user is free to click another tab while the model is still streaming.
|
|
73
|
+
*/
|
|
74
|
+
const reanchorBatch = () => {
|
|
75
|
+
if (!getTab) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const current = getTab();
|
|
79
|
+
if (current !== batchTab()) {
|
|
80
|
+
endBatch();
|
|
81
|
+
beginBatch(current);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
49
85
|
try {
|
|
50
86
|
for (let round = 1; round <= MAX_ROUNDS; round++) {
|
|
51
87
|
const messages = chatState.toMessagesArray();
|
|
@@ -119,6 +155,7 @@ export async function runAgentLoop({
|
|
|
119
155
|
error: `Failed to parse arguments: ${(error as Error).message}`,
|
|
120
156
|
};
|
|
121
157
|
}
|
|
158
|
+
reanchorBatch();
|
|
122
159
|
if (!result.success && result.error) {
|
|
123
160
|
allErrors.push(result.error);
|
|
124
161
|
}
|
|
@@ -135,8 +172,9 @@ export async function runAgentLoop({
|
|
|
135
172
|
}
|
|
136
173
|
|
|
137
174
|
/*
|
|
138
|
-
* Surface the actual errors so the user knows what went wrong, not just a
|
|
139
|
-
* "I couldn't do it"
|
|
175
|
+
* The round cap. Surface the actual errors so the user knows what went wrong, not just a
|
|
176
|
+
* generic "I couldn't do it" — and, when anything was applied, say it as the ASSISTANT rather
|
|
177
|
+
* than as a failure (§7.4). A partial success is not a failure.
|
|
140
178
|
*/
|
|
141
179
|
const uniqueErrors = [...new Set(allErrors)];
|
|
142
180
|
const applied =
|
|
@@ -147,10 +185,18 @@ export async function runAgentLoop({
|
|
|
147
185
|
uniqueErrors.length > 0
|
|
148
186
|
? `\n\nErrors encountered:\n${uniqueErrors.map((e) => `- ${e}`).join("\n")}`
|
|
149
187
|
: "";
|
|
150
|
-
|
|
151
|
-
`I ran out of tool-call rounds (${MAX_ROUNDS}) before finishing.${applied}${errors}
|
|
152
|
-
|
|
188
|
+
const tail =
|
|
189
|
+
`I ran out of tool-call rounds (${MAX_ROUNDS}) before finishing.${applied}${errors}` +
|
|
190
|
+
`\n\nYou can continue by sending another message, or try a more specific request.`;
|
|
191
|
+
if (appliedSummaries.length > 0) {
|
|
192
|
+
chatState.beginAssistantTurn();
|
|
193
|
+
chatState.appendDelta(tail);
|
|
194
|
+
chatState.finishStream("length");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
chatState.setError(tail);
|
|
153
198
|
} finally {
|
|
154
199
|
endBatch();
|
|
200
|
+
endTurn(turnId());
|
|
155
201
|
}
|
|
156
202
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place a string becomes markup in the Studio shell — Trusted Types (W3C).
|
|
3
|
+
*
|
|
4
|
+
* **What the standard actually gates, verified rather than assumed.** The tempting reading is that
|
|
5
|
+
* `require-trusted-types-for 'script'` governs DOM injection sinks and leaves `eval` to
|
|
6
|
+
* `script-src`. That is wrong: under Trusted Types, `eval()` and `new Function()` are gated too,
|
|
7
|
+
* and throw when no default policy exists. Getting that backwards produces a plan that cannot be
|
|
8
|
+
* executed — which is why the shell does **not** enforce Trusted Types yet, and why this module
|
|
9
|
+
* ships the policy first (see `spec.md` §21.5).
|
|
10
|
+
*
|
|
11
|
+
* **This policy refuses to be a rubber stamp.** A `createHTML` that returns its input unchanged
|
|
12
|
+
* satisfies the API and defends nothing; it converts a real control into a type-level ceremony. So
|
|
13
|
+
* `createHTML` asserts, and throws on anything that looks like an injection — the value is meant to
|
|
14
|
+
* have come from `markdownToHtml`, which already drops raw HTML and `javascript:` URLs, and this is
|
|
15
|
+
* the check that says so out loud rather than trusting a call site to have remembered.
|
|
16
|
+
*
|
|
17
|
+
* **`createScript` and `createScriptURL` throw.** Nothing in the shell builds either from a string.
|
|
18
|
+
* The canvas interpreter does evaluate expressions, but it runs in the canvas iframe under its own
|
|
19
|
+
* policy — a permissive `createScript` here would re-permit evaluation for the whole shell to buy
|
|
20
|
+
* something the shell does not use.
|
|
21
|
+
*
|
|
22
|
+
* @docs studio/interface/problems-and-progress
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** The subset of the Trusted Types API this module uses — typed here so no DOM lib is required. */
|
|
26
|
+
interface TrustedTypesApi {
|
|
27
|
+
createPolicy: (
|
|
28
|
+
name: string,
|
|
29
|
+
rules: {
|
|
30
|
+
createHTML?: (input: string) => string;
|
|
31
|
+
createScript?: (input: string) => string;
|
|
32
|
+
createScriptURL?: (input: string) => string;
|
|
33
|
+
},
|
|
34
|
+
) => { createHTML: (input: string) => unknown };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The policy name, which a CSP `trusted-types` directive would have to allow. */
|
|
38
|
+
export const STUDIO_POLICY_NAME = "jx-studio";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Patterns that must never survive sanitization.
|
|
42
|
+
*
|
|
43
|
+
* Deliberately a small, explicit list rather than a parser: this is an **assertion** over output
|
|
44
|
+
* that is already sanitized, not a sanitizer of its own. A second sanitizer here would be a second
|
|
45
|
+
* thing to keep correct, and the failure mode of a wrong one is a false sense of safety.
|
|
46
|
+
*/
|
|
47
|
+
const FORBIDDEN: readonly { pattern: RegExp; what: string }[] = [
|
|
48
|
+
{ pattern: /<\s*script\b/i, what: "a <script> element" },
|
|
49
|
+
{ pattern: /<\s*iframe\b/i, what: "an <iframe> element" },
|
|
50
|
+
{ pattern: /<\s*object\b/i, what: "an <object> element" },
|
|
51
|
+
{ pattern: /<\s*embed\b/i, what: "an <embed> element" },
|
|
52
|
+
{ pattern: /\son[a-z]+\s*=/i, what: "an inline event handler attribute" },
|
|
53
|
+
{ pattern: /javascript:/i, what: "a javascript: URL" },
|
|
54
|
+
{ pattern: /data:text\/html/i, what: "a data: URL carrying HTML" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Assert that a string is markup this app is willing to inject.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} value
|
|
61
|
+
* @returns {string} The same string, when it passes.
|
|
62
|
+
* @throws {Error} Naming what it found, so the failure is diagnosable rather than a blank refusal.
|
|
63
|
+
*/
|
|
64
|
+
export function assertSanitized(value: string): string {
|
|
65
|
+
for (const { pattern, what } of FORBIDDEN) {
|
|
66
|
+
if (pattern.test(value)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Refusing to inject markup containing ${what}. This value should have come from ` +
|
|
69
|
+
"markdownToHtml, which removes it — a value that reaches here with it still present " +
|
|
70
|
+
"means the sanitizer was bypassed, not that this check is too strict.",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Created once. A second `createPolicy` with the same name throws when policies are restricted. */
|
|
78
|
+
let policy: { createHTML: (input: string) => unknown } | null = null;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The shell's Trusted Types policy, or null where the API does not exist.
|
|
82
|
+
*
|
|
83
|
+
* @returns {{ createHTML: (input: string) => unknown } | null}
|
|
84
|
+
*/
|
|
85
|
+
export function studioPolicy(): { createHTML: (input: string) => unknown } | null {
|
|
86
|
+
if (policy) {
|
|
87
|
+
return policy;
|
|
88
|
+
}
|
|
89
|
+
const api = (globalThis as unknown as { trustedTypes?: TrustedTypesApi }).trustedTypes;
|
|
90
|
+
if (!api) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
policy = api.createPolicy(STUDIO_POLICY_NAME, {
|
|
94
|
+
createHTML: assertSanitized,
|
|
95
|
+
createScript: () => {
|
|
96
|
+
throw new Error("The Studio shell does not build scripts from strings.");
|
|
97
|
+
},
|
|
98
|
+
createScriptURL: () => {
|
|
99
|
+
throw new Error("The Studio shell does not build script URLs from strings.");
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
return policy;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Turn already-sanitized markup into something an injection sink accepts.
|
|
107
|
+
*
|
|
108
|
+
* Returns a `TrustedHTML` where the API exists and the plain string where it does not — and in
|
|
109
|
+
* **both** cases the assertion has run, so the check is not contingent on a browser feature. A
|
|
110
|
+
* guard that only fires under Trusted Types would be a guard that never fires in the tests.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} value Markup from `markdownToHtml`.
|
|
113
|
+
* @returns {unknown} A `TrustedHTML`, or the asserted string.
|
|
114
|
+
*/
|
|
115
|
+
export function trustedHtml(value: string): unknown {
|
|
116
|
+
const active = studioPolicy();
|
|
117
|
+
return active ? active.createHTML(value) : assertSanitized(value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Forget the memoized policy. Tests only — each one installs its own `trustedTypes` stub. */
|
|
121
|
+
export function resetStudioPolicy(): void {
|
|
122
|
+
policy = null;
|
|
123
|
+
}
|