@jxsuite/studio 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +21080 -32585
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +7 -0
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +221 -245
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -53
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +30 -1
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +7 -1
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
  363. package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
  364. package/dist/chunks/files-j8a1t06k.js +0 -50
  365. package/dist/chunks/files-j8a1t06k.js.map +0 -9
  366. package/dist/chunks/git-panel-xtq5z81x.js +0 -28
  367. package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
  368. package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
  369. package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
  370. package/dist/chunks/settings-modal-17ttr96g.js +0 -29
  371. package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-0xgs7xrs.js +0 -429
  381. package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
  382. package/dist/chunks/studio-4d8hzcz5.js +0 -3427
  383. package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
  384. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  385. package/dist/chunks/studio-9smfzqhc.js +0 -19
  386. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  387. package/dist/chunks/studio-jm0z592w.js.map +0 -115
  388. package/dist/chunks/studio-m2t4mdg5.js +0 -1854
  389. package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
  390. package/dist/chunks/studio-n4p11dfy.js +0 -853
  391. package/dist/chunks/studio-n4p11dfy.js.map +0 -12
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-sjh1wt1a.js +0 -3744
  394. package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
  395. package/dist/chunks/studio-v7jkjqng.js.map +0 -46
  396. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  397. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  398. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  399. package/dist/chunks/studio-yfcstdzy.js +0 -399
  400. package/dist/chunks/studio-yfcstdzy.js.map +0 -12
  401. package/dist/chunks/studio-zp7jncs8.js +0 -469
  402. package/dist/chunks/studio-zp7jncs8.js.map +0 -14
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Redirect rules — the model, the three validations, and the two import formats.
3
+ *
4
+ * Pure: no platform, no DOM, no project state. `redirects-grid.ts` is what puts this in front of an
5
+ * author; everything decidable about a redirect is decided here, where it can be tested with a
6
+ * literal array instead of a project.
7
+ *
8
+ * **Why three validations and not a lint pass.** `project.json`'s `redirects` map is the one part
9
+ * of a Jx project that is invisible in the thing it configures: nothing in the site renders it, and
10
+ * a rule that is wrong looks exactly like a rule that is right (specs/site-architecture.md §11.4
11
+ * asks for precisely these three). Each has a different cost and a different fix:
12
+ *
13
+ * - **Chain** — `/a → /b` where `/b` is itself a rule. The visitor pays a second round trip, and
14
+ * every hop is separately cacheable, so the last one to be corrected wins for a while. Fix: point
15
+ * the first rule at the final destination.
16
+ * - **Loop** — a cycle. The page is unreachable. This is the only one filed as an error.
17
+ * - **Shadow** — the project has a real route the rule's source covers. On a host that serves static
18
+ * assets before consulting `_redirects` (Netlify, Cloudflare Pages) the page wins and the rule is
19
+ * dead config; in `dist/` the compiler's own meta-refresh file overwrites the page and warns
20
+ * (§11.1). Two hosts, two answers, and neither is what the author intended by writing both.
21
+ *
22
+ * **What is deliberately NOT decided.** A destination containing `:param` or `*` is not followed:
23
+ * its concrete target depends on the request, so calling it a chain would be a guess. Nothing is
24
+ * reported about it either way — see {@link validateRedirects}.
25
+ */
26
+
27
+ import { REDIRECT_STATUSES } from "@jxsuite/schema/defs";
28
+ import { parseCsv } from "./csv-codec";
29
+
30
+ /** One redirect rule, flattened out of `project.json`'s two accepted spellings. */
31
+ /**
32
+ * A rule's target: an RFC 9110 §15.4 redirection status, or a rewrite.
33
+ *
34
+ * A rewrite is NOT status 200. It serves the destination's content at the source URL, and the 200
35
+ * that reaches `_redirects` is the host's convention for saying so. Modelling it as a status is
36
+ * what let the compiler emit a meta-refresh page for one — see site-architecture.md §11.3.
37
+ */
38
+ export const REWRITE = "rewrite";
39
+ export type RedirectTarget = (typeof REDIRECT_STATUSES)[number] | typeof REWRITE;
40
+
41
+ export interface RedirectRule {
42
+ source: string;
43
+ destination: string;
44
+ status: RedirectTarget;
45
+ }
46
+
47
+ /** What the status column offers, in the order it offers them. */
48
+ export const REDIRECT_TARGETS: readonly RedirectTarget[] = [...REDIRECT_STATUSES, REWRITE];
49
+
50
+ /** The status `project.json` means when a rule is written as a bare string. */
51
+ export const DEFAULT_REDIRECT_STATUS = 301;
52
+
53
+ /** The status a rewrite is written as in `_redirects`, and read back from an import. */
54
+ export const REWRITE_WIRE_STATUS = 200;
55
+
56
+ /** `project.json`'s redirects map, as the schema types it. */
57
+ export type RedirectConfig = Record<
58
+ string,
59
+ string | { destination: string; status?: number } | { destination: string; rewrite: true }
60
+ >;
61
+
62
+ // ─── Paths and patterns ───────────────────────────────────────────────────────
63
+
64
+ /** Trailing-slash-insensitive form, matching the compiler's own `normalizeUrl` in `site-build.ts`. */
65
+ export function normalizePath(path: string): string {
66
+ const trimmed = path.trim();
67
+ return trimmed.length > 1 && trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
68
+ }
69
+
70
+ /** Whether a path is a URLPattern rather than a literal — `:param` or `*` (§11.2). */
71
+ export function isPattern(path: string): boolean {
72
+ return path.includes(":") || path.includes("*");
73
+ }
74
+
75
+ /** Whether a path leaves the site: a scheme, a protocol-relative URL, or a mailto/tel. */
76
+ export function isExternal(path: string): boolean {
77
+ return /^[a-z][\d+.a-z-]*:/i.test(path) || path.startsWith("//");
78
+ }
79
+
80
+ /**
81
+ * Whether `path` is covered by `pattern`.
82
+ *
83
+ * URLPattern-lite over pathname segments, which is all §11.2 uses: `:name` matches exactly one
84
+ * non-empty segment and `*` matches the rest, including nothing. Enough to answer "does this rule
85
+ * ever fire for that request", which is the only question chain, loop and shadow ask.
86
+ */
87
+ export function matchesPattern(pattern: string, path: string): boolean {
88
+ const p = normalizePath(pattern).split("/");
89
+ const s = normalizePath(path).split("/");
90
+ for (const [i, segment] of p.entries()) {
91
+ if (segment === "*") {
92
+ return true;
93
+ }
94
+ if (i >= s.length) {
95
+ return false;
96
+ }
97
+ if (segment.startsWith(":")) {
98
+ if (s[i] === "") {
99
+ return false;
100
+ }
101
+ continue;
102
+ }
103
+ if (segment !== s[i]) {
104
+ return false;
105
+ }
106
+ }
107
+ return s.length === p.length;
108
+ }
109
+
110
+ /**
111
+ * A page route as a pattern: `/blog/[slug]` → `/blog/:slug`.
112
+ *
113
+ * File-based routing spells a dynamic segment with brackets and a redirect spells it with a colon.
114
+ * The shadow check compares the two, so one of them has to be translated, and translating the route
115
+ * leaves the author's own rule text untouched in every message.
116
+ */
117
+ export function routePattern(route: string): string {
118
+ return route.replaceAll(/\[([^\]]+)]/g, ":$1");
119
+ }
120
+
121
+ // ─── project.json ↔ rules ─────────────────────────────────────────────────────
122
+
123
+ /** Flatten `project.json`'s redirects map into rules, in declaration order. */
124
+ export function rulesFromConfig(config: RedirectConfig | undefined): RedirectRule[] {
125
+ return Object.entries(config ?? {}).map(([source, target]) => {
126
+ if (typeof target === "string") {
127
+ return { destination: target, source, status: DEFAULT_REDIRECT_STATUS };
128
+ }
129
+ if ("rewrite" in target) {
130
+ return { destination: target.destination, source, status: REWRITE };
131
+ }
132
+ return {
133
+ destination: target.destination,
134
+ source,
135
+ status: (target.status ?? DEFAULT_REDIRECT_STATUS) as RedirectTarget,
136
+ };
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Rules back to the map `project.json` holds.
142
+ *
143
+ * A 301 collapses to the string spelling, because that is what the file already says for every rule
144
+ * an author wrote by hand and expanding them all to objects would rewrite the whole block on the
145
+ * first edit — the same "a no-op edit writes nothing" rule `tabs/project-config.ts` enforces one
146
+ * level down. A later duplicate source wins, which is what a JSON object means anyway; the grid
147
+ * refuses duplicates before they get here.
148
+ */
149
+ export function configFromRules(rules: readonly RedirectRule[]): RedirectConfig {
150
+ const config: RedirectConfig = {};
151
+ for (const rule of rules) {
152
+ if (rule.status === REWRITE) {
153
+ config[rule.source] = { destination: rule.destination, rewrite: true };
154
+ } else if (rule.status === DEFAULT_REDIRECT_STATUS) {
155
+ config[rule.source] = rule.destination;
156
+ } else {
157
+ config[rule.source] = { destination: rule.destination, status: rule.status };
158
+ }
159
+ }
160
+ return config;
161
+ }
162
+
163
+ // ─── Validation ───────────────────────────────────────────────────────────────
164
+
165
+ /** Which of the three rules a finding names. Printed in the Problem, so it is part of the contract. */
166
+ export type RedirectRuleName = "chain" | "loop" | "shadow";
167
+
168
+ export interface RedirectProblem {
169
+ rule: RedirectRuleName;
170
+ /** The rule's source path — the row the author has to look at. */
171
+ source: string;
172
+ /** One line, naming the rule and the path it walks. */
173
+ message: string;
174
+ /** Why it costs something, and what to do instead. */
175
+ detail: string;
176
+ }
177
+
178
+ /** The rule a request for `path` would hit: a literal source first, then the first matching pattern. */
179
+ function ruleFor(rules: readonly RedirectRule[], path: string): RedirectRule | undefined {
180
+ const target = normalizePath(path);
181
+ return (
182
+ rules.find((rule) => !isPattern(rule.source) && normalizePath(rule.source) === target) ??
183
+ rules.find((rule) => isPattern(rule.source) && matchesPattern(rule.source, target))
184
+ );
185
+ }
186
+
187
+ /** Follow destinations from a rule until they stop resolving. Returns the path walked. */
188
+ function walk(
189
+ rules: readonly RedirectRule[],
190
+ start: RedirectRule,
191
+ ): { path: string[]; looped: boolean } {
192
+ const path = [start.source];
193
+ const seen = new Set([normalizePath(start.source)]);
194
+ let current = start;
195
+ for (;;) {
196
+ if (isPattern(current.destination) || isExternal(current.destination)) {
197
+ return { looped: false, path };
198
+ }
199
+ const next = ruleFor(rules, current.destination);
200
+ path.push(current.destination);
201
+ if (!next) {
202
+ return { looped: false, path };
203
+ }
204
+ const key = normalizePath(next.source);
205
+ if (seen.has(key)) {
206
+ return { looped: true, path };
207
+ }
208
+ seen.add(key);
209
+ current = next;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Every chain, loop and shadow in a rule set.
215
+ *
216
+ * `routes` are the site's real routes with their file-based spelling (`/blog/[slug]`); they are
217
+ * translated by {@link routePattern} here so callers hand over what the project actually has rather
218
+ * than a pre-massaged list. An empty `routes` produces no shadow findings — which is honest only
219
+ * because the caller knows whether it enumerated them; a caller that could not read `pages/` must
220
+ * say so rather than pass `[]` and let this report a clean bill.
221
+ */
222
+ export function validateRedirects(
223
+ rules: readonly RedirectRule[],
224
+ routes: readonly string[],
225
+ ): RedirectProblem[] {
226
+ const problems: RedirectProblem[] = [];
227
+ const reportedLoops = new Set<string>();
228
+
229
+ for (const rule of rules) {
230
+ const { looped, path } = walk(rules, rule);
231
+ if (looped) {
232
+ // One cycle, one Problem: every rule on it walks the same ring, and three copies of the same
233
+ // Finding is three things to fix where there is one.
234
+ const ring = [...new Set(path.map((step) => normalizePath(step)))].toSorted().join(">");
235
+ if (!reportedLoops.has(ring)) {
236
+ reportedLoops.add(ring);
237
+ problems.push({
238
+ detail:
239
+ `Following the rules from ${rule.source} returns to a source already visited, so a ` +
240
+ `request for it never reaches a page. Break the cycle by pointing one of these rules ` +
241
+ `at a real route.`,
242
+ // The walk already closes the ring — its last step IS the source it returned to.
243
+ message: `Redirect loop: ${path.join(" → ")}`,
244
+ rule: "loop",
245
+ source: rule.source,
246
+ });
247
+ }
248
+ } else if (path.length > 2) {
249
+ problems.push({
250
+ detail:
251
+ `Each hop is a separate request and is cached separately, so a visitor pays for the ` +
252
+ `whole chain and a correction to the last hop can be masked by a cached earlier one. ` +
253
+ `Point ${rule.source} straight at ${path.at(-1)}.`,
254
+ message: `Redirect chain: ${path.join(" → ")}`,
255
+ rule: "chain",
256
+ source: rule.source,
257
+ });
258
+ }
259
+ }
260
+
261
+ for (const rule of rules) {
262
+ const covered = routes.filter((route) =>
263
+ isPattern(rule.source)
264
+ ? matchesPattern(rule.source, routePattern(route))
265
+ : matchesPattern(routePattern(route), rule.source),
266
+ );
267
+ const [first] = covered;
268
+ if (first === undefined) {
269
+ continue;
270
+ }
271
+ problems.push({
272
+ detail:
273
+ `This project has a page at ${first}${covered.length > 1 ? ` (and ${covered.length - 1} more)` : ""}. ` +
274
+ `A host that serves static files before consulting _redirects answers with the page and ` +
275
+ `this rule never fires; the build meanwhile writes its meta-refresh file over that page in ` +
276
+ `dist/ and warns (site-architecture.md §11.1). Remove the rule or the page.`,
277
+ message: `Redirect shadowed by a page: ${rule.source}`,
278
+ rule: "shadow",
279
+ source: rule.source,
280
+ });
281
+ }
282
+
283
+ return problems;
284
+ }
285
+
286
+ // ─── Import ───────────────────────────────────────────────────────────────────
287
+
288
+ export interface RedirectImport {
289
+ /** Which reader ran. Reported to the author, because the sniff can be wrong. */
290
+ format: "_redirects" | "csv";
291
+ rules: RedirectRule[];
292
+ /** One line per input line that could not be read. Never silently dropped. */
293
+ errors: string[];
294
+ }
295
+
296
+ /** A status token, tolerating Netlify's forcing `!` suffix. Null when it is not a status at all. */
297
+ function parseStatus(token: string | undefined): RedirectTarget | null {
298
+ if (token === undefined || token === "") {
299
+ return DEFAULT_REDIRECT_STATUS;
300
+ }
301
+ const raw = token.trim().replace(/!$/, "");
302
+ // An imported `_redirects` writes a rewrite as 200, and a pasted CSV may say so in words.
303
+ if (raw.toLowerCase() === REWRITE || Number(raw) === REWRITE_WIRE_STATUS) {
304
+ return REWRITE;
305
+ }
306
+ const value = Number(raw);
307
+ return (REDIRECT_STATUSES as readonly number[]).includes(value)
308
+ ? (value as RedirectTarget)
309
+ : null;
310
+ }
311
+
312
+ /** Netlify/Cloudflare `_redirects`: `source destination [status]`, `#` comments, blank lines. */
313
+ export function parseRedirectsFile(text: string): RedirectImport {
314
+ const rules: RedirectRule[] = [];
315
+ const errors: string[] = [];
316
+ for (const [index, raw] of text.split(/\r?\n/).entries()) {
317
+ const line = raw.trim();
318
+ if (line === "" || line.startsWith("#")) {
319
+ continue;
320
+ }
321
+ const [source, destination, status] = line.split(/\s+/);
322
+ if (!source || !destination) {
323
+ errors.push(`Line ${index + 1}: "${line}" — expected "source destination [status]".`);
324
+ continue;
325
+ }
326
+ const code = parseStatus(status);
327
+ if (code === null) {
328
+ errors.push(`Line ${index + 1}: "${status}" is not an HTTP status.`);
329
+ continue;
330
+ }
331
+ rules.push({ destination, source, status: code });
332
+ }
333
+ return { errors, format: "_redirects", rules };
334
+ }
335
+
336
+ /** Header spellings each column answers to. First match wins, left to right. */
337
+ const CSV_HEADERS: Readonly<Record<"source" | "destination" | "status", readonly string[]>> = {
338
+ destination: ["destination", "to", "new", "target"],
339
+ source: ["source", "from", "old", "path"],
340
+ status: ["status", "code"],
341
+ };
342
+
343
+ /** CSV with a `source,destination[,status]` header, or the same three columns positionally. */
344
+ export function parseRedirectsCsv(text: string): RedirectImport {
345
+ const doc = parseCsv(text);
346
+ const errors: string[] = [];
347
+ const rules: RedirectRule[] = [];
348
+ const headers = doc.headers.map((header) => header.trim().toLowerCase());
349
+ const indexOf = (names: readonly string[]) => headers.findIndex((h) => names.includes(h));
350
+
351
+ const sourceAt = indexOf(CSV_HEADERS.source);
352
+ const destinationAt = indexOf(CSV_HEADERS.destination);
353
+ const headed = sourceAt !== -1 && destinationAt !== -1;
354
+ // No recognizable header means the first record is data, not names — the two-column paste.
355
+ const records = headed ? doc.rows : [doc.headers, ...doc.rows];
356
+ const columns = headed
357
+ ? { destination: destinationAt, source: sourceAt, status: indexOf(CSV_HEADERS.status) }
358
+ : { destination: 1, source: 0, status: 2 };
359
+
360
+ for (const [index, record] of records.entries()) {
361
+ const source = (record[columns.source] ?? "").trim();
362
+ const destination = (record[columns.destination] ?? "").trim();
363
+ if (source === "" && destination === "") {
364
+ continue;
365
+ }
366
+ const line = headed ? index + 2 : index + 1;
367
+ if (source === "" || destination === "") {
368
+ errors.push(`Row ${line}: both a source and a destination are required.`);
369
+ continue;
370
+ }
371
+ const code = parseStatus(
372
+ columns.status === -1 ? undefined : (record[columns.status] ?? "").trim(),
373
+ );
374
+ if (code === null) {
375
+ errors.push(
376
+ `Row ${line}: "${record[columns.status]}" is not one of ${REDIRECT_TARGETS.join(", ")}.`,
377
+ );
378
+ continue;
379
+ }
380
+ rules.push({ destination, source, status: code });
381
+ }
382
+ return { errors, format: "csv", rules };
383
+ }
384
+
385
+ /**
386
+ * Read pasted text as whichever format it looks like.
387
+ *
388
+ * The sniff is one rule — a comma on the first content line means CSV — and the result SAYS which
389
+ * reader ran, because a `_redirects` line with a comma in a query string would be read as CSV and
390
+ * the author is the only one who can tell that the answer is wrong.
391
+ */
392
+ export function parseRedirectImport(text: string): RedirectImport {
393
+ const first = text
394
+ .split(/\r?\n/)
395
+ .map((line) => line.trim())
396
+ .find((line) => line !== "" && !line.startsWith("#"));
397
+ return first?.includes(",") ? parseRedirectsCsv(text) : parseRedirectsFile(text);
398
+ }
@@ -11,10 +11,24 @@
11
11
  *
12
12
  * Collections type their columns from the content-type schema (plus inferred extras found in the
13
13
  * data); the pages grid is fully inference-driven.
14
+ *
15
+ * **A collection listing obeys the draft perspective (§7.6).** `content/draft-state.ts` owns both
16
+ * the one flag and the one definition of what a draft is, and this source calls it rather than
17
+ * testing `draft === true` itself — which is what makes "including drafts" mean the same thing here
18
+ * as it does on the tab chip. It can, because that module imports nothing but `reactivity`: the
19
+ * richer `content/entry-model.ts` asks THIS file where a collection's entries live, so an edge back
20
+ * to it would be the cycle `import/no-cycle` rejects.
14
21
  */
15
22
  import { getPlatform } from "../../platform";
16
23
  import { projectState } from "../../store";
17
24
  import { workspace } from "../../workspace/workspace";
25
+ import {
26
+ DRAFT_FIELD,
27
+ applyDraftFilter,
28
+ hasDraftAxis,
29
+ includingDrafts,
30
+ } from "../../content/draft-state";
31
+ import { getGridController } from "../grid-controller";
18
32
  import { isRecentLocal, markLocalMutation } from "../../files/fs-events";
19
33
  import {
20
34
  defaultContentFormat,
@@ -192,10 +206,55 @@ interface EntryFileSourceOptions {
192
206
  insertFormatName: (path: string) => string | null;
193
207
  /** Inferred columns to front-load, in order (e.g. title/description for pages). */
194
208
  priorityFields?: string[];
209
+ /**
210
+ * Whether this listing is subject to the draft perspective (§7.6) — collections are, the pages
211
+ * tree is not. A draft is a property of a content ENTRY: a page carrying `draft: true` would
212
+ * vanish from the only tree that lists it, with no collection schema to explain why.
213
+ */
214
+ drafts?: boolean;
215
+ }
216
+
217
+ /**
218
+ * The live sources that obey the draft perspective.
219
+ *
220
+ * A `WeakSet` of the source objects rather than a list of tab-id prefixes: whether a listing is
221
+ * draft filtered is decided by one `drafts: true` in this file, and re-deriving it from
222
+ * `"grid://collection/"` in {@link reloadDraftAwareGrids} would be a second answer free to drift.
223
+ * Membership is added by the engine off that same option, so the two cannot disagree. Weak, so a
224
+ * closed tab's source is collectable.
225
+ */
226
+ const draftAwareSources = new WeakSet<GridSource>();
227
+
228
+ /**
229
+ * Reload every open grid whose rows depend on the draft perspective.
230
+ *
231
+ * The perspective is a flag, and a grid holds its rows in controller state that was loaded once —
232
+ * so `content.setIncludeDrafts` changing the flag has to say so. `load()` (not `refresh()`) is the
233
+ * right verb: it re-pulls columns and rows and leaves the edit buffer alone, whereas `refresh()`
234
+ * would stop to ask an author with pending edits whether to discard them, which is not a question a
235
+ * change of perspective should provoke.
236
+ */
237
+ export async function reloadDraftAwareGrids(): Promise<void> {
238
+ const reloads: Promise<void>[] = [];
239
+ for (const tab of workspace.tabs.values()) {
240
+ const controller = getGridController(tab);
241
+ if (controller && draftAwareSources.has(controller.source)) {
242
+ reloads.push(controller.load());
243
+ }
244
+ }
245
+ await Promise.all(reloads);
195
246
  }
196
247
 
197
248
  /** Shared engine for frontmatter-file sources (collections, pages). */
198
249
  function createEntryFileSource(opts: EntryFileSourceOptions): GridSource {
250
+ const source = buildEntryFileSource(opts);
251
+ if (opts.drafts) {
252
+ draftAwareSources.add(source);
253
+ }
254
+ return source;
255
+ }
256
+
257
+ function buildEntryFileSource(opts: EntryFileSourceOptions): GridSource {
199
258
  const entries = new Map<string, EntryRecord>();
200
259
  let loadPromise: Promise<void> | null = null;
201
260
  let columnsCache: GridColumn[] | null = null;
@@ -268,10 +327,46 @@ function createEntryFileSource(opts: EntryFileSourceOptions): GridSource {
268
327
  extraColumns.sort((a, b) => rank(a) - rank(b));
269
328
  }
270
329
 
271
- columnsCache = [pathColumn, ...schemaColumns, ...extraColumns];
330
+ columnsCache = withDraftColumn([pathColumn, ...schemaColumns, ...extraColumns]);
272
331
  return columnsCache;
273
332
  };
274
333
 
334
+ /** Whether this listing has a draft axis at all — the schema declares one, or an entry claims one. */
335
+ const draftAxis = (): boolean => {
336
+ if (!opts.drafts) {
337
+ return false;
338
+ }
339
+ const schema = opts.schema();
340
+ return (
341
+ hasDraftAxis(schema) ||
342
+ [...entries.values()].some((entry) => hasDraftAxis(schema, entry.frontmatter))
343
+ );
344
+ };
345
+
346
+ /**
347
+ * Put the Draft column immediately after Path, when this collection has a draft axis.
348
+ *
349
+ * A MOVE, and deliberately not a synthesis: wherever there is an axis there is already a column.
350
+ * A schema that declares `draft` gave `columnsFromSchema` one, and an entry that carries the
351
+ * field without the schema declaring it gave `inferColumnsFromRows` one — the two conditions of
352
+ * {@link hasDraftAxis} are exactly the two conditions that produce a column. Building a third here
353
+ * would be an unreachable branch pretending to be a safety net.
354
+ *
355
+ * What the move buys is that the one field deciding whether an entry appears in its own list is
356
+ * not sitting out at column nine, where declaration order or first-sight order happened to put
357
+ * it. A collection whose entries only ever say `draft: false` has NO axis and is left alone: the
358
+ * column is ordinary data there, and promoting it would invent a workflow the project never
359
+ * declared.
360
+ */
361
+ const withDraftColumn = (columns: GridColumn[]): GridColumn[] => {
362
+ const index = columns.findIndex((column) => column.field === DRAFT_FIELD);
363
+ if (index < 1 || !draftAxis()) {
364
+ return columns;
365
+ }
366
+ const rest = columns.filter((column) => column.field !== DRAFT_FIELD);
367
+ return [rest[0]!, columns[index]!, ...rest.slice(1)];
368
+ };
369
+
275
370
  const rowFor = (path: string, entry: EntryRecord): GridRow => {
276
371
  const cells: Record<string, GridCellValue> = { [PATH_FIELD]: path };
277
372
  for (const column of buildColumns()) {
@@ -468,7 +563,17 @@ function createEntryFileSource(opts: EntryFileSourceOptions): GridSource {
468
563
 
469
564
  async rows(): Promise<GridRowsResult> {
470
565
  await load();
471
- const rows = [...entries.entries()].map(([path, entry]) => rowFor(path, entry));
566
+ // The perspective is applied to the LISTING, not the load: `entries` keeps every parsed file,
567
+ // So the Draft column still knows the collection has an axis while its drafts are hidden, and
568
+ // Turning the perspective back on is a re-list rather than a re-read of the directory.
569
+ const listed = opts.drafts
570
+ ? applyDraftFilter(
571
+ [...entries.entries()],
572
+ ([, entry]) => entry.frontmatter,
573
+ includingDrafts(),
574
+ )
575
+ : [...entries.entries()];
576
+ const rows = listed.map(([path, entry]) => rowFor(path, entry));
472
577
  return { rows, total: rows.length };
473
578
  },
474
579
  };
@@ -485,6 +590,7 @@ export function createCollectionSource(typeName: string): GridSource {
485
590
  };
486
591
 
487
592
  return createEntryFileSource({
593
+ drafts: true,
488
594
  id: makeGridTabId({ kind: "collection", name: typeName }),
489
595
  insertFormatName: (path) => {
490
596
  const info = requireInfo();