@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
@@ -6,6 +6,15 @@
6
6
  * project and assigns it, and a Browse dropdown of media already in the project's public/
7
7
  * directory, with thumbnail previews for images.
8
8
  *
9
+ * **The browse list carries metadata, and it costs nothing.** Size comes from the directory listing
10
+ * the widget already performs to enumerate the files (`seedMediaMeta`), and pixel dimensions come
11
+ * from the thumbnails it already loads (`recordImageSize`) — measuring an image is a decode the
12
+ * `<img>` has finished by the time `load` fires, so the caption is a read of work already done
13
+ * rather than a second fetch per row. What is NOT here is a usage count: `findReferences` sweeps
14
+ * every document in the project per query, and fifty of those to caption a dropdown is the wrong
15
+ * trade. Usage is asked once, about one file, where it changes a decision — the delete confirmation
16
+ * (`files/media-usage.ts`).
17
+ *
9
18
  * @docs studio/projects/media
10
19
  */
11
20
 
@@ -14,7 +23,8 @@ import { live } from "lit-html/directives/live.js";
14
23
  import { ref } from "lit-html/directives/ref.js";
15
24
  import { getPlatform } from "../platform";
16
25
  import { debouncedStyleCommit, renderOnly } from "../store";
17
- import { getLayerSlot } from "./layers";
26
+ import { getLayerSlot, popoverLayerFor } from "./layers";
27
+ import type { LayerKind } from "./layers";
18
28
  import { rectOf } from "../utils/geometry";
19
29
  import { loopbackAssetSrc } from "../canvas/canvas-origin";
20
30
  import { previewAssetSrc } from "../canvas/content-assets";
@@ -25,28 +35,42 @@ import {
25
35
  extensionOf,
26
36
  uploadAssets,
27
37
  } from "../files/media-upload";
38
+ import {
39
+ invalidateMediaMeta,
40
+ mediaMetaSummary,
41
+ peekMediaMeta,
42
+ recordImageSize,
43
+ seedMediaMeta,
44
+ } from "../files/media-meta";
45
+ import { mediaSiteUrl } from "../files/media-paths";
28
46
 
29
47
  // ─── Media file cache ────────────────────────────────────────────────────────
30
48
 
31
- /** @type {{ path: string; name: string; isImage: boolean }[]} */
32
- let mediaCache: { path: string; name: string; isImage: boolean }[] = [];
49
+ /** One browsable media file: the ref a pick writes, and the file that ref names. */
50
+ interface MediaEntry {
51
+ /** The value committed to the field — the site URL, which is the authored form. */
52
+ path: string;
53
+ /** Project-relative path on disk, the key everything in `media-meta` is stored under. */
54
+ file: string;
55
+ name: string;
56
+ isImage: boolean;
57
+ }
58
+
59
+ let mediaCache: MediaEntry[] = [];
33
60
  let mediaCacheLoaded = false;
34
61
 
35
62
  /**
36
- * Recursively collect media files from a directory.
37
- *
38
- * @param {string} dir
39
- * @param {ReturnType<typeof getPlatform>} platform
40
- * @returns {Promise<{ path: string; name: string; isImage: boolean }[]>}
63
+ * Recursively collect media files from a directory, seeding {@link seedMediaMeta} with the listing
64
+ * on the way past — the size of every row is already in the response that enumerated it.
41
65
  */
42
66
  async function collectMedia(
43
67
  dir: string,
44
68
  platform: ReturnType<typeof getPlatform>,
45
- ): Promise<{ path: string; name: string; isImage: boolean }[]> {
46
- /** @type {{ path: string; name: string; isImage: boolean }[]} */
47
- const results = [];
69
+ ): Promise<MediaEntry[]> {
70
+ const results: MediaEntry[] = [];
48
71
  try {
49
72
  const entries = await platform.listDirectory(dir);
73
+ seedMediaMeta(entries);
50
74
  for (const entry of entries) {
51
75
  if (entry.type === "directory") {
52
76
  const sub = await collectMedia(entry.path, platform);
@@ -55,9 +79,12 @@ async function collectMedia(
55
79
  const ext = extensionOf(entry.name);
56
80
  if (MEDIA_EXTENSIONS.has(ext)) {
57
81
  results.push({
82
+ file: entry.path,
58
83
  isImage: IMAGE_EXTENSIONS.has(ext),
59
84
  name: entry.name,
60
- path: `/${entry.path}`,
85
+ // The site URL is what production serves and therefore what a document should say.
86
+ // Media-paths holds the one definition of that mapping.
87
+ path: mediaSiteUrl(entry.path),
61
88
  });
62
89
  }
63
90
  }
@@ -74,20 +101,22 @@ async function loadMediaCache() {
74
101
  }
75
102
  const platform = getPlatform();
76
103
  mediaCache = await collectMedia("public", platform);
77
- // Strip "public/" prefix so paths match production (public/ contents served at root)
78
- for (const m of mediaCache) {
79
- m.path = m.path.replace(/^\/public\//, "/");
80
- }
81
104
  mediaCacheLoaded = true;
82
105
  // Re-render the host panels so the browse popover has entries to show once the async listing
83
106
  // Resolves. Mirrors loadLayoutEntries()'s renderOnly() in head-panel.
84
107
  renderOnly("leftPanel", "rightPanel", "frontmatterPanel");
85
108
  }
86
109
 
87
- /** Force media cache reload (e.g. after upload). */
110
+ /**
111
+ * Force media cache reload (e.g. after upload).
112
+ *
113
+ * The metadata cache goes with it, and for the same reason: both were derived from a listing that
114
+ * is now out of date, and a size that survives the write it contradicts is worse than no size.
115
+ */
88
116
  export function invalidateMediaCache() {
89
117
  mediaCache = [];
90
118
  mediaCacheLoaded = false;
119
+ invalidateMediaMeta();
91
120
  }
92
121
 
93
122
  // ─── Popover state ───────────────────────────────────────────────────────────
@@ -97,6 +126,32 @@ let _popoverOnCommit: ((val: string) => void) | null = null;
97
126
 
98
127
  /** @type {HTMLElement | null} */
99
128
  let _popoverAnchorEl: HTMLElement | null = null;
129
+ /**
130
+ * Which layer the open popover is living in.
131
+ *
132
+ * Chosen from the anchor at open time and then REMEMBERED, because `getLayerSlot` keys its slots by
133
+ * `${layer}:${id}`: dismissing through a different layer than the one that opened would clear an
134
+ * empty slot and leave the real popover on screen.
135
+ */
136
+ let _popoverLayer: LayerKind = "popover";
137
+
138
+ /** The open popover's slot — the one place that names the layer and the id together. */
139
+ function popoverSlot(): HTMLElement {
140
+ return getLayerSlot(_popoverLayer, "media-picker");
141
+ }
142
+
143
+ /**
144
+ * The popover's z-index, which depends on the company it is keeping.
145
+ *
146
+ * In `#layer-popover` it has the layer to itself and 30 is enough — that is what it has always
147
+ * been, and every panel-hosted picker keeps rendering identically. Sharing a layer with a modal
148
+ * body is different: those declare `z-index: 1000` (`.seo-modal`, `.about-modal`, `.settings-modal`
149
+ * — the house shape), so a picker anchored inside one is a later sibling that still paints beneath
150
+ * it. Beating that number is the whole point of moving layers in the first place.
151
+ */
152
+ function popoverZIndex(): number {
153
+ return _popoverLayer === "popover" ? 30 : 1001;
154
+ }
100
155
 
101
156
  /** @type {HTMLInputElement | null} */
102
157
  let _popoverFilterEl: HTMLInputElement | null = null;
@@ -110,7 +165,7 @@ function dismissMediaPickerPopover() {
110
165
  _popoverFilterEl = null;
111
166
  document.removeEventListener("keydown", onPopoverKeydown, true);
112
167
  document.removeEventListener("mousedown", onPopoverOutsideClick, true);
113
- litRender(nothing, getLayerSlot("popover", "media-picker"));
168
+ litRender(nothing, popoverSlot());
114
169
  }
115
170
 
116
171
  /** @param {KeyboardEvent} e */
@@ -124,14 +179,38 @@ function onPopoverKeydown(e: KeyboardEvent) {
124
179
 
125
180
  /** @param {MouseEvent} e */
126
181
  function onPopoverOutsideClick(e: MouseEvent) {
127
- const host = getLayerSlot("popover", "media-picker");
182
+ const host = popoverSlot();
128
183
  if (!host.contains(e.target as Node)) {
129
184
  dismissMediaPickerPopover();
130
185
  }
131
186
  }
132
187
 
188
+ /** A repaint scheduled by a thumbnail that just reported its size, or 0 when none is pending. */
189
+ let _sizeRepaint = 0;
190
+
191
+ /**
192
+ * Fold a loaded thumbnail's intrinsic size into the metadata cache and, if that was news, repaint
193
+ * the popover once so the caption appears.
194
+ *
195
+ * The repaint is coalesced across a whole grid of images landing in the same frame, and it cannot
196
+ * loop: re-rendering recreates the `<img>` elements, whose `load` fires again from cache, and
197
+ * `recordImageSize` returns false the second time because the measurement has not changed.
198
+ */
199
+ function noteImageSize(file: string, target: EventTarget | null) {
200
+ const img = target as HTMLImageElement | null;
201
+ if (!img || !recordImageSize(file, img.naturalWidth, img.naturalHeight) || _sizeRepaint !== 0) {
202
+ return;
203
+ }
204
+ _sizeRepaint = requestAnimationFrame(() => {
205
+ _sizeRepaint = 0;
206
+ if (_popoverAnchorEl) {
207
+ renderMediaPickerPopover();
208
+ }
209
+ });
210
+ }
211
+
133
212
  function renderMediaPickerPopover() {
134
- const host = getLayerSlot("popover", "media-picker");
213
+ const host = popoverSlot();
135
214
  const rect = _popoverAnchorEl ? rectOf(_popoverAnchorEl) : undefined;
136
215
  if (!rect) {
137
216
  return;
@@ -166,10 +245,11 @@ function renderMediaPickerPopover() {
166
245
  html`
167
246
  <sp-popover
168
247
  open
248
+ data-jx-region="overlay.menu:media-picker"
169
249
  ${ref((el) => {
170
250
  _popoverEl = (el as HTMLElement | undefined) || null;
171
251
  })}
172
- style="position:fixed;left:${left}px;top:${top}px;z-index:30;max-height:360px;overflow-y:auto;min-width:240px"
252
+ style="position:fixed;left:${left}px;top:${top}px;z-index:${popoverZIndex()};max-height:360px;overflow-y:auto;min-width:240px"
173
253
  >
174
254
  <input
175
255
  class="media-picker-filter"
@@ -195,8 +275,9 @@ function renderMediaPickerPopover() {
195
275
  >
196
276
  ${
197
277
  options.length > 0
198
- ? options.map(
199
- (m) => html`
278
+ ? options.map((m) => {
279
+ const caption = mediaMetaSummary(peekMediaMeta(m.file));
280
+ return html`
200
281
  <sp-menu-item value=${m.path}>
201
282
  ${
202
283
  m.isImage
@@ -205,13 +286,15 @@ function renderMediaPickerPopover() {
205
286
  src=${loopbackAssetSrc(m.path)}
206
287
  alt=""
207
288
  style="width:24px;height:24px;object-fit:cover;border-radius:var(--spectrum-corner-radius-75, 2px)"
289
+ @load=${(e: Event) => noteImageSize(m.file, e.target)}
208
290
  />`
209
291
  : nothing
210
292
  }
211
293
  ${m.name}
294
+ ${caption ? html`<span slot="description">${caption}</span>` : nothing}
212
295
  </sp-menu-item>
213
- `,
214
- )
296
+ `;
297
+ })
215
298
  : html`<sp-menu-item disabled>No matches</sp-menu-item>`
216
299
  }
217
300
  ${
@@ -270,6 +353,8 @@ function showMediaPickerPopover(anchorEl: HTMLElement, onCommit: (val: string) =
270
353
  dismissMediaPickerPopover();
271
354
  _popoverOnCommit = onCommit;
272
355
  _popoverAnchorEl = anchorEl;
356
+ // Before the first render, so every later `popoverSlot()` agrees with where it was drawn.
357
+ _popoverLayer = popoverLayerFor(anchorEl);
273
358
  _popoverFilter = "";
274
359
  renderMediaPickerPopover();
275
360
  document.addEventListener("keydown", onPopoverKeydown, true);
@@ -362,6 +447,11 @@ export function renderMediaPicker(prop: string, value: string, onCommit: (val: s
362
447
  >
363
448
  <sp-icon-upload slot="icon"></sp-icon-upload>
364
449
  </sp-action-button>
450
+ <!-- No data-jx-region here. The class IS the handle: ui/regions.ts derives
451
+ inspector/field:PROP/browse by finding this button inside the Inspector's own
452
+ [data-prop] row, so the id resolves to one element however many panes are drawing a
453
+ media picker. The stamp claimed an inspector/... id on every picker the app renders,
454
+ including the two the Document Header card draws inside each pane's STAGE. -->
365
455
  <sp-action-button
366
456
  class="media-picker-browse"
367
457
  size="xs"
@@ -1,76 +1,88 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Panel-resize.js — Draggable resize handles for left and right sidebars.
3
+ * Panel-resize.js — Draggable resize handles for the three docks.
4
4
  *
5
- * Self-initializing module. Import it and the resize handles become interactive. Persists widths to
6
- * localStorage so they survive page reloads.
5
+ * Self-initializing module. Import it and the resize handles become interactive.
6
+ *
7
+ * The handles are the only thing here: dock sizes, their persistence and their projection onto the
8
+ * grid's CSS custom properties all belong to the reactive `shell` record (`../shell`). A drag
9
+ * writes `setDockSize()` and the shell's own effect moves the track; release persists once.
7
10
  */
8
11
 
9
- import { applyPanelCollapse, view } from "../view";
10
-
11
- const STORAGE_KEY = "jx-studio-panel-widths";
12
- const MIN_WIDTH = 160;
13
- const MAX_RATIO = 0.5; // Max 50% of viewport
14
- const DEFAULT_LEFT = 240;
15
- const DEFAULT_RIGHT = 280;
16
- const DEFAULT_CHAT = 320;
12
+ import {
13
+ DOCK_DEFAULT_SIZES,
14
+ persistDocks,
15
+ registerShellSurface,
16
+ setDockSize,
17
+ shell,
18
+ } from "../shell";
19
+ import type { DockId } from "../shell";
17
20
 
18
- const root = document.documentElement;
19
-
20
- /** Read a px-valued CSS custom property as a number (e.g. "320px" → 320). */
21
- function readPxVar(cssVar: string): number {
22
- return Number(getComputedStyle(root).getPropertyValue(cssVar).replace(/px$/, ""));
21
+ /**
22
+ * What a handle drags.
23
+ *
24
+ * Generalised out of the three dock rows so the pane splitter can be the fourth. A dock is sized in
25
+ * px and a pane split is a ratio, which is the whole of the difference: `scale` converts a pointer
26
+ * delta in px into the target's own units, and everything else — capture, the dragging class, the
27
+ * text-selection suppression, the double-click reset, the one persist on release — is identical and
28
+ * was worth having once.
29
+ */
30
+ export interface ResizeTarget {
31
+ /** Which coordinate the drag reads. */
32
+ axis: "x" | "y";
33
+ /** The current value. */
34
+ read: () => number;
35
+ /** Set it. Called on every pointermove, so it must be cheap and idempotent. */
36
+ write: (value: number) => void;
37
+ /** The value a double-click restores. */
38
+ reset: () => number;
39
+ /** Lower and upper bounds, read fresh because both can depend on the viewport. */
40
+ min: () => number;
41
+ max: () => number;
42
+ /** Target units per pixel of pointer movement, signed: negative grows toward the origin. */
43
+ scale: () => number;
44
+ /** Persist. Called once on release and once on reset — never during the drag. */
45
+ settle: () => void;
23
46
  }
24
47
 
25
- // ─── Restore saved widths & collapse state ──────────────────────────────────
48
+ /**
49
+ * The smallest a dock may be dragged to, per axis.
50
+ *
51
+ * The Bottom dock's floor is lower because its content is rows of text: 120px is four problems or
52
+ * two activity rows, which is a useful dock, where 160px of a 24-row list is not meaningfully
53
+ * more.
54
+ */
55
+ const MIN_SIZE: Readonly<Record<"x" | "y", number>> = { x: 160, y: 120 };
26
56
 
27
- try {
28
- const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}") as {
29
- left?: number;
30
- right?: number;
31
- chat?: number;
32
- leftCollapsed?: boolean;
33
- rightCollapsed?: boolean;
34
- chatCollapsed?: boolean;
35
- };
36
- if (saved.left) {
37
- root.style.setProperty("--panel-w-left", `${saved.left}px`);
38
- }
39
- if (saved.right) {
40
- root.style.setProperty("--panel-w-right", `${saved.right}px`);
41
- }
42
- if (saved.chat) {
43
- root.style.setProperty("--panel-w-chat", `${saved.chat}px`);
44
- }
45
- if (saved.leftCollapsed) {
46
- view.leftPanelCollapsed = true;
47
- }
48
- if (saved.rightCollapsed) {
49
- view.rightPanelCollapsed = true;
50
- }
51
- if (saved.chatCollapsed) {
52
- view.chatPanelCollapsed = true;
53
- }
54
- applyPanelCollapse();
55
- } catch {
56
- // Ignore
57
- }
57
+ /** The largest, as a fraction of the viewport along the dragged axis. */
58
+ const MAX_RATIO = 0.5;
58
59
 
59
- // ─── Setup handles ───────────────────────────────────────────────────────────
60
+ /**
61
+ * Which handle drives which dock, and which direction grows it.
62
+ *
63
+ * Three rows, and the third resizes on the other axis: `grow` is the sign a pointer moving in the
64
+ * positive direction of `axis` contributes, so the Navigator grows rightward, and the Inspector and
65
+ * the Bottom dock grow back toward the pointer's origin. The assistant is not here and never will
66
+ * be — it is an Inspector TAB, resized by resizing the Inspector.
67
+ */
68
+ const HANDLES: { selector: string; dock: DockId; axis: "x" | "y"; grow: 1 | -1 }[] = [
69
+ { axis: "x", dock: "left", grow: 1, selector: "#resize-left" },
70
+ { axis: "x", dock: "right", grow: -1, selector: "#resize-right" },
71
+ { axis: "y", dock: "bottom", grow: -1, selector: "#resize-bottom" },
72
+ ];
60
73
 
61
74
  /**
75
+ * Wire one handle to one dock.
76
+ *
62
77
  * @param {HTMLElement} handle
63
- * @param {string} cssVar
64
- * @param {"left" | "right"} side
65
- * @param {number} defaultWidth
78
+ * @param {DockId} dock
79
+ * @param {"x" | "y"} axis — which coordinate the drag reads
80
+ * @param {1 | -1} grow — the sign a positive move along `axis` contributes to the dock's size
66
81
  */
67
- function setupHandle(
68
- handle: HTMLElement,
69
- cssVar: string,
70
- side: "left" | "right",
71
- defaultWidth: number,
72
- ) {
73
- let drag: { startX: number; startWidth: number } | null = null;
82
+ export function setupHandle(handle: HTMLElement, target: ResizeTarget) {
83
+ const { axis } = target;
84
+ let drag: { start: number; startSize: number } | null = null;
85
+ const coord = (e: { clientX: number; clientY: number }) => (axis === "x" ? e.clientX : e.clientY);
74
86
 
75
87
  handle.addEventListener("pointerdown", (e) => {
76
88
  e.preventDefault();
@@ -81,19 +93,17 @@ function setupHandle(
81
93
  }
82
94
  handle.classList.add("dragging");
83
95
  document.body.style.userSelect = "none";
84
-
85
- const current = readPxVar(cssVar) || defaultWidth;
86
- drag = { startWidth: current, startX: e.clientX };
96
+ drag = { start: coord(e), startSize: target.read() };
87
97
  });
88
98
 
89
99
  handle.addEventListener("pointermove", (e) => {
90
100
  if (!drag) {
91
101
  return;
92
102
  }
93
- const delta = side === "left" ? e.clientX - drag.startX : drag.startX - e.clientX;
94
- const maxWidth = window.innerWidth * MAX_RATIO;
95
- const newWidth = Math.round(Math.min(maxWidth, Math.max(MIN_WIDTH, drag.startWidth + delta)));
96
- root.style.setProperty(cssVar, `${newWidth}px`);
103
+ const delta = (coord(e) - drag.start) * target.scale();
104
+ const wanted = drag.startSize + delta;
105
+ const floored = Math.max(target.min(), wanted);
106
+ target.write(Math.min(target.max(), floored));
97
107
  });
98
108
 
99
109
  handle.addEventListener("pointerup", (e) => {
@@ -108,38 +118,46 @@ function setupHandle(
108
118
  }
109
119
  handle.classList.remove("dragging");
110
120
  document.body.style.userSelect = "";
111
- persistWidths();
121
+ target.settle();
112
122
  });
113
123
 
114
124
  handle.addEventListener("dblclick", () => {
115
- root.style.setProperty(cssVar, `${defaultWidth}px`);
116
- persistWidths();
125
+ target.write(target.reset());
126
+ target.settle();
117
127
  });
118
128
  }
119
129
 
120
- function persistWidths() {
121
- const left = readPxVar("--panel-w-left") || DEFAULT_LEFT;
122
- const right = readPxVar("--panel-w-right") || DEFAULT_RIGHT;
123
- const chat = readPxVar("--panel-w-chat") || DEFAULT_CHAT;
124
- try {
125
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ chat, left, right }));
126
- } catch {
127
- // Storage full or unavailable
128
- }
130
+ /** The largest a dock may be dragged to along `axis`. */
131
+ function maxDockSize(axis: "x" | "y"): number {
132
+ const viewport = axis === "x" ? window.innerWidth : window.innerHeight;
133
+ return viewport * MAX_RATIO;
129
134
  }
130
135
 
131
- // ─── Initialize ──────────────────────────────────────────────────────────────
132
-
133
- const resizeLeft = document.querySelector<HTMLElement>("#resize-left");
134
- const resizeRight = document.querySelector<HTMLElement>("#resize-right");
135
- const resizeChat = document.querySelector<HTMLElement>("#resize-chat");
136
-
137
- if (resizeLeft) {
138
- setupHandle(resizeLeft, "--panel-w-left", "left", DEFAULT_LEFT);
139
- }
140
- if (resizeRight) {
141
- setupHandle(resizeRight, "--panel-w-right", "right", DEFAULT_RIGHT);
136
+ /** A dock, as a {@link ResizeTarget}. The three rows of {@link HANDLES}, given the shared shape. */
137
+ function dockTarget(dock: DockId, axis: "x" | "y", grow: 1 | -1): ResizeTarget {
138
+ return {
139
+ axis,
140
+ max: () => maxDockSize(axis),
141
+ min: () => MIN_SIZE[axis],
142
+ read: () => shell.docks[dock].size,
143
+ reset: () => DOCK_DEFAULT_SIZES[dock],
144
+ scale: () => grow,
145
+ settle: () => persistDocks(),
146
+ write: (value) => setDockSize(dock, Math.round(value)),
147
+ };
142
148
  }
143
- if (resizeChat) {
144
- setupHandle(resizeChat, "--panel-w-chat", "right", DEFAULT_CHAT);
149
+
150
+ /** Attach every handle present in the document. Idempotent per element by construction. */
151
+ export function mountPanelResize(): void {
152
+ for (const { axis, dock, grow, selector } of HANDLES) {
153
+ const handle = document.querySelector<HTMLElement>(selector);
154
+ if (handle) {
155
+ setupHandle(handle, dockTarget(dock, axis, grow));
156
+ }
157
+ }
145
158
  }
159
+
160
+ /* Mounted through the shell's own lifecycle rather than by a bare `mountPanelResize()` at module
161
+ scope. The import-time call read `document` before anything had said the tree existed, which is
162
+ the one part of `registerShellSurface`'s bargain this module was not keeping. */
163
+ registerShellSurface({ mount: mountPanelResize, unmount: () => {} });