@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
@@ -4,12 +4,16 @@
4
4
  *
5
5
  * When a project has a project.json, its $media and style cascade into every file. File-level
6
6
  * definitions merge on top (file wins on conflict).
7
+ *
8
+ * @docs framework/site/i18n
7
9
  */
8
10
 
9
- import { projectState, requireProjectState, setProjectState } from "./store";
11
+ import { projectState } from "./store";
10
12
  import { getPlatform } from "./platform";
11
- import { setWorkspaceProject, workspace } from "./workspace/workspace";
13
+ import { commitProjectConfig } from "./tabs/project-config";
12
14
 
15
+ import { resolveI18n } from "@jxsuite/schema/locale";
16
+ import type { ResolvedI18n } from "@jxsuite/schema/locale";
13
17
  import type {
14
18
  JxElement,
15
19
  JxHeadEntry,
@@ -133,6 +137,27 @@ export function getEffectiveHead(docHead?: JxHeadEntry[]) {
133
137
  return merged;
134
138
  }
135
139
 
140
+ /**
141
+ * The project's resolved locales, or null when it declares none.
142
+ *
143
+ * The resolution is `@jxsuite/schema/locale`'s own — literally the function the compiler runs — so
144
+ * a tag Studio offers is a tag the build accepts, and the two cannot drift into disagreeing about
145
+ * what `EN-us` means or which locales exist.
146
+ *
147
+ * Errors are dropped here on purpose. Every helper in this file answers a render, and a render has
148
+ * nowhere to put a sentence; `settings/locales-section.ts` is where a malformed tag is refused with
149
+ * words, before it can reach the file.
150
+ *
151
+ * Called at render time rather than cached: `projectState` is a plain module-level binding replaced
152
+ * wholesale on a project switch, so a cached answer would outlive the project it described.
153
+ *
154
+ * @returns {ResolvedI18n | null}
155
+ */
156
+ export function getEffectiveLocales(): ResolvedI18n | null {
157
+ const config = projectState?.projectConfig;
158
+ return config ? resolveI18n(config).i18n : null;
159
+ }
160
+
136
161
  // ─── Layout resolution ──────────────────────────────────────────────────────
137
162
 
138
163
  const layoutCache = new Map<string, JxMutableNode>();
@@ -259,26 +284,26 @@ function fillSlots(
259
284
  }
260
285
 
261
286
  /**
262
- * Update the project's project.json with a partial patch and persist to disk. A patch touching
263
- * `extensions` invalidates the format/extensions caches and refreshes the editor's per-project
264
- * schemas plus the contributed settings sections — the enabled-extension surface just changed.
287
+ * Merge a partial patch into the project's configuration and persist it.
288
+ *
289
+ * The patch-shaped door onto `tabs/project-config.ts` — the single chokepoint every configuration
290
+ * write goes through. It used to serialize and write `project.json` itself, which is how this file
291
+ * came to hold one of the two serializations in play, and how ten call sites came to change the
292
+ * file that defines the project with no history behind them.
293
+ *
294
+ * **It still rejects.** `settings/general-settings.ts` and `settings/contexts-section.ts` catch the
295
+ * rejection and park it on the control that caused it (§7.1's inline tier), and that is the right
296
+ * treatment for a field the author is looking at. The chokepoint ALSO files the failure as a
297
+ * Problem, because the eight other sites reach this through `void updateSiteConfig(...)` and used
298
+ * to drop it on the floor.
265
299
  *
266
- * @param {Partial<ProjectConfig>} patch - Fields to merge into the current projectConfig
300
+ * @param {Partial<ProjectConfig>} patch - Fields to merge into the current projectConfig; a value
301
+ * of `undefined` clears the key.
302
+ * @throws {unknown} The platform's rejection, when the write fails.
267
303
  */
268
304
  export async function updateSiteConfig(patch: Partial<ProjectConfig>) {
269
- const platform = getPlatform();
270
- const config = {
271
- ...requireProjectState().projectConfig,
272
- ...patch,
273
- } as ProjectConfig;
274
- await platform.writeFile("project.json", JSON.stringify(config, null, 2));
275
- setProjectState({ ...requireProjectState(), projectConfig: config });
276
- setWorkspaceProject(workspace.projectRoot, config);
277
- if ("extensions" in patch) {
278
- const { loadFormats, refreshExtensionUi, refreshFormats } =
279
- await import("./format/format-host");
280
- refreshFormats();
281
- void loadFormats();
282
- refreshExtensionUi(platform);
305
+ const result = await commitProjectConfig(patch);
306
+ if (!result.ok) {
307
+ throw result.error;
283
308
  }
284
309
  }
package/src/state.ts CHANGED
@@ -1,57 +1,22 @@
1
- /// <reference lib="dom" />
2
1
  /**
3
- * State.js — Builder state model and mutation API
2
+ * Document paths, the tree walk over them, and the project record.
4
3
  *
5
- * All state changes go through named mutation functions. State is immutable — every mutation
6
- * produces a new state object. History is a linear stack of { document, selection } snapshots.
4
+ * A `JxPath` addresses one node from the document root: `[]` is the root itself, `["children", 0]`
5
+ * its first child, `["children", 0, "children", 2]` that child's third. Every module that points at
6
+ * a node — selection, hover, the layers tree, a patch — spells it this way, which is why the
7
+ * comparison and ancestry predicates live here rather than beside any one of them.
7
8
  *
8
- * Path convention: [] = root document ['children', 0] = first child ['children', 0, 'children', 2]
9
- * = third child of first child
9
+ * The editable document and its history belong to a TAB (`tabs/tab.ts`) and its session lives on
10
+ * the workspace; nothing here holds mutable document state. The one exception is `projectState`,
11
+ * which outlives every document because it describes the folder they all come from.
10
12
  */
11
13
 
12
14
  import type { ProjectState } from "./types";
13
- import { isRef } from "@jxsuite/schema/guards";
15
+ import { displayTagName, isRef } from "@jxsuite/schema/guards";
14
16
  import type { JxMutableNode } from "@jxsuite/schema/types";
15
17
 
16
18
  export type JxPath = (string | number)[];
17
19
 
18
- interface HistorySnapshot {
19
- document: JxMutableNode;
20
- selection: JxPath | null;
21
- }
22
-
23
- export interface StudioState {
24
- document: JxMutableNode;
25
- selection: JxPath | null;
26
- hover: JxPath | null;
27
- history: HistorySnapshot[];
28
- historyIndex: number;
29
- dirty: boolean;
30
- fileHandle: FileSystemFileHandle | null;
31
- documentPath: string | null;
32
- documentStack: StudioStackFrame[];
33
- handlersSource: string | null;
34
- mode: string;
35
- content: { frontmatter: Record<string, unknown> };
36
- ui: Record<string, unknown>;
37
- canvas: {
38
- status: string;
39
- scope: Record<string, unknown> | null;
40
- error: string | null;
41
- };
42
- }
43
-
44
- interface StudioStackFrame {
45
- document: JxMutableNode;
46
- selection: JxPath | null;
47
- fileHandle: FileSystemFileHandle | null;
48
- documentPath: string | null;
49
- dirty: boolean;
50
- history: HistorySnapshot[];
51
- historyIndex: number;
52
- mode: string;
53
- }
54
-
55
20
  // ─── Path utilities ───────────────────────────────────────────────────────────
56
21
 
57
22
  /**
@@ -73,35 +38,6 @@ export function getNodeAtPath(doc: JxMutableNode, path: JxPath) {
73
38
  return node;
74
39
  }
75
40
 
76
- /**
77
- * Shallow-clone every node along `path` from root to target, leaving non-path subtrees as shared
78
- * references. Returns the new root and the mutable target node at the end of the path.
79
- *
80
- * Used by mutation functions for structural-sharing history: the old root becomes an immutable
81
- * snapshot (shared subtrees are never mutated), and the new root is the live document.
82
- */
83
- export function cloneAlongPath(
84
- doc: JxMutableNode,
85
- path: JxPath,
86
- ): { root: JxMutableNode; target: JxMutableNode } {
87
- const root = Array.isArray(doc) ? [...doc] : { ...doc };
88
- let node: Record<string | number, unknown> = root as Record<string | number, unknown>;
89
-
90
- for (const key of path) {
91
- const child = node[key];
92
- if (child == null) {
93
- return { root: root as JxMutableNode, target: node as JxMutableNode };
94
- }
95
- const cloned = Array.isArray(child)
96
- ? [...(child as unknown[])]
97
- : { ...(child as Record<string, unknown>) };
98
- node[key] = cloned;
99
- node = cloned as Record<string | number, unknown>;
100
- }
101
-
102
- return { root: root as JxMutableNode, target: node as JxMutableNode };
103
- }
104
-
105
41
  /**
106
42
  * The node's children when they are a static array (the edit-mode invariant); an empty array for
107
43
  * mapped-array or absent children.
@@ -300,7 +236,7 @@ function collectRows(
300
236
  }
301
237
 
302
238
  // Custom component instances without user-authored children are atomic in the layer tree
303
- if (doc.$props && (doc.tagName || "").includes("-") && !Array.isArray(doc.children)) {
239
+ if (doc.$props && displayTagName(doc.tagName).includes("-") && !Array.isArray(doc.children)) {
304
240
  return;
305
241
  }
306
242
 
@@ -372,7 +308,10 @@ export function nodeLabel(node: JxMutableNode | null) {
372
308
  const name = node.attributes?.name;
373
309
  return typeof name === "string" && name.trim() ? `slot: ${name.trim()}` : "slot";
374
310
  }
375
- const tag = node.tagName ?? "div";
311
+ /* `displayTagName`, because a tag may be CHOSEN rather than written: this label reached the
312
+ Outline row, the Inspector's header and the jump bar, and a raw read rendered `[object Object]`
313
+ in all three. `a|div` is the honest answer — one element, tag decided at creation. */
314
+ const tag = displayTagName(node.tagName) || "div";
376
315
  const suffix = node.$switch ? " ⇆" : "";
377
316
  if (typeof node.textContent === "string" && node.textContent.length > 0) {
378
317
  return `${tag} — ${node.textContent.slice(0, 24)}${suffix}`;
@@ -380,113 +319,6 @@ export function nodeLabel(node: JxMutableNode | null) {
380
319
  return tag + suffix;
381
320
  }
382
321
 
383
- // ─── State factory ────────────────────────────────────────────────────────────
384
-
385
- /**
386
- * @param {JxMutableNode} doc
387
- * @returns {StudioState}
388
- */
389
- export function createState(doc: JxMutableNode): StudioState {
390
- const initial = { document: doc, selection: null };
391
- return {
392
- canvas: {
393
- error: null, // Error message on failure
394
- scope: null, // $defs scope from runtime buildScope
395
- status: "idle", // "idle" | "loading" | "ready" | "error"
396
- },
397
- content: { frontmatter: {} }, // Frontmatter metadata for .md files
398
- dirty: false,
399
- document: doc,
400
- documentPath: null, // Root-relative path, e.g. "examples/markdown/blog.json"
401
- documentStack: [], // Frames for component navigation
402
- fileHandle: null,
403
- handlersSource: null,
404
- history: [initial],
405
- historyIndex: 0,
406
- hover: null,
407
- mode: "component", // 'component' | 'content'
408
- selection: null,
409
- ui: {
410
- activeMedia: null, // '--md' | null (base) — focused canvas/breakpoint
411
- activeSelector: null, // ':hover' | '.child' | null (base) — nested selector context
412
- editingFunction: null, // Null | { type: 'def', defName } | { type: 'event', path, eventKey }
413
- featureToggles: {}, // { '--dark': true } — non-size media toggles
414
- gitBranches: null, // { current, branches: [] }
415
- gitCommitMessage: "", // Commit message input
416
- gitDiffState: null,
417
- gitError: null, // Error message string
418
- gitLoading: false, // Loading indicator during async ops
419
- gitStatus: null, // { branch, ahead, behind, files: [] }
420
- inspectorSections: {}, // { identity: true, ... } — properties panel section open/closed state
421
- pendingInlineEdit: null, // Null | { path, mediaName } — deferred inline edit awaiting canvas readiness
422
- rightTab: "properties", // 'properties' | 'events' | 'style'
423
- settingsTab: "stylebook", // "stylebook" | "definitions" | "contentTypes"
424
- styleFilter: "", // Free-text filter for CSS property names
425
- styleFilterActive: false, // True = show only props with values set
426
- styleSections: {}, // { layout: true, ... } — section open/closed state
427
- styleShorthands: {}, // { padding: true, ... } — shorthand expand/collapse state
428
- stylebookCustomizedOnly: false, // Show only customized elements
429
- stylebookFilter: "", // Search filter text
430
- stylebookSelection: null, // Tag name string, e.g. "h1"
431
- stylebookTab: "elements", // "elements" | "variables"
432
- zoom: 1,
433
- },
434
- };
435
- }
436
-
437
- // ─── Doc/Session slice helpers ───────────────────────────────────────────────
438
-
439
- /**
440
- * Compose a flat StudioState from separate doc and session slices.
441
- *
442
- * @param {Partial<StudioState>} doc
443
- * @param {Partial<StudioState>} session
444
- * @returns {StudioState}
445
- */
446
- export function toFlat(doc: Partial<StudioState>, session: Partial<StudioState>) {
447
- return { ...doc, ...session } as StudioState;
448
- }
449
-
450
- /**
451
- * Decompose a flat StudioState into doc and session slices.
452
- *
453
- * @param {StudioState} S
454
- * @returns {{ doc: Partial<StudioState>; session: Partial<StudioState> }}
455
- */
456
- export function fromFlat(S: StudioState) {
457
- const {
458
- document,
459
- dirty,
460
- fileHandle,
461
- documentPath,
462
- documentStack,
463
- handlersSource,
464
- mode,
465
- content,
466
- history,
467
- historyIndex,
468
- selection,
469
- hover,
470
- ui,
471
- canvas,
472
- } = S;
473
- return {
474
- doc: {
475
- content,
476
- dirty,
477
- document,
478
- documentPath,
479
- documentStack,
480
- fileHandle,
481
- handlersSource,
482
- history,
483
- historyIndex,
484
- mode,
485
- },
486
- session: { canvas, hover, selection, ui },
487
- };
488
- }
489
-
490
322
  // ─── Project state (persists across document switches) ────────────────────────
491
323
  //
492
324
  // Shape: { root, name, projectRoot, isSiteProject, projectConfig,
@@ -510,96 +342,3 @@ export function setProjectState(ps: ProjectState | null) {
510
342
  export function requireProjectState() {
511
343
  return projectState as ProjectState;
512
344
  }
513
-
514
- // ─── Frontmatter mutation ───────────────────────────────────────────────────
515
-
516
- /**
517
- * Update a frontmatter field. Does not use applyMutation because frontmatter lives in S.content,
518
- * not S.document.
519
- *
520
- * @param {StudioState} state
521
- * @param {string} field
522
- * @param {unknown} value
523
- * @returns {StudioState}
524
- */
525
- export function updateFrontmatter(state: StudioState, field: string, value: unknown) {
526
- const fm = { ...state.content?.frontmatter };
527
- if (value === undefined || value === null || value === "") {
528
- delete fm[field];
529
- } else {
530
- fm[field] = value;
531
- }
532
- return {
533
- ...state,
534
- content: { ...state.content, frontmatter: fm },
535
- dirty: true,
536
- };
537
- }
538
-
539
- // ─── Selection / hover ────────────────────────────────────────────────────────
540
-
541
- /**
542
- * @param {StudioState} state
543
- * @param {JxPath | null} path
544
- * @returns {StudioState}
545
- */
546
- export function selectNode(state: StudioState, path: JxPath | null) {
547
- return { ...state, selection: path };
548
- }
549
-
550
- /**
551
- * @param {StudioState} state
552
- * @param {JxPath | null} path
553
- * @returns {StudioState}
554
- */
555
- export function hoverNode(state: StudioState, path: JxPath | null) {
556
- return { ...state, hover: path };
557
- }
558
-
559
- // ─── Document stack (component navigation) ──────────────────────────────────
560
-
561
- /**
562
- * Push current document onto the stack and switch to editing a new document.
563
- *
564
- * @param {StudioState} state
565
- * @param {JxMutableNode} doc
566
- * @param {string | null} documentPath
567
- * @returns {StudioState}
568
- */
569
- export function pushDocument(state: StudioState, doc: JxMutableNode, documentPath: string | null) {
570
- const frame = {
571
- dirty: state.dirty,
572
- document: state.document,
573
- documentPath: state.documentPath,
574
- fileHandle: state.fileHandle,
575
- history: state.history,
576
- historyIndex: state.historyIndex,
577
- mode: state.mode,
578
- selection: state.selection,
579
- };
580
- const newState = createState(doc);
581
- newState.documentStack = [...(state.documentStack || []), frame];
582
- newState.documentPath = documentPath;
583
- newState.ui = { ...state.ui, activeMedia: null, activeSelector: null };
584
- return newState;
585
- }
586
-
587
- /**
588
- * Pop the document stack and return to the previous document.
589
- *
590
- * @param {StudioState} state
591
- * @returns {StudioState}
592
- */
593
- export function popDocument(state: StudioState) {
594
- if (!state.documentStack || state.documentStack.length === 0) {
595
- return state;
596
- }
597
- const stack = [...state.documentStack];
598
- const frame = stack.pop();
599
- return {
600
- ...state,
601
- ...frame,
602
- documentStack: stack,
603
- ui: { ...state.ui },
604
- };
605
- }
package/src/store.ts CHANGED
@@ -9,13 +9,12 @@
9
9
 
10
10
  // ─── Re-exports from state.js ────────────────────────────────────────────────
11
11
 
12
- import { activeTab } from "./workspace/workspace";
12
+ import { INPUT_DEBOUNCE } from "./ui/timing";
13
13
  import type { JxPath } from "./state";
14
+ import type { Tab } from "./tabs/tab";
14
15
  import type { JxMutableNode } from "@jxsuite/schema/types";
15
- import type { CanvasPanel } from "./types";
16
16
 
17
17
  export {
18
- createState,
19
18
  getNodeAtPath,
20
19
  childList,
21
20
  flattenTree,
@@ -28,35 +27,29 @@ export {
28
27
  projectState,
29
28
  setProjectState,
30
29
  requireProjectState,
31
- updateFrontmatter,
32
30
  } from "./state";
33
31
 
34
32
  // ─── Shell element refs (populated by initShellRefs) ─────────────────────────
35
33
 
36
- export let canvasWrap = null as unknown as HTMLElement;
34
+ /* There is no `canvasWrap` here, and there cannot be one.
35
+ It was `#canvas-wrap`, the shell's single stage, imported by six modules and read at ~30 sites —
36
+ each of which meant "the pane I am acting on" and got "the pane that happens to be focused". A
37
+ stage belongs to a pane (`canvas/canvas-surface.ts`), a pane's cell is built by
38
+ `panels/pane-grid.ts`, and every one of those readers now takes its surface. */
37
39
  export let activityBar = null as unknown as HTMLElement;
38
40
  export let leftPanel = null as unknown as HTMLElement;
39
41
  export let rightPanel = null as unknown as HTMLElement;
40
- export let chatPanelEl = null as unknown as HTMLElement;
41
42
  export let toolbarEl = null as unknown as HTMLElement;
42
43
  export let statusbarEl = null as unknown as HTMLElement;
43
- export let frontmatterPanelEl = null as unknown as HTMLElement;
44
44
 
45
45
  export function initShellRefs() {
46
- canvasWrap = document.querySelector("#canvas-wrap") as HTMLElement;
47
46
  activityBar = document.querySelector("#activity-bar") as HTMLElement;
48
47
  leftPanel = document.querySelector("#left-panel") as HTMLElement;
49
48
  rightPanel = document.querySelector("#right-panel") as HTMLElement;
50
- chatPanelEl = document.querySelector("#chat-panel") as HTMLElement;
51
49
  toolbarEl = document.querySelector("#toolbar") as HTMLElement;
52
50
  statusbarEl = document.querySelector("#statusbar") as HTMLElement;
53
- frontmatterPanelEl = document.querySelector("#frontmatter-panel") as HTMLElement;
54
51
  }
55
52
 
56
- // ─── Shared containers (mutated in place by owner modules) ───────────────────
57
-
58
- export const canvasPanels: CanvasPanel[] = [];
59
-
60
53
  // ─── Shared constants ────────────────────────────────────────────────────────
61
54
 
62
55
  /** Void elements that cannot accept children */
@@ -102,14 +95,15 @@ const _styleDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
102
95
 
103
96
  /**
104
97
  * @param {string} prop
105
- * @param {number} ms
98
+ * @param {number | undefined} ms Debounce delay; `undefined` takes the standard input debounce.
106
99
  * @param {(...args: unknown[]) => void} fn
107
100
  */
108
101
  export function debouncedStyleCommit<A extends unknown[]>(
109
102
  prop: string,
110
- ms: number,
103
+ ms: number | undefined,
111
104
  fn: (...args: A) => void,
112
105
  ) {
106
+ const delay = ms ?? INPUT_DEBOUNCE;
113
107
  return (...args: A) => {
114
108
  clearTimeout(_styleDebounceTimers.get(prop));
115
109
  _styleDebounceTimers.set(
@@ -117,7 +111,7 @@ export function debouncedStyleCommit<A extends unknown[]>(
117
111
  setTimeout(() => {
118
112
  _styleDebounceTimers.delete(prop);
119
113
  fn(...args);
120
- }, ms),
114
+ }, delay),
121
115
  );
122
116
  };
123
117
  }
@@ -136,6 +130,47 @@ export { stripEventHandlers } from "./utils/strip-events";
136
130
 
137
131
  const _renderers = new Map<string, () => void>();
138
132
 
133
+ /**
134
+ * How many registered renderers are mid-paint right now.
135
+ *
136
+ * Condition 1 of the `probe.idle()` predicate (`services/idle.ts`, spec §13.5). A renderer is
137
+ * declared `() => void`, but TypeScript happily assigns an `async` function to that type, so a
138
+ * renderer that awaits keeps repainting long after `render()` returned. Counting the returned
139
+ * thenable is what makes "no queued lit render" an answerable question instead of an assumption —
140
+ * the alternative is a sleep, and a sleep cannot name what it is waiting for.
141
+ */
142
+ let _rendersInFlight = 0;
143
+
144
+ /** Renderers currently mid-paint — zero when the shell's DOM has caught up with its state. */
145
+ export function rendersInFlight(): number {
146
+ return _rendersInFlight;
147
+ }
148
+
149
+ /** Run one renderer, counting it as in-flight until it returns (or its promise settles). */
150
+ function runRenderer(name: string, fn: () => void) {
151
+ _rendersInFlight += 1;
152
+ const done = () => {
153
+ _rendersInFlight -= 1;
154
+ };
155
+ let pending: PromiseLike<unknown> | null = null;
156
+ try {
157
+ const result = fn() as unknown;
158
+ if (typeof (result as PromiseLike<unknown> | null)?.then === "function") {
159
+ pending = result as PromiseLike<unknown>;
160
+ }
161
+ } catch (error) {
162
+ console.error(`Renderer "${name}" failed:`, error);
163
+ }
164
+ if (!pending) {
165
+ done();
166
+ return;
167
+ }
168
+ pending.then(done, (error: unknown) => {
169
+ console.error(`Renderer "${name}" failed:`, error);
170
+ done();
171
+ });
172
+ }
173
+
139
174
  /**
140
175
  * Register a named renderer. Called at module import time by each module.
141
176
  *
@@ -149,11 +184,7 @@ export function registerRenderer(name: string, fn: () => void) {
149
184
  /** Call all registered renderers (full repaint). */
150
185
  export function render() {
151
186
  for (const [name, fn] of _renderers.entries()) {
152
- try {
153
- fn();
154
- } catch (error) {
155
- console.error(`Renderer "${name}" failed:`, error);
156
- }
187
+ runRenderer(name, fn);
157
188
  }
158
189
  }
159
190
 
@@ -168,32 +199,46 @@ export function renderOnly(...names: string[]) {
168
199
  if (!fn) {
169
200
  continue;
170
201
  }
171
- try {
172
- fn();
173
- } catch (error) {
174
- console.error(`Renderer "${name}" failed:`, error);
175
- }
202
+ runRenderer(name, fn);
176
203
  }
177
204
  }
178
205
 
179
206
  // ─── Session dispatch ──────────────────────────────────────────────────────
180
207
 
208
+ /* **Every dispatcher below takes the tab it writes, and none of them can find one on its own.**
209
+ All three used to open with `const tab = activeTab.value`, which made "the document this
210
+ control is about" and "the document the keyboard happens to be in" the same expression — the
211
+ defect that produced the Document Header card writing through focus, the zoom axis writing
212
+ through focus, the debounced Monaco commit writing through focus, and the whole pane context
213
+ bar writing through focus. Four instances, four audits, no gate: while the shell had one stage
214
+ the two readings could not disagree, so nothing could observe the difference.
215
+
216
+ `store.ts` is where they could not disagree, so `store.ts` is where the shape is fixed. There is
217
+ no `activeTab` import in this file and no zero-argument variant of any of these: a caller that
218
+ means the focused pane spells `activeTab.value` at its own call site, where a reviewer and
219
+ `scripts/check-pane-singletons.ts` can both see it, and a caller drawn FOR a pane passes the tab
220
+ it was drawn for. */
221
+
181
222
  /**
182
- * Dispatch a session-only state update (selection, hover, ui). Writes directly to reactive tab.
223
+ * Dispatch a session-only state update (selection, hover, ui) into `tab`.
183
224
  *
225
+ * @param {Tab | null} tab — the tab this update is about. `null` is a no-op.
184
226
  * @param {object} patch — partial session object, e.g. { ui: { zoom: 2 } }
185
227
  */
186
- export function updateSession(patch: {
187
- selection?: JxPath | null;
188
- hover?: JxPath | null;
189
- clipboard?: JxMutableNode | null;
190
- ui?: Record<string, unknown>;
191
- canvas?: Record<string, unknown>;
192
- }) {
193
- const tab = activeTab.value;
228
+ export function updateSession(
229
+ tab: Tab | null,
230
+ patch: {
231
+ /** The whole selection SET — `[]` clears it. Widened with `session.selection` itself (§6.5). */
232
+ selection?: JxPath[];
233
+ hover?: JxPath | null;
234
+ clipboard?: JxMutableNode | null;
235
+ ui?: Record<string, unknown>;
236
+ canvas?: Record<string, unknown>;
237
+ },
238
+ ) {
194
239
  if (tab) {
195
240
  if (patch.selection !== undefined) {
196
- tab.session.selection = patch.selection as JxPath | null;
241
+ tab.session.selection = patch.selection;
197
242
  }
198
243
  if (patch.hover !== undefined) {
199
244
  tab.session.hover = patch.hover as JxPath | null;
@@ -215,25 +260,25 @@ export function updateSession(patch: {
215
260
  }
216
261
 
217
262
  /**
218
- * Update a single UI field.
263
+ * Update a single UI field on `tab`.
219
264
  *
265
+ * @param {Tab | null} tab — the tab this field belongs to. `null` is a no-op.
220
266
  * @param {string} field
221
267
  * @param {unknown} value
222
268
  */
223
- export function updateUi(field: string, value: unknown) {
224
- const tab = activeTab.value;
269
+ export function updateUi(tab: Tab | null, field: string, value: unknown) {
225
270
  if (tab) {
226
271
  (tab.session.ui as unknown as Record<string, unknown>)[field] = value;
227
272
  }
228
273
  }
229
274
 
230
275
  /**
231
- * Update the canvas async state (status, scope, error).
276
+ * Update `tab`'s canvas async state (status, scope, error).
232
277
  *
278
+ * @param {Tab | null} tab — the tab whose stage produced this state. `null` is a no-op.
233
279
  * @param {object} patch
234
280
  */
235
- export function updateCanvas(patch: Record<string, unknown>) {
236
- const tab = activeTab.value;
281
+ export function updateCanvas(tab: Tab | null, patch: Record<string, unknown>) {
237
282
  if (tab) {
238
283
  for (const [k, v] of Object.entries(patch)) {
239
284
  (tab.session.canvas as Record<string, unknown>)[k] = v;