@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
@@ -13,9 +13,10 @@ import { live } from "lit-html/directives/live.js";
13
13
  import { renderFieldRow } from "../ui/field-row";
14
14
  import { spNumberField, spTextField } from "../ui/field-input";
15
15
  import { renderMediaPicker } from "../ui/media-picker";
16
- import { activeTab } from "../workspace/workspace";
17
16
  import { mutateUpdateFrontmatter, transactDoc } from "../tabs/transact";
18
17
  import { findContentTypeSchema } from "../utils/studio-utils";
18
+ import { NULL_FORM_CONTEXT, getFormControl, referenceTarget } from "../ui/schema-form";
19
+ import type { JsonSchema } from "../ui/schema-form";
19
20
 
20
21
  import type { JsonValue } from "../types";
21
22
  import type { Tab } from "../tabs/tab";
@@ -26,6 +27,8 @@ export interface FmSchemaEntry {
26
27
  enum?: string[];
27
28
  format?: string;
28
29
  properties?: Record<string, unknown>;
30
+ /** `#/content/<type>` — a relationship to another collection (site-architecture.md §6.1). */
31
+ $ref?: string;
29
32
  }
30
33
 
31
34
  export interface FmField {
@@ -103,8 +106,19 @@ export function collectFmFields(
103
106
 
104
107
  /**
105
108
  * Render one frontmatter field as a typed widget row. Commits through `transactDoc` +
106
- * `mutateUpdateFrontmatter` on the active tab.
109
+ * `mutateUpdateFrontmatter` on `tab`.
107
110
  *
111
+ * **`tab` is a parameter because this renderer has two hosts and only one of them follows the
112
+ * focus.** It committed to `activeTab.value` at each of its seven widgets, which is right for the
113
+ * Navigator's Document panel (an app-level surface showing the focused document) and wrong for the
114
+ * Document Header card, which `panels/frontmatter-panel.ts` draws INSIDE a pane's stage, once per
115
+ * pane. So a collection field edited on the card in one pane wrote into whichever document had the
116
+ * keyboard: the card went on showing the old value, and a document nobody was looking at changed.
117
+ * The Document Header's `transact(tab, …)` branch was fixed for JSON documents in an earlier pass
118
+ * and this — every schema-driven frontmatter field, on every content document — is what that pass
119
+ * missed.
120
+ *
121
+ * @param {Tab | null} tab The document to commit into.
108
122
  * @param {string} field
109
123
  * @param {FmSchemaEntry} entry
110
124
  * @param {JsonValue} value
@@ -112,6 +126,7 @@ export function collectFmFields(
112
126
  * @returns {import("lit-html").TemplateResult}
113
127
  */
114
128
  export function renderFmField(
129
+ tab: Tab | null,
115
130
  field: string,
116
131
  entry: FmSchemaEntry,
117
132
  value: JsonValue,
@@ -121,7 +136,34 @@ export function renderFmField(
121
136
  const label = field.replaceAll(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
122
137
  const displayLabel = label + (isRequired ? " *" : "");
123
138
  const hasVal = value !== undefined && value !== "" && value !== false;
124
- const onClear = () => transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field));
139
+ const onClear = () => transactDoc(tab, (t) => mutateUpdateFrontmatter(t, field));
140
+
141
+ /* A relationship to another collection is the ONE registered `reference` control — the same
142
+ picker the entry editor and the settings forms draw, reached through the registry rather than
143
+ reimplemented here. Before this branch a `$ref` field fell through to the textfield at the
144
+ bottom of this function, so the author typed an entry id from memory with no way to see what
145
+ ids exist and no sign when the one they typed was wrong. */
146
+ const referenceControl =
147
+ referenceTarget(entry) === null ? undefined : getFormControl("reference");
148
+ if (referenceControl) {
149
+ return renderFieldRow({
150
+ hasValue: hasVal,
151
+ label: displayLabel,
152
+ onClear,
153
+ prop: field,
154
+ widget: referenceControl({
155
+ // The reference control resolves nothing through the context — its choices are files.
156
+ ctx: NULL_FORM_CONTEXT,
157
+ key: field,
158
+ onChange: (next) =>
159
+ transactDoc(tab, (t) =>
160
+ mutateUpdateFrontmatter(t, field, (next ?? undefined) as JsonValue),
161
+ ),
162
+ schema: entry as JsonSchema,
163
+ value,
164
+ }),
165
+ });
166
+ }
125
167
 
126
168
  if (entry.type === "boolean") {
127
169
  return renderFieldRow({
@@ -134,7 +176,7 @@ export function renderFmField(
134
176
  size="s"
135
177
  .checked=${live(Boolean(value))}
136
178
  @change=${(e: Event) =>
137
- transactDoc(activeTab.value, (t) =>
179
+ transactDoc(tab, (t) =>
138
180
  mutateUpdateFrontmatter(
139
181
  t,
140
182
  field,
@@ -163,7 +205,7 @@ export function renderFmField(
163
205
  .map((s: string) => s.trim())
164
206
  .filter(Boolean)
165
207
  : undefined;
166
- transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, arr));
208
+ transactDoc(tab, (t) => mutateUpdateFrontmatter(t, field, arr));
167
209
  },
168
210
  { placeholder: "comma, separated" },
169
211
  ),
@@ -181,7 +223,7 @@ export function renderFmField(
181
223
  size="s"
182
224
  .value=${live(value || "")}
183
225
  @change=${(e: Event) =>
184
- transactDoc(activeTab.value, (t) =>
226
+ transactDoc(tab, (t) =>
185
227
  mutateUpdateFrontmatter(t, field, (e.target as HTMLInputElement).value || undefined),
186
228
  )}
187
229
  >
@@ -198,7 +240,7 @@ export function renderFmField(
198
240
  onClear,
199
241
  prop: field,
200
242
  widget: renderMediaPicker(field, value as string, (v: string) =>
201
- transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
243
+ transactDoc(tab, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
202
244
  ),
203
245
  });
204
246
  }
@@ -210,7 +252,7 @@ export function renderFmField(
210
252
  onClear,
211
253
  prop: field,
212
254
  widget: spNumberField(value !== undefined ? Number(value) : undefined, (n) =>
213
- transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, n)),
255
+ transactDoc(tab, (t) => mutateUpdateFrontmatter(t, field, n)),
214
256
  ),
215
257
  });
216
258
  }
@@ -223,8 +265,7 @@ export function renderFmField(
223
265
  widget: spTextField(
224
266
  `fm:${field}`,
225
267
  (value as string) || "",
226
- (v: string) =>
227
- transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
268
+ (v: string) => transactDoc(tab, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
228
269
  { placeholder: entry.format === "date" ? "YYYY-MM-DD" : "" },
229
270
  ),
230
271
  });
@@ -1,61 +1,141 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Frontmatter-panel.ts — Obsidian-style "Properties" panel above the canvas.
3
+ * The Document Header card (§3.2 ⑧) — the artefact's own header, drawn IN the stage.
4
4
  *
5
- * Shown only when the active tab is a content-collection document (matched via
6
- * `findContentTypeSchema`) in content mode with the effective canvas mode "edit". The fields sit
7
- * inside a single collapsible accordion item so the panel can be dismissed to a slim header bar;
8
- * the expanded/collapsed state persists per tab (`tab.session.ui.frontmatterOpen`).
5
+ * It replaces the old "Properties" bar, which was a fourth full-width band that **appeared and
6
+ * vanished as a side effect of the canvas mode** with no control to summon it, and which showed
7
+ * nothing at all unless `findContentTypeSchema` matched the document to a content collection — so
8
+ * it never appeared on the default home page, the one document every new author opens first.
9
9
  *
10
- * Unlike the Document-tab section (head-panel), no keys are reserved here — `title` renders as a
11
- * regular property, matching Obsidian.
10
+ * Three gates are gone, and each was a lie about what the card is for:
11
+ *
12
+ * - The **collection gate** (`if (!fieldSet?.collection)`) — a page's `title` is frontmatter whether
13
+ * or not a collection schema describes it;
14
+ * - The **mode gate** (`getCanvasMode() === "edit"`) — the header is part of the document, not a view
15
+ * of it, so switching to Design must not delete it;
16
+ * - The **document-mode gate** (`doc.mode === "content"`) — a JSON page carries `title` and `$head`
17
+ * directly on the root node, and it has a header for exactly the same reason.
18
+ *
19
+ * What it renders instead: **any** document with frontmatter or `$head`, in every named layout and
20
+ * in both authoring views of the stage — Title, Route, the layout picker, the
21
+ * schema-and-frontmatter field list, the door to Search appearance and a "Raw head tags"
22
+ * disclosure. `hasDocumentHeader` is the whole predicate, and it is a fact about the DOCUMENT.
23
+ *
24
+ * **One reserved-key policy, one `collectFmFields` call.** The two field sets this card merges used
25
+ * to disagree: this module passed an empty reserved set and `head-panel.ts` passed `{title}`, so
26
+ * `title` could render twice with two different commit paths. `RESERVED_FM_KEYS` wins and is
27
+ * imported, not restated — see the note on its declaration.
28
+ *
29
+ * **The card has no host of its own.** `#frontmatter-panel` — the grid row, the `hidden` div, the
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.
12
35
  */
13
36
 
14
37
  import { html, nothing, render as litRender } from "lit-html";
15
- import { frontmatterPanelEl, projectState } from "../store";
16
- import { activeTab } from "../workspace/workspace";
38
+ import { projectState } from "../store";
39
+ import { workspace } from "../workspace/workspace";
40
+ import { tabOfPane } from "../canvas/canvas-surface";
41
+ import { paneRegion } from "../ui/regions";
17
42
  import { effect, effectScope } from "../reactivity";
18
43
  import { createPanelScheduler } from "./panel-scheduler";
19
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";
48
+ import { pageRoute } from "./tab-strip";
49
+ import {
50
+ RESERVED_FM_KEYS,
51
+ applyContentMutation,
52
+ buildHeadDoc,
53
+ entryLabel,
54
+ entryValue,
55
+ isPageDocument,
56
+ isManagedEntry,
57
+ renderLayoutPickerRow,
58
+ } from "./head-panel";
59
+ import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts";
60
+ import { activeRegistry } from "../commands/active-registry";
20
61
 
62
+ import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
63
+ import type { Tab } from "../tabs/tab";
21
64
  import type { PanelScheduler } from "./panel-scheduler";
65
+ import type { TemplateResult } from "lit-html";
22
66
 
23
- interface FrontmatterPanelCtx {
24
- /** Effective canvas mode (composes the base mode with the preview toggle). */
25
- getCanvasMode: () => string;
26
- }
27
-
28
- const NO_RESERVED_KEYS = new Set<string>();
29
-
30
- let _ctx: FrontmatterPanelCtx | null = null;
31
- let _scheduler: PanelScheduler | null = null;
67
+ /**
68
+ * The card's host and scheduler, per PANE.
69
+ *
70
+ * A single `_host` was the same singleton every other stage-content module had, and it produced a
71
+ * subtler failure than most: the stage hands the host over from a lit `ref`, so with two stages
72
+ * drawing a header the second `ref` to fire silently took the card away from the first, leaving one
73
+ * pane's Document Header frozen on the frontmatter of the moment it lost the handle.
74
+ */
75
+ const _hosts = new Map<string, { el: HTMLElement; scheduler: PanelScheduler }>();
32
76
  let _scope: { stop: () => void; run: <T>(fn: () => T) => T | undefined } | null = null;
33
77
 
78
+ /** Per-document disclosure state, keyed by tab id — not stored on the document. */
79
+ const _rawOpen = new Set<string>();
80
+
34
81
  /**
35
- * Mount the frontmatter panel: bind the focus-aware scheduler to the shell host and re-render
36
- * reactively on tab/mode/frontmatter changes.
82
+ * The element the stage has made available for the card, or `null` while no stage hosts it.
37
83
  *
38
- * @param {FrontmatterPanelCtx} ctx
84
+ * 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.
88
+ *
89
+ * @param {HTMLElement | null} el
39
90
  */
40
- export function mount(ctx: FrontmatterPanelCtx) {
41
- if (!frontmatterPanelEl) {
91
+ export function attachDocumentHeaderHost(paneId: string, el: HTMLElement | null): void {
92
+ const held = _hosts.get(paneId);
93
+ if (held?.el === el) {
94
+ return;
95
+ }
96
+ held?.scheduler.unbind();
97
+ _hosts.delete(paneId);
98
+ if (!el) {
42
99
  return;
43
100
  }
44
- _ctx = ctx;
45
- _scheduler = createPanelScheduler({ render: _doRender, root: frontmatterPanelEl });
46
- _scheduler.bindFocus();
101
+ const scheduler = createPanelScheduler({ render: () => _doRender(paneId), root: el });
102
+ _hosts.set(paneId, { el, scheduler });
103
+ scheduler.bindFocus();
104
+ scheduler.schedule();
105
+ }
106
+
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
+ }
114
+
115
+ /**
116
+ * Mount the Document Header card: subscribe to the tab / document / frontmatter reads it renders
117
+ * from, so a change repaints the card without repainting the canvas around it.
118
+ *
119
+ * The card takes no context. It used to take `getCanvasMode()` purely to decide whether to exist,
120
+ * and that predicate is the thing this change deletes.
121
+ */
122
+ export function mount() {
47
123
  _scope = effectScope();
48
124
  _scope.run(() => {
49
125
  effect(() => {
50
- const tab = activeTab.value;
51
- if (tab) {
52
- // Track everything the panel reads: mode/ui flags plus the frontmatter object itself AND
53
- // Its entries — field commits mutate keys in place while the source-mode round-trip swaps
54
- // The whole object; both must re-fire this effect.
126
+ // Every pane's tab, not the focused one's: two stages can each be drawing a header, and a
127
+ // Card that only tracked `activeTab` stopped repainting the moment focus moved away from it.
128
+ for (const tab of paneTabs()) {
129
+ // Track everything the card reads: the document root (title / `$head` / `$layout` live
130
+ // There for a JSON page) plus the frontmatter object AND its entries — field commits mutate
131
+ // Keys in place while the source-mode round-trip swaps the whole object, and both must
132
+ // Re-fire this effect.
55
133
  void tab.doc.mode;
56
- void tab.session.ui.canvasMode;
57
- void tab.session.ui.preview;
58
- void tab.session.ui.frontmatterOpen;
134
+ void tab.documentPath;
135
+ void tab.doc.document;
136
+ void tab.doc.document?.title;
137
+ void tab.doc.document?.$head;
138
+ void tab.doc.document?.$layout;
59
139
  const fm = tab.doc.content?.frontmatter;
60
140
  if (fm) {
61
141
  for (const key of Object.keys(fm)) {
@@ -71,62 +151,260 @@ export function mount(ctx: FrontmatterPanelCtx) {
71
151
  export function unmount() {
72
152
  _scope?.stop();
73
153
  _scope = null;
74
- _ctx = null;
75
- _scheduler?.unbind();
76
- _scheduler = null;
154
+ for (const { scheduler } of _hosts.values()) {
155
+ scheduler.unbind();
156
+ }
157
+ _hosts.clear();
158
+ _rawOpen.clear();
159
+ }
160
+
161
+ /** Each pane's tab, deduplicated — the set of documents a header could be drawn for. */
162
+ function paneTabs(): Tab[] {
163
+ const tabs: Tab[] = [];
164
+ for (const pane of workspace.panes) {
165
+ const tab = tabOfPane(pane.id);
166
+ if (tab && !tabs.includes(tab)) {
167
+ tabs.push(tab);
168
+ }
169
+ }
170
+ return tabs;
77
171
  }
78
172
 
79
173
  /**
80
- * Request a render. Coalesced and deferred while a text input in the panel is focused, so
81
- * re-renders never clobber a field mid-edit.
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.
82
176
  */
83
177
  export function render() {
84
- _scheduler?.schedule();
178
+ for (const { scheduler } of _hosts.values()) {
179
+ scheduler.schedule();
180
+ }
85
181
  }
86
182
 
87
- function _doRender() {
88
- if (!_ctx || !frontmatterPanelEl) {
89
- return;
183
+ /**
184
+ * Whether a document has a header to show.
185
+ *
186
+ * The one remaining gate, and it is a fact about the document rather than about the view: a
187
+ * component definition with no frontmatter, no title and no `$head` has no header, and printing an
188
+ * empty card over its canvas would be chrome pretending to be content.
189
+ *
190
+ * @param {Tab} tab
191
+ * @returns {boolean}
192
+ */
193
+ export function hasDocumentHeader(tab: Tab): boolean {
194
+ const fm = tab.doc.content?.frontmatter ?? {};
195
+ const doc = tab.doc.document;
196
+ if (Object.keys(fm).length > 0) {
197
+ return true;
90
198
  }
199
+ if (typeof doc?.title === "string" || (doc?.$head?.length ?? 0) > 0) {
200
+ return true;
201
+ }
202
+ // A page always has one, even an empty one: Title and Route are the two facts it must state.
203
+ // THIS tab's page-ness. `isPageDocument()` was zero-argument and answered about the focused
204
+ // Pane's document, so a predicate whose every other line reads `tab` finished by asking about a
205
+ // Different one: a page in the side pane lost its header whenever a component was focused, and a
206
+ // Bare component in the side pane grew one whenever a page was.
207
+ return isPageDocument(tab);
208
+ }
91
209
 
92
- const tab = activeTab.value;
93
- const eligible = Boolean(
94
- tab && tab.doc.mode === "content" && _ctx.getCanvasMode() === "edit" && tab.documentPath,
95
- );
96
- const fieldSet = eligible
97
- ? collectFmFields(tab!, projectState?.projectConfig, NO_RESERVED_KEYS)
98
- : null;
99
-
100
- if (!fieldSet?.collection) {
101
- // Lit leaves comment markers, so the host is hidden explicitly rather than relying on :empty.
102
- litRender(nothing, frontmatterPanelEl);
103
- frontmatterPanelEl.hidden = true;
210
+ function _doRender(paneId: string) {
211
+ const host = _hosts.get(paneId)?.el;
212
+ if (!host) {
213
+ return;
214
+ }
215
+ const tab = tabOfPane(paneId);
216
+ 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;
104
222
  return;
105
223
  }
224
+ litRender(documentHeaderTemplate(tab, paneId), host);
225
+ host.hidden = false;
226
+ }
106
227
 
107
- const { collection, fields, requiredFields } = fieldSet;
108
- const open = tab!.session.ui.frontmatterOpen !== false;
109
-
110
- litRender(
111
- html`
112
- <sp-accordion size="s">
113
- <sp-accordion-item
114
- label="Properties · ${collection.name}"
115
- ?open=${open}
116
- @sp-accordion-item-toggle=${(e: Event) => {
117
- const t = activeTab.value;
118
- if (t) {
119
- t.session.ui.frontmatterOpen = (e.target as HTMLElement & { open: boolean }).open;
120
- }
121
- }}
122
- >
123
- <div class="fm-panel-body">
124
- ${fields.map((f) => renderFmField(f.field, f.entry, f.value, requiredFields))}
125
- </div>
126
- </sp-accordion-item>
127
- </sp-accordion>
128
- `,
129
- frontmatterPanelEl,
228
+ /**
229
+ * The card.
230
+ *
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.
235
+ *
236
+ * @param {Tab} tab
237
+ * @returns {TemplateResult}
238
+ */
239
+ export function documentHeaderTemplate(tab: Tab, paneId: string): TemplateResult {
240
+ const isContent = tab.doc.mode === "content";
241
+ const fm = (tab.doc.content?.frontmatter ?? {}) as Record<string, unknown>;
242
+ // ONE view of the document's head material, whichever realm it lives in: frontmatter keys for a
243
+ // Markdown page, root properties for a JSON one.
244
+ 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
+ const applyMutation = isContent
258
+ ? (fn: (doc: JxMutableNode) => void) => applyContentMutation(tab, render, fn)
259
+ : (fn: (doc: JxMutableNode) => void) => {
260
+ transact(tab, fn);
261
+ };
262
+
263
+ // ONE call. The old pair of surfaces made two, with two different reserved-key policies.
264
+ const { collection, fields, requiredFields } = collectFmFields(
265
+ tab,
266
+ projectState?.projectConfig,
267
+ RESERVED_FM_KEYS,
130
268
  );
131
- frontmatterPanelEl.hidden = false;
269
+
270
+ const route = tab.documentPath ? pageRoute(tab.documentPath) : null;
271
+ const title = typeof headDoc.title === "string" ? headDoc.title : "";
272
+ const head = headDoc.$head ?? [];
273
+
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>`
286
+ }
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
+ `;
318
+ }
319
+
320
+ /**
321
+ * The door to Search appearance (`panels/seo-modal.ts`).
322
+ *
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.
326
+ */
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
+ `;
343
+ }
344
+
345
+ /**
346
+ * A collapsible block whose open state is per tab and lives in this module.
347
+ *
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.
351
+ */
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
+ `;
376
+ }
377
+
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
+ /**
386
+ * The `$head` entries no structured control owns, listed read-only.
387
+ *
388
+ * Read-only on purpose: the card discloses what is there so the author is never surprised by a tag
389
+ * they cannot see, and the Page panel remains the surface that adds and removes them.
390
+ */
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
+ `;
132
410
  }