@jxsuite/studio 4.0.0 → 5.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 (613) hide show
  1. package/README.md +1 -1
  2. package/data/elements-meta.json +5 -15
  3. package/data/html-meta.json +51 -6
  4. package/dist/chunks/{2020-xp8vnwma.js → 2020-g79n01v9.js} +14 -14
  5. package/dist/chunks/action-group-sp6tna8v.js +118 -0
  6. package/dist/chunks/adapter-nj7tnjfn.js +13 -0
  7. package/dist/chunks/cf-account-picker-fyf63pmy.js +495 -0
  8. package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
  9. package/dist/chunks/color-area-v17df7b8.js +154 -0
  10. package/dist/chunks/color-field-anwybnkn.js +208 -0
  11. package/dist/chunks/color-slider-44ez96fc.js +79 -0
  12. package/dist/chunks/color-xgg3kfw7.js +35 -0
  13. package/dist/chunks/combobox-5e7jev23.js +236 -0
  14. package/dist/chunks/components-2w1f35qs.js +18 -0
  15. package/dist/chunks/content-source-3wqeas45.js +46 -0
  16. package/dist/chunks/convert-to-repeater-d0cgnb6d.js +639 -0
  17. package/dist/chunks/dialog-s890b918.js +85 -0
  18. package/dist/chunks/editor-3qhbg51x.js +35 -0
  19. package/dist/chunks/entry-commands-d6g50j7s.js +42 -0
  20. package/dist/chunks/extension-sections-fy7q4fe9.js +651 -0
  21. package/dist/chunks/file-ops-3btj6st0.js +47 -0
  22. package/dist/chunks/files-ef8fgs3w.js +72 -0
  23. package/dist/chunks/format-host-3c33nwr8.js +40 -0
  24. package/dist/chunks/fs-events-13tj6sex.js +18 -0
  25. package/dist/chunks/git-panel-4wp2rn9b.js +49 -0
  26. package/dist/chunks/icons-yy10wk9r.js +402 -0
  27. package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
  28. package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
  29. package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
  30. package/dist/chunks/iframe-host-jgesd924.js +115 -0
  31. package/dist/chunks/{index-nj44jpzj.js → index-mdmyx6s2.js} +11 -11
  32. package/dist/chunks/index-n4j7qxyr.js +109 -0
  33. package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
  34. package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
  35. package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
  36. package/dist/chunks/layers-1nq5evsh.js +29 -0
  37. package/dist/chunks/layers-panel-ar2gh90g.js +40 -0
  38. package/dist/chunks/listbox-6v2ac08j.js +44 -0
  39. package/dist/chunks/lit-html-m244w9nh.js +17 -0
  40. package/dist/chunks/media-picker-2qbetq73.js +33 -0
  41. package/dist/chunks/menu-q99sy9rf.js +285 -0
  42. package/dist/chunks/menu-rf7n72mf.js +14 -0
  43. package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
  44. package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
  45. package/dist/chunks/{monaco-setup-rws7k4br.js → monaco-setup-8r62fj6y.js} +27 -37
  46. package/dist/chunks/notify-628t0anh.js +21 -0
  47. package/dist/chunks/number-field-5cndyft8.js +72 -0
  48. package/dist/chunks/parse-0zx14y15.js +10 -0
  49. package/dist/chunks/platform-ts9cg1a4.js +15 -0
  50. package/dist/chunks/popover-47z3ytva.js +25 -0
  51. package/dist/chunks/publish-panel-98f7sr3g.js +27 -0
  52. package/dist/chunks/quick-search-srzz83k2.js +45 -0
  53. package/dist/chunks/references-n553f0e1.js +26 -0
  54. package/dist/chunks/right-panel-rrgx2dn3.js +45 -0
  55. package/dist/chunks/section-registry-5pq32t0q.js +25 -0
  56. package/dist/chunks/select-2r16ez41.js +49 -0
  57. package/dist/chunks/split-g7nm1rgb.js +191 -0
  58. package/dist/chunks/studio-0hqbpbd0.js +62 -0
  59. package/dist/chunks/studio-18avnhjp.js +2430 -0
  60. package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
  61. package/dist/chunks/{studio-hbtp0eh1.js → studio-25hkdrkw.js} +356 -85
  62. package/dist/chunks/studio-26d7myd1.js +258 -0
  63. package/dist/chunks/{studio-p8j86wag.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/studio-3gxv60tn.js +422 -0
  65. package/dist/chunks/{studio-8swrjbny.js → studio-3hxkd92t.js} +275 -163
  66. package/dist/chunks/studio-4m0haseg.js +185 -0
  67. package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
  68. package/dist/chunks/{studio-gv8vvshc.js → studio-5523bpzt.js} +11 -11
  69. package/dist/chunks/{studio-5q69gfag.js → studio-57mf66w2.js} +1753 -2484
  70. package/dist/chunks/studio-5jy81t3s.js +17463 -0
  71. package/dist/chunks/studio-5rt7sqa0.js +1168 -0
  72. package/dist/chunks/studio-5te5tqng.js +81 -0
  73. package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
  74. package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
  75. package/dist/chunks/studio-6xa21ej9.js +28 -0
  76. package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
  77. package/dist/chunks/{studio-eenfkt4e.js → studio-79y5sz51.js} +52 -38
  78. package/dist/chunks/{studio-4q0wznzf.js → studio-7e11jpa9.js} +461 -145
  79. package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
  80. package/dist/chunks/studio-7xptrg0j.js +954 -0
  81. package/dist/chunks/{studio-cft9r4r7.js → studio-81390n6f.js} +22 -22
  82. package/dist/chunks/{studio-q43a5y9h.js → studio-8jn5yhyn.js} +16 -16
  83. package/dist/chunks/studio-8qtrpzr3.js +839 -0
  84. package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
  85. package/dist/chunks/studio-9xxz1q69.js +1356 -0
  86. package/dist/chunks/studio-akbww0j5.js +13615 -0
  87. package/dist/chunks/studio-arfpe389.js +195 -0
  88. package/dist/chunks/{studio-2zkz8evn.js → studio-az3zd49t.js} +473 -281
  89. package/dist/chunks/studio-cpvb2ed1.js +1171 -0
  90. package/dist/chunks/{studio-9r6j5ev1.js → studio-d0300636.js} +5078 -19
  91. package/dist/chunks/{studio-bgxt93ga.js → studio-d055c37e.js} +13 -13
  92. package/dist/chunks/studio-d1ja2x9r.js +362 -0
  93. package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
  94. package/dist/chunks/{studio-4pqqxbrt.js → studio-em9nw8eh.js} +301 -112
  95. package/dist/chunks/studio-erdatzvp.js +1190 -0
  96. package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
  97. package/dist/chunks/studio-fabxk6r9.js +2811 -0
  98. package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
  99. package/dist/chunks/studio-hhvs4sdg.js +195 -0
  100. package/dist/chunks/{studio-68n67094.js → studio-kcgff4r0.js} +15 -18
  101. package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
  102. package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
  103. package/dist/chunks/studio-pm6hgy7r.js +4763 -0
  104. package/dist/chunks/studio-q0a3yk4n.js +33 -0
  105. package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
  106. package/dist/chunks/studio-qahxvnan.js +111 -0
  107. package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
  108. package/dist/chunks/{studio-kjjsjak4.js → studio-rkstz940.js} +3661 -6136
  109. package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
  110. package/dist/chunks/studio-rpyah2ew.js +2805 -0
  111. package/dist/chunks/studio-rs76k67r.js +12 -0
  112. package/dist/chunks/{studio-yxrkyv8a.js → studio-s6z853aq.js} +12108 -11766
  113. package/dist/chunks/{studio-sf7zjgv1.js → studio-sgdc5pfc.js} +8 -8
  114. package/dist/chunks/studio-tb3ev01j.js +763 -0
  115. package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
  116. package/dist/chunks/studio-vhv5tgxm.js +51 -0
  117. package/dist/chunks/{studio-htsf0w6m.js → studio-vz68yn9e.js} +31 -154
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/{studio-eesa1myv.js → studio-x3rty6tr.js} +58 -58
  120. package/dist/chunks/studio-xsykpvsh.js +8448 -0
  121. package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
  122. package/dist/chunks/studio-yxa81f45.js +12 -0
  123. package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
  124. package/dist/chunks/tabs-wbp1cdws.js +156 -0
  125. package/dist/chunks/textfield-abk6qyee.js +73 -0
  126. package/dist/chunks/toast-780pvfbv.js +27 -0
  127. package/dist/chunks/toast-host-3sqv46sn.js +116 -0
  128. package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
  129. package/dist/chunks/tooltip-eptb614w.js +369 -0
  130. package/dist/chunks/tree-a73p0pqm.js +367 -0
  131. package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
  132. package/dist/chunks/workspace-wrew8zk4.js +69 -0
  133. package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
  134. package/dist/iframe-entry.js +1812 -698
  135. package/dist/manifest.json +7 -0
  136. package/dist/studio.js +52900 -36573
  137. package/dist/workers/editor.worker.js +1040 -1085
  138. package/dist/workers/json.worker.js +1946 -1991
  139. package/dist/workers/ts.worker.js +4960 -5005
  140. package/favicon.ico +0 -0
  141. package/index.html +2 -0
  142. package/package.json +22 -55
  143. package/src/about/about-modal.ts +73 -123
  144. package/src/browse/library-commands.ts +20 -49
  145. package/src/browse/library-layouts.ts +87 -218
  146. package/src/browse/library-pane.ts +406 -438
  147. package/src/canvas/canvas-helpers.ts +0 -49
  148. package/src/canvas/canvas-live-render.ts +25 -2
  149. package/src/canvas/canvas-render.ts +666 -231
  150. package/src/canvas/canvas-surface.ts +0 -1
  151. package/src/canvas/canvas-utils.ts +310 -117
  152. package/src/canvas/dialog-path.ts +76 -0
  153. package/src/canvas/dialog-state.ts +67 -0
  154. package/src/canvas/diff-marks.ts +252 -0
  155. package/src/canvas/diff-toolbar.ts +349 -0
  156. package/src/canvas/diff-view.ts +123 -0
  157. package/src/canvas/edit-width-drag.ts +187 -0
  158. package/src/canvas/edit-width.ts +150 -0
  159. package/src/canvas/iframe-entry.ts +125 -8
  160. package/src/canvas/iframe-host.ts +271 -15
  161. package/src/canvas/iframe-interaction.ts +133 -13
  162. package/src/canvas/iframe-patch.ts +7 -1
  163. package/src/canvas/iframe-position.ts +5 -6
  164. package/src/canvas/iframe-protocol.ts +106 -5
  165. package/src/canvas/iframe-render.ts +373 -5
  166. package/src/canvas/iframe-slash.ts +2 -2
  167. package/src/canvas/iframe-subtree.ts +5 -8
  168. package/src/canvas/path-mapping.ts +25 -3
  169. package/src/canvas/popover-path.ts +106 -0
  170. package/src/canvas/popover-state.ts +155 -0
  171. package/src/canvas/surface-registry.ts +21 -10
  172. package/src/collab/presence-chips.ts +51 -73
  173. package/src/commands/app-commands.ts +10 -0
  174. package/src/commands/budget.ts +12 -0
  175. package/src/commands/command-args.ts +369 -6
  176. package/src/commands/defaults.ts +44 -14
  177. package/src/commands/live-context.ts +11 -5
  178. package/src/commands/reference.ts +12 -2
  179. package/src/commands/registry.ts +165 -8
  180. package/src/content/entry-commands.ts +16 -23
  181. package/src/content/entry-editor.ts +144 -101
  182. package/src/editor/canvas-slash-bridge.ts +2 -2
  183. package/src/editor/context-menu.ts +49 -200
  184. package/src/editor/convert-to-component.ts +42 -82
  185. package/src/editor/convert-to-repeater.ts +132 -173
  186. package/src/editor/inline-edit.ts +11 -0
  187. package/src/editor/shortcuts.ts +65 -26
  188. package/src/editor/slash-menu.ts +150 -157
  189. package/src/files/file-ops.ts +117 -11
  190. package/src/files/files.ts +949 -664
  191. package/src/files/json-layout.ts +474 -0
  192. package/src/files/media-paths.ts +13 -42
  193. package/src/files/media-usage.ts +41 -131
  194. package/src/files/serialize-document.ts +8 -1
  195. package/src/format/convert-file.ts +16 -10
  196. package/src/format/format-host.ts +50 -1
  197. package/src/github/github-auth.ts +71 -29
  198. package/src/github/github-publish.ts +57 -65
  199. package/src/grid/cell-editors.ts +46 -4
  200. package/src/grid/cell-popovers.ts +65 -62
  201. package/src/grid/grid-open.ts +86 -64
  202. package/src/grid/grid-panel.ts +390 -469
  203. package/src/grid/redirects-grid.ts +40 -44
  204. package/src/hosting/document.ts +2 -0
  205. package/src/hosting/layout.ts +10 -0
  206. package/src/i18n/i18n-commands.ts +127 -62
  207. package/src/media/media-pane.ts +83 -133
  208. package/src/new-project/add-repo-modal.ts +125 -176
  209. package/src/new-project/import-tab.ts +201 -250
  210. package/src/new-project/location-fields.ts +101 -165
  211. package/src/new-project/new-project-modal.ts +376 -367
  212. package/src/packages/jxsuite-update.ts +76 -18
  213. package/src/panels/activity-panel.ts +137 -104
  214. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  215. package/src/panels/ai-chat/chat-view.ts +307 -472
  216. package/src/panels/ai-chat/composer.ts +175 -153
  217. package/src/panels/ai-chat/sessions-view.ts +31 -62
  218. package/src/panels/ai-panel.ts +210 -185
  219. package/src/panels/block-action-bar.ts +437 -643
  220. package/src/panels/bottom-dock.ts +112 -47
  221. package/src/panels/chat-panel.ts +19 -14
  222. package/src/panels/data-explorer.ts +141 -102
  223. package/src/panels/data-grid.ts +183 -150
  224. package/src/panels/dnd.ts +75 -20
  225. package/src/panels/editors.ts +26 -19
  226. package/src/panels/elements-panel.ts +243 -186
  227. package/src/panels/empty-state.ts +26 -42
  228. package/src/panels/events-panel.ts +950 -729
  229. package/src/panels/formula-workspace.ts +434 -207
  230. package/src/panels/frontmatter-fields.ts +185 -153
  231. package/src/panels/frontmatter-panel.ts +411 -187
  232. package/src/panels/git-diff-open.ts +148 -0
  233. package/src/panels/git-panel.ts +679 -605
  234. package/src/panels/head-panel.ts +617 -435
  235. package/src/panels/i18n-panel.ts +153 -133
  236. package/src/panels/imports-panel.ts +388 -414
  237. package/src/panels/jump-bar.ts +174 -147
  238. package/src/panels/layers-panel.ts +785 -671
  239. package/src/panels/left-panel.ts +166 -94
  240. package/src/panels/navigator-panels.ts +1 -1
  241. package/src/panels/pane-context.ts +529 -677
  242. package/src/panels/pane-grid.ts +265 -241
  243. package/src/panels/panel-registry.ts +15 -4
  244. package/src/panels/problems-panel.ts +177 -99
  245. package/src/panels/properties-panel.ts +1525 -966
  246. package/src/panels/provenance.ts +31 -113
  247. package/src/panels/quick-search.ts +217 -172
  248. package/src/panels/right-panel.ts +135 -169
  249. package/src/panels/seo-modal.ts +346 -167
  250. package/src/panels/settings-menu.ts +109 -646
  251. package/src/panels/settings-pane.ts +102 -86
  252. package/src/panels/signals-panel.ts +1318 -1172
  253. package/src/panels/statement-editor.ts +556 -428
  254. package/src/panels/style-panel.ts +1367 -630
  255. package/src/panels/style-utils.ts +36 -32
  256. package/src/panels/stylebook-doc.ts +30 -73
  257. package/src/panels/stylebook-layers-panel.ts +202 -109
  258. package/src/panels/stylebook-panel.ts +124 -88
  259. package/src/panels/tab-strip.ts +385 -287
  260. package/src/platforms/cloud.ts +347 -86
  261. package/src/platforms/devserver.ts +13 -0
  262. package/src/publish/deploy-checklist.ts +124 -78
  263. package/src/publish/publish-commands.ts +4 -12
  264. package/src/publish/publish-panel.ts +165 -299
  265. package/src/services/a11y-report.ts +100 -43
  266. package/src/services/ai-command-tools.ts +468 -0
  267. package/src/services/ai-import-tools.ts +50 -12
  268. package/src/services/ai-models.ts +108 -1
  269. package/src/services/ai-session-store.ts +6 -0
  270. package/src/services/ai-system-prompt.ts +110 -18
  271. package/src/services/ai-tools.ts +28 -143
  272. package/src/services/ai-write-report.ts +174 -0
  273. package/src/services/context-manager.ts +12 -1
  274. package/src/services/document-assistant.ts +50 -2
  275. package/src/services/idle.ts +16 -14
  276. package/src/services/live-surfaces.ts +237 -0
  277. package/src/services/model-uri.ts +75 -0
  278. package/src/services/notify.ts +63 -6
  279. package/src/services/popover-report.ts +325 -0
  280. package/src/services/surface-registry.ts +56 -0
  281. package/src/services/tool-executor.ts +17 -0
  282. package/src/settings/contexts-section.ts +301 -299
  283. package/src/settings/contributed-section.ts +361 -222
  284. package/src/settings/css-vars-editor.ts +320 -325
  285. package/src/settings/defs-editor.ts +572 -535
  286. package/src/settings/dependencies-editor.ts +79 -107
  287. package/src/settings/extension-commands.ts +379 -0
  288. package/src/settings/extension-rows.ts +113 -0
  289. package/src/settings/extensions-section.ts +287 -0
  290. package/src/settings/general-settings.ts +130 -181
  291. package/src/settings/head-editor.ts +10 -305
  292. package/src/settings/locales-section.ts +146 -196
  293. package/src/settings/preferences-accounts.ts +193 -12
  294. package/src/settings/preferences-dialog.ts +384 -307
  295. package/src/settings/project-sections.ts +108 -181
  296. package/src/settings/schema-field-ui.ts +11 -399
  297. package/src/settings/section-registry.ts +8 -1
  298. package/src/settings/settings-document.ts +31 -29
  299. package/src/shell/tree.ts +42 -87
  300. package/src/shell.ts +48 -36
  301. package/src/studio.ts +90 -67
  302. package/src/style/token-ref.ts +0 -39
  303. package/src/surfaces/about.json +263 -0
  304. package/src/surfaces/about.ts +163 -0
  305. package/src/surfaces/add-repo.json +486 -0
  306. package/src/surfaces/add-repo.ts +236 -0
  307. package/src/surfaces/ai-chat.json +2185 -0
  308. package/src/surfaces/ai-chat.ts +549 -0
  309. package/src/surfaces/ai-credentials-form.json +332 -0
  310. package/src/surfaces/ai-credentials-form.ts +151 -0
  311. package/src/surfaces/ai-managed-connect.json +108 -0
  312. package/src/surfaces/ai-managed-connect.ts +123 -0
  313. package/src/surfaces/ai-model-picker.json +114 -0
  314. package/src/surfaces/ai-model-picker.ts +139 -0
  315. package/src/surfaces/block-action-bar.json +640 -0
  316. package/src/surfaces/block-action-bar.ts +368 -0
  317. package/src/surfaces/bottom-dock.json +154 -0
  318. package/src/surfaces/bottom-dock.ts +125 -0
  319. package/src/surfaces/canvas-stage.json +444 -0
  320. package/src/surfaces/canvas-stage.ts +253 -0
  321. package/src/surfaces/cf-account-picker.json +300 -0
  322. package/src/surfaces/cf-account-picker.ts +214 -0
  323. package/src/surfaces/commandbar.json +984 -0
  324. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +414 -389
  325. package/src/surfaces/convert-repeater.json +309 -0
  326. package/src/surfaces/convert-repeater.ts +276 -0
  327. package/src/surfaces/data-actions.json +157 -0
  328. package/src/surfaces/data-actions.ts +115 -0
  329. package/src/surfaces/dialog.json +289 -0
  330. package/src/surfaces/dialog.ts +343 -0
  331. package/src/surfaces/diff-toolbar.json +266 -0
  332. package/src/surfaces/diff-toolbar.ts +139 -0
  333. package/src/surfaces/doc-header.json +739 -0
  334. package/src/surfaces/doc-header.ts +223 -0
  335. package/src/surfaces/empty-state.json +152 -0
  336. package/src/surfaces/empty-state.ts +173 -0
  337. package/src/surfaces/entry-editor.json +281 -0
  338. package/src/surfaces/entry-editor.ts +191 -0
  339. package/src/surfaces/expression-editor.json +977 -0
  340. package/src/surfaces/expression-editor.ts +266 -0
  341. package/src/surfaces/files-panel.json +592 -0
  342. package/src/surfaces/files-panel.ts +301 -0
  343. package/src/surfaces/formula-palette.json +364 -0
  344. package/src/surfaces/formula-palette.ts +340 -0
  345. package/src/surfaces/git-panel.json +1822 -0
  346. package/src/surfaces/git-panel.ts +272 -0
  347. package/src/surfaces/github-auth.json +112 -0
  348. package/src/surfaces/github-auth.ts +139 -0
  349. package/src/surfaces/github-publish.json +206 -0
  350. package/src/surfaces/github-publish.ts +169 -0
  351. package/src/surfaces/grid-cell.json +218 -0
  352. package/src/surfaces/grid-cell.ts +120 -0
  353. package/src/surfaces/grid-open.json +147 -0
  354. package/src/surfaces/grid-open.ts +87 -0
  355. package/src/surfaces/grid-panel.json +706 -0
  356. package/src/surfaces/grid-panel.ts +206 -0
  357. package/src/surfaces/grid-replace.json +179 -0
  358. package/src/surfaces/grid-replace.ts +89 -0
  359. package/src/surfaces/grid-views.json +446 -0
  360. package/src/surfaces/grid-views.ts +172 -0
  361. package/src/surfaces/inspector-dock.json +187 -0
  362. package/src/surfaces/inspector-dock.ts +161 -0
  363. package/src/surfaces/jump-bar.json +243 -0
  364. package/src/surfaces/jump-bar.ts +129 -0
  365. package/src/surfaces/jxsuite-update.json +146 -0
  366. package/src/surfaces/jxsuite-update.ts +149 -0
  367. package/src/surfaces/library-pane.json +1509 -0
  368. package/src/surfaces/library-pane.ts +363 -0
  369. package/src/surfaces/logic-panel.json +1949 -0
  370. package/src/surfaces/logic-panel.ts +369 -0
  371. package/src/surfaces/logic-workspace.json +685 -0
  372. package/src/surfaces/logic-workspace.ts +238 -0
  373. package/src/surfaces/media-browser.json +303 -0
  374. package/src/surfaces/media-browser.ts +145 -0
  375. package/src/surfaces/media-field.json +163 -0
  376. package/src/surfaces/media-field.ts +116 -0
  377. package/src/surfaces/media-pane.json +513 -0
  378. package/src/surfaces/media-pane.ts +197 -0
  379. package/src/surfaces/menu.json +232 -0
  380. package/src/surfaces/menu.ts +375 -0
  381. package/src/surfaces/navigator-dock.json +113 -0
  382. package/src/surfaces/navigator-dock.ts +130 -0
  383. package/src/surfaces/new-project.json +1654 -0
  384. package/src/surfaces/new-project.ts +411 -0
  385. package/src/surfaces/palette.json +535 -0
  386. package/src/surfaces/pane-context.json +1497 -0
  387. package/src/surfaces/pane-context.ts +513 -0
  388. package/src/surfaces/pane-grid.json +188 -0
  389. package/src/surfaces/pane-grid.ts +250 -0
  390. package/src/surfaces/panel-activity.json +563 -0
  391. package/src/surfaces/panel-activity.ts +187 -0
  392. package/src/surfaces/panel-data.json +141 -0
  393. package/src/surfaces/panel-data.ts +156 -0
  394. package/src/surfaces/panel-deploy-checklist.json +283 -0
  395. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  396. package/src/surfaces/panel-elements.json +367 -0
  397. package/src/surfaces/panel-elements.ts +175 -0
  398. package/src/surfaces/panel-i18n.json +569 -0
  399. package/src/surfaces/panel-i18n.ts +212 -0
  400. package/src/surfaces/panel-imports.json +780 -0
  401. package/src/surfaces/panel-imports.ts +209 -0
  402. package/src/surfaces/panel-outline.json +734 -0
  403. package/src/surfaces/panel-outline.ts +326 -0
  404. package/src/surfaces/panel-page.json +1017 -0
  405. package/src/surfaces/panel-page.ts +240 -0
  406. package/src/surfaces/panel-problems.json +497 -0
  407. package/src/surfaces/panel-problems.ts +157 -0
  408. package/src/surfaces/panel-signals.json +1973 -0
  409. package/src/surfaces/panel-signals.ts +436 -0
  410. package/src/surfaces/panel-stylebook-layers.json +317 -0
  411. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  412. package/src/surfaces/preferences.json +850 -0
  413. package/src/surfaces/preferences.ts +332 -0
  414. package/src/surfaces/progress-modal.json +110 -0
  415. package/src/surfaces/progress-modal.ts +150 -0
  416. package/src/surfaces/properties-panel.json +1578 -0
  417. package/src/surfaces/properties-panel.ts +337 -0
  418. package/src/surfaces/publish.json +945 -0
  419. package/src/surfaces/publish.ts +392 -0
  420. package/src/surfaces/push-plan.json +110 -0
  421. package/src/surfaces/push-plan.ts +87 -0
  422. package/src/surfaces/rail.json +255 -0
  423. package/src/surfaces/rail.ts +206 -0
  424. package/src/surfaces/reference-field.json +191 -0
  425. package/src/surfaces/reference-field.ts +168 -0
  426. package/src/surfaces/schema-builder.json +1011 -0
  427. package/src/surfaces/schema-builder.ts +181 -0
  428. package/src/surfaces/schema-form.json +1013 -0
  429. package/src/surfaces/schema-form.ts +287 -0
  430. package/src/surfaces/secret-field.json +60 -0
  431. package/src/surfaces/secret-field.ts +146 -0
  432. package/src/surfaces/seo.json +932 -0
  433. package/src/surfaces/seo.ts +334 -0
  434. package/src/surfaces/settings-contexts.json +538 -0
  435. package/src/surfaces/settings-contexts.ts +196 -0
  436. package/src/surfaces/settings-contributed.json +489 -0
  437. package/src/surfaces/settings-contributed.ts +199 -0
  438. package/src/surfaces/settings-css-vars.json +820 -0
  439. package/src/surfaces/settings-css-vars.ts +199 -0
  440. package/src/surfaces/settings-defs.json +1363 -0
  441. package/src/surfaces/settings-defs.ts +232 -0
  442. package/src/surfaces/settings-deploy.json +108 -0
  443. package/src/surfaces/settings-deploy.ts +135 -0
  444. package/src/surfaces/settings-extensions.json +486 -0
  445. package/src/surfaces/settings-extensions.ts +201 -0
  446. package/src/surfaces/settings-head.json +779 -0
  447. package/src/surfaces/settings-head.ts +397 -0
  448. package/src/surfaces/settings-locales.json +420 -0
  449. package/src/surfaces/settings-locales.ts +137 -0
  450. package/src/surfaces/settings-overview.json +423 -0
  451. package/src/surfaces/settings-overview.ts +218 -0
  452. package/src/surfaces/settings-packages.json +549 -0
  453. package/src/surfaces/settings-packages.ts +158 -0
  454. package/src/surfaces/settings-pane.json +147 -0
  455. package/src/surfaces/settings-pane.ts +150 -0
  456. package/src/surfaces/settings-rawjson.json +104 -0
  457. package/src/surfaces/settings-rawjson.ts +107 -0
  458. package/src/surfaces/shell.json +99 -0
  459. package/src/surfaces/shell.ts +30 -0
  460. package/src/surfaces/slash-menu.json +180 -0
  461. package/src/surfaces/slash-menu.ts +272 -0
  462. package/src/surfaces/statements.json +607 -0
  463. package/src/surfaces/statements.ts +238 -0
  464. package/src/surfaces/statusbar.json +94 -0
  465. package/src/{panels → surfaces}/statusbar.ts +163 -90
  466. package/src/surfaces/style-panel.json +1643 -0
  467. package/src/surfaces/style-panel.ts +338 -0
  468. package/src/surfaces/stylebook-chrome.json +122 -0
  469. package/src/surfaces/stylebook-chrome.ts +122 -0
  470. package/src/surfaces/tab-strip.json +448 -0
  471. package/src/surfaces/tab-strip.ts +200 -0
  472. package/src/surfaces/target-line.json +419 -0
  473. package/src/surfaces/target-line.ts +349 -0
  474. package/src/surfaces/toasts.json +189 -0
  475. package/src/surfaces/welcome.json +571 -0
  476. package/src/surfaces/welcome.ts +338 -0
  477. package/src/tabs/project-config.ts +71 -15
  478. package/src/tabs/tab.ts +52 -1
  479. package/src/types.ts +49 -3
  480. package/src/ui/ai-credentials-form.ts +104 -100
  481. package/src/ui/ai-managed-connect.ts +116 -49
  482. package/src/ui/ai-model-picker.ts +102 -47
  483. package/src/ui/cf-account-picker.ts +135 -0
  484. package/src/ui/color-selector.ts +80 -301
  485. package/src/ui/dynamic-slot.ts +43 -283
  486. package/src/ui/expression-editor.ts +797 -576
  487. package/src/ui/field-input.ts +8 -181
  488. package/src/ui/form-controls.ts +838 -366
  489. package/src/ui/formula-chips.ts +80 -85
  490. package/src/ui/kit.ts +33 -0
  491. package/src/ui/layers.ts +293 -593
  492. package/src/ui/media-picker.ts +203 -284
  493. package/src/ui/panel-resize.ts +251 -64
  494. package/src/ui/popover-surface.ts +153 -0
  495. package/src/ui/progress-modal.ts +22 -60
  496. package/src/ui/regions.ts +13 -7
  497. package/src/ui/schema-form.ts +906 -440
  498. package/src/ui/surface.ts +122 -0
  499. package/src/ui/unit-selector.ts +15 -131
  500. package/src/ui/value-source.ts +14 -0
  501. package/src/ui/virtual-window.ts +88 -9
  502. package/src/utils/canvas-media.ts +72 -0
  503. package/src/utils/edit-display.ts +7 -2
  504. package/src/utils/element-selectors.ts +69 -0
  505. package/src/utils/link-target.ts +2 -2
  506. package/src/utils/studio-utils.ts +0 -10
  507. package/src/view.ts +15 -0
  508. package/src/workspace/pane-derive.ts +36 -14
  509. package/src/workspace/workspace.ts +15 -6
  510. package/styles/canvas.css +72 -467
  511. package/styles/forced-colors.css +48 -82
  512. package/styles/forced-colors.json +41 -0
  513. package/styles/inspector.css +2 -1301
  514. package/styles/overlays.css +96 -2823
  515. package/styles/panels.css +6 -1423
  516. package/styles/shell-frame.css +272 -0
  517. package/styles/shell-frame.json +227 -0
  518. package/styles/shell.css +0 -1532
  519. package/styles/tokens.css +65 -136
  520. package/styles/tokens.json +84 -0
  521. package/dist/chunks/Overlay.dev-723bv0df.js +0 -23
  522. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  523. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  524. package/dist/chunks/components-zmx10t62.js +0 -21
  525. package/dist/chunks/content-source-t88vb2dr.js +0 -49
  526. package/dist/chunks/convert-to-repeater-dhw1nrz9.js +0 -268
  527. package/dist/chunks/editor-cptq5jp1.js +0 -37
  528. package/dist/chunks/entry-commands-6z6bhxg0.js +0 -46
  529. package/dist/chunks/extension-sections-3ydgjbdy.js +0 -311
  530. package/dist/chunks/file-ops-rskmhxyq.js +0 -48
  531. package/dist/chunks/files-0cratsr0.js +0 -92
  532. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  533. package/dist/chunks/focus-trap.esm-ggawf1hy.js +0 -1024
  534. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  535. package/dist/chunks/format-host-tgcmfs4p.js +0 -43
  536. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  537. package/dist/chunks/git-panel-0yywyk76.js +0 -58
  538. package/dist/chunks/index-zqxmqxmh.js +0 -109
  539. package/dist/chunks/jx-validate-8q2g05x1.js +0 -19
  540. package/dist/chunks/layers-panel-z3tqe5cd.js +0 -67
  541. package/dist/chunks/layers-q75tj2mf.js +0 -45
  542. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  543. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  544. package/dist/chunks/notify-yjm79n4s.js +0 -30
  545. package/dist/chunks/parse-24d0gay3.js +0 -19
  546. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  547. package/dist/chunks/publish-panel-54jvhskq.js +0 -28
  548. package/dist/chunks/quick-search-cgfmjwq5.js +0 -65
  549. package/dist/chunks/references-3qx8fkyp.js +0 -29
  550. package/dist/chunks/right-panel-cqp8bc4v.js +0 -46
  551. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  552. package/dist/chunks/sp-overlay.dev-yztmtrs1.js +0 -18
  553. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  554. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  555. package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +0 -15
  556. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  557. package/dist/chunks/studio-0a5qrh01.js +0 -3
  558. package/dist/chunks/studio-18j3bmcz.js +0 -14
  559. package/dist/chunks/studio-1hbqnt19.js +0 -1823
  560. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  561. package/dist/chunks/studio-5rywrkxz.js +0 -1903
  562. package/dist/chunks/studio-7afd21nw.js +0 -67
  563. package/dist/chunks/studio-7nte2hj4.js +0 -613
  564. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  565. package/dist/chunks/studio-8q517dfr.js +0 -24
  566. package/dist/chunks/studio-9c4k4vhv.js +0 -357
  567. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  568. package/dist/chunks/studio-a0tswy0w.js +0 -58
  569. package/dist/chunks/studio-ahjfdy6e.js +0 -387
  570. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  571. package/dist/chunks/studio-bw5dz6km.js +0 -608
  572. package/dist/chunks/studio-cb12yz9w.js +0 -15055
  573. package/dist/chunks/studio-d1ks410h.js +0 -175
  574. package/dist/chunks/studio-eqq1w638.js +0 -113
  575. package/dist/chunks/studio-ex8zyp98.js +0 -1813
  576. package/dist/chunks/studio-h4rxscfc.js +0 -10
  577. package/dist/chunks/studio-kyh9avvv.js +0 -28
  578. package/dist/chunks/studio-kyk2v92a.js +0 -659
  579. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  580. package/dist/chunks/studio-mmv72a27.js +0 -4344
  581. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  582. package/dist/chunks/studio-pdfjntbk.js +0 -6939
  583. package/dist/chunks/studio-qq6jym04.js +0 -53
  584. package/dist/chunks/studio-r1r7afxx.js +0 -1066
  585. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  586. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  587. package/dist/chunks/studio-t7qb9db8.js +0 -665
  588. package/dist/chunks/studio-tmc5emrw.js +0 -8
  589. package/dist/chunks/studio-txwkm6p9.js +0 -12
  590. package/dist/chunks/studio-v38hmvx1.js +0 -390
  591. package/dist/chunks/studio-vadzgkrz.js +0 -318
  592. package/dist/chunks/studio-wx5609xd.js +0 -341
  593. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  594. package/dist/chunks/studio-zczps70q.js +0 -378
  595. package/dist/chunks/workspace-xpres40v.js +0 -89
  596. package/src/panels/activity-bar.ts +0 -251
  597. package/src/panels/panel-scheduler.ts +0 -219
  598. package/src/panels/style-inputs.ts +0 -237
  599. package/src/panels/target-line.ts +0 -274
  600. package/src/panels/welcome-screen.ts +0 -346
  601. package/src/ui/button-group.ts +0 -102
  602. package/src/ui/choice-field.ts +0 -72
  603. package/src/ui/field-row.ts +0 -249
  604. package/src/ui/formula-palette.ts +0 -213
  605. package/src/ui/icons.ts +0 -52
  606. package/src/ui/jx-theme.ts +0 -130
  607. package/src/ui/spectrum.ts +0 -340
  608. package/src/ui/value-selector.ts +0 -174
  609. package/src/ui/widgets.ts +0 -172
  610. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  611. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  612. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  613. /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
@@ -28,23 +28,46 @@
28
28
  *
29
29
  * **The card has no host of its own.** `#frontmatter-panel` — the grid row, the `hidden` div, the
30
30
  * `frontmatterPanelEl` ref and the 40vh cap that came with them — is deleted. The STAGE renders the
31
- * host now ({@link attachDocumentHeaderHost}, called from a `ref` in `canvas/canvas-render.ts`), so
32
- * the card sits inside the artefact rather than in a band above it. This module keeps only the
33
- * focus-aware scheduler and the reactive effect, because a field commit must still repaint the card
34
- * WITHOUT repainting the canvas — a full canvas render remounts the document iframe.
31
+ * host now ({@link attachDocumentHeaderHost}, called from the stage document's `onNodeCreated` in
32
+ * `canvas/canvas-render.ts`), so the card sits inside the artefact rather than in a band above it.
33
+ *
34
+ * **The card is a Jx document now** (`src/surfaces/doc-header.json`), and this module is the flow
35
+ * behind it: it decides which document a pane's card is drawn for, which rows that document has,
36
+ * what each row commits into and where a media or reference row gets its choices, then hands the
37
+ * surface a projection whose every field is already a string. Three things follow.
38
+ *
39
+ * **The focus-aware scheduler is gone, and nothing replaced it.** `panels/panel-scheduler.ts`
40
+ * existed to withhold a repaint while a text input in the card had focus, because a lit re-render
41
+ * rebuilt every control and truncated whatever was being typed into one. A document binds each
42
+ * value on its own runtime effect and skips a write equal to what the control already holds
43
+ * (specs/studio-ui-guidelines.md §9.3), so a projection that changed one field repaints one field
44
+ * and a projection that changed nothing repaints nothing. There is no window left to withhold.
45
+ *
46
+ * **ONE row vocabulary.** Title, the Layout picker and every schema-driven frontmatter field are
47
+ * the same row, told apart by which control they draw; what makes them different is what they
48
+ * WRITE, and that is this module's business rather than the document's. {@link RowCommit} is that
49
+ * table, rebuilt with each projection and reached by the row's `key` — which is why a row's key and
50
+ * its `data-prop` are two different strings: a collection whose schema declares a `layout` property
51
+ * would otherwise collide with the Layout picker.
52
+ *
53
+ * **Two widgets are borrowed as BEHAVIOUR rather than as markup.** The media browser
54
+ * (`ui/media-picker.ts`) renders into the popover layer rather than into the field, and the entry
55
+ * ids behind a `#/content/<type>` reference are a read (`ui/form-controls.ts`), so the card draws
56
+ * its own control for each and calls the owner for the part that is not markup. Neither module's
57
+ * lit template is interpolated here — a document and a lit template cannot share a container, and
58
+ * §9.4 forbids the mix outright.
59
+ *
60
+ * @docs studio/editing/frontmatter
35
61
  */
36
62
 
37
- import { html, nothing, render as litRender } from "lit-html";
38
63
  import { projectState } from "../store";
39
64
  import { workspace } from "../workspace/workspace";
40
65
  import { tabOfPane } from "../canvas/canvas-surface";
41
66
  import { paneRegion } from "../ui/regions";
42
67
  import { effect, effectScope } from "../reactivity";
43
- import { createPanelScheduler } from "./panel-scheduler";
44
- import { collectFmFields, renderFmField } from "./frontmatter-fields";
45
- import { renderFieldRow } from "../ui/field-row";
46
- import { spTextField } from "../ui/field-input";
47
- import { transact } from "../tabs/transact";
68
+ import { collectFmFields, projectFmField } from "./frontmatter-fields";
69
+ import { LIVE_PREVIEW } from "../ui/timing";
70
+ import { mutateUpdateFrontmatter, transact, transactDoc } from "../tabs/transact";
48
71
  import { pageRoute } from "./tab-strip";
49
72
  import {
50
73
  RESERVED_FM_KEYS,
@@ -54,25 +77,58 @@ import {
54
77
  entryValue,
55
78
  isPageDocument,
56
79
  isManagedEntry,
57
- renderLayoutPickerRow,
80
+ layoutPickerEntries,
58
81
  } from "./head-panel";
59
82
  import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts";
60
83
  import { activeRegistry } from "../commands/active-registry";
84
+ import { invalidateLayoutCache } from "../site-context";
85
+ import { mountDocHeaderSurface } from "../surfaces/doc-header";
61
86
 
62
87
  import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
63
88
  import type { Tab } from "../tabs/tab";
64
- import type { PanelScheduler } from "./panel-scheduler";
65
- import type { TemplateResult } from "lit-html";
89
+ import type { FmSchemaEntry } from "./frontmatter-fields";
90
+ import type * as MediaPickerModule from "../ui/media-picker";
91
+ import type {
92
+ DocHeaderActions,
93
+ DocHeaderChoice,
94
+ DocHeaderRawEntry,
95
+ DocHeaderRow,
96
+ DocHeaderSurface,
97
+ DocHeaderView,
98
+ } from "../surfaces/doc-header";
99
+
100
+ /** What one row of the card writes. The document knows the control; this knows the document. */
101
+ interface RowCommit {
102
+ /** Remove the value this row shows. */
103
+ clear: () => void;
104
+ /** Commit what the control settled on — a string from every kind but the checkbox. */
105
+ commit: (raw: string | boolean) => void;
106
+ }
66
107
 
67
108
  /**
68
- * The card's host and scheduler, per PANE.
109
+ * One pane's card: the node the stage gave it, the mounted surface, its commit table and the
110
+ * actions the surface was handed.
69
111
  *
70
112
  * A single `_host` was the same singleton every other stage-content module had, and it produced a
71
113
  * subtler failure than most: the stage hands the host over from a lit `ref`, so with two stages
72
114
  * drawing a header the second `ref` to fire silently took the card away from the first, leaving one
73
115
  * pane's Document Header frozen on the frontmatter of the moment it lost the handle.
74
116
  */
75
- const _hosts = new Map<string, { el: HTMLElement; scheduler: PanelScheduler }>();
117
+ interface HeaderCard {
118
+ /** The pane this card is drawn for. Held rather than looked up: an action has to answer it. */
119
+ paneId: string;
120
+ el: HTMLElement;
121
+ /** `null` until the pane's document first has a header to draw. */
122
+ surface: DocHeaderSurface | null;
123
+ /** This pane's rows, by key. Rebuilt with each projection. */
124
+ commits: Map<string, RowCommit>;
125
+ /** Handed to the surface once, when it is mounted; every one of them reads `commits`. */
126
+ actions: DocHeaderActions;
127
+ /** Text edits waiting out {@link LIVE_PREVIEW}, one per row key. */
128
+ pending: Map<string, { commit: () => void; timer: ReturnType<typeof setTimeout> }>;
129
+ }
130
+
131
+ const _hosts = new Map<string, HeaderCard>();
76
132
  let _scope: { stop: () => void; run: <T>(fn: () => T) => T | undefined } | null = null;
77
133
 
78
134
  /** Per-document disclosure state, keyed by tab id — not stored on the document. */
@@ -82,9 +138,7 @@ const _rawOpen = new Set<string>();
82
138
  * The element the stage has made available for the card, or `null` while no stage hosts it.
83
139
  *
84
140
  * Called from a lit `ref` in `canvas/canvas-render.ts`, so the host's lifetime is the stage's: the
85
- * canvas creates it when it draws a document that has a header and drops it otherwise. The
86
- * scheduler is re-created per host because it binds `focusin`/`focusout` to that exact node, and
87
- * the focus guard is the whole reason this module still owns a render path of its own.
141
+ * canvas creates it when it draws a document that has a header and drops it otherwise.
88
142
  *
89
143
  * @param {HTMLElement | null} el
90
144
  */
@@ -93,24 +147,33 @@ export function attachDocumentHeaderHost(paneId: string, el: HTMLElement | null)
93
147
  if (held?.el === el) {
94
148
  return;
95
149
  }
96
- held?.scheduler.unbind();
97
- _hosts.delete(paneId);
150
+ if (held) {
151
+ takeDown(held);
152
+ _hosts.delete(paneId);
153
+ }
98
154
  if (!el) {
99
155
  return;
100
156
  }
101
- const scheduler = createPanelScheduler({ render: () => _doRender(paneId), root: el });
102
- _hosts.set(paneId, { el, scheduler });
103
- scheduler.bindFocus();
104
- scheduler.schedule();
157
+ const card: HeaderCard = {
158
+ actions: {} as DocHeaderActions,
159
+ commits: new Map(),
160
+ el,
161
+ paneId,
162
+ pending: new Map(),
163
+ surface: null,
164
+ };
165
+ card.actions = makeActions(card);
166
+ _hosts.set(paneId, card);
167
+ paint(paneId, card);
105
168
  }
106
169
 
107
- /**
108
- * The host the stage last handed over, or `null`. The stage reads it back to settle Lit's
109
- * order-independent detach report; nothing else needs to know where the card lives.
110
- */
111
- export function documentHeaderHost(paneId: string): HTMLElement | null {
112
- return _hosts.get(paneId)?.el ?? null;
113
- }
170
+ /* There is no `documentHeaderHost` getter any more, and its absence is the point.
171
+ It existed for ONE caller: the stage's lit `ref`, which was told about a removal WITHOUT being
172
+ told which node had gone, so it had to read the host back and ask whether it was still connected
173
+ before deciding the report was about the host it held. `surfaces/canvas-stage.json` states both
174
+ placements as a view, so a frame that draws no card simply announces none and `renderCanvasImpl`
175
+ releases the slot in one place — there is nothing left to settle after the fact, and a getter
176
+ whose only reader is gone is a fact about the module nobody is entitled to. */
114
177
 
115
178
  /**
116
179
  * Mount the Document Header card: subscribe to the tab / document / frontmatter reads it renders
@@ -151,13 +214,24 @@ export function mount() {
151
214
  export function unmount() {
152
215
  _scope?.stop();
153
216
  _scope = null;
154
- for (const { scheduler } of _hosts.values()) {
155
- scheduler.unbind();
217
+ for (const card of _hosts.values()) {
218
+ takeDown(card);
156
219
  }
157
220
  _hosts.clear();
158
221
  _rawOpen.clear();
159
222
  }
160
223
 
224
+ /** Take one pane's card down: cancel its waiting edits and dispose the document it mounted. */
225
+ function takeDown(card: HeaderCard): void {
226
+ for (const { timer } of card.pending.values()) {
227
+ clearTimeout(timer);
228
+ }
229
+ card.pending.clear();
230
+ card.surface?.dispose();
231
+ card.surface = null;
232
+ card.commits.clear();
233
+ }
234
+
161
235
  /** Each pane's tab, deduplicated — the set of documents a header could be drawn for. */
162
236
  function paneTabs(): Tab[] {
163
237
  const tabs: Tab[] = [];
@@ -171,12 +245,15 @@ function paneTabs(): Tab[] {
171
245
  }
172
246
 
173
247
  /**
174
- * Request a render. Coalesced and deferred while a text input in the card is focused, so re-renders
175
- * never clobber a field mid-edit.
248
+ * Repaint every card.
249
+ *
250
+ * Coalescing is the runtime's now: a projection assigns to a standing scope and each binding
251
+ * re-runs only where the value it reads has changed, so calling this twice in one tick costs one
252
+ * comparison per binding rather than two renders.
176
253
  */
177
254
  export function render() {
178
- for (const { scheduler } of _hosts.values()) {
179
- scheduler.schedule();
255
+ for (const [paneId, card] of _hosts) {
256
+ paint(paneId, card);
180
257
  }
181
258
  }
182
259
 
@@ -207,204 +284,351 @@ export function hasDocumentHeader(tab: Tab): boolean {
207
284
  return isPageDocument(tab);
208
285
  }
209
286
 
210
- function _doRender(paneId: string) {
211
- const host = _hosts.get(paneId)?.el;
212
- if (!host) {
213
- return;
214
- }
287
+ /**
288
+ * Draw (or take down) one pane's card.
289
+ *
290
+ * The surface is only ever mounted ONCE per host and assigned to afterwards. Rebuilding it on a
291
+ * repaint would take a field out from under a reader mid-edit, which is the failure the scheduler
292
+ * this conversion deletes was built to prevent; `connected()` answers the one case assignment
293
+ * cannot, a stage that redrew and took the card's root away.
294
+ */
295
+ function paint(paneId: string, card: HeaderCard): void {
215
296
  const tab = tabOfPane(paneId);
216
297
  if (!tab || !hasDocumentHeader(tab)) {
217
- // The stage decides whether the host exists; this only covers the window between a document
218
- // Losing its header and the canvas noticing. Lit leaves comment markers, so `:empty` cannot be
219
- // The signal and the host is hidden explicitly.
220
- litRender(nothing, host);
221
- host.hidden = true;
298
+ /* The stage decides whether the host exists; this only covers the window between a document
299
+ losing its header and the canvas noticing. */
300
+ takeDown(card);
301
+ card.el.replaceChildren();
302
+ card.el.hidden = true;
303
+ return;
304
+ }
305
+ const view = viewFor(tab, paneId, card);
306
+ card.el.hidden = false;
307
+ if (card.surface?.connected()) {
308
+ card.surface.update(view);
222
309
  return;
223
310
  }
224
- litRender(documentHeaderTemplate(tab, paneId), host);
225
- host.hidden = false;
311
+ card.surface?.dispose();
312
+ card.surface = mountDocHeaderSurface(card.el, view, card.actions);
226
313
  }
227
314
 
315
+ // ─── The projection ──────────────────────────────────────────────────────────
316
+
228
317
  /**
229
- * The card.
318
+ * Everything the card says about one document, and the commit table behind it.
230
319
  *
231
- * Exported as a template because the STAGE hosts it: `canvas-render.ts` emits the host div and this
232
- * module paints into it. The region id (`pane.primary/frontmatter`) rides on the `<section>` rather
233
- * than on a shell host, which is what made the move a layout change instead of a rename — the id
234
- * names the card's PLACE in the pane, and the pane still has one.
320
+ * `tab`, not `activeTab` — the card is drawn for the pane that mounted it. Both branches of the
321
+ * commit path resolved through FOCUS once, so with two panes the visible card's controls edited
322
+ * whichever document happened to be focused: click "Clear title" on the card in the left pane and
323
+ * the field disappeared from the right pane's document instead.
235
324
  *
236
325
  * @param {Tab} tab
237
- * @returns {TemplateResult}
326
+ * @returns {DocHeaderView}
238
327
  */
239
- export function documentHeaderTemplate(tab: Tab, paneId: string): TemplateResult {
328
+ function viewFor(tab: Tab, paneId: string, card: HeaderCard): DocHeaderView {
240
329
  const isContent = tab.doc.mode === "content";
241
330
  const fm = (tab.doc.content?.frontmatter ?? {}) as Record<string, unknown>;
242
331
  // ONE view of the document's head material, whichever realm it lives in: frontmatter keys for a
243
332
  // Markdown page, root properties for a JSON one.
244
333
  const headDoc = isContent ? buildHeadDoc(tab.doc.document, fm) : tab.doc.document;
245
- /*
246
- * `tab`, not `activeTab` — the card is drawn for the pane that mounted it, and every other line
247
- * in this template already reads that. Both branches resolved through FOCUS, so with two panes
248
- * the visible card's controls edited whichever document happened to be focused: click "Clear
249
- * title" on the card in the left pane and the field disappears from the right pane's document
250
- * instead. Invisible with one stage, which is why it survived the pane keying.
251
- *
252
- * **The first fix reached the JSON branch only, and the comment here said it was done.** A
253
- * markdown page takes the CONTENT branch, where `applyContentMutation` resolved the focus for
254
- * itself one call deeper — and `renderFmField` below did the same at each of its seven widgets.
255
- * Both take their tab now, so the whole card commits into one document: the one it is showing.
256
- */
257
334
  const applyMutation = isContent
258
335
  ? (fn: (doc: JxMutableNode) => void) => applyContentMutation(tab, render, fn)
259
336
  : (fn: (doc: JxMutableNode) => void) => {
260
337
  transact(tab, fn);
261
338
  };
262
339
 
340
+ const commits = new Map<string, RowCommit>();
341
+ const rows: DocHeaderRow[] = [titleRow(headDoc, applyMutation, commits)];
342
+ const layout = layoutRow(tab, headDoc, applyMutation, commits);
343
+ if (layout) {
344
+ rows.push(layout);
345
+ }
346
+
263
347
  // ONE call. The old pair of surfaces made two, with two different reserved-key policies.
264
348
  const { collection, fields, requiredFields } = collectFmFields(
265
349
  tab,
266
350
  projectState?.projectConfig,
267
351
  RESERVED_FM_KEYS,
268
352
  );
353
+ for (const f of fields) {
354
+ rows.push(fieldRow(tab, f.field, f.entry, f.value, requiredFields, commits));
355
+ }
356
+ card.commits = commits;
269
357
 
270
358
  const route = tab.documentPath ? pageRoute(tab.documentPath) : null;
271
- const title = typeof headDoc.title === "string" ? headDoc.title : "";
272
- const head = headDoc.$head ?? [];
359
+ const raw = rawEntries(headDoc.$head ?? []);
360
+ return {
361
+ collection: collection ? collection.name : "Document",
362
+ hasRoute: route !== null,
363
+ /* The stage's two placements, read off the host it handed over. In Edit the card is a block of
364
+ the document's own column; in Design it is pinned above a pan/zoom surface at 1:1, and only
365
+ that one wants the band's edge rather than the card's. `surfaces/canvas-stage.json` says the
366
+ same thing from the STAGE's side, which a document's own scoped style block cannot answer —
367
+ and it says it on `data-placement` rather than on a class, because the stage emits none. */
368
+ placement: card.el.dataset["placement"] === "pinned" ? "pinned" : "in-column",
369
+ rawEntries: raw,
370
+ rawOpen: _rawOpen.has(tab.id),
371
+ rawState: raw.length === 0 ? "empty" : "list",
372
+ region: paneRegion(paneId, "frontmatter"),
373
+ route: route ?? "",
374
+ rows,
375
+ };
376
+ }
377
+
378
+ /** A blank row, so every kind carries every field the document's bindings read. */
379
+ function blankRow(key: string, prop: string, label: string): DocHeaderRow {
380
+ return {
381
+ checked: false,
382
+ clearLabel: `Clear ${prop}`,
383
+ hasNote: false,
384
+ hasThumb: false,
385
+ isSet: false,
386
+ key,
387
+ kind: "text",
388
+ label,
389
+ note: "",
390
+ options: [],
391
+ placeholder: "",
392
+ prop,
393
+ thumb: "",
394
+ value: "",
395
+ };
396
+ }
273
397
 
274
- return html`
275
- <section
276
- class="doc-header"
277
- aria-label="Document header"
278
- data-jx-region=${paneRegion(paneId, "frontmatter")}
279
- >
280
- <header class="doc-header-bar">
281
- <span class="doc-header-title">${collection ? collection.name : "Document"}</span>
282
- ${
283
- route === null
284
- ? nothing
285
- : html`<code class="doc-header-route" title="This page's route">${route}</code>`
398
+ /** The one head value you type while writing, in both realms. */
399
+ function titleRow(
400
+ headDoc: JxMutableNode,
401
+ applyMutation: (fn: (doc: JxMutableNode) => void) => void,
402
+ commits: Map<string, RowCommit>,
403
+ ): DocHeaderRow {
404
+ const title = typeof headDoc.title === "string" ? headDoc.title : "";
405
+ commits.set("title", {
406
+ clear: () =>
407
+ applyMutation((d) => {
408
+ delete d.title;
409
+ }),
410
+ commit: (raw) =>
411
+ applyMutation((d) => {
412
+ const val = String(raw).trim();
413
+ if (val) {
414
+ d.title = val;
415
+ } else {
416
+ delete d.title;
286
417
  }
287
- </header>
288
- <div class="doc-header-body">
289
- ${renderFieldRow({
290
- hasValue: Boolean(title),
291
- label: "Title",
292
- onClear: () =>
293
- applyMutation((d) => {
294
- delete d.title;
295
- }),
296
- prop: "title",
297
- widget: spTextField(
298
- "doc-header:title",
299
- title,
300
- (v: string) =>
301
- applyMutation((d) => {
302
- const val = v.trim();
303
- if (val) {
304
- d.title = val;
305
- } else {
306
- delete d.title;
307
- }
308
- }),
309
- { placeholder: "Untitled" },
310
- ),
311
- })}
312
- ${isPageDocument(tab) ? renderLayoutPickerRow(headDoc, applyMutation) : nothing}
313
- ${fields.map((f) => renderFmField(tab, f.field, f.entry, f.value, requiredFields))}
314
- </div>
315
- ${seoButtonRow()} ${disclosure(tab.id, _rawOpen, "Raw head tags", rawHeadBody(head))}
316
- </section>
317
- `;
418
+ }),
419
+ });
420
+ return {
421
+ ...blankRow("title", "title", "Title"),
422
+ clearLabel: "Clear title",
423
+ isSet: Boolean(title),
424
+ placeholder: "Untitled",
425
+ value: title,
426
+ };
318
427
  }
319
428
 
320
429
  /**
321
- * The door to Search appearance (`panels/seo-modal.ts`).
430
+ * The layout picker, or `null` when this document takes none — a component has no layout, and a
431
+ * page whose layouts directory is still being listed has nothing to offer yet.
322
432
  *
323
- * A row rather than a disclosure, because what is behind it is a surface and not more of this card.
324
- * It runs the COMMAND rather than calling `openSeoModal` — the Page panel offers the same door and
325
- * neither of them should own it.
433
+ * The listing is `head-panel.ts`'s, asked for as data: the Page panel draws the same choices from
434
+ * the same cache, so creating a layout invalidates one thing rather than two.
326
435
  */
327
- function seoButtonRow(): TemplateResult {
328
- return html`
329
- <div class="doc-header-seo">
330
- <sp-action-button
331
- quiet
332
- size="s"
333
- class="doc-header-seo-btn"
334
- @click=${() => {
335
- void activeRegistry()?.run("document.openSeo");
336
- }}
337
- >
338
- <sp-icon-search slot="icon"></sp-icon-search>
339
- Search appearance…
340
- </sp-action-button>
341
- </div>
342
- `;
436
+ function layoutRow(
437
+ tab: Tab,
438
+ headDoc: JxMutableNode,
439
+ applyMutation: (fn: (doc: JxMutableNode) => void) => void,
440
+ commits: Map<string, RowCommit>,
441
+ ): DocHeaderRow | null {
442
+ if (!isPageDocument(tab)) {
443
+ return null;
444
+ }
445
+ const entries = layoutPickerEntries();
446
+ if (entries === null) {
447
+ return null;
448
+ }
449
+ const current = headDoc.$layout;
450
+ const defaultPath = projectState?.projectConfig?.defaults?.layout;
451
+ const defaultLabel = defaultPath ? layoutName(defaultPath) : "";
452
+ const options: DocHeaderChoice[] = [
453
+ { label: defaultLabel ? `Default (${defaultLabel})` : "Default", value: "__default__" },
454
+ { label: "None", value: "__none__" },
455
+ ...entries.map((l) => ({ label: l.name, value: l.path })),
456
+ ];
457
+ commits.set("__layout", {
458
+ clear: () =>
459
+ applyMutation((d) => {
460
+ delete d.$layout;
461
+ }),
462
+ commit: (raw) => {
463
+ const val = String(raw);
464
+ applyMutation((d) => {
465
+ if (val === "__default__") {
466
+ delete d.$layout;
467
+ } else if (val === "__none__") {
468
+ d.$layout = false;
469
+ } else {
470
+ d.$layout = val;
471
+ }
472
+ });
473
+ invalidateLayoutCache();
474
+ },
475
+ });
476
+ return {
477
+ ...blankRow("__layout", "layout", "Layout"),
478
+ isSet: current !== undefined,
479
+ kind: "select",
480
+ options,
481
+ value: current === false ? "__none__" : current || "__default__",
482
+ };
483
+ }
484
+
485
+ /** `./layouts/blog-post.json` → `Blog Post`. */
486
+ function layoutName(path: string): string {
487
+ return path
488
+ .replace(/^\.\/layouts\//, "")
489
+ .replace(/\.json$/, "")
490
+ .replaceAll(/[-_]+/g, " ")
491
+ .replaceAll(/\b\w/g, (c: string) => c.toUpperCase());
343
492
  }
344
493
 
345
494
  /**
346
- * A collapsible block whose open state is per tab and lives in this module.
495
+ * One schema-driven frontmatter field, as the row that draws it and the write that commits it.
347
496
  *
348
- * Deliberately NOT on `tab.session.ui`: P3's first workstream is hoisting view flags OUT of the tab
349
- * record, and adding two more would be moving in the wrong direction for a disclosure nobody needs
350
- * restored across a relaunch.
497
+ * **What the row LOOKS like is `panels/frontmatter-fields.ts`'s answer, not this module's.** The
498
+ * Navigator's Page panel draws the same field set from the same schemas, and the two surfaces
499
+ * deciding independently that a `$ref` is a picker, that `"uri-reference"` is a media format or
500
+ * that an array is a comma-separated line is exactly how they came to disagree about `title`. What
501
+ * stays here is what the card COMMITS INTO — this tab's frontmatter, through its transaction log.
502
+ *
503
+ * `tab` is a parameter because this used to commit to `activeTab.value` at each of seven widgets,
504
+ * which is right for the Navigator's Document panel and wrong for a card drawn once per pane: a
505
+ * collection field edited on the card in one pane wrote into whichever document had the keyboard.
351
506
  */
352
- function disclosure(
353
- tabId: string,
354
- open: Set<string>,
355
- label: string,
356
- body: TemplateResult,
357
- ): TemplateResult {
358
- const isOpen = open.has(tabId);
359
- return html`
360
- <details
361
- class="doc-header-disclosure"
362
- ?open=${isOpen}
363
- @toggle=${(e: Event) => {
364
- const el = e.target as HTMLDetailsElement;
365
- if (el.open) {
366
- open.add(tabId);
367
- } else {
368
- open.delete(tabId);
369
- }
370
- }}
371
- >
372
- <summary>${label}</summary>
373
- <div class="doc-header-body">${body}</div>
374
- </details>
375
- `;
507
+ function fieldRow(
508
+ tab: Tab,
509
+ field: string,
510
+ entry: FmSchemaEntry,
511
+ value: unknown,
512
+ requiredFields: Set<string>,
513
+ commits: Map<string, RowCommit>,
514
+ ): DocHeaderRow {
515
+ const key = `fm:${field}`;
516
+ const { parse, row } = projectFmField(field, entry, value, requiredFields, { rerender: render });
517
+ commits.set(key, {
518
+ clear: () => transactDoc(tab, (t) => mutateUpdateFrontmatter(t, field)),
519
+ commit: (raw) => transactDoc(tab, (t) => mutateUpdateFrontmatter(t, field, parse(raw))),
520
+ });
521
+ return { ...blankRow(key, field, row.label), ...row, clearLabel: `Clear ${field}` };
376
522
  }
377
523
 
378
- /* THE SEO BLOCK IS A MODAL — `panels/seo-modal.ts`.
379
- Two rendered previews, a resolved-field list, a warning list, the page and Open Graph meta rows
380
- and an icon picker, all disclosed inside a card whose job is the four or five fields you fill in
381
- while writing. A previewed SERP result is not a field; it is a picture you study, and studying it
382
- in a strip above the canvas made the card taller than the document it describes. The card keeps
383
- Title — the one head value you type while writing — and offers the door. */
384
-
385
524
  /**
386
525
  * The `$head` entries no structured control owns, listed read-only.
387
526
  *
388
527
  * Read-only on purpose: the card discloses what is there so the author is never surprised by a tag
389
528
  * they cannot see, and the Page panel remains the surface that adds and removes them.
390
529
  */
391
- function rawHeadBody(head: JxHeadEntry[]): TemplateResult {
392
- const custom = head.filter(
393
- (e: JxHeadEntry) => !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
394
- );
395
- if (custom.length === 0) {
396
- return html`<p class="doc-header-empty">No custom head tags on this document.</p>`;
397
- }
398
- return html`
399
- <ul class="doc-header-raw">
400
- ${custom.map(
401
- (entry) => html`
402
- <li>
403
- <code>${entryLabel(entry)}</code>
404
- <span title=${entryValue(entry)}>${entryValue(entry)}</span>
405
- </li>
406
- `,
407
- )}
408
- </ul>
409
- `;
530
+ function rawEntries(head: JxHeadEntry[]): DocHeaderRawEntry[] {
531
+ return head
532
+ .filter(
533
+ (e: JxHeadEntry) => !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
534
+ )
535
+ .map((entry, index) => ({
536
+ key: `raw:${index}`,
537
+ label: entryLabel(entry),
538
+ value: entryValue(entry),
539
+ }));
540
+ }
541
+
542
+ // ─── The actions ─────────────────────────────────────────────────────────────
543
+
544
+ /**
545
+ * What the reader can do on one pane's card.
546
+ *
547
+ * Made once per host and handed to the surface at mount, so every one of them reads the CURRENT
548
+ * commit table rather than closing over the projection that was standing when the card was drawn.
549
+ */
550
+ function makeActions(card: HeaderCard): DocHeaderActions {
551
+ const flush = (key: string): void => {
552
+ const waiting = card.pending.get(key);
553
+ if (waiting) {
554
+ clearTimeout(waiting.timer);
555
+ card.pending.delete(key);
556
+ }
557
+ };
558
+ const now = (key: string, raw: string | boolean): void => {
559
+ flush(key);
560
+ card.commits.get(key)?.commit(raw);
561
+ };
562
+ return {
563
+ browse: (key, anchor) => {
564
+ flush(key);
565
+ void mediaPicker().then((m) => {
566
+ if (anchor instanceof HTMLElement) {
567
+ m.showMediaPickerPopover(anchor, (val: string) => {
568
+ card.commits.get(key)?.commit(val);
569
+ });
570
+ }
571
+ });
572
+ },
573
+ clear: (key) => {
574
+ flush(key);
575
+ card.commits.get(key)?.clear();
576
+ },
577
+ commitText: (key, value) => now(key, value),
578
+ /* Debounced, so the canvas follows the typing without a document write per keystroke. The
579
+ control is NOT reset in the meantime: the projection this commit causes resolves to the text
580
+ already in the field, and a document binding skips a write equal to what the element holds. */
581
+ editText: (key, value) => {
582
+ flush(key);
583
+ const commit = () => {
584
+ card.pending.delete(key);
585
+ card.commits.get(key)?.commit(value);
586
+ };
587
+ card.pending.set(key, { commit, timer: setTimeout(commit, LIVE_PREVIEW) });
588
+ },
589
+ openSeo: () => {
590
+ // The COMMAND, not a local open() — the Page panel offers the same door and neither of them
591
+ // Owns it.
592
+ void activeRegistry()?.run("document.openSeo");
593
+ },
594
+ setBoolean: (key, checked) => now(key, checked),
595
+ setChoice: (key, value) => now(key, value),
596
+ setNumber: (key, value) => now(key, value),
597
+ setRawOpen: (open) => {
598
+ // Per DOCUMENT rather than per pane: the same file open in two stages discloses the same
599
+ // Tags, and a flag on the pane would make the two cards disagree about one document.
600
+ const tab = tabOfPane(card.paneId);
601
+ if (!tab) {
602
+ return;
603
+ }
604
+ if (open) {
605
+ _rawOpen.add(tab.id);
606
+ } else {
607
+ _rawOpen.delete(tab.id);
608
+ }
609
+ },
610
+ upload: (key) => {
611
+ flush(key);
612
+ void mediaPicker().then((m) => {
613
+ m.pickAndUpload((val: string) => {
614
+ card.commits.get(key)?.commit(val);
615
+ });
616
+ });
617
+ },
618
+ };
619
+ }
620
+
621
+ /**
622
+ * The media picker's two behaviours, imported on the first press.
623
+ *
624
+ * ONE cached promise, deliberately: two dynamic imports of a module in flight at once is the shape
625
+ * that loses its coverage record (see the note in the repository's agent guide), and the file also
626
+ * pulls in the upload pipeline and the media metadata cache — neither of which a card that draws no
627
+ * media field should ever load.
628
+ */
629
+ let _mediaPicker: Promise<typeof MediaPickerModule> | null = null;
630
+
631
+ function mediaPicker(): Promise<typeof MediaPickerModule> {
632
+ _mediaPicker ??= import("../ui/media-picker");
633
+ return _mediaPicker;
410
634
  }