@jxsuite/studio 6.1.2 → 8.0.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 (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
@@ -9,32 +9,47 @@
9
9
  * identical sentence — "No files found" — which is the lie §16 exists to end.
10
10
  * {@link scanLibrary} returns {@link LibraryScan.failures} beside the files; the pane raises
11
11
  * them as Problems with a Retry and refuses to call an incomplete listing empty.
12
- * 2. **A category has a directory.** The Manage view filtered by category and uploaded to whatever
13
- * directory the active filter happened to name, which for "All" was nothing at all — the upload
14
- * landed wherever `uploadAssets` defaulted. {@link uploadDirForCategory} still answers, but the
15
- * pane never uploads to an unnamed destination: it asks.
12
+ * 2. **A file is in one collection or in none.** The Library this model served filtered by FOLDER
13
+ * (Pages, Layouts, Components, Content, Media) and matched content with a third matcher of its
14
+ * own, which disagreed with the collection grid about the same entry. Every file now carries the
15
+ * key of the collection the Content workspace lists it under ({@link LibraryFile.collection}),
16
+ * from `content/content-collections.ts`, whose one matcher is `content/collection-match.ts`
17
+ * (studio.md §9.1.2).
16
18
  *
17
19
  * Everything here is pure over its inputs (the scan takes the platform as an argument) so the
18
20
  * 300-page acceptance case can be measured without a DOM.
19
21
  */
20
22
 
21
23
  import { MEDIA_EXTENSIONS, extensionOf } from "../files/media-upload";
24
+ import { PAGES_COLLECTION, collectionKeyOfPath } from "../content/content-collections";
25
+ import { isRoutedPageFile } from "@jxsuite/site/routes";
26
+ import { collectionForFile } from "../content/collection-match";
27
+ import { LOCALE_PLACEHOLDER } from "@jxsuite/schema/asset-paths";
22
28
  import { projectState } from "../store";
23
29
  import { notedListing } from "../files/asset-versions";
30
+ import { localDay } from "../utils/local-day";
24
31
  import { errorMessage } from "@jxsuite/schema/parse";
32
+ import { isString } from "@jxsuite/schema/guards";
25
33
  import { resolveI18n, servedLocaleOfPath } from "@jxsuite/schema/locale";
26
34
  import type { ResolvedI18n } from "@jxsuite/schema/locale";
27
35
  import type { ContentSectionEntry, DirEntry, StudioPlatform } from "../types";
36
+ import type { ProjectConfig } from "@jxsuite/schema/types";
28
37
 
29
38
  // ─── Files ───────────────────────────────────────────────────────────────────
30
39
 
31
- /** One row of the Library, whichever layout is drawing it. */
40
+ /** One scanned file, whichever layout is drawing it. */
32
41
  export interface LibraryFile {
33
42
  name: string;
34
43
  path: string;
35
- /** The content type name for a content file; the extension otherwise. */
44
+ /** The extension, or `"file"` for a name without one. */
36
45
  type: string;
37
- category: string;
46
+ /**
47
+ * The key of the collection the file is in (`pages`, or a collection's name), or `null` for a
48
+ * file in none: a template, a component, a media file, anything else.
49
+ */
50
+ collection: string | null;
51
+ /** Whether the file is media by its extension — never an entry, whatever folder it is in. */
52
+ media: boolean;
38
53
  ext: string;
39
54
  /** Bytes, when the platform reported them. */
40
55
  size?: number | undefined;
@@ -52,6 +67,13 @@ export interface LibraryFile {
52
67
  * is in none.
53
68
  */
54
69
  locale?: string | undefined;
70
+ /**
71
+ * Set when this row is one RECORD of a file-backed collection rather than a file: `path` is then
72
+ * the record's key (`prices.csv#3`), and `source` the file it is stored in, which is what opening
73
+ * it opens (`content/file-records.ts`); `row` is its place among the file's data rows, which is
74
+ * the row that file's grid lands on.
75
+ */
76
+ record?: { source: string; row: number; fields: Readonly<Record<string, string>> } | undefined;
55
77
  }
56
78
 
57
79
  /** A directory the scan could not read. Its presence means the listing is INCOMPLETE, not empty. */
@@ -66,103 +88,117 @@ export interface LibraryScan {
66
88
  failures: ScanFailure[];
67
89
  }
68
90
 
69
- // ─── Categories ──────────────────────────────────────────────────────────────
91
+ // ─── The scan ────────────────────────────────────────────────────────────────
70
92
 
71
- /** A Library category: what it is called, and the directory an upload into it lands in. */
72
- export interface LibraryCategory {
73
- key: string;
74
- label: string;
75
- /** Absent for "All" — which is why "All" has no upload destination and must ask for one. */
76
- dir?: string;
93
+ /** A project-relative path without `./`, backslashes or a trailing slash. */
94
+ function normalizeDir(path: string): string {
95
+ return path
96
+ .replaceAll("\\", "/")
97
+ .replace(/^(?:\.\/)+/, "")
98
+ .replace(/\/+$/, "");
77
99
  }
78
100
 
79
- export const LIBRARY_CATEGORIES: readonly LibraryCategory[] = [
80
- { key: "all", label: "All" },
81
- { dir: "pages", key: "pages", label: "Pages" },
82
- { dir: "layouts", key: "layouts", label: "Layouts" },
83
- { dir: "components", key: "components", label: "Components" },
84
- { dir: "content", key: "content", label: "Content" },
85
- { dir: "public", key: "media", label: "Media" },
86
- ];
87
-
88
- /** The category keys, for a command's `enum` — so `library.setCategory` refuses an unknown one. */
89
- export const LIBRARY_CATEGORY_KEYS: readonly string[] = LIBRARY_CATEGORIES.map((c) => c.key);
90
-
91
- /** The category with this key, or undefined. */
92
- export function libraryCategory(key: string): LibraryCategory | undefined {
93
- return LIBRARY_CATEGORIES.find((c) => c.key === key);
101
+ /** Whether a `source` names one file (a CSV catalogue) rather than a directory to walk. */
102
+ function namesOneFile(source: string): boolean {
103
+ if (source.endsWith("/")) {
104
+ return false;
105
+ }
106
+ const base = source.slice(source.lastIndexOf("/") + 1);
107
+ return /\.[a-z\d]+$/i.test(base);
94
108
  }
95
109
 
96
110
  /**
97
- * The directory a drop on the Library targets while `key` is the active category.
111
+ * The directories the Content scan walks: the conventional project directories that exist, plus
112
+ * every declared directory-collection `source` none of them already covers.
98
113
  *
99
- * `undefined` for "All" and for any category without one, and the caller MUST treat that as "ask",
100
- * never as "use the default": an upload that lands somewhere the author did not choose is the same
101
- * class of surprise as a rename that silently rewrites files.
114
+ * The conventional list alone missed two real collections. A collection may live BESIDE the project
115
+ * (`sites/jxsuite.com` declares `content.docs.source: "../../docs"`), which no project directory
116
+ * can contain; and a collection created after the project opened (Add new › Collection) has its
117
+ * source under a `content/` that did not exist when the conventional list was read. Both were
118
+ * listed as empty, which is the lie {@link LibraryScan.failures} exists to end.
119
+ *
120
+ * A localized source is walked from the directory above its `{locale}` segment, which the walk
121
+ * descends into on its own. A file-backed source is a file, not a directory to walk.
122
+ *
123
+ * @param projectDirs The conventional directories present at the project root.
124
+ * @param config The project configuration whose `content` section declares the collections.
102
125
  */
103
- export function uploadDirForCategory(key: string): string | undefined {
104
- return libraryCategory(key)?.dir;
105
- }
106
-
107
- /** Map a file path to a display category. Media files override by extension. */
108
- export function categoryFor(dir: string, ext: string): string {
109
- if (ext && MEDIA_EXTENSIONS.has(ext)) {
110
- return "Media";
111
- }
112
- if (dir.startsWith("pages")) {
113
- return "Pages";
114
- }
115
- if (dir.startsWith("layouts")) {
116
- return "Layouts";
126
+ export function libraryScanDirs(
127
+ projectDirs: readonly string[],
128
+ config: ProjectConfig | null | undefined,
129
+ ): readonly string[] {
130
+ const dirs: string[] = [];
131
+ for (const dir of projectDirs) {
132
+ dirs.push(normalizeDir(dir));
117
133
  }
118
- if (dir.startsWith("components")) {
119
- return "Components";
120
- }
121
- if (dir.startsWith("content")) {
122
- return "Content";
123
- }
124
- if (dir.startsWith("public")) {
125
- return "Media";
126
- }
127
- if (dir.startsWith("data")) {
128
- return "Content";
129
- }
130
- if (dir.startsWith("styles")) {
131
- return "Components";
134
+ const extra: string[] = [];
135
+ /* SAFETY: `content` is the parser extension's section, collection name → entry object; only the
136
+ optional string `source` is read, and it is tested before use. */
137
+ const content = (config?.content ?? {}) as Record<string, ContentSectionEntry>;
138
+ for (const def of Object.values(content)) {
139
+ if (!isString(def.source) || def.source === "" || namesOneFile(def.source)) {
140
+ continue;
141
+ }
142
+ let source = normalizeDir(def.source);
143
+ const cut = source.indexOf(LOCALE_PLACEHOLDER);
144
+ if (cut !== -1) {
145
+ source = normalizeDir(source.slice(0, cut));
146
+ }
147
+ if (source === "" || source === ".") {
148
+ continue;
149
+ }
150
+ const covered = [...dirs, ...extra].some(
151
+ (dir) => source === dir || source.startsWith(`${dir}/`),
152
+ );
153
+ if (!covered) {
154
+ extra.push(source);
155
+ }
132
156
  }
133
- return "Other";
157
+ // The project's own list, unchanged, when every collection is already under it.
158
+ return extra.length === 0 ? projectDirs : [...projectDirs, ...extra];
134
159
  }
135
160
 
136
161
  /**
137
- * The content type a path belongs to, from the project's `content` section — capitalized, or null.
162
+ * The collection a scanned file is an ENTRY of, or `null`.
163
+ *
164
+ * `collectionKeyOfPath` answers which collection's folder holds a file, which is the question a
165
+ * convert or a creation asks. A listing asks a narrower one: is this a file the collection's loader
166
+ * reads? A Markdown collection's folder may hold a `nav.json` beside its pages (the jxsuite docs
167
+ * do), and counting it made the Content home and the Site panel disagree about one collection. So a
168
+ * collection that DECLARES its format lists only the files carrying that format's extension; one
169
+ * that declares none is left to the loader's reading of the source, as before.
138
170
  *
139
- * @param {string} filePath
140
- * @returns {string | null}
171
+ * Pages are judged the way the build's walk judges them: with `pageExtensions` given, a file under
172
+ * `pages/` is a page only when it carries one of them, so `index.json.bak` beside the home page is
173
+ * a file in the folder rather than a second page (it publishes nothing).
174
+ *
175
+ * @param path The project-relative file.
176
+ * @param pageExtensions The extensions the build routes (`routedPageExtensions`); omitted, a page's
177
+ * extension is not judged.
141
178
  */
142
- export function contentTypeFor(filePath: string): string | null {
143
- /* SAFETY: `content` is the parser extension's opaque project section, which its project fragment
144
- schema defines as a map of section entries; the loop reads only the optional `source`, and skips
145
- an entry without one. */
146
- const content = (projectState?.projectConfig?.content ?? {}) as Record<
147
- string,
148
- ContentSectionEntry
149
- >;
150
- for (const [name, entry] of Object.entries(content)) {
151
- if (!entry.source) {
152
- continue;
153
- }
154
- const prefix = entry.source.replace(/^\.\//, "").replace(/\/$/, "");
155
- if (filePath.startsWith(`${prefix}/`) || filePath === prefix) {
156
- return name.charAt(0).toUpperCase() + name.slice(1);
157
- }
179
+ export function entryCollectionOf(path: string, pageExtensions?: readonly string[]): string | null {
180
+ const key = collectionKeyOfPath(path);
181
+ if (key === null) {
182
+ return null;
158
183
  }
159
- return null;
184
+ if (key === PAGES_COLLECTION) {
185
+ return pageExtensions === undefined || isRoutedPageFile(path, pageExtensions) ? key : null;
186
+ }
187
+ const match = collectionForFile(path);
188
+ if (!match || match.name !== key || !match.def.format || match.ext === null) {
189
+ return key;
190
+ }
191
+ return path.toLowerCase().endsWith(match.ext.toLowerCase()) ? key : null;
160
192
  }
161
193
 
162
- // ─── The scan ────────────────────────────────────────────────────────────────
163
-
164
- /** Directory names never worth walking — a `node_modules` scan is minutes, not milliseconds. */
165
- const SKIP_DIRS = new Set(["node_modules", ".git", ".jx", "dist", "build", ".cache"]);
194
+ /**
195
+ * Directory names never worth walking — a `node_modules` scan is minutes, not milliseconds.
196
+ *
197
+ * Not `build` or `dist`: the scan starts inside the project's own directories, never at its root
198
+ * where a build writes, and a collection is free to name a section `build` (the jxsuite docs file
199
+ * their CLI pages under `framework/build/`). Skipping the name dropped those entries silently.
200
+ */
201
+ const SKIP_DIRS = new Set(["node_modules", ".git", ".jx", ".cache"]);
166
202
 
167
203
  /**
168
204
  * Walk one directory tree, collecting files and recording — never swallowing — what it could not
@@ -175,13 +211,18 @@ async function walk(
175
211
  out: LibraryFile[],
176
212
  failures: ScanFailure[],
177
213
  i18n: ResolvedI18n | null,
214
+ optional = false,
215
+ pageExtensions?: readonly string[],
178
216
  ): Promise<void> {
179
217
  let entries: DirEntry[];
180
218
  try {
181
219
  // Noted: the Library walks every media directory, so it is where most versions are learned.
182
220
  entries = await notedListing(() => platform.listDirectory(dir));
183
221
  } catch (error) {
184
- failures.push({ dir, error: errorMessage(error) });
222
+ // A declared collection's folder that does not exist yet is an empty collection, not a fault.
223
+ if (!optional) {
224
+ failures.push({ dir, error: errorMessage(error) });
225
+ }
185
226
  return;
186
227
  }
187
228
  const subdirectories: string[] = [];
@@ -193,21 +234,23 @@ async function walk(
193
234
  continue;
194
235
  }
195
236
  const ext = extensionOf(entry.name);
196
- const category = categoryFor(entry.path, ext);
197
237
  out.push({
198
- category,
238
+ collection: entryCollectionOf(entry.path, pageExtensions),
199
239
  ext,
200
240
  locale: servedLocaleOfPath(entry.path, i18n) ?? undefined,
241
+ media: MEDIA_EXTENSIONS.has(ext),
201
242
  modified: entry.modified,
202
243
  name: entry.name,
203
244
  path: entry.path,
204
245
  size: entry.size,
205
- type: category === "Content" ? (contentTypeFor(entry.path) ?? ext) : ext || "file",
246
+ type: ext || "file",
206
247
  });
207
248
  }
208
249
  // Siblings in parallel, depth serialized per branch: the platform counts every call as in-flight,
209
250
  // So `probe.idle()` already knows the scan is running without the Library declaring a source.
210
- await Promise.all(subdirectories.map((sub) => walk(sub, platform, out, failures, i18n)));
251
+ await Promise.all(
252
+ subdirectories.map((sub) => walk(sub, platform, out, failures, i18n, false, pageExtensions)),
253
+ );
211
254
  }
212
255
 
213
256
  /**
@@ -219,13 +262,19 @@ async function walk(
219
262
  export async function scanLibrary(
220
263
  dirs: readonly string[],
221
264
  platform: Pick<StudioPlatform, "listDirectory">,
265
+ optional: ReadonlySet<string> = new Set(),
266
+ pageExtensions?: readonly string[],
222
267
  ): Promise<LibraryScan> {
223
268
  const files: LibraryFile[] = [];
224
269
  const failures: ScanFailure[] = [];
225
270
  // Resolved ONCE for the whole walk. `resolveI18n` canonicalizes every declared tag through
226
271
  // `Intl.Locale`, and a project with 300 pages would otherwise pay for that per file.
227
272
  const { i18n } = resolveI18n(projectState?.projectConfig ?? {});
228
- await Promise.all(dirs.map((dir) => walk(dir, platform, files, failures, i18n)));
273
+ await Promise.all(
274
+ dirs.map((dir) =>
275
+ walk(dir, platform, files, failures, i18n, optional.has(dir), pageExtensions),
276
+ ),
277
+ );
229
278
  files.sort((a, b) => a.path.localeCompare(b.path));
230
279
  return { failures, files };
231
280
  }
@@ -233,25 +282,19 @@ export async function scanLibrary(
233
282
  // ─── Filtering ───────────────────────────────────────────────────────────────
234
283
 
235
284
  export interface LibraryFilter {
236
- category: string;
285
+ /** The collection key to keep: `pages`, or a declared collection's name. */
286
+ collection: string;
237
287
  query: string;
238
288
  /** A canonical tag, or "" for every language. Never a label — the labels are not unique. */
239
289
  locale?: string;
240
290
  }
241
291
 
242
- /**
243
- * Category, then text over name and path, then language. Returns the input array unchanged when
244
- * nothing filters.
245
- */
292
+ /** Collection, then text over name and path, then language. */
246
293
  export function filterLibrary(
247
294
  files: readonly LibraryFile[],
248
295
  filter: LibraryFilter,
249
296
  ): readonly LibraryFile[] {
250
- let result = files;
251
- const category = libraryCategory(filter.category);
252
- if (category && category.key !== "all") {
253
- result = result.filter((f) => f.category === category.label);
254
- }
297
+ let result = files.filter((f) => f.collection === filter.collection);
255
298
  const query = filter.query.trim().toLowerCase();
256
299
  if (query) {
257
300
  result = result.filter(
@@ -267,7 +310,8 @@ export function filterLibrary(
267
310
 
268
311
  // ─── Layouts ─────────────────────────────────────────────────────────────────
269
312
 
270
- export const LIBRARY_LAYOUTS = ["table", "cards", "media", "calendar", "board"] as const;
313
+ /** The Content home's four views (studio.md §22.5), in the order its view switch draws them. */
314
+ export const LIBRARY_LAYOUTS = ["table", "cards", "calendar", "board"] as const;
271
315
 
272
316
  export type LibraryLayout = (typeof LIBRARY_LAYOUTS)[number];
273
317
 
@@ -275,20 +319,16 @@ export const LIBRARY_LAYOUT_LABELS: Readonly<Record<LibraryLayout, string>> = {
275
319
  board: "Board",
276
320
  calendar: "Calendar",
277
321
  cards: "Cards",
278
- media: "Media",
279
322
  table: "Table",
280
323
  };
281
324
 
282
- /** Whether a string names a layout — the guard behind `library.setLayout`'s argument. */
325
+ /** Whether a string names a layout — the guard behind `content.setLayout`'s argument. */
283
326
  export function isLibraryLayout(value: string): value is LibraryLayout {
284
327
  return LIBRARY_LAYOUTS.some((layout) => layout === value);
285
328
  }
286
329
 
287
- /** Layouts that draw a live preview per item. Table and Board draw text, and stay cheap. */
288
- export const PREVIEW_LAYOUTS: ReadonlySet<LibraryLayout> = new Set<LibraryLayout>([
289
- "cards",
290
- "media",
291
- ]);
330
+ /** Layouts that draw a live preview per item. Table, Calendar and Board draw text. */
331
+ export const PREVIEW_LAYOUTS: ReadonlySet<LibraryLayout> = new Set<LibraryLayout>(["cards"]);
292
332
 
293
333
  // ─── Derived axes (Calendar and Board) ───────────────────────────────────────
294
334
 
@@ -308,9 +348,10 @@ export function libraryDate(file: LibraryFile): string | null {
308
348
  return prefix[1]!;
309
349
  }
310
350
  if (file.modified) {
311
- const parsed = new Date(file.modified);
312
- if (!Number.isNaN(parsed.getTime())) {
313
- return parsed.toISOString().slice(0, 10);
351
+ // The day it was modified where the reader is, not in UTC (`utils/local-day.ts`).
352
+ const day = localDay(new Date(file.modified));
353
+ if (day) {
354
+ return day;
314
355
  }
315
356
  }
316
357
  return null;
@@ -318,7 +359,7 @@ export function libraryDate(file: LibraryFile): string | null {
318
359
 
319
360
  /**
320
361
  * The locales this project DECLARES, canonical and in declaration order — the value space of the
321
- * Library's language facet and of `library.setLocale`'s `enum`.
362
+ * Content home's language facet and of `content.setLocale`'s `enum`.
322
363
  *
323
364
  * Read at call time rather than cached: `projectState` is a plain binding replaced wholesale on a
324
365
  * project switch, so a cached list would describe the project before last.
@@ -330,10 +371,9 @@ export function projectLocales(): readonly string[] {
330
371
  /**
331
372
  * The locales actually PRESENT among these files, in the project's declaration order.
332
373
  *
333
- * Ordered by the project and not by the scan for the reason {@link groupByCategory} orders its
334
- * columns by {@link LIBRARY_CATEGORIES}: a facet whose options reshuffle when a file is added is a
335
- * facet nobody can build muscle memory for. Declaration order also puts the default locale first,
336
- * because {@link resolveI18n} guarantees it is `locales[0]`.
374
+ * Ordered by the project and not by the scan: a facet whose options reshuffle when a file is added
375
+ * is a facet nobody can build muscle memory for. Declaration order also puts the default locale
376
+ * first, because {@link resolveI18n} guarantees it is `locales[0]`.
337
377
  *
338
378
  * Only declared tags can appear — `localeOfPath` matches nothing else — so filtering the
339
379
  * declaration through the presence set loses no file.
@@ -361,11 +401,14 @@ export interface LibraryDateGroups {
361
401
  }
362
402
 
363
403
  /** Calendar buckets, newest day first, plus the files no date could be derived for. */
364
- export function groupByDate(files: readonly LibraryFile[]): LibraryDateGroups {
404
+ export function groupByDate(
405
+ files: readonly LibraryFile[],
406
+ dateOf: (file: LibraryFile) => string | null = libraryDate,
407
+ ): LibraryDateGroups {
365
408
  const byDay = new Map<string, LibraryFile[]>();
366
409
  const undated: LibraryFile[] = [];
367
410
  for (const file of files) {
368
- const date = libraryDate(file);
411
+ const date = dateOf(file);
369
412
  if (date === null) {
370
413
  undated.push(file);
371
414
  continue;
@@ -383,28 +426,33 @@ export function groupByDate(files: readonly LibraryFile[]): LibraryDateGroups {
383
426
  return { days, undated };
384
427
  }
385
428
 
386
- /** Board columns — one per category present, in the Library's own category order, then the rest. */
387
- export function groupByCategory(
429
+ /** The folder a file is in, project-relative: `pages/blog/a.md` → `pages/blog`. */
430
+ export function folderOf(path: string): string {
431
+ const slash = path.lastIndexOf("/");
432
+ return slash === -1 ? "." : path.slice(0, slash);
433
+ }
434
+
435
+ /**
436
+ * Board columns: one per folder the files are in, in path order.
437
+ *
438
+ * A collection is browsed one at a time, so a column per collection would be one column; the
439
+ * folders inside it (`pages/` and `pages/blog/`, a dated archive's years) are the grouping a
440
+ * collection already has on disk, and the one a Board can state without reading any file.
441
+ */
442
+ export function groupByFolder(
388
443
  files: readonly LibraryFile[],
389
444
  ): { group: string; files: LibraryFile[] }[] {
390
445
  const byGroup = new Map<string, LibraryFile[]>();
391
446
  for (const file of files) {
392
- const bucket = byGroup.get(file.category);
447
+ const folder = folderOf(file.path);
448
+ const bucket = byGroup.get(folder);
393
449
  if (bucket) {
394
450
  bucket.push(file);
395
451
  } else {
396
- byGroup.set(file.category, [file]);
452
+ byGroup.set(folder, [file]);
397
453
  }
398
454
  }
399
- const order = LIBRARY_CATEGORIES.map((c) => c.label);
400
455
  return [...byGroup.entries()]
401
456
  .map(([group, groupFiles]) => ({ files: groupFiles, group }))
402
- .toSorted((a, b) => {
403
- const ai = order.indexOf(a.group);
404
- const bi = order.indexOf(b.group);
405
- if (ai === bi) {
406
- return a.group.localeCompare(b.group);
407
- }
408
- return (ai === -1 ? order.length : ai) - (bi === -1 ? order.length : bi);
409
- });
457
+ .toSorted((a, b) => a.group.localeCompare(b.group));
410
458
  }