@jxsuite/studio 6.1.1 → 8.0.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.
Files changed (785) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/2020-5wtzce5g.js +4 -0
  3. package/dist/chunks/{action-group-2mqby73c.js → action-group-4aan7g6g.js} +3 -3
  4. package/dist/chunks/adapter-5tphg7xe.js +4 -0
  5. package/dist/chunks/add-new-flow-psym14sn.js +4 -0
  6. package/dist/chunks/cf-account-picker-haqazq5r.js +4 -0
  7. package/dist/chunks/cf-settings-h2wrh6hq.js +4 -0
  8. package/dist/chunks/color-area-6rbra5dy.js +4 -0
  9. package/dist/chunks/color-field-gy56c94g.js +4 -0
  10. package/dist/chunks/color-m7r1vk54.js +4 -0
  11. package/dist/chunks/color-slider-sqqdyf6c.js +4 -0
  12. package/dist/chunks/combobox-72h32ejk.js +4 -0
  13. package/dist/chunks/components-r22xca09.js +4 -0
  14. package/dist/chunks/content-source-mmppmcyg.js +4 -0
  15. package/dist/chunks/convert-to-repeater-he70fc2b.js +4 -0
  16. package/dist/chunks/dialog-c4x05vyx.js +4 -0
  17. package/dist/chunks/editor-ccd9jwsa.js +4 -0
  18. package/dist/chunks/entry-commands-6qxz9e8p.js +4 -0
  19. package/dist/chunks/extension-sections-0egb88dm.js +4 -0
  20. package/dist/chunks/file-ops-qnab36ek.js +4 -0
  21. package/dist/chunks/files-rcn56qvm.js +4 -0
  22. package/dist/chunks/format-host-yxz0kjjs.js +4 -0
  23. package/dist/chunks/fs-events-d1qxq4z4.js +4 -0
  24. package/dist/chunks/grid-open-4z1g062t.js +4 -0
  25. package/dist/chunks/grid-view-qg1b5eqx.js +51 -0
  26. package/dist/chunks/icons-63bjtbs7.js +4 -0
  27. package/dist/chunks/iframe-entry-2zggzajv.js +5 -0
  28. package/dist/chunks/iframe-entry-76j8vwwx.js +5 -0
  29. package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
  30. package/dist/chunks/iframe-entry-pkgmcgp0.js +5 -0
  31. package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
  32. package/dist/chunks/{index-2veqmnhr.js → index-32eqzv1k.js} +3 -3
  33. package/dist/chunks/index-f0jn7nnp.js +4 -0
  34. package/dist/chunks/{javascript-8nbvs12s.js → javascript-pjp734q4.js} +3 -3
  35. package/dist/chunks/jsonMode-njkvsa02.js +13 -0
  36. package/dist/chunks/jx-validate-ttasrbh7.js +4 -0
  37. package/dist/chunks/layers-bgyr11tr.js +4 -0
  38. package/dist/chunks/layers-panel-7ez66zeq.js +4 -0
  39. package/dist/chunks/listbox-c421cf8c.js +4 -0
  40. package/dist/chunks/lit-html-v9gh687x.js +4 -0
  41. package/dist/chunks/media-library-sq468wts.js +4 -0
  42. package/dist/chunks/media-picker-shfe59pt.js +4 -0
  43. package/dist/chunks/menu-bdpzbfvs.js +4 -0
  44. package/dist/chunks/monaco-binding-vq1hgnn0.js +4 -0
  45. package/dist/chunks/monaco-buffer-3afgwvbp.js +4 -0
  46. package/dist/chunks/monaco-setup-xrd2cbkp.js +4 -0
  47. package/dist/chunks/notify-kzrjr3fv.js +4 -0
  48. package/dist/chunks/number-field-y62xq0ty.js +4 -0
  49. package/dist/chunks/parse-v77xtwv7.js +4 -0
  50. package/dist/chunks/platform-7v4rtd7m.js +4 -0
  51. package/dist/chunks/popover-b89sfej7.js +4 -0
  52. package/dist/chunks/publish-panel-2z9gw8ee.js +4 -0
  53. package/dist/chunks/publish-quick-qx2455cj.js +4 -0
  54. package/dist/chunks/publish-workspace-wrc1dgwx.js +4 -0
  55. package/dist/chunks/quick-search-n0t0eqxb.js +4 -0
  56. package/dist/chunks/references-9547rg5s.js +4 -0
  57. package/dist/chunks/right-panel-wsgnr9pn.js +4 -0
  58. package/dist/chunks/select-rzds841e.js +4 -0
  59. package/dist/chunks/source-control-kwktr9y6.js +4 -0
  60. package/dist/chunks/split-c9zfdgxf.js +4 -0
  61. package/dist/chunks/studio-03ct6yfv.js +9 -0
  62. package/dist/chunks/studio-07rcfje3.js +5 -0
  63. package/dist/chunks/studio-3e54qh39.js +7 -0
  64. package/dist/chunks/studio-3exxhz5g.js +5 -0
  65. package/dist/chunks/studio-3q40e3rj.js +5 -0
  66. package/dist/chunks/studio-3qeh9t0h.js +6 -0
  67. package/dist/chunks/studio-3qqjeep5.js +6 -0
  68. package/dist/chunks/studio-43sd2mam.js +4 -0
  69. package/dist/chunks/studio-4hp8pkyt.js +5 -0
  70. package/dist/chunks/studio-58xg6j2q.js +9 -0
  71. package/dist/chunks/studio-5g0rprdt.js +7 -0
  72. package/dist/chunks/studio-6m380wp7.js +5 -0
  73. package/dist/chunks/studio-80jyh1xh.js +4 -0
  74. package/dist/chunks/studio-83mk4bgg.js +9 -0
  75. package/dist/chunks/{studio-kggey1jt.js → studio-881z1xrx.js} +4 -4
  76. package/dist/chunks/studio-8m0y2qe7.js +5 -0
  77. package/dist/chunks/studio-8tr6cfrc.js +5 -0
  78. package/dist/chunks/studio-8vcvfs7r.js +6 -0
  79. package/dist/chunks/studio-9tmebf0s.js +5 -0
  80. package/dist/chunks/studio-a2bvaptz.js +5 -0
  81. package/dist/chunks/studio-b7y5mr30.js +5 -0
  82. package/dist/chunks/studio-bavb31r0.js +5 -0
  83. package/dist/chunks/studio-bbe4c90a.js +5 -0
  84. package/dist/chunks/studio-c05wdnt3.js +5 -0
  85. package/dist/chunks/studio-ccv2svc6.js +5 -0
  86. package/dist/chunks/studio-d5grtb9e.js +16 -0
  87. package/dist/chunks/studio-df0baea9.js +5 -0
  88. package/dist/chunks/studio-dgk4dqtx.js +7 -0
  89. package/dist/chunks/studio-e1g64q43.js +5 -0
  90. package/dist/chunks/studio-ed1hp6zf.js +6 -0
  91. package/dist/chunks/studio-ed3xx40h.js +7 -0
  92. package/dist/chunks/studio-f14e1zdj.js +5 -0
  93. package/dist/chunks/studio-fbhxxc5f.js +5 -0
  94. package/dist/chunks/studio-fyh0jc96.js +5 -0
  95. package/dist/chunks/studio-gcxk20n7.js +5 -0
  96. package/dist/chunks/studio-jaaz8ce9.js +5 -0
  97. package/dist/chunks/{studio-8grwxtqk.js → studio-jcnsdaep.js} +4 -4
  98. package/dist/chunks/studio-jsgf2q5k.js +7 -0
  99. package/dist/chunks/{studio-1q1sh2ax.js → studio-k3xxbagr.js} +3 -3
  100. package/dist/chunks/studio-k7evxqh4.js +5 -0
  101. package/dist/chunks/studio-kt3pjkwd.js +5 -0
  102. package/dist/chunks/studio-m8g16bgz.js +5 -0
  103. package/dist/chunks/studio-mn0476ag.js +5 -0
  104. package/dist/chunks/studio-mx1smyq0.js +5 -0
  105. package/dist/chunks/studio-n0c5h6mf.js +5 -0
  106. package/dist/chunks/{studio-rv11kfwa.js → studio-nh6d342n.js} +7 -7
  107. package/dist/chunks/studio-nqxqf8p2.js +4 -0
  108. package/dist/chunks/studio-p4nrkvqr.js +5 -0
  109. package/dist/chunks/{studio-h46vtzk8.js → studio-pbxa5fpc.js} +4 -4
  110. package/dist/chunks/studio-pgmvdv7z.js +7 -0
  111. package/dist/chunks/studio-ph84avkf.js +5 -0
  112. package/dist/chunks/{studio-wszjyh0j.js → studio-q0mvbaq6.js} +4 -4
  113. package/dist/chunks/studio-q0tccd43.js +5 -0
  114. package/dist/chunks/studio-q7rtx6zq.js +14 -0
  115. package/dist/chunks/{studio-pre0skq6.js → studio-qgh13jtz.js} +4 -4
  116. package/dist/chunks/studio-qmn1xc24.js +5 -0
  117. package/dist/chunks/studio-qwdqwkj0.js +5 -0
  118. package/dist/chunks/{studio-t6b6qe31.js → studio-r5qy30vx.js} +4 -4
  119. package/dist/chunks/studio-rj9eamf8.js +5 -0
  120. package/dist/chunks/studio-rptgsk96.js +5 -0
  121. package/dist/chunks/studio-rthyg42z.js +928 -0
  122. package/dist/chunks/studio-s19tp1xr.js +5 -0
  123. package/dist/chunks/studio-s32578pn.js +5 -0
  124. package/dist/chunks/studio-svb7yyve.js +5 -0
  125. package/dist/chunks/studio-syreb644.js +5 -0
  126. package/dist/chunks/studio-tf9jryb1.js +10 -0
  127. package/dist/chunks/studio-tfkznv39.js +5 -0
  128. package/dist/chunks/studio-tft6r105.js +5 -0
  129. package/dist/chunks/studio-txf2kysx.js +5 -0
  130. package/dist/chunks/studio-v8867ddv.js +12 -0
  131. package/dist/chunks/studio-vxq7fcvv.js +5 -0
  132. package/dist/chunks/studio-w4rz9hdk.js +5 -0
  133. package/dist/chunks/studio-wbcjbhy1.js +9 -0
  134. package/dist/chunks/studio-wc649wss.js +5 -0
  135. package/dist/chunks/studio-wys23mxz.js +5 -0
  136. package/dist/chunks/studio-x1x167jh.js +5 -0
  137. package/dist/chunks/studio-x9hsze61.js +5 -0
  138. package/dist/chunks/studio-y0n7yxea.js +9 -0
  139. package/dist/chunks/studio-y3ahhdpx.js +5 -0
  140. package/dist/chunks/studio-z97c48fk.js +5 -0
  141. package/dist/chunks/studio-zat62m0c.js +5 -0
  142. package/dist/chunks/studio-zcd2e7yz.js +5 -0
  143. package/dist/chunks/studio-zz9qe3rw.js +64 -0
  144. package/dist/chunks/swatch-group-ewh24n1f.js +4 -0
  145. package/dist/chunks/tabs-f5cw3tgp.js +4 -0
  146. package/dist/chunks/textfield-wewc8rq2.js +4 -0
  147. package/dist/chunks/toast-1gv0h53h.js +4 -0
  148. package/dist/chunks/toast-host-1yg5ar8d.js +4 -0
  149. package/dist/chunks/{toolbar-2nnzqhrj.js → toolbar-44cm5cta.js} +3 -3
  150. package/dist/chunks/tooltip-yypz28gq.js +4 -0
  151. package/dist/chunks/tree-9xa4hm7n.js +4 -0
  152. package/dist/chunks/tsMode-epyfz1hv.js +14 -0
  153. package/dist/chunks/workspace-dz7nxfhv.js +4 -0
  154. package/dist/chunks/workspace-home-xsd7hkjw.js +4 -0
  155. package/dist/chunks/ws-client-bv552sfx.js +4 -0
  156. package/dist/iframe-entry.js +20 -16
  157. package/dist/manifest.json +77 -63
  158. package/dist/studio.js +106 -107
  159. package/dist/workers/ts.worker.js +3 -3
  160. package/package.json +17 -16
  161. package/src/about/about-modal.ts +4 -3
  162. package/src/account/action-flow.ts +45 -38
  163. package/src/account-status.ts +7 -2
  164. package/src/browse/component-examples.ts +272 -0
  165. package/src/browse/library-commands.ts +64 -104
  166. package/src/browse/library-layouts.ts +270 -53
  167. package/src/browse/library-model.ts +194 -134
  168. package/src/browse/library-pane.ts +748 -440
  169. package/src/browse/library-preview.ts +161 -26
  170. package/src/browse/library-source.ts +308 -41
  171. package/src/browse/preview-root.ts +78 -0
  172. package/src/browse/preview-style.ts +155 -0
  173. package/src/canvas/asset-refs.ts +40 -10
  174. package/src/canvas/asset-resolve.ts +19 -23
  175. package/src/canvas/canvas-helpers.ts +7 -6
  176. package/src/canvas/canvas-live-render.ts +260 -77
  177. package/src/canvas/canvas-origin.ts +37 -12
  178. package/src/canvas/canvas-page-context.ts +112 -0
  179. package/src/canvas/canvas-patcher.ts +30 -30
  180. package/src/canvas/canvas-perf.ts +11 -5
  181. package/src/canvas/canvas-render.ts +175 -148
  182. package/src/canvas/canvas-surface.ts +5 -3
  183. package/src/canvas/canvas-utils.ts +147 -61
  184. package/src/canvas/dialog-path.ts +9 -4
  185. package/src/canvas/dialog-state.ts +1 -2
  186. package/src/canvas/diff-marks.ts +9 -16
  187. package/src/canvas/diff-toolbar.ts +8 -7
  188. package/src/canvas/edit-width-drag.ts +4 -1
  189. package/src/canvas/edit-width.ts +1 -3
  190. package/src/canvas/iframe-channel.ts +27 -13
  191. package/src/canvas/iframe-drop.ts +120 -12
  192. package/src/canvas/iframe-editable-root.ts +29 -16
  193. package/src/canvas/iframe-entry.ts +104 -75
  194. package/src/canvas/iframe-eval.ts +73 -49
  195. package/src/canvas/iframe-host.ts +269 -158
  196. package/src/canvas/iframe-inline-edit.ts +49 -30
  197. package/src/canvas/iframe-insert.ts +5 -4
  198. package/src/canvas/iframe-interaction.ts +30 -25
  199. package/src/canvas/iframe-keys.ts +11 -0
  200. package/src/canvas/iframe-overlay.ts +1 -1
  201. package/src/canvas/iframe-patch.ts +25 -21
  202. package/src/canvas/iframe-position.ts +12 -6
  203. package/src/canvas/iframe-protocol.ts +36 -21
  204. package/src/canvas/iframe-render.ts +84 -35
  205. package/src/canvas/iframe-slash.ts +24 -11
  206. package/src/canvas/iframe-subtree.ts +10 -3
  207. package/src/canvas/npm-modules.ts +22 -0
  208. package/src/canvas/path-mapping.ts +19 -12
  209. package/src/canvas/popover-path.ts +7 -3
  210. package/src/canvas/popover-state.ts +1 -2
  211. package/src/canvas/preview-navigate.ts +16 -0
  212. package/src/canvas/repeater-sources.ts +200 -0
  213. package/src/canvas/serialize-scope.ts +13 -5
  214. package/src/canvas/stage-scrollbar.ts +7 -2
  215. package/src/canvas/surface-registry.ts +27 -3
  216. package/src/collab/collab-commands.ts +7 -2
  217. package/src/collab/collab-session.ts +84 -45
  218. package/src/collab/collab-state.ts +4 -3
  219. package/src/collab/monaco-binding.ts +16 -13
  220. package/src/collab/monaco-cursors.ts +5 -2
  221. package/src/commands/app-commands.ts +16 -6
  222. package/src/commands/budget.ts +13 -11
  223. package/src/commands/command-args.ts +103 -71
  224. package/src/commands/context.ts +55 -28
  225. package/src/commands/defaults.ts +69 -45
  226. package/src/commands/keymap.ts +56 -43
  227. package/src/commands/levels.ts +14 -8
  228. package/src/commands/live-context.ts +9 -7
  229. package/src/commands/reference.ts +2 -1
  230. package/src/commands/registry.ts +22 -11
  231. package/src/commands/run-reported.ts +15 -10
  232. package/src/component-props.ts +46 -30
  233. package/src/content/collection-match.ts +11 -9
  234. package/src/content/collection-pages.ts +412 -0
  235. package/src/content/content-collections.ts +270 -0
  236. package/src/content/draft-state.ts +4 -3
  237. package/src/content/entry-commands.ts +54 -62
  238. package/src/content/entry-editor.ts +137 -32
  239. package/src/content/entry-fields.ts +25 -8
  240. package/src/content/entry-inspector.ts +225 -0
  241. package/src/content/entry-model.ts +38 -15
  242. package/src/content/entry-open.ts +80 -0
  243. package/src/content/entry-summaries.ts +440 -0
  244. package/src/content/file-record-ids.ts +80 -0
  245. package/src/content/file-records.ts +125 -0
  246. package/src/content/page-summaries.ts +129 -0
  247. package/src/content/record-entry.ts +201 -0
  248. package/src/content/writing-banner.ts +177 -0
  249. package/src/create/add-new-commands.ts +185 -0
  250. package/src/create/add-new-flow.ts +1116 -0
  251. package/src/create/add-new.ts +1014 -0
  252. package/src/create/element-name.ts +121 -0
  253. package/src/editor/canvas-context-menu.ts +1 -2
  254. package/src/editor/canvas-slash-bridge.ts +3 -6
  255. package/src/editor/content-slice.ts +16 -14
  256. package/src/editor/context-menu.ts +35 -22
  257. package/src/editor/convert-targets.ts +30 -35
  258. package/src/editor/convert-to-component.ts +78 -48
  259. package/src/editor/convert-to-repeater.ts +34 -22
  260. package/src/editor/editable-tags.ts +17 -2
  261. package/src/editor/file-drop-action.ts +29 -22
  262. package/src/editor/inline-edit-apply.ts +79 -17
  263. package/src/editor/inline-edit.ts +76 -50
  264. package/src/editor/inline-format.ts +59 -56
  265. package/src/editor/inline-link.ts +40 -15
  266. package/src/editor/insert-component.ts +91 -0
  267. package/src/editor/insert-zone-action.ts +14 -5
  268. package/src/editor/merge-tags.ts +58 -19
  269. package/src/editor/repeater-scope.ts +39 -28
  270. package/src/editor/shortcuts.ts +76 -42
  271. package/src/editor/slash-menu.ts +52 -14
  272. package/src/files/asset-versions.ts +4 -4
  273. package/src/files/components.ts +89 -20
  274. package/src/files/elements.ts +5 -6
  275. package/src/files/file-ops.ts +92 -41
  276. package/src/files/files.ts +248 -239
  277. package/src/files/fs-events.ts +18 -7
  278. package/src/files/media-meta.ts +97 -2
  279. package/src/files/media-paths.ts +3 -0
  280. package/src/files/media-types.ts +55 -0
  281. package/src/files/media-upload.ts +209 -52
  282. package/src/files/new-file-name.ts +156 -0
  283. package/src/files/serialize-document.ts +15 -5
  284. package/src/format/constraints.ts +1 -1
  285. package/src/format/convert-file.ts +27 -13
  286. package/src/format/format-choices.ts +2 -1
  287. package/src/format/format-host.ts +91 -50
  288. package/src/github/github-auth-status.ts +27 -3
  289. package/src/github/github-auth.ts +21 -11
  290. package/src/github/github-publish.ts +28 -23
  291. package/src/grid/cell-editors.ts +46 -31
  292. package/src/grid/cell-popovers.ts +5 -3
  293. package/src/grid/edit-buffer.ts +10 -9
  294. package/src/grid/grid-controller.ts +25 -23
  295. package/src/grid/grid-layout.ts +21 -5
  296. package/src/grid/grid-open.ts +3 -33
  297. package/src/grid/grid-panel.ts +65 -6
  298. package/src/grid/grid-reveal.ts +34 -0
  299. package/src/grid/grid-source.ts +9 -31
  300. package/src/grid/grid-view.ts +45 -23
  301. package/src/grid/redirects-grid.ts +14 -5
  302. package/src/grid/redirects.ts +16 -14
  303. package/src/grid/schema-columns.ts +25 -19
  304. package/src/grid/sources/connector-source.ts +47 -20
  305. package/src/grid/sources/content-source.ts +100 -31
  306. package/src/grid/sources/csv-file-source.ts +2 -2
  307. package/src/grid/tabulator-tables.d.ts +61 -34
  308. package/src/hosting/document.ts +2 -2
  309. package/src/hosting/stage.ts +7 -7
  310. package/src/i18n/i18n-commands.ts +3 -3
  311. package/src/media/media-commands.ts +181 -0
  312. package/src/media/media-library-model.ts +232 -0
  313. package/src/media/media-library.ts +1283 -0
  314. package/src/media/media-pane.ts +51 -20
  315. package/src/new-project/add-repo-modal.ts +20 -19
  316. package/src/new-project/import-tab.ts +10 -14
  317. package/src/new-project/location-fields.ts +2 -1
  318. package/src/new-project/new-project-modal.ts +12 -9
  319. package/src/packages/jxsuite-update.ts +4 -3
  320. package/src/packages/pull-package-sync.ts +26 -25
  321. package/src/page-params.ts +97 -68
  322. package/src/panels/activity-panel.ts +38 -14
  323. package/src/panels/ai-chat/attached-context.ts +13 -7
  324. package/src/panels/ai-chat/chat-markdown.ts +8 -8
  325. package/src/panels/ai-chat/chat-view.ts +80 -73
  326. package/src/panels/ai-chat/composer.ts +30 -33
  327. package/src/panels/ai-chat/sessions-view.ts +1 -2
  328. package/src/panels/ai-panel.ts +23 -20
  329. package/src/panels/block-action-bar.ts +57 -46
  330. package/src/panels/bottom-dock.ts +5 -3
  331. package/src/panels/canvas-dnd-bridge.ts +36 -26
  332. package/src/panels/chat-panel.ts +6 -6
  333. package/src/panels/collections-panel.ts +306 -0
  334. package/src/panels/component-preview.ts +35 -13
  335. package/src/panels/data-explorer.ts +63 -26
  336. package/src/panels/data-grid.ts +18 -11
  337. package/src/panels/dnd.ts +115 -138
  338. package/src/panels/editors.ts +52 -21
  339. package/src/panels/elements-panel.ts +19 -5
  340. package/src/panels/empty-state.ts +2 -2
  341. package/src/panels/events-panel.ts +84 -69
  342. package/src/panels/formula-workspace.ts +78 -54
  343. package/src/panels/frontmatter-fields.ts +33 -28
  344. package/src/panels/git-diff-open.ts +10 -6
  345. package/src/panels/head-panel.ts +488 -202
  346. package/src/panels/i18n-panel.ts +20 -12
  347. package/src/panels/imports-panel.ts +25 -47
  348. package/src/panels/jump-bar.ts +75 -31
  349. package/src/panels/layers-panel.ts +130 -101
  350. package/src/panels/left-panel.ts +25 -11
  351. package/src/panels/navigator-panels.ts +23 -11
  352. package/src/panels/pane-context.ts +140 -49
  353. package/src/panels/pane-grid.ts +55 -26
  354. package/src/panels/panel-registry.ts +36 -46
  355. package/src/panels/properties-panel.ts +182 -124
  356. package/src/panels/quick-search.ts +53 -31
  357. package/src/panels/right-panel.ts +201 -45
  358. package/src/panels/seo-modal.ts +54 -125
  359. package/src/panels/settings-menu.ts +63 -61
  360. package/src/panels/settings-pane.ts +268 -162
  361. package/src/panels/signals-panel.ts +156 -135
  362. package/src/panels/site-model.ts +367 -0
  363. package/src/panels/site-panel.ts +638 -0
  364. package/src/panels/statement-editor.ts +230 -140
  365. package/src/panels/style-panel.ts +253 -206
  366. package/src/panels/style-utils.ts +114 -51
  367. package/src/panels/stylebook-doc.ts +195 -60
  368. package/src/panels/stylebook-layers-panel.ts +48 -89
  369. package/src/panels/stylebook-panel.ts +60 -27
  370. package/src/panels/styles-panel.ts +327 -0
  371. package/src/panels/tab-drop.ts +27 -11
  372. package/src/panels/tab-strip.ts +143 -49
  373. package/src/platform-errors.ts +57 -43
  374. package/src/platform.ts +59 -31
  375. package/src/platforms/cloud.ts +373 -176
  376. package/src/platforms/default-platform.ts +14 -8
  377. package/src/platforms/devserver.ts +180 -142
  378. package/src/platforms/read-batcher.ts +16 -8
  379. package/src/project-list.ts +2 -1
  380. package/src/publish/deploy-checklist.ts +20 -9
  381. package/src/publish/pages-service.ts +66 -27
  382. package/src/publish/publish-changes.ts +491 -0
  383. package/src/publish/publish-commands.ts +88 -22
  384. package/src/publish/publish-ids.ts +49 -0
  385. package/src/publish/publish-panel.ts +10 -6
  386. package/src/publish/publish-quick.ts +148 -0
  387. package/src/publish/publish-workspace.ts +926 -0
  388. package/src/publish/source-control.ts +907 -0
  389. package/src/recent-projects.ts +15 -8
  390. package/src/resize-edges.ts +26 -36
  391. package/src/services/a11y-report.ts +26 -19
  392. package/src/services/agent-seed.ts +23 -11
  393. package/src/services/ai-ask.ts +9 -12
  394. package/src/services/ai-command-tools.ts +68 -62
  395. package/src/services/ai-import-tools.ts +80 -67
  396. package/src/services/ai-models.ts +17 -13
  397. package/src/services/ai-project-tools.ts +68 -32
  398. package/src/services/ai-session-store.ts +19 -10
  399. package/src/services/ai-system-prompt.ts +46 -20
  400. package/src/services/ai-tools.ts +71 -27
  401. package/src/services/ai-write-report.ts +20 -12
  402. package/src/services/ai-writes.ts +4 -4
  403. package/src/services/automation.ts +74 -51
  404. package/src/services/cem-export.ts +149 -58
  405. package/src/services/cf-settings.ts +4 -4
  406. package/src/services/clock.ts +6 -1
  407. package/src/services/code-services.ts +37 -31
  408. package/src/services/connection.ts +2 -2
  409. package/src/services/context-manager.ts +14 -10
  410. package/src/services/context-resolver.ts +20 -8
  411. package/src/services/data-service.ts +19 -15
  412. package/src/services/directory-picker.ts +20 -14
  413. package/src/services/document-assistant.ts +39 -19
  414. package/src/services/gated-registry.ts +10 -6
  415. package/src/services/harness/chat-reducer.ts +23 -1
  416. package/src/services/import-client.ts +91 -38
  417. package/src/services/import-seed.ts +8 -2
  418. package/src/services/jx-validate.ts +28 -31
  419. package/src/services/live-preview.ts +0 -0
  420. package/src/services/live-surfaces.ts +6 -3
  421. package/src/services/model-uri.ts +10 -2
  422. package/src/services/module-preloads.ts +12 -8
  423. package/src/services/monaco-buffer.ts +43 -24
  424. package/src/services/monaco-lazy.ts +7 -8
  425. package/src/services/monaco-setup.ts +23 -16
  426. package/src/services/notify.ts +44 -17
  427. package/src/services/popover-report.ts +25 -10
  428. package/src/services/preview-eval.ts +20 -8
  429. package/src/services/profile.ts +13 -6
  430. package/src/services/project-path.ts +13 -10
  431. package/src/services/references.ts +0 -0
  432. package/src/services/render-critic.ts +29 -9
  433. package/src/services/settings/definitions.ts +25 -14
  434. package/src/services/settings/kernel.ts +13 -4
  435. package/src/services/settings/write-queue.ts +9 -9
  436. package/src/services/token-lint.ts +12 -10
  437. package/src/services/tool-executor.ts +17 -5
  438. package/src/services/tool-outcomes.ts +44 -24
  439. package/src/services/trusted-types.ts +24 -7
  440. package/src/settings/contexts-section.ts +11 -8
  441. package/src/settings/contributed-section.ts +128 -70
  442. package/src/settings/css-vars-editor.ts +119 -32
  443. package/src/settings/defs-editor.ts +38 -35
  444. package/src/settings/developer-panels.ts +43 -0
  445. package/src/settings/extension-rows.ts +16 -8
  446. package/src/settings/extension-sections.ts +42 -14
  447. package/src/settings/extensions-section.ts +4 -1
  448. package/src/settings/general-settings.ts +101 -28
  449. package/src/settings/locales-section.ts +20 -12
  450. package/src/settings/preferences-dialog.ts +17 -7
  451. package/src/settings/preferences-keymap.ts +7 -5
  452. package/src/settings/preferences-sections.ts +11 -5
  453. package/src/settings/project-sections.ts +28 -11
  454. package/src/settings/schema-field-ui.ts +12 -1
  455. package/src/settings/section-registry.ts +116 -11
  456. package/src/settings/settings-dialog.ts +192 -0
  457. package/src/settings/settings-document.ts +186 -166
  458. package/src/shell/tree.ts +6 -5
  459. package/src/shell.ts +536 -477
  460. package/src/site-context.ts +101 -32
  461. package/src/state.ts +68 -57
  462. package/src/store.ts +54 -34
  463. package/src/studio.ts +219 -116
  464. package/src/style/live-preview.ts +1 -1
  465. package/src/style/project-styles.ts +40 -7
  466. package/src/style/stylebook-specimens.ts +89 -0
  467. package/src/style/stylebook-target.ts +26 -0
  468. package/src/style/tag-inheritance.ts +105 -0
  469. package/src/style/token-ref.ts +5 -4
  470. package/src/style/variables-focus.ts +61 -0
  471. package/src/style/variables-view.ts +235 -0
  472. package/src/surfaces/about.ts +18 -19
  473. package/src/surfaces/add-new.json +1482 -0
  474. package/src/surfaces/add-new.ts +325 -0
  475. package/src/surfaces/add-repo.ts +17 -19
  476. package/src/surfaces/ai-chat.ts +33 -38
  477. package/src/surfaces/ai-credentials-form.ts +6 -16
  478. package/src/surfaces/ai-managed-connect.ts +3 -4
  479. package/src/surfaces/ai-model-picker.ts +3 -5
  480. package/src/surfaces/block-action-bar.ts +22 -11
  481. package/src/surfaces/boot-failure.json +4 -3
  482. package/src/surfaces/boot-failure.ts +4 -5
  483. package/src/surfaces/bottom-dock.json +1 -1
  484. package/src/surfaces/bottom-dock.ts +9 -18
  485. package/src/surfaces/canvas-stage.json +30 -19
  486. package/src/surfaces/canvas-stage.ts +17 -16
  487. package/src/surfaces/cf-account-picker.ts +17 -18
  488. package/src/surfaces/collections-panel.json +615 -0
  489. package/src/surfaces/collections-panel.ts +168 -0
  490. package/src/surfaces/commandbar.json +241 -96
  491. package/src/surfaces/commandbar.ts +145 -105
  492. package/src/surfaces/convert-repeater.ts +17 -19
  493. package/src/surfaces/data-actions.ts +9 -7
  494. package/src/surfaces/dialog.ts +26 -28
  495. package/src/surfaces/diff-toolbar.ts +9 -7
  496. package/src/surfaces/empty-state.json +1 -1
  497. package/src/surfaces/empty-state.ts +7 -10
  498. package/src/surfaces/entry-editor.json +136 -0
  499. package/src/surfaces/entry-editor.ts +35 -11
  500. package/src/surfaces/entry-inspector.json +371 -0
  501. package/src/surfaces/entry-inspector.ts +143 -0
  502. package/src/surfaces/expression-editor.ts +11 -25
  503. package/src/surfaces/files-panel.ts +27 -14
  504. package/src/surfaces/formula-palette.ts +4 -5
  505. package/src/surfaces/github-auth.ts +16 -20
  506. package/src/surfaces/github-publish.ts +17 -22
  507. package/src/surfaces/grid-cell.ts +30 -31
  508. package/src/surfaces/grid-open.ts +10 -14
  509. package/src/surfaces/grid-panel.ts +13 -9
  510. package/src/surfaces/grid-replace.ts +13 -13
  511. package/src/surfaces/grid-views.ts +15 -18
  512. package/src/surfaces/inspector-dock.json +162 -14
  513. package/src/surfaces/inspector-dock.ts +59 -31
  514. package/src/surfaces/jump-bar.ts +5 -6
  515. package/src/surfaces/jxsuite-update.ts +16 -22
  516. package/src/surfaces/library-pane.json +670 -437
  517. package/src/surfaces/library-pane.ts +111 -77
  518. package/src/surfaces/logic-panel.ts +17 -30
  519. package/src/surfaces/logic-workspace.ts +13 -15
  520. package/src/surfaces/media-browser.json +62 -6
  521. package/src/surfaces/media-browser.ts +26 -11
  522. package/src/surfaces/media-field.json +1 -1
  523. package/src/surfaces/media-field.ts +4 -5
  524. package/src/surfaces/media-library.json +1586 -0
  525. package/src/surfaces/media-library.ts +282 -0
  526. package/src/surfaces/media-pane.ts +8 -7
  527. package/src/surfaces/media-pick-dialog.json +71 -0
  528. package/src/surfaces/media-pick-dialog.ts +132 -0
  529. package/src/surfaces/menu.ts +47 -23
  530. package/src/surfaces/navigator-dock.ts +11 -12
  531. package/src/surfaces/new-project.ts +27 -26
  532. package/src/surfaces/pane-context.json +144 -77
  533. package/src/surfaces/pane-context.ts +44 -25
  534. package/src/surfaces/pane-grid.ts +15 -10
  535. package/src/surfaces/panel-activity.ts +5 -6
  536. package/src/surfaces/panel-data.ts +3 -4
  537. package/src/surfaces/panel-deploy-checklist.json +12 -6
  538. package/src/surfaces/panel-deploy-checklist.ts +8 -7
  539. package/src/surfaces/panel-elements.ts +5 -7
  540. package/src/surfaces/panel-i18n.ts +5 -7
  541. package/src/surfaces/panel-imports.ts +6 -13
  542. package/src/surfaces/panel-outline.ts +25 -19
  543. package/src/surfaces/panel-page.json +72 -2
  544. package/src/surfaces/panel-page.ts +49 -25
  545. package/src/surfaces/panel-problems.ts +5 -7
  546. package/src/surfaces/panel-signals.json +1 -1
  547. package/src/surfaces/panel-signals.ts +25 -39
  548. package/src/surfaces/panel-stylebook-layers.json +71 -204
  549. package/src/surfaces/panel-stylebook-layers.ts +5 -29
  550. package/src/surfaces/preferences.json +120 -48
  551. package/src/surfaces/preferences.ts +29 -11
  552. package/src/surfaces/progress-modal.ts +15 -9
  553. package/src/surfaces/properties-panel.json +1 -1
  554. package/src/surfaces/properties-panel.ts +20 -42
  555. package/src/surfaces/publish-quick.json +428 -0
  556. package/src/surfaces/publish-quick.ts +165 -0
  557. package/src/surfaces/publish-workspace.json +2892 -0
  558. package/src/surfaces/publish-workspace.ts +391 -0
  559. package/src/surfaces/publish.ts +18 -18
  560. package/src/surfaces/push-plan.ts +7 -8
  561. package/src/surfaces/rail.json +1 -159
  562. package/src/surfaces/rail.ts +22 -79
  563. package/src/surfaces/reference-field.ts +11 -15
  564. package/src/surfaces/schema-builder.ts +16 -22
  565. package/src/surfaces/schema-form.json +10 -2
  566. package/src/surfaces/schema-form.ts +17 -25
  567. package/src/surfaces/secret-field.ts +9 -6
  568. package/src/surfaces/seo.json +1 -1
  569. package/src/surfaces/seo.ts +21 -15
  570. package/src/surfaces/settings-contexts.ts +9 -7
  571. package/src/surfaces/settings-contributed.json +5 -5
  572. package/src/surfaces/settings-contributed.ts +32 -12
  573. package/src/surfaces/settings-css-vars.json +44 -36
  574. package/src/surfaces/settings-css-vars.ts +82 -16
  575. package/src/surfaces/settings-defs.json +2 -2
  576. package/src/surfaces/settings-defs.ts +17 -21
  577. package/src/surfaces/settings-deploy.json +2 -15
  578. package/src/surfaces/settings-deploy.ts +9 -16
  579. package/src/surfaces/settings-dialog.json +51 -0
  580. package/src/surfaces/settings-dialog.ts +169 -0
  581. package/src/surfaces/settings-extensions.ts +5 -6
  582. package/src/surfaces/settings-head.ts +33 -39
  583. package/src/surfaces/settings-locales.ts +8 -20
  584. package/src/surfaces/settings-overview.json +3 -3
  585. package/src/surfaces/settings-overview.ts +14 -8
  586. package/src/surfaces/settings-packages.ts +8 -7
  587. package/src/surfaces/settings-pane.json +269 -46
  588. package/src/surfaces/settings-pane.ts +88 -36
  589. package/src/surfaces/settings-rawjson.ts +5 -6
  590. package/src/surfaces/shell.json +2 -1
  591. package/src/surfaces/shell.ts +1 -2
  592. package/src/surfaces/site-panel.json +1029 -0
  593. package/src/surfaces/site-panel.ts +233 -0
  594. package/src/surfaces/slash-menu.json +63 -21
  595. package/src/surfaces/slash-menu.ts +42 -9
  596. package/src/surfaces/statements.ts +13 -18
  597. package/src/surfaces/statusbar.ts +56 -29
  598. package/src/surfaces/style-panel.ts +18 -31
  599. package/src/surfaces/stylebook-chrome.ts +3 -5
  600. package/src/surfaces/styles-panel.json +735 -0
  601. package/src/surfaces/styles-panel.ts +195 -0
  602. package/src/surfaces/styles-variables.json +149 -0
  603. package/src/surfaces/styles-variables.ts +145 -0
  604. package/src/surfaces/tab-strip.json +4 -4
  605. package/src/surfaces/tab-strip.ts +14 -13
  606. package/src/surfaces/target-line.ts +6 -7
  607. package/src/surfaces/welcome.ts +6 -8
  608. package/src/surfaces/writing-banner.json +94 -0
  609. package/src/surfaces/writing-banner.ts +80 -0
  610. package/src/tabs/bare-repeaters.ts +101 -0
  611. package/src/tabs/patch-ops.ts +4 -3
  612. package/src/tabs/project-config.ts +32 -21
  613. package/src/tabs/selection.ts +36 -8
  614. package/src/tabs/tab.ts +99 -48
  615. package/src/tabs/transact.ts +98 -53
  616. package/src/types.ts +53 -43
  617. package/src/ui/ai-credentials-form.ts +10 -13
  618. package/src/ui/ai-model-picker.ts +3 -3
  619. package/src/ui/cf-account-picker.ts +7 -9
  620. package/src/ui/color-selector.ts +9 -5
  621. package/src/ui/dynamic-slot.ts +7 -8
  622. package/src/ui/expression-editor.ts +112 -105
  623. package/src/ui/form-controls.ts +104 -56
  624. package/src/ui/formula-catalog.ts +239 -230
  625. package/src/ui/formula-chips.ts +33 -18
  626. package/src/ui/layers.ts +57 -31
  627. package/src/ui/media-picker.ts +300 -47
  628. package/src/ui/popover-surface.ts +20 -12
  629. package/src/ui/progress-modal.ts +13 -8
  630. package/src/ui/regions.ts +14 -12
  631. package/src/ui/schema-form.ts +372 -97
  632. package/src/ui/surface.ts +71 -8
  633. package/src/ui/timing.ts +9 -0
  634. package/src/ui/value-source.ts +44 -31
  635. package/src/ui/virtual-window.ts +29 -3
  636. package/src/utils/base64.ts +6 -4
  637. package/src/utils/edit-display.ts +366 -111
  638. package/src/utils/element-selectors.ts +3 -2
  639. package/src/utils/google-fonts.ts +15 -14
  640. package/src/utils/inherited-style.ts +18 -20
  641. package/src/utils/link-target.ts +1 -1
  642. package/src/utils/local-day.ts +18 -0
  643. package/src/utils/preview-format.ts +25 -3
  644. package/src/utils/strip-events.ts +49 -22
  645. package/src/utils/studio-utils.ts +29 -14
  646. package/src/view.ts +2 -3
  647. package/src/workspace/pane-derive.ts +14 -16
  648. package/src/workspace/session.ts +81 -50
  649. package/src/workspace/tab-workspace.ts +137 -0
  650. package/src/workspace/workspace-back.ts +189 -0
  651. package/src/workspace/workspace-home.ts +133 -0
  652. package/src/workspace/workspace-ids.ts +129 -0
  653. package/src/workspace/workspace-switch.ts +372 -0
  654. package/src/workspace/workspace.ts +165 -47
  655. package/styles/shell-frame.css +30 -7
  656. package/styles/shell-frame.json +15 -3
  657. package/dist/chunks/2020-va2wwra4.js +0 -4
  658. package/dist/chunks/adapter-f93pafd7.js +0 -4
  659. package/dist/chunks/cf-account-picker-af1h381v.js +0 -4
  660. package/dist/chunks/cf-settings-125tdm0w.js +0 -4
  661. package/dist/chunks/color-area-jqhna8zy.js +0 -4
  662. package/dist/chunks/color-field-embq5ctg.js +0 -4
  663. package/dist/chunks/color-fpezvnkt.js +0 -4
  664. package/dist/chunks/color-slider-1qmw7wqy.js +0 -4
  665. package/dist/chunks/combobox-773hdzyj.js +0 -4
  666. package/dist/chunks/components-ax2f1fnn.js +0 -4
  667. package/dist/chunks/content-source-108aqtk1.js +0 -4
  668. package/dist/chunks/convert-to-repeater-ry5rmppr.js +0 -4
  669. package/dist/chunks/dialog-mtm9fq5t.js +0 -4
  670. package/dist/chunks/editor-pfgdmkb0.js +0 -4
  671. package/dist/chunks/entry-commands-t0k1q0kx.js +0 -4
  672. package/dist/chunks/extension-sections-6mwh2hf8.js +0 -4
  673. package/dist/chunks/file-ops-meej5mqf.js +0 -4
  674. package/dist/chunks/files-44vp8a2s.js +0 -4
  675. package/dist/chunks/format-host-7q1petb9.js +0 -4
  676. package/dist/chunks/fs-events-h2mwnxaz.js +0 -4
  677. package/dist/chunks/git-panel-zngbymvd.js +0 -4
  678. package/dist/chunks/grid-view-sx9vynsp.js +0 -51
  679. package/dist/chunks/icons-p44xhjf0.js +0 -4
  680. package/dist/chunks/iframe-entry-f7mqjwpm.js +0 -5
  681. package/dist/chunks/iframe-entry-haw7jkvz.js +0 -5
  682. package/dist/chunks/iframe-entry-z6qrytv3.js +0 -5
  683. package/dist/chunks/iframe-host-qk3ycq87.js +0 -4
  684. package/dist/chunks/index-wkyfjeqz.js +0 -4
  685. package/dist/chunks/jsonMode-s8kt9dsj.js +0 -13
  686. package/dist/chunks/jx-validate-psgd7vsx.js +0 -4
  687. package/dist/chunks/layers-panel-1kq2jyfk.js +0 -4
  688. package/dist/chunks/layers-xd92fg8d.js +0 -4
  689. package/dist/chunks/listbox-s8fkmnq8.js +0 -4
  690. package/dist/chunks/lit-html-ac4cwwr4.js +0 -4
  691. package/dist/chunks/media-picker-0svzkdyb.js +0 -4
  692. package/dist/chunks/menu-37d07z9m.js +0 -4
  693. package/dist/chunks/menu-5c55ea3d.js +0 -4
  694. package/dist/chunks/monaco-binding-z6r70pdt.js +0 -4
  695. package/dist/chunks/monaco-buffer-fz6gq5y7.js +0 -4
  696. package/dist/chunks/monaco-setup-q4axkgj7.js +0 -4
  697. package/dist/chunks/notify-0xgtn8q1.js +0 -4
  698. package/dist/chunks/number-field-gw7sq8rn.js +0 -4
  699. package/dist/chunks/parse-897fvqah.js +0 -4
  700. package/dist/chunks/platform-v1qctbe6.js +0 -4
  701. package/dist/chunks/popover-8htra4er.js +0 -4
  702. package/dist/chunks/publish-panel-ym1fmgq5.js +0 -4
  703. package/dist/chunks/quick-search-a54e6t5n.js +0 -4
  704. package/dist/chunks/references-gmqp82fs.js +0 -4
  705. package/dist/chunks/right-panel-v7t9awbk.js +0 -4
  706. package/dist/chunks/section-registry-vhyf3zsr.js +0 -4
  707. package/dist/chunks/select-n3v7gwas.js +0 -4
  708. package/dist/chunks/split-pff8f3ze.js +0 -4
  709. package/dist/chunks/studio-0bhx5hz6.js +0 -5
  710. package/dist/chunks/studio-0k1ddnyy.js +0 -6
  711. package/dist/chunks/studio-1b04pd0j.js +0 -64
  712. package/dist/chunks/studio-4dwbj6gb.js +0 -5
  713. package/dist/chunks/studio-4tanh828.js +0 -5
  714. package/dist/chunks/studio-55h4mstj.js +0 -4
  715. package/dist/chunks/studio-5tnvjkz4.js +0 -5
  716. package/dist/chunks/studio-60dqgb0c.js +0 -5
  717. package/dist/chunks/studio-61dp09qg.js +0 -9
  718. package/dist/chunks/studio-63epcrj2.js +0 -5
  719. package/dist/chunks/studio-74k29dy9.js +0 -5
  720. package/dist/chunks/studio-7vg6jcd3.js +0 -5
  721. package/dist/chunks/studio-7zx31knw.js +0 -9
  722. package/dist/chunks/studio-8bxk97km.js +0 -5
  723. package/dist/chunks/studio-933e30n4.js +0 -5
  724. package/dist/chunks/studio-98wnwhbg.js +0 -5
  725. package/dist/chunks/studio-9mccjwjh.js +0 -14
  726. package/dist/chunks/studio-a2ke1e4t.js +0 -5
  727. package/dist/chunks/studio-e3a2a6v3.js +0 -4
  728. package/dist/chunks/studio-e81q1vb9.js +0 -5
  729. package/dist/chunks/studio-epgrmqqm.js +0 -5
  730. package/dist/chunks/studio-ery42sqr.js +0 -4
  731. package/dist/chunks/studio-g1shz58g.js +0 -5
  732. package/dist/chunks/studio-gf0fz03c.js +0 -5
  733. package/dist/chunks/studio-gjtpe62q.js +0 -6
  734. package/dist/chunks/studio-gkskhg7m.js +0 -5
  735. package/dist/chunks/studio-gx4dnjzd.js +0 -5
  736. package/dist/chunks/studio-j03zga3y.js +0 -9
  737. package/dist/chunks/studio-jhpxeckz.js +0 -5
  738. package/dist/chunks/studio-k01r7ykp.js +0 -5
  739. package/dist/chunks/studio-kk7h30ne.js +0 -7
  740. package/dist/chunks/studio-kt1zqney.js +0 -5
  741. package/dist/chunks/studio-m3cwhvye.js +0 -5
  742. package/dist/chunks/studio-mcayt6hz.js +0 -5
  743. package/dist/chunks/studio-myjcghv3.js +0 -5
  744. package/dist/chunks/studio-n8bxwc3y.js +0 -4
  745. package/dist/chunks/studio-ngm8gvpb.js +0 -5
  746. package/dist/chunks/studio-nj5a01jy.js +0 -928
  747. package/dist/chunks/studio-pcgz8heb.js +0 -5
  748. package/dist/chunks/studio-qcsq0t15.js +0 -5
  749. package/dist/chunks/studio-qkstvve9.js +0 -5
  750. package/dist/chunks/studio-qv1pwd3c.js +0 -5
  751. package/dist/chunks/studio-rc6dvxjb.js +0 -7
  752. package/dist/chunks/studio-s8dmmz66.js +0 -5
  753. package/dist/chunks/studio-sp5xzbj4.js +0 -6
  754. package/dist/chunks/studio-szsjbj0g.js +0 -5
  755. package/dist/chunks/studio-t1xzawhr.js +0 -16
  756. package/dist/chunks/studio-tdcprbqb.js +0 -5
  757. package/dist/chunks/studio-vrmcjf9g.js +0 -7
  758. package/dist/chunks/studio-vyc0b4he.js +0 -10
  759. package/dist/chunks/studio-w0cgf6jg.js +0 -5
  760. package/dist/chunks/studio-w3hskzqk.js +0 -5
  761. package/dist/chunks/studio-xgrgcdyh.js +0 -9
  762. package/dist/chunks/studio-xz2yxfcb.js +0 -5
  763. package/dist/chunks/studio-y6n42tgs.js +0 -5
  764. package/dist/chunks/studio-yb560vxr.js +0 -5
  765. package/dist/chunks/studio-yn9f8vtx.js +0 -5
  766. package/dist/chunks/studio-yw05w8f0.js +0 -7
  767. package/dist/chunks/studio-z16sgavc.js +0 -5
  768. package/dist/chunks/studio-zey7zsdw.js +0 -12
  769. package/dist/chunks/studio-zyqn40w4.js +0 -5
  770. package/dist/chunks/swatch-group-8qh9a3bm.js +0 -4
  771. package/dist/chunks/tabs-jwthg40w.js +0 -4
  772. package/dist/chunks/textfield-spy3hc77.js +0 -4
  773. package/dist/chunks/toast-4d35zd64.js +0 -4
  774. package/dist/chunks/toast-host-8sjmfed3.js +0 -4
  775. package/dist/chunks/tooltip-fd6g9xy3.js +0 -4
  776. package/dist/chunks/tree-h19v1jfz.js +0 -4
  777. package/dist/chunks/tsMode-2frah6h1.js +0 -14
  778. package/dist/chunks/workspace-zt81g1ms.js +0 -4
  779. package/dist/chunks/ws-client-n2bh4hkf.js +0 -4
  780. package/src/panels/frontmatter-panel.ts +0 -701
  781. package/src/panels/git-panel.ts +0 -1120
  782. package/src/surfaces/doc-header.json +0 -776
  783. package/src/surfaces/doc-header.ts +0 -231
  784. package/src/surfaces/git-panel.json +0 -1880
  785. package/src/surfaces/git-panel.ts +0 -272
package/src/shell.ts CHANGED
@@ -8,8 +8,8 @@
8
8
  * per-render cleanup arrays. It stays a plain object, because wrapping a live editor instance and
9
9
  * detached DOM nodes in a reactive proxy is an active hazard.
10
10
  * - `shell` (here) holds UI **inputs** — which panel each dock shows, whether each dock is open and
11
- * how wide, the focused region, the layout preset, and the project-level state (source control,
12
- * the stylebook selection, the settings tab) that a _document_ must not own.
11
+ * how wide, the focused region, the workspace, and the project-level state (source control, the
12
+ * stylebook selection, the settings tab) that a _document_ must not own.
13
13
  *
14
14
  * Because this record is reactive, a surface tracks it by reading it: the rail, the left panel and
15
15
  * the toolbar all repaint from the effects they already run. The stand-in that preceded this —
@@ -21,7 +21,8 @@
21
21
  * the branch. See specs/studio.md and the level × placement matrix.
22
22
  */
23
23
 
24
- import { effect, effectScope, reactive } from "./reactivity";
24
+ import { isBoolean, isJsonObject, isNumber, isString } from "@jxsuite/schema/guards";
25
+ import { effect, effectScope, pauseTracking, reactive, resetTracking } from "./reactivity";
25
26
  import { SETTINGS } from "./services/settings/definitions";
26
27
  import { readStoredSetting, setSetting } from "./services/settings/kernel";
27
28
  import { loadedMonaco } from "./services/monaco-lazy";
@@ -35,21 +36,22 @@ import {
35
36
  booleanProperty,
36
37
  enumArg,
37
38
  enumProperty,
38
- stringArg,
39
- stringProperty,
40
39
  } from "./commands/command-args";
41
40
  import type { EffectScope } from "@vue/reactivity";
41
+ import type { JsonObject, JsonValue } from "@jxsuite/schema/types";
42
42
  import type { LayoutHit } from "./canvas/iframe-protocol";
43
- import type { GitBranchesResult, GitDiffState, GitStatusResult } from "./types";
43
+ import type { GitBranchesResult, GitDiffState, GitLogEntry, GitStatusResult } from "./types";
44
44
  import type { AnyCommand, CommandRegistry } from "./commands/registry";
45
-
46
- /**
47
- * The canvas selection when the author clicked LAYOUT chrome rather than page content — a header, a
48
- * footer, anything contributed by the layout file. It is deliberately NOT a document selection: the
49
- * node is not in the open page at all, so the properties panel shows the read-only layout panel
50
- * (with "Open Layout →") instead of the element inspector. See {@link setLayoutSelection}.
51
- */
52
- export type LayoutSelection = LayoutHit;
45
+ import {
46
+ DEFAULT_WORKSPACE,
47
+ isWorkspaceId,
48
+ WORKSPACE_IDS,
49
+ WORKSPACES,
50
+ } from "./workspace/workspace-ids";
51
+ import type { WorkspaceId } from "./workspace/workspace-ids";
52
+ import { DEFAULT_PUBLISH_DESTINATION, DEFAULT_PUBLISH_TAB } from "./publish/publish-ids";
53
+ import type { PublishDestination, PublishTab } from "./publish/publish-ids";
54
+ import type { PagesDeploymentInfo } from "./publish/pages-service";
53
55
 
54
56
  /**
55
57
  * The three resizable docks around the pane grid: two flanking it, one under it.
@@ -60,7 +62,7 @@ export type LayoutSelection = LayoutHit;
60
62
  * {@link INSPECTOR_TAB_IDS}, the same mechanism that selects Content, Style and Logic.
61
63
  *
62
64
  * `"bottom"` is the one the Bottom dock (§16.3) added. It is a dock like the other two — a collapse
63
- * flag, a remembered size, a resize handle, a place in every {@link LayoutPreset} — and the reason
65
+ * flag, a remembered size, a resize handle, a place in every workspace's frame — and the reason
64
66
  * `view.toggleBottomDock` had no idempotent setter for two phases is that it was not on this
65
67
  * record. It sits under the PANE GRID rather than under the window (§16.3), so opening it never
66
68
  * narrows the Navigator or the Inspector.
@@ -94,12 +96,13 @@ export type DockId = "left" | "right" | "bottom";
94
96
  * `tests/panel-registry.test.ts` asserts the two agree, so the duplicate cannot drift.
95
97
  */
96
98
  export const NAVIGATOR_PANEL_IDS = [
99
+ "site",
100
+ "collections",
101
+ "styles",
97
102
  "files",
98
103
  "search",
99
- "git",
100
104
  "i18n",
101
105
  "layers",
102
- "page",
103
106
  "data",
104
107
  "packages",
105
108
  "insert",
@@ -107,6 +110,32 @@ export const NAVIGATOR_PANEL_IDS = [
107
110
 
108
111
  export type NavigatorPanelId = (typeof NAVIGATOR_PANEL_IDS)[number];
109
112
 
113
+ /**
114
+ * The Navigator panels that exist only as one workspace's preset panel: no rail button, and no
115
+ * frame but that workspace's draws them. Collections is the Content workspace's Navigator (§22.5),
116
+ * and Styles the Styles workspace's (§7.1).
117
+ *
118
+ * The dock record is the WINDOW's, so `leftTab` is persisted whatever workspace wrote it; a window
119
+ * closed in Content stores `collections`. Every workspace's own frame replaces it on entry, but a
120
+ * window with no project open applies no workspace frame, and drew "Collections · project" over a
121
+ * rail that has no Collections button. {@link navigatorPanelOutsideWorkspace} is the guard both of
122
+ * those moments go through. `tests/panel-registry.test.ts` asserts this set is exactly the
123
+ * rail-less panels a preset pins, so it cannot drift from the records.
124
+ */
125
+ export const WORKSPACE_ONLY_PANELS: ReadonlySet<NavigatorPanelId> = new Set([
126
+ "collections",
127
+ "styles",
128
+ ]);
129
+
130
+ /**
131
+ * The Navigator panel to show when no workspace frame chooses one (boot, and no project open): the
132
+ * stored panel, unless it is {@link WORKSPACE_ONLY_PANELS workspace-only}, in which case the
133
+ * default workspace's own first panel.
134
+ */
135
+ export function navigatorPanelOutsideWorkspace(stored: NavigatorPanelId | null): NavigatorPanelId {
136
+ return stored && !WORKSPACE_ONLY_PANELS.has(stored) ? stored : DEFAULT_PANEL_ID;
137
+ }
138
+
110
139
  /**
111
140
  * Panel ids a previous build persisted, and what they are called now.
112
141
  *
@@ -115,42 +144,50 @@ export type NavigatorPanelId = (typeof NAVIGATOR_PANEL_IDS)[number];
115
144
  * value is the one place those old strings can still arrive from, so they are translated once, on
116
145
  * read, and then never again — there is no alias in the registry and no branch in the rail.
117
146
  */
118
- const RENAMED_PANEL_IDS: Readonly<Record<string, NavigatorPanelId>> = {
119
- blocks: "insert",
120
- head: "page",
121
- imports: "packages",
147
+ const RENAMED_PANEL_IDS = new Map<string, NavigatorPanelId>([
148
+ ["blocks", "insert"],
149
+ /* Not a rename either — a MOVE. The Page panel is the Inspector's first tab now (§6), so the
150
+ Navigator has no panel of that name; a stored `page` (or the `head` it was before that) reopens
151
+ the Navigator on the Site panel, the Design workspace's own default, rather than on nothing. */
152
+ ["head", "site"],
153
+ ["page", "site"],
154
+ ["imports", "packages"],
155
+ /* A move out of the Navigator altogether: Source Control is the Publish workspace's Review
156
+ changes and Branches now (§5.5, §22.8), so the Navigator has no panel of that name. A stored
157
+ `git` reopens on the Site panel, the Design workspace's own default, rather than on nothing. */
158
+ ["git", "site"],
122
159
  /* Not a rename — a MERGE. The State editor is part of the Data panel now (§5.6), so a
123
160
  stored `leftTab: "state"` names a real place and lands on it, rather than returning `null` and
124
161
  booting into the default the way `problems` does. Defining a value and watching it resolve are
125
162
  one task; they were two panels. */
126
- state: "data",
127
- };
128
-
129
- /** The panel the Navigator wakes up on when nothing usable is stored. */
130
- export const DEFAULT_PANEL_ID: NavigatorPanelId = "layers";
163
+ ["state", "data"],
164
+ ]);
131
165
 
132
- /** Whether a string is a declared panel id. */
133
- export function isNavigatorPanelId(value: unknown): value is NavigatorPanelId {
134
- return typeof value === "string" && (NAVIGATOR_PANEL_IDS as readonly string[]).includes(value);
135
- }
166
+ /**
167
+ * The panel the Navigator wakes up on when nothing usable is stored: the Site panel, the project by
168
+ * route (§5.7), because a fresh project is opened to find a page rather than to edit one.
169
+ */
170
+ export const DEFAULT_PANEL_ID: NavigatorPanelId = "site";
136
171
 
137
172
  /**
138
173
  * Narrow an id arriving from a `string`-typed roster, or throw naming where it came from.
139
174
  *
140
- * The lock on the door {@link NAVIGATOR_PANEL_IDS} describes, in one place so the rail and the ⌘1–8
141
- * dispatcher cannot disagree about what an undeclared id means. Both receive their id from
142
- * `PanelRecord.id`, which is deliberately a `string` — the registry hosts the Bottom dock's panels
143
- * too, and their ids are {@link BOTTOM_TAB_IDS}.
175
+ * The lock on the door {@link NAVIGATOR_PANEL_IDS} describes, in one place so the rail and the
176
+ * `panel.focus.*` dispatcher cannot disagree about what an undeclared id means. Both receive their
177
+ * id from `PanelRecord.id`, which is deliberately a `string` — the registry hosts the Bottom dock's
178
+ * panels too, and their ids are {@link BOTTOM_TAB_IDS}.
144
179
  *
145
180
  * It throws rather than returning null because `tests/panel-registry.test.ts` asserts the enum and
146
181
  * the registry agree, so a failure here means the two have drifted — and the symptom of the last
147
182
  * drift was a control that silently did nothing.
148
183
  */
149
184
  export function requireNavigatorPanelId(value: string, source: string): NavigatorPanelId {
150
- if (!isNavigatorPanelId(value)) {
185
+ // `find` hands back the DECLARED id, typed as one, which is the same string the caller sent.
186
+ const id = NAVIGATOR_PANEL_IDS.find((candidate) => candidate === value);
187
+ if (id === undefined) {
151
188
  throw new Error(`${source}: "${value}" is not a declared Navigator panel id.`);
152
189
  }
153
- return value;
190
+ return id;
154
191
  }
155
192
 
156
193
  /**
@@ -166,40 +203,56 @@ export function requireNavigatorPanelId(value: string, source: string): Navigato
166
203
  * Bottom dock keeps its own `bottomTab`, which already defaults to Problems. This is exactly the
167
204
  * wedge this function exists to prevent.
168
205
  */
169
- export function migratePanelId(stored: unknown): NavigatorPanelId | null {
170
- if (typeof stored !== "string") {
206
+ export function migratePanelId(stored: JsonValue | undefined): NavigatorPanelId | null {
207
+ if (!isString(stored)) {
171
208
  return null;
172
209
  }
173
- const renamed = RENAMED_PANEL_IDS[stored];
210
+ const renamed = RENAMED_PANEL_IDS.get(stored);
174
211
  if (renamed) {
175
212
  return renamed;
176
213
  }
177
- return isNavigatorPanelId(stored) ? stored : null;
214
+ return NAVIGATOR_PANEL_IDS.find((id) => id === stored) ?? null;
178
215
  }
179
216
 
180
217
  /**
181
- * The Inspector's tab ids, in strip order — the `value`s `panels/right-panel.ts` renders.
218
+ * The Inspector's LEAF ids, in strip order — every view the dock can show, and the values
219
+ * `view.setRightTab` accepts (§6).
182
220
  *
183
- * Four now, and `"assistant"` is one of them: the chat is a tab of this dock, so the id space that
184
- * addresses Content, Style and Logic addresses it too. The old arrangement needed a fourth verb
185
- * (`view.setAssistant`) precisely because the assistant was somewhere else; it survives as the
186
- * dock-toggle spelling of "show me the assistant", but it now writes the same field as the others.
187
- *
188
- * The ORDER is §6's — Content · Style · Logic · Assistant — which is the order ⌘⇧1–4 follow. The
189
- * human titles live in `commands/defaults.ts`'s `INSPECTOR_TABS` (a module with no state and no
190
- * DOM, so the CI checks can load it); `tests/right-panel.test.ts` asserts the two agree, the same
191
- * way `tests/panel-registry.test.ts` does for the rail.
221
+ * The strip draws three tabs, **Page · Element · Assistant**, and five leaves sit under them:
222
+ * `page`, then Element's three views (`properties` = Content, `style` = Style, `events` = Logic)
223
+ * behind a sub-switch, then `assistant`. The leaves stay addressable one by one because they are
224
+ * what scripts, chords and shots name — `view.setRightTab { tab: "style" }` shows Element with
225
+ * Style chosen — and a strip of three that addressed only itself would make Style unreachable by
226
+ * name. `commands/defaults.ts`'s `INSPECTOR_TABS` carries the titles and the strip grouping (a
227
+ * module with no state and no DOM, so the CI checks can load it); `tests/right-panel.test.ts`
228
+ * asserts the two agree, the same way `tests/panel-registry.test.ts` does for the rail.
192
229
  */
193
- export const INSPECTOR_TAB_IDS = ["properties", "style", "events", "assistant"] as const;
230
+ export const INSPECTOR_TAB_IDS = ["page", "properties", "style", "events", "assistant"] as const;
194
231
 
195
232
  export type InspectorTabId = (typeof INSPECTOR_TAB_IDS)[number];
196
233
 
197
- /** The tab the Inspector falls back to — the leftmost, Content. */
198
- export const DEFAULT_INSPECTOR_TAB: InspectorTabId = "properties";
234
+ /**
235
+ * The leaf the Inspector falls back to — Page, the strip's first tab, which is what the dock shows
236
+ * when nothing is selected (§6: an undeclared stored id coerces to Page).
237
+ */
238
+ export const DEFAULT_INSPECTOR_TAB: InspectorTabId = "page";
239
+
240
+ /** The Element tab's three leaves, in sub-switch order. */
241
+ export const ELEMENT_TAB_IDS = ["properties", "style", "events"] as const;
242
+
243
+ export type ElementTabId = (typeof ELEMENT_TAB_IDS)[number];
244
+
245
+ /** The Element leaf the strip lands on when the selection first arrives and none was used yet. */
246
+ export const DEFAULT_ELEMENT_TAB: ElementTabId = "properties";
247
+
248
+ /** Whether a leaf is one of the Element tab's three views. */
249
+ export function isElementTabId(value: unknown): value is ElementTabId {
250
+ return typeof value === "string" && ELEMENT_TAB_IDS.some((id) => id === value);
251
+ }
199
252
 
200
253
  /** Whether a string is a declared Inspector tab id. */
201
254
  export function isInspectorTabId(value: unknown): value is InspectorTabId {
202
- return typeof value === "string" && (INSPECTOR_TAB_IDS as readonly string[]).includes(value);
255
+ return typeof value === "string" && INSPECTOR_TAB_IDS.some((id) => id === value);
203
256
  }
204
257
 
205
258
  /**
@@ -225,7 +278,7 @@ export const DEFAULT_BOTTOM_TAB: BottomTabId = "problems";
225
278
 
226
279
  /** Whether a string is a declared Bottom dock tab id. */
227
280
  export function isBottomTabId(value: unknown): value is BottomTabId {
228
- return typeof value === "string" && (BOTTOM_TAB_IDS as readonly string[]).includes(value);
281
+ return typeof value === "string" && BOTTOM_TAB_IDS.some((id) => id === value);
229
282
  }
230
283
 
231
284
  /** The Studio chrome's themes — the `data-theme` stamp on `<html>` (studio-ui-guidelines.md §1.1). */
@@ -249,13 +302,9 @@ export interface DockState {
249
302
  size: number;
250
303
  }
251
304
 
252
- /** One row of `git log`, as the Source Control panel's History tab renders it. */
253
- export interface GitLogEntry {
254
- hash: string;
255
- message: string;
256
- author: string;
257
- date: string;
258
- }
305
+ /* One row of `git log`, as the Source Control panel's History tab renders it: the platform's own
306
+ `gitLog` row, re-exported so the panels reading shell state keep one import. */
307
+ export type { GitLogEntry } from "./types";
259
308
 
260
309
  /** Source control — project level, one copy, independent of which document is focused. */
261
310
  export interface ShellGit {
@@ -276,57 +325,149 @@ export interface ShellGit {
276
325
  */
277
326
  rev: number;
278
327
  logEntries: GitLogEntry[] | null;
279
- /** "changes" | "history" — the panel's sub-tab. */
280
- subTab: string;
281
328
  /** Epoch ms of the last successful refresh, or null when never refreshed. */
282
329
  lastUpdated: number | null;
283
330
  }
284
331
 
332
+ /**
333
+ * What the Publish workspace is showing (studio.md §22.8): which sub-tab, and which destination its
334
+ * Review changes and the quick publish dialog offer. Project level, like the repository both
335
+ * describe, and reset with it, so the next project opens on Review changes rather than on the last
336
+ * project's Branches.
337
+ */
338
+ export interface ShellPublish {
339
+ tab: PublishTab;
340
+ destination: PublishDestination;
341
+ /** What the host said about the connected project's deployments, and for which Pages project. */
342
+ deploys: {
343
+ state: "idle" | "loading" | "loaded" | "failed";
344
+ rows: PagesDeploymentInfo[];
345
+ error: string;
346
+ project: string;
347
+ };
348
+ /** The last build check, and the change set it was run against (`publish/publish-changes.ts`). */
349
+ build: { state: "idle" | "running" | "passed" | "failed"; text: string; for: string };
350
+ /** The pull request Send for review last opened; `number` 0 for none. */
351
+ review: { number: number; url: string };
352
+ }
353
+
285
354
  /** The stylebook catalogue's selection and filters — project level, like the styles they edit. */
286
355
  export interface ShellStylebook {
287
356
  selection: string | null;
288
- /** "elements" | "variables" */
289
- tab: string;
357
+ /**
358
+ * The custom property the Variables view is pointed at (`--accent`), chosen from the Outline's
359
+ * variable rows, or `null`. Which LIST the Outline draws is not stored here: it follows the view
360
+ * the pane is in (Elements or Variables), so the two can never disagree.
361
+ */
362
+ variable: string | null;
363
+ /**
364
+ * The view the Styles workspace's home is in: Variables (`tokens`, the first visit's view, which
365
+ * is the most common reason to come here) or Elements (`stylebook`). Written whenever
366
+ * `project.json` is shown in one of the two, so the next visit, and Back from Code, return to the
367
+ * view the author was last in. Code is never the home: it is a view opened beside it.
368
+ */
369
+ view: "stylebook" | "tokens";
290
370
  filter: string;
291
371
  customizedOnly: boolean;
292
372
  }
293
373
 
294
374
  /**
295
- * A named arrangement of the shell — a layout tab (`docs/studio/interface.md`, "Command Bar").
296
- *
297
- * A layout is a TUPLE, not a mode: which Navigator panel is showing, whether each dock is open and
298
- * how wide, and which Inspector tab is selected. Picking one reconfigures; it never removes. Every
299
- * panel stays reachable by rail, chord and palette afterwards, because workspaces that gate
300
- * features are refused (`docs/studio/interface.md`, "Command Bar") — telling a copy editor the
301
- * Style panel "is in ⌘K" hands the non-technical user the one affordance they are least likely to
302
- * reach for.
303
- *
304
- * The field this replaces (`shell.layout`, a string with a declared default and NO writer) was the
305
- * exact failure `layoutSelection` had already been through: a record nothing sets, read by chrome
306
- * that therefore cannot change. Wiring it was the alternative to deleting it, and these are the
307
- * writers.
308
- *
309
- * **Editor kind is deliberately not in the tuple.** The shell redesign's layout tuple named it, but
310
- * nothing in the shell can apply it: switching a pane's editor is `studio.ts`'s `setCanvasMode`,
311
- * which this module does not have and cannot be handed without changing a bootstrap it does not
312
- * own. A fourth field no writer could honour would recreate the defect this type exists to close,
313
- * so the tuple is three fields and the fourth arrives with the pane record that can carry it.
375
+ * What a workspace does to the frame when it is entered (`docs/studio/interface.md`, "Workspaces").
376
+ *
377
+ * A preset is applied on EVERY entry and is not a lock (`specs/studio.md` §22.3). Entering a
378
+ * workspace draws the rail only where the rail has something to offer (Design), and opens or
379
+ * collapses the Navigator and the Inspector for the work the workspace is for; the dock toggles
380
+ * keep working inside it, and the next entry applies the preset again. The one thing carried from
381
+ * visit to visit is which Navigator panel a workspace that names none was showing, so Design comes
382
+ * back on the panel it was left on rather than on Styles' Outline.
383
+ *
384
+ * It replaces the four named layouts (Write · Design · Build · Ship) that preceded it. A layout was
385
+ * a tuple of panels with no job attached; a workspace owns its tabs, so switching to one changes
386
+ * WHAT is on screen, not only where the docks are.
314
387
  */
315
- export interface LayoutPreset {
316
- /** Stable key. Built-ins use their own lowercase name; a saved layout gets a slug of its. */
317
- id: string;
318
- /** What the Command Bar tab reads. Renamed by double-clicking it. */
319
- name: string;
320
- /** Which Navigator panel this arrangement shows. */
321
- navigatorPanel: NavigatorPanelId;
322
- /** Which Inspector tab this arrangement selects. */
323
- inspectorTab: InspectorTabId;
324
- /** Which Bottom dock tab this arrangement selects — read whether or not the dock is open. */
325
- bottomTab: BottomTabId;
326
- /** Dock visibility and sizes, all three of them. */
327
- docks: Record<DockId, DockState>;
388
+ export interface WorkspacePreset {
389
+ id: WorkspaceId;
390
+ /** Whether the rail column is drawn at all. It has no toggle of its own, so this is a class. */
391
+ rail: boolean;
392
+ /**
393
+ * The Navigator on entry: open or collapsed, and on which panel. `panel` is applied on EVERY
394
+ * entry; `initial` is the panel a workspace that remembers its own opens on when it has nothing
395
+ * to remember yet — Design's Site panel (§22.3: "the Site panel, or the last Design panel").
396
+ */
397
+ navigator: { open: boolean; panel?: NavigatorPanelId; initial?: NavigatorPanelId };
398
+ /**
399
+ * The Inspector: open or collapsed on entry, and the tab it selects when one is named. The tab is
400
+ * per-document state, so naming it costs no other document anything.
401
+ *
402
+ * `whileWriting` draws it over a collapsed `open` while the active tab is a document written on
403
+ * the canvas ({@link WorkspaceDeps.writingOnCanvas}): Content's entries are edited on the canvas,
404
+ * where element selection and the Inspector apply, and its Library and forms are not; Styles'
405
+ * Elements view selects element defaults on its canvas, and its Variables and Code views do not.
406
+ */
407
+ inspector: { open: boolean; tab?: InspectorTabId; whileWriting?: boolean };
408
+ /**
409
+ * Whether each pane draws its document tab strip. Design and Content do: they are where several
410
+ * documents are open at once. Media, Styles and Publish draw their home surface alone, and what
411
+ * they open beside it (a media file, a comparison, `project.json` as Code) is left through the
412
+ * pane context bar's Back control (`workspace/workspace-back.ts`), so no strip is needed to get
413
+ * back. Projected as {@link TAB_STRIP_HIDDEN_CLASS} on `#app`.
414
+ */
415
+ tabStrip: boolean;
328
416
  }
329
417
 
418
+ /**
419
+ * The five presets.
420
+ *
421
+ * - **Design** draws the rail and both side docks: the canvas, its outline and its inspector.
422
+ * - **Content** opens the Navigator on the Collections panel and collapses the Inspector, drawing it
423
+ * while an entry is being written on the canvas.
424
+ * - **Media** and **Publish** collapse both side docks: each one's home is a surface that lays itself
425
+ * out (the media grid, the change list).
426
+ * - **Styles** opens the Navigator on the Styles panel (variables, element defaults and color
427
+ * schemes), and the Inspector on its Style tab only while the Elements view is on screen: that
428
+ * view selects an element default on a canvas, and the Inspector is where it is styled. Variables
429
+ * is edited in the view itself, and Code has no canvas, so both leave the pane its width.
430
+ *
431
+ * Only Design and Content draw a tab strip; the other three draw their home surface alone.
432
+ */
433
+ export const WORKSPACE_PRESETS: Readonly<Record<WorkspaceId, WorkspacePreset>> = {
434
+ content: {
435
+ id: "content",
436
+ inspector: { open: false, whileWriting: true },
437
+ navigator: { open: true, panel: "collections" },
438
+ rail: false,
439
+ tabStrip: true,
440
+ },
441
+ design: {
442
+ id: "design",
443
+ inspector: { open: true },
444
+ navigator: { initial: "site", open: true },
445
+ rail: true,
446
+ tabStrip: true,
447
+ },
448
+ media: {
449
+ id: "media",
450
+ inspector: { open: false },
451
+ navigator: { open: false },
452
+ rail: false,
453
+ tabStrip: false,
454
+ },
455
+ publish: {
456
+ id: "publish",
457
+ inspector: { open: false },
458
+ navigator: { open: false },
459
+ rail: false,
460
+ tabStrip: false,
461
+ },
462
+ styles: {
463
+ id: "styles",
464
+ inspector: { open: false, tab: "style", whileWriting: true },
465
+ navigator: { open: true, panel: "styles" },
466
+ rail: false,
467
+ tabStrip: false,
468
+ },
469
+ };
470
+
330
471
  export interface ShellState {
331
472
  /** Which panel the Navigator dock shows. One of {@link NAVIGATOR_PANEL_IDS}. */
332
473
  leftTab: string;
@@ -350,25 +491,30 @@ export interface ShellState {
350
491
  */
351
492
  paneSplit: number;
352
493
  focusRegion: FocusRegion;
353
- /** The active {@link LayoutPreset}'s id — the Command Bar tab drawn as selected. */
354
- layout: string;
355
- /** Every layout this project has, built-ins included. Persisted per project root. */
356
- layouts: LayoutPreset[];
357
- layoutSelection: LayoutSelection | null;
358
- /** Which section Project Settings opens on. */
359
- settingsTab: string;
494
+ /**
495
+ * The workspace on screen — the top bar's selected tab, and the filter every pane's tab strip
496
+ * applies. Written by {@link enterWorkspace} alone. Persisted per project root.
497
+ */
498
+ workspace: WorkspaceId;
499
+ /**
500
+ * Layout chrome the author clicked, or `null`: a header, a footer, anything contributed by the
501
+ * layout file. It is deliberately NOT a document selection: the node is not in the open page at
502
+ * all, so the properties panel shows the read-only layout panel (with "Open Layout →") instead of
503
+ * the element inspector. See {@link setLayoutSelection}.
504
+ */
505
+ layoutSelection: LayoutHit | null;
360
506
  stylebook: ShellStylebook;
361
507
  git: ShellGit;
508
+ publish: ShellPublish;
362
509
  }
363
510
 
364
- const DOCK_STORAGE_KEY = "jx-studio-panel-widths";
511
+ export const DOCK_STORAGE_KEY = "jx-studio-panel-widths";
365
512
 
366
513
  /**
367
514
  * The per-project record's key prefix — one namespaced record per project root (§14.8).
368
515
  *
369
- * Namespaced because a layout is an arrangement of THIS project's panels: "Ship" over a repo with
370
- * no git remote is not the same tuple as "Ship" over one that has one, and two projects sharing a
371
- * key means opening the second silently rewrites the first.
516
+ * Namespaced because a workspace and its session are THIS project's: two projects sharing a key
517
+ * means opening the second silently rewrites the first.
372
518
  *
373
519
  * Dock geometry stays in {@link DOCK_STORAGE_KEY}, which is read at module scope, before any
374
520
  * project root exists. It is workspace state — `resetProjectShell()` has always kept it across a
@@ -387,8 +533,8 @@ const PROJECT_STORAGE_PREFIX = "jx-studio-project::";
387
533
  const DEFAULT_THEME: ChromeTheme = "dark";
388
534
 
389
535
  /** Whether a string is one of the declared {@link CHROME_THEMES}. */
390
- function isChromeTheme(value: unknown): value is ChromeTheme {
391
- return typeof value === "string" && (CHROME_THEMES as readonly string[]).includes(value);
536
+ export function isChromeTheme(value: unknown): value is ChromeTheme {
537
+ return typeof value === "string" && CHROME_THEMES.some((theme) => theme === value);
392
538
  }
393
539
 
394
540
  /** Read the persisted theme, falling back to {@link DEFAULT_THEME} on an absent/unknown value. */
@@ -486,8 +632,8 @@ export const PANE_SPLIT_MAX = 0.8;
486
632
  * range, so the only way a bad value arrives is a corrupt or hand-edited record — and that is the
487
633
  * one caller that has to be defended.
488
634
  */
489
- function clampPaneSplit(value: unknown): number {
490
- return typeof value === "number" && value >= PANE_SPLIT_MIN && value <= PANE_SPLIT_MAX
635
+ function clampPaneSplit(value: JsonValue | undefined): number {
636
+ return isNumber(value) && value >= PANE_SPLIT_MIN && value <= PANE_SPLIT_MAX
491
637
  ? value
492
638
  : DEFAULT_PANE_SPLIT;
493
639
  }
@@ -497,15 +643,27 @@ export function setPaneSplit(value: number): void {
497
643
  shell.paneSplit = Math.min(PANE_SPLIT_MAX, Math.max(PANE_SPLIT_MIN, value));
498
644
  }
499
645
 
500
- /** Read the persisted dock record, tolerating absent/corrupt storage. */
501
- function readPersistedDocks(): PersistedDocks {
646
+ /**
647
+ * Read the persisted dock record, tolerating absent/corrupt storage.
648
+ *
649
+ * What comes back is the parsed JSON object rather than a {@link PersistedDocks}: that interface is
650
+ * what {@link persistDocks} WRITES, and a stored record is untrusted input, so every member is
651
+ * checked where it is read.
652
+ */
653
+ function readPersistedDocks(): JsonObject {
502
654
  try {
503
- return JSON.parse(localStorage.getItem(DOCK_STORAGE_KEY) || "{}") as PersistedDocks;
655
+ const parsed: unknown = JSON.parse(localStorage.getItem(DOCK_STORAGE_KEY) || "{}");
656
+ return isJsonObject(parsed) ? parsed : {};
504
657
  } catch {
505
658
  return {};
506
659
  }
507
660
  }
508
661
 
662
+ /** One state per dock, in {@link DOCK_IDS} order, each built by `make`. */
663
+ function perDock(make: (id: DockId) => DockState): Record<DockId, DockState> {
664
+ return { left: make("left"), right: make("right"), bottom: make("bottom") };
665
+ }
666
+
509
667
  function freshGit(): ShellGit {
510
668
  return {
511
669
  branches: null,
@@ -517,93 +675,31 @@ function freshGit(): ShellGit {
517
675
  logEntries: null,
518
676
  rev: 0,
519
677
  status: null,
520
- subTab: "changes",
521
678
  };
522
679
  }
523
680
 
524
- function freshStylebook(): ShellStylebook {
525
- return { customizedOnly: false, filter: "", selection: null, tab: "elements" };
681
+ function freshPublish(): ShellPublish {
682
+ return {
683
+ build: { for: "", state: "idle", text: "" },
684
+ deploys: { error: "", project: "", rows: [], state: "idle" },
685
+ destination: DEFAULT_PUBLISH_DESTINATION,
686
+ review: { number: 0, url: "" },
687
+ tab: DEFAULT_PUBLISH_TAB,
688
+ };
526
689
  }
527
690
 
528
- /**
529
- * The four layouts every project starts with — `Write · Design · Build · Ship`.
530
- *
531
- * Each is a real arrangement of surfaces that already exist, not a mode: Write puts the file set
532
- * beside the prose and the Inspector on Content; Design opens the Outline against the Style tab;
533
- * Build pairs Data with Logic; Ship opens Source Control and gives the whole width to the diff by
534
- * collapsing the Inspector — which stays one ⌘B away, because a layout never removes.
535
- *
536
- * Written as a factory so every project gets its own objects: they are mutable records (a saved
537
- * layout overwrites the one with its name) and a shared array literal would leak one project's
538
- * edits into the next.
539
- */
540
- function builtInLayouts(): LayoutPreset[] {
541
- return [
542
- {
543
- bottomTab: "problems",
544
- docks: {
545
- bottom: { collapsed: true, size: 220 },
546
- left: { collapsed: false, size: 240 },
547
- /* WRITE COLLAPSES THE INSPECTOR. The shell redesign's layout arithmetic is the whole point
548
- of this preset: 1600px − 56 rail − 240 navigator ≈ **1284px of page, 80%**, against
549
- Design's 974. It shipped with the Inspector open at 280px, which is Design's number and
550
- makes Write a Design with a different panel selected.
551
-
552
- It does NOT re-open on selection. The shell redesign's sentence read "until something is
553
- selected", and clicking a paragraph to put a caret in it IS a selection — a dock that
554
- opened every time you clicked into prose would take the 80% back on the first keystroke.
555
- `⌘⇧1`–`⌘⇧4` expand it (`focusInspectorTab` sets `collapsed: false` before it focuses), so
556
- the Inspector is one chord away whenever it is wanted. */
557
- right: { collapsed: true, size: 280 },
558
- },
559
- id: "write",
560
- inspectorTab: "properties",
561
- name: "Write",
562
- navigatorPanel: "files",
563
- },
564
- {
565
- bottomTab: "problems",
566
- docks: {
567
- bottom: { collapsed: true, size: 220 },
568
- left: { collapsed: false, size: 240 },
569
- right: { collapsed: false, size: 320 },
570
- },
571
- id: "design",
572
- inspectorTab: "style",
573
- name: "Design",
574
- navigatorPanel: "layers",
575
- },
576
- {
577
- bottomTab: "logic",
578
- docks: {
579
- bottom: { collapsed: true, size: 260 },
580
- left: { collapsed: false, size: 280 },
581
- right: { collapsed: false, size: 300 },
582
- },
583
- id: "build",
584
- inspectorTab: "events",
585
- name: "Build",
586
- navigatorPanel: "data",
587
- },
588
- {
589
- // The one built-in that OPENS the Bottom dock, on Activity: shipping is a long operation
590
- // With a log, which is exactly what that tab is (§16.4 — Deploy folds into Activity).
591
- bottomTab: "activity",
592
- docks: {
593
- bottom: { collapsed: false, size: 240 },
594
- left: { collapsed: false, size: 300 },
595
- right: { collapsed: true, size: 280 },
596
- },
597
- id: "ship",
598
- inspectorTab: "properties",
599
- name: "Ship",
600
- navigatorPanel: "git",
601
- },
602
- ];
603
- }
691
+ /** The view the Styles workspace opens on until the author picks another: Variables. */
692
+ export const DEFAULT_STYLES_HOME_VIEW: ShellStylebook["view"] = "tokens";
604
693
 
605
- /** The layout a project wakes up on, and the fallback when a stored id names nothing. */
606
- export const DEFAULT_LAYOUT_ID = "design";
694
+ function freshStylebook(): ShellStylebook {
695
+ return {
696
+ customizedOnly: false,
697
+ filter: "",
698
+ selection: null,
699
+ variable: null,
700
+ view: DEFAULT_STYLES_HOME_VIEW,
701
+ };
702
+ }
607
703
 
608
704
  /**
609
705
  * Build the whole record — nested collections complete — before it is handed to `reactive()`.
@@ -613,31 +709,29 @@ export const DEFAULT_LAYOUT_ID = "design";
613
709
  */
614
710
  function createShellState(): ShellState {
615
711
  const saved = readPersistedDocks();
616
- const docks = {} as Record<DockId, DockState>;
617
- for (const id of DOCK_IDS) {
712
+ const docks = perDock((id) => {
618
713
  const width = saved[id];
619
- const collapsed = saved[`${id}Collapsed` as const];
620
- docks[id] = {
714
+ const collapsed = saved[`${id}Collapsed`];
715
+ return {
621
716
  // A stored `false` has to REOPEN a dock, so this cannot be the usual
622
717
  // `if (saved.x) { collapse() }` shape — that silently pins a dock shut forever for
623
718
  // Everyone who ever closed it once.
624
- collapsed: typeof collapsed === "boolean" ? collapsed : DOCK_DEFAULTS[id].collapsed,
625
- size: typeof width === "number" && width > 0 ? width : DOCK_DEFAULTS[id].size,
719
+ collapsed: isBoolean(collapsed) ? collapsed : DOCK_DEFAULTS[id].collapsed,
720
+ size: isNumber(width) && width > 0 ? width : DOCK_DEFAULTS[id].size,
626
721
  };
627
- }
722
+ });
628
723
  return {
629
724
  bottomTab: isBottomTabId(saved.bottomTab) ? saved.bottomTab : DEFAULT_BOTTOM_TAB,
630
725
  docks,
631
726
  focusRegion: "pane",
632
727
  paneSplit: clampPaneSplit(saved.paneSplit),
633
728
  git: freshGit(),
634
- layout: DEFAULT_LAYOUT_ID,
729
+ publish: freshPublish(),
635
730
  layoutSelection: null,
636
- layouts: builtInLayouts(),
637
- leftTab: migratePanelId(saved.leftTab) ?? DEFAULT_PANEL_ID,
638
- settingsTab: "stylebook",
731
+ leftTab: navigatorPanelOutsideWorkspace(migratePanelId(saved.leftTab)),
639
732
  stylebook: freshStylebook(),
640
733
  theme: readPersistedTheme(),
734
+ workspace: DEFAULT_WORKSPACE,
641
735
  };
642
736
  }
643
737
 
@@ -651,15 +745,37 @@ function createShellState(): ShellState {
651
745
  */
652
746
  applyStartupProfile();
653
747
 
748
+ /**
749
+ * Storage keys a retired surface used to write, removed on boot so a stale preference never
750
+ * outlives the control that set it.
751
+ *
752
+ * `jx-studio-doc-header-expanded` was the Document Header card's fold-to-bar preference. Edit draws
753
+ * no card any more (studio.md §4.2), so nothing reads the key and nothing should be left holding
754
+ * it.
755
+ */
756
+ export const RETIRED_STORAGE_KEYS: readonly string[] = ["jx-studio-doc-header-expanded"];
757
+
758
+ /** Drop every {@link RETIRED_STORAGE_KEYS} entry, tolerating unavailable storage. Never throws. */
759
+ export function forgetRetiredStorage(): void {
760
+ try {
761
+ for (const key of RETIRED_STORAGE_KEYS) {
762
+ localStorage.removeItem(key);
763
+ }
764
+ } catch {
765
+ // Storage blocked or unavailable: there is nothing stored to forget.
766
+ }
767
+ }
768
+
769
+ forgetRetiredStorage();
770
+
654
771
  /*
655
- * The input is widened before `reactive()` sees it: `UnwrapNestedRefs<ShellState>` recurses through
656
- * `LayoutHit` and the git result shapes deeply enough for tsc to give up (TS2589). The proxy is
657
- * deep either way — only the type computation is short-circuited — and the declared annotation is
658
- * what every consumer sees. Same reason `tabs/tab.ts` casts its own reactive tree.
772
+ * SAFETY: `reactive()` returns a deep proxy carrying its target's own members, and ShellState holds
773
+ * no refs for `UnwrapNestedRefs` to unwrap, so the proxy IS a ShellState. The type argument is
774
+ * pinned to `object` because `UnwrapNestedRefs<ShellState>` recurses through `LayoutHit` and the
775
+ * git result shapes deeply enough for tsc to give up (TS2589). The proxy is deep either way — only
776
+ * the type computation is short-circuited. Same reason `tabs/tab.ts` casts its own reactive tree.
659
777
  */
660
- export const shell: ShellState = reactive(
661
- createShellState() as unknown as Record<string, unknown>,
662
- ) as unknown as ShellState;
778
+ export const shell = reactive<object>(createShellState()) as ShellState;
663
779
 
664
780
  /**
665
781
  * Write the whole dock record in one shot.
@@ -689,75 +805,69 @@ export function persistDocks(): void {
689
805
  }
690
806
  }
691
807
 
692
- // ─── Named layouts ────────────────────────────────────────────────────────────
808
+ // ─── The project's record: its workspace, and its session ────────────────────
693
809
 
694
- /** What a project's namespaced record holds: its named layouts, and the session it was left in. */
810
+ /**
811
+ * What a project's namespaced record holds: the workspace it was left in, and the session.
812
+ *
813
+ * A record written by a build that had named layouts also carries `layouts` and `activeLayout`.
814
+ * Neither is read, and the next write drops them: the layouts they name were replaced by the
815
+ * workspaces, and there is nothing in them a workspace could adopt.
816
+ */
695
817
  interface PersistedProject {
696
- layouts?: unknown;
697
- activeLayout?: unknown;
698
- /** §14.8's session — open documents per pane, the active one, and each one's view settings. */
699
- session?: unknown;
818
+ workspace?: WorkspaceId;
819
+ /**
820
+ * §14.8's session — open documents per pane, the active one, and each one's view settings. A
821
+ * session this boot has not read yet is carried over as the stored JSON it was.
822
+ */
823
+ session?: PersistedSession | JsonValue | undefined;
700
824
  }
701
825
 
702
- /** The project whose layouts are loaded, or `undefined` before the first sync. */
703
- let _layoutRoot: string | null | undefined;
826
+ /** The project whose workspace is loaded, or `undefined` before the first sync. */
827
+ let _workspaceRoot: string | null | undefined;
704
828
 
705
- /** Whether a parsed value is a usable {@link LayoutPreset} — a stored record is untrusted input. */
706
- function isLayoutPreset(value: unknown): value is LayoutPreset {
707
- if (typeof value !== "object" || value === null) {
708
- return false;
709
- }
710
- const preset = value as Partial<LayoutPreset>;
711
- return (
712
- typeof preset.id === "string" &&
713
- preset.id !== "" &&
714
- typeof preset.name === "string" &&
715
- isNavigatorPanelId(preset.navigatorPanel) &&
716
- isInspectorTabId(preset.inspectorTab) &&
717
- isBottomTabId(preset.bottomTab) &&
718
- DOCK_IDS.every((dock) => typeof preset.docks?.[dock]?.size === "number")
719
- );
720
- }
829
+ /**
830
+ * The Navigator panel each workspace whose preset names none was last showing, in this window. Not
831
+ * persisted: it is a convenience between two visits, and the preset is the statement of what a
832
+ * workspace looks like on entry.
833
+ */
834
+ let _panels: Partial<Record<WorkspaceId, string>> = {};
721
835
 
722
836
  /** The raw record for `root`, or `{}` for absent, corrupt or hand-edited storage. */
723
- function rawPersistedProject(root: string | null): PersistedProject {
837
+ function rawPersistedProject(root: string | null): JsonObject {
724
838
  if (!root) {
725
839
  return {};
726
840
  }
727
841
  try {
728
- return JSON.parse(
842
+ const parsed: unknown = JSON.parse(
729
843
  localStorage.getItem(`${PROJECT_STORAGE_PREFIX}${root}`) || "{}",
730
- ) as PersistedProject;
844
+ );
845
+ return isJsonObject(parsed) ? parsed : {};
731
846
  } catch {
732
847
  return {};
733
848
  }
734
849
  }
735
850
 
736
- /** Read a project's namespaced record, tolerating absent, corrupt and hand-edited storage. */
737
- function readPersistedProject(root: string | null): {
738
- layouts: LayoutPreset[];
739
- activeLayout: string | null;
740
- } {
741
- const fresh = { activeLayout: null, layouts: builtInLayouts() };
742
- if (!root) {
743
- return fresh;
744
- }
745
- const parsed = rawPersistedProject(root);
746
- const layouts = Array.isArray(parsed.layouts) ? parsed.layouts.filter(isLayoutPreset) : [];
747
- if (layouts.length === 0) {
748
- return fresh;
749
- }
750
- return {
751
- activeLayout: typeof parsed.activeLayout === "string" ? parsed.activeLayout : null,
752
- layouts,
753
- };
851
+ /**
852
+ * The workspace `root` was last left in: a missing, stale or unknown id (a layout id from the build
853
+ * that had named layouts, a workspace a later build renamed, a hand edit) is
854
+ * {@link DEFAULT_WORKSPACE}. Never throws: a stale record must not be the reason a project cannot
855
+ * open.
856
+ *
857
+ * The open sequence asks for it again AFTER restoring the session (`files/files.ts`), because
858
+ * restoring activates tabs and activating a tab enters that tab's workspace: a project left in
859
+ * Media, whose home tab is not a document the session records, would otherwise reopen in Design.
860
+ */
861
+ export function persistedWorkspace(root: string | null): WorkspaceId {
862
+ const stored = rawPersistedProject(root).workspace;
863
+ return isWorkspaceId(stored) ? stored : DEFAULT_WORKSPACE;
754
864
  }
755
865
 
756
866
  /**
757
867
  * The session `root` was last left in, or `null` when there is none to restore.
758
868
  *
759
- * Separate from {@link readPersistedProject} because the two are read at different moments: the
760
- * layouts load reactively the instant the project root changes, and the session is read once, by
869
+ * Separate from {@link syncProjectWorkspace} because the two are read at different moments: the
870
+ * workspace loads reactively the instant the project root changes, and the session is read once, by
761
871
  * the open sequence, at the point where it would otherwise open the home page.
762
872
  */
763
873
  export function persistedSession(root: string | null): PersistedSession | null {
@@ -783,13 +893,6 @@ export function markSessionRestored(root: string | null): void {
783
893
  _sessionRoot = root;
784
894
  }
785
895
 
786
- /**
787
- * Write the project's namespaced record. One key, one writer, one shape.
788
- *
789
- * The dock record's two-writer bug (`persistWidths` vs `applyPanelCollapse`, both on
790
- * {@link DOCK_STORAGE_KEY}) is the reason this is a single function that serialises the whole
791
- * record rather than a merge at each call site.
792
- */
793
896
  /**
794
897
  * Write the session on the way out — the moment the whole feature exists for.
795
898
  *
@@ -802,6 +905,13 @@ export function flushSession(): void {
802
905
  persistProjectShell();
803
906
  }
804
907
 
908
+ /**
909
+ * Write the project's namespaced record. One key, one writer, one shape.
910
+ *
911
+ * The dock record's two-writer bug (`persistWidths` vs `applyPanelCollapse`, both on
912
+ * {@link DOCK_STORAGE_KEY}) is the reason this is a single function that serialises the whole
913
+ * record rather than a merge at each call site.
914
+ */
805
915
  export function persistProjectShell(): void {
806
916
  const root = workspace.projectRoot;
807
917
  if (!root) {
@@ -811,10 +921,9 @@ export function persistProjectShell(): void {
811
921
  localStorage.setItem(
812
922
  `${PROJECT_STORAGE_PREFIX}${root}`,
813
923
  JSON.stringify({
814
- activeLayout: shell.layout,
815
- layouts: shell.layouts,
816
924
  // Only for a project whose session has been read — see {@link markSessionRestored}.
817
925
  session: root === _sessionRoot ? captureSession() : rawPersistedProject(root).session,
926
+ workspace: shell.workspace,
818
927
  } satisfies PersistedProject),
819
928
  );
820
929
  } catch {
@@ -823,147 +932,99 @@ export function persistProjectShell(): void {
823
932
  }
824
933
 
825
934
  /**
826
- * Load the layouts belonging to `root`. Idempotent per root, so the effect can call it freely.
935
+ * Load the workspace belonging to `root`. Idempotent per root, so the effect can call it freely.
827
936
  *
828
- * Loading does NOT apply the active layout: a boot that re-applied it would overwrite the dock
829
- * widths the user dragged since, which are persisted separately and are the more recent statement.
830
- * The stored id says which tab is drawn as selected; clicking it is what re-applies the tuple.
937
+ * The id is read by {@link persistedWorkspace}, so a stale one opens the project in
938
+ * {@link DEFAULT_WORKSPACE} and loading never throws.
939
+ *
940
+ * Opening a project ENTERS its workspace, so the workspace's preset is applied here too. The dock
941
+ * record is the window's rather than the project's, and without this a project closed while Media
942
+ * had collapsed both side docks left the next project's Design frame collapsed. The rail follows
943
+ * `shell.workspace` on its own, through {@link applyDockLayout}. No preset is applied for `null`
944
+ * (no project open): there is no workspace on screen to lay out.
831
945
  */
832
- export function syncProjectLayouts(root: string | null): void {
833
- if (root === _layoutRoot) {
946
+ export function syncProjectWorkspace(root: string | null): void {
947
+ if (root === _workspaceRoot) {
834
948
  return;
835
949
  }
836
- _layoutRoot = root;
837
- const { activeLayout, layouts } = readPersistedProject(root);
838
- shell.layouts = layouts;
839
- const has = (id: string | null) => id !== null && layouts.some((preset) => preset.id === id);
840
- // The stored active layout wins, then the one already selected, then the default, then whatever
841
- // The project actually has — a bar drawn with nothing selected says the app has lost your place.
842
- shell.layout = has(activeLayout)
843
- ? activeLayout!
844
- : has(shell.layout)
845
- ? shell.layout
846
- : has(DEFAULT_LAYOUT_ID)
847
- ? DEFAULT_LAYOUT_ID
848
- : (layouts[0]?.id ?? DEFAULT_LAYOUT_ID);
849
- }
850
-
851
- /** The layout with this id, or `undefined`. */
852
- export function layoutById(id: string): LayoutPreset | undefined {
853
- return shell.layouts.find((preset) => preset.id === id);
854
- }
855
-
856
- /** A stable id for a saved layout's name: `My Layout` → `my-layout`. */
857
- export function layoutIdFor(name: string): string {
858
- const slug = name
859
- .trim()
860
- .toLowerCase()
861
- .replaceAll(/[^a-z0-9]+/g, "-")
862
- .replaceAll(/^-|-$/g, "");
863
- return slug || "layout";
950
+ _workspaceRoot = root;
951
+ _panels = {};
952
+ shell.workspace = persistedWorkspace(root);
953
+ if (root === null) {
954
+ /* No frame to apply, but the Navigator is still on screen beside the rail: a panel only the
955
+ closed project's workspace drew (Collections, left in Content) is not this window's to show. */
956
+ shell.leftTab = navigatorPanelOutsideWorkspace(migratePanelId(shell.leftTab));
957
+ return;
958
+ }
959
+ /* Untracked: this runs inside the shell's project-root effect, which must depend on the root
960
+ alone and not on the panel and docks the preset reads and writes. */
961
+ pauseTracking();
962
+ try {
963
+ applyFrame(shell.workspace);
964
+ persistDocks();
965
+ } finally {
966
+ resetTracking();
967
+ }
864
968
  }
865
969
 
866
- /** What applying or capturing a layout needs from the Inspector, which lives on the active tab. */
867
- export interface LayoutDeps {
970
+ /** What entering a workspace needs from the Inspector, whose tab lives on the active document. */
971
+ export interface WorkspaceDeps {
868
972
  setInspectorTab: (tab: InspectorTabId) => void;
869
- inspectorTab: () => InspectorTabId;
973
+ /**
974
+ * Whether the active tab is a document being written on the canvas, which is what draws a
975
+ * `whileWriting` Inspector ({@link WorkspacePreset.inspector}). Injected by
976
+ * `workspace/workspace-switch.ts`, which owns the rule that reads a tab; absent, it is `false`.
977
+ */
978
+ writingOnCanvas?: () => boolean;
870
979
  }
871
980
 
872
- /**
873
- * Adopt a layout: its panel, its docks, its Inspector tab — and remember that it is the active one.
874
- *
875
- * Throws on an unknown id rather than silently doing nothing, because the id space is the user's
876
- * own (their saved layouts) and a typo in a command argument has to be reportable.
877
- */
878
- export function applyLayout(id: string, deps: LayoutDeps): void {
879
- const preset = layoutById(id);
880
- if (!preset) {
881
- throw new RangeError(
882
- `no layout named "${id}" — this project has: ${shell.layouts.map((p) => p.id).join(", ")}`,
883
- );
884
- }
885
- shell.layout = preset.id;
886
- shell.leftTab = preset.navigatorPanel;
887
- shell.bottomTab = preset.bottomTab;
888
- for (const dock of DOCK_IDS) {
889
- shell.docks[dock].collapsed = preset.docks[dock].collapsed;
890
- shell.docks[dock].size = preset.docks[dock].size;
891
- }
892
- deps.setInspectorTab(preset.inspectorTab);
893
- persistDocks();
894
- persistProjectShell();
981
+ /** Whether a workspace's preset draws its Inspector, given whether a document is being written. */
982
+ export function presetInspectorOpen(id: WorkspaceId, writing: boolean): boolean {
983
+ const { inspector } = WORKSPACE_PRESETS[id];
984
+ return inspector.open || (inspector.whileWriting === true && writing);
895
985
  }
896
986
 
897
- /**
898
- * Save the current arrangement under `name`, and make it the active layout.
899
- *
900
- * Saving over an existing name overwrites it — that is what `+` on a layout you have just adjusted
901
- * means, and it keeps the operation idempotent for a caller that cannot see the list.
902
- */
903
- export function saveLayout(name: string, deps: LayoutDeps): LayoutPreset {
904
- const trimmed = name.trim();
905
- const id = layoutIdFor(trimmed);
906
- const docks = {} as Record<DockId, DockState>;
907
- for (const dock of DOCK_IDS) {
908
- docks[dock] = { collapsed: shell.docks[dock].collapsed, size: shell.docks[dock].size };
987
+ /** Lay a workspace's preset out: the Navigator's panel, both side docks and the Inspector's tab. */
988
+ function applyFrame(id: WorkspaceId, deps?: WorkspaceDeps): void {
989
+ const preset = WORKSPACE_PRESETS[id];
990
+ const panel = migratePanelId(
991
+ preset.navigator.panel ?? _panels[id] ?? preset.navigator.initial ?? shell.leftTab,
992
+ );
993
+ if (panel) {
994
+ shell.leftTab = panel;
909
995
  }
910
- const preset: LayoutPreset = {
911
- bottomTab: isBottomTabId(shell.bottomTab) ? shell.bottomTab : DEFAULT_BOTTOM_TAB,
912
- docks,
913
- id,
914
- inspectorTab: deps.inspectorTab(),
915
- name: trimmed || id,
916
- navigatorPanel: migratePanelId(shell.leftTab) ?? DEFAULT_PANEL_ID,
917
- };
918
- const existing = shell.layouts.findIndex((entry) => entry.id === id);
919
- if (existing === -1) {
920
- shell.layouts.push(preset);
921
- } else {
922
- shell.layouts.splice(existing, 1, preset);
996
+ shell.docks.left.collapsed = !preset.navigator.open;
997
+ shell.docks.right.collapsed = !presetInspectorOpen(id, deps?.writingOnCanvas?.() === true);
998
+ if (preset.inspector.tab && deps) {
999
+ deps.setInspectorTab(preset.inspector.tab);
923
1000
  }
924
- shell.layout = id;
925
- persistProjectShell();
926
- return preset;
927
- }
928
-
929
- /** Rename a layout in place. The id — and therefore every reference to it — is unchanged. */
930
- export function renameLayout(id: string, name: string): void {
931
- const preset = layoutById(id);
932
- const trimmed = name.trim();
933
- if (!preset || !trimmed) {
934
- return;
935
- }
936
- preset.name = trimmed;
937
- persistProjectShell();
938
1001
  }
939
1002
 
940
1003
  /**
941
- * Forget a layout. The last one cannot be deleted: an empty Command Bar with a `+` and no way to
942
- * know what it does is worse than a layout you do not use.
1004
+ * Enter a workspace: lay its frame out by its {@link WORKSPACE_PRESETS} preset, which every entry
1005
+ * applies afresh (`specs/studio.md` §22.3).
1006
+ *
1007
+ * This is the frame half of a switch and the one writer of `shell.workspace`. Which TAB is on
1008
+ * screen is the other half, and it is not this module's: `workspace/workspace-switch.ts` activates
1009
+ * the workspace's tab, and activating any tab enters that tab's workspace through here.
1010
+ *
1011
+ * Re-entering the workspace already showing changes nothing, so the verb is idempotent and a dock
1012
+ * the author just toggled is not put back.
1013
+ *
1014
+ * @returns Whether the workspace changed.
943
1015
  */
944
- export function deleteLayout(id: string): void {
945
- if (shell.layouts.length < 2) {
946
- return;
947
- }
948
- const index = shell.layouts.findIndex((preset) => preset.id === id);
949
- if (index === -1) {
950
- return;
1016
+ export function enterWorkspace(id: WorkspaceId, deps?: WorkspaceDeps): boolean {
1017
+ if (shell.workspace === id) {
1018
+ return false;
951
1019
  }
952
- shell.layouts.splice(index, 1);
953
- if (shell.layout === id) {
954
- shell.layout = shell.layouts[0]!.id;
1020
+ if (!WORKSPACE_PRESETS[shell.workspace].navigator.panel) {
1021
+ _panels[shell.workspace] = shell.leftTab;
955
1022
  }
1023
+ shell.workspace = id;
1024
+ applyFrame(id, deps);
1025
+ persistDocks();
956
1026
  persistProjectShell();
957
- }
958
-
959
- /**
960
- * Re-apply the active layout as declared — `View: Reset Layout`, always one action away.
961
- *
962
- * This is the escape hatch that makes a layout safe to drift from: drag a dock, collapse the
963
- * Inspector, open a different panel, and one command puts the arrangement back.
964
- */
965
- export function resetLayout(deps: LayoutDeps): void {
966
- applyLayout(shell.layout, deps);
1027
+ return true;
967
1028
  }
968
1029
 
969
1030
  /**
@@ -1006,7 +1067,28 @@ export function applyChromeTheme(): void {
1006
1067
  loadedMonaco()?.editor.setTheme(monacoTheme());
1007
1068
  }
1008
1069
 
1009
- /** Project the dock record onto the shell grid: collapse classes on #app, sizes as CSS vars. */
1070
+ /**
1071
+ * The `#app` class that takes the rail column out of the grid. `styles/shell-frame.json` owns what
1072
+ * it means; the rail has no toggle and no size, so it is a class and never a dock.
1073
+ */
1074
+ export const RAIL_HIDDEN_CLASS = "rail-hidden";
1075
+
1076
+ /**
1077
+ * The `#app` class that takes every pane's tab strip out of the grid, in a workspace whose preset
1078
+ * draws none ({@link WorkspacePreset.tabStrip}). `styles/shell-frame.json` owns what it means; the
1079
+ * strips stay mounted underneath, so returning to Design repaints nothing.
1080
+ */
1081
+ export const TAB_STRIP_HIDDEN_CLASS = "tabs-hidden";
1082
+
1083
+ /** Whether the workspace on screen draws pane tab strips. */
1084
+ export function workspaceDrawsTabStrip(id: WorkspaceId = shell.workspace): boolean {
1085
+ return WORKSPACE_PRESETS[id].tabStrip;
1086
+ }
1087
+
1088
+ /**
1089
+ * Project the dock record onto the shell grid: collapse classes on #app, sizes as CSS vars, and the
1090
+ * active workspace's rail and tab strips.
1091
+ */
1010
1092
  export function applyDockLayout(): void {
1011
1093
  const root = document.documentElement;
1012
1094
  for (const id of DOCK_IDS) {
@@ -1019,6 +1101,8 @@ export function applyDockLayout(): void {
1019
1101
  for (const id of DOCK_IDS) {
1020
1102
  app.classList.toggle(DOCK_CLASS[id], shell.docks[id].collapsed);
1021
1103
  }
1104
+ app.classList.toggle(RAIL_HIDDEN_CLASS, !WORKSPACE_PRESETS[shell.workspace].rail);
1105
+ app.classList.toggle(TAB_STRIP_HIDDEN_CLASS, !WORKSPACE_PRESETS[shell.workspace].tabStrip);
1022
1106
  }
1023
1107
 
1024
1108
  let _scope: EffectScope | null = null;
@@ -1086,11 +1170,11 @@ export function mountShell(): void {
1086
1170
  effect(() => {
1087
1171
  applyChromeTheme();
1088
1172
  });
1089
- // Layouts belong to a project, and the project root is reactive: opening one loads its record
1090
- // With no boot-order hook to forget. `resetProjectShell()` runs while the OLD root is still
1091
- // Set — it is the closing half of a switch — which is why the load cannot live there.
1173
+ // The workspace belongs to a project, and the project root is reactive: opening one loads its
1174
+ // Record with no boot-order hook to forget. `resetProjectShell()` runs while the OLD root is
1175
+ // Still set — it is the closing half of a switch — which is why the load cannot live there.
1092
1176
  effect(() => {
1093
- syncProjectLayouts(workspace.projectRoot);
1177
+ syncProjectWorkspace(workspace.projectRoot);
1094
1178
  });
1095
1179
  /*
1096
1180
  * §14.8's session, written whenever its SHAPE changes.
@@ -1219,7 +1303,7 @@ export function toggleActivityTab(tab: NavigatorPanelId): void {
1219
1303
  * a long release cycle with a reader (the properties panel's layout panel) and no writer at all,
1220
1304
  * which is why clicking a header did nothing.
1221
1305
  */
1222
- export function setLayoutSelection(selection: LayoutSelection | null): void {
1306
+ export function setLayoutSelection(selection: LayoutHit | null): void {
1223
1307
  shell.layoutSelection = selection;
1224
1308
  }
1225
1309
 
@@ -1228,21 +1312,21 @@ export function setLayoutSelection(selection: LayoutSelection | null): void {
1228
1312
  *
1229
1313
  * Source control, the stylebook selection and the settings tab all describe _a_ project; carrying
1230
1314
  * them into the next one showed the previous repository's branch, file count and "last updated"
1231
- * time under the new project's name. Docks, the layout preset and the active rail panel are
1232
- * deliberately kept — they describe the workspace, not the project.
1315
+ * time under the new project's name. Docks and the active rail panel are deliberately kept: they
1316
+ * describe the window, not the project. The workspace is the project's, and the next
1317
+ * {@link syncProjectWorkspace} reads it from that project's own record.
1233
1318
  */
1234
1319
  export function resetProjectShell(): void {
1235
1320
  Object.assign(shell.git, freshGit());
1321
+ Object.assign(shell.publish, freshPublish());
1236
1322
  Object.assign(shell.stylebook, freshStylebook());
1237
- shell.settingsTab = "stylebook";
1238
1323
  shell.layoutSelection = null;
1239
1324
  // …and which project's session may be written, so the one being opened cannot be overwritten
1240
1325
  // Before it has been read.
1241
1326
  _sessionRoot = null;
1242
- // Forget which project's layouts are loaded, so the next `syncProjectLayouts()` reloads even if
1243
- // The same root is opened again. The docks and the active rail panel stay: they describe the
1244
- // Workspace, and re-applying a layout on project open would discard the widths you just dragged.
1245
- _layoutRoot = undefined;
1327
+ // Forget which project's workspace is loaded, so the next `syncProjectWorkspace()` reloads even
1328
+ // If the same root is opened again.
1329
+ _workspaceRoot = undefined;
1246
1330
  }
1247
1331
 
1248
1332
  // ─── Commands ─────────────────────────────────────────────────────────────────
@@ -1264,6 +1348,14 @@ export interface ShellCommandDeps {
1264
1348
  * screenshot that closed the assistant would silently also lose your Style tab.
1265
1349
  */
1266
1350
  inspectorTab: () => InspectorTabId;
1351
+ /**
1352
+ * The tab the strip rests on when nothing chose one: Page with nothing selected, otherwise the
1353
+ * Element leaf last used (`specs/studio.md` §6). `view.setAssistant { open: false }` steps onto
1354
+ * it, so closing the Assistant with a node selected shows that node rather than the document.
1355
+ * Injected because the selection and the remembered leaf are per-document session state; absent
1356
+ * (the CI projection), it is {@link DEFAULT_INSPECTOR_TAB}.
1357
+ */
1358
+ restingInspectorTab?: () => InspectorTabId;
1267
1359
  /**
1268
1360
  * Whether a dock tab's panel record is currently available — its own `when` holding.
1269
1361
  *
@@ -1277,6 +1369,24 @@ export interface ShellCommandDeps {
1277
1369
  * so a caller with no registry (the CI projection) is unaffected.
1278
1370
  */
1279
1371
  panelAvailable?: (id: string) => boolean;
1372
+ /**
1373
+ * Show a workspace: enter it AND put its tabs on screen (its most recent tab, or its home).
1374
+ *
1375
+ * Injected for the reason the Inspector writers are: opening a workspace's home reaches the
1376
+ * Library, the settings document and the pane model, none of which this module may import. Absent
1377
+ * (the CI projection, a test with no panes), the verb enters the workspace's frame and nothing
1378
+ * else, which is still the state the argument names.
1379
+ */
1380
+ showWorkspace?: (id: WorkspaceId) => void;
1381
+ }
1382
+
1383
+ /** Run {@link ShellCommandDeps.showWorkspace}, or enter the frame alone when none was handed in. */
1384
+ function showWorkspace(id: WorkspaceId, deps: ShellCommandDeps): void {
1385
+ if (deps.showWorkspace) {
1386
+ deps.showWorkspace(id);
1387
+ return;
1388
+ }
1389
+ enterWorkspace(id, deps);
1280
1390
  }
1281
1391
 
1282
1392
  /** The Navigator dock hosts a panel — the precondition its own verbs share. */
@@ -1453,7 +1563,7 @@ export function shellViewCommands(deps: ShellCommandDeps): AnyCommand[] {
1453
1563
  duplicate title among 130 records. A palette row is a NAME, and two rows with one name is
1454
1564
  the defect the registry exists to prevent, arriving through the registry itself.
1455
1565
  `inspector.focus.assistant` is the row (it carries ⌘⇧4). This one keeps every other
1456
- consumer — the `open` STATE that `setRightTab` cannot express, the layout presets, three
1566
+ consumer — the `open` STATE that `setRightTab` cannot express, the workspace presets, three
1457
1567
  screenshot steps, `⌘J`'s legacy branch — none of which needs a row to work. */
1458
1568
  menus: ["never"],
1459
1569
  group: "4_docks",
@@ -1464,100 +1574,49 @@ export function shellViewCommands(deps: ShellCommandDeps): AnyCommand[] {
1464
1574
  return;
1465
1575
  }
1466
1576
  if (deps.inspectorTab() === "assistant") {
1467
- deps.setInspectorTab(DEFAULT_INSPECTOR_TAB);
1577
+ deps.setInspectorTab(deps.restingInspectorTab?.() ?? DEFAULT_INSPECTOR_TAB);
1468
1578
  }
1469
1579
  },
1470
1580
  title: "Show Assistant",
1471
1581
  },
1472
- // ── Named layouts ────────────────────────────────────────────────────────
1473
- // Five verbs, because a named arrangement you can create but not rename, delete or restore is
1474
- // A trap. All five are SETTERS: `view.setLayout { layout: "ship" }` means the same thing twice
1475
- // In a row, and `view.resetLayout` is the one action that puts a drifted arrangement back.
1582
+ // ── Workspaces ───────────────────────────────────────────────────────────
1583
+ // One SETTER, and one chord record per workspace. A chord cannot carry an argument, so ⌘1–5
1584
+ // Are five records that each run the setter's own path rather than five implementations of it.
1476
1585
  {
1477
1586
  args: argsSchema({
1478
- layout: stringProperty("The layout's id — one of this project's saved layouts."),
1587
+ workspace: enumProperty(WORKSPACE_IDS, "Which workspace to show."),
1479
1588
  }),
1480
1589
  category: "View",
1481
- id: "view.setLayout",
1590
+ id: "view.setWorkspace",
1482
1591
  level: "application",
1483
- menus: ["palette"],
1484
- group: "4_layouts",
1592
+ /* `never`, not `palette`: the five `workspace.focus.*` rows below are the palette's way in,
1593
+ each with its chord, and an arguments prompt beside them would be the same action under a
1594
+ second name. This record is the scripted door and the one the top bar's tabs run. */
1595
+ menus: ["never"],
1596
+ group: "4_workspaces",
1485
1597
  requires: "an open project",
1486
1598
  when: projectOpen,
1487
1599
  /* No `aiTool`: application-level chrome, refused at registration
1488
1600
  (`studio-ui-guidelines.md` §12.4). */
1489
1601
  run: (_ctx, args) => {
1490
- applyLayout(stringArg("view.setLayout", args, "layout"), deps);
1491
- },
1492
- title: "Switch Layout",
1493
- },
1494
- {
1495
- args: argsSchema({
1496
- name: stringProperty("What to call this arrangement. An existing name is overwritten."),
1497
- }),
1498
- category: "View",
1499
- id: "view.saveLayout",
1500
- level: "application",
1501
- menus: ["palette"],
1502
- group: "4_layouts",
1503
- requires: "an open project",
1504
- when: projectOpen,
1505
- run: (_ctx, args) => {
1506
- saveLayout(stringArg("view.saveLayout", args, "name"), deps);
1602
+ showWorkspace(enumArg("view.setWorkspace", args, "workspace", WORKSPACE_IDS), deps);
1507
1603
  },
1508
- title: "Save Layout",
1604
+ title: "Switch Workspace",
1509
1605
  },
1510
- {
1511
- args: argsSchema({
1512
- layout: stringProperty("The layout's id."),
1513
- name: stringProperty("Its new name. The id, and every reference to it, is unchanged."),
1514
- }),
1606
+ ...WORKSPACES.map((entry, index): AnyCommand => ({
1515
1607
  category: "View",
1516
- id: "view.renameLayout",
1608
+ id: `workspace.focus.${entry.id}`,
1609
+ keybinding: `mod+${index + 1}`,
1517
1610
  level: "application",
1518
1611
  menus: ["palette"],
1519
- group: "4_layouts",
1520
- requires: "an open project",
1521
- when: projectOpen,
1522
- run: (_ctx, args) => {
1523
- renameLayout(
1524
- stringArg("view.renameLayout", args, "layout"),
1525
- stringArg("view.renameLayout", args, "name"),
1526
- );
1527
- },
1528
- title: "Rename Layout",
1529
- },
1530
- {
1531
- args: argsSchema({
1532
- layout: stringProperty("The layout's id. The last remaining layout is kept."),
1533
- }),
1534
- category: "View",
1535
- id: "view.deleteLayout",
1536
- level: "application",
1537
- menus: ["palette"],
1538
- group: "4_layouts",
1539
- requires: "an open project",
1540
- when: projectOpen,
1541
- run: (_ctx, args) => {
1542
- deleteLayout(stringArg("view.deleteLayout", args, "layout"));
1543
- },
1544
- title: "Delete Layout",
1545
- },
1546
- {
1547
- // No `args`: this verb takes none. An empty schema would be a promise of parameters the
1548
- // Palette would then prompt for and the record would ignore.
1549
- category: "View",
1550
- id: "view.resetLayout",
1551
- level: "application",
1552
- menus: ["palette"],
1553
- group: "4_layouts",
1612
+ group: "4_workspaces",
1554
1613
  requires: "an open project",
1555
1614
  when: projectOpen,
1556
1615
  run: () => {
1557
- resetLayout(deps);
1616
+ showWorkspace(entry.id, deps);
1558
1617
  },
1559
- title: "Reset Layout",
1560
- },
1618
+ title: `Go to ${entry.title}`,
1619
+ })),
1561
1620
  {
1562
1621
  args: argsSchema({
1563
1622
  color: enumProperty(CHROME_THEMES, "The Studio chrome's theme."),