@jxsuite/studio 2.0.0 → 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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
  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 +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  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 -120
  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 +75 -2
  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 +113 -0
  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-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  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-2c188n41.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-hhj92gcr.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -0,0 +1,395 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Seo-modal.ts — Search appearance, as a surface of its own.
4
+ *
5
+ * It was a collapsible block inside the Document Header card: two rendered previews, a resolved
6
+ * field list, a warning list, the page and Open Graph meta rows, and an icon picker — all disclosed
7
+ * under one summary row, inside a card whose job is the four or five fields you fill in while
8
+ * writing. A previewed SERP result is not a field; it is a picture you study, and studying it in a
9
+ * strip above the canvas meant the card grew taller than the thing it describes.
10
+ *
11
+ * So it opens as a modal, from two places — the Document Header card and the Navigator's Page
12
+ * panel. Two doors because the two are different moments: one while writing the page, one while
13
+ * working on its head material. Both run `document.openSeo`, so there is a third door in the
14
+ * palette and no surface owns the capability (§2 principle 1).
15
+ *
16
+ * **The card keeps the fields; the modal keeps the picture.** Title still lives on the card,
17
+ * because it is the one head value you type while writing. Everything the modal holds is either a
18
+ * rendering of what the build will emit or a field you set once and leave.
19
+ *
20
+ * The mutation path is the card's, unchanged: a markdown page commits through
21
+ * `applyContentMutation` and a JSON one through `transact`, both taking the tab so the modal edits
22
+ * the document it was opened over rather than whichever pane has focus.
23
+ */
24
+
25
+ import { html, nothing } from "lit-html";
26
+ import { activeRegistry } from "../commands/active-registry";
27
+ import { loopbackAssetSrc } from "../canvas/canvas-origin";
28
+ import { previewAssetSrc } from "../canvas/content-assets";
29
+ import { renderFieldRow } from "../ui/field-row";
30
+ import { renderMediaPicker } from "../ui/media-picker";
31
+ import { renderProvenanceChip } from "./provenance";
32
+ import { tabLabel } from "./tab-strip";
33
+ import { openModal } from "../ui/layers";
34
+ import { transact } from "../tabs/transact";
35
+ import { activeTab } from "../workspace/workspace";
36
+ import {
37
+ OG_FIELDS,
38
+ PAGE_FIELDS,
39
+ applyContentMutation,
40
+ buildHeadDoc,
41
+ findLinkEntry,
42
+ renderMetaFieldRow,
43
+ seoField,
44
+ reportSeoProblems,
45
+ seoPreviewFor,
46
+ upsertLink,
47
+ visibleLength,
48
+ } from "./head-panel";
49
+ import type { SeoField, SeoPreview } from "./head-panel";
50
+ import type { FieldProvenance } from "./provenance";
51
+ import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
52
+ import type { Tab } from "../tabs/tab";
53
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
54
+ import type { TemplateResult } from "lit-html";
55
+
56
+ // ─── The body ─────────────────────────────────────────────────────────────────
57
+
58
+ /*
59
+ * Two rendered previews, a resolved-field list and a warning list, over the MERGED head — and no
60
+ * score. A number out of a hundred aggregates unrelated facts into a verdict, and a verdict is
61
+ * what gets optimised; a count beside a limit and a named consequence say the same thing without
62
+ * ranking anything (plan §9.2, §14).
63
+ *
64
+ * The previews are pictures of what the build emits, so a value the page did not author is marked
65
+ * as inherited with the donor NAMED — the third cascade to use `panels/provenance.ts`'s vocabulary
66
+ * after the style cascade and component props, and deliberately not a fourth vocabulary. It is the
67
+ * whole reason the block can say "no description reaches this page" without saying it to a page
68
+ * that inherits one from the site.
69
+ */
70
+
71
+ /**
72
+ * A resolved field's provenance chip, in the shared vocabulary.
73
+ *
74
+ * The two chips that can go somewhere do: a value from the site's own `$head` opens Project
75
+ * Settings › Site head, and one from the site `name` opens Overview. The layout and build donors
76
+ * get a `<span>` rather than a `<button>`, because the card has no verb for "open that layout" and
77
+ * a control that looks pressable and does nothing is the defect §6.2 exists to remove.
78
+ *
79
+ * @param {SeoField} field
80
+ * @returns {FieldProvenance}
81
+ */
82
+ function seoProvenance(field: SeoField): FieldProvenance {
83
+ const open = (section: string) => () => {
84
+ void activeRegistry()?.run("settings.open", { section });
85
+ };
86
+ switch (field.source) {
87
+ case "page": {
88
+ return { state: "set", title: `${field.label} is set on this page` };
89
+ }
90
+ case "layout": {
91
+ const donor = field.donor ?? "the layout";
92
+ return {
93
+ donor,
94
+ state: "inherited",
95
+ title: `${field.label} comes from the ${donor} layout — this page does not set it`,
96
+ };
97
+ }
98
+ case "site": {
99
+ const fromName = field.donor === "Site name";
100
+ return {
101
+ donor: field.donor ?? "the site",
102
+ onClick: open(fromName ? "overview" : "head"),
103
+ state: "inherited",
104
+ title: `${field.label} comes from ${fromName ? "the project's name" : "the site-level $head"} — click to open it`,
105
+ };
106
+ }
107
+ case "build": {
108
+ return {
109
+ donor: "the build",
110
+ state: "inherited",
111
+ title: `Nothing declares ${field.label}, so the build supplies “${field.value}”`,
112
+ };
113
+ }
114
+ default: {
115
+ return { state: "default" };
116
+ }
117
+ }
118
+ }
119
+
120
+ /** A previewed line of text, or the placeholder that says nothing supplies it. */
121
+ function previewText(field: SeoField): TemplateResult {
122
+ const text = field.value.trim();
123
+ return text
124
+ ? html`<span>${text}</span>`
125
+ : html`<span class="seo-unset">No ${field.label.toLowerCase()}</span>`;
126
+ }
127
+
128
+ /** The mock search result: the breadcrumb the canonical produces, the title, the description. */
129
+ function serpCard(preview: SeoPreview): TemplateResult {
130
+ return html`
131
+ <figure class="seo-card seo-card--serp" aria-label="Search result preview">
132
+ <figcaption class="seo-card-label">Search result</figcaption>
133
+ <div class="seo-serp-url">${preview.url.crumb}</div>
134
+ <div class="seo-serp-title">${seoField(preview, "title").value}</div>
135
+ <div class="seo-serp-desc">${previewText(seoField(preview, "description"))}</div>
136
+ </figure>
137
+ `;
138
+ }
139
+
140
+ /**
141
+ * The mock social card.
142
+ *
143
+ * The image is resolved the same way every other image in the studio chrome is —
144
+ * `loopbackAssetSrc(previewAssetSrc(…))` — so a content-relative `./images/hero.jpg` previews at
145
+ * its asset-mount URL while the authored ref stays exactly as written.
146
+ */
147
+ function socialCard(preview: SeoPreview): TemplateResult {
148
+ const image = seoField(preview, "og:image").value.trim();
149
+ return html`
150
+ <figure class="seo-card seo-card--social" aria-label="Social card preview">
151
+ <figcaption class="seo-card-label">Social card</figcaption>
152
+ <div class="seo-social-media">
153
+ ${
154
+ image
155
+ ? html`<img src=${loopbackAssetSrc(previewAssetSrc(image))} alt="" />`
156
+ : html`<span class="seo-unset">No image</span>`
157
+ }
158
+ </div>
159
+ <div class="seo-social-text">
160
+ <span class="seo-social-domain"
161
+ >${preview.url.host || html`<span class="seo-unset">No site URL</span>`}</span
162
+ >
163
+ <span class="seo-social-title">${previewText(seoField(preview, "og:title"))}</span>
164
+ <span class="seo-social-desc">${previewText(seoField(preview, "og:description"))}</span>
165
+ </div>
166
+ </figure>
167
+ `;
168
+ }
169
+
170
+ /** One row per resolved field: what reaches the browser, how long it is, and where it came from. */
171
+ function seoFieldList(preview: SeoPreview): TemplateResult {
172
+ return html`
173
+ <ul class="seo-fields">
174
+ ${preview.fields.map((field) => {
175
+ const length = visibleLength(field.value);
176
+ const over = field.limit !== null && length > field.limit;
177
+ return html`
178
+ <li class="seo-field" data-seo-field=${field.key}>
179
+ <span class="seo-field-label">${field.label}</span>
180
+ <span class="seo-field-value" title=${field.value}>${previewText(field)}</span>
181
+ ${
182
+ field.limit === null
183
+ ? nothing
184
+ : html`<span
185
+ class=${over ? "seo-field-count seo-field-count--over" : "seo-field-count"}
186
+ >${length}/${field.limit}</span
187
+ >`
188
+ }
189
+ ${renderProvenanceChip(field.key, seoProvenance(field))}
190
+ </li>
191
+ `;
192
+ })}
193
+ </ul>
194
+ `;
195
+ }
196
+
197
+ /** The named warnings. A list, never a total — see the note at the top of this section. */
198
+ function seoWarningList(preview: SeoPreview): TemplateResult {
199
+ if (preview.warnings.length === 0) {
200
+ return html`<p class="doc-header-empty">
201
+ Nothing to flag — every previewed field resolves to a value.
202
+ </p>`;
203
+ }
204
+ return html`
205
+ <ul class="seo-warnings">
206
+ ${preview.warnings.map(
207
+ (warning) => html`
208
+ <li class="seo-warning" data-seo-warning=${warning.id}>
209
+ <code class="seo-warning-field">${warning.field}</code>
210
+ <span>${warning.message}</span>
211
+ </li>
212
+ `,
213
+ )}
214
+ </ul>
215
+ `;
216
+ }
217
+
218
+ /**
219
+ * The previews, the resolved fields, then the controls that change them.
220
+ *
221
+ * That order on purpose: what it looks like, what is wrong with it, and only then the form. The
222
+ * form was all this block used to be, and a form cannot tell you that the description you are about
223
+ * to write is already coming from the site.
224
+ */
225
+ function seoBody(
226
+ tab: Tab,
227
+ headDoc: JxMutableNode,
228
+ head: JxHeadEntry[],
229
+ applyMutation: (fn: (doc: JxMutableNode) => void) => void,
230
+ ): TemplateResult {
231
+ const iconHref = String(findLinkEntry(head, "icon")?.attributes?.href ?? "");
232
+ // The card's tab, so the SERP row shows this document's route and this document's layout layer.
233
+ const preview = seoPreviewFor(tab, headDoc);
234
+ return html`
235
+ <div class="seo-previews">${serpCard(preview)} ${socialCard(preview)}</div>
236
+ ${seoFieldList(preview)} ${seoWarningList(preview)}
237
+ <!-- GROUPED, because the two sets collide by name: Open Graph has its own Title, Description
238
+ and Image, and eight unlabelled rows in a column made "Description" mean two things. Each
239
+ group is headed by the preview card it feeds, so a row and the picture it changes are
240
+ nameable together. -->
241
+ <div class="seo-modal-group">
242
+ <h3 class="seo-modal-group-title">Search result</h3>
243
+ ${PAGE_FIELDS.map((field) => renderMetaFieldRow(field, head, applyMutation))}
244
+ ${renderFieldRow({
245
+ hasValue: Boolean(iconHref),
246
+ label: "Icon",
247
+ onClear: () => applyMutation((d) => upsertLink(d, "icon", "")),
248
+ prop: "icon",
249
+ widget: renderMediaPicker("icon", iconHref, (v: string) => {
250
+ applyMutation((d) => upsertLink(d, "icon", v || ""));
251
+ }),
252
+ })}
253
+ </div>
254
+ <div class="seo-modal-group">
255
+ <h3 class="seo-modal-group-title">Social card</h3>
256
+ <!-- No fallback is promised here, because the build emits none: the warning list above says
257
+ an unset og:title means "a shared link carries no headline of its own", and a note
258
+ claiming otherwise would contradict the app two inches higher. -->
259
+ <p class="seo-modal-group-note">Open Graph — what a shared link shows.</p>
260
+ ${OG_FIELDS.map((field) => renderMetaFieldRow(field, head, applyMutation))}
261
+ </div>
262
+ `;
263
+ }
264
+
265
+ // ─── The modal ───────────────────────────────────────────────────────────────
266
+
267
+ /** The open modal, or `null`. One at a time: it is about the focused document. */
268
+ let _handle: { update: (tpl: TemplateResult) => void; close: () => void } | null = null;
269
+
270
+ /** The tab it was opened over, so a re-render draws the same document the author opened. */
271
+ let _tab: Tab | null = null;
272
+
273
+ /**
274
+ * What the SEO body needs from a tab, resolved once.
275
+ *
276
+ * Both realms in one place: a markdown page keeps its head material in frontmatter and a JSON one
277
+ * in root properties, and `buildHeadDoc` is the card's own view of that difference.
278
+ */
279
+ function seoContextFor(tab: Tab): {
280
+ headDoc: JxMutableNode;
281
+ head: JxHeadEntry[];
282
+ applyMutation: (fn: (doc: JxMutableNode) => void) => void;
283
+ } {
284
+ const isContent = tab.doc.mode === "content";
285
+ const fm = (tab.doc.content?.frontmatter ?? {}) as Record<string, unknown>;
286
+ const headDoc = isContent ? buildHeadDoc(tab.doc.document, fm) : tab.doc.document;
287
+ return {
288
+ applyMutation: isContent
289
+ ? (fn: (doc: JxMutableNode) => void) => applyContentMutation(tab, renderSeoModal, fn)
290
+ : (fn: (doc: JxMutableNode) => void) => {
291
+ transact(tab, fn);
292
+ },
293
+ head: headDoc.$head ?? [],
294
+ headDoc,
295
+ };
296
+ }
297
+
298
+ /** Repaint the modal if it is open. Every field commits live, so the picture follows the edit. */
299
+ export function renderSeoModal(): void {
300
+ if (!_handle || !_tab) {
301
+ return;
302
+ }
303
+ const { applyMutation, head, headDoc } = seoContextFor(_tab);
304
+ const tab = _tab;
305
+ _handle.update(html`
306
+ <!-- The region goes on the BODY, not on the layer slot: the slot is a zero-height wrapper
307
+ around a fixed-position body, so a shot capturing it would capture nothing. The publish
308
+ panel stamps its own for the same reason. An overlay.instance:id name is a DERIVED shape,
309
+ so this costs the manifest no non-derived-region budget. -->
310
+ <div class="seo-modal" data-jx-region="overlay.dialog:seo">
311
+ <div class="settings-modal-header">
312
+ <h2 class="settings-modal-title">Search appearance</h2>
313
+ <!-- WHICH document, in the header. A modal has no tab strip behind it to say so, and every
314
+ field below resolves through this document's layout and site head. -->
315
+ <span class="seo-modal-doc">${tab.documentPath ?? tabLabel(tab)}</span>
316
+ <sp-action-button quiet size="s" title="Close" @click=${closeSeoModal}>
317
+ <sp-icon-close slot="icon"></sp-icon-close>
318
+ </sp-action-button>
319
+ </div>
320
+ <div class="seo-modal-body">${seoBody(tab, headDoc, head, applyMutation)}</div>
321
+ </div>
322
+ `);
323
+ }
324
+
325
+ /** Open it over `tab`. Idempotent — opening it again re-points it at the current document. */
326
+ export function openSeoModal(tab: Tab): void {
327
+ _tab = tab;
328
+ if (!_handle) {
329
+ _handle = openModal(html``, { label: "Search appearance", onDismiss: closeSeoModal });
330
+ }
331
+ renderSeoModal();
332
+ /*
333
+ * File the same warnings as Problems on the way in.
334
+ *
335
+ * A window someone has to open is not where a fact should live alone: a page shipped with no
336
+ * description is worth knowing whether or not you thought to look, and Problems is where this app
337
+ * keeps the records that outlive the frame you were not watching. Same list, keyed by warning id,
338
+ * so the two surfaces are naming one thing rather than two.
339
+ */
340
+ reportSeoProblems(seoPreviewFor(tab, tab.doc.document), tab.documentPath ?? undefined);
341
+ }
342
+
343
+ /** Close it, and forget the document it was about. */
344
+ export function closeSeoModal(): void {
345
+ _handle?.close();
346
+ _handle = null;
347
+ _tab = null;
348
+ }
349
+
350
+ /**
351
+ * `document.openSeo` — the one capability behind both buttons.
352
+ *
353
+ * The Document Header card and the Page panel each render a control that runs this, so neither owns
354
+ * it and the palette has it by name. A record rather than two click handlers, for the reason the
355
+ * rendering-context axes became records: a surface that IS the capability is a capability the
356
+ * palette, the assistant and `__jxAutomation` cannot reach.
357
+ *
358
+ * @returns {AnyCommand[]}
359
+ */
360
+ export function seoCommands(): AnyCommand[] {
361
+ return [
362
+ {
363
+ category: "Document",
364
+ id: "document.openSeo",
365
+ level: "document",
366
+ menus: ["palette"],
367
+ group: "2_document",
368
+ requires: "an open document",
369
+ when: (ctx) => ctx.document.open,
370
+ aiTool: {
371
+ description:
372
+ "Open Search appearance for the current document — the SERP and social previews, the " +
373
+ "resolved head fields with their donors, and the page/Open Graph meta fields.",
374
+ name: "open_seo",
375
+ },
376
+ run: () => {
377
+ const tab = activeTab.value;
378
+ if (!tab) {
379
+ throw new RangeError(`command "document.openSeo" needs an open document`);
380
+ }
381
+ openSeoModal(tab);
382
+ },
383
+ title: "Search Appearance",
384
+ },
385
+ ];
386
+ }
387
+
388
+ /**
389
+ * Register it.
390
+ *
391
+ * @param {CommandRegistry} registry
392
+ */
393
+ export function registerSeoCommands(registry: CommandRegistry): void {
394
+ registry.registerAll(seoCommands());
395
+ }
@@ -0,0 +1,203 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The Project Settings editor, drawn inside the pane — the stage, not an overlay.
4
+ *
5
+ * Two columns: the section list (inner nav) and the section body. It is deliberately the same
6
+ * two-column shape the modal had, because the modal's IA was never the problem — its MODALITY was.
7
+ * Moving the identical layout into `#canvas-wrap` is what makes the seven screenshot crops change
8
+ * frame (`overlay.dialog:settings` → `pane.primary`) without one manifest STEP changing, which is
9
+ * §13.7's proof that the command boundary was drawn in the right place.
10
+ *
11
+ * Mounting follows `grid/grid-panel.ts`: `canvas/canvas-render.ts` calls {@link renderSettingsPane}
12
+ * when the tab enters this mode, and this module owns its reactivity from there — a subscription to
13
+ * the section registry and to the chosen section, so an extension's section appearing a tick later
14
+ * redraws the nav without the canvas being re-rendered.
15
+ *
16
+ * **It imports the registry, never the sections.** `canvas-render.ts` reaches this module, so
17
+ * anything it imports joins the canvas's import graph; the section RENDERERS are registered by
18
+ * `settings/settings-document.ts`, which the app loads when it registers its commands.
19
+ *
20
+ * Nothing here is stamped with a region of its own. `#canvas-wrap` already carries `pane.primary`
21
+ * (`ui/regions.ts`'s `SHELL_REGION_HOSTS`), the entry rows and the entry editor are stamped by
22
+ * `settings/contributed-section.ts` as `pane.primary/entry:<key>` and `pane.primary/editor`, and a
23
+ * third id for "the settings body" would be a hand-stamped region with nothing to say.
24
+ *
25
+ * @docs studio/projects/settings
26
+ */
27
+
28
+ import { html, render as litRender } from "lit-html";
29
+ import { classMap } from "lit-html/directives/class-map.js";
30
+ import { ref } from "lit-html/directives/ref.js";
31
+ import {
32
+ onSettingsDocumentChanged,
33
+ settingsSection,
34
+ settingsDocumentSection,
35
+ setSettingsSection,
36
+ sortedSettingsSections,
37
+ } from "../settings/section-registry";
38
+ import type { CanvasSurface } from "../canvas/canvas-surface";
39
+
40
+ /**
41
+ * One mounted editor per PANE.
42
+ *
43
+ * `config` is a kind the side pane may host, so the previous single `_host` had both panes' stages
44
+ * competing for it: `settingsPaneMounted` answered false for whichever host lost, and that pane's
45
+ * fast path rebuilt the whole section body on every render — throwing away an open inline form
46
+ * mid-keystroke, which is the exact failure the idempotence was written to prevent.
47
+ */
48
+ interface ActiveSettingsPane {
49
+ host: HTMLElement;
50
+ /** The section body container, handed to whichever section renderer is current. */
51
+ body: HTMLElement | null;
52
+ /** The section the body currently holds, so an idle re-render does not rebuild it. */
53
+ rendered: string | null;
54
+ /** Unsubscribe from the document's change notifications. */
55
+ off: (() => void) | null;
56
+ /**
57
+ * The body's `ref` callback, allocated ONCE per record.
58
+ *
59
+ * Lit re-invokes a `ref` whose callback IDENTITY changed — the old one with `undefined`, the new
60
+ * one with the element. Built inline in the template it was a fresh closure every render, so
61
+ * every render ran `panel.body = null; panel.rendered = null` and then re-bound: `rendered` was
62
+ * null again by the time the guard in {@link draw} read it, on every single pass. The guard could
63
+ * not fire and `force` decided nothing.
64
+ *
65
+ * What that did NOT cost, despite appearances: the mid-keystroke rebuild. `canvas-render.ts`
66
+ * returns at its own fast path (`canvasMode === "settings" && settingsPaneMounted(surface) &&
67
+ * !modeChanged`) before it ever calls back in, and on a real mode change it detaches this record
68
+ * first — so `draw(paneId, false)` over a SURVIVING record has no caller in the app. That fast
69
+ * path is what keeps an open inline form alive today; this guard is the second line, and it was
70
+ * vacuous. Do not read the two as redundant and delete either.
71
+ *
72
+ * Per RECORD, not per pane id: a same-host remount after `detachSettingsPane` needs the identity
73
+ * to change so lit re-binds the element onto the new record.
74
+ */
75
+ bindBody: (el: Element | undefined) => void;
76
+ }
77
+
78
+ const _active = new Map<string, ActiveSettingsPane>();
79
+
80
+ /**
81
+ * Build one record's body binder.
82
+ *
83
+ * The guard is an IDENTITY test, not a presence test, and that distinction is the whole of it. A
84
+ * binder outlives its record — lit calls the previous one with `undefined` whenever the callback
85
+ * changes, and again when the part disconnects — so by the time it runs, `_active` may hold no
86
+ * record at all, or a DIFFERENT one for the same pane. Writing into whatever `_active` happens to
87
+ * return would be worse than doing nothing: it would null the live record's `body`, and since a
88
+ * stable callback over an unchanged element is never re-invoked, nothing would ever re-bind it —
89
+ * `draw` would return at `!panel.body` forever, `force` included. A superseded binder therefore
90
+ * touches nothing; its own record is detached and no one reads it.
91
+ */
92
+ function bodyBinder(paneId: string): (el: Element | undefined) => void {
93
+ const self = (el: Element | undefined) => {
94
+ const panel = _active.get(paneId);
95
+ if (panel?.bindBody !== self) {
96
+ return;
97
+ }
98
+ const next = (el as HTMLElement | undefined) ?? null;
99
+ if (next !== panel.body) {
100
+ panel.body = next;
101
+ panel.rendered = null;
102
+ }
103
+ };
104
+ return self;
105
+ }
106
+
107
+ /**
108
+ * Mount (or refresh) the settings editor inside the pane.
109
+ *
110
+ * Idempotent for the same host: canvas-render calls this on every render for this mode, and
111
+ * rebuilding the section body on each one would throw away an open inline form mid-keystroke.
112
+ *
113
+ * @param {CanvasSurface} surface - The pane whose stage hosts the editor
114
+ */
115
+ export function renderSettingsPane(surface: CanvasSurface): void {
116
+ const { paneId, wrap: host } = surface;
117
+ let panel = _active.get(paneId);
118
+ if (panel?.host !== host) {
119
+ detachSettingsPane(paneId);
120
+ panel = {
121
+ bindBody: bodyBinder(paneId),
122
+ body: null,
123
+ host,
124
+ off: onSettingsDocumentChanged(() => draw(paneId, true)),
125
+ rendered: null,
126
+ };
127
+ _active.set(paneId, panel);
128
+ }
129
+ draw(paneId, false);
130
+ }
131
+
132
+ /** Tear one pane's editor down — the mode-change and project-close path. */
133
+ export function detachSettingsPane(paneId: string): void {
134
+ const panel = _active.get(paneId);
135
+ if (!panel) {
136
+ return;
137
+ }
138
+ panel.off?.();
139
+ _active.delete(paneId);
140
+ }
141
+
142
+ /**
143
+ * Whether the editor is mounted on this pane's stage — canvas-render's "did I already build this".
144
+ *
145
+ * @param {CanvasSurface} surface
146
+ * @returns {boolean}
147
+ */
148
+ export function settingsPaneMounted(surface: CanvasSurface): boolean {
149
+ return _active.get(surface.paneId)?.host === surface.wrap;
150
+ }
151
+
152
+ /**
153
+ * Draw the nav, then the body.
154
+ *
155
+ * @param {boolean} force - Re-run the section renderer even when the section has not changed. True
156
+ * for a real state change (a nav click, a section registering, an entry being selected by
157
+ * command); false for canvas-render's idempotent remount.
158
+ */
159
+ function draw(paneId: string, force: boolean): void {
160
+ const panel = _active.get(paneId);
161
+ if (!panel) {
162
+ return;
163
+ }
164
+ const active = settingsDocumentSection();
165
+ const sections = sortedSettingsSections();
166
+
167
+ const tpl = html`
168
+ <div class="settings-doc">
169
+ <nav class="settings-doc-nav" aria-label="Project Settings sections">
170
+ ${sections.map(
171
+ (section) => html`
172
+ <button
173
+ class=${classMap({
174
+ active: active === section.key,
175
+ "settings-nav-item": true,
176
+ })}
177
+ aria-current=${active === section.key ? "page" : "false"}
178
+ @click=${() => setSettingsSection(section.key)}
179
+ >
180
+ ${section.label}
181
+ </button>
182
+ `,
183
+ )}
184
+ </nav>
185
+ <div class="settings-doc-content" ${ref(panel.bindBody)}></div>
186
+ </div>
187
+ `;
188
+
189
+ litRender(tpl, panel.host);
190
+
191
+ if (!panel.body || (!force && panel.rendered === active)) {
192
+ return;
193
+ }
194
+ panel.rendered = active;
195
+ const section = settingsSection(active);
196
+ if (section) {
197
+ section.render(panel.body);
198
+ return;
199
+ }
200
+ /* Every section unregistered at once — a project closing while the editor is open. A blank
201
+ content area beside a nav reads as a broken pane, so it says which it is. */
202
+ litRender(html`<div class="settings-empty-state">No settings sections.</div>`, panel.body);
203
+ }