@jxsuite/studio 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +21080 -32585
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +7 -0
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +221 -245
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -53
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +30 -1
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +7 -1
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
  363. package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
  364. package/dist/chunks/files-j8a1t06k.js +0 -50
  365. package/dist/chunks/files-j8a1t06k.js.map +0 -9
  366. package/dist/chunks/git-panel-xtq5z81x.js +0 -28
  367. package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
  368. package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
  369. package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
  370. package/dist/chunks/settings-modal-17ttr96g.js +0 -29
  371. package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-0xgs7xrs.js +0 -429
  381. package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
  382. package/dist/chunks/studio-4d8hzcz5.js +0 -3427
  383. package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
  384. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  385. package/dist/chunks/studio-9smfzqhc.js +0 -19
  386. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  387. package/dist/chunks/studio-jm0z592w.js.map +0 -115
  388. package/dist/chunks/studio-m2t4mdg5.js +0 -1854
  389. package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
  390. package/dist/chunks/studio-n4p11dfy.js +0 -853
  391. package/dist/chunks/studio-n4p11dfy.js.map +0 -12
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-sjh1wt1a.js +0 -3744
  394. package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
  395. package/dist/chunks/studio-v7jkjqng.js.map +0 -46
  396. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  397. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  398. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  399. package/dist/chunks/studio-yfcstdzy.js +0 -399
  400. package/dist/chunks/studio-yfcstdzy.js.map +0 -12
  401. package/dist/chunks/studio-zp7jncs8.js +0 -469
  402. package/dist/chunks/studio-zp7jncs8.js.map +0 -14
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -46,6 +46,22 @@ export interface WireMapperCtx {
46
46
  arrayPaths: string[];
47
47
  }
48
48
 
49
+ /**
50
+ * One live binding, as the frame needs to know it: the chord and the scope that holds it.
51
+ *
52
+ * Scopes are carried rather than flattened because the frame picks its own stack per keystroke —
53
+ * `caret` when an inline-edit session is live, `canvas` when the artboard has a selection, `global`
54
+ * under both — which is the same ladder `commands/context.ts`'s `keyScopeStack` walks host-side.
55
+ * Flattening to "chords the parent wants" would lose exactly the distinction that lets ⌘C reach the
56
+ * browser mid-sentence and the structural copy verb at every other moment.
57
+ *
58
+ * The command ID is deliberately absent: the frame decides whether to FORWARD, never what runs.
59
+ */
60
+ export interface SyncedChord {
61
+ chord: string;
62
+ scope: "caret" | "canvas" | "global";
63
+ }
64
+
49
65
  /** Messages the editor (parent) sends into the canvas iframe. */
50
66
  export type ParentToIframe =
51
67
  | { kind: "init"; gen: number }
@@ -82,6 +98,53 @@ export type ParentToIframe =
82
98
  // Flip the forced color-scheme preview on the iframe root without re-rendering — a document-level
83
99
  // Idempotent attribute write, deliberately gen-less (like endEdit).
84
100
  | { kind: "setColorScheme"; scheme: "light" | "dark" | null }
101
+ /**
102
+ * Set the language the artboard is drawn in — `lang` and `dir` on the frame's document element.
103
+ *
104
+ * Render-free and gen-less, like `setColorScheme` above, because it changes no content: Jx has no
105
+ * message catalogue, so the TEXT is whatever file is open and only the direction, the font stack
106
+ * and CSS's own `:lang()` selectors move. `dir` travels with the tag rather than being derived in
107
+ * the frame — `localeDirection` is CLDR's answer via `Intl.Locale`, and the frame has no reason
108
+ * to hold a second copy of the RTL script list.
109
+ *
110
+ * `null` is "the document's own language": both attributes come off, and the frame falls back to
111
+ * whatever the rendered document declares.
112
+ */
113
+ | { kind: "setLocale"; locale: string | null; dir: "ltr" | "rtl" }
114
+ /**
115
+ * Open the slash menu at the caret, by name rather than by typing "/".
116
+ *
117
+ * The gesture is recognised inside the frame (`canvas/iframe-inline-edit.ts`), because that is
118
+ * where the keystroke is; this is the door for `insert.openSlashMenu` — the palette, a rebound
119
+ * chord, the automation runner and the assistant. The frame opens it UNANCHORED: there is no "/"
120
+ * in the document, so the menu carries its own filter field and selecting a block deletes
121
+ * nothing.
122
+ *
123
+ * Gen-less and render-free, like `setColorScheme` above. A frame with no caret session ignores
124
+ * it, which is the same refusal the record's `requires` sentence states.
125
+ */
126
+ | { kind: "openSlash" }
127
+ /**
128
+ * The chord table, so the frame forwards exactly what the host's registry binds.
129
+ *
130
+ * The frame used to answer "does the parent want this keystroke?" from three hand-written lists —
131
+ * eight bare keys, four "the editor owns these" chords, three "the browser owns these" chords —
132
+ * maintained beside a registry that already knew the answer. They disagreed in both directions:
133
+ * ⌘A was forwarded and `preventDefault`ed by a frame that assumed the host would claim it (no
134
+ * record binds it, so select-all did nothing AND the native one was suppressed), while ⌘B was
135
+ * withheld on the assumption the editing engine handled it (it does not — the parent's
136
+ * block-level keydown listener never fires against a container-level editing host, and
137
+ * `canvas/editable-actions.ts` rejects the browser's own `formatBold`), so Bold in the canvas did
138
+ * nothing at all.
139
+ *
140
+ * Idempotent and render-free, like `setColorScheme` above, and deliberately gen-less: it
141
+ * describes the APP, not a document. The host reposts it whenever the keymap changes, which is
142
+ * what makes a rebinding in Preferences take effect inside the canvas.
143
+ *
144
+ * A frame built before this message existed ignores it and keeps its own lists — which is the
145
+ * compatibility story `dist/iframe-entry.js` shipping prebuilt requires.
146
+ */
147
+ | { kind: "keymap"; mac: boolean; chords: readonly SyncedChord[] }
85
148
  // Replace the injected site-style sheet in place (live design-token editing) — idempotent and
86
149
  // Render-free; the superseding render carries the same style via its own siteStyle.
87
150
  | {
@@ -240,6 +303,29 @@ export interface DropPreview {
240
303
  edge: "top" | "bottom" | "inside";
241
304
  }
242
305
 
306
+ /**
307
+ * A click that landed on LAYOUT chrome — a header, a footer, anything the layout file contributes
308
+ * that is not the page's own content. Such a node has no page-document path, so it can never be a
309
+ * {@link NodeHit}; it is addressed by the layout FILE it came from plus its path inside that file.
310
+ *
311
+ * This is the message that makes the first click a new author makes do something. On a default
312
+ * project the two most conspicuous strings on the page ("My Site", "Built with Jx") both come from
313
+ * `layouts/base.json`, and before this existed clicking either one selected nothing, hovered
314
+ * nothing and posted nothing.
315
+ */
316
+ export interface LayoutHit {
317
+ /** Project-relative path of the layout document, e.g. `layouts/base.json`. */
318
+ layoutFile: string;
319
+ /** The clicked node's path WITHIN that layout document (what to select once it is open). */
320
+ layoutPath: (string | number)[];
321
+ /** The rendered tag, for the panel's `<header>` label. */
322
+ tagName: string;
323
+ /** The rendered class list, shown read-only in the panel. */
324
+ className: string;
325
+ /** The node's box, in iframe-viewport coords (same convention as {@link NodeHit}). */
326
+ rect: SerializableRect;
327
+ }
328
+
243
329
  /** A document path plus the iframe-space rect of the node it resolves to. */
244
330
  export interface NodeHit {
245
331
  path: (string | number)[];
@@ -308,7 +394,31 @@ export type IframeToParent =
308
394
  // Not the parent, now resolves the scope). `gen` lets the parent drop a snapshot from a superseded
309
395
  // Render. Posted right after `renderComplete`; values are JSON-safe deep clones (see serialize-scope).
310
396
  | { kind: "dataScope"; gen: number; scope: Record<string, unknown> }
311
- | { kind: "hit"; hit: NodeHit }
397
+ // The frame's own quiescence, ANSWERED rather than polled (spec §13.5, plan §13.4 condition 5).
398
+ // Nothing in the parent realm can see inside a cross-origin frame, so a parent that wanted to
399
+ // Know whether the canvas had settled could only sleep. The frame samples itself at its own rAF
400
+ // And posts whenever the tuple changes, ending with the quiet one; the host holds the latest.
401
+ //
402
+ // `fonts` is `document.fonts.status === "loaded"` and is NOT sufficient alone — in a blank canvas
403
+ // Frame it reports "loaded" against an EMPTY font set (measured in S0: `hero` drifted 0.150 RMSE
404
+ // Because the first capture measured fallback metrics while Plus Jakarta Sans was still in
405
+ // Flight). It is honest only together with `gen` (a frame that has not acked a render has not
406
+ // Loaded its fonts either) and the runner's per-frame network count.
407
+ //
408
+ // `images` counts images whose load FAILED and whose retry (`installCanvasImageRetry`, re-firing
409
+ // At 150/300/450 ms) has not resolved. Deliberately NOT "images not yet complete": blocking on
410
+ // `img.complete` failed 8 of 61 shots — the design canvas renders unresolved bindings as literal
411
+ // Srcs, several starters ship a 404 favicon, and lazy images below the fold never complete. A
412
+ // Pending retry is the one image fact only the app knows.
413
+ | { kind: "idle"; gen: number; fonts: boolean; animations: number; images: number }
414
+ // `additive` is Ctrl/Cmd being held at the moment of the click — the ACCUMULATE gesture, which
415
+ // The parent turns into a toggle against `session.selection` (studio §6.5). Absent/false is a
416
+ // Plain replace, which is what every canvas click was before the selection became a set.
417
+ | { kind: "hit"; hit: NodeHit; additive?: boolean }
418
+ // A click on layout chrome (see {@link LayoutHit}). Distinct from `hit` because the target is not
419
+ // In the page document at all: the parent adopts it as `view.layoutSelection` (which shows the
420
+ // Read-only layout panel with its "Open Layout →" action) rather than as a document selection.
421
+ | { kind: "layoutHit"; hit: LayoutHit }
312
422
  | { kind: "hover"; hit: NodeHit | null }
313
423
  // Candidate insertion "+" zones for the hovered node, recomputed on pointermove (cross-origin
314
424
  // Cousin of the legacy insertion-helper). `null` clears the "+" (cursor mid-element / left the
@@ -417,7 +527,17 @@ export type IframeToParent =
417
527
  // The engine (in the iframe) detected "/" in a live edit session; the parent shows the real
418
528
  // Lit/Spectrum menu. Re-posted with a new `filter` as the author keeps typing (the engine's
419
529
  // UpdateSlashMenu drives it). `rect` is the edited element's bbox in IFRAME-VIEWPORT coords.
420
- | { kind: "slashShow"; rect: SerializableRect; filter: string }
530
+ /**
531
+ * Show the parent's slash menu at this rect.
532
+ *
533
+ * `showFilter` is what an UNANCHORED menu needs — one opened by `insert.openSlashMenu` rather
534
+ * than by typing "/". There is no "/…" run in the document to narrow the list with, so the menu
535
+ * has to carry its own field or the only way past fifteen blocks is scrolling. It existed as a
536
+ * callback option INSIDE the frame and was dropped at this boundary, which is the argument for a
537
+ * message carrying every fact its receiver needs rather than most of them: the loss was
538
+ * invisible, because the menu still appeared.
539
+ */
540
+ | { kind: "slashShow"; rect: SerializableRect; filter: string; showFilter?: boolean }
421
541
  // A menu-navigation key pressed in the iframe while the parent menu is open. The iframe
422
542
  // Intercepts these four keys capture-phase (restoring the "menu captures Enter" contract) and the
423
543
  // Host drives the parent menu's key handler directly — no synthetic keydown redispatch.
@@ -430,6 +550,21 @@ export type IframeToParent =
430
550
  // Browser menu is still suppressed, legacy parity); x/y are IFRAME-VIEWPORT coords for the host
431
551
  // To convert via its empirical geometry.
432
552
  | { kind: "contextMenu"; path: (string | number)[] | null; x: number; y: number }
553
+ // ─── Pane focus ─────────────────────────────────────────────────────────────
554
+ // A pointer went down somewhere in this frame. Nothing else — no path, no coordinates, no
555
+ // Selection: it says only "the person is working in this pane now", which the parent realm cannot
556
+ // Observe for itself because a pointer event inside a cross-origin iframe is delivered in the
557
+ // Frame's own realm and never surfaces above it.
558
+ //
559
+ // `hit` used to be the whole seam, and it has two holes. It is not posted in PREVIEW at all — a
560
+ // Click there is a click on the page, never a selection — so a pane showing Preview could not be
561
+ // Focused by clicking the thing it is showing; and in edit/design it is only posted when the
562
+ // Click lands ON a `[data-jx-path]` node, so clicking an artboard's empty margin focused nothing
563
+ // Either. Both left the keyboard in the other pane while the person was plainly in this one.
564
+ //
565
+ // Deliberately NOT in the host's preview block-list: focusing a pane is not an edit, and it is
566
+ // The one thing preview must still report.
567
+ | { kind: "paneFocus" }
433
568
  // ─── Preview navigation ─────────────────────────────────────────────────────
434
569
  // A link was clicked in PREVIEW mode. Preview keeps anchors live (design/edit de-link them onto
435
570
  // `data-jx-href`), so the click would navigate the canvas iframe away and destroy the render —
@@ -165,6 +165,36 @@ export const EDIT_PLACEHOLDER_CSS = `
165
165
  font-size: 13px;
166
166
  pointer-events: none;
167
167
  }
168
+ [data-jx-layout-region] {
169
+ position: relative;
170
+ opacity: 0.5;
171
+ transition: opacity 120ms ease;
172
+ }
173
+ [data-jx-layout-region]:hover {
174
+ opacity: 0.85;
175
+ outline: 1px dashed color-mix(in srgb, #808080 55%, transparent);
176
+ outline-offset: -1px;
177
+ }
178
+ [data-jx-layout-region] [data-jx-layout-region] {
179
+ opacity: 1;
180
+ }
181
+ [data-jx-layout-region]::before {
182
+ content: "LAYOUT \\00B7 " attr(data-jx-layout-file);
183
+ position: absolute;
184
+ top: 0;
185
+ left: 0;
186
+ z-index: 2;
187
+ padding: 1px 5px;
188
+ border-radius: 0 0 3px 0;
189
+ background: color-mix(in srgb, #808080 78%, transparent);
190
+ color: #fff;
191
+ font: 700 9px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
192
+ letter-spacing: 0.08em;
193
+ pointer-events: none;
194
+ }
195
+ [data-jx-layout-region] [data-jx-layout-region]::before {
196
+ content: none;
197
+ }
168
198
  `;
169
199
 
170
200
  /**
@@ -187,6 +217,22 @@ export function syncEditModeCss(doc: Document, mode: CanvasMode): void {
187
217
  doc.head.append(style);
188
218
  }
189
219
 
220
+ /**
221
+ * Flag the canvas document as a preview shell, so `canvas.html`'s preview rules apply.
222
+ *
223
+ * The document's default box is built for EDITING: `html, body { overflow: hidden }`, because the
224
+ * host grows the iframe to its content height there and the parent canvas is what pans. Preview
225
+ * inverts that — the frame stays at the pane's height and the document scrolls itself — and until
226
+ * this existed nothing turned the clipping off, so preview showed the first screenful of every page
227
+ * and no more. An attribute rather than an injected stylesheet: the rules it switches live beside
228
+ * the ones they override, which is where a reader looks for them.
229
+ *
230
+ * Called on every render because one iframe is reused across modes.
231
+ */
232
+ export function syncPreviewShell(doc: Document, mode: CanvasMode): void {
233
+ doc.documentElement.toggleAttribute("data-jx-preview", mode === "preview");
234
+ }
235
+
190
236
  /**
191
237
  * Whether a canvas mode gets a live caret. Design and edit are the interactive modes; preview must
192
238
  * look and behave like the shipped page, and stylebook specimens are not documents.
@@ -429,7 +475,10 @@ export function injectHead(doc: JxDocument): void {
429
475
  }
430
476
  }
431
477
 
432
- /** Build the `onNodeCreated` hook that stamps `data-jx-path` / `data-jx-layout` on rendered nodes. */
478
+ /**
479
+ * Build the `onNodeCreated` hook that stamps `data-jx-path` (page content) or `data-jx-layout-path`
480
+ * + `data-jx-layout-file` (layout-originated nodes) on rendered nodes.
481
+ */
433
482
  export function makeStamper(ctx: PathMapCtx) {
434
483
  return (created: Node, path: (string | number)[], def: unknown) => {
435
484
  if (!(created instanceof HTMLElement)) {
@@ -459,7 +508,25 @@ export function makeStamper(ctx: PathMapCtx) {
459
508
  }
460
509
  const classified = classifyRenderNode(path, def, ctx);
461
510
  if (classified.kind === "layout") {
462
- created.dataset.jxLayout = "";
511
+ // A layout node has no page-document path, but it is NOT anonymous: stamp where it came from
512
+ // So a click on it can select it and offer to open the layout at that node. Without this the
513
+ // Two most clickable strings on a brand-new project ("My Site", "Built with Jx") answered a
514
+ // Click with nothing at all.
515
+ created.dataset.jxLayoutPath = serializeJxPath(classified.layoutPath);
516
+ if (classified.layoutFile) {
517
+ created.dataset.jxLayoutFile = classified.layoutFile;
518
+ }
519
+ if (classified.chrome) {
520
+ // Chrome — a region of the layout that does NOT wrap the page content. Marked so the canvas
521
+ // Can dim and label it, and frozen so no caret can land there. The container is permanently
522
+ // `contenteditable`, so without this the browser happily put a caret in the site header and
523
+ // Then dropped every keystroke on the floor at the `beforeinput` chokepoint: the one place a
524
+ // New author is most likely to click looked editable and silently was not.
525
+ created.dataset.jxLayoutRegion = "";
526
+ if (isEditableMode(ctx.canvasMode)) {
527
+ created.contentEditable = "false";
528
+ }
529
+ }
463
530
  return;
464
531
  }
465
532
  created.dataset.jxPath = serializeJxPath(classified.path);
@@ -542,6 +609,7 @@ export async function renderResolvedDocument(opts: {
542
609
  applySiteStyle(opts.siteStyle, (opts.doc as { $media?: Record<string, string> }).$media ?? {});
543
610
  injectHead(opts.doc);
544
611
  syncEditModeCss(opts.container.ownerDocument, opts.mode);
612
+ syncPreviewShell(opts.container.ownerDocument, opts.mode);
545
613
  syncStylebookCss(opts.container.ownerDocument, opts.mode);
546
614
  // Seed the runtime's root $media before buildScope so a COMPONENT with its own `@--name` blocks
547
615
  // But no own `$media` resolves the breakpoint to its real query (the iframe path calls buildScope
@@ -59,6 +59,12 @@ export function startIframeSlashBridge(
59
59
  filter,
60
60
  kind: "slashShow",
61
61
  rect: { height: r.height, width: r.width, x: r.left, y: r.top },
62
+ /* Carried, because the parent draws the menu and only the FRAME knows why it opened. A
63
+ menu opened by name has no "/…" run in the document to filter with, so it needs its own
64
+ field; the "/" gesture needs none, because the author types into the block. This option
65
+ was dropped at this boundary and the loss was invisible — the menu still appeared, just
66
+ with no way past fifteen items but scrolling. */
67
+ ...(cbs.showFilter === true ? { showFilter: true } : {}),
62
68
  });
63
69
  },
64
70
  };
@@ -22,17 +22,67 @@ export interface PathMapCtx {
22
22
  arrayPaths: Set<string>;
23
23
  }
24
24
 
25
- /** A render node either belongs to the layout chrome (no document path) or maps to a document path. */
26
- export type RenderNodeClass = { kind: "layout" } | { kind: "path"; path: JxPath };
25
+ /**
26
+ * The marker `markLayoutNodes` stamps on every node of a resolved layout document (see
27
+ * {@link file://./canvas-live-render.ts}). It names the layout FILE and the node's own path inside
28
+ * that file — enough for the editor to select the node, label it, and open the layout at it. It
29
+ * used to be a bare `true`, which is why clicking layout chrome on a fresh project selected nothing
30
+ * at all: the render had thrown away the only two facts needed to act on the click.
31
+ */
32
+ export interface LayoutMarker {
33
+ /** Project-relative path of the layout document, e.g. `layouts/base.json`. */
34
+ file: string;
35
+ /** The node's path WITHIN the layout document. */
36
+ path: JxPath;
37
+ }
38
+
39
+ /**
40
+ * A render node either belongs to the layout (carrying its origin in the layout file, since it has
41
+ * no page-document path) or maps to a page-document path.
42
+ *
43
+ * `chrome` distinguishes the two very different kinds of layout node. Most are chrome — a header, a
44
+ * footer, a `<noscript>` — regions the page cannot edit in place. But the nodes on the path down to
45
+ * the `<slot>` (the layout root, the `<main>` around it) WRAP the page content, so they must stay
46
+ * transparent: dimming or freezing them would dim and freeze the whole document. Only chrome is
47
+ * dimmed, labelled, and made `contenteditable="false"`.
48
+ */
49
+ export type RenderNodeClass =
50
+ | { kind: "layout"; layoutFile: string; layoutPath: JxPath; chrome: boolean }
51
+ | { kind: "path"; path: JxPath };
52
+
53
+ /**
54
+ * Whether `path` is the page-content container or one of its ancestors — i.e. whether this node's
55
+ * subtree contains the distributed page content. `prefix` is the render path of the container whose
56
+ * children hold that content (`PathMapCtx.pageContentPrefix`).
57
+ */
58
+ export function wrapsPageContent(path: JxPath, prefix: JxPath | null): boolean {
59
+ if (!prefix) {
60
+ return false;
61
+ }
62
+ return path.length <= prefix.length && path.every((seg, i) => prefix[i] === seg);
63
+ }
27
64
 
28
65
  /**
29
66
  * Resolve a runtime render path to a document path (or flag it as layout-originated). Pure — the
30
- * caller decides whether to record the result in a WeakMap or a `data-jx-path` attribute.
67
+ * caller decides whether to record the result in a WeakMap or `data-jx-*` attributes.
31
68
  */
32
69
  export function classifyRenderNode(path: JxPath, def: unknown, ctx: PathMapCtx): RenderNodeClass {
33
- // Layout-originated nodes have no page-document path; the caller marks them (data-jx-layout).
34
- if (ctx.layoutWrapped && typeof def === "object" && (def as { $__layout?: unknown })?.$__layout) {
35
- return { kind: "layout" };
70
+ // Layout-originated nodes have no page-document path; the caller stamps their origin instead
71
+ // (data-jx-layout-path / data-jx-layout-file).
72
+ const marker =
73
+ ctx.layoutWrapped && typeof def === "object"
74
+ ? (def as { $__layout?: unknown } | null)?.$__layout
75
+ : undefined;
76
+ if (marker) {
77
+ const origin = (typeof marker === "object" ? marker : {}) as Partial<LayoutMarker>;
78
+ return {
79
+ // The root is never chrome even when the layout distributed nothing: it IS the render, and a
80
+ // Dimmed, frozen root would be a canvas nobody can touch.
81
+ chrome: path.length > 0 && !wrapsPageContent(path, ctx.pageContentPrefix),
82
+ kind: "layout",
83
+ layoutFile: typeof origin.file === "string" ? origin.file : "",
84
+ layoutPath: Array.isArray(origin.path) ? origin.path : [],
85
+ };
36
86
  }
37
87
 
38
88
  let mappedPath = path;
@@ -0,0 +1,292 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The surface REGISTRY — one stage record per pane, and nothing that knows what a pane contains.
4
+ *
5
+ * A pane is the unit of split, focus and zoom (§4.1), so it is also the unit of RENDER. The panels
6
+ * a pass mounted, the host element they mounted into, the mode that pass was drawn in and the full
7
+ * render a failed patch escalates to are all facts about ONE pane; they were app-level singletons
8
+ * only because the shell had a single stage.
9
+ *
10
+ * They could not stay that way. `classifyOps` gated a patch on `canvasPanels.every(p => p.ready)`
11
+ * and on one `getCanvasMode()`, and `escalateToFullRender` scheduled the one global render — so the
12
+ * moment a second stage exists, an edit typed into one pane would be refused because the OTHER
13
+ * pane's canvas was still mounting, and every escalation would re-render both. That is a
14
+ * correctness bug, not a performance one, which is why this lands before the second host rather
15
+ * than with it.
16
+ *
17
+ * A surface is addressed by pane id and nothing else. `panels/pane-grid.ts` builds a cell per pane
18
+ * and registers its stage as it goes.
19
+ *
20
+ * **Split from `canvas-surface.ts` for one reason: the WORKSPACE.** The helpers that answer "which
21
+ * tab is this pane showing" need `workspace/workspace.ts`, and `workspace.ts` needs
22
+ * `services/monaco-buffer.ts` — which in turn has to enumerate every stage's source buffer, because
23
+ * a close gate that asked only "the" Monaco would miss whichever pane it was not holding. That is a
24
+ * cycle. The store of records has no opinion about tabs, so it is the half that comes out.
25
+ */
26
+
27
+ import type { CanvasPanel } from "../types";
28
+ import type { MonacoSurface } from "../view";
29
+
30
+ /**
31
+ * The class name every pane's stage element carries.
32
+ *
33
+ * Declared here rather than in the grid because it is what the two genuinely document-level
34
+ * gestures test with `closest()` — "is this event over SOME stage" is a question about surfaces,
35
+ * not about the reconciler that happens to build them.
36
+ */
37
+ export const STAGE_CLASS = "pane-stage";
38
+
39
+ /** One pane's stage: where it renders, what it mounted there, and what it last drew. */
40
+ export interface CanvasSurface {
41
+ /** The pane this stage belongs to. Stable for the life of the pane. */
42
+ readonly paneId: string;
43
+ /**
44
+ * The element the pane's stage renders into.
45
+ *
46
+ * Typed non-null and initialised null, exactly as the shell refs in `store.ts` are: a surface is
47
+ * addressable before its host exists in the DOM, and every renderer runs after `initShellRefs`.
48
+ */
49
+ wrap: HTMLElement;
50
+ /** The panels the last pass mounted here. Mutated in place by the render, never reassigned. */
51
+ readonly panels: CanvasPanel[];
52
+ /**
53
+ * The effective canvas mode this stage last drew, or null before its first pass.
54
+ *
55
+ * Per-surface because it decides a TEARDOWN: a mode transition rebuilds the panel structure, and
56
+ * one pane switching from Design to Code must not make the other pane's next pass believe its own
57
+ * structure is stale.
58
+ */
59
+ prevCanvasMode: string | null;
60
+ /**
61
+ * The transformed element the pan/zoom modes draw into, or null in the modes that render none.
62
+ *
63
+ * Per-surface because it IS the stage's content. `view.panzoomWrap` held whichever pane painted
64
+ * last, so a fit computed for one pane wrote a transform onto the other's artboards.
65
+ */
66
+ panzoomWrap: HTMLElement | null;
67
+ /** The observer that re-centres this stage until the author pans it. One per stage. */
68
+ centerObserver: ResizeObserver | null;
69
+ /** Whether this stage still wants re-centring — cleared by the first deliberate pan. */
70
+ needsCenter: boolean;
71
+ panX: number;
72
+ panY: number;
73
+ /** The source view's Monaco, mounted into THIS stage. Two Code panes are two models. */
74
+ monacoEditor: MonacoSurface | null;
75
+ /**
76
+ * The Stylebook filter and Customized flag THIS stage's catalogue was last built for.
77
+ *
78
+ * A one-slot module cache, until two panes started evicting each other from it.
79
+ * `shell.stylebook.filter` is a render input for every pane, so both stages render in the same
80
+ * frame: the first stored the new filter and rebuilt, the second compared against a slot the
81
+ * first had already advanced, found "nothing changed", and returned through the cheap
82
+ * `postStyleUpdateToStylebookHosts` path with its catalogue still listing the previous filter's
83
+ * specimens. Which pane lost was decided by render order, and it stayed wrong until a mode
84
+ * change. `null` means "this stage has never drawn a catalogue", which is not the same as "drew
85
+ * one for the empty filter" — the first pass must always rebuild.
86
+ */
87
+ prevStylebookFilter: string | null;
88
+ prevStylebookCustomizedOnly: boolean | null;
89
+ /** Teardown for this stage's source-mode collab binding (unbind + release the lock). */
90
+ sourceCollabCleanup: (() => void) | null;
91
+ /**
92
+ * The generation of the last render pass this stage opened.
93
+ *
94
+ * Per-surface because it is a STALENESS comparison within one pane: a deferred artboard asks "is
95
+ * the pass I belong to still the current one HERE". While it was app-wide, pane A opening a pass
96
+ * invalidated pane B's in-flight one, so B's artboards 1..n never reached `ready` and every edit
97
+ * in B escalated to a full render, permanently. The VALUES are still globally unique — see
98
+ * {@link nextRenderGeneration} — because the iframe host caches a pass's resolved document by
99
+ * generation identity.
100
+ */
101
+ renderGeneration: number;
102
+ }
103
+
104
+ /**
105
+ * The next render generation, monotonic across every surface.
106
+ *
107
+ * Unique across panes even though the comparison is per-pane: `iframe-host.ts`'s
108
+ * `preparePassRender` caches the resolved+serialized document on `byDoc` keyed by the generation's
109
+ * identity, so two surfaces reusing the number would thrash that cache — and the frame's own
110
+ * `latestGen` drop rule would start discarding the other pane's live renders.
111
+ */
112
+ let _nextGen = 0;
113
+
114
+ /** Take the next globally-unique render generation. */
115
+ export function nextRenderGeneration(): number {
116
+ _nextGen += 1;
117
+ return _nextGen;
118
+ }
119
+
120
+ const _surfaces = new Map<string, CanvasSurface>();
121
+
122
+ /**
123
+ * The surface record for a pane, created on first mention.
124
+ *
125
+ * Created rather than looked up so every reader has an answer before the host is attached — the
126
+ * same contract `store.ts`'s shell refs have, and what keeps `activeCanvasSurface()` non-nullable.
127
+ *
128
+ * @param {string} paneId
129
+ * @returns {CanvasSurface}
130
+ */
131
+ export function surfaceForPane(paneId: string): CanvasSurface {
132
+ let surface = _surfaces.get(paneId);
133
+ if (!surface) {
134
+ surface = freshSurface(paneId);
135
+ _surfaces.set(paneId, surface);
136
+ }
137
+ return surface;
138
+ }
139
+
140
+ /** A surface record with nothing in it. One place the per-stage defaults are written down. */
141
+ function freshSurface(paneId: string): CanvasSurface {
142
+ return {
143
+ centerObserver: null,
144
+ monacoEditor: null,
145
+ needsCenter: true,
146
+ panX: 0,
147
+ panY: 0,
148
+ panels: [],
149
+ paneId,
150
+ panzoomWrap: null,
151
+ prevCanvasMode: null,
152
+ prevStylebookCustomizedOnly: null,
153
+ prevStylebookFilter: null,
154
+ renderGeneration: 0,
155
+ sourceCollabCleanup: null,
156
+ wrap: null as unknown as HTMLElement,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Every registered surface, in registration order. The grid's inventory, and the tests'.
162
+ *
163
+ * @returns {CanvasSurface[]}
164
+ */
165
+ export function allCanvasSurfaces(): CanvasSurface[] {
166
+ return [..._surfaces.values()];
167
+ }
168
+
169
+ /**
170
+ * The stage `node` sits inside, or null when it is outside every one of them.
171
+ *
172
+ * The N-stage replacement for `canvasWrap.contains(target)`. Two document-level gestures — the
173
+ * browser-zoom block and the wheel forwarded out of a frame — need "which stage is this in", and
174
+ * with one stage they were allowed to spell it as "is it in THE stage".
175
+ *
176
+ * @param {Node | null} node
177
+ * @returns {CanvasSurface | null}
178
+ */
179
+ export function stageContaining(node: Node | null): CanvasSurface | null {
180
+ if (!node) {
181
+ return null;
182
+ }
183
+ for (const surface of _surfaces.values()) {
184
+ if (surface.wrap?.contains(node)) {
185
+ return surface;
186
+ }
187
+ }
188
+ return null;
189
+ }
190
+
191
+ /**
192
+ * Forget everything a surface's last pass mounted.
193
+ *
194
+ * Dropping the panel records is not enough: each artboard owns a render `EffectScope` (and the
195
+ * surgical-subtree scopes nested inside it) that goes on reacting to the document until it is
196
+ * stopped. The render path has always stopped them before emptying the array; a surface whose HOST
197
+ * changes hands has to do exactly the same, or every handover leaks one live scope per artboard —
198
+ * scopes that then repaint DOM belonging to a stage this pane no longer has.
199
+ */
200
+ export function releaseMountedPanels(surface: CanvasSurface): void {
201
+ for (const panel of surface.panels) {
202
+ panel.renderScope?.stop();
203
+ panel.renderScope = null;
204
+ }
205
+ surface.panels.length = 0;
206
+ surface.prevCanvasMode = null;
207
+ }
208
+
209
+ /**
210
+ * Attach a pane's stage element. Called once per host that exists in the shell.
211
+ *
212
+ * A fresh host carries nothing over: the panels named by the previous one are detached DOM and the
213
+ * mode it last drew describes a structure that is gone, so both are cleared here rather than by
214
+ * each caller remembering to.
215
+ *
216
+ * @param {string} paneId
217
+ * @param {HTMLElement} wrap
218
+ * @returns {CanvasSurface}
219
+ */
220
+ export function registerCanvasSurface(paneId: string, wrap: HTMLElement): CanvasSurface {
221
+ const surface = surfaceForPane(paneId);
222
+ surface.wrap = wrap;
223
+ releaseMountedPanels(surface);
224
+ return surface;
225
+ }
226
+
227
+ /* There is no `moveCanvasStage`, and there is no handover.
228
+ §18.3 handed the shell's ONE stage to whichever pane had focus, because there was one stage. The
229
+ grid gives every pane its own, registered by `panels/pane-grid.ts` when the cell is built and
230
+ released when it is removed, so nothing moves and no pane is ever left describing DOM it does
231
+ not own. The rule that survives is {@link releaseMountedPanels}: a surface that loses its host
232
+ has to stop the render scope of every artboard it mounted, or each goes on repainting a stage
233
+ that is not there. */
234
+
235
+ /**
236
+ * Build a pane's surface record from scratch, replacing anything a previous pane of that id left.
237
+ *
238
+ * The grid calls this as it builds a cell, BEFORE the stage element exists — a pane is complete
239
+ * before it is published (§18.1 rule 1), and that includes the record every renderer resolves
240
+ * through.
241
+ *
242
+ * @param {string} paneId
243
+ * @returns {CanvasSurface}
244
+ */
245
+ export function createPaneSurface(paneId: string): CanvasSurface {
246
+ const existing = _surfaces.get(paneId);
247
+ if (existing) {
248
+ disposePaneSurface(paneId);
249
+ }
250
+ const surface = freshSurface(paneId);
251
+ _surfaces.set(paneId, surface);
252
+ return surface;
253
+ }
254
+
255
+ /**
256
+ * How a surface releases the editors and observers its renders created.
257
+ *
258
+ * Injected, because the teardown lives in `canvas/canvas-render.ts` — which imports this module —
259
+ * and a surface record must stay reachable from the dependency-light half of the graph. Same idiom
260
+ * as `setIframePatchEscalation`.
261
+ */
262
+ let _teardown: ((surface: CanvasSurface) => void) | null = null;
263
+
264
+ /** Register the per-surface teardown. Called once, at import time, by `canvas-render.ts`. */
265
+ export function setSurfaceTeardown(fn: ((surface: CanvasSurface) => void) | null): void {
266
+ _teardown = fn;
267
+ }
268
+
269
+ /**
270
+ * Release everything a pane's stage owns and forget the record.
271
+ *
272
+ * Ordered: the render-owned surfaces first (through the injected teardown — Monaco, the collab
273
+ * binding, the centering observer), then the artboards' render scopes, then the record. The DOM is
274
+ * the caller's: `pane-grid.ts` removes the cell, and `iframe-host.ts`'s `releaseCanvasHosts` tears
275
+ * the frames down BEFORE the removal, while they are still reachable from the stage.
276
+ *
277
+ * @param {string} paneId
278
+ */
279
+ export function disposePaneSurface(paneId: string): void {
280
+ const surface = _surfaces.get(paneId);
281
+ if (!surface) {
282
+ return;
283
+ }
284
+ _teardown?.(surface);
285
+ releaseMountedPanels(surface);
286
+ unregisterCanvasSurface(paneId);
287
+ }
288
+
289
+ /** Forget a pane's surface record without releasing anything. Tests, and the app reset. */
290
+ export function unregisterCanvasSurface(paneId: string): void {
291
+ _surfaces.delete(paneId);
292
+ }