@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
@@ -46,6 +46,93 @@ const VOID_TAGS = new Set([
46
46
  "wbr",
47
47
  ]);
48
48
 
49
+ /**
50
+ * Phrasing content (HTML's content model) that is NOT also an embedded or interactive block an
51
+ * author drops as its own block: text-level elements. Only these may land inside a paragraph. An
52
+ * image, a video and a component are phrasing by the letter of HTML, but a card dragged from the
53
+ * Site panel onto a paragraph means "put it here", not "glue it into this sentence": in Markdown
54
+ * the latter serialises as an inline directive or image stuck to the text.
55
+ */
56
+ const TEXT_LEVEL_TAGS = new Set([
57
+ "a",
58
+ "abbr",
59
+ "b",
60
+ "bdi",
61
+ "bdo",
62
+ "br",
63
+ "cite",
64
+ "code",
65
+ "data",
66
+ "del",
67
+ "dfn",
68
+ "em",
69
+ "i",
70
+ "ins",
71
+ "kbd",
72
+ "mark",
73
+ "q",
74
+ "s",
75
+ "samp",
76
+ "small",
77
+ "span",
78
+ "strong",
79
+ "sub",
80
+ "sup",
81
+ "time",
82
+ "u",
83
+ "var",
84
+ "wbr",
85
+ ]);
86
+
87
+ /** Elements whose content model is phrasing only: a block dropped inside one breaks it. */
88
+ const PHRASING_HOSTS = new Set([
89
+ ...TEXT_LEVEL_TAGS,
90
+ "button",
91
+ "dt",
92
+ "h1",
93
+ "h2",
94
+ "h3",
95
+ "h4",
96
+ "h5",
97
+ "h6",
98
+ "label",
99
+ "legend",
100
+ "p",
101
+ "pre",
102
+ "summary",
103
+ ]);
104
+
105
+ /** A node's tag, lower-cased, `div` when it names none. */
106
+ function tagOf(node: JxMutableNode | null | undefined): string {
107
+ return (displayTagName(node?.tagName) || "div").toLowerCase();
108
+ }
109
+
110
+ /** Whether the dragged thing is text-level content, which may be placed inside a paragraph. */
111
+ function isTextLevelSource(src: DragSrcKind, shadowDoc: JxMutableNode): boolean {
112
+ const tag = src.type === "block" ? (src.tag ?? "") : tagOf(getNodeAtPath(shadowDoc, src.path));
113
+ return TEXT_LEVEL_TAGS.has(tag.toLowerCase());
114
+ }
115
+
116
+ /**
117
+ * The outermost phrasing host at or above `el` (the paragraph around a `<strong>`), or null when
118
+ * `el` is in none. A block is placed beside that host, never inside it.
119
+ */
120
+ function phrasingHostOf(el: HTMLElement, shadowDoc: JxMutableNode): HTMLElement | null {
121
+ let host: HTMLElement | null = null;
122
+ let current: HTMLElement | null = el;
123
+ while (current) {
124
+ const path = parseJxPath(current.dataset.jxPath ?? "");
125
+ if (path.length === 0) {
126
+ break;
127
+ }
128
+ if (PHRASING_HOSTS.has(tagOf(getNodeAtPath(shadowDoc, path)))) {
129
+ host = current;
130
+ }
131
+ current = nearestPathEl(current.parentElement);
132
+ }
133
+ return host;
134
+ }
135
+
49
136
  /**
50
137
  * Walk up from an element to the nearest ancestor carrying a `data-jx-path`. Refactored out of the
51
138
  * `nearestHit` walk (iframe-interaction.ts) to take an element directly so the hit-test adapter can
@@ -122,6 +209,26 @@ export function computeDropInstruction(
122
209
  return null;
123
210
  }
124
211
 
212
+ /* A block over a paragraph (or anything inside one) lands above or below the paragraph: a
213
+ component or an image nested in a `<p>` is serialised glued to its text. */
214
+ const host = isTextLevelSource(src, shadowDoc) ? null : phrasingHostOf(targetEl, shadowDoc);
215
+ if (host) {
216
+ const hostPath = parseJxPath(host.dataset.jxPath ?? "");
217
+ if (!canDrop(src, hostPath)) {
218
+ return null;
219
+ }
220
+ const hostRect = rectFor(host);
221
+ const hostRelY = hostRect.height === 0 ? 0 : (cursorY - hostRect.y) / hostRect.height;
222
+ return hostRelY < 0.5
223
+ ? { edge: "top", instruction: "reorder-above", referenceRect: hostRect, targetPath: hostPath }
224
+ : {
225
+ edge: "bottom",
226
+ instruction: "reorder-below",
227
+ referenceRect: hostRect,
228
+ targetPath: hostPath,
229
+ };
230
+ }
231
+
125
232
  const node = getNodeAtPath(shadowDoc, targetPath);
126
233
  const tag = (displayTagName(node?.tagName) || "div").toLowerCase();
127
234
  const hasElementChildren =
@@ -12,6 +12,8 @@ import {
12
12
  applyCanvasDialogOpen,
13
13
  applyCanvasPopoverOpen,
14
14
  applyPreviewColorScheme,
15
+ applyPreviewLocale,
16
+ captureLocaleBaseline,
15
17
  applySiteStyle,
16
18
  installCanvasImageRetry,
17
19
  renderResolvedDocument,
@@ -54,9 +56,11 @@ import {
54
56
  redefineElement,
55
57
  resetDocumentStyles,
56
58
  setCanvasAssetResolver,
59
+ setNpmModuleResolver,
57
60
  setResolveToken,
58
61
  } from "@jxsuite/runtime";
59
62
  import { resolveAssetRef } from "./asset-resolve";
63
+ import { npmModuleUrl } from "./npm-modules";
60
64
  import type { AssetContext } from "./asset-resolve";
61
65
  import { KIT_LOADERS } from "@jxsuite/ui/loaders";
62
66
  import type { CanvasEnvelope, IframeChannel } from "./iframe-channel";
@@ -92,6 +96,14 @@ for (const [specifier, load] of Object.entries(KIT_LOADERS)) {
92
96
  preloadModule(specifier, load);
93
97
  }
94
98
 
99
+ /*
100
+ * A document's `npm:` sidecar, from the package the project installed. The build bundles one
101
+ * (`compiler/src/site/bundler.ts`); this frame asks the dev server, which bundles
102
+ * `/node_modules/<package>/<subpath>` on demand through the package's own `exports`, the URL space
103
+ * `registerElements` already imports a bare `$elements` specifier from.
104
+ */
105
+ setNpmModuleResolver((specifier) => npmModuleUrl(specifier, location.href));
106
+
95
107
  /**
96
108
  * Resolve the drop placement for a forwarded cursor: point hit-test → nearest `[data-jx-path]` →
97
109
  * pure {@link computeDropInstruction}. Returns null when the cursor resolves to no droppable target.
@@ -300,6 +312,8 @@ export function startCanvasIframe(opts: {
300
312
  // Alongside the handle so a superseded render's refs can't keep posting.
301
313
  let stopDataScopeWatch: (() => void) | null = null;
302
314
  let latestGen = -1;
315
+ // The root's language as the frame booted it, which a render with no language goes back to.
316
+ const localeBaseline = captureLocaleBaseline(container.ownerDocument);
303
317
  // The raw page doc the current DOM was rendered from — the patch source-of-truth. `renderedGen`
304
318
  // Tracks which generation it (and the DOM) reflect, so patches for an in-flight/superseded render
305
319
  // Are handled correctly rather than applied against the wrong tree.
@@ -963,17 +977,12 @@ export function startCanvasIframe(opts: {
963
977
  /* The other document-level attribute flip, and the whole visible half of an axis-3 locale:
964
978
  `dir` is what makes an RTL preview actually mirror, and `lang` is what CSS's `:lang()` and
965
979
  the font stack select on. Patch-free like the scheme above — the TEXT is whatever file is
966
- open, because a translation in Jx is a different file rather than a different rendering.
967
- Cleared rather than blanked when the pane goes back to the document's own language: an
968
- empty `lang=""` is a document that claims to be in no language at all. */
969
- const root = container.ownerDocument.documentElement;
970
- if (msg.locale === null) {
971
- root.removeAttribute("lang");
972
- root.removeAttribute("dir");
973
- } else {
974
- root.setAttribute("lang", msg.locale);
975
- root.setAttribute("dir", msg.dir);
976
- }
980
+ open, because a translation in Jx is a different file rather than a different rendering. */
981
+ applyPreviewLocale(
982
+ container.ownerDocument,
983
+ msg.locale === null ? null : { dir: msg.dir, locale: msg.locale },
984
+ localeBaseline,
985
+ );
977
986
  return;
978
987
  }
979
988
  if (msg.kind === "siteStyleUpdate") {
@@ -985,6 +994,7 @@ export function startCanvasIframe(opts: {
985
994
  return;
986
995
  }
987
996
  applyPreviewColorScheme(container.ownerDocument, msg.colorScheme ?? null);
997
+ applyPreviewLocale(container.ownerDocument, msg.locale ?? null, localeBaseline);
988
998
  // A render replaces the DOM under a live edit session — COMMIT it first (never discard). The
989
999
  // Resulting editCommit/editEnd post synchronously here, so on the FIFO channel they precede
990
1000
  // This render's renderComplete and the parent routes them by the host's not-yet-flipped tab
@@ -1080,6 +1090,8 @@ export function startCanvasIframe(opts: {
1080
1090
  channel.post({ kind: "ready" });
1081
1091
  return () => {
1082
1092
  off();
1093
+ // Leave the root as it booted, so a frame started after this one captures the same baseline.
1094
+ applyPreviewLocale(container.ownerDocument, null, localeBaseline);
1083
1095
  stopInteraction();
1084
1096
  stopKeyForwarding();
1085
1097
  stopInlineEdit();
@@ -1127,7 +1139,7 @@ export function bootCanvasIframe(win: BootWindow): () => void {
1127
1139
  setResolveToken(params.get("rpcToken"));
1128
1140
  // ParentOrigin authenticates the parent peer. The host (iframe-host.ts) passes it ONLY for an
1129
1141
  // Http(s) parent (dev / chromium — same-origin, the origin round-trips). It OMITS it for a
1130
- // Non-http(s) parent (electrobun views://), whose custom scheme may not surface as a postMessage
1142
+ // Non-http(s) parent (a custom scheme), whose custom scheme may not surface as a postMessage
1131
1143
  // Origin: a missing value here means fall back to "*" — token-gated, NOT a silent omission — and
1132
1144
  // Log it loudly so the looser origin check is visible.
1133
1145
  const explicitParentOrigin = params.get("parentOrigin");
@@ -23,7 +23,7 @@ import {
23
23
  import { canvasRectToParent, createOverlayLayer } from "./iframe-overlay";
24
24
  import { serializeJxPath } from "./path-mapping";
25
25
  import { getActivePanel, panelMediaToActiveMedia } from "./canvas-helpers";
26
- import { panToParentRect } from "./canvas-utils";
26
+ import { panToParentRect, revealParentRect } from "./canvas-utils";
27
27
  import { clearDragGhost, moveDragGhost } from "../panels/drag-ghost";
28
28
  import { applyDropInstruction } from "../panels/dnd";
29
29
  import { rectOf } from "../utils/geometry";
@@ -67,7 +67,9 @@ import type {
67
67
  SerializableRect,
68
68
  SerializedKey,
69
69
  SyncedChord,
70
+ WireLocale,
70
71
  } from "./iframe-protocol";
72
+ import { getEffectiveRenderLocale } from "../site-context";
71
73
  import type { JxDocOp } from "../tabs/patch-ops";
72
74
  import type { IframeChannel } from "./iframe-channel";
73
75
  import type { OverlayLayer, OverlayPlacement } from "./iframe-overlay";
@@ -136,9 +138,8 @@ export interface HostState {
136
138
  canvasEl: HTMLElement;
137
139
  channel: IframeChannel<ParentToIframe, IframeToParent>;
138
140
  /**
139
- * The resolved canvasUrl this iframe was built with — so a host built early with the default URL
140
- * is rebuilt once the platform's loopback canvasUrl becomes available (electrobun resolves it
141
- * async).
141
+ * The resolved canvasUrl this iframe was built with — so a host built with one URL is rebuilt if
142
+ * the platform later reports a different one.
142
143
  */
143
144
  canvasUrl: string;
144
145
  ready: boolean;
@@ -154,7 +155,21 @@ export interface HostState {
154
155
  selectionPath: (string | number)[] | null;
155
156
  /** The whole selection this host last measured, so a re-measure redraws every box. */
156
157
  selectionPaths: readonly JxPath[];
157
- /** Id of the most recent selection `measure` request, so stale `geometry` replies are dropped. */
158
+ /**
159
+ * The allocator every `measure` request this host posts draws its id from: selection, presence,
160
+ * pan-to-card and {@link measureCanvasPath}. One counter keeps the ids disjoint, so the `geometry`
161
+ * handler can dispatch on the id alone.
162
+ */
163
+ measureSeq: number;
164
+ /**
165
+ * Id of the most recent SELECTION `measure` request, so a stale selection reply is dropped.
166
+ *
167
+ * It names the request, not the counter. It used to be both, so any other measure posted while a
168
+ * selection measure was in flight superseded it: picking a tag in the Styles Outline posts the
169
+ * selection measure and then a pan-to-card measure in the same tick, the pan took the newer id,
170
+ * and the selection reply arrived to find itself stale. The specimen was centred and never
171
+ * outlined. A live peer's presence measure did the same to the page selection.
172
+ */
158
173
  selReqId: number;
159
174
  /**
160
175
  * The NON-primary selected paths the in-flight selection `measure` covers, serialized. The
@@ -162,7 +177,7 @@ export interface HostState {
162
177
  * selected node leaves it empty, so nothing but the selection box is ever drawn.
163
178
  */
164
179
  coSelectionKeys: Set<string>;
165
- /** Id of the most recent presence `measure` request (allocated from the selReqId counter). */
180
+ /** Id of the most recent presence `measure` request (allocated from {@link HostState.measureSeq}). */
166
181
  presenceReqId: number;
167
182
  /** Serialized peer path → presence box meta for the in-flight presence measure. */
168
183
  presenceMeta: Map<string, { color: string; label: string }>;
@@ -270,8 +285,8 @@ export interface HostState {
270
285
  /**
271
286
  * Resolvers for {@link measureCanvasPath} requests, keyed by the `measure` reqId.
272
287
  *
273
- * Allocated from the same `selReqId` counter as the selection and presence measures, so the three
274
- * can never collide and the `geometry` handler can dispatch on the id alone.
288
+ * Allocated from the same `measureSeq` counter as the selection and presence measures, so the
289
+ * three can never collide and the `geometry` handler can dispatch on the id alone.
275
290
  */
276
291
  pendingMeasures: Map<number, (hits: NodeHit[]) => void>;
277
292
  /**
@@ -772,8 +787,8 @@ function measureIn(
772
787
  path: readonly (string | number)[],
773
788
  ): Promise<CanvasPoint | null> {
774
789
  return new Promise((resolve) => {
775
- host.selReqId += 1;
776
- const reqId = host.selReqId;
790
+ host.measureSeq += 1;
791
+ const reqId = host.measureSeq;
777
792
  const timer = setTimeout(() => {
778
793
  host.pendingMeasures.delete(reqId);
779
794
  resolve(null);
@@ -1716,8 +1731,8 @@ function requestPresence(host: HostState): void {
1716
1731
  host.overlay.setPresence([]);
1717
1732
  return;
1718
1733
  }
1719
- host.selReqId += 1;
1720
- host.presenceReqId = host.selReqId;
1734
+ host.measureSeq += 1;
1735
+ host.presenceReqId = host.measureSeq;
1721
1736
  host.channel.post({ kind: "measure", paths, reqId: host.presenceReqId });
1722
1737
  }
1723
1738
 
@@ -1741,6 +1756,8 @@ function requestSelection(host: HostState, sel: readonly JxPath[]): void {
1741
1756
  return;
1742
1757
  }
1743
1758
  if (!primary) {
1759
+ // Nothing selected, so a reply still in flight answers a question nobody is asking any more.
1760
+ host.selReqId = -1;
1744
1761
  host.coSelectionKeys.clear();
1745
1762
  host.overlay.setSelection(null);
1746
1763
  host.overlay.setCoSelection([]);
@@ -1753,7 +1770,8 @@ function requestSelection(host: HostState, sel: readonly JxPath[]): void {
1753
1770
  // Cross the postMessage boundary.
1754
1771
  const others = cloneSelection(sel.slice(0, -1));
1755
1772
  host.coSelectionKeys = new Set(others.map((path) => JSON.stringify(path)));
1756
- host.selReqId += 1;
1773
+ host.measureSeq += 1;
1774
+ host.selReqId = host.measureSeq;
1757
1775
  host.channel.post({
1758
1776
  kind: "measure",
1759
1777
  paths: [[...primary], ...others],
@@ -1774,13 +1792,15 @@ function requestStylebookSelection(host: HostState): void {
1774
1792
  const tag = shell.stylebook.selection;
1775
1793
  const cardPath = tag ? host.stylebook?.tagToCardPath.get(tag) : undefined;
1776
1794
  if (!tag || !cardPath) {
1795
+ host.selReqId = -1;
1777
1796
  host.overlay.setSelection(null);
1778
1797
  return;
1779
1798
  }
1780
1799
  if (!host.ready) {
1781
1800
  return;
1782
1801
  }
1783
- host.selReqId += 1;
1802
+ host.measureSeq += 1;
1803
+ host.selReqId = host.measureSeq;
1784
1804
  host.channel.post({ kind: "measure", paths: [[...cardPath]], reqId: host.selReqId });
1785
1805
  }
1786
1806
 
@@ -1789,9 +1809,9 @@ function requestStylebookSelection(host: HostState): void {
1789
1809
  *
1790
1810
  * This was the literal `/packages/studio/canvas.html` — a repo dev-server path baked into a browser
1791
1811
  * bundle, correct on exactly one host out of four. It happened to work because every other host
1792
- * overrides `canvasUrl`: the chromium launcher builds one from its token, electrobun fetches one
1793
- * over RPC, and the cloud adapter hard-codes its own. So the default was never the default; it was
1794
- * the dev server's URL with nothing saying so.
1812
+ * overrides `canvasUrl`: the chromium launcher builds one from its token and the cloud adapter
1813
+ * hard-codes its own. So the default was never the default; it was the dev server's URL with
1814
+ * nothing saying so.
1795
1815
  *
1796
1816
  * `bundleUrl` resolves it against the ENTRY's directory, which is the one fact every host agrees on
1797
1817
  * (see services/bundle-base.ts), so the fallback is now correct everywhere rather than accidentally
@@ -1801,12 +1821,6 @@ function defaultCanvasUrl(): string {
1801
1821
  return bundleUrl("../canvas.html");
1802
1822
  }
1803
1823
 
1804
- /**
1805
- * Loads nothing, deliberately: what a platform that resolves its canvasUrl LATER gets in the
1806
- * meantime. See {@link StudioPlatform.canvasUrlDeferred}.
1807
- */
1808
- const DEFERRED_CANVAS_URL = "about:blank";
1809
-
1810
1824
  /**
1811
1825
  * Release one host: its channel, its overlay, its frame, and everything awaiting a reply from it.
1812
1826
  *
@@ -1852,27 +1866,12 @@ function releaseHost(host: HostState): void {
1852
1866
  liveHosts.delete(host);
1853
1867
  }
1854
1868
 
1855
- /**
1856
- * Which document this pane's canvas iframe should load.
1857
- *
1858
- * The `canvasUrlDeferred` branch is the one that needs saying. Electrobun resolves its canvasUrl
1859
- * asynchronously — it is this window's loopback port, fetched over RPC inside `activate()` — and
1860
- * until the fix above, the bundle-relative default resolved to nothing servable under `views://`,
1861
- * so an early frame simply failed and `ensureHost` rebuilt against the real URL when it landed. Now
1862
- * that default RESOLVES: `views://studio/canvas.html` is a document electrobun really does stage.
1863
- * An early frame would therefore boot the whole canvas bundle inside the SHELL's app-privileged
1864
- * origin, in a CEF instance running `disable-site-isolation-trials` — and the cross-origin loopback
1865
- * canvas exists precisely so that never happens.
1866
- */
1869
+ /** Which document this pane's canvas iframe should load. */
1867
1870
  function resolveCanvasUrl(): string {
1868
1871
  if (!hasPlatform()) {
1869
1872
  return defaultCanvasUrl();
1870
1873
  }
1871
- const platform = getPlatform();
1872
- if (platform.canvasUrl) {
1873
- return platform.canvasUrl;
1874
- }
1875
- return platform.canvasUrlDeferred ? DEFERRED_CANVAS_URL : defaultCanvasUrl();
1874
+ return getPlatform().canvasUrl || defaultCanvasUrl();
1876
1875
  }
1877
1876
 
1878
1877
  /**
@@ -1912,9 +1911,8 @@ function ensureHost(canvasEl: HTMLElement): HostState {
1912
1911
  if (existing.canvasUrl === canvasUrl) {
1913
1912
  return existing;
1914
1913
  }
1915
- // The platform's loopback canvasUrl arrived after this host was built with the default URL
1916
- // (electrobun resolves it async over RPC) — tear the early iframe down and rebuild against the
1917
- // Right cross-origin origin.
1914
+ // The platform now reports a different canvasUrl than this host was built with — tear the
1915
+ // Iframe down and rebuild against the right origin.
1918
1916
  releaseHost(existing);
1919
1917
  }
1920
1918
  // ParentOrigin is the parent's real origin, passed into the iframe URL so the cross-origin iframe
@@ -1922,8 +1920,8 @@ function ensureHost(canvasEl: HTMLElement): HostState {
1922
1920
  const parentOrigin = location.origin;
1923
1921
  const token = crypto.randomUUID();
1924
1922
  // IframeOrigin is the iframe's own origin. For a RELATIVE canvasUrl (dev / chromium) it resolves to
1925
- // The parent's location.origin (same-origin). For an absolute loopback canvasUrl (electrobun) it is
1926
- // The loopback origin, so the channel accepts/targets the right cross-origin peer.
1923
+ // The parent's location.origin (same-origin). For an absolute canvasUrl it is
1924
+ // That URL's origin, so the channel accepts/targets the right cross-origin peer.
1927
1925
  const iframeOrigin = new URL(canvasUrl, location.href).origin;
1928
1926
  const iframe = document.createElement("iframe");
1929
1927
  iframe.className = "jx-canvas-iframe";
@@ -1932,16 +1930,16 @@ function ensureHost(canvasEl: HTMLElement): HostState {
1932
1930
  iframe.title = "Canvas — live page render";
1933
1931
  iframe.style.cssText =
1934
1932
  "width:100%;min-height:480px;height:100%;border:0;display:block;background:#fff";
1935
- // Preserve any query already on canvasUrl (e.g. electrobun's ?win=7) and append the token (always)
1933
+ // Preserve any query already on canvasUrl (e.g. a per-window `?win=7`) and append the token (always)
1936
1934
  // Plus parentOrigin (conditionally — see below).
1937
1935
  const srcUrl = new URL(canvasUrl, location.href);
1938
1936
  srcUrl.searchParams.set("token", token);
1939
1937
  // Pass parentOrigin into the iframe src ONLY when the PARENT is served over http(s) (dev / chromium
1940
1938
  // — same-origin, so the origin round-trips and the iframe can keep its acceptOrigin STRICT). For a
1941
- // Non-http(s) parent (electrobun views://) OMIT it: a custom scheme may not surface as a postMessage
1939
+ // Non-http(s) parent (a custom scheme) OMIT it: a custom scheme may not surface as a postMessage
1942
1940
  // Origin (event.origin), so the iframe falls back to acceptOrigin '*' + the shared token (it logs
1943
1941
  // The warn) rather than silently stalling. The PARENT side here stays STRICT — acceptOrigin /
1944
- // TargetOrigin are the real (loopback) iframeOrigin below, a gateable origin.
1942
+ // TargetOrigin are the real iframeOrigin below, a gateable origin.
1945
1943
  if (location.protocol === "http:" || location.protocol === "https:") {
1946
1944
  srcUrl.searchParams.set("parentOrigin", parentOrigin);
1947
1945
  }
@@ -1987,6 +1985,7 @@ function ensureHost(canvasEl: HTMLElement): HostState {
1987
1985
  lastHover: null,
1988
1986
  lastSelectionRect: null,
1989
1987
  lastSnapshotSeq: 0,
1988
+ measureSeq: 0,
1990
1989
  overlay,
1991
1990
  panReqId: -1,
1992
1991
  pending: null,
@@ -2002,7 +2001,7 @@ function ensureHost(canvasEl: HTMLElement): HostState {
2002
2001
  ready: false,
2003
2002
  selectionPath: null,
2004
2003
  selectionPaths: [],
2005
- selReqId: 0,
2004
+ selReqId: -1,
2006
2005
  snapshot: null,
2007
2006
  stylebook: null,
2008
2007
  tabId: null,
@@ -2397,7 +2396,7 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2397
2396
  const [hit] = msg.hits;
2398
2397
  if (hit) {
2399
2398
  const { rect: ifr, scale } = hostDragGeometry(state);
2400
- panToParentRect({ height: hit.rect.height * scale, top: hit.rect.y * scale + ifr.top });
2399
+ revealParentRect({ height: hit.rect.height * scale, top: hit.rect.y * scale + ifr.top });
2401
2400
  }
2402
2401
  return;
2403
2402
  }
@@ -2884,6 +2883,7 @@ function onDomUpdated(state: HostState, gen: number): void {
2884
2883
  requestSelection(state, state.selectionPaths);
2885
2884
  requestPresence(state);
2886
2885
  hideInsertZoneNow(state);
2886
+ servePendingStylebookPan(state);
2887
2887
  if (state.pendingEnterEdit && gen >= state.pendingEnterEdit.minGen) {
2888
2888
  const { offset, path, prop } = state.pendingEnterEdit;
2889
2889
  state.pendingEnterEdit = null;
@@ -3008,7 +3008,7 @@ function drawHover(state: HostState, hit: NodeHit | null): void {
3008
3008
  */
3009
3009
  type PreparedRender = Omit<
3010
3010
  Extract<ParentToIframe, { kind: "render" }>,
3011
- "colorScheme" | "gen" | "kind"
3011
+ "colorScheme" | "gen" | "kind" | "locale"
3012
3012
  >;
3013
3013
 
3014
3014
  /** One render pass's prepared payloads, by document IDENTITY. */
@@ -3237,6 +3237,8 @@ export async function mountIframeCanvas(
3237
3237
  colorScheme: schemeWireFor(viewTab),
3238
3238
  gen,
3239
3239
  kind: "render",
3240
+ // The language this artboard draws in, per tab and at post time like the scheme.
3241
+ locale: localeWireFor(viewTab),
3240
3242
  // Read at POST time for the same reason `colorScheme` is: a render replaces the DOM, so a panel
3241
3243
  // The author opened before this pass would close under them without it.
3242
3244
  popoverOpen: viewTab?.session.ui.openPopover ?? null,
@@ -3267,14 +3269,27 @@ export async function mountIframeCanvas(
3267
3269
  * until it says `ready`. Counted either way: the message is this pass's work for this host whether
3268
3270
  * or not the frame has finished loading.
3269
3271
  */
3270
- function deliverRender(state: HostState, message: ParentToIframe): void {
3272
+ function deliverRender(state: HostState, message: RenderMsg): void {
3271
3273
  canvasPerf.hostRenderPosts += 1;
3272
3274
  if (state.ready) {
3273
3275
  syncAssetVersions(state);
3274
3276
  state.channel.post(message);
3275
- } else {
3276
- state.pending = message;
3277
+ return;
3278
+ }
3279
+ /* The queue holds ONE render, and it must be the NEWEST, not the last to arrive. A page render
3280
+ awaits its own resolution, so two renders started in order can finish out of order: on a
3281
+ reload that restores two tabs, the frame is still booting, the first-opened tab's render (an
3282
+ older gen) can resolve after the active tab's, and overwriting the slot posted the stale one
3283
+ on `ready`. The frame drew the other tab's document, the ack adopted that tab's identity, and
3284
+ the active tab's render was lost. A ready frame drops a stale gen itself (`latestGen`); the
3285
+ queue has to make the same call. */
3286
+ const queued = state.pending;
3287
+ if (queued?.kind === "render" && queued.gen > message.gen) {
3288
+ // Never posted, so never acked: its identity must not wait for an ack that cannot come.
3289
+ state.pendingTabIds.delete(message.gen);
3290
+ return;
3277
3291
  }
3292
+ state.pending = message;
3278
3293
  }
3279
3294
 
3280
3295
  /**
@@ -3304,13 +3319,15 @@ export function mountStylebookCanvas(
3304
3319
  // (and fake test channels pass messages by reference, so sharing one object would alias them).
3305
3320
  const cloneableDoc = plainCopy(generated.doc);
3306
3321
  const cloneableShadow = plainCopy(generated.doc);
3307
- const message: ParentToIframe = {
3322
+ const message: RenderMsg = {
3308
3323
  // The specimen carries no tab identity, but the STAGE it is mounted into does: a stylebook is
3309
3324
  // Opened as a tab like anything else, and its `previewColorScheme` is that tab's.
3310
3325
  /* A stylebook specimen is generated, not a project document, so it references no project
3311
3326
  media and needs no resolution. */
3312
3327
  assets: null,
3313
3328
  colorScheme: schemeWireFor(tabOfContainer(canvasEl)),
3329
+ // And its language, for the same reason: a stylebook draws `:lang()` rules and `dir` too.
3330
+ locale: localeWireFor(tabOfContainer(canvasEl)),
3314
3331
  doc: cloneableDoc,
3315
3332
  docBase: `${canvasBaseOrigin()}/`,
3316
3333
  gen,
@@ -3392,6 +3409,21 @@ export function postColorSchemeToLiveHosts(
3392
3409
  * `dir` is computed here and sent with the tag: `localeDirection` is CLDR's answer through
3393
3410
  * `Intl.Locale`, and a frame deriving it again is a second copy of the RTL script list to keep.
3394
3411
  */
3412
+ /** A locale tag as the frame applies it, with its direction; null stays null. */
3413
+ function localeWire(locale: string | null): WireLocale | null {
3414
+ return locale === null ? null : { dir: localeDirection(locale), locale };
3415
+ }
3416
+
3417
+ /**
3418
+ * The language a TAB's artboard draws in, as wire data (studio.md §20.2): the one rule
3419
+ * `getEffectiveRenderLocale` answers, read at post time for `schemeWireFor`'s reason.
3420
+ */
3421
+ function localeWireFor(tab: Tab | null): WireLocale | null {
3422
+ return localeWire(
3423
+ tab ? getEffectiveRenderLocale(tab.session.ui.previewLocale, tab.documentPath) : null,
3424
+ );
3425
+ }
3426
+
3395
3427
  export function postLocaleToLiveHosts(locale: string | null, root?: HTMLElement | null): void {
3396
3428
  const dir = localeDirection(locale);
3397
3429
  for (const host of liveHosts) {
@@ -3404,6 +3436,12 @@ export function postLocaleToLiveHosts(locale: string | null, root?: HTMLElement
3404
3436
  }
3405
3437
  if (host.ready) {
3406
3438
  host.channel.post({ dir, kind: "setLocale", locale });
3439
+ } else if (host.pending?.kind === "render") {
3440
+ /* A host still booting draws the render it is waiting for, so THAT is what learns the
3441
+ language: a restored session writes `previewLocale` after its files have opened, by which
3442
+ time a pane's render may already be pending with the old value, and no render follows. A
3443
+ host with nothing pending is skipped; its render reads the value when it is posted. */
3444
+ host.pending = { ...host.pending, locale: localeWire(locale) };
3407
3445
  }
3408
3446
  }
3409
3447
  }
@@ -3484,23 +3522,57 @@ export function postStyleUpdateToStylebookHosts(style: JxStyle): number {
3484
3522
  return posted;
3485
3523
  }
3486
3524
 
3525
+ /**
3526
+ * A pan asked for before any stylebook canvas could serve it: the tag, served by the next stylebook
3527
+ * render that draws its card while it is still the selection ({@link servePendingStylebookPan}).
3528
+ */
3529
+ let pendingStylebookPan: string | null = null;
3530
+
3531
+ /** Post the measure that pans `host` to `tag`'s card. `false` when the host cannot serve it yet. */
3532
+ function postStylebookPan(host: HostState | null, tag: string): boolean {
3533
+ if (!host?.stylebook || !host.ready) {
3534
+ return false;
3535
+ }
3536
+ const cardPath = host.stylebook.tagToCardPath.get(tag);
3537
+ if (!cardPath) {
3538
+ return false;
3539
+ }
3540
+ host.measureSeq += 1;
3541
+ host.panReqId = host.measureSeq;
3542
+ host.channel.post({ kind: "measure", paths: [[...cardPath]], reqId: host.panReqId });
3543
+ return true;
3544
+ }
3545
+
3487
3546
  /**
3488
3547
  * Pan the canvas so the selected tag's card is centered (layers-panel "locate" affordance). The
3489
3548
  * card lives inside the iframe, so it is measured over the bridge and the reply pans by the
3490
3549
  * converted parent-viewport rect (see the `geometry` handler's panReqId branch).
3550
+ *
3551
+ * A pan the canvas cannot serve yet is KEPT, not dropped. The Styles panel switches the pane to the
3552
+ * Elements view and selects a tag in one tick, from the Variables view, where no stylebook frame
3553
+ * exists: the frame that will draw the card has not been mounted. The request waits for it.
3491
3554
  */
3492
3555
  export function panToStylebookTag(tag: string): void {
3493
- const host = hostForActivePanel();
3494
- if (!host?.stylebook || !host.ready) {
3556
+ pendingStylebookPan = postStylebookPan(hostForActivePanel(), tag) ? null : tag;
3557
+ }
3558
+
3559
+ /**
3560
+ * Serve a kept pan on a stylebook render that has just landed: on the canvas the author is in, and
3561
+ * only while its tag is still the selection, so a request the author has since moved past is
3562
+ * forgotten rather than served late.
3563
+ */
3564
+ function servePendingStylebookPan(state: HostState): void {
3565
+ const tag = pendingStylebookPan;
3566
+ if (tag === null || !state.stylebook) {
3495
3567
  return;
3496
3568
  }
3497
- const cardPath = host.stylebook.tagToCardPath.get(tag);
3498
- if (!cardPath) {
3569
+ if (shell.stylebook.selection !== tag) {
3570
+ pendingStylebookPan = null;
3499
3571
  return;
3500
3572
  }
3501
- host.selReqId += 1;
3502
- host.panReqId = host.selReqId;
3503
- host.channel.post({ kind: "measure", paths: [[...cardPath]], reqId: host.selReqId });
3573
+ if (state === hostForActivePanel() && postStylebookPan(state, tag)) {
3574
+ pendingStylebookPan = null;
3575
+ }
3504
3576
  }
3505
3577
 
3506
3578
  // ─── Format-toolbar bridge (Phase 4b-2) ─────────────────────────────────────────
@@ -56,6 +56,17 @@ import type { IframeToParent, ParentToIframe, SerializedKey, SyncedChord } from
56
56
  */
57
57
  export const FRAME_KEY_SCOPES = ["caret", "canvas", "global"] as const;
58
58
 
59
+ /**
60
+ * Commands whose chords the frame never forwards, though their scope is on its stack.
61
+ *
62
+ * `pane.backToHome` holds bare Escape in the global scope, and it applies only to a view opened
63
+ * beside a strip-less workspace's home (a media viewer, a comparison, `project.json` as Code), none
64
+ * of which is a canvas frame. Projected into the table, it made every frame cancel Escape in
65
+ * Preview and mid-sentence too, where nothing else claims it, so an author's own `<dialog>` or
66
+ * popover in the rendered page could no longer be dismissed with it.
67
+ */
68
+ export const HOST_ONLY_COMMANDS: ReadonlySet<string> = new Set(["pane.backToHome"]);
69
+
59
70
  /** The scope stack a caret session is in — app chords still fire, element-level ones do not. */
60
71
  const CARET_STACK = ["caret", "global"] as const;
61
72
  /** The stack with a selection but no caret: structural verbs are live. */