@jxsuite/studio 6.1.2 → 8.0.1

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 (522) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/2020-5wtzce5g.js +4 -0
  3. package/dist/chunks/adapter-5tphg7xe.js +4 -0
  4. package/dist/chunks/add-new-flow-psym14sn.js +4 -0
  5. package/dist/chunks/cf-account-picker-haqazq5r.js +4 -0
  6. package/dist/chunks/cf-settings-h2wrh6hq.js +4 -0
  7. package/dist/chunks/{color-area-0fj8a4ga.js → color-area-6rbra5dy.js} +2 -2
  8. package/dist/chunks/{color-field-xch8kx2g.js → color-field-gy56c94g.js} +3 -3
  9. package/dist/chunks/color-m7r1vk54.js +4 -0
  10. package/dist/chunks/{color-slider-jxd8v350.js → color-slider-sqqdyf6c.js} +2 -2
  11. package/dist/chunks/{combobox-p0efwjke.js → combobox-72h32ejk.js} +3 -3
  12. package/dist/chunks/components-r22xca09.js +4 -0
  13. package/dist/chunks/content-source-mmppmcyg.js +4 -0
  14. package/dist/chunks/convert-to-repeater-he70fc2b.js +4 -0
  15. package/dist/chunks/editor-ccd9jwsa.js +4 -0
  16. package/dist/chunks/entry-commands-6qxz9e8p.js +4 -0
  17. package/dist/chunks/extension-sections-0egb88dm.js +4 -0
  18. package/dist/chunks/file-ops-qnab36ek.js +4 -0
  19. package/dist/chunks/files-rcn56qvm.js +4 -0
  20. package/dist/chunks/format-host-yxz0kjjs.js +4 -0
  21. package/dist/chunks/fs-events-d1qxq4z4.js +4 -0
  22. package/dist/chunks/grid-open-4z1g062t.js +4 -0
  23. package/dist/chunks/{grid-view-d2wq9ezg.js → grid-view-qg1b5eqx.js} +25 -25
  24. package/dist/chunks/{icons-pnfz478q.js → icons-63bjtbs7.js} +2 -2
  25. package/dist/chunks/{iframe-entry-jdcktez8.js → iframe-entry-2zggzajv.js} +4 -4
  26. package/dist/chunks/{iframe-entry-zap785cw.js → iframe-entry-76j8vwwx.js} +4 -4
  27. package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
  28. package/dist/chunks/{iframe-entry-x0ygf2yr.js → iframe-entry-pkgmcgp0.js} +4 -4
  29. package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
  30. package/dist/chunks/{index-zpjmzqfk.js → index-32eqzv1k.js} +3 -3
  31. package/dist/chunks/index-f0jn7nnp.js +4 -0
  32. package/dist/chunks/{javascript-m54mnwp6.js → javascript-pjp734q4.js} +3 -3
  33. package/dist/chunks/jsonMode-njkvsa02.js +13 -0
  34. package/dist/chunks/jx-validate-ttasrbh7.js +4 -0
  35. package/dist/chunks/layers-bgyr11tr.js +4 -0
  36. package/dist/chunks/layers-panel-7ez66zeq.js +4 -0
  37. package/dist/chunks/{listbox-e74qjyq6.js → listbox-c421cf8c.js} +2 -2
  38. package/dist/chunks/lit-html-v9gh687x.js +4 -0
  39. package/dist/chunks/media-library-sq468wts.js +4 -0
  40. package/dist/chunks/media-picker-shfe59pt.js +4 -0
  41. package/dist/chunks/{menu-n6yzswmy.js → menu-bdpzbfvs.js} +2 -2
  42. package/dist/chunks/{monaco-binding-7gx83x3r.js → monaco-binding-vq1hgnn0.js} +3 -3
  43. package/dist/chunks/monaco-buffer-3afgwvbp.js +4 -0
  44. package/dist/chunks/monaco-setup-xrd2cbkp.js +4 -0
  45. package/dist/chunks/notify-kzrjr3fv.js +4 -0
  46. package/dist/chunks/parse-v77xtwv7.js +4 -0
  47. package/dist/chunks/platform-7v4rtd7m.js +4 -0
  48. package/dist/chunks/popover-b89sfej7.js +4 -0
  49. package/dist/chunks/publish-panel-2z9gw8ee.js +4 -0
  50. package/dist/chunks/publish-quick-qx2455cj.js +4 -0
  51. package/dist/chunks/publish-workspace-wrc1dgwx.js +4 -0
  52. package/dist/chunks/quick-search-n0t0eqxb.js +4 -0
  53. package/dist/chunks/references-9547rg5s.js +4 -0
  54. package/dist/chunks/right-panel-wsgnr9pn.js +4 -0
  55. package/dist/chunks/source-control-kwktr9y6.js +4 -0
  56. package/dist/chunks/{split-n2gfpm5m.js → split-c9zfdgxf.js} +2 -2
  57. package/dist/chunks/studio-03ct6yfv.js +9 -0
  58. package/dist/chunks/studio-07rcfje3.js +5 -0
  59. package/dist/chunks/studio-3e54qh39.js +7 -0
  60. package/dist/chunks/studio-3exxhz5g.js +5 -0
  61. package/dist/chunks/studio-3q40e3rj.js +5 -0
  62. package/dist/chunks/studio-3qeh9t0h.js +6 -0
  63. package/dist/chunks/studio-3qqjeep5.js +6 -0
  64. package/dist/chunks/studio-43sd2mam.js +4 -0
  65. package/dist/chunks/studio-4hp8pkyt.js +5 -0
  66. package/dist/chunks/studio-58xg6j2q.js +9 -0
  67. package/dist/chunks/studio-5g0rprdt.js +7 -0
  68. package/dist/chunks/{studio-2j1cb95x.js → studio-6m380wp7.js} +4 -4
  69. package/dist/chunks/studio-80jyh1xh.js +4 -0
  70. package/dist/chunks/studio-83mk4bgg.js +9 -0
  71. package/dist/chunks/{studio-v0jzqdjm.js → studio-881z1xrx.js} +4 -4
  72. package/dist/chunks/studio-8m0y2qe7.js +5 -0
  73. package/dist/chunks/{studio-d81xfh1g.js → studio-8tr6cfrc.js} +4 -4
  74. package/dist/chunks/studio-8vcvfs7r.js +6 -0
  75. package/dist/chunks/studio-9tmebf0s.js +5 -0
  76. package/dist/chunks/studio-a2bvaptz.js +5 -0
  77. package/dist/chunks/studio-b7y5mr30.js +5 -0
  78. package/dist/chunks/studio-bavb31r0.js +5 -0
  79. package/dist/chunks/{studio-dgz8d0c1.js → studio-bbe4c90a.js} +4 -4
  80. package/dist/chunks/studio-c05wdnt3.js +5 -0
  81. package/dist/chunks/studio-ccv2svc6.js +5 -0
  82. package/dist/chunks/studio-d5grtb9e.js +16 -0
  83. package/dist/chunks/studio-df0baea9.js +5 -0
  84. package/dist/chunks/studio-dgk4dqtx.js +7 -0
  85. package/dist/chunks/studio-e1g64q43.js +5 -0
  86. package/dist/chunks/studio-ed1hp6zf.js +6 -0
  87. package/dist/chunks/studio-ed3xx40h.js +7 -0
  88. package/dist/chunks/studio-f14e1zdj.js +5 -0
  89. package/dist/chunks/studio-fbhxxc5f.js +5 -0
  90. package/dist/chunks/studio-fyh0jc96.js +5 -0
  91. package/dist/chunks/studio-gcxk20n7.js +5 -0
  92. package/dist/chunks/studio-jaaz8ce9.js +5 -0
  93. package/dist/chunks/{studio-90pq9km1.js → studio-jcnsdaep.js} +4 -4
  94. package/dist/chunks/studio-jsgf2q5k.js +7 -0
  95. package/dist/chunks/{studio-0q988f9w.js → studio-k3xxbagr.js} +3 -3
  96. package/dist/chunks/{studio-5ve6bbep.js → studio-k7evxqh4.js} +4 -4
  97. package/dist/chunks/studio-kt3pjkwd.js +5 -0
  98. package/dist/chunks/studio-m8g16bgz.js +5 -0
  99. package/dist/chunks/studio-mn0476ag.js +5 -0
  100. package/dist/chunks/studio-mx1smyq0.js +5 -0
  101. package/dist/chunks/studio-n0c5h6mf.js +5 -0
  102. package/dist/chunks/{studio-485046te.js → studio-nh6d342n.js} +7 -7
  103. package/dist/chunks/studio-nqxqf8p2.js +4 -0
  104. package/dist/chunks/studio-p4nrkvqr.js +5 -0
  105. package/dist/chunks/{studio-1fxkaaea.js → studio-pbxa5fpc.js} +4 -4
  106. package/dist/chunks/studio-pgmvdv7z.js +7 -0
  107. package/dist/chunks/studio-ph84avkf.js +5 -0
  108. package/dist/chunks/{studio-tq0rtfwj.js → studio-q0mvbaq6.js} +4 -4
  109. package/dist/chunks/studio-q0tccd43.js +5 -0
  110. package/dist/chunks/studio-q7rtx6zq.js +14 -0
  111. package/dist/chunks/{studio-a3g98wjr.js → studio-qgh13jtz.js} +4 -4
  112. package/dist/chunks/studio-qmn1xc24.js +5 -0
  113. package/dist/chunks/studio-qwdqwkj0.js +5 -0
  114. package/dist/chunks/studio-r5qy30vx.js +5 -0
  115. package/dist/chunks/studio-rj9eamf8.js +5 -0
  116. package/dist/chunks/studio-rptgsk96.js +5 -0
  117. package/dist/chunks/studio-rthyg42z.js +928 -0
  118. package/dist/chunks/studio-s19tp1xr.js +5 -0
  119. package/dist/chunks/studio-s32578pn.js +5 -0
  120. package/dist/chunks/studio-svb7yyve.js +5 -0
  121. package/dist/chunks/studio-syreb644.js +5 -0
  122. package/dist/chunks/studio-tf9jryb1.js +10 -0
  123. package/dist/chunks/studio-tfkznv39.js +5 -0
  124. package/dist/chunks/studio-tft6r105.js +5 -0
  125. package/dist/chunks/studio-txf2kysx.js +5 -0
  126. package/dist/chunks/studio-v8867ddv.js +12 -0
  127. package/dist/chunks/studio-vxq7fcvv.js +5 -0
  128. package/dist/chunks/studio-w4rz9hdk.js +5 -0
  129. package/dist/chunks/studio-wbcjbhy1.js +9 -0
  130. package/dist/chunks/studio-wc649wss.js +5 -0
  131. package/dist/chunks/studio-wys23mxz.js +5 -0
  132. package/dist/chunks/studio-x1x167jh.js +5 -0
  133. package/dist/chunks/studio-x9hsze61.js +5 -0
  134. package/dist/chunks/studio-y0n7yxea.js +9 -0
  135. package/dist/chunks/studio-y3ahhdpx.js +5 -0
  136. package/dist/chunks/studio-z97c48fk.js +5 -0
  137. package/dist/chunks/studio-zat62m0c.js +5 -0
  138. package/dist/chunks/studio-zcd2e7yz.js +5 -0
  139. package/dist/chunks/studio-zz9qe3rw.js +64 -0
  140. package/dist/chunks/toast-1gv0h53h.js +4 -0
  141. package/dist/chunks/{toast-host-ke5z4ng7.js → toast-host-1yg5ar8d.js} +3 -3
  142. package/dist/chunks/{tooltip-5v8ngwj2.js → tooltip-yypz28gq.js} +3 -3
  143. package/dist/chunks/{tree-mdq3v5cw.js → tree-9xa4hm7n.js} +2 -2
  144. package/dist/chunks/tsMode-epyfz1hv.js +14 -0
  145. package/dist/chunks/workspace-dz7nxfhv.js +4 -0
  146. package/dist/chunks/workspace-home-xsd7hkjw.js +4 -0
  147. package/dist/chunks/{ws-client-nze0k6mv.js → ws-client-bv552sfx.js} +3 -3
  148. package/dist/iframe-entry.js +20 -16
  149. package/dist/manifest.json +77 -63
  150. package/dist/studio.js +103 -104
  151. package/dist/workers/ts.worker.js +3 -3
  152. package/package.json +12 -11
  153. package/src/browse/component-examples.ts +272 -0
  154. package/src/browse/library-commands.ts +64 -104
  155. package/src/browse/library-layouts.ts +263 -49
  156. package/src/browse/library-model.ts +181 -133
  157. package/src/browse/library-pane.ts +738 -446
  158. package/src/browse/library-preview.ts +149 -19
  159. package/src/browse/library-source.ts +308 -41
  160. package/src/browse/preview-root.ts +78 -0
  161. package/src/browse/preview-style.ts +155 -0
  162. package/src/canvas/asset-refs.ts +16 -1
  163. package/src/canvas/canvas-live-render.ts +222 -30
  164. package/src/canvas/canvas-origin.ts +37 -12
  165. package/src/canvas/canvas-page-context.ts +112 -0
  166. package/src/canvas/canvas-render.ts +113 -107
  167. package/src/canvas/canvas-utils.ts +57 -12
  168. package/src/canvas/iframe-drop.ts +107 -0
  169. package/src/canvas/iframe-entry.ts +24 -12
  170. package/src/canvas/iframe-host.ts +134 -62
  171. package/src/canvas/iframe-keys.ts +11 -0
  172. package/src/canvas/iframe-patch.ts +8 -7
  173. package/src/canvas/iframe-protocol.ts +24 -5
  174. package/src/canvas/iframe-render.ts +48 -1
  175. package/src/canvas/iframe-subtree.ts +5 -2
  176. package/src/canvas/npm-modules.ts +22 -0
  177. package/src/canvas/preview-navigate.ts +16 -0
  178. package/src/canvas/repeater-sources.ts +200 -0
  179. package/src/canvas/stage-scrollbar.ts +5 -1
  180. package/src/canvas/surface-registry.ts +6 -0
  181. package/src/commands/app-commands.ts +16 -6
  182. package/src/commands/budget.ts +10 -8
  183. package/src/commands/context.ts +35 -10
  184. package/src/commands/defaults.ts +69 -45
  185. package/src/commands/keymap.ts +9 -0
  186. package/src/commands/levels.ts +11 -5
  187. package/src/commands/live-context.ts +2 -0
  188. package/src/commands/registry.ts +10 -1
  189. package/src/content/collection-match.ts +6 -5
  190. package/src/content/collection-pages.ts +412 -0
  191. package/src/content/content-collections.ts +270 -0
  192. package/src/content/entry-commands.ts +51 -60
  193. package/src/content/entry-editor.ts +111 -18
  194. package/src/content/entry-fields.ts +13 -1
  195. package/src/content/entry-inspector.ts +225 -0
  196. package/src/content/entry-model.ts +17 -3
  197. package/src/content/entry-open.ts +80 -0
  198. package/src/content/entry-summaries.ts +440 -0
  199. package/src/content/file-record-ids.ts +80 -0
  200. package/src/content/file-records.ts +125 -0
  201. package/src/content/page-summaries.ts +129 -0
  202. package/src/content/record-entry.ts +201 -0
  203. package/src/content/writing-banner.ts +177 -0
  204. package/src/create/add-new-commands.ts +185 -0
  205. package/src/create/add-new-flow.ts +1116 -0
  206. package/src/create/add-new.ts +1014 -0
  207. package/src/create/element-name.ts +121 -0
  208. package/src/editor/convert-to-component.ts +67 -43
  209. package/src/editor/file-drop-action.ts +9 -1
  210. package/src/editor/inline-edit-apply.ts +44 -0
  211. package/src/editor/inline-edit.ts +10 -0
  212. package/src/editor/insert-component.ts +91 -0
  213. package/src/editor/insert-zone-action.ts +13 -4
  214. package/src/editor/shortcuts.ts +44 -22
  215. package/src/editor/slash-menu.ts +51 -8
  216. package/src/files/components.ts +74 -10
  217. package/src/files/file-ops.ts +15 -5
  218. package/src/files/files.ts +164 -150
  219. package/src/files/fs-events.ts +18 -7
  220. package/src/files/media-meta.ts +95 -1
  221. package/src/files/media-types.ts +55 -0
  222. package/src/files/media-upload.ts +209 -52
  223. package/src/files/new-file-name.ts +156 -0
  224. package/src/files/serialize-document.ts +13 -3
  225. package/src/format/format-host.ts +15 -0
  226. package/src/github/github-auth.ts +2 -2
  227. package/src/github/github-publish.ts +4 -3
  228. package/src/grid/grid-controller.ts +5 -1
  229. package/src/grid/grid-open.ts +0 -27
  230. package/src/grid/grid-panel.ts +54 -2
  231. package/src/grid/grid-reveal.ts +34 -0
  232. package/src/grid/grid-source.ts +3 -25
  233. package/src/grid/grid-view.ts +31 -6
  234. package/src/grid/sources/content-source.ts +75 -7
  235. package/src/grid/tabulator-tables.d.ts +6 -0
  236. package/src/hosting/document.ts +2 -2
  237. package/src/i18n/i18n-commands.ts +2 -1
  238. package/src/media/media-commands.ts +181 -0
  239. package/src/media/media-library-model.ts +232 -0
  240. package/src/media/media-library.ts +1283 -0
  241. package/src/media/media-pane.ts +47 -19
  242. package/src/packages/pull-package-sync.ts +1 -1
  243. package/src/panels/activity-panel.ts +21 -5
  244. package/src/panels/ai-chat/chat-markdown.ts +8 -8
  245. package/src/panels/block-action-bar.ts +11 -11
  246. package/src/panels/canvas-dnd-bridge.ts +5 -2
  247. package/src/panels/chat-panel.ts +6 -6
  248. package/src/panels/collections-panel.ts +306 -0
  249. package/src/panels/component-preview.ts +35 -13
  250. package/src/panels/data-explorer.ts +1 -1
  251. package/src/panels/data-grid.ts +4 -0
  252. package/src/panels/dnd.ts +17 -30
  253. package/src/panels/elements-panel.ts +9 -2
  254. package/src/panels/empty-state.ts +2 -2
  255. package/src/panels/formula-workspace.ts +2 -3
  256. package/src/panels/frontmatter-fields.ts +9 -9
  257. package/src/panels/git-diff-open.ts +9 -5
  258. package/src/panels/head-panel.ts +369 -103
  259. package/src/panels/i18n-panel.ts +11 -10
  260. package/src/panels/imports-panel.ts +2 -0
  261. package/src/panels/jump-bar.ts +27 -2
  262. package/src/panels/layers-panel.ts +36 -30
  263. package/src/panels/left-panel.ts +22 -8
  264. package/src/panels/navigator-panels.ts +23 -11
  265. package/src/panels/pane-context.ts +111 -29
  266. package/src/panels/pane-grid.ts +6 -6
  267. package/src/panels/panel-registry.ts +14 -9
  268. package/src/panels/properties-panel.ts +7 -3
  269. package/src/panels/right-panel.ts +199 -43
  270. package/src/panels/seo-modal.ts +41 -108
  271. package/src/panels/settings-menu.ts +52 -50
  272. package/src/panels/settings-pane.ts +268 -162
  273. package/src/panels/signals-panel.ts +3 -4
  274. package/src/panels/site-model.ts +367 -0
  275. package/src/panels/site-panel.ts +638 -0
  276. package/src/panels/style-panel.ts +37 -3
  277. package/src/panels/stylebook-doc.ts +142 -10
  278. package/src/panels/stylebook-layers-panel.ts +39 -80
  279. package/src/panels/stylebook-panel.ts +47 -10
  280. package/src/panels/styles-panel.ts +327 -0
  281. package/src/panels/tab-strip.ts +109 -25
  282. package/src/platform.ts +1 -1
  283. package/src/platforms/cloud.ts +28 -1
  284. package/src/platforms/default-platform.ts +4 -4
  285. package/src/platforms/devserver.ts +51 -25
  286. package/src/publish/deploy-checklist.ts +12 -7
  287. package/src/publish/pages-service.ts +38 -14
  288. package/src/publish/publish-changes.ts +491 -0
  289. package/src/publish/publish-commands.ts +83 -18
  290. package/src/publish/publish-ids.ts +49 -0
  291. package/src/publish/publish-quick.ts +148 -0
  292. package/src/publish/publish-workspace.ts +926 -0
  293. package/src/publish/source-control.ts +907 -0
  294. package/src/services/automation.ts +16 -20
  295. package/src/services/cf-settings.ts +4 -4
  296. package/src/services/connection.ts +2 -2
  297. package/src/services/directory-picker.ts +2 -2
  298. package/src/services/references.ts +0 -0
  299. package/src/services/render-critic.ts +21 -2
  300. package/src/services/settings/definitions.ts +25 -14
  301. package/src/settings/contributed-section.ts +49 -10
  302. package/src/settings/css-vars-editor.ts +93 -27
  303. package/src/settings/developer-panels.ts +43 -0
  304. package/src/settings/extension-sections.ts +20 -2
  305. package/src/settings/general-settings.ts +91 -21
  306. package/src/settings/preferences-dialog.ts +8 -1
  307. package/src/settings/project-sections.ts +27 -10
  308. package/src/settings/schema-field-ui.ts +3 -1
  309. package/src/settings/section-registry.ts +116 -11
  310. package/src/settings/settings-dialog.ts +192 -0
  311. package/src/settings/settings-document.ts +182 -162
  312. package/src/shell.ts +467 -431
  313. package/src/site-context.ts +64 -10
  314. package/src/state.ts +28 -9
  315. package/src/studio.ts +194 -41
  316. package/src/style/project-styles.ts +34 -0
  317. package/src/style/stylebook-specimens.ts +89 -0
  318. package/src/style/stylebook-target.ts +26 -0
  319. package/src/style/tag-inheritance.ts +105 -0
  320. package/src/style/variables-focus.ts +61 -0
  321. package/src/style/variables-view.ts +235 -0
  322. package/src/surfaces/add-new.json +1482 -0
  323. package/src/surfaces/add-new.ts +325 -0
  324. package/src/surfaces/ai-chat.ts +2 -2
  325. package/src/surfaces/boot-failure.json +4 -3
  326. package/src/surfaces/bottom-dock.json +1 -1
  327. package/src/surfaces/canvas-stage.json +30 -19
  328. package/src/surfaces/canvas-stage.ts +5 -5
  329. package/src/surfaces/collections-panel.json +615 -0
  330. package/src/surfaces/collections-panel.ts +168 -0
  331. package/src/surfaces/commandbar.json +241 -96
  332. package/src/surfaces/commandbar.ts +134 -99
  333. package/src/surfaces/empty-state.json +1 -1
  334. package/src/surfaces/entry-editor.json +136 -0
  335. package/src/surfaces/entry-editor.ts +21 -0
  336. package/src/surfaces/entry-inspector.json +371 -0
  337. package/src/surfaces/entry-inspector.ts +143 -0
  338. package/src/surfaces/inspector-dock.json +162 -14
  339. package/src/surfaces/inspector-dock.ts +46 -19
  340. package/src/surfaces/library-pane.json +670 -437
  341. package/src/surfaces/library-pane.ts +94 -59
  342. package/src/surfaces/media-browser.json +62 -6
  343. package/src/surfaces/media-browser.ts +15 -1
  344. package/src/surfaces/media-field.json +1 -1
  345. package/src/surfaces/media-library.json +1586 -0
  346. package/src/surfaces/media-library.ts +282 -0
  347. package/src/surfaces/media-pick-dialog.json +71 -0
  348. package/src/surfaces/media-pick-dialog.ts +132 -0
  349. package/src/surfaces/menu.ts +18 -0
  350. package/src/surfaces/pane-context.json +144 -77
  351. package/src/surfaces/pane-context.ts +24 -0
  352. package/src/surfaces/panel-deploy-checklist.json +12 -6
  353. package/src/surfaces/panel-deploy-checklist.ts +3 -1
  354. package/src/surfaces/panel-page.json +72 -2
  355. package/src/surfaces/panel-page.ts +38 -7
  356. package/src/surfaces/panel-signals.json +1 -1
  357. package/src/surfaces/panel-signals.ts +5 -5
  358. package/src/surfaces/panel-stylebook-layers.json +71 -204
  359. package/src/surfaces/panel-stylebook-layers.ts +0 -21
  360. package/src/surfaces/preferences.json +120 -48
  361. package/src/surfaces/preferences.ts +8 -0
  362. package/src/surfaces/publish-quick.json +428 -0
  363. package/src/surfaces/publish-quick.ts +165 -0
  364. package/src/surfaces/publish-workspace.json +2892 -0
  365. package/src/surfaces/publish-workspace.ts +391 -0
  366. package/src/surfaces/rail.json +1 -159
  367. package/src/surfaces/rail.ts +18 -75
  368. package/src/surfaces/schema-form.json +10 -2
  369. package/src/surfaces/schema-form.ts +2 -0
  370. package/src/surfaces/seo.json +1 -1
  371. package/src/surfaces/settings-contributed.json +5 -5
  372. package/src/surfaces/settings-contributed.ts +18 -2
  373. package/src/surfaces/settings-css-vars.json +44 -36
  374. package/src/surfaces/settings-css-vars.ts +77 -4
  375. package/src/surfaces/settings-deploy.json +2 -15
  376. package/src/surfaces/settings-deploy.ts +1 -1
  377. package/src/surfaces/settings-dialog.json +51 -0
  378. package/src/surfaces/settings-dialog.ts +169 -0
  379. package/src/surfaces/settings-overview.json +3 -3
  380. package/src/surfaces/settings-overview.ts +9 -2
  381. package/src/surfaces/settings-pane.json +269 -46
  382. package/src/surfaces/settings-pane.ts +85 -24
  383. package/src/surfaces/shell.json +2 -1
  384. package/src/surfaces/site-panel.json +1029 -0
  385. package/src/surfaces/site-panel.ts +233 -0
  386. package/src/surfaces/slash-menu.json +63 -21
  387. package/src/surfaces/slash-menu.ts +23 -0
  388. package/src/surfaces/statusbar.ts +39 -15
  389. package/src/surfaces/styles-panel.json +735 -0
  390. package/src/surfaces/styles-panel.ts +195 -0
  391. package/src/surfaces/styles-variables.json +149 -0
  392. package/src/surfaces/styles-variables.ts +145 -0
  393. package/src/surfaces/tab-strip.json +4 -4
  394. package/src/surfaces/welcome.ts +1 -1
  395. package/src/surfaces/writing-banner.json +94 -0
  396. package/src/surfaces/writing-banner.ts +80 -0
  397. package/src/tabs/bare-repeaters.ts +101 -0
  398. package/src/tabs/tab.ts +47 -7
  399. package/src/types.ts +21 -26
  400. package/src/ui/form-controls.ts +47 -8
  401. package/src/ui/layers.ts +13 -0
  402. package/src/ui/media-picker.ts +284 -40
  403. package/src/ui/regions.ts +7 -6
  404. package/src/ui/schema-form.ts +259 -27
  405. package/src/ui/virtual-window.ts +26 -1
  406. package/src/utils/base64.ts +3 -3
  407. package/src/utils/edit-display.ts +322 -67
  408. package/src/utils/local-day.ts +18 -0
  409. package/src/workspace/pane-derive.ts +4 -4
  410. package/src/workspace/session.ts +28 -7
  411. package/src/workspace/tab-workspace.ts +137 -0
  412. package/src/workspace/workspace-back.ts +189 -0
  413. package/src/workspace/workspace-home.ts +133 -0
  414. package/src/workspace/workspace-ids.ts +129 -0
  415. package/src/workspace/workspace-switch.ts +372 -0
  416. package/src/workspace/workspace.ts +125 -11
  417. package/styles/shell-frame.css +30 -7
  418. package/styles/shell-frame.json +15 -3
  419. package/dist/chunks/2020-f2gkpy8d.js +0 -4
  420. package/dist/chunks/adapter-4912g339.js +0 -4
  421. package/dist/chunks/cf-account-picker-wccaenh6.js +0 -4
  422. package/dist/chunks/cf-settings-br6105tr.js +0 -4
  423. package/dist/chunks/color-28ksk2qm.js +0 -4
  424. package/dist/chunks/components-db68vsn7.js +0 -4
  425. package/dist/chunks/content-source-1449qesq.js +0 -4
  426. package/dist/chunks/convert-to-repeater-5h457egj.js +0 -4
  427. package/dist/chunks/editor-x9jn86a7.js +0 -4
  428. package/dist/chunks/entry-commands-may24cpt.js +0 -4
  429. package/dist/chunks/extension-sections-pvr14vxr.js +0 -4
  430. package/dist/chunks/file-ops-x9v54pcj.js +0 -4
  431. package/dist/chunks/files-j3knqf0s.js +0 -4
  432. package/dist/chunks/format-host-tyre2trb.js +0 -4
  433. package/dist/chunks/fs-events-64ma9htc.js +0 -4
  434. package/dist/chunks/git-panel-84ps5dex.js +0 -4
  435. package/dist/chunks/iframe-entry-fvbk89gm.js +0 -5
  436. package/dist/chunks/iframe-host-ck6vejt5.js +0 -4
  437. package/dist/chunks/index-b8xw3vy9.js +0 -4
  438. package/dist/chunks/jsonMode-w15cmrcm.js +0 -13
  439. package/dist/chunks/jx-validate-at3zn8wa.js +0 -4
  440. package/dist/chunks/layers-h4xp1pss.js +0 -4
  441. package/dist/chunks/layers-panel-m5hw1d7g.js +0 -4
  442. package/dist/chunks/lit-html-8t8t3dnr.js +0 -4
  443. package/dist/chunks/media-picker-2cweexc5.js +0 -4
  444. package/dist/chunks/menu-ynw13y0s.js +0 -4
  445. package/dist/chunks/monaco-buffer-qzxn567r.js +0 -4
  446. package/dist/chunks/monaco-setup-gxf6656g.js +0 -4
  447. package/dist/chunks/notify-cj5zbrs2.js +0 -4
  448. package/dist/chunks/parse-b7ddhr8m.js +0 -4
  449. package/dist/chunks/platform-3dfws9js.js +0 -4
  450. package/dist/chunks/popover-jt2gxmzs.js +0 -4
  451. package/dist/chunks/publish-panel-1gvxbj21.js +0 -4
  452. package/dist/chunks/quick-search-0dajrwqg.js +0 -4
  453. package/dist/chunks/references-49qgt404.js +0 -4
  454. package/dist/chunks/right-panel-jm1bwe53.js +0 -4
  455. package/dist/chunks/section-registry-6kwc4fky.js +0 -4
  456. package/dist/chunks/studio-0rt52vpq.js +0 -5
  457. package/dist/chunks/studio-18vgbhs5.js +0 -5
  458. package/dist/chunks/studio-24mccdrj.js +0 -5
  459. package/dist/chunks/studio-2eh507zk.js +0 -9
  460. package/dist/chunks/studio-2hcbccfv.js +0 -5
  461. package/dist/chunks/studio-2jbs3rxv.js +0 -5
  462. package/dist/chunks/studio-4h8k4dw9.js +0 -5
  463. package/dist/chunks/studio-5f6n18kx.js +0 -5
  464. package/dist/chunks/studio-5gthpky6.js +0 -5
  465. package/dist/chunks/studio-6ep3js6b.js +0 -9
  466. package/dist/chunks/studio-6wkmf3wz.js +0 -9
  467. package/dist/chunks/studio-841rf3cg.js +0 -5
  468. package/dist/chunks/studio-8gmwgheg.js +0 -5
  469. package/dist/chunks/studio-8qm0ep50.js +0 -5
  470. package/dist/chunks/studio-924p1tm1.js +0 -6
  471. package/dist/chunks/studio-a3shpz9y.js +0 -5
  472. package/dist/chunks/studio-amsbaaar.js +0 -6
  473. package/dist/chunks/studio-at5spjbc.js +0 -7
  474. package/dist/chunks/studio-b2xv945e.js +0 -5
  475. package/dist/chunks/studio-bn5nkafw.js +0 -5
  476. package/dist/chunks/studio-bw6q5xjf.js +0 -9
  477. package/dist/chunks/studio-c7q0t9x1.js +0 -5
  478. package/dist/chunks/studio-camnhc96.js +0 -6
  479. package/dist/chunks/studio-cffqvnzd.js +0 -5
  480. package/dist/chunks/studio-efvfrsf8.js +0 -5
  481. package/dist/chunks/studio-er30bpcx.js +0 -5
  482. package/dist/chunks/studio-ezscgr64.js +0 -5
  483. package/dist/chunks/studio-ffrrncvz.js +0 -5
  484. package/dist/chunks/studio-fpphgn8y.js +0 -10
  485. package/dist/chunks/studio-j1kvhdjk.js +0 -4
  486. package/dist/chunks/studio-j430n16g.js +0 -7
  487. package/dist/chunks/studio-jjkg84nj.js +0 -5
  488. package/dist/chunks/studio-m0q32ars.js +0 -4
  489. package/dist/chunks/studio-m6bdst11.js +0 -5
  490. package/dist/chunks/studio-nv2cptd4.js +0 -64
  491. package/dist/chunks/studio-nwfrc98n.js +0 -5
  492. package/dist/chunks/studio-p1wgvp7r.js +0 -5
  493. package/dist/chunks/studio-pag2wncm.js +0 -5
  494. package/dist/chunks/studio-qvanvzfd.js +0 -5
  495. package/dist/chunks/studio-qwa64kt7.js +0 -5
  496. package/dist/chunks/studio-rk1zc4x3.js +0 -928
  497. package/dist/chunks/studio-se2r3ptc.js +0 -5
  498. package/dist/chunks/studio-sg2a0dh0.js +0 -5
  499. package/dist/chunks/studio-tjzjhe68.js +0 -5
  500. package/dist/chunks/studio-v2w7q8r1.js +0 -7
  501. package/dist/chunks/studio-v5vjq1h0.js +0 -4
  502. package/dist/chunks/studio-v8aga3vp.js +0 -14
  503. package/dist/chunks/studio-vfcdrfmv.js +0 -5
  504. package/dist/chunks/studio-vmfkgdjf.js +0 -5
  505. package/dist/chunks/studio-wt1816jj.js +0 -5
  506. package/dist/chunks/studio-wz73q5kq.js +0 -4
  507. package/dist/chunks/studio-y04qtxvp.js +0 -7
  508. package/dist/chunks/studio-y21g8zzg.js +0 -5
  509. package/dist/chunks/studio-y64hrt8p.js +0 -12
  510. package/dist/chunks/studio-ynn90f58.js +0 -16
  511. package/dist/chunks/studio-yxz682sx.js +0 -5
  512. package/dist/chunks/studio-zp7snvkq.js +0 -5
  513. package/dist/chunks/studio-zq4z6tm6.js +0 -5
  514. package/dist/chunks/toast-c9fn075p.js +0 -4
  515. package/dist/chunks/tsMode-sf49ee9x.js +0 -14
  516. package/dist/chunks/workspace-jtfahpee.js +0 -4
  517. package/src/panels/frontmatter-panel.ts +0 -711
  518. package/src/panels/git-panel.ts +0 -1108
  519. package/src/surfaces/doc-header.json +0 -776
  520. package/src/surfaces/doc-header.ts +0 -222
  521. package/src/surfaces/git-panel.json +0 -1880
  522. package/src/surfaces/git-panel.ts +0 -264
@@ -3,10 +3,19 @@
3
3
  * Edit-mode display transforms — extracted from studio.js (Phase 4i). Pure stateless functions that
4
4
  * convert document trees for visual editing (template expressions, $map, $switch, empty
5
5
  * placeholders).
6
+ *
7
+ * @docs studio/interface/modes
6
8
  */
7
9
 
8
10
  import type { JxDocumentValue, JxMutableNode } from "@jxsuite/schema/types";
9
- import { displayTagName, isNodeObject, isNonNullObject, isString } from "@jxsuite/schema/guards";
11
+ import {
12
+ displayTagName,
13
+ isNodeObject,
14
+ isNonNullObject,
15
+ isNumber,
16
+ isRef,
17
+ isString,
18
+ } from "@jxsuite/schema/guards";
10
19
  import { isDocumentContainer } from "./strip-events";
11
20
  import type { RebuiltNode } from "./strip-events";
12
21
 
@@ -57,6 +66,37 @@ function isImageUrlKey(key: string): boolean {
57
66
  return IMAGE_KEY_RE.test(key);
58
67
  }
59
68
 
69
+ /** A template that reads the repeater row (`item`, `index`, `$map`), which edit mode cannot supply. */
70
+ const ROW_BINDING_RE = /(?<![\w$.])(?:item|index|\$map)\b/u;
71
+
72
+ /** Whether a value is a template or `$ref` that reads the repeater row. */
73
+ export function isRowBound(value: JxDocumentValue | undefined): boolean {
74
+ if (isString(value)) {
75
+ return value.includes("${") && ROW_BINDING_RE.test(value);
76
+ }
77
+ return isNodeObject(value) && isString(value.$ref) && /^(?:#\/)?\$map\//u.test(value.$ref);
78
+ }
79
+
80
+ /**
81
+ * What a row-bound value shows in the perimeter: link targets are inert, image-like keys the
82
+ * placeholder image, anything else its readable binding text.
83
+ *
84
+ * @param {string} key
85
+ * @param {JxDocumentValue | undefined} value
86
+ */
87
+ function rowBoundDisplay(key: string, value: JxDocumentValue | undefined): string {
88
+ if (key === "href" || key === "action") {
89
+ return "";
90
+ }
91
+ if (isImageUrlKey(key)) {
92
+ return PLACEHOLDER_IMG;
93
+ }
94
+ if (isString(value)) {
95
+ return templateToEditDisplay(value);
96
+ }
97
+ return isNodeObject(value) && isString(value.$ref) ? refDisplayLabel(value.$ref) : "";
98
+ }
99
+
60
100
  const textTags = new Set([
61
101
  "p",
62
102
  "h1",
@@ -118,6 +158,31 @@ export const EMPTY_PLACEHOLDER_CLASSES = [
118
158
  "empty-container-placeholder",
119
159
  ] as const;
120
160
 
161
+ /** The style keys that give an element a size of its own: its box, not its content, sets it. */
162
+ const SIZING_KEYS = ["width", "height", "inlineSize", "blockSize"] as const;
163
+
164
+ /**
165
+ * Whether the node's own style fixes its width or height — a dot, a bar, a spacer.
166
+ *
167
+ * Such an element is drawn as a SHAPE, and an empty one is not missing anything: the jxsuite.com
168
+ * home's 6px status dot is an empty `<span>` sized in its style, and the text placeholder turned it
169
+ * into a column of letters one glyph wide over the hero. A placeholder that changes the box the
170
+ * author sized (the container one sets a `min-height` and a border) misdraws the page it is meant
171
+ * to explain, so a sized element gets none. Only the top-level declaration counts: a size set under
172
+ * a media query or a state is a variant of a box whose base the content still sizes.
173
+ */
174
+ function fixesItsOwnSize(node: JxMutableNode): boolean {
175
+ const { style } = node;
176
+ if (!isNonNullObject(style)) {
177
+ return false;
178
+ }
179
+ return SIZING_KEYS.some((key) => {
180
+ const value = style[key];
181
+ const text = isNumber(value) ? String(value) : isString(value) ? value.trim() : "";
182
+ return text !== "" && text !== "auto";
183
+ });
184
+ }
185
+
121
186
  /**
122
187
  * The empty-placeholder class prepareForEditMode would add for this node, or null. Used by the
123
188
  * canvas patcher to keep placeholder classes in sync when patching text without a full render.
@@ -136,6 +201,9 @@ export function computeEmptyPlaceholderClass(node: JxMutableNode): string | null
136
201
  if (Array.isArray(node.children) && node.children.length > 0) {
137
202
  return null;
138
203
  }
204
+ if (fixesItsOwnSize(node)) {
205
+ return null;
206
+ }
139
207
  if (textTags.has(displayTagName(node.tagName))) {
140
208
  return "empty-text-placeholder";
141
209
  }
@@ -181,15 +249,184 @@ export function restoreTemplateExpressions(el: HTMLElement) {
181
249
  * @param {JxMutableNode} arrayObj
182
250
  * @returns {JxMutableNode}
183
251
  */
184
- function arrayToPerimeter(arrayObj: JxMutableNode): JxMutableNode {
252
+ function arrayToPerimeter(arrayObj: JxMutableNode, row: SampleRow): JxMutableNode {
185
253
  const template = arrayObj.map;
186
254
  return {
187
- children: isNonNullObject(template) ? [prepareForEditMode(template)] : [],
255
+ children: isNonNullObject(template)
256
+ ? [prepareNode(template, sampleRowOf(arrayObj.items, row))]
257
+ : [],
188
258
  className: "repeater-perimeter",
189
259
  tagName: "div",
190
260
  };
191
261
  }
192
262
 
263
+ // ─── Live component props ────────────────────────────────────────────────────
264
+
265
+ /**
266
+ * The row a repeater's template is drawn with in edit mode, as a JavaScript expression the runtime
267
+ * evaluates in the template's own scope, or null outside a repeater or where the repeater's items
268
+ * cannot be read.
269
+ *
270
+ * It is the repeater's FIRST item. A perimeter draws one instance of the template, and a component
271
+ * inside it used to be handed each bound prop's display text (`❪ item.data.date ❫`) as its value.
272
+ * Text survives that; a component that formats the prop does not: `Intl/formatDate` and a locale
273
+ * tag reject it with a `RangeError` (spec.md §16.4), and the instance was not rendered at all. The
274
+ * first item is real data of exactly the shape the template was written for, and it is the row
275
+ * Preview draws first.
276
+ */
277
+ export type SampleRow = string | null;
278
+
279
+ /** A JSON value as a JavaScript literal: JSON is a subset of ECMAScript since ES2019. */
280
+ function jsLiteral(value: JxDocumentValue): string {
281
+ return JSON.stringify(value);
282
+ }
283
+
284
+ /**
285
+ * A pointer the runtime resolves, as a JavaScript accessor over the template parameters, or null
286
+ * for a scheme a template cannot reach (`window#/`, `$defs`, an external file).
287
+ */
288
+ function refAccessor(ref: string): string | null {
289
+ const pointer = ref.startsWith("#/") ? ref.slice(2) : ref;
290
+ const [head, ...rest] = pointer.split("/");
291
+ const base = head === "state" || head === "$map" ? head : null;
292
+ if (base === null) {
293
+ return null;
294
+ }
295
+ // JSON Pointer unescaping (RFC 6901): `~1` is `/`, `~0` is `~`, in that order.
296
+ const keys = rest.map((seg) => seg.replaceAll("~1", "/").replaceAll("~0", "~"));
297
+ return base + keys.map((key) => `?.[${jsLiteral(key)}]`).join("");
298
+ }
299
+
300
+ /** Evaluate `expr` where `item`, `index` and `$map` are the sample `row`, or as-is with no row. */
301
+ function inRow(expr: string, row: SampleRow): string {
302
+ if (row === null) {
303
+ return `(${expr})`;
304
+ }
305
+ return `((r) => ((item, index, $map) => (${expr}))(r, 0, { index: 0, item: r }))(${row})`;
306
+ }
307
+
308
+ /** The first item of a repeater's `items`, read inside the enclosing sample `row` (if any). */
309
+ function sampleRowOf(items: JxDocumentValue | undefined, row: SampleRow): SampleRow {
310
+ if (Array.isArray(items)) {
311
+ return `${jsLiteral(items)}[0]`;
312
+ }
313
+ if (isRef(items)) {
314
+ const accessor = refAccessor(items.$ref);
315
+ return accessor === null ? null : `${inRow(accessor, row)}?.[0]`;
316
+ }
317
+ const parts = isString(items) ? templateExpressions(items) : [];
318
+ if (isString(items) && parts.length === 1 && isWholeTemplate(items, parts[0]!)) {
319
+ return `${inRow(parts[0]!.expr, row)}?.[0]`;
320
+ }
321
+ return null;
322
+ }
323
+
324
+ /** One `${…}` of a template: where it starts and ends in the string, and its expression. */
325
+ interface TemplatePart {
326
+ start: number;
327
+ end: number;
328
+ expr: string;
329
+ }
330
+
331
+ /** Whether `part` is the whole of `template`: one expression end to end, so it keeps its type. */
332
+ function isWholeTemplate(template: string, part: TemplatePart): boolean {
333
+ return part.start === 0 && part.end === template.length - 1;
334
+ }
335
+
336
+ /**
337
+ * Each `${…}` of a template, located by brace depth rather than by pattern: an expression may hold
338
+ * an object literal or a nested template, which a `[^}]+` pattern cuts short.
339
+ */
340
+ function templateExpressions(str: string): TemplatePart[] {
341
+ const found: TemplatePart[] = [];
342
+ let at = str.indexOf("${");
343
+ while (at !== -1) {
344
+ let depth = 0;
345
+ let end = -1;
346
+ for (let i = at + 1; i < str.length; i += 1) {
347
+ if (str[i] === "{") {
348
+ depth += 1;
349
+ } else if (str[i] === "}") {
350
+ depth -= 1;
351
+ if (depth === 0) {
352
+ end = i;
353
+ break;
354
+ }
355
+ }
356
+ }
357
+ if (end === -1) {
358
+ break;
359
+ }
360
+ found.push({ end, expr: str.slice(at + 2, end), start: at });
361
+ at = str.indexOf("${", end + 1);
362
+ }
363
+ return found;
364
+ }
365
+
366
+ /**
367
+ * A bound component prop as edit mode passes it: the binding itself, evaluated LIVE by the runtime
368
+ * (against the page's own state and `$page`, and inside a repeater against its sample row) and
369
+ * guarded, so a binding with nothing to read yet (a collection still loading, an empty one) reads
370
+ * as the empty string, a component prop's usual default, rather than throwing out of the binding's
371
+ * effect.
372
+ *
373
+ * Display text is what edit mode shows for a binding the PAGE draws, because the author edits that
374
+ * text in place. A component's prop is not drawn by the page: the component decides what it means,
375
+ * and a date, a locale, a count or a colour cannot be a label.
376
+ */
377
+ export function liveEditProp(template: string, row: SampleRow = null): string {
378
+ const parts = templateExpressions(template);
379
+ if (parts.length === 0) {
380
+ return template;
381
+ }
382
+ let out = "";
383
+ let last = 0;
384
+ for (const { start, end, expr } of parts) {
385
+ out += template.slice(last, start);
386
+ out += `\${(() => { try { return ${inRow(expr, row)}; } catch { return ""; } })()}`;
387
+ last = end + 1;
388
+ }
389
+ return out + template.slice(last);
390
+ }
391
+
392
+ /**
393
+ * A `$ref`-bound component prop in edit mode: a scope pointer resolves live as it is, and a row
394
+ * pointer (`$map/item/…`) is read from the sample row, or dropped where there is none so the
395
+ * component keeps its own default.
396
+ */
397
+ function liveEditRefProp(ref: string, row: SampleRow): JxDocumentValue | undefined {
398
+ const pointer = ref.startsWith("#/") ? ref.slice(2) : ref;
399
+ if (pointer !== "$map" && !pointer.startsWith("$map/")) {
400
+ return { $ref: ref };
401
+ }
402
+ const accessor = refAccessor(ref);
403
+ return row === null || accessor === null ? undefined : liveEditProp(`\${${accessor}}`, row);
404
+ }
405
+
406
+ /**
407
+ * The sample row of the repeater template at `path` in `doc` (what {@link prepareForEditMode} draws
408
+ * a perimeter's instance with), so a subtree re-rendered on its own (`iframe-subtree.ts`) is
409
+ * prepared exactly as the full render prepared it.
410
+ */
411
+ export function sampleRowAt(doc: JxDocumentValue, path: readonly (string | number)[]): SampleRow {
412
+ let node: JxDocumentValue | undefined = doc;
413
+ let row: SampleRow = null;
414
+ for (const seg of path) {
415
+ if (Array.isArray(node)) {
416
+ node = isNumber(seg) ? node[seg] : undefined;
417
+ continue;
418
+ }
419
+ if (!isNodeObject(node)) {
420
+ return row;
421
+ }
422
+ if (seg === "map" && node.$prototype === "Array") {
423
+ row = sampleRowOf(node.items, row);
424
+ }
425
+ node = isString(seg) ? node[seg] : undefined;
426
+ }
427
+ return row;
428
+ }
429
+
193
430
  /**
194
431
  * The text a `$ref` binding shows in edit mode: its pointer, with the `#/state/` prefix dropped.
195
432
  *
@@ -210,13 +447,33 @@ function refDisplayLabel(ref: string): string {
210
447
  * @param {JxMutableNode} node
211
448
  * @returns {JxMutableNode}
212
449
  */
213
- export function prepareForEditMode(node: JxMutableNode): JxMutableNode;
214
- export function prepareForEditMode(node: JxDocumentValue): JxDocumentValue;
215
- export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumentValue | undefined;
216
- export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumentValue | undefined {
450
+ export function prepareForEditMode(node: JxMutableNode, row?: SampleRow): JxMutableNode;
451
+ export function prepareForEditMode(node: JxDocumentValue, row?: SampleRow): JxDocumentValue;
452
+ export function prepareForEditMode(
453
+ node: JxDocumentValue | undefined,
454
+ row?: SampleRow,
455
+ ): JxDocumentValue | undefined;
456
+ export function prepareForEditMode(
457
+ node: JxDocumentValue | undefined,
458
+ row: SampleRow = null,
459
+ ): JxDocumentValue | undefined {
460
+ return prepareNode(node, row);
461
+ }
462
+
463
+ /** {@link prepareForEditMode} for one value, inside the sample `row` of its enclosing repeater. */
464
+ function prepareNode(node: JxMutableNode, row: SampleRow): JxMutableNode;
465
+ function prepareNode(node: JxDocumentValue, row: SampleRow): JxDocumentValue;
466
+ function prepareNode(
467
+ node: JxDocumentValue | undefined,
468
+ row: SampleRow,
469
+ ): JxDocumentValue | undefined;
470
+ function prepareNode(
471
+ node: JxDocumentValue | undefined,
472
+ row: SampleRow,
473
+ ): JxDocumentValue | undefined {
217
474
  if (Array.isArray(node)) {
218
475
  // Arrays of nodes round-trip element-wise; the array itself is not a node.
219
- return node.map((n) => prepareForEditMode(n));
476
+ return node.map((n) => prepareNode(n, row));
220
477
  }
221
478
  if (!isNodeObject(node)) {
222
479
  return node;
@@ -225,7 +482,7 @@ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumen
225
482
  // A mapped-array node itself → its edit-mode perimeter (e.g. when the patcher re-renders an
226
483
  // Array node directly). Members inside a children array funnel here via the children branch.
227
484
  if (node.$prototype === "Array") {
228
- return arrayToPerimeter(node);
485
+ return arrayToPerimeter(node, row);
229
486
  }
230
487
 
231
488
  const obj = node;
@@ -252,21 +509,27 @@ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumen
252
509
  if (k === "state" || k === "$media" || k === "$elements") {
253
510
  out[k] = v; // Preserve as-is for runtime resolution
254
511
  } else if (k === "$props" && isDocumentContainer(v)) {
255
- // Process $props values: convert template strings to display format
512
+ /* A component's inputs render LIVE. They are its configuration, not text the author edits in
513
+ place (a template-valued prop is refused by inline prop editing), and showing their binding
514
+ text instead fed it to the component as a value: a header's `variant` became the literal
515
+ `❪ state.headerVariant ❫` and matched none of its styles. A prop that reads the repeater
516
+ row is drawn with the repeater's sample row (see liveEditProp), guarded, so a date or a
517
+ locale the component formats is real data rather than a label; a row-bound link target is
518
+ inert and a row-bound image-like key the placeholder image. */
256
519
  const propsOut: RebuiltNode = {};
257
520
  for (const [pk, pv] of Object.entries(v)) {
258
- if (isString(pv) && pv.includes("${")) {
259
- // Image-like prop → neutral placeholder (the component renders it as an <img src>); link
260
- // Targets → inert ""; everything else → the readable ❪ expr ❫ binding text.
261
- if (isImageUrlKey(pk)) {
262
- propsOut[pk] = PLACEHOLDER_IMG;
263
- } else if (pk === "href" || pk === "action") {
264
- propsOut[pk] = "";
265
- } else {
266
- propsOut[pk] = templateToEditDisplay(pv);
521
+ if (!isRowBound(pv)) {
522
+ propsOut[pk] = pv;
523
+ } else if (isString(pv)) {
524
+ propsOut[pk] =
525
+ isImageUrlKey(pk) || pk === "href" || pk === "action"
526
+ ? rowBoundDisplay(pk, pv)
527
+ : liveEditProp(pv, row);
528
+ } else if (isRef(pv)) {
529
+ const live = liveEditRefProp(pv.$ref, row);
530
+ if (live !== undefined) {
531
+ propsOut[pk] = live;
267
532
  }
268
- } else if (isNodeObject(pv) && pv.$ref) {
269
- propsOut[pk] = refDisplayLabel(pv.$ref);
270
533
  } else {
271
534
  propsOut[pk] = pv;
272
535
  }
@@ -276,12 +539,12 @@ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumen
276
539
  if (Array.isArray(v)) {
277
540
  // Each member recurses; array pseudo-elements become a single repeater-perimeter element
278
541
  // (via the top-level array case) so they stay a 1:1 DOM node among their siblings.
279
- out.children = v.map((c) => prepareForEditMode(c));
542
+ out.children = v.map((c) => prepareNode(c, row));
280
543
  } else if (isNodeObject(v) && v.$prototype === "Array") {
281
544
  // Legacy whole-children repeater → a single perimeter as the sole child.
282
- out.children = [prepareForEditMode(v)];
545
+ out.children = [prepareNode(v, row)];
283
546
  } else {
284
- out.children = prepareForEditMode(v);
547
+ out.children = prepareNode(v, row);
285
548
  }
286
549
  } else if (k === "cases" && obj.$switch && isDocumentContainer(v)) {
287
550
  // Replace $switch cases with a placeholder showing the first case or a label
@@ -290,7 +553,7 @@ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumen
290
553
  const [, firstCase] = firstEntry;
291
554
  out.children =
292
555
  isDocumentContainer(firstCase) && !(isNodeObject(firstCase) && firstCase.$ref)
293
- ? [prepareForEditMode(firstCase)]
556
+ ? [prepareNode(firstCase, row)]
294
557
  : [
295
558
  {
296
559
  style: {
@@ -309,35 +572,32 @@ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumen
309
572
  ];
310
573
  }
311
574
  } else if (k === "attributes" && isMediaElement && isDocumentContainer(v)) {
312
- // Process attributes for media elements: replace src/poster with transparent pixel
575
+ // A media element's src/poster: live, unless it reads the repeater row or names nothing.
313
576
  const processed: RebuiltNode = {};
314
577
  for (const [ak, av] of Object.entries(v)) {
315
- if (ak === "src" || ak === "poster") {
316
- if (isString(av) && av !== "" && !av.includes("${")) {
317
- processed[ak] = av;
318
- } else {
319
- needsMediaPlaceholder = true;
320
- processed[ak] = TRANSPARENT_PX;
321
- }
322
- } else if (isString(av) && av.includes("${")) {
323
- const isUrlAttr = ak === "href" || ak === "action";
324
- processed[ak] = isUrlAttr ? "" : templateToEditDisplay(av);
325
- } else if (isNodeObject(av) && av.$ref) {
326
- processed[ak] = refDisplayLabel(av.$ref);
578
+ if ((ak === "src" || ak === "poster") && (av === "" || av == null || isRowBound(av))) {
579
+ needsMediaPlaceholder = true;
580
+ processed[ak] = TRANSPARENT_PX;
327
581
  } else {
328
- processed[ak] = av;
582
+ processed[ak] = isRowBound(av) ? rowBoundDisplay(ak, av) : av;
329
583
  }
330
584
  }
331
585
  out.attributes = processed;
586
+ } else if (k === "attributes" && isDocumentContainer(v)) {
587
+ const processed: RebuiltNode = {};
588
+ for (const [ak, av] of Object.entries(v)) {
589
+ processed[ak] = isRowBound(av) ? rowBoundDisplay(ak, av) : av;
590
+ }
591
+ out.attributes = processed;
332
592
  } else if (k === "style") {
333
- /* Drop template-valued style declarations. State is not live in edit mode, so the value
334
- cannot be resolved; omitting the property lets the cascade answer, which is exactly what
335
- the `""` this used to write meant while declarations went inline. As a RULE, `color: ` is
336
- a malformed declaration instead of a no-op. Mirrored by iframe-patch's editModeStyle. */
593
+ /* Live, like every binding but text: state IS live on the canvas. Only a declaration that
594
+ reads the repeater row is omitted — the perimeter has no row — and omitted rather than
595
+ blanked, because `color: ` is a malformed declaration in a rule where the cascade answers
596
+ an absent one. Mirrored by iframe-patch's editModeStyle. */
337
597
  if (isDocumentContainer(v)) {
338
598
  const s: RebuiltNode = {};
339
599
  for (const [sk, sv] of Object.entries(v)) {
340
- if (!isString(sv) || !sv.includes("${")) {
600
+ if (!isRowBound(sv)) {
341
601
  s[sk] = sv;
342
602
  }
343
603
  }
@@ -345,34 +605,29 @@ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumen
345
605
  } else {
346
606
  out.style = v;
347
607
  }
348
- } else if (isString(v) && v.includes("${")) {
349
- // Template string in a display property → show raw expression
350
- const isMediaSrc = (k === "src" || k === "poster") && isMediaElement;
351
- if (isMediaSrc) {
608
+ } else if (k === "$switch" && isString(v) && v.includes("${")) {
609
+ /* Not a render input any more: `cases` is replaced by the first case above, so a live
610
+ expression would hand the runtime a switch with nothing to choose from. */
611
+ out[k] = templateToEditDisplay(v);
612
+ } else if (k === "textContent" && isString(v) && v.includes("${")) {
613
+ // The author's text: shown as its binding, so editing it in place edits the template.
614
+ out[k] = templateToEditDisplay(v);
615
+ } else if (k === "textContent" && isNodeObject(v) && v.$ref) {
616
+ out[k] = refDisplayLabel(v.$ref);
617
+ } else if (isMediaElement && (k === "src" || k === "poster")) {
618
+ if (v === "" || v == null || isRowBound(v)) {
352
619
  needsMediaPlaceholder = true;
353
620
  out[k] = TRANSPARENT_PX;
354
621
  } else {
355
- const isUrlAttr = k === "src" || k === "href" || k === "poster" || k === "action";
356
- out[k] = isUrlAttr ? "" : templateToEditDisplay(v);
357
- }
358
- } else if (isNodeObject(v) && v.$ref) {
359
- // $ref binding → show ref path as literal text
360
- const label = refDisplayLabel(v.$ref);
361
- const isMediaSrc = (k === "src" || k === "poster") && isMediaElement;
362
- if (isMediaSrc) {
363
- needsMediaPlaceholder = true;
364
- out[k] = TRANSPARENT_PX;
365
- } else {
366
- out[k] = label;
622
+ out[k] = v;
367
623
  }
624
+ } else if (isRowBound(v)) {
625
+ out[k] = rowBoundDisplay(k, v);
626
+ } else if (isString(v) || (isNodeObject(v) && v.$ref)) {
627
+ // Any other bound or literal property renders as the page will.
628
+ out[k] = v;
368
629
  } else {
369
- // Empty src/poster on media elements → use transparent pixel placeholder
370
- if ((k === "src" || k === "poster") && v === "" && isMediaElement) {
371
- needsMediaPlaceholder = true;
372
- out[k] = TRANSPARENT_PX;
373
- } else {
374
- out[k] = prepareForEditMode(v);
375
- }
630
+ out[k] = prepareNode(v, row);
376
631
  }
377
632
  }
378
633
 
@@ -0,0 +1,18 @@
1
+ /**
2
+ * An instant as the calendar day it is where Studio runs.
3
+ *
4
+ * A file's modification time, an upload's, "today": each is an instant, and the day a reader means
5
+ * by it is their own. `toISOString().slice(0, 10)` answers in UTC, so a file saved at 9 pm in
6
+ * Denver was listed as edited tomorrow. A date the author WROTE (`2026-06-03`) is not an instant
7
+ * and never goes through here: it is taken as written (`content/entry-summaries.ts` `entryDay`).
8
+ */
9
+
10
+ /** `date` as `YYYY-MM-DD` in the local time zone, or "" for an invalid date. */
11
+ export function localDay(date: Date): string {
12
+ if (Number.isNaN(date.getTime())) {
13
+ return "";
14
+ }
15
+ const month = String(date.getMonth() + 1).padStart(2, "0");
16
+ const day = String(date.getDate()).padStart(2, "0");
17
+ return `${String(date.getFullYear()).padStart(4, "0")}-${month}-${day}`;
18
+ }
@@ -79,10 +79,10 @@ export type DerivePreset = (typeof DERIVE_PRESETS)[number];
79
79
  * The canvas mode a preset needs the SOURCE document to DECLARE, for the presets that need one.
80
80
  *
81
81
  * `tab.capabilities.modes` is the format's own answer to "what can this document be shown as", and
82
- * the preset menu consulted it nowhere: over a document that declares `["settings", "stylebook",
83
- * "source"]` it still offered "Same page at Base" — a lens whose stage can only draw an empty
84
- * artboard — and "Component definition", whose rule can never resolve because nothing in a settings
85
- * form is a component instance. Both produced a pane the author then had to close.
82
+ * the preset menu consulted it nowhere: over a document that declares `["stylebook", "source"]` it
83
+ * still offered "Same page at Base" — a lens whose stage can only draw an empty artboard — and
84
+ * "Component definition", whose rule can never resolve because nothing in a configuration object is
85
+ * a component instance. Both produced a pane the author then had to close.
86
86
  *
87
87
  * Two presets are absent, for two different reasons. `diff` needs no mode: a comparison is two
88
88
  * texts and every document has those, and it is already gated on the file having changes. `layout`
@@ -25,7 +25,7 @@
25
25
  * across versions. Every field is validated on the way in and a bad record restores nothing.
26
26
  */
27
27
 
28
- import { PRIMARY_PANE, SECONDARY_PANE, focusPane, workspace } from "./workspace";
28
+ import { PRIMARY_PANE, SECONDARY_PANE, activateTab, focusPane, workspace } from "./workspace";
29
29
  import {
30
30
  isBoolean,
31
31
  isJsonObject,
@@ -307,8 +307,22 @@ export async function restoreSession(
307
307
  ui.showLayout = stored.showLayout;
308
308
  }
309
309
  }
310
- // Each pane's active tab, then the pane that had the keyboard — in that order, because activating
311
- // A tab in a pane is what makes that pane's strip meaningful and focusing comes last.
310
+ /*
311
+ * Each pane's active tab, then the pane that had the keyboard, in that order, because activating
312
+ * a tab in a pane is what makes that pane's strip meaningful and focusing comes last.
313
+ *
314
+ * The focused pane's tab is ACTIVATED, not merely assigned, because the restored tab is the most
315
+ * recently used one and the workspace switch that follows a restore asks exactly that question.
316
+ * Every document was reopened without moving the keyboard, so the MRU list was empty, and
317
+ * `focusPane` adds nothing for the pane that already has the keyboard. Entering the stored
318
+ * workspace then fell back to that workspace's FIRST tab in the strip: two exhibition entries
319
+ * restored with the English one active came back on whichever was opened first, and a Design
320
+ * session left on `/exhibitions` came back on `/`.
321
+ */
322
+ const focused = workspace.panes.some((pane) => pane.id === session.focusedPane)
323
+ ? session.focusedPane
324
+ : null;
325
+ let focusedTab: string | null = null;
312
326
  for (const stored of session.panes) {
313
327
  const pane = workspace.panes.find((p) => p.id === stored.id);
314
328
  if (!pane || !stored.activeFile) {
@@ -317,12 +331,19 @@ export async function restoreSession(
317
331
  const match = pane.tabOrder.find(
318
332
  (tabId) => workspace.tabs.get(tabId)?.documentPath === stored.activeFile,
319
333
  );
320
- if (match) {
321
- pane.activeTabId = match;
334
+ if (!match) {
335
+ continue;
336
+ }
337
+ if (pane.id === focused) {
338
+ focusedTab = match;
339
+ } else {
340
+ activateTab(match, { focus: false });
322
341
  }
323
342
  }
324
- if (workspace.panes.some((pane) => pane.id === session.focusedPane)) {
325
- focusPane(session.focusedPane);
343
+ if (focusedTab) {
344
+ activateTab(focusedTab);
345
+ } else if (focused) {
346
+ focusPane(focused);
326
347
  }
327
348
  return opened;
328
349
  }