@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
@@ -0,0 +1,323 @@
1
+ /**
2
+ * The selection SET — one vocabulary for `session.selection`, now that it is `JxPath[]` (§6.5).
3
+ *
4
+ * ## The invariant this module exists to hold
5
+ *
6
+ * **A selection of one behaves exactly as a selection did before it was a list.** Every surface
7
+ * that addresses a single node — the Inspector's three tabs, the block action bar, the status bar,
8
+ * the canvas overlay box, the context menu — reads {@link primarySelection} and nothing else. When
9
+ * `length === 1` that function returns the sole path, so those surfaces receive byte-identically
10
+ * what `session.selection` used to hand them. Nothing downstream of `primarySelection` can tell the
11
+ * difference, and that is what makes the widening shippable: the multi cases are additions beside
12
+ * the single case, never a rewrite of it.
13
+ *
14
+ * The corollary is a rule about writes: **replace the array, never mutate it in place.** Several
15
+ * render effects track the selection with a bare `void tab.session.selection` read, which
16
+ * subscribes to the property and not to the array's indices; an in-place `push` would move the
17
+ * selection without repainting the panel that draws it. Every helper here returns a NEW array.
18
+ *
19
+ * ## Ordering, and why two ends of the list have names
20
+ *
21
+ * The list is kept in the order the user built it.
22
+ *
23
+ * - {@link selectionAnchor} is `selection[0]` — where a shift-range extends FROM. It survives every
24
+ * range extension, so shift-clicking A then D then B gives A..D then A..B, which is what every
25
+ * list in every file manager does.
26
+ * - {@link primarySelection} is the LAST element — the most recently added path, and therefore the
27
+ * one the user just pointed at. Single-target surfaces address it.
28
+ *
29
+ * ## Structural batches
30
+ *
31
+ * A multi-node structural edit is one decision and must be one undo step, which `transactDoc`
32
+ * already gives for free — everything mutated inside one call is one history entry. What it does
33
+ * NOT give is index safety: removing `children/0` renumbers `children/1`, so a naive loop over the
34
+ * selection deletes the wrong nodes. {@link structuralBatch} answers that once — descendants
35
+ * dropped, then descending document order, so every splice happens below coordinates nothing later
36
+ * in the batch depends on.
37
+ *
38
+ * It also answers a second question the single-path world never had to ask: whether a selected path
39
+ * is a splice coordinate AT ALL. The Outline gives the document element, a repeater's map template
40
+ * and a `$switch` case rows of their own, and any of them can be ctrl-clicked into a selection
41
+ * beside an ordinary element. {@link isSpliceablePath} is the test, and it is the one thing
42
+ * standing between a mixed selection and a half-applied, unrecorded delete.
43
+ */
44
+
45
+ import type { JxPath } from "../state";
46
+ import { isAncestor, pathsEqual } from "../state";
47
+
48
+ /**
49
+ * The path single-target surfaces address — the most recently added, or `null` when empty.
50
+ *
51
+ * @param {readonly JxPath[] | null | undefined} selection
52
+ * @returns {JxPath | null}
53
+ */
54
+ export function primarySelection(selection: readonly JxPath[] | null | undefined): JxPath | null {
55
+ return selection && selection.length > 0 ? (selection.at(-1) as JxPath) : null;
56
+ }
57
+
58
+ /**
59
+ * The path a shift-range extends from — the first in the list, or `null` when empty.
60
+ *
61
+ * @param {readonly JxPath[] | null | undefined} selection
62
+ * @returns {JxPath | null}
63
+ */
64
+ export function selectionAnchor(selection: readonly JxPath[] | null | undefined): JxPath | null {
65
+ return selection && selection.length > 0 ? (selection[0] as JxPath) : null;
66
+ }
67
+
68
+ /**
69
+ * Whether `path` is one of the selected paths.
70
+ *
71
+ * @param {readonly JxPath[] | null | undefined} selection
72
+ * @param {JxPath | null} path
73
+ * @returns {boolean}
74
+ */
75
+ export function isSelected(
76
+ selection: readonly JxPath[] | null | undefined,
77
+ path: JxPath | null,
78
+ ): boolean {
79
+ if (!path || !selection) {
80
+ return false;
81
+ }
82
+ return selection.some((p) => pathsEqual(p, path));
83
+ }
84
+
85
+ /**
86
+ * Copy a selection, so nothing can mutate one through the array it was handed.
87
+ *
88
+ * History entries and the collab awareness payload both take a copy for the same reason they always
89
+ * did: `session.selection` is a reactive proxy, and a stored reference to it would follow the live
90
+ * selection instead of recording where it was. (A third caller, the sub-document stack's frames,
91
+ * went with the stack — nothing pushes one any more.)
92
+ *
93
+ * @param {readonly JxPath[]} paths
94
+ * @returns {JxPath[]}
95
+ */
96
+ export function cloneSelection(paths: readonly JxPath[]): JxPath[] {
97
+ return clonePaths(paths);
98
+ }
99
+
100
+ /** Copy a list of paths, so no caller can mutate a selection through the array it was handed. */
101
+ function clonePaths(paths: readonly JxPath[]): JxPath[] {
102
+ const out: JxPath[] = [];
103
+ for (const path of paths) {
104
+ out.push([...path]);
105
+ }
106
+ return out;
107
+ }
108
+
109
+ /**
110
+ * Ctrl/Cmd-click: add the path when it is absent, remove it when it is present.
111
+ *
112
+ * Adding appends, so the newly clicked path becomes the primary and the anchor is untouched.
113
+ * Removing preserves the order of the survivors — including the anchor, unless the anchor is what
114
+ * was removed.
115
+ *
116
+ * @param {readonly JxPath[]} selection
117
+ * @param {JxPath} path
118
+ * @returns {JxPath[]}
119
+ */
120
+ export function toggleSelected(selection: readonly JxPath[], path: JxPath): JxPath[] {
121
+ if (isSelected(selection, path)) {
122
+ return clonePaths(selection.filter((p) => !pathsEqual(p, path)));
123
+ }
124
+ return [...clonePaths(selection), [...path]];
125
+ }
126
+
127
+ /**
128
+ * Shift-click: the contiguous run of `rows` between the anchor and `target`, inclusive.
129
+ *
130
+ * `rows` is the surface's own visible order — the Outline's flattened, expansion-aware row list —
131
+ * because "the range between these two" is a question only the surface that draws them can answer.
132
+ * When the anchor is absent from `rows` (it was collapsed away, or nothing was selected) the range
133
+ * degenerates to `[target]`, which is a plain click.
134
+ *
135
+ * The returned list starts at the anchor end, so the anchor stays `selection[0]` and the target
136
+ * becomes the primary — a further shift-click re-extends from the same anchor.
137
+ *
138
+ * @param {readonly JxPath[]} rows Visible rows in display order.
139
+ * @param {JxPath | null} anchor
140
+ * @param {JxPath} target
141
+ * @returns {JxPath[]}
142
+ */
143
+ export function rangeSelection(
144
+ rows: readonly JxPath[],
145
+ anchor: JxPath | null,
146
+ target: JxPath,
147
+ ): JxPath[] {
148
+ const to = rows.findIndex((p) => pathsEqual(p, target));
149
+ const from = anchor ? rows.findIndex((p) => pathsEqual(p, anchor)) : -1;
150
+ if (to === -1 || from === -1) {
151
+ return [[...target]];
152
+ }
153
+ const slice = from <= to ? rows.slice(from, to + 1) : rows.slice(to, from + 1).toReversed();
154
+ return clonePaths(slice);
155
+ }
156
+
157
+ /**
158
+ * Drop every selected path that `removed` addresses or contains.
159
+ *
160
+ * This is `mutateRemoveNode`'s selection repair, widened: deleting a node used to clear the
161
+ * selection outright, and with a list it clears only the entries the deletion actually invalidated.
162
+ * A one-path selection therefore still ends up empty exactly when it used to.
163
+ *
164
+ * @param {readonly JxPath[]} selection
165
+ * @param {JxPath} removed
166
+ * @returns {JxPath[]}
167
+ */
168
+ export function pruneSelection(selection: readonly JxPath[], removed: JxPath): JxPath[] {
169
+ return clonePaths(selection.filter((p) => !isAncestor(removed, p)));
170
+ }
171
+
172
+ /**
173
+ * Order two paths the way the document reads: parents before children, siblings by index.
174
+ *
175
+ * Segments are compared pairwise; numbers numerically (so `children/10` follows `children/9`), and
176
+ * anything else by string order. A prefix sorts before what extends it.
177
+ *
178
+ * @param {JxPath} a
179
+ * @param {JxPath} b
180
+ * @returns {number}
181
+ */
182
+ export function comparePaths(a: JxPath, b: JxPath): number {
183
+ const len = Math.min(a.length, b.length);
184
+ for (let i = 0; i < len; i++) {
185
+ const x = a[i]!;
186
+ const y = b[i]!;
187
+ if (x === y) {
188
+ continue;
189
+ }
190
+ if (typeof x === "number" && typeof y === "number") {
191
+ return x - y;
192
+ }
193
+ return String(x) < String(y) ? -1 : 1;
194
+ }
195
+ return a.length - b.length;
196
+ }
197
+
198
+ /**
199
+ * Drop any path whose ancestor is also selected.
200
+ *
201
+ * Deleting a `<section>` and one of its own paragraphs is one deletion, not two: the paragraph goes
202
+ * with its parent, and a second splice into coordinates that no longer exist is how a batch
203
+ * corrupts a document.
204
+ *
205
+ * @param {readonly JxPath[]} selection
206
+ * @returns {JxPath[]}
207
+ */
208
+ export function topLevelSelection(selection: readonly JxPath[]): JxPath[] {
209
+ return clonePaths(
210
+ selection.filter((p) => !selection.some((q) => !pathsEqual(q, p) && isAncestor(q, p))),
211
+ );
212
+ }
213
+
214
+ /**
215
+ * Whether `path` names a position a structural verb can splice — a tail of `"children"` + index.
216
+ *
217
+ * Every structural mutator (`mutateRemoveNode`, `mutateDuplicateNode`, `mutateWrapNode`) reaches
218
+ * its parent with `parentElementPath` — drop the last two segments — and its position with
219
+ * `childIndex` — take the last. That produces a splice coordinate for exactly one path shape, and
220
+ * the Outline emits three others as first-class rows: the document element (`[]`), a repeater's map
221
+ * template (`[…, "children", 1, "map"]`) and a `$switch` case (`[…, "cases", "warn"]`). For the
222
+ * template, `parentElementPath` resolves to the enclosing children ARRAY, whose `.children` is
223
+ * `undefined`, so the splice throws mid-transaction; for a case it splices at `NaN` and takes the
224
+ * wrong child.
225
+ *
226
+ * Selecting one of those beside an ordinary element is a single ctrl-click, so this is the
227
+ * invariant that keeps a batch from mutating the document and then throwing before anything is
228
+ * recorded.
229
+ *
230
+ * @param {JxPath} path
231
+ * @returns {boolean}
232
+ */
233
+ export function isSpliceablePath(path: JxPath): boolean {
234
+ return path.at(-2) === "children" && typeof path.at(-1) === "number";
235
+ }
236
+
237
+ /**
238
+ * The order a structural batch must run in: contained paths dropped, unspliceable paths dropped,
239
+ * then LAST node first.
240
+ *
241
+ * Descending document order is what makes a loop of splices safe. Removing or duplicating at index
242
+ * `n` renumbers every sibling after `n`, so a batch that starts at the end never touches a
243
+ * coordinate a later step still needs. It is also why duplicating a multi-selection inserts each
244
+ * clone directly after its own original instead of walking the whole run forward.
245
+ *
246
+ * Containment is decided BEFORE spliceability, on the selection the user actually made: a selected
247
+ * repeater template still shadows its own descendants, so selecting the template and something
248
+ * inside it yields nothing rather than quietly deleting the child the user never pointed at.
249
+ *
250
+ * @param {readonly JxPath[]} selection
251
+ * @returns {JxPath[]} Deepest/last first, every entry a splice coordinate.
252
+ */
253
+ export function structuralBatch(selection: readonly JxPath[]): JxPath[] {
254
+ return topLevelSelection(selection)
255
+ .filter((path) => isSpliceablePath(path))
256
+ .toSorted((a, b) => comparePaths(b, a));
257
+ }
258
+
259
+ /**
260
+ * Deduplicate a list of paths, keeping the FIRST occurrence of each.
261
+ *
262
+ * Keeping the first is what makes `selection.setPaths` idempotent: passing the same list twice — or
263
+ * a list that names one node twice — always lands on the same selection, in the same order, with
264
+ * the same anchor and the same primary.
265
+ *
266
+ * @param {readonly JxPath[]} paths
267
+ * @returns {JxPath[]}
268
+ */
269
+ export function uniquePaths(paths: readonly JxPath[]): JxPath[] {
270
+ const out: JxPath[] = [];
271
+ for (const path of paths) {
272
+ if (!out.some((p) => pathsEqual(p, path))) {
273
+ out.push([...path]);
274
+ }
275
+ }
276
+ return out;
277
+ }
278
+
279
+ /**
280
+ * Whether two selections name the same paths in the same order.
281
+ *
282
+ * @param {readonly JxPath[] | null | undefined} a
283
+ * @param {readonly JxPath[] | null | undefined} b
284
+ * @returns {boolean}
285
+ */
286
+ export function selectionsEqual(
287
+ a: readonly JxPath[] | null | undefined,
288
+ b: readonly JxPath[] | null | undefined,
289
+ ): boolean {
290
+ const x = a ?? [];
291
+ const y = b ?? [];
292
+ return x.length === y.length && x.every((p, i) => pathsEqual(p, y[i] as JxPath));
293
+ }
294
+
295
+ /**
296
+ * Collapse one field's value across the selection into a single reading.
297
+ *
298
+ * `{ mixed: true }` is the answer the three inspector tabs render as the **Mixed** provenance
299
+ * state: the selected nodes disagree, so there is no one value to show and typing one would
300
+ * overwrite several different ones. `{ mixed: false, value }` is the answer when they agree — and
301
+ * when the selection is a single node it is ALWAYS that answer, which is why a single selection
302
+ * renders no Mixed state anywhere.
303
+ *
304
+ * Comparison is by JSON shape rather than reference: two nodes that both carry `{ $ref: "#/state/x"
305
+ * }` hold equal values written at different times.
306
+ *
307
+ * @template T
308
+ * @param {readonly T[]} values One per selected node, in selection order.
309
+ * @returns {{ mixed: boolean; value: T | undefined }}
310
+ */
311
+ export function unifyValues<T>(values: readonly T[]): { mixed: boolean; value: T | undefined } {
312
+ if (values.length === 0) {
313
+ return { mixed: false, value: undefined };
314
+ }
315
+ const first = values[0] as T;
316
+ const key = JSON.stringify(first ?? null);
317
+ for (let i = 1; i < values.length; i++) {
318
+ if (JSON.stringify(values[i] ?? null) !== key) {
319
+ return { mixed: true, value: undefined };
320
+ }
321
+ }
322
+ return { mixed: false, value: first };
323
+ }
package/src/tabs/tab.ts CHANGED
@@ -2,19 +2,21 @@
2
2
  import { effectScope, reactive } from "../reactivity";
3
3
  import { formatByName, formatForPath } from "../format/format-host";
4
4
  import { normalizeArrayChildren } from "../state";
5
- import type {
6
- DocumentStackEntry,
7
- FormulaEditDef,
8
- FunctionEditDef,
9
- GitBranchesResult,
10
- GitDiffState,
11
- GitStatusResult,
12
- InlineEditDef,
13
- JsonValue,
14
- } from "../types";
5
+ import type { FormulaEditDef, FunctionEditDef, InlineEditDef, JsonValue } from "../types";
6
+ import { editorKindForMode } from "../commands/context";
7
+ import type { EditorKind } from "../commands/context";
15
8
  import type { JxMutableNode } from "@jxsuite/schema/types";
16
9
  import type { JxDocOp, JxFmOp } from "./patch-ops";
17
10
 
11
+ /**
12
+ * The project's configuration file, project-relative.
13
+ *
14
+ * Named here rather than at the four places that used to spell it, because it is a DOCUMENT PATH —
15
+ * `inferModes` answers `["stylebook", "source"]` for it (editor kind `config`), and
16
+ * `tabs/project-config.ts` binds the tab at this path as the configuration document.
17
+ */
18
+ export const PROJECT_CONFIG_PATH = "project.json";
19
+
18
20
  export interface TabUi {
19
21
  rightTab: string;
20
22
  canvasMode: string;
@@ -22,8 +24,6 @@ export interface TabUi {
22
24
  preview: boolean;
23
25
  /** Show elements inherited from the page's layout (pages with an effective layout only). */
24
26
  showLayout: boolean;
25
- /** Above-canvas frontmatter Properties accordion expanded (content-collection docs, edit mode). */
26
- frontmatterOpen: boolean;
27
27
  /** Chosen literal values for dynamic route params (e.g. { sku: "mini-trencher" }). */
28
28
  previewParams: Record<string, string>;
29
29
  /**
@@ -40,7 +40,7 @@ export interface TabUi {
40
40
  activeMedia: string | null;
41
41
  activeSelector: string | null;
42
42
  editingFunction: FunctionEditDef | null;
43
- /** Full-screen formula workspace target ($expression editing); editingFunction wins if both set. */
43
+ /** Logic-tab formula target ($expression editing); editingFunction wins if both are set. */
44
44
  editingFormula: FormulaEditDef | null;
45
45
  featureToggles: Record<string, boolean>;
46
46
  /**
@@ -49,22 +49,40 @@ export interface TabUi {
49
49
  * which scheme layer edits target.
50
50
  */
51
51
  previewColorScheme: "auto" | "light" | "dark";
52
+ /**
53
+ * The locale this pane renders AS — the artboard's `lang` and `dir` (§13.4), never which file is
54
+ * open.
55
+ *
56
+ * Jx has no message catalogue: a translation is a different file in a different directory, so
57
+ * "show this page in French" is a navigation and belongs to the locale preset. This axis is the
58
+ * other half — the rendering context a document is _drawn_ under, which is how an author sees
59
+ * their layout mirror under an RTL locale without leaving the document they are editing.
60
+ *
61
+ * `null` is "the document's own language", resolved from its path at render time rather than
62
+ * baked in here: a file moved into `pages/fr/` must change what the pane draws, and a stored tag
63
+ * would go on claiming the language the file used to be in.
64
+ */
65
+ previewLocale: string | null;
52
66
  styleSections: Record<string, boolean>;
67
+ /**
68
+ * Which Data rows have their editor and value open, by state-entry name.
69
+ *
70
+ * PER TAB, and a set rather than a single name: comparing two entries means seeing both at once,
71
+ * and coming back to a tab means finding it as you left it. It was one module-global string
72
+ * (`expandedSignal`) plus one module-global Set (`expandedDataKeys`) — two answers to one
73
+ * question, both shared across every open document.
74
+ */
75
+ dataRows: Record<string, boolean>;
76
+ /**
77
+ * How far each truncation marker in a Data row's value tree has been opened, keyed by tree path.
78
+ *
79
+ * PER TAB, for the reason `dataRows` is: the paths are entry names, and a module-global record
80
+ * would have applied "show 50 more of `listings`" to a different document's `listings`.
81
+ */
82
+ dataLimits: Record<string, number>;
53
83
  inspectorSections: Record<string, boolean>;
54
84
  styleShorthands: Record<string, boolean>;
55
85
  styleFilter: string;
56
- styleFilterActive: boolean;
57
- stylebookSelection: string | null;
58
- stylebookTab: string;
59
- stylebookFilter: string;
60
- stylebookCustomizedOnly: boolean;
61
- settingsTab: string;
62
- gitStatus: GitStatusResult | null;
63
- gitBranches: GitBranchesResult | null;
64
- gitCommitMessage: string;
65
- gitLoading: boolean;
66
- gitError: string | null;
67
- gitDiffState: GitDiffState | null;
68
86
  pendingInlineEdit: InlineEditDef | null;
69
87
  }
70
88
 
@@ -83,10 +101,10 @@ export interface StoredLivePreview {
83
101
  interface HistorySnapshot {
84
102
  /** Full document at this state (checkpoint), or null when the ops describe the transition. */
85
103
  document: Record<string, unknown> | null;
86
- /** Selection after this state's transaction. */
87
- selection: (string | number)[] | null;
104
+ /** Selection after this state's transaction — the whole set, so a batch undoes back to it. */
105
+ selection: (string | number)[][];
88
106
  /** Selection before this state's transaction (restored on surgical undo). */
89
- selectionBefore?: (string | number)[] | null;
107
+ selectionBefore?: (string | number)[][];
90
108
  /** Replayable ops transforming the previous state into this one. */
91
109
  forwardOps?: JxDocOp[] | null;
92
110
  /** Replayable ops transforming this state back into the previous one. */
@@ -103,11 +121,34 @@ interface HistorySnapshot {
103
121
  coalesceKey?: string | null;
104
122
  }
105
123
 
124
+ /**
125
+ * Where a tab was drilled in FROM — a relationship, not a navigation stack.
126
+ *
127
+ * Drilling into a component opens a real tab of its own (see `workspace/workspace.ts`), so the old
128
+ * "swap the document under the same tab id" breadcrumb is gone. What survives is this: the tab
129
+ * remembers which document sent the author here, and the tab strip prints it. Nothing restores from
130
+ * it, nothing pops it — closing the parent leaves the child perfectly usable.
131
+ */
132
+ export interface TabOrigin {
133
+ /** Id of the tab the author drilled in from. */
134
+ tabId: string;
135
+ /** That tab's document path at the moment of the drill-in — what the strip prints. */
136
+ documentPath: string | null;
137
+ }
138
+
106
139
  export interface Tab {
107
140
  id: string;
108
141
  documentPath: string | null;
109
142
  fileHandle: FileSystemFileHandle | null;
110
143
  capabilities: { modes: string[] };
144
+ /** Pinned tabs hold the head of their pane's strip, and a preview open never takes their slot. */
145
+ pinned: boolean;
146
+ /**
147
+ * A PREVIEW tab is disposable: the next preview open in the same pane replaces it, and it renders
148
+ * italic to say so. It stops being one the moment the author commits to it — an edit, a pin, a
149
+ * double-click. The palette's `@`/`#` modes make browsing cheap, and browsing must not litter.
150
+ */
151
+ preview: boolean;
111
152
  scope: {
112
153
  stop: () => void;
113
154
  run: <T>(fn: () => T) => T | undefined;
@@ -122,13 +163,33 @@ export interface Tab {
122
163
  dirty: boolean;
123
164
  };
124
165
  session: {
125
- selection: (string | number)[] | null;
166
+ /**
167
+ * The selected document paths, in the order the user built them (§6.5).
168
+ *
169
+ * `[]` is "nothing selected" — there is no `null` state, because "no selection" and "a
170
+ * selection of nothing" were never two different things. `selection[0]` is the anchor a
171
+ * shift-range extends from; the LAST entry is the primary, which every single-target surface
172
+ * reads through `tabs/selection.ts`'s `primarySelection`. **Always assign a fresh array**:
173
+ * several render effects track this with a bare property read and would miss an in-place push.
174
+ */
175
+ selection: (string | number)[][];
126
176
  hover: (string | number)[] | null;
127
177
  clipboard: JxMutableNode | null;
128
- documentStack: DocumentStackEntry[];
178
+ /** The document this tab was drilled in from, if any. Rendered by the tab strip. */
179
+ openedFrom: TabOrigin | null;
129
180
  ui: TabUi;
130
181
  canvas: {
131
182
  status: string;
183
+ /**
184
+ * A Refresh is out and the snapshot below has not come back yet.
185
+ *
186
+ * The button used to guess: fire the re-render, then `setTimeout(…, 200)` and repaint,
187
+ * because nothing told the panel when the values had actually changed. A fetch slower than
188
+ * 200ms repainted the OLD values and looked like a Refresh that did nothing. The iframe posts
189
+ * `dataScope` when the render resolves, so the honest answer is to say "refreshing" until it
190
+ * arrives and let the arrival be the repaint.
191
+ */
192
+ refreshing: boolean;
132
193
  // A serializable snapshot of the iframe's resolved `$defs` (data-source values), posted over
133
194
  // The bridge as a `dataScope` message and read by the data-explorer panel. Plain data now —
134
195
  // The old live `EffectScope` (with `.stop()`) moved into the iframe realm with buildScope.
@@ -160,30 +221,20 @@ function createDefaultUi(canvasMode: string, preview = false) {
160
221
  editingFormula: null,
161
222
  editingFunction: null,
162
223
  featureToggles: {},
163
- frontmatterOpen: true,
164
- gitBranches: null,
165
- gitCommitMessage: "",
166
- gitDiffState: null,
167
- gitError: null,
168
- gitLoading: false,
169
- gitStatus: null,
170
224
  inspectorSections: {},
171
225
  pendingInlineEdit: null,
172
226
  preview,
173
227
  previewColorScheme: "auto" as const,
228
+ previewLocale: null as string | null,
174
229
  previewParams: {},
175
230
  previewProps: null,
176
231
  rightTab: "properties",
177
- settingsTab: "stylebook",
178
232
  showLayout: true,
179
233
  styleFilter: "",
180
- styleFilterActive: false,
181
234
  styleSections: {},
235
+ dataRows: {},
236
+ dataLimits: {},
182
237
  styleShorthands: {},
183
- stylebookCustomizedOnly: false,
184
- stylebookFilter: "",
185
- stylebookSelection: null,
186
- stylebookTab: "elements",
187
238
  zoom: 1,
188
239
  };
189
240
  }
@@ -201,6 +252,8 @@ const ALL_MODES = ["edit", "design", "preview", "source", "stylebook"];
201
252
  * frontmatter?: Record<string, unknown>;
202
253
  * sourceFormat?: string | null;
203
254
  * capabilities?: { modes?: string[] };
255
+ * openedFrom?: TabOrigin | null;
256
+ * preview?: boolean;
204
257
  * }} opts
205
258
  * @returns {Tab}
206
259
  */
@@ -212,6 +265,8 @@ export function createTab({
212
265
  frontmatter,
213
266
  sourceFormat = null,
214
267
  capabilities,
268
+ openedFrom = null,
269
+ preview: previewTab = false,
215
270
  }: {
216
271
  id: string;
217
272
  documentPath?: string | null;
@@ -220,6 +275,8 @@ export function createTab({
220
275
  frontmatter?: Record<string, unknown>;
221
276
  sourceFormat?: string | null;
222
277
  capabilities?: { modes?: string[] };
278
+ openedFrom?: TabOrigin | null;
279
+ preview?: boolean;
223
280
  }) {
224
281
  const scope = effectScope();
225
282
 
@@ -249,12 +306,15 @@ export function createTab({
249
306
  fileHandle,
250
307
  history: reactive({
251
308
  index: 0,
252
- snapshots: [{ document: structuredClone(document), selection: null }],
309
+ snapshots: [{ document: structuredClone(document), selection: [] }],
253
310
  }),
254
311
  id,
312
+ pinned: false,
313
+ preview: previewTab,
255
314
  scope,
256
315
  session: reactive({
257
316
  canvas: {
317
+ refreshing: false,
258
318
  error: null,
259
319
  livePreviews: null,
260
320
  pendingInlineEdit: null,
@@ -262,9 +322,9 @@ export function createTab({
262
322
  status: "idle",
263
323
  },
264
324
  clipboard: null,
265
- documentStack: [],
266
325
  hover: null,
267
- selection: null,
326
+ openedFrom,
327
+ selection: [],
268
328
  ui: createDefaultUi(initialCanvasMode, initialPreview),
269
329
  }),
270
330
  })) as unknown as Tab;
@@ -281,7 +341,7 @@ export function createTab({
281
341
  * @returns {string[]}
282
342
  */
283
343
  function inferModes(documentPath: string | null | undefined, sourceFormat: string | null) {
284
- if (documentPath === "project.json") {
344
+ if (documentPath === PROJECT_CONFIG_PATH) {
285
345
  return ["stylebook", "source"];
286
346
  }
287
347
  const format = formatByName(sourceFormat) ?? formatForPath(documentPath);
@@ -307,6 +367,81 @@ function inferDocumentMode(documentPath: string | null | undefined, sourceFormat
307
367
  return "component";
308
368
  }
309
369
 
370
+ // ─── Editor kinds ─────────────────────────────────────────────────────────────
371
+ // §4.2's first axis. `canvasMode` conflates two orthogonal questions — WHICH EDITOR is open, and
372
+ // (for the Canvas editor only) WHICH VIEW of it. The pane context bar labels them separately, and
373
+ // The pane model needs the first one on its own: the second pane is capped to non-Canvas kinds
374
+ // Until P8's `canvas-patcher` fan-out lands, because a second live `@jxsuite/runtime` host is the
375
+ // Expensive part.
376
+ //
377
+ // The mode → kind map itself is `commands/context.ts`'s `editorKindForMode`. It was duplicated
378
+ // Here, and the copy that drifted is what let a settings document resolve into the canvas key
379
+ // Scope; the pane model and the command context now read the same table.
380
+
381
+ /**
382
+ * The editor kind a tab is currently showing. Reads the BASE mode: the preview toggle is a value on
383
+ * the Canvas view axis, not a different editor.
384
+ *
385
+ * @param {Tab} tab
386
+ * @returns {EditorKind}
387
+ */
388
+ export function editorKindOf(tab: Tab): EditorKind {
389
+ return editorKindForMode(tab.session.ui.canvasMode);
390
+ }
391
+
392
+ /**
393
+ * Every editor kind this document supports, in the format's own mode order and deduplicated — the
394
+ * dropdown's entries, so it can never contain a permanently dead one (§4.2).
395
+ *
396
+ * `preview` contributes nothing: it is a Canvas VIEW, and the kind it would add is already there.
397
+ *
398
+ * @param {Tab} tab
399
+ * @returns {EditorKind[]}
400
+ */
401
+ export function editorKindsOf(tab: Tab): EditorKind[] {
402
+ const kinds: EditorKind[] = [];
403
+ for (const mode of tab.capabilities.modes) {
404
+ if (mode === "preview") {
405
+ continue;
406
+ }
407
+ const kind = editorKindForMode(mode);
408
+ if (!kinds.includes(kind)) {
409
+ kinds.push(kind);
410
+ }
411
+ }
412
+ return kinds;
413
+ }
414
+
415
+ /**
416
+ * The first mode this tab supports that renders `kind`, or `undefined` when it supports none.
417
+ *
418
+ * The inverse of {@link editorKindOf}: choosing an editor kind has to land on a real `canvasMode`,
419
+ * because that string is what the canvas render message carries.
420
+ *
421
+ * @param {Tab} tab
422
+ * @param {EditorKind} kind
423
+ * @returns {string | undefined}
424
+ */
425
+ export function modeForEditorKind(tab: Tab, kind: EditorKind): string | undefined {
426
+ return tab.capabilities.modes.find(
427
+ (mode) => mode !== "preview" && editorKindForMode(mode) === kind,
428
+ );
429
+ }
430
+
431
+ // ─── There is no sub-document stack ───────────────────────────────────────────
432
+ // A tab held one, and it was scaffolding for a navigation model the tab model replaced. `studio.md`
433
+ // §14.3 justified it for exactly two cases and both moved: a function body opens in the Bottom
434
+ // Dock's Logic tab (P8), and a `$map` template is a subtree of its parent document, selected in
435
+ // Place on the canvas rather than loaded as a document of its own. Everything with a file of its
436
+ // Own — a component, a layout — opens a REAL TAB with an `openedFrom` relationship (§14.1–2):
437
+ // {@link TabOrigin}, above, which nothing pops and nothing restores from.
438
+ //
439
+ // So `pushSubDocument` had no caller in `src/`, the stack was permanently `[]`, and every reader
440
+ // Below it — `popSubDocument`, `popToSubDocument`, the UI capture/restore pair, the pane-context
441
+ // Breadcrumb, the jump bar's `subdocument` segment, `document.setStackLevel`, collab's `drilled`
442
+ // Guard — was live code that could only ever take its empty branch. They are gone together: a
443
+ // Stack with no way in is not an unfinished feature, it is a shape the design moved past.
444
+
310
445
  /**
311
446
  * Dispose a tab — stops its effectScope, killing all effects created within it.
312
447
  *