@jxsuite/studio 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
  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 +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  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 -120
  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 +75 -2
  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 +113 -0
  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-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  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-2c188n41.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-hhj92gcr.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
@@ -0,0 +1,252 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Library previews — a live document render is expensive, so it is capped, lazy, and evictable.
4
+ *
5
+ * Four limits, each answering a different way the old Manage view fell over on a large project:
6
+ *
7
+ * 1. **Lazy.** A preview is built when its card INTERSECTS the viewport, not when the card is created.
8
+ * Scrolling a 300-item Library therefore builds a preview per card actually looked at.
9
+ * 2. **Capped.** {@link createPreviewCache} is an LRU with a hard limit. The predecessor's
10
+ * `_previewCache` was an unbounded `Map<string, HTMLElement>` of live runtime subtrees, so a
11
+ * long browse leaked every document it had ever shown until the tab closed.
12
+ * 3. **Cancellable.** A preview whose card left the window before its read resolved is dropped on
13
+ * arrival rather than appended to a detached node.
14
+ * 4. **Released.** The visibility gate keeps a set too, and it is the same leak one level down: a card
15
+ * observed and then scrolled past before it ever intersected was retained for the life of the
16
+ * pane. {@link PreviewObserver.releaseDetached} is how the window gives those back.
17
+ *
18
+ * The renders themselves go through `platform.readFile`, which the PAL counts, so `probe.idle()`
19
+ * already knows a preview is in flight without this module declaring an idle source of its own.
20
+ */
21
+
22
+ import { buildScope, renderNode, setSkipServerFunctions } from "@jxsuite/runtime";
23
+ import { componentRegistry } from "../files/components";
24
+ import { getPlatform } from "../platform";
25
+ import { loadFormats, formatForPath } from "../format/format-host";
26
+ import { parseSourceForPath } from "../files/file-ops";
27
+ import { renderComponentPreview } from "../panels/component-preview";
28
+ import type { ComponentEntry } from "../files/components";
29
+ import type { JxDocument } from "@jxsuite/schema/types";
30
+
31
+ /**
32
+ * How many rendered previews stay alive at once.
33
+ *
34
+ * **The cap must exceed one window, or it thrashes against itself.** The first value tried here was
35
+ * 48, and the 300-page measurement caught it immediately: a 1180×700 pane of cards renders 55 items
36
+ * (11 rows of 5, overscan included), so the cache evicted documents that were still on screen and
37
+ * re-rendered them on the next repaint — 560 reads for 300 documents. 150 clears a nine-column,
38
+ * twelve-row window on a wide display with room to spare, so eviction is only ever reached by
39
+ * SCROLLING, which is what an LRU is for.
40
+ *
41
+ * It remains a fixed cost rather than a function of project size, which is the whole point: the
42
+ * predecessor's cache was unbounded, so a long browse retained every document it had ever shown.
43
+ */
44
+ export const PREVIEW_CACHE_LIMIT = 150;
45
+
46
+ export interface PreviewCache {
47
+ /** Fetch and mark as most-recently-used. */
48
+ get: (key: string) => HTMLElement | undefined;
49
+ /** Insert, evicting the least-recently-used entry when over the limit. */
50
+ set: (key: string, value: HTMLElement) => void;
51
+ has: (key: string) => boolean;
52
+ /** Live entry count — what the perf test asserts stays bounded. */
53
+ size: () => number;
54
+ /** Keys, least-recently-used first. */
55
+ keys: () => string[];
56
+ clear: () => void;
57
+ }
58
+
59
+ /**
60
+ * A least-recently-used cache over rendered previews.
61
+ *
62
+ * `Map` iterates in insertion order, so re-inserting on read is the whole LRU: the oldest key is
63
+ * always the first one the iterator yields. Evicted subtrees are removed from the DOM as they go —
64
+ * an evicted preview that stayed attached would keep its runtime alive and defeat the cap.
65
+ */
66
+ export function createPreviewCache(limit: number = PREVIEW_CACHE_LIMIT): PreviewCache {
67
+ const entries = new Map<string, HTMLElement>();
68
+
69
+ function evict() {
70
+ // `size > limit` guarantees a first key, so the iterator result is never `done` here.
71
+ while (entries.size > limit) {
72
+ const oldest = entries.keys().next().value as string;
73
+ const stale = entries.get(oldest);
74
+ entries.delete(oldest);
75
+ stale?.remove();
76
+ }
77
+ }
78
+
79
+ return {
80
+ clear() {
81
+ for (const element of entries.values()) {
82
+ element.remove();
83
+ }
84
+ entries.clear();
85
+ },
86
+ get(key) {
87
+ const value = entries.get(key);
88
+ if (value !== undefined) {
89
+ entries.delete(key);
90
+ entries.set(key, value);
91
+ }
92
+ return value;
93
+ },
94
+ has: (key) => entries.has(key),
95
+ keys: () => [...entries.keys()],
96
+ set(key, value) {
97
+ entries.delete(key);
98
+ entries.set(key, value);
99
+ evict();
100
+ },
101
+ size: () => entries.size,
102
+ };
103
+ }
104
+
105
+ // ─── Rendering one preview ───────────────────────────────────────────────────
106
+
107
+ /**
108
+ * Render a page/layout/content document to a detached element, or null when it cannot be rendered.
109
+ *
110
+ * Returning null rather than throwing is deliberate: a document that fails to parse is a real state
111
+ * of the project, and the card shows a document glyph for it. The Problems list is for failures the
112
+ * author must ACT on; an in-progress file that does not parse yet is not one.
113
+ */
114
+ export async function renderDocPreview(filePath: string): Promise<HTMLElement | null> {
115
+ try {
116
+ const content = await getPlatform().readFile(filePath);
117
+ setSkipServerFunctions(true);
118
+ await loadFormats();
119
+ let document_: JxDocument;
120
+ if (formatForPath(filePath)) {
121
+ const parsed = await parseSourceForPath(filePath, content);
122
+ document_ = parsed.document as JxDocument;
123
+ } else {
124
+ document_ = JSON.parse(content) as JxDocument;
125
+ }
126
+ const scope = await buildScope(document_, {}, location.href);
127
+ const element = renderNode(document_, scope);
128
+ return element instanceof HTMLElement ? element : null;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * The preview for one file — a registered component's own preview when there is one, the document
136
+ * render otherwise. Cached; a cache hit does no work at all.
137
+ */
138
+ export async function previewFor(
139
+ path: string,
140
+ cache: PreviewCache,
141
+ ): Promise<HTMLElement | undefined> {
142
+ const cached = cache.get(path);
143
+ if (cached) {
144
+ return cached;
145
+ }
146
+ let rendered: HTMLElement | null | undefined;
147
+ try {
148
+ const component = componentRegistry.find((c: ComponentEntry) => c.path === path);
149
+ rendered = component
150
+ ? ((await renderComponentPreview(component)) as HTMLElement | undefined)
151
+ : await renderDocPreview(path);
152
+ } catch {
153
+ return undefined;
154
+ }
155
+ if (!rendered) {
156
+ return undefined;
157
+ }
158
+ cache.set(path, rendered);
159
+ return rendered;
160
+ }
161
+
162
+ // ─── Visibility ──────────────────────────────────────────────────────────────
163
+
164
+ export interface PreviewObserver {
165
+ /** Start watching a card. Safe to call repeatedly for the same element. */
166
+ observe: (element: Element) => void;
167
+ /**
168
+ * Stop watching every card the document no longer contains; answers how many went.
169
+ *
170
+ * The caller is whoever re-renders the window: once lit has committed, a card outside the new
171
+ * window is detached, and an observation of a detached node can never fire again. It is pure cost
172
+ * — the browser still walks it on every scroll frame — and it is unreachable garbage that pins
173
+ * the card's whole subtree.
174
+ *
175
+ * **The one release path, deliberately.** There is no per-element `unobserve` here: the previous
176
+ * shape had one, nothing ever called it, and "a card left the window" is a fact the WINDOW knows
177
+ * and an individual card never does. One caller, at one moment, over the whole set.
178
+ */
179
+ releaseDetached: () => number;
180
+ destroy: () => void;
181
+ }
182
+
183
+ /**
184
+ * Watch cards for viewport entry and report each one ONCE.
185
+ *
186
+ * **Every observation is owned here, not by the caller.** The first version of this module let the
187
+ * intersect callback be the only release path, which is only ever taken by a card that actually
188
+ * came into view: scroll fast enough that the callback never runs and nothing is ever released.
189
+ * Four sweeps of the same 300-page list retained 805 → 1310 → 1815 → 2320 cards with zero
190
+ * `unobserve` calls, every one of them a node no longer in the document — the very leak the LRU
191
+ * above was written to end, one level down. So the `watched` set is kept here, and
192
+ * {@link PreviewObserver.releaseDetached} sweeps it; `tests/library-observer-leak.test.ts` is the
193
+ * measurement, and it fails on the shape this replaced.
194
+ *
195
+ * Where `IntersectionObserver` is unavailable — happy-dom, and any host that has not implemented it
196
+ * — every observed element is reported immediately. That is the honest degradation: the window
197
+ * already bounds how many cards exist, so "all of them" is at most one screen plus the overscan,
198
+ * and a Library that silently showed no previews would look broken rather than slow. Nothing is
199
+ * retained on that path, so there is nothing for the sweep to find.
200
+ */
201
+ export function createPreviewObserver(onVisible: (element: Element) => void): PreviewObserver {
202
+ if (typeof IntersectionObserver !== "function") {
203
+ return {
204
+ destroy() {
205
+ /* Nothing to disconnect. */
206
+ },
207
+ observe: (element) => onVisible(element),
208
+ releaseDetached: () => 0,
209
+ };
210
+ }
211
+ const watched = new Set<Element>();
212
+ const observer = new IntersectionObserver(
213
+ (records) => {
214
+ for (const record of records) {
215
+ if (record.isIntersecting) {
216
+ watched.delete(record.target);
217
+ observer.unobserve(record.target);
218
+ onVisible(record.target);
219
+ }
220
+ }
221
+ },
222
+ // One card-height of lead-in, so a preview is usually ready by the time it is looked at.
223
+ { rootMargin: "200px" },
224
+ );
225
+ return {
226
+ destroy() {
227
+ observer.disconnect();
228
+ watched.clear();
229
+ },
230
+ observe(element) {
231
+ // Re-observing is a no-op in the browser, but the repaint that follows every scroll frame
232
+ // Hands the same slot back, and a call per card per frame is work nobody needs.
233
+ if (watched.has(element)) {
234
+ return;
235
+ }
236
+ watched.add(element);
237
+ observer.observe(element);
238
+ },
239
+ releaseDetached() {
240
+ let released = 0;
241
+ // Deleting the current key during a `Set` iteration is defined; later keys are still visited.
242
+ for (const element of watched) {
243
+ if (!element.isConnected) {
244
+ watched.delete(element);
245
+ observer.unobserve(element);
246
+ released += 1;
247
+ }
248
+ }
249
+ return released;
250
+ },
251
+ };
252
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The Library as a {@link GridSource}.
3
+ *
4
+ * The Library is one more source over the same contract the CSV, collection, pages and connector
5
+ * grids already use, which is what makes Table a real grid rather than a fifth hand-written table:
6
+ * columns come from {@link libraryColumns}, rows are the scan, and every layout — Cards, Media,
7
+ * Calendar, Board — draws the SAME rows through a different arrangement. A layout switch never
8
+ * re-reads the project.
9
+ *
10
+ * It is a **read-only** source, and says so rather than pretending. Renaming, duplicating and
11
+ * deleting a file are real operations with real consequences (a rename rewrites references; a
12
+ * delete needs the reference count in its confirmation), and they belong to the commands that
13
+ * already do that work correctly. A grid cell that silently renamed a file on blur would bypass
14
+ * both. {@link LibrarySource.commit} therefore refuses, naming the surface that can.
15
+ */
16
+
17
+ import { getPlatform } from "../platform";
18
+ import { projectState } from "../store";
19
+ import { makeGridTabId } from "../grid/grid-source";
20
+ import { scanLibrary } from "./library-model";
21
+ import type { LibraryFile, LibraryScan, ScanFailure } from "./library-model";
22
+ import type {
23
+ CommitResult,
24
+ GridColumn,
25
+ GridEditBatch,
26
+ GridRow,
27
+ GridRowsResult,
28
+ GridSource,
29
+ } from "../grid/grid-source";
30
+
31
+ /** Why a cell edit is refused — printed by the grid, so the author is told where to go instead. */
32
+ export const LIBRARY_READ_ONLY =
33
+ "The Library lists files; it does not edit them. Rename, duplicate or delete from the file's " +
34
+ "context menu, or open the file to edit it.";
35
+
36
+ /** The Table layout's columns. `path` is the row key and is never editable. */
37
+ export function libraryColumns(): GridColumn[] {
38
+ return [
39
+ { editable: false, field: "name", kind: "string", pk: true, title: "Name", widthHint: 260 },
40
+ { editable: false, field: "category", kind: "string", title: "Category", widthHint: 120 },
41
+ { editable: false, field: "locale", kind: "string", title: "Locale", widthHint: 90 },
42
+ { editable: false, field: "type", kind: "string", title: "Type", widthHint: 120 },
43
+ { editable: false, field: "size", kind: "number", title: "Size", widthHint: 90 },
44
+ { editable: false, field: "modified", kind: "date", title: "Modified", widthHint: 160 },
45
+ { editable: false, field: "path", kind: "readonly", title: "Path", widthHint: 320 },
46
+ ];
47
+ }
48
+
49
+ /** One scanned file as a grid row. */
50
+ export function libraryRow(file: LibraryFile): GridRow {
51
+ return {
52
+ cells: {
53
+ category: file.category,
54
+ // Null, not "": the platform reported no locale directory for this file, and a grid cell
55
+ // Distinguishes "no value" from "the empty string" everywhere else in this contract.
56
+ locale: file.locale ?? null,
57
+ modified: file.modified ?? null,
58
+ name: file.name,
59
+ path: file.path,
60
+ size: file.size ?? null,
61
+ type: file.type,
62
+ },
63
+ key: file.path,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * A Library source, plus the two questions a grid source cannot ask but the Library must answer:
69
+ * what the scan could not read, and the files behind the rows (the non-table layouts want the typed
70
+ * record, not a cell bag).
71
+ */
72
+ export interface LibrarySource extends GridSource {
73
+ /** The scanned files, in path order. Empty before the first {@link GridSource.rows} call. */
74
+ files: () => readonly LibraryFile[];
75
+ /** Directories the last scan could not read. NON-EMPTY MEANS THE LISTING IS INCOMPLETE. */
76
+ failures: () => readonly ScanFailure[];
77
+ /** Whether a scan has completed at least once — "not loaded yet" is not "empty". */
78
+ scanned: () => boolean;
79
+ }
80
+
81
+ /** The Library's tab id. One Library per window; category and layout are view state, not identity. */
82
+ export function libraryTabId(): string {
83
+ return makeGridTabId({ kind: "library" });
84
+ }
85
+
86
+ /**
87
+ * Build the Library source. The scan is lazy: nothing is read until the first `rows()`.
88
+ *
89
+ * @param {() => readonly string[]} [dirs] Project directories to scan, injectable for tests and for
90
+ * the perf measurement; defaults to the open project's own list.
91
+ */
92
+ export function createLibrarySource(dirs?: () => readonly string[]): LibrarySource {
93
+ let scan: LibraryScan | null = null;
94
+ let inFlight: Promise<LibraryScan> | null = null;
95
+
96
+ const projectDirs = dirs ?? (() => (projectState?.projectDirs ?? []) as readonly string[]);
97
+
98
+ async function ensureScan(): Promise<LibraryScan> {
99
+ if (scan) {
100
+ return scan;
101
+ }
102
+ // Coalesce: five layouts and a command can all ask at once on the first paint, and the answer
103
+ // Is one walk of the project, not six.
104
+ inFlight ??= scanLibrary(projectDirs(), getPlatform()).then((result) => {
105
+ scan = result;
106
+ inFlight = null;
107
+ return result;
108
+ });
109
+ return inFlight;
110
+ }
111
+
112
+ return {
113
+ capabilities: { delete: false, insert: false, remotePaging: false, remoteSort: false },
114
+ columns: () => Promise.resolve(libraryColumns()),
115
+ commit(batch: GridEditBatch): Promise<CommitResult> {
116
+ return Promise.resolve({
117
+ cells: batch.cells.map((cell) => ({
118
+ error: LIBRARY_READ_ONLY,
119
+ field: cell.field,
120
+ ok: false,
121
+ rowKey: cell.rowKey,
122
+ })),
123
+ deletes: batch.deletes.map((row) => ({
124
+ error: LIBRARY_READ_ONLY,
125
+ ok: false,
126
+ rowKey: row.rowKey,
127
+ })),
128
+ inserts: batch.inserts.map((row) => ({
129
+ error: LIBRARY_READ_ONLY,
130
+ ok: false,
131
+ tempKey: row.tempKey,
132
+ })),
133
+ });
134
+ },
135
+ failures: () => scan?.failures ?? [],
136
+ files: () => scan?.files ?? [],
137
+ id: libraryTabId(),
138
+ label: "Library",
139
+ async refresh() {
140
+ scan = null;
141
+ inFlight = null;
142
+ await ensureScan();
143
+ },
144
+ async rows(): Promise<GridRowsResult> {
145
+ const result = await ensureScan();
146
+ return { rows: result.files.map((file) => libraryRow(file)), total: result.files.length };
147
+ },
148
+ scanned: () => scan !== null,
149
+ };
150
+ }
@@ -5,9 +5,12 @@
5
5
  * multiple canvas-related modules: element lookup, zoom, panel resolution, inline bubbling.
6
6
  */
7
7
 
8
- import { canvasPanels, getNodeAtPath, parentElementPath } from "../store";
9
- import { activeTab } from "../workspace/workspace";
8
+ import { getNodeAtPath, parentElementPath } from "../store";
9
+ import { displayTagName } from "@jxsuite/schema/guards";
10
+ import { activeCanvasSurface, activeMediaOfPane } from "./canvas-surface";
10
11
  import { isInlineInContext } from "../editor/inline-edit";
12
+ import type { CanvasSurface } from "./canvas-surface";
13
+ import type { CanvasPanel } from "../types";
11
14
  import type { JxPath } from "../state";
12
15
  import type { JxMutableNode } from "@jxsuite/schema/types";
13
16
 
@@ -22,15 +25,26 @@ export function panelMediaToActiveMedia(mediaName: string | null | undefined) {
22
25
  return !mediaName || mediaName === "base" ? null : mediaName;
23
26
  }
24
27
 
25
- /** Return the active canvas panel based on the current activeMedia setting. */
26
- export function getActivePanel() {
28
+ /**
29
+ * The active canvas panel of ONE pane's stage, from that pane's own `activeMedia`.
30
+ *
31
+ * The breakpoint a stage is showing is a fact about the tab in it — `session.ui.activeMedia` — so
32
+ * this reads it from `surface.paneId`'s tab and never from the focus. {@link getActivePanel} is this
33
+ * function applied to the focused pane, which is what "the active panel" means when nobody says
34
+ * which one.
35
+ *
36
+ * @param {CanvasSurface} surface
37
+ * @returns {CanvasPanel | null}
38
+ */
39
+ export function panelOfSurface(surface: CanvasSurface): CanvasPanel | null {
40
+ const canvasPanels = surface.panels;
27
41
  if (canvasPanels.length === 0) {
28
42
  return null;
29
43
  }
30
44
  if (canvasPanels.length === 1) {
31
- return canvasPanels[0];
45
+ return canvasPanels[0]!;
32
46
  }
33
- const activeMedia = activeTab.value?.session.ui.activeMedia ?? null;
47
+ const activeMedia = activeMediaOfPane(surface.paneId);
34
48
  for (const p of canvasPanels) {
35
49
  if (activeMedia === null && (p.mediaName === "base" || p.mediaName === null)) {
36
50
  return p;
@@ -39,7 +53,17 @@ export function getActivePanel() {
39
53
  return p;
40
54
  }
41
55
  }
42
- return canvasPanels[0];
56
+ return canvasPanels[0]!;
57
+ }
58
+
59
+ /**
60
+ * Return the active canvas panel of the FOCUSED pane, based on its activeMedia setting.
61
+ *
62
+ * Panels belong to a pane's stage (`canvas-surface.ts`), so "the active panel" is only answerable
63
+ * relative to one — this is the pane whose artboards the person is looking at.
64
+ */
65
+ export function getActivePanel() {
66
+ return panelOfSurface(activeCanvasSurface());
43
67
  }
44
68
 
45
69
  /**
@@ -61,8 +85,8 @@ export function bubbleInlinePath(doc: JxMutableNode | undefined, path: JxPath) {
61
85
  if (!node || !parentNode) {
62
86
  break;
63
87
  }
64
- const childTag = (node.tagName ?? "div").toLowerCase();
65
- const parentTag = (parentNode.tagName ?? "div").toLowerCase();
88
+ const childTag = (displayTagName(node.tagName) || "div").toLowerCase();
89
+ const parentTag = (displayTagName(parentNode.tagName) || "div").toLowerCase();
66
90
  if (!isInlineInContext(childTag, parentTag)) {
67
91
  break;
68
92
  }
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  import { projectState, stripEventHandlers } from "../store";
10
- import { activeTab } from "../workspace/workspace";
10
+ import { displayTagName } from "@jxsuite/schema/guards";
11
+ import { canvasModeOfTab } from "./canvas-surface";
11
12
  import { toRaw } from "../reactivity";
12
13
  import {
13
14
  distributePageIntoLayout,
@@ -29,13 +30,13 @@ import {
29
30
  import { isComponentDoc, substitutePreviewProps } from "../component-props";
30
31
  import { rewriteContentAssets } from "./content-assets";
31
32
 
32
- import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
33
+ import type { JxElement, JxMutableNode, JxPath } from "@jxsuite/schema/types";
33
34
  import type { ComponentEntry } from "../files/components.js";
34
35
  import type { ContentSectionEntry } from "../types";
36
+ import type { LayoutMarker } from "./path-mapping";
37
+ import type { Tab } from "../tabs/tab";
35
38
  import type { WireMapperCtx } from "./iframe-protocol";
36
39
 
37
- let _ctx: { getCanvasMode: () => string } | null = null;
38
-
39
40
  /**
40
41
  * Walk the merged document tree to find where page children were distributed into the layout slot.
41
42
  * Returns the `prefix` path to the container whose children hold the page content (first container
@@ -77,58 +78,71 @@ function findPageContentPrefix(
77
78
  }
78
79
 
79
80
  /**
80
- * Recursively mark all nodes in a layout doc tree with $__layout: true so we can identify which
81
- * rendered DOM elements came from the layout vs page content.
81
+ * Recursively mark every node of a layout doc tree with its ORIGIN — `$__layout: { file, path }` —
82
+ * so the renderer can both tell layout nodes apart from page content and say which node of which
83
+ * file each one is. The marker used to be a bare `true`, which made a layout node identifiable but
84
+ * unaddressable: the canvas could tell you had clicked something it could not edit, but not what
85
+ * you had clicked or where to go to edit it. `path` is the node's own path inside `file`, so the
86
+ * editor can open the layout with that node selected. See {@link LayoutMarker}.
82
87
  */
83
- function markLayoutNodes(node: JxMutableNode) {
88
+ export function markLayoutNodes(node: JxMutableNode, file: string, path: JxPath = []) {
84
89
  if (!node || typeof node !== "object") {
85
90
  return;
86
91
  }
87
- node.$__layout = true;
92
+ node.$__layout = { file, path } satisfies LayoutMarker;
88
93
  if (Array.isArray(node.children)) {
89
- for (const child of node.children) {
94
+ for (const [i, child] of node.children.entries()) {
90
95
  if (typeof child === "string") {
91
96
  continue;
92
97
  }
93
- markLayoutNodes(child);
98
+ markLayoutNodes(child, file, [...path, "children", i]);
94
99
  }
95
100
  }
96
- if (node.$elements) {
97
- for (const el of node.$elements) {
101
+ if (Array.isArray(node.$elements)) {
102
+ for (const [i, el] of node.$elements.entries()) {
98
103
  if (typeof el !== "string") {
99
- markLayoutNodes(el as JxMutableNode);
104
+ markLayoutNodes(el as JxMutableNode, file, [...path, "$elements", i]);
100
105
  }
101
106
  }
102
107
  }
103
108
  }
104
109
 
105
- /**
106
- * Initialize the canvas live render module.
107
- *
108
- * @param {{
109
- * getCanvasMode: () => string;
110
- * }} ctx
111
- */
112
- export function initCanvasLiveRender(ctx: { getCanvasMode: () => string }) {
113
- _ctx = ctx;
114
- }
115
-
116
110
  /**
117
111
  * Resolve a document into the form the iframe canvas renders: layout-distributed, edit-transformed,
118
112
  * with components/imports/$media/$head merged in, plus the path-mapper context and site style. This
119
113
  * is the "parent resolves, iframe renders" split — the realm-specific work (defineElement,
120
114
  * injecting $head/site-style into the DOM, buildScope/renderNode) happens inside the iframe from
121
115
  * this result.
116
+ *
117
+ * **`tab` is the tab the render is FOR, and it is a parameter because it is not "the active tab".**
118
+ * This function opened with `const tab = activeTab.value` and took its mode from an injected
119
+ * `getCanvasMode()` — the focused pane's, one layer down — which threw away everything the caller
120
+ * had already resolved correctly: `canvas-render.ts` picks the document with
121
+ * `tabOfPane(surface.paneId)` and threads that tab's id through `mountIframeCanvas` →
122
+ * `preparePassRender`. Six values came from the wrong tab whenever the pane being drawn was not the
123
+ * focused one: `documentPath` (hence `docBase` and the `isPage` test), `showLayout`,
124
+ * `previewParams`, `previewProps`, and the composed `canvasMode`.
125
+ *
126
+ * The mode is the one with teeth. `mountIframeCanvas` ends with `setHostPreview(state, message.mode
127
+ * === "preview")`, so a side pane in Edit whose neighbour was in Preview got a PREVIEW frame — no
128
+ * overlay, no editing messages honoured — and a pane being previewed beside an editing one got an
129
+ * editable frame for a document nobody was editing. `canvasModeOfTab(tab)` is the same composition
130
+ * every other gate reads, applied to the tab that is actually being drawn.
131
+ *
132
+ * @param {JxMutableNode} doc
133
+ * @param {Tab | null} tab — the tab whose document and view settings this render is of.
122
134
  */
123
- export async function resolveCanvasDocument(doc: JxMutableNode): Promise<{
135
+ export async function resolveCanvasDocument(
136
+ doc: JxMutableNode,
137
+ tab: Tab | null,
138
+ ): Promise<{
124
139
  renderDoc: JxMutableNode;
125
140
  docBase: string | undefined;
126
141
  mapperCtx: WireMapperCtx;
127
142
  siteStyle: Record<string, unknown> | null;
128
143
  }> {
129
- const tab = activeTab.value;
130
144
  const S = { documentPath: tab?.documentPath, mode: tab?.doc.mode };
131
- const canvasMode = _ctx!.getCanvasMode();
145
+ const canvasMode = canvasModeOfTab(tab);
132
146
 
133
147
  let renderDoc =
134
148
  canvasMode === "preview"
@@ -150,7 +164,9 @@ export async function resolveCanvasDocument(doc: JxMutableNode): Promise<{
150
164
  if (layoutPath) {
151
165
  const layoutDoc = (await resolveLayoutDoc(layoutPath)) as JxMutableNode | null;
152
166
  if (layoutDoc) {
153
- markLayoutNodes(layoutDoc);
167
+ // The SAME normalization resolveLayoutDoc reads the file with, so the marker's `file` is a
168
+ // Path the editor can hand straight to navigateToComponent when the author asks to open it.
169
+ markLayoutNodes(layoutDoc, layoutPath.replace(/^\.\//, ""));
154
170
  const pageForSlots = canvasMode === "preview" ? structuredClone(toRaw(doc)) : renderDoc;
155
171
  const merged = distributePageIntoLayout(layoutDoc, pageForSlots);
156
172
  renderDoc =
@@ -262,7 +278,7 @@ export async function resolveCanvasDocument(doc: JxMutableNode): Promise<{
262
278
  return tags;
263
279
  }
264
280
  if (node.tagName) {
265
- tags.add(node.tagName);
281
+ tags.add(displayTagName(node.tagName));
266
282
  }
267
283
  if (Array.isArray(node.children)) {
268
284
  for (const child of node.children) {