@jxsuite/studio 6.1.2 → 8.0.1

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 (522) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/2020-5wtzce5g.js +4 -0
  3. package/dist/chunks/adapter-5tphg7xe.js +4 -0
  4. package/dist/chunks/add-new-flow-psym14sn.js +4 -0
  5. package/dist/chunks/cf-account-picker-haqazq5r.js +4 -0
  6. package/dist/chunks/cf-settings-h2wrh6hq.js +4 -0
  7. package/dist/chunks/{color-area-0fj8a4ga.js → color-area-6rbra5dy.js} +2 -2
  8. package/dist/chunks/{color-field-xch8kx2g.js → color-field-gy56c94g.js} +3 -3
  9. package/dist/chunks/color-m7r1vk54.js +4 -0
  10. package/dist/chunks/{color-slider-jxd8v350.js → color-slider-sqqdyf6c.js} +2 -2
  11. package/dist/chunks/{combobox-p0efwjke.js → combobox-72h32ejk.js} +3 -3
  12. package/dist/chunks/components-r22xca09.js +4 -0
  13. package/dist/chunks/content-source-mmppmcyg.js +4 -0
  14. package/dist/chunks/convert-to-repeater-he70fc2b.js +4 -0
  15. package/dist/chunks/editor-ccd9jwsa.js +4 -0
  16. package/dist/chunks/entry-commands-6qxz9e8p.js +4 -0
  17. package/dist/chunks/extension-sections-0egb88dm.js +4 -0
  18. package/dist/chunks/file-ops-qnab36ek.js +4 -0
  19. package/dist/chunks/files-rcn56qvm.js +4 -0
  20. package/dist/chunks/format-host-yxz0kjjs.js +4 -0
  21. package/dist/chunks/fs-events-d1qxq4z4.js +4 -0
  22. package/dist/chunks/grid-open-4z1g062t.js +4 -0
  23. package/dist/chunks/{grid-view-d2wq9ezg.js → grid-view-qg1b5eqx.js} +25 -25
  24. package/dist/chunks/{icons-pnfz478q.js → icons-63bjtbs7.js} +2 -2
  25. package/dist/chunks/{iframe-entry-jdcktez8.js → iframe-entry-2zggzajv.js} +4 -4
  26. package/dist/chunks/{iframe-entry-zap785cw.js → iframe-entry-76j8vwwx.js} +4 -4
  27. package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
  28. package/dist/chunks/{iframe-entry-x0ygf2yr.js → iframe-entry-pkgmcgp0.js} +4 -4
  29. package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
  30. package/dist/chunks/{index-zpjmzqfk.js → index-32eqzv1k.js} +3 -3
  31. package/dist/chunks/index-f0jn7nnp.js +4 -0
  32. package/dist/chunks/{javascript-m54mnwp6.js → javascript-pjp734q4.js} +3 -3
  33. package/dist/chunks/jsonMode-njkvsa02.js +13 -0
  34. package/dist/chunks/jx-validate-ttasrbh7.js +4 -0
  35. package/dist/chunks/layers-bgyr11tr.js +4 -0
  36. package/dist/chunks/layers-panel-7ez66zeq.js +4 -0
  37. package/dist/chunks/{listbox-e74qjyq6.js → listbox-c421cf8c.js} +2 -2
  38. package/dist/chunks/lit-html-v9gh687x.js +4 -0
  39. package/dist/chunks/media-library-sq468wts.js +4 -0
  40. package/dist/chunks/media-picker-shfe59pt.js +4 -0
  41. package/dist/chunks/{menu-n6yzswmy.js → menu-bdpzbfvs.js} +2 -2
  42. package/dist/chunks/{monaco-binding-7gx83x3r.js → monaco-binding-vq1hgnn0.js} +3 -3
  43. package/dist/chunks/monaco-buffer-3afgwvbp.js +4 -0
  44. package/dist/chunks/monaco-setup-xrd2cbkp.js +4 -0
  45. package/dist/chunks/notify-kzrjr3fv.js +4 -0
  46. package/dist/chunks/parse-v77xtwv7.js +4 -0
  47. package/dist/chunks/platform-7v4rtd7m.js +4 -0
  48. package/dist/chunks/popover-b89sfej7.js +4 -0
  49. package/dist/chunks/publish-panel-2z9gw8ee.js +4 -0
  50. package/dist/chunks/publish-quick-qx2455cj.js +4 -0
  51. package/dist/chunks/publish-workspace-wrc1dgwx.js +4 -0
  52. package/dist/chunks/quick-search-n0t0eqxb.js +4 -0
  53. package/dist/chunks/references-9547rg5s.js +4 -0
  54. package/dist/chunks/right-panel-wsgnr9pn.js +4 -0
  55. package/dist/chunks/source-control-kwktr9y6.js +4 -0
  56. package/dist/chunks/{split-n2gfpm5m.js → split-c9zfdgxf.js} +2 -2
  57. package/dist/chunks/studio-03ct6yfv.js +9 -0
  58. package/dist/chunks/studio-07rcfje3.js +5 -0
  59. package/dist/chunks/studio-3e54qh39.js +7 -0
  60. package/dist/chunks/studio-3exxhz5g.js +5 -0
  61. package/dist/chunks/studio-3q40e3rj.js +5 -0
  62. package/dist/chunks/studio-3qeh9t0h.js +6 -0
  63. package/dist/chunks/studio-3qqjeep5.js +6 -0
  64. package/dist/chunks/studio-43sd2mam.js +4 -0
  65. package/dist/chunks/studio-4hp8pkyt.js +5 -0
  66. package/dist/chunks/studio-58xg6j2q.js +9 -0
  67. package/dist/chunks/studio-5g0rprdt.js +7 -0
  68. package/dist/chunks/{studio-2j1cb95x.js → studio-6m380wp7.js} +4 -4
  69. package/dist/chunks/studio-80jyh1xh.js +4 -0
  70. package/dist/chunks/studio-83mk4bgg.js +9 -0
  71. package/dist/chunks/{studio-v0jzqdjm.js → studio-881z1xrx.js} +4 -4
  72. package/dist/chunks/studio-8m0y2qe7.js +5 -0
  73. package/dist/chunks/{studio-d81xfh1g.js → studio-8tr6cfrc.js} +4 -4
  74. package/dist/chunks/studio-8vcvfs7r.js +6 -0
  75. package/dist/chunks/studio-9tmebf0s.js +5 -0
  76. package/dist/chunks/studio-a2bvaptz.js +5 -0
  77. package/dist/chunks/studio-b7y5mr30.js +5 -0
  78. package/dist/chunks/studio-bavb31r0.js +5 -0
  79. package/dist/chunks/{studio-dgz8d0c1.js → studio-bbe4c90a.js} +4 -4
  80. package/dist/chunks/studio-c05wdnt3.js +5 -0
  81. package/dist/chunks/studio-ccv2svc6.js +5 -0
  82. package/dist/chunks/studio-d5grtb9e.js +16 -0
  83. package/dist/chunks/studio-df0baea9.js +5 -0
  84. package/dist/chunks/studio-dgk4dqtx.js +7 -0
  85. package/dist/chunks/studio-e1g64q43.js +5 -0
  86. package/dist/chunks/studio-ed1hp6zf.js +6 -0
  87. package/dist/chunks/studio-ed3xx40h.js +7 -0
  88. package/dist/chunks/studio-f14e1zdj.js +5 -0
  89. package/dist/chunks/studio-fbhxxc5f.js +5 -0
  90. package/dist/chunks/studio-fyh0jc96.js +5 -0
  91. package/dist/chunks/studio-gcxk20n7.js +5 -0
  92. package/dist/chunks/studio-jaaz8ce9.js +5 -0
  93. package/dist/chunks/{studio-90pq9km1.js → studio-jcnsdaep.js} +4 -4
  94. package/dist/chunks/studio-jsgf2q5k.js +7 -0
  95. package/dist/chunks/{studio-0q988f9w.js → studio-k3xxbagr.js} +3 -3
  96. package/dist/chunks/{studio-5ve6bbep.js → studio-k7evxqh4.js} +4 -4
  97. package/dist/chunks/studio-kt3pjkwd.js +5 -0
  98. package/dist/chunks/studio-m8g16bgz.js +5 -0
  99. package/dist/chunks/studio-mn0476ag.js +5 -0
  100. package/dist/chunks/studio-mx1smyq0.js +5 -0
  101. package/dist/chunks/studio-n0c5h6mf.js +5 -0
  102. package/dist/chunks/{studio-485046te.js → studio-nh6d342n.js} +7 -7
  103. package/dist/chunks/studio-nqxqf8p2.js +4 -0
  104. package/dist/chunks/studio-p4nrkvqr.js +5 -0
  105. package/dist/chunks/{studio-1fxkaaea.js → studio-pbxa5fpc.js} +4 -4
  106. package/dist/chunks/studio-pgmvdv7z.js +7 -0
  107. package/dist/chunks/studio-ph84avkf.js +5 -0
  108. package/dist/chunks/{studio-tq0rtfwj.js → studio-q0mvbaq6.js} +4 -4
  109. package/dist/chunks/studio-q0tccd43.js +5 -0
  110. package/dist/chunks/studio-q7rtx6zq.js +14 -0
  111. package/dist/chunks/{studio-a3g98wjr.js → studio-qgh13jtz.js} +4 -4
  112. package/dist/chunks/studio-qmn1xc24.js +5 -0
  113. package/dist/chunks/studio-qwdqwkj0.js +5 -0
  114. package/dist/chunks/studio-r5qy30vx.js +5 -0
  115. package/dist/chunks/studio-rj9eamf8.js +5 -0
  116. package/dist/chunks/studio-rptgsk96.js +5 -0
  117. package/dist/chunks/studio-rthyg42z.js +928 -0
  118. package/dist/chunks/studio-s19tp1xr.js +5 -0
  119. package/dist/chunks/studio-s32578pn.js +5 -0
  120. package/dist/chunks/studio-svb7yyve.js +5 -0
  121. package/dist/chunks/studio-syreb644.js +5 -0
  122. package/dist/chunks/studio-tf9jryb1.js +10 -0
  123. package/dist/chunks/studio-tfkznv39.js +5 -0
  124. package/dist/chunks/studio-tft6r105.js +5 -0
  125. package/dist/chunks/studio-txf2kysx.js +5 -0
  126. package/dist/chunks/studio-v8867ddv.js +12 -0
  127. package/dist/chunks/studio-vxq7fcvv.js +5 -0
  128. package/dist/chunks/studio-w4rz9hdk.js +5 -0
  129. package/dist/chunks/studio-wbcjbhy1.js +9 -0
  130. package/dist/chunks/studio-wc649wss.js +5 -0
  131. package/dist/chunks/studio-wys23mxz.js +5 -0
  132. package/dist/chunks/studio-x1x167jh.js +5 -0
  133. package/dist/chunks/studio-x9hsze61.js +5 -0
  134. package/dist/chunks/studio-y0n7yxea.js +9 -0
  135. package/dist/chunks/studio-y3ahhdpx.js +5 -0
  136. package/dist/chunks/studio-z97c48fk.js +5 -0
  137. package/dist/chunks/studio-zat62m0c.js +5 -0
  138. package/dist/chunks/studio-zcd2e7yz.js +5 -0
  139. package/dist/chunks/studio-zz9qe3rw.js +64 -0
  140. package/dist/chunks/toast-1gv0h53h.js +4 -0
  141. package/dist/chunks/{toast-host-ke5z4ng7.js → toast-host-1yg5ar8d.js} +3 -3
  142. package/dist/chunks/{tooltip-5v8ngwj2.js → tooltip-yypz28gq.js} +3 -3
  143. package/dist/chunks/{tree-mdq3v5cw.js → tree-9xa4hm7n.js} +2 -2
  144. package/dist/chunks/tsMode-epyfz1hv.js +14 -0
  145. package/dist/chunks/workspace-dz7nxfhv.js +4 -0
  146. package/dist/chunks/workspace-home-xsd7hkjw.js +4 -0
  147. package/dist/chunks/{ws-client-nze0k6mv.js → ws-client-bv552sfx.js} +3 -3
  148. package/dist/iframe-entry.js +20 -16
  149. package/dist/manifest.json +77 -63
  150. package/dist/studio.js +103 -104
  151. package/dist/workers/ts.worker.js +3 -3
  152. package/package.json +12 -11
  153. package/src/browse/component-examples.ts +272 -0
  154. package/src/browse/library-commands.ts +64 -104
  155. package/src/browse/library-layouts.ts +263 -49
  156. package/src/browse/library-model.ts +181 -133
  157. package/src/browse/library-pane.ts +738 -446
  158. package/src/browse/library-preview.ts +149 -19
  159. package/src/browse/library-source.ts +308 -41
  160. package/src/browse/preview-root.ts +78 -0
  161. package/src/browse/preview-style.ts +155 -0
  162. package/src/canvas/asset-refs.ts +16 -1
  163. package/src/canvas/canvas-live-render.ts +222 -30
  164. package/src/canvas/canvas-origin.ts +37 -12
  165. package/src/canvas/canvas-page-context.ts +112 -0
  166. package/src/canvas/canvas-render.ts +113 -107
  167. package/src/canvas/canvas-utils.ts +57 -12
  168. package/src/canvas/iframe-drop.ts +107 -0
  169. package/src/canvas/iframe-entry.ts +24 -12
  170. package/src/canvas/iframe-host.ts +134 -62
  171. package/src/canvas/iframe-keys.ts +11 -0
  172. package/src/canvas/iframe-patch.ts +8 -7
  173. package/src/canvas/iframe-protocol.ts +24 -5
  174. package/src/canvas/iframe-render.ts +48 -1
  175. package/src/canvas/iframe-subtree.ts +5 -2
  176. package/src/canvas/npm-modules.ts +22 -0
  177. package/src/canvas/preview-navigate.ts +16 -0
  178. package/src/canvas/repeater-sources.ts +200 -0
  179. package/src/canvas/stage-scrollbar.ts +5 -1
  180. package/src/canvas/surface-registry.ts +6 -0
  181. package/src/commands/app-commands.ts +16 -6
  182. package/src/commands/budget.ts +10 -8
  183. package/src/commands/context.ts +35 -10
  184. package/src/commands/defaults.ts +69 -45
  185. package/src/commands/keymap.ts +9 -0
  186. package/src/commands/levels.ts +11 -5
  187. package/src/commands/live-context.ts +2 -0
  188. package/src/commands/registry.ts +10 -1
  189. package/src/content/collection-match.ts +6 -5
  190. package/src/content/collection-pages.ts +412 -0
  191. package/src/content/content-collections.ts +270 -0
  192. package/src/content/entry-commands.ts +51 -60
  193. package/src/content/entry-editor.ts +111 -18
  194. package/src/content/entry-fields.ts +13 -1
  195. package/src/content/entry-inspector.ts +225 -0
  196. package/src/content/entry-model.ts +17 -3
  197. package/src/content/entry-open.ts +80 -0
  198. package/src/content/entry-summaries.ts +440 -0
  199. package/src/content/file-record-ids.ts +80 -0
  200. package/src/content/file-records.ts +125 -0
  201. package/src/content/page-summaries.ts +129 -0
  202. package/src/content/record-entry.ts +201 -0
  203. package/src/content/writing-banner.ts +177 -0
  204. package/src/create/add-new-commands.ts +185 -0
  205. package/src/create/add-new-flow.ts +1116 -0
  206. package/src/create/add-new.ts +1014 -0
  207. package/src/create/element-name.ts +121 -0
  208. package/src/editor/convert-to-component.ts +67 -43
  209. package/src/editor/file-drop-action.ts +9 -1
  210. package/src/editor/inline-edit-apply.ts +44 -0
  211. package/src/editor/inline-edit.ts +10 -0
  212. package/src/editor/insert-component.ts +91 -0
  213. package/src/editor/insert-zone-action.ts +13 -4
  214. package/src/editor/shortcuts.ts +44 -22
  215. package/src/editor/slash-menu.ts +51 -8
  216. package/src/files/components.ts +74 -10
  217. package/src/files/file-ops.ts +15 -5
  218. package/src/files/files.ts +164 -150
  219. package/src/files/fs-events.ts +18 -7
  220. package/src/files/media-meta.ts +95 -1
  221. package/src/files/media-types.ts +55 -0
  222. package/src/files/media-upload.ts +209 -52
  223. package/src/files/new-file-name.ts +156 -0
  224. package/src/files/serialize-document.ts +13 -3
  225. package/src/format/format-host.ts +15 -0
  226. package/src/github/github-auth.ts +2 -2
  227. package/src/github/github-publish.ts +4 -3
  228. package/src/grid/grid-controller.ts +5 -1
  229. package/src/grid/grid-open.ts +0 -27
  230. package/src/grid/grid-panel.ts +54 -2
  231. package/src/grid/grid-reveal.ts +34 -0
  232. package/src/grid/grid-source.ts +3 -25
  233. package/src/grid/grid-view.ts +31 -6
  234. package/src/grid/sources/content-source.ts +75 -7
  235. package/src/grid/tabulator-tables.d.ts +6 -0
  236. package/src/hosting/document.ts +2 -2
  237. package/src/i18n/i18n-commands.ts +2 -1
  238. package/src/media/media-commands.ts +181 -0
  239. package/src/media/media-library-model.ts +232 -0
  240. package/src/media/media-library.ts +1283 -0
  241. package/src/media/media-pane.ts +47 -19
  242. package/src/packages/pull-package-sync.ts +1 -1
  243. package/src/panels/activity-panel.ts +21 -5
  244. package/src/panels/ai-chat/chat-markdown.ts +8 -8
  245. package/src/panels/block-action-bar.ts +11 -11
  246. package/src/panels/canvas-dnd-bridge.ts +5 -2
  247. package/src/panels/chat-panel.ts +6 -6
  248. package/src/panels/collections-panel.ts +306 -0
  249. package/src/panels/component-preview.ts +35 -13
  250. package/src/panels/data-explorer.ts +1 -1
  251. package/src/panels/data-grid.ts +4 -0
  252. package/src/panels/dnd.ts +17 -30
  253. package/src/panels/elements-panel.ts +9 -2
  254. package/src/panels/empty-state.ts +2 -2
  255. package/src/panels/formula-workspace.ts +2 -3
  256. package/src/panels/frontmatter-fields.ts +9 -9
  257. package/src/panels/git-diff-open.ts +9 -5
  258. package/src/panels/head-panel.ts +369 -103
  259. package/src/panels/i18n-panel.ts +11 -10
  260. package/src/panels/imports-panel.ts +2 -0
  261. package/src/panels/jump-bar.ts +27 -2
  262. package/src/panels/layers-panel.ts +36 -30
  263. package/src/panels/left-panel.ts +22 -8
  264. package/src/panels/navigator-panels.ts +23 -11
  265. package/src/panels/pane-context.ts +111 -29
  266. package/src/panels/pane-grid.ts +6 -6
  267. package/src/panels/panel-registry.ts +14 -9
  268. package/src/panels/properties-panel.ts +7 -3
  269. package/src/panels/right-panel.ts +199 -43
  270. package/src/panels/seo-modal.ts +41 -108
  271. package/src/panels/settings-menu.ts +52 -50
  272. package/src/panels/settings-pane.ts +268 -162
  273. package/src/panels/signals-panel.ts +3 -4
  274. package/src/panels/site-model.ts +367 -0
  275. package/src/panels/site-panel.ts +638 -0
  276. package/src/panels/style-panel.ts +37 -3
  277. package/src/panels/stylebook-doc.ts +142 -10
  278. package/src/panels/stylebook-layers-panel.ts +39 -80
  279. package/src/panels/stylebook-panel.ts +47 -10
  280. package/src/panels/styles-panel.ts +327 -0
  281. package/src/panels/tab-strip.ts +109 -25
  282. package/src/platform.ts +1 -1
  283. package/src/platforms/cloud.ts +28 -1
  284. package/src/platforms/default-platform.ts +4 -4
  285. package/src/platforms/devserver.ts +51 -25
  286. package/src/publish/deploy-checklist.ts +12 -7
  287. package/src/publish/pages-service.ts +38 -14
  288. package/src/publish/publish-changes.ts +491 -0
  289. package/src/publish/publish-commands.ts +83 -18
  290. package/src/publish/publish-ids.ts +49 -0
  291. package/src/publish/publish-quick.ts +148 -0
  292. package/src/publish/publish-workspace.ts +926 -0
  293. package/src/publish/source-control.ts +907 -0
  294. package/src/services/automation.ts +16 -20
  295. package/src/services/cf-settings.ts +4 -4
  296. package/src/services/connection.ts +2 -2
  297. package/src/services/directory-picker.ts +2 -2
  298. package/src/services/references.ts +0 -0
  299. package/src/services/render-critic.ts +21 -2
  300. package/src/services/settings/definitions.ts +25 -14
  301. package/src/settings/contributed-section.ts +49 -10
  302. package/src/settings/css-vars-editor.ts +93 -27
  303. package/src/settings/developer-panels.ts +43 -0
  304. package/src/settings/extension-sections.ts +20 -2
  305. package/src/settings/general-settings.ts +91 -21
  306. package/src/settings/preferences-dialog.ts +8 -1
  307. package/src/settings/project-sections.ts +27 -10
  308. package/src/settings/schema-field-ui.ts +3 -1
  309. package/src/settings/section-registry.ts +116 -11
  310. package/src/settings/settings-dialog.ts +192 -0
  311. package/src/settings/settings-document.ts +182 -162
  312. package/src/shell.ts +467 -431
  313. package/src/site-context.ts +64 -10
  314. package/src/state.ts +28 -9
  315. package/src/studio.ts +194 -41
  316. package/src/style/project-styles.ts +34 -0
  317. package/src/style/stylebook-specimens.ts +89 -0
  318. package/src/style/stylebook-target.ts +26 -0
  319. package/src/style/tag-inheritance.ts +105 -0
  320. package/src/style/variables-focus.ts +61 -0
  321. package/src/style/variables-view.ts +235 -0
  322. package/src/surfaces/add-new.json +1482 -0
  323. package/src/surfaces/add-new.ts +325 -0
  324. package/src/surfaces/ai-chat.ts +2 -2
  325. package/src/surfaces/boot-failure.json +4 -3
  326. package/src/surfaces/bottom-dock.json +1 -1
  327. package/src/surfaces/canvas-stage.json +30 -19
  328. package/src/surfaces/canvas-stage.ts +5 -5
  329. package/src/surfaces/collections-panel.json +615 -0
  330. package/src/surfaces/collections-panel.ts +168 -0
  331. package/src/surfaces/commandbar.json +241 -96
  332. package/src/surfaces/commandbar.ts +134 -99
  333. package/src/surfaces/empty-state.json +1 -1
  334. package/src/surfaces/entry-editor.json +136 -0
  335. package/src/surfaces/entry-editor.ts +21 -0
  336. package/src/surfaces/entry-inspector.json +371 -0
  337. package/src/surfaces/entry-inspector.ts +143 -0
  338. package/src/surfaces/inspector-dock.json +162 -14
  339. package/src/surfaces/inspector-dock.ts +46 -19
  340. package/src/surfaces/library-pane.json +670 -437
  341. package/src/surfaces/library-pane.ts +94 -59
  342. package/src/surfaces/media-browser.json +62 -6
  343. package/src/surfaces/media-browser.ts +15 -1
  344. package/src/surfaces/media-field.json +1 -1
  345. package/src/surfaces/media-library.json +1586 -0
  346. package/src/surfaces/media-library.ts +282 -0
  347. package/src/surfaces/media-pick-dialog.json +71 -0
  348. package/src/surfaces/media-pick-dialog.ts +132 -0
  349. package/src/surfaces/menu.ts +18 -0
  350. package/src/surfaces/pane-context.json +144 -77
  351. package/src/surfaces/pane-context.ts +24 -0
  352. package/src/surfaces/panel-deploy-checklist.json +12 -6
  353. package/src/surfaces/panel-deploy-checklist.ts +3 -1
  354. package/src/surfaces/panel-page.json +72 -2
  355. package/src/surfaces/panel-page.ts +38 -7
  356. package/src/surfaces/panel-signals.json +1 -1
  357. package/src/surfaces/panel-signals.ts +5 -5
  358. package/src/surfaces/panel-stylebook-layers.json +71 -204
  359. package/src/surfaces/panel-stylebook-layers.ts +0 -21
  360. package/src/surfaces/preferences.json +120 -48
  361. package/src/surfaces/preferences.ts +8 -0
  362. package/src/surfaces/publish-quick.json +428 -0
  363. package/src/surfaces/publish-quick.ts +165 -0
  364. package/src/surfaces/publish-workspace.json +2892 -0
  365. package/src/surfaces/publish-workspace.ts +391 -0
  366. package/src/surfaces/rail.json +1 -159
  367. package/src/surfaces/rail.ts +18 -75
  368. package/src/surfaces/schema-form.json +10 -2
  369. package/src/surfaces/schema-form.ts +2 -0
  370. package/src/surfaces/seo.json +1 -1
  371. package/src/surfaces/settings-contributed.json +5 -5
  372. package/src/surfaces/settings-contributed.ts +18 -2
  373. package/src/surfaces/settings-css-vars.json +44 -36
  374. package/src/surfaces/settings-css-vars.ts +77 -4
  375. package/src/surfaces/settings-deploy.json +2 -15
  376. package/src/surfaces/settings-deploy.ts +1 -1
  377. package/src/surfaces/settings-dialog.json +51 -0
  378. package/src/surfaces/settings-dialog.ts +169 -0
  379. package/src/surfaces/settings-overview.json +3 -3
  380. package/src/surfaces/settings-overview.ts +9 -2
  381. package/src/surfaces/settings-pane.json +269 -46
  382. package/src/surfaces/settings-pane.ts +85 -24
  383. package/src/surfaces/shell.json +2 -1
  384. package/src/surfaces/site-panel.json +1029 -0
  385. package/src/surfaces/site-panel.ts +233 -0
  386. package/src/surfaces/slash-menu.json +63 -21
  387. package/src/surfaces/slash-menu.ts +23 -0
  388. package/src/surfaces/statusbar.ts +39 -15
  389. package/src/surfaces/styles-panel.json +735 -0
  390. package/src/surfaces/styles-panel.ts +195 -0
  391. package/src/surfaces/styles-variables.json +149 -0
  392. package/src/surfaces/styles-variables.ts +145 -0
  393. package/src/surfaces/tab-strip.json +4 -4
  394. package/src/surfaces/welcome.ts +1 -1
  395. package/src/surfaces/writing-banner.json +94 -0
  396. package/src/surfaces/writing-banner.ts +80 -0
  397. package/src/tabs/bare-repeaters.ts +101 -0
  398. package/src/tabs/tab.ts +47 -7
  399. package/src/types.ts +21 -26
  400. package/src/ui/form-controls.ts +47 -8
  401. package/src/ui/layers.ts +13 -0
  402. package/src/ui/media-picker.ts +284 -40
  403. package/src/ui/regions.ts +7 -6
  404. package/src/ui/schema-form.ts +259 -27
  405. package/src/ui/virtual-window.ts +26 -1
  406. package/src/utils/base64.ts +3 -3
  407. package/src/utils/edit-display.ts +322 -67
  408. package/src/utils/local-day.ts +18 -0
  409. package/src/workspace/pane-derive.ts +4 -4
  410. package/src/workspace/session.ts +28 -7
  411. package/src/workspace/tab-workspace.ts +137 -0
  412. package/src/workspace/workspace-back.ts +189 -0
  413. package/src/workspace/workspace-home.ts +133 -0
  414. package/src/workspace/workspace-ids.ts +129 -0
  415. package/src/workspace/workspace-switch.ts +372 -0
  416. package/src/workspace/workspace.ts +125 -11
  417. package/styles/shell-frame.css +30 -7
  418. package/styles/shell-frame.json +15 -3
  419. package/dist/chunks/2020-f2gkpy8d.js +0 -4
  420. package/dist/chunks/adapter-4912g339.js +0 -4
  421. package/dist/chunks/cf-account-picker-wccaenh6.js +0 -4
  422. package/dist/chunks/cf-settings-br6105tr.js +0 -4
  423. package/dist/chunks/color-28ksk2qm.js +0 -4
  424. package/dist/chunks/components-db68vsn7.js +0 -4
  425. package/dist/chunks/content-source-1449qesq.js +0 -4
  426. package/dist/chunks/convert-to-repeater-5h457egj.js +0 -4
  427. package/dist/chunks/editor-x9jn86a7.js +0 -4
  428. package/dist/chunks/entry-commands-may24cpt.js +0 -4
  429. package/dist/chunks/extension-sections-pvr14vxr.js +0 -4
  430. package/dist/chunks/file-ops-x9v54pcj.js +0 -4
  431. package/dist/chunks/files-j3knqf0s.js +0 -4
  432. package/dist/chunks/format-host-tyre2trb.js +0 -4
  433. package/dist/chunks/fs-events-64ma9htc.js +0 -4
  434. package/dist/chunks/git-panel-84ps5dex.js +0 -4
  435. package/dist/chunks/iframe-entry-fvbk89gm.js +0 -5
  436. package/dist/chunks/iframe-host-ck6vejt5.js +0 -4
  437. package/dist/chunks/index-b8xw3vy9.js +0 -4
  438. package/dist/chunks/jsonMode-w15cmrcm.js +0 -13
  439. package/dist/chunks/jx-validate-at3zn8wa.js +0 -4
  440. package/dist/chunks/layers-h4xp1pss.js +0 -4
  441. package/dist/chunks/layers-panel-m5hw1d7g.js +0 -4
  442. package/dist/chunks/lit-html-8t8t3dnr.js +0 -4
  443. package/dist/chunks/media-picker-2cweexc5.js +0 -4
  444. package/dist/chunks/menu-ynw13y0s.js +0 -4
  445. package/dist/chunks/monaco-buffer-qzxn567r.js +0 -4
  446. package/dist/chunks/monaco-setup-gxf6656g.js +0 -4
  447. package/dist/chunks/notify-cj5zbrs2.js +0 -4
  448. package/dist/chunks/parse-b7ddhr8m.js +0 -4
  449. package/dist/chunks/platform-3dfws9js.js +0 -4
  450. package/dist/chunks/popover-jt2gxmzs.js +0 -4
  451. package/dist/chunks/publish-panel-1gvxbj21.js +0 -4
  452. package/dist/chunks/quick-search-0dajrwqg.js +0 -4
  453. package/dist/chunks/references-49qgt404.js +0 -4
  454. package/dist/chunks/right-panel-jm1bwe53.js +0 -4
  455. package/dist/chunks/section-registry-6kwc4fky.js +0 -4
  456. package/dist/chunks/studio-0rt52vpq.js +0 -5
  457. package/dist/chunks/studio-18vgbhs5.js +0 -5
  458. package/dist/chunks/studio-24mccdrj.js +0 -5
  459. package/dist/chunks/studio-2eh507zk.js +0 -9
  460. package/dist/chunks/studio-2hcbccfv.js +0 -5
  461. package/dist/chunks/studio-2jbs3rxv.js +0 -5
  462. package/dist/chunks/studio-4h8k4dw9.js +0 -5
  463. package/dist/chunks/studio-5f6n18kx.js +0 -5
  464. package/dist/chunks/studio-5gthpky6.js +0 -5
  465. package/dist/chunks/studio-6ep3js6b.js +0 -9
  466. package/dist/chunks/studio-6wkmf3wz.js +0 -9
  467. package/dist/chunks/studio-841rf3cg.js +0 -5
  468. package/dist/chunks/studio-8gmwgheg.js +0 -5
  469. package/dist/chunks/studio-8qm0ep50.js +0 -5
  470. package/dist/chunks/studio-924p1tm1.js +0 -6
  471. package/dist/chunks/studio-a3shpz9y.js +0 -5
  472. package/dist/chunks/studio-amsbaaar.js +0 -6
  473. package/dist/chunks/studio-at5spjbc.js +0 -7
  474. package/dist/chunks/studio-b2xv945e.js +0 -5
  475. package/dist/chunks/studio-bn5nkafw.js +0 -5
  476. package/dist/chunks/studio-bw6q5xjf.js +0 -9
  477. package/dist/chunks/studio-c7q0t9x1.js +0 -5
  478. package/dist/chunks/studio-camnhc96.js +0 -6
  479. package/dist/chunks/studio-cffqvnzd.js +0 -5
  480. package/dist/chunks/studio-efvfrsf8.js +0 -5
  481. package/dist/chunks/studio-er30bpcx.js +0 -5
  482. package/dist/chunks/studio-ezscgr64.js +0 -5
  483. package/dist/chunks/studio-ffrrncvz.js +0 -5
  484. package/dist/chunks/studio-fpphgn8y.js +0 -10
  485. package/dist/chunks/studio-j1kvhdjk.js +0 -4
  486. package/dist/chunks/studio-j430n16g.js +0 -7
  487. package/dist/chunks/studio-jjkg84nj.js +0 -5
  488. package/dist/chunks/studio-m0q32ars.js +0 -4
  489. package/dist/chunks/studio-m6bdst11.js +0 -5
  490. package/dist/chunks/studio-nv2cptd4.js +0 -64
  491. package/dist/chunks/studio-nwfrc98n.js +0 -5
  492. package/dist/chunks/studio-p1wgvp7r.js +0 -5
  493. package/dist/chunks/studio-pag2wncm.js +0 -5
  494. package/dist/chunks/studio-qvanvzfd.js +0 -5
  495. package/dist/chunks/studio-qwa64kt7.js +0 -5
  496. package/dist/chunks/studio-rk1zc4x3.js +0 -928
  497. package/dist/chunks/studio-se2r3ptc.js +0 -5
  498. package/dist/chunks/studio-sg2a0dh0.js +0 -5
  499. package/dist/chunks/studio-tjzjhe68.js +0 -5
  500. package/dist/chunks/studio-v2w7q8r1.js +0 -7
  501. package/dist/chunks/studio-v5vjq1h0.js +0 -4
  502. package/dist/chunks/studio-v8aga3vp.js +0 -14
  503. package/dist/chunks/studio-vfcdrfmv.js +0 -5
  504. package/dist/chunks/studio-vmfkgdjf.js +0 -5
  505. package/dist/chunks/studio-wt1816jj.js +0 -5
  506. package/dist/chunks/studio-wz73q5kq.js +0 -4
  507. package/dist/chunks/studio-y04qtxvp.js +0 -7
  508. package/dist/chunks/studio-y21g8zzg.js +0 -5
  509. package/dist/chunks/studio-y64hrt8p.js +0 -12
  510. package/dist/chunks/studio-ynn90f58.js +0 -16
  511. package/dist/chunks/studio-yxz682sx.js +0 -5
  512. package/dist/chunks/studio-zp7snvkq.js +0 -5
  513. package/dist/chunks/studio-zq4z6tm6.js +0 -5
  514. package/dist/chunks/toast-c9fn075p.js +0 -4
  515. package/dist/chunks/tsMode-sf49ee9x.js +0 -14
  516. package/dist/chunks/workspace-jtfahpee.js +0 -4
  517. package/src/panels/frontmatter-panel.ts +0 -711
  518. package/src/panels/git-panel.ts +0 -1108
  519. package/src/surfaces/doc-header.json +0 -776
  520. package/src/surfaces/doc-header.ts +0 -222
  521. package/src/surfaces/git-panel.json +0 -1880
  522. package/src/surfaces/git-panel.ts +0 -264
@@ -20,7 +20,6 @@ import {
20
20
  } from "./sources/content-source";
21
21
  import { createConnectorSource } from "./sources/connector-source";
22
22
  import { GRID_STUB_DOCUMENT, makeGridTabId, parseGridTabId } from "./grid-source";
23
- import { libraryTabId } from "../browse/library-source";
24
23
  import { argsSchema, optionalStringArg, stringArg, stringProperty } from "../commands/command-args";
25
24
  import type { GridSource } from "./grid-source";
26
25
  import type { GridOpenSurfaceHandle, GridSourceGroup } from "../surfaces/grid-open";
@@ -99,32 +98,6 @@ export function openCollectionGrid(typeName: string): Tab {
99
98
  return openVirtualGridTab(createCollectionSource(typeName));
100
99
  }
101
100
 
102
- /**
103
- * Open (or activate) the Library.
104
- *
105
- * A real tab, in the primary pane, with `manage` as its only mode — `commands/context.ts` maps that
106
- * to the `library` editor kind, so the status bar and the pane context bar can name what is on
107
- * screen. It is opened here rather than in `browse/` because this is the module that knows how a
108
- * virtual tab is made; the pane itself resolves its own source and its own view state.
109
- *
110
- * Idempotent by construction, which is what lets a shot say "the Library is open" without caring
111
- * whether an earlier step opened it.
112
- */
113
- export function openLibraryTab(): Tab {
114
- const id = libraryTabId();
115
- const existing = workspace.tabs.get(id);
116
- if (existing) {
117
- activateTab(id);
118
- return existing;
119
- }
120
- return openTab({
121
- capabilities: { modes: ["manage"] },
122
- document: structuredClone(GRID_STUB_DOCUMENT),
123
- documentPath: null,
124
- id,
125
- });
126
- }
127
-
128
101
  /** Open (or activate) the pages metadata grid. */
129
102
  export function openPagesGrid(): Tab {
130
103
  const id = makeGridTabId({ kind: "pages" });
@@ -41,6 +41,7 @@ import { createGridController, getGridController } from "./grid-controller";
41
41
  import { createCsvFileSource } from "./sources/csv-file-source";
42
42
  import { loadedGridEngine, loadGridEngine } from "./grid-lazy";
43
43
  import { parseGridTabId } from "./grid-source";
44
+ import { clearGridReveal, pendingGridReveal } from "./grid-reveal";
44
45
  import { mountGridPanelSurface } from "../surfaces/grid-panel";
45
46
  import { openGridViewsSurface } from "../surfaces/grid-views";
46
47
  import { openGridReplaceSurface } from "../surfaces/grid-replace";
@@ -615,15 +616,66 @@ export function renderGridMode(surface: CanvasSurface, tab: Tab) {
615
616
  }
616
617
  restored = true;
617
618
  const layout = loadGridLayout(engine.source.id);
618
- void engine.setSort(layout?.sort ?? null);
619
- engine.setGrouping(layout?.groupBy ?? null);
619
+ restoreStoredOrder(engine, layout);
620
620
  view.setSearch(layout?.filter ?? "");
621
621
  });
622
622
  }
623
+ revealRequested(engine, panel, tab.documentPath);
623
624
  });
624
625
  });
625
626
  }
626
627
 
628
+ /**
629
+ * Put the stored sort and grouping back on a freshly built engine, asking the controller only for
630
+ * what differs from the order it already holds.
631
+ *
632
+ * The controller outlives the panel: leaving the grid's workspace and coming back rebuilds the
633
+ * table but keeps the controller, so on every return after the first the stored order IS the
634
+ * controller's order. Asking for it anyway was not free. Each of `setSort` and `setGrouping`
635
+ * re-projects the rows into the view, and a view still building parks that as a refresh it runs the
636
+ * moment it is built, so every open rendered its rows twice (the second time measuring every row's
637
+ * height again); a remote-sorting source re-queried its backend for the same rows on top. An
638
+ * explicit change (a saved view applied, a header in the Views popover) still goes straight to the
639
+ * controller, so re-asking for the order you are looking at keeps its meaning there.
640
+ */
641
+ export function restoreStoredOrder(engine: GridController, layout: GridLayout | null): void {
642
+ const sort = layout?.sort ?? null;
643
+ const { query, grouping } = engine.state;
644
+ const sortHeld =
645
+ sort === null
646
+ ? query.orderBy === undefined
647
+ : query.orderBy === sort.field && query.dir === sort.dir;
648
+ if (!sortHeld) {
649
+ void engine.setSort(sort);
650
+ }
651
+ const groupBy = layout?.groupBy ?? null;
652
+ if (grouping !== groupBy) {
653
+ engine.setGrouping(groupBy);
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Land on the row a record's opener asked for (`grid-reveal.ts`), once the engine and the rows
659
+ * exist. The position counts the file's data rows as loaded, so it names a row whatever order a
660
+ * saved view sorts them into. The request is taken either way: a row that is gone is not revealed
661
+ * later by surprise.
662
+ */
663
+ function revealRequested(
664
+ engine: GridController,
665
+ panel: ActiveGridPanel,
666
+ path: string | null | undefined,
667
+ ): void {
668
+ const position = pendingGridReveal(path);
669
+ if (position === undefined || !path || !panel.view || engine.state.loading) {
670
+ return;
671
+ }
672
+ clearGridReveal(path);
673
+ const row = engine.state.rows[position];
674
+ if (row) {
675
+ void panel.view.revealRow(row.key);
676
+ }
677
+ }
678
+
627
679
  /** The frame a tab with no grid source draws: one sentence, and no controls to press. */
628
680
  function missingView(): GridPanelView {
629
681
  return {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The row a file's grid should land on when it opens: a file-backed collection's record.
3
+ *
4
+ * A collection whose `source` names one file (`./content/projects.csv`) is written in that file's
5
+ * grid, so opening one of its records in the Content home opens the file (`specs/studio.md` §22.5).
6
+ * Opening the whole table and leaving the reader to find the row they clicked is not opening the
7
+ * record, so the opener names the record's position here and the grid selects that row and scrolls
8
+ * it into view once its rows have loaded.
9
+ *
10
+ * Reactive and keyed by the file's path, so a request reaches a grid that is already on screen as
11
+ * well as one still loading: the grid panel's one effect reads it, applies it and takes it.
12
+ *
13
+ * @docs studio/projects/browse
14
+ */
15
+
16
+ import { reactive } from "../reactivity";
17
+
18
+ /** File path → the position of the row to reveal, among the file's data rows from 0. */
19
+ const pending = reactive(new Map<string, number>());
20
+
21
+ /** Ask the grid of `path` to reveal the row at `position` (its place among the data rows, from 0). */
22
+ export function requestGridReveal(path: string, position: number): void {
23
+ pending.set(path, position);
24
+ }
25
+
26
+ /** The row the grid of `path` is asked to reveal, or `undefined`. Reading it tracks it. */
27
+ export function pendingGridReveal(path: string | null | undefined): number | undefined {
28
+ return path ? pending.get(path) : undefined;
29
+ }
30
+
31
+ /** Drop the request for `path` once the grid has acted on it. */
32
+ export function clearGridReveal(path: string): void {
33
+ pending.delete(path);
34
+ }
@@ -143,19 +143,9 @@ const GRID_SCHEME = "grid://";
143
143
  */
144
144
  export const GRID_STUB_DOCUMENT = { children: [], tagName: "div" };
145
145
 
146
- /**
147
- * The virtual sources a tab id can name.
148
- *
149
- * `library` is the odd one and deliberately so: the Library is a source over this same contract
150
- * (`browse/library-source.ts`) but its tab's editor kind is `library`, not `grid` — Table is one of
151
- * its five layouts rather than its identity. It lives in this id space because "which virtual tab
152
- * is this?" has exactly one answer per window and there is no second scheme worth inventing.
153
- * Category and layout are VIEW state and are deliberately absent from the id: a Library that
154
- * changed identity when you clicked "Pages" would open a second tab per filter.
155
- */
146
+ /** The virtual sources a grid tab id can name. */
156
147
  export type GridTabRef =
157
148
  | { kind: "pages" }
158
- | { kind: "library" }
159
149
  | { kind: "collection"; name: string }
160
150
  | { kind: "data"; connection: string; table: string };
161
151
 
@@ -165,9 +155,6 @@ export function makeGridTabId(ref: GridTabRef): string {
165
155
  case "pages": {
166
156
  return `${GRID_SCHEME}pages`;
167
157
  }
168
- case "library": {
169
- return `${GRID_SCHEME}library`;
170
- }
171
158
  case "collection": {
172
159
  return `${GRID_SCHEME}collection/${encodeURIComponent(ref.name)}`;
173
160
  }
@@ -191,9 +178,6 @@ export function parseGridTabId(id: string): GridTabRef | null {
191
178
  if (parts[0] === "pages" && parts.length === 1) {
192
179
  return { kind: "pages" };
193
180
  }
194
- if (parts[0] === "library" && parts.length === 1) {
195
- return { kind: "library" };
196
- }
197
181
  if (parts[0] === "collection" && parts.length === 2 && parts[1]) {
198
182
  return { kind: "collection", name: decodeURIComponent(parts[1]) };
199
183
  }
@@ -208,11 +192,8 @@ export function parseGridTabId(id: string): GridTabRef | null {
208
192
  }
209
193
 
210
194
  /**
211
- * Short human label for a virtual tab ("posts · grid", "users @ main · grid", "Pages · grid").
212
- *
213
- * The Library is just "Library": the `· grid` suffix distinguishes a grid over a source from the
214
- * document tab of the same name, and the Library has no document tab to be confused with — nor is a
215
- * grid what it is, only what one of its layouts draws.
195
+ * Short human label for a virtual tab ("posts · grid", "users @ main · grid", "Pages · grid"). The
196
+ * `· grid` suffix distinguishes a grid over a source from the document tab of the same name.
216
197
  */
217
198
  export function gridTabLabel(id: string): string | null {
218
199
  const ref = parseGridTabId(id);
@@ -223,9 +204,6 @@ export function gridTabLabel(id: string): string | null {
223
204
  case "pages": {
224
205
  return "Pages · grid";
225
206
  }
226
- case "library": {
227
- return "Library";
228
- }
229
207
  case "collection": {
230
208
  return `${ref.name} · grid`;
231
209
  }
@@ -52,6 +52,12 @@ export interface GridView {
52
52
  getSelectedRowKeys: () => string[];
53
53
  /** Local text filter across all columns (empty clears). */
54
54
  setSearch: (term: string) => void;
55
+ /**
56
+ * Select one row's cells and scroll it into view, once the table is built.
57
+ *
58
+ * @returns False when no row the table shows carries the key (filtered out, or gone).
59
+ */
60
+ revealRow: (rowKey: string) => Promise<boolean>;
55
61
  destroy: () => void;
56
62
  }
57
63
 
@@ -216,12 +222,17 @@ export function createGridView(host: HTMLElement, controller: GridController): G
216
222
 
217
223
  controller.bindView({ refreshData });
218
224
 
219
- table.on("tableBuilt", () => {
220
- built = true;
221
- if (pendingRefresh) {
222
- pendingRefresh = false;
223
- refreshData();
224
- }
225
+ /* Settles once the table is built: a reveal asked for before then waits for it, because the
226
+ engine has no rows to find, and its default range would replace the one the reveal drew. */
227
+ const whenBuilt = new Promise<void>((resolve) => {
228
+ table.on("tableBuilt", () => {
229
+ built = true;
230
+ if (pendingRefresh) {
231
+ pendingRefresh = false;
232
+ refreshData();
233
+ }
234
+ resolve();
235
+ });
225
236
  });
226
237
 
227
238
  /*
@@ -384,6 +395,20 @@ export function createGridView(host: HTMLElement, controller: GridController): G
384
395
 
385
396
  refreshData,
386
397
 
398
+ async revealRow(rowKey: string) {
399
+ await whenBuilt;
400
+ const row = table.getRow(rowKey);
401
+ const cells = row ? row.getCells() : [];
402
+ const [first] = cells;
403
+ const last = cells.at(-1);
404
+ if (!first || !last) {
405
+ return false;
406
+ }
407
+ table.addRange(first, last);
408
+ await table.scrollToRow(rowKey, "center", false).catch(() => {});
409
+ return true;
410
+ },
411
+
387
412
  setSearch(term: string) {
388
413
  const query = term.trim().toLowerCase();
389
414
  if (query === "") {
@@ -39,6 +39,10 @@ import {
39
39
  loadFormats,
40
40
  } from "../../format/format-host";
41
41
  import { parseSourceForPath } from "../../files/file-ops";
42
+ import { collectionForDirectory } from "../../content/collection-match";
43
+ import { collectionSourceFile } from "../../content/content-collections";
44
+ import { fileRecordIds as recordIdsOf } from "../../content/file-record-ids";
45
+ import { LOCALE_PLACEHOLDER } from "../../canvas/asset-resolve";
42
46
  import { columnsFromSchema, inferColumnsFromRows } from "../schema-columns";
43
47
  import { makeGridTabId } from "../grid-source";
44
48
  import type {
@@ -129,14 +133,77 @@ async function listEntryFiles(dir: string, exts: string[]): Promise<string[]> {
129
133
  return results.toSorted();
130
134
  }
131
135
 
132
- /** Entry ids (file stems) of a collection — used by relationship cell editors. */
136
+ /**
137
+ * The real directories a collection's entries live in.
138
+ *
139
+ * A plain source is its one directory. A localized source (`content/exhibitions/{locale}`) is one
140
+ * directory per language, and the literal path with the placeholder is a directory nobody has:
141
+ * listing it is a 500 from the dev server and an empty collection on screen. So the placeholder's
142
+ * parent is listed, and each subdirectory is kept when the one collection matcher
143
+ * (`content/collection-match.ts`) says that directory IS this collection's, which is what keeps an
144
+ * undeclared language or a co-located `images/` folder out.
145
+ */
146
+ export async function collectionSourceDirs(info: { dir: string; name: string }): Promise<string[]> {
147
+ const cut = info.dir.indexOf(LOCALE_PLACEHOLDER);
148
+ if (cut === -1) {
149
+ return [info.dir];
150
+ }
151
+ const head = info.dir.slice(0, cut).replace(/\/$/, "");
152
+ const tail = info.dir.slice(cut + LOCALE_PLACEHOLDER.length);
153
+ let children;
154
+ try {
155
+ children = await getPlatform().listDirectory(head);
156
+ } catch {
157
+ return []; // No parent directory yet: a localized collection with no entries in any language.
158
+ }
159
+ return children
160
+ .filter((entry) => entry.type === "directory")
161
+ .map((entry) => `${entry.path}${tail}`.replace(/\/$/, ""))
162
+ .filter((dir) => collectionForDirectory(dir)?.name === info.name)
163
+ .toSorted();
164
+ }
165
+
166
+ /** Every entry file of a collection, across all of its directories. */
167
+ async function listCollectionFiles(info: CollectionInfo): Promise<string[]> {
168
+ const dirs = await collectionSourceDirs(info);
169
+ const lists = await Promise.all(dirs.map((dir) => listEntryFiles(dir, [info.ext])));
170
+ return lists.flat().toSorted();
171
+ }
172
+
173
+ /**
174
+ * The ids of a file-backed collection's records (`content/file-records.ts`), or `null` when the
175
+ * source is a directory. A source that names a file is read, never listed: listing a CSV as a
176
+ * directory is a 500 from the dev server and a collection of no entries on screen.
177
+ */
178
+ async function fileRecordIds(info: CollectionInfo): Promise<string[] | null> {
179
+ const file = collectionSourceFile(info.name);
180
+ if (file === null) {
181
+ return null;
182
+ }
183
+ let text: string;
184
+ try {
185
+ text = await getPlatform().readFile(file);
186
+ } catch {
187
+ return []; // A source file not written yet: an empty collection.
188
+ }
189
+ return recordIdsOf(file, text, info.def.idField);
190
+ }
191
+
192
+ /**
193
+ * Entry ids of a collection (file stems, or a file-backed collection's record ids), used by
194
+ * relationship cell editors and the Site panel's counts.
195
+ */
133
196
  export async function listCollectionEntryIds(typeName: string): Promise<string[]> {
134
197
  await loadFormats().catch(() => {}); // Extension resolution needs the format registry.
135
198
  const info = collectionInfo(typeName);
136
199
  if (!info) {
137
200
  return [];
138
201
  }
139
- const files = await listEntryFiles(info.dir, [info.ext]);
202
+ const recordIds = await fileRecordIds(info);
203
+ if (recordIds !== null) {
204
+ return recordIds;
205
+ }
206
+ const files = await listCollectionFiles(info);
140
207
  return files.map((path) => {
141
208
  const base = path.split("/").pop() ?? path;
142
209
  return base.slice(0, base.length - info.ext.length);
@@ -421,7 +488,11 @@ function buildEntryFileSource(opts: EntryFileSourceOptions): GridSource {
421
488
  }
422
489
  const doc = entry.document;
423
490
  const fullDoc = { ...frontmatter, ...doc, children: doc.children ?? [] };
424
- const text = await formatSerialize(entry.formatName, fullDoc, { mode: "roundtrip" });
491
+ // The entry's own text, so only the patched keys are re-spelled.
492
+ const text = await formatSerialize(entry.formatName, fullDoc, {
493
+ mode: "roundtrip",
494
+ source: entry.rawText,
495
+ });
425
496
  markLocalMutation(path);
426
497
  await platform.writeFile(path, text);
427
498
  entries.set(path, { ...entry, frontmatter, rawText: text });
@@ -628,10 +699,7 @@ export function createCollectionSource(typeName: string): GridSource {
628
699
  );
629
700
  },
630
701
  label: typeName,
631
- list: () => {
632
- const info = requireInfo();
633
- return listEntryFiles(info.dir, [info.ext]);
634
- },
702
+ list: () => listCollectionFiles(requireInfo()),
635
703
  resolveInsertPath: (raw) => {
636
704
  const info = requireInfo();
637
705
  let path = raw.trim().replace(/^\.\//, "").replace(/^\//, "");
@@ -114,6 +114,12 @@ declare module "tabulator-tables" {
114
114
  replaceData(data: RowData<Value>[]): Promise<void>;
115
115
  getRows(activeOnly?: string): RowComponent<Value>[];
116
116
  getRanges(): RangeComponent<Value>[];
117
+ /** The row whose `index` field holds `index`, or `false` for none. */
118
+ getRow(index: string): RowComponent<Value> | false;
119
+ /** Scroll a row into view; rejects when no row holds `index`. */
120
+ scrollToRow(index: string, position?: string, ifVisible?: boolean): Promise<void>;
121
+ /** Select the range between two cells, replacing the oldest range past `selectableRange`. */
122
+ addRange(start: CellComponent<Value>, end: CellComponent<Value>): RangeComponent<Value>;
117
123
  setFilter(filter: (data: RowData<Value>) => boolean): void;
118
124
  clearFilter(includeHeaderFilters?: boolean): void;
119
125
  redraw(force?: boolean): void;
@@ -109,8 +109,8 @@ export function studioShellHtml(options: DocumentOptions = {}): string {
109
109
  const boot = (options.boot ?? []).map((mod) => ` ${scriptTag(mod)}`).join("\n");
110
110
  /* Only when there IS a boot module: `packages/studio/index.html` is generated with none and CI
111
111
  diffs it byte for byte, and the dev server it is served by is the one host that should fall
112
- back. The `<head>` below stays a literal, attribute-free tag — Electrobun's macOS CEF path
113
- injects its preload by splicing after that exact string. */
112
+ back. The `<head>` below stays a literal, attribute-free tag — a host that
113
+ injects a preload splices it in after that exact string. */
114
114
  const bootMeta = boot
115
115
  ? ` <meta name="${BOOT_META.name}" content="${BOOT_META.content}" />\n`
116
116
  : "";
@@ -127,6 +127,7 @@ function siblingPath(documentPath: string, locale: string): string | null {
127
127
  if (i18n === null || !i18n.locales.includes(locale)) {
128
128
  notify.error(`This project does not declare ${localeLabel(locale)}.`, {
129
129
  action: "settings.open",
130
+ actionArgs: { section: "locales" },
130
131
  detail: "Add it in Settings › Locales, or with the Add Language command.",
131
132
  source: "Languages",
132
133
  });
@@ -321,7 +322,7 @@ export function i18nCommands(): AnyCommand[] {
321
322
  when: (ctx) => ctx.project.isMultilingual,
322
323
  /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: the panel is a surface
323
324
  for a person. */
324
- /* The panel is off the rail — it spends no rail slot and shifts no ⌘1-8 chord — so this
325
+ /* The panel is off the rail — it spends no rail slot — so this
325
326
  command and the generated `panel.focus.i18n` are its only two doors, and both open the
326
327
  same one. The TITLES must differ even though the behaviour does not: the palette is a flat
327
328
  searchable list where "View: Show Languages" twice is two rows a reader cannot choose
@@ -0,0 +1,181 @@
1
+ /**
2
+ * The Media workspace's verbs, as command records (studio.md §22.6, §9.3).
3
+ *
4
+ * - `media.browse { filter?, query?, select? }` shows the Media home, set to a filter, a search or
5
+ * one file selected. It is what Add new › Media runs after an upload, so the reader lands on what
6
+ * arrived, and what a script or the palette runs to ask "what is unused".
7
+ * - `media.upload` shows the Media home and opens its Upload picker.
8
+ * - `media.replace { path }` selects a file and opens its Replace picker: the one overwrite.
9
+ * - `media.delete { path }` deletes a file, through the confirmation that states its usage.
10
+ *
11
+ * Every one runs the flow, never a copy of it: the surface's Upload label and Replace input are the
12
+ * same controls these open (`HTMLInputElement.showPicker()`), and Delete is the detail pane's own.
13
+ *
14
+ * DOM-free at import: the flow and the home opener are loaded on the first run, so
15
+ * `commands/app-commands.ts` can list these records in a bare Bun process.
16
+ *
17
+ * @docs studio/projects/media
18
+ */
19
+
20
+ import {
21
+ argsSchema,
22
+ enumArg,
23
+ enumProperty,
24
+ optionalStringArg,
25
+ stringArg,
26
+ stringProperty,
27
+ } from "../commands/command-args";
28
+ import { isMediaFile } from "../files/media-upload";
29
+ import { MEDIA_FILTER_KEYS } from "./media-library-model";
30
+ import type { MediaBrowseRequest } from "./media-library";
31
+ import type { CommandArgValues } from "../commands/command-args";
32
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
33
+ import type * as MediaLibraryModule from "./media-library";
34
+ import type * as WorkspaceHomeModule from "../workspace/workspace-home";
35
+
36
+ /** The ids, which Add new and the Site panel run. */
37
+ export const MEDIA_BROWSE = "media.browse";
38
+ export const MEDIA_UPLOAD = "media.upload";
39
+ export const MEDIA_REPLACE = "media.replace";
40
+ export const MEDIA_DELETE = "media.delete";
41
+
42
+ /**
43
+ * The flow and the home opener, each imported once. One cached promise per module: two dynamic
44
+ * imports of one module in flight at once is the shape that loses its coverage record.
45
+ */
46
+ let _library: Promise<typeof MediaLibraryModule> | null = null;
47
+ let _home: Promise<typeof WorkspaceHomeModule> | null = null;
48
+
49
+ function library(): Promise<typeof MediaLibraryModule> {
50
+ _library ??= import("./media-library");
51
+ return _library;
52
+ }
53
+
54
+ function home(): Promise<typeof WorkspaceHomeModule> {
55
+ _home ??= import("../workspace/workspace-home");
56
+ return _home;
57
+ }
58
+
59
+ /** A path argument that must name a media file. Refused with the sentence a person can act on. */
60
+ function mediaPathArg(commandId: string, args: CommandArgValues, key: string): string {
61
+ const path = stringArg(commandId, args, key).replace(/^\.\//, "");
62
+ if (!isMediaFile(path)) {
63
+ throw new RangeError(
64
+ `command "${commandId}": "${path}" is not a media file (an image, video, audio, PDF or font).`,
65
+ );
66
+ }
67
+ return path;
68
+ }
69
+
70
+ /** The `media.browse` arguments as a request, each read with the reader its schema names. */
71
+ function browseRequestOf(args: CommandArgValues): MediaBrowseRequest {
72
+ const request: MediaBrowseRequest = {};
73
+ if (args.filter !== undefined) {
74
+ request.filter = enumArg(MEDIA_BROWSE, args, "filter", MEDIA_FILTER_KEYS);
75
+ }
76
+ const query = optionalStringArg(MEDIA_BROWSE, args, "query");
77
+ if (query !== undefined) {
78
+ request.query = query;
79
+ }
80
+ if (args.select !== undefined) {
81
+ request.select = mediaPathArg(MEDIA_BROWSE, args, "select");
82
+ }
83
+ return request;
84
+ }
85
+
86
+ /** Open the Media home tab, which enters the Media workspace by being activated (§22.1). */
87
+ async function showHome(): Promise<void> {
88
+ const { openWorkspaceHome } = await home();
89
+ openWorkspaceHome("media");
90
+ }
91
+
92
+ const PATH_DESCRIPTION = "The media file's project path, such as public/media/hero.jpg.";
93
+
94
+ /** The four records. */
95
+ export function mediaCommands(): AnyCommand[] {
96
+ const base = {
97
+ category: "Project",
98
+ group: "1_media",
99
+ level: "project",
100
+ menus: ["palette"],
101
+ requires: "an open project",
102
+ when: (ctx) => ctx.project.open,
103
+ } satisfies Partial<AnyCommand>;
104
+ return [
105
+ {
106
+ ...base,
107
+ args: argsSchema(
108
+ {
109
+ filter: enumProperty(
110
+ MEDIA_FILTER_KEYS,
111
+ "Which media to show: all, image, video, audio, document, font, or unused (files no document references).",
112
+ ),
113
+ query: stringProperty("Search the media by name or path."),
114
+ select: stringProperty(`A file to select and describe. ${PATH_DESCRIPTION}`),
115
+ },
116
+ [],
117
+ ),
118
+ icon: "image",
119
+ id: MEDIA_BROWSE,
120
+ run: async (_ctx, args) => {
121
+ const request = browseRequestOf(args);
122
+ const flow = await library();
123
+ flow.applyMediaBrowse(request);
124
+ await showHome();
125
+ },
126
+ title: "Browse Media",
127
+ undo: "none",
128
+ },
129
+ {
130
+ ...base,
131
+ icon: "upload-simple",
132
+ id: MEDIA_UPLOAD,
133
+ run: async () => {
134
+ const flow = await library();
135
+ await showHome();
136
+ flow.requestMediaUpload();
137
+ },
138
+ title: "Upload Media…",
139
+ undo: "none",
140
+ },
141
+ {
142
+ ...base,
143
+ args: argsSchema({ path: stringProperty(`The file to replace. ${PATH_DESCRIPTION}`) }, [
144
+ "path",
145
+ ]),
146
+ icon: "arrows-clockwise",
147
+ id: MEDIA_REPLACE,
148
+ run: async (_ctx, args) => {
149
+ const path = mediaPathArg(MEDIA_REPLACE, args, "path");
150
+ const flow = await library();
151
+ await showHome();
152
+ flow.requestMediaReplace(path);
153
+ },
154
+ title: "Replace Media File…",
155
+ undo: "none",
156
+ },
157
+ {
158
+ ...base,
159
+ args: argsSchema({ path: stringProperty(`The file to delete. ${PATH_DESCRIPTION}`) }, [
160
+ "path",
161
+ ]),
162
+ // A delete is a danger row wherever it is listed, and sorts last (studio-ui-guidelines.md §12.3).
163
+ destructive: true,
164
+ group: "9_danger",
165
+ icon: "trash",
166
+ id: MEDIA_DELETE,
167
+ run: async (_ctx, args) => {
168
+ const path = mediaPathArg(MEDIA_DELETE, args, "path");
169
+ const flow = await library();
170
+ await flow.deleteMediaFile(path);
171
+ },
172
+ title: "Delete Media File…",
173
+ undo: "none",
174
+ },
175
+ ];
176
+ }
177
+
178
+ /** Register the Media records. */
179
+ export function registerMediaCommands(registry: CommandRegistry): void {
180
+ registry.registerAll(mediaCommands());
181
+ }