@jxsuite/studio 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +21080 -32585
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +7 -0
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +221 -245
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -53
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +30 -1
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +7 -1
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
  363. package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
  364. package/dist/chunks/files-j8a1t06k.js +0 -50
  365. package/dist/chunks/files-j8a1t06k.js.map +0 -9
  366. package/dist/chunks/git-panel-xtq5z81x.js +0 -28
  367. package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
  368. package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
  369. package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
  370. package/dist/chunks/settings-modal-17ttr96g.js +0 -29
  371. package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-0xgs7xrs.js +0 -429
  381. package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
  382. package/dist/chunks/studio-4d8hzcz5.js +0 -3427
  383. package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
  384. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  385. package/dist/chunks/studio-9smfzqhc.js +0 -19
  386. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  387. package/dist/chunks/studio-jm0z592w.js.map +0 -115
  388. package/dist/chunks/studio-m2t4mdg5.js +0 -1854
  389. package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
  390. package/dist/chunks/studio-n4p11dfy.js +0 -853
  391. package/dist/chunks/studio-n4p11dfy.js.map +0 -12
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-sjh1wt1a.js +0 -3744
  394. package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
  395. package/dist/chunks/studio-v7jkjqng.js.map +0 -46
  396. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  397. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  398. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  399. package/dist/chunks/studio-yfcstdzy.js +0 -399
  400. package/dist/chunks/studio-yfcstdzy.js.map +0 -12
  401. package/dist/chunks/studio-zp7jncs8.js +0 -469
  402. package/dist/chunks/studio-zp7jncs8.js.map +0 -14
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -0,0 +1,270 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Preferences-keymap.ts — the user's keyboard layer: where it is kept, and what it refuses.
4
+ *
5
+ * `specs/studio.md` §15 rule 2 says the Keyboard sheet is a PROJECTION of the command registry —
6
+ * the same `shortcutReference()` that generates `docs/studio/interface/shortcuts.md` — so it cannot
7
+ * drift from the app. Rebinding must not break that, which is why nothing here writes to a command
8
+ * record. A rebinding is one entry in an override map that `keymap.setOverrides` lays over the
9
+ * declared chords; `keymap.declaredFor(id)` keeps answering what the app ships with, and a reset is
10
+ * the removal of an entry rather than the restoration of a value this module remembered.
11
+ *
12
+ * Three refusals, in the order they are checked, because each one is a different mistake:
13
+ *
14
+ * 1. **Not a chord.** Modifier-only, or a string no keyboard produces.
15
+ * 2. **A bare printable key.** `s` bound globally fires while you are typing your name into a field:
16
+ * the dispatcher is one `document` keydown listener, and only some scopes are narrow enough to
17
+ * hold the key back. So a letter, digit or punctuation needs a modifier; ↑, Esc, F5 and their
18
+ * kind do not, because nothing types them.
19
+ * 3. **Already taken.** The chord is claimed by another command in a scope that can be live at the
20
+ * same instant as this one — `keymap.overlappingScopes`. In the SAME scope that is exactly the
21
+ * condition `keymap.add` throws on at registration (the check that caught ⌘W); across scopes it
22
+ * is the quieter version of the same loss, because every dispatch stack is `[engine, "global"]`
23
+ * and a narrow binding silently shadows a global one for as long as that engine has focus. Two
24
+ * engine scopes (`caret` and `grid`, say) never share a stack and may hold one chord each for
25
+ * ever, so that is allowed — refusing it would forbid the shadowing ladder its whole purpose.
26
+ * Here it must not throw and must not silently win: the result NAMES the command holding the
27
+ * chord AND where that command is live, so the sheet can say who has it and offer to show that
28
+ * row.
29
+ *
30
+ * Storage is `localStorage`, the same place the chrome theme lives, and it is read defensively at
31
+ * every layer: unavailable storage, invalid JSON, a non-array value and an unparseable chord all
32
+ * degrade to "this author has no override for that command" rather than to a keyboard that throws
33
+ * during boot.
34
+ */
35
+
36
+ import { normalizeChord, overlappingScopes, parseChord } from "../commands/keymap";
37
+ import type { KeybindingOverrides } from "../commands/keymap";
38
+ import type { KeyScope } from "../commands/levels";
39
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
40
+
41
+ /** Where the user's layer is kept. One key, one JSON object of `id → chords`. */
42
+ export const KEYBINDINGS_STORAGE_KEY = "jx.keybindings";
43
+
44
+ /** Keys that type nothing, and so may be bound without a modifier. */
45
+ const UNMODIFIED_KEYS = new Set([
46
+ "escape",
47
+ "enter",
48
+ "tab",
49
+ "backspace",
50
+ "delete",
51
+ "arrowup",
52
+ "arrowdown",
53
+ "arrowleft",
54
+ "arrowright",
55
+ "home",
56
+ "end",
57
+ "pageup",
58
+ "pagedown",
59
+ "insert",
60
+ ]);
61
+
62
+ const FUNCTION_KEY = /^f([1-9]|1\d|2[0-4])$/;
63
+
64
+ /**
65
+ * Where each scope's chords are live, in words a refusal can end with.
66
+ *
67
+ * A cross-scope conflict is otherwise unreadable: "⌘S is already Save" is baffling when the sheet
68
+ * is showing you a canvas command and Save is a global one. The scope is the missing half of the
69
+ * sentence, and it is also the half that tells the author whether they care.
70
+ */
71
+ const SCOPE_WHERE: Readonly<Record<KeyScope, string>> = {
72
+ global: "everywhere",
73
+ canvas: "on the canvas",
74
+ caret: "while text is being edited",
75
+ grid: "in the data grid",
76
+ code: "in the code editor",
77
+ dock: "in the panels",
78
+ palette: "in the command palette",
79
+ };
80
+
81
+ /** The command that already holds a chord — everything the refusal needs to name it. */
82
+ export interface RebindConflict {
83
+ /** Canonical chord, as the user just pressed it. */
84
+ chord: string;
85
+ scope: KeyScope;
86
+ /** The holder. */
87
+ commandId: string;
88
+ /** The holder's title, so the refusal names an action rather than an id. */
89
+ title: string;
90
+ }
91
+
92
+ /**
93
+ * What a rebind did.
94
+ *
95
+ * A refusal always carries a `reason` fit to show a person, and carries `conflict` when the reason
96
+ * is another command — the case the sheet turns into "show me that one".
97
+ */
98
+ export type RebindResult = { ok: true } | { ok: false; reason: string; conflict?: RebindConflict };
99
+
100
+ /** Whether a canonical chord may be bound at all. Modifier-less printable keys may not. */
101
+ export function isBindableChord(chord: string): boolean {
102
+ let parsed;
103
+ try {
104
+ parsed = parseChord(chord);
105
+ } catch {
106
+ // Modifier-only, or empty: `parseChord` is the one place that decides what a chord is.
107
+ return false;
108
+ }
109
+ if (parsed.mod || parsed.ctrl || parsed.alt) {
110
+ return true;
111
+ }
112
+ // Shift alone is not enough: ⇧A is how a capital A is typed.
113
+ return UNMODIFIED_KEYS.has(parsed.key) || FUNCTION_KEY.test(parsed.key);
114
+ }
115
+
116
+ function readRaw(): string {
117
+ try {
118
+ return globalThis.localStorage?.getItem(KEYBINDINGS_STORAGE_KEY) ?? "";
119
+ } catch {
120
+ // Storage unavailable (private mode): every command keeps the chord it declared.
121
+ return "";
122
+ }
123
+ }
124
+
125
+ /** Every chord in one stored entry, canonicalised — or `null` if any of them is not a chord. */
126
+ function canonicalise(chords: unknown): string[] | null {
127
+ if (!Array.isArray(chords)) {
128
+ return null;
129
+ }
130
+ const canonical: string[] = [];
131
+ for (const chord of chords as unknown[]) {
132
+ if (typeof chord !== "string") {
133
+ return null;
134
+ }
135
+ try {
136
+ canonical.push(normalizeChord(chord));
137
+ } catch {
138
+ // A stored string that is not a chord: drop the whole entry rather than half of it.
139
+ return null;
140
+ }
141
+ }
142
+ return canonical;
143
+ }
144
+
145
+ /**
146
+ * The stored layer, validated.
147
+ *
148
+ * Nothing here trusts the store: it is a JSON blob a user can hand-edit and an older release can
149
+ * have written. An entry that does not survive validation is dropped, and the command keeps its
150
+ * declared chord — which is the only failure mode that leaves the app usable.
151
+ */
152
+ export function loadKeybindingOverrides(): Map<string, string[]> {
153
+ const layer = new Map<string, string[]>();
154
+ const raw = readRaw();
155
+ if (!raw) {
156
+ return layer;
157
+ }
158
+ let parsed: unknown;
159
+ try {
160
+ parsed = JSON.parse(raw);
161
+ } catch {
162
+ // Corrupt store: the defaults are the whole keymap.
163
+ return layer;
164
+ }
165
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
166
+ return layer;
167
+ }
168
+ for (const [id, chords] of Object.entries(parsed as Record<string, unknown>)) {
169
+ const canonical = canonicalise(chords);
170
+ if (canonical) {
171
+ layer.set(id, canonical);
172
+ }
173
+ }
174
+ return layer;
175
+ }
176
+
177
+ function store(layer: KeybindingOverrides): void {
178
+ try {
179
+ if (layer.size === 0) {
180
+ globalThis.localStorage?.removeItem(KEYBINDINGS_STORAGE_KEY);
181
+ return;
182
+ }
183
+ globalThis.localStorage?.setItem(
184
+ KEYBINDINGS_STORAGE_KEY,
185
+ JSON.stringify(Object.fromEntries(layer)),
186
+ );
187
+ } catch {
188
+ // Storage unavailable — the rebinding is live in this window and is not remembered.
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Lay the stored overrides over a registry's keymap.
194
+ *
195
+ * Called from `registerPreferencesCommands`, which is the bootstrap's one contact point with
196
+ * Preferences. Because the layer lives INSIDE the keymap, applying it before the rest of the
197
+ * bootstrap has finished registering is safe: a record registered afterwards is indexed against the
198
+ * layer that is already there.
199
+ */
200
+ export function applyKeybindingOverrides(registry: CommandRegistry): void {
201
+ registry.keymap.setOverrides(loadKeybindingOverrides());
202
+ }
203
+
204
+ /**
205
+ * Bind `command` to `chord`, or say why not.
206
+ *
207
+ * Rebinding a command back to the chord it declared REMOVES the override rather than pinning the
208
+ * current default: the point of a layer is that a command whose default moves in a later release
209
+ * moves with it unless the author asked for something else.
210
+ */
211
+ export function rebindCommand(
212
+ registry: CommandRegistry,
213
+ command: AnyCommand,
214
+ chord: string,
215
+ ): RebindResult {
216
+ let canonical: string;
217
+ try {
218
+ canonical = normalizeChord(chord);
219
+ } catch {
220
+ return { ok: false, reason: "That is not a shortcut — hold a key as well as the modifiers." };
221
+ }
222
+ if (!isBindableChord(canonical)) {
223
+ return {
224
+ ok: false,
225
+ reason:
226
+ `${registry.keymap.format(canonical)} would fire while you type. ` +
227
+ "Add ⌘, Ctrl or Alt, or use a key that types nothing.",
228
+ };
229
+ }
230
+ const scope: KeyScope = command.keyScope ?? "global";
231
+ // Every scope this one can share a dispatch stack with, not just its own: a chord claimed in an
232
+ // Overlapping scope is one of the two commands going quiet, and which one it is depends only on
233
+ // Which is narrower. `resolveChord` walks the list in order, so the same scope is named first.
234
+ const held = registry.keymap.resolveChord(canonical, overlappingScopes(scope));
235
+ if (held && held.commandId !== command.id) {
236
+ const holder = registry.get(held.commandId);
237
+ const title = holder?.title ?? held.commandId;
238
+ const where = held.scope === scope ? "" : `, which is live ${SCOPE_WHERE[held.scope]}`;
239
+ return {
240
+ ok: false,
241
+ reason: `${registry.keymap.format(canonical)} is already ${title}${where}.`,
242
+ conflict: { chord: canonical, commandId: held.commandId, scope: held.scope, title },
243
+ };
244
+ }
245
+ const next = new Map(registry.keymap.overrides());
246
+ const declared = registry.keymap.declaredFor(command.id);
247
+ if (declared.length === 1 && declared[0] === canonical) {
248
+ next.delete(command.id);
249
+ } else {
250
+ next.set(command.id, [canonical]);
251
+ }
252
+ registry.keymap.setOverrides(next);
253
+ store(next);
254
+ return { ok: true };
255
+ }
256
+
257
+ /**
258
+ * Drop the override on `commandId`, restoring what the registry declares.
259
+ *
260
+ * @returns Whether there was one to drop, so a caller does not repaint over nothing.
261
+ */
262
+ export function resetKeybinding(registry: CommandRegistry, commandId: string): boolean {
263
+ const next = new Map(registry.keymap.overrides());
264
+ if (!next.delete(commandId)) {
265
+ return false;
266
+ }
267
+ registry.keymap.setOverrides(next);
268
+ store(next);
269
+ return true;
270
+ }
@@ -0,0 +1,260 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The three Project Settings sections the document adds — Extensions, Deploy and Raw JSON.
4
+ *
5
+ * They live together because each is a small, direct view of ONE `project.json` key and none of
6
+ * them owns a dialog, a picker or a schema form: `extensions` (an array of package names),
7
+ * `build.adapter` (one enum), and the file itself.
8
+ *
9
+ * **Extensions had no UI at all** — plan §9.2's last row — despite `site-context.ts` carrying a
10
+ * dedicated branch to react to the array changing. Adding or removing a package here is therefore
11
+ * the first way to turn an extension on from inside Studio, and the chokepoint's own
12
+ * `refreshExtensionSurfaces()` re-reads the formats and the contributed sections afterwards, so the
13
+ * section list beside this one updates without a reload.
14
+ *
15
+ * **Raw JSON does not open a second editor.** It shows what is on disk and hands the author to the
16
+ * Code editor over the SAME tab — one document, three editors (§9.3), so switching to the text and
17
+ * back keeps one undo stack instead of forking two.
18
+ *
19
+ * @docs studio/projects/settings
20
+ */
21
+
22
+ import { html, render as litRender, nothing } from "lit-html";
23
+ import { errorMessage } from "@jxsuite/schema/parse";
24
+ import { projectState, updateUi } from "../store";
25
+ import { serializeProjectConfig } from "../tabs/project-config";
26
+ import { tabOfContainer } from "../canvas/canvas-surface";
27
+ import { updateSiteConfig } from "../site-context";
28
+
29
+ import type { ProjectConfig } from "@jxsuite/schema/types";
30
+
31
+ /** The mode the Code editor draws under — `tabs/tab.ts`'s `EDITOR_KIND_BY_MODE` maps it to `code`. */
32
+ const CODE_MODE = "source";
33
+
34
+ /**
35
+ * The last failed write per rendered section container.
36
+ *
37
+ * Keyed by container so two mounted copies (and two tests) never share a message — the same shape
38
+ * `general-settings.ts` uses, and for the same reason: these sections save on change, so a silent
39
+ * rejection would read as "my edit just vanished".
40
+ */
41
+ const errors = new WeakMap<HTMLElement, string>();
42
+
43
+ /** Drop a parked error and re-render. */
44
+ function clearError(container: HTMLElement): void {
45
+ errors.delete(container);
46
+ }
47
+
48
+ /** The error line for a section, if its last write failed. */
49
+ function errorLine(container: HTMLElement) {
50
+ const message = errors.get(container);
51
+ return message === undefined
52
+ ? nothing
53
+ : html`<p class="settings-field-error" role="alert">${message}</p>`;
54
+ }
55
+
56
+ /**
57
+ * Persist a patch, surfacing the rejection instead of swallowing it.
58
+ *
59
+ * `updateSiteConfig` rejects on a failed write and the chokepoint has already filed the Problem, so
60
+ * all that is left here is to keep the form honest about what is on disk.
61
+ *
62
+ * @param {HTMLElement} container
63
+ * @param {Partial<ProjectConfig>} patch
64
+ * @param {() => void} rerender
65
+ */
66
+ async function persist(
67
+ container: HTMLElement,
68
+ patch: Partial<ProjectConfig>,
69
+ rerender: () => void,
70
+ ): Promise<void> {
71
+ try {
72
+ await updateSiteConfig(patch);
73
+ clearError(container);
74
+ } catch (error) {
75
+ errors.set(container, `Could not save project.json — ${errorMessage(error)}`);
76
+ }
77
+ rerender();
78
+ }
79
+
80
+ /** The live project configuration, or an empty one before a project is open. */
81
+ function config(): ProjectConfig {
82
+ return (projectState?.projectConfig ?? {}) as ProjectConfig;
83
+ }
84
+
85
+ // ─── Extensions ───────────────────────────────────────────────────────────────
86
+
87
+ /** Pending package name per container, so typing survives the section re-rendering. */
88
+ const pendingExtension = new WeakMap<HTMLElement, string>();
89
+
90
+ /**
91
+ * The `extensions` array, as a list you can add to and remove from.
92
+ *
93
+ * @param {HTMLElement} container
94
+ */
95
+ export function renderExtensionsSection(container: HTMLElement): void {
96
+ const rerender = () => renderExtensionsSection(container);
97
+ const extensions = config().extensions ?? [];
98
+ const pending = pendingExtension.get(container) ?? "";
99
+
100
+ /* Both handlers re-read rather than closing over the render's values. Typing updates the pending
101
+ name WITHOUT a re-render (re-rendering per keystroke would fight the field's own value), so a
102
+ closed-over `pending` would always be whatever it was when the section was last drawn — which
103
+ is "" on the first add. */
104
+ const add = () => {
105
+ const name = (pendingExtension.get(container) ?? "").trim();
106
+ const current = config().extensions ?? [];
107
+ if (!name || current.includes(name)) {
108
+ return;
109
+ }
110
+ pendingExtension.delete(container);
111
+ void persist(container, { extensions: [...current, name] }, rerender);
112
+ };
113
+
114
+ const remove = (name: string) => {
115
+ const current = config().extensions ?? [];
116
+ void persist(container, { extensions: current.filter((e) => e !== name) }, rerender);
117
+ };
118
+
119
+ const tpl = html`
120
+ <div class="settings-section">
121
+ <h3 class="settings-section-title">Extensions</h3>
122
+ <p class="settings-field-desc">
123
+ Packages this project loads. An extension can contribute formats, elements, data connectors
124
+ — and settings sections of its own, which appear in the list beside this one.
125
+ </p>
126
+ ${errorLine(container)}
127
+ <div class="settings-list-panel">
128
+ ${
129
+ extensions.length === 0
130
+ ? html`<div class="settings-empty-state">No extensions.</div>`
131
+ : extensions.map(
132
+ (name) => html`
133
+ <div class="settings-row">
134
+ <span class="settings-row-name">${name}</span>
135
+ <sp-action-button
136
+ size="xs"
137
+ quiet
138
+ title=${`Remove ${name}`}
139
+ @click=${() => remove(name)}
140
+ >
141
+ <sp-icon-delete slot="icon"></sp-icon-delete>
142
+ </sp-action-button>
143
+ </div>
144
+ `,
145
+ )
146
+ }
147
+ <div class="settings-inline-form">
148
+ <sp-textfield
149
+ size="s"
150
+ class="settings-extension-name"
151
+ placeholder="@scope/package"
152
+ .value=${pending}
153
+ @input=${(e: Event) => {
154
+ pendingExtension.set(container, (e.target as HTMLInputElement).value);
155
+ }}
156
+ @keydown=${(e: KeyboardEvent) => {
157
+ if (e.key === "Enter") {
158
+ add();
159
+ }
160
+ }}
161
+ ></sp-textfield>
162
+ <sp-action-button size="s" @click=${add}>Add</sp-action-button>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ `;
167
+
168
+ litRender(tpl, container);
169
+ }
170
+
171
+ // ─── Deploy ───────────────────────────────────────────────────────────────────
172
+
173
+ /** The build adapters `@jxsuite/compiler` ships. One enumeration, shared with New Project. */
174
+ export const BUILD_ADAPTERS: readonly { value: string; label: string }[] = [
175
+ { label: "Static", value: "static" },
176
+ { label: "Bun", value: "bun" },
177
+ { label: "Node", value: "node" },
178
+ { label: "Cloudflare Workers", value: "cloudflare-workers" },
179
+ { label: "Cloudflare Pages", value: "cloudflare-pages" },
180
+ ];
181
+
182
+ /**
183
+ * Where the project is built for, and what it is built with.
184
+ *
185
+ * The adapter moved off Overview: Overview says what the site IS, and the adapter is a fact about
186
+ * where it SHIPS — the same split §2 principle 5 applies to Contexts.
187
+ *
188
+ * @param {HTMLElement} container
189
+ */
190
+ export function renderDeploySection(container: HTMLElement): void {
191
+ const rerender = () => renderDeploySection(container);
192
+ const current = config().build?.adapter || "static";
193
+
194
+ const onAdapterChange = (e: Event) => {
195
+ void persist(
196
+ container,
197
+ { build: { ...config().build, adapter: (e.target as HTMLInputElement).value } },
198
+ rerender,
199
+ );
200
+ };
201
+
202
+ const tpl = html`
203
+ <div class="settings-section">
204
+ <h3 class="settings-section-title">Deploy</h3>
205
+ ${errorLine(container)}
206
+ <div class="settings-field">
207
+ <label class="settings-field-label">Platform Adapter</label>
208
+ <p class="settings-field-desc">
209
+ What the site is built for. Static writes plain files; the others emit a server entry for
210
+ that runtime.
211
+ </p>
212
+ <sp-picker
213
+ size="s"
214
+ label="Platform Adapter"
215
+ class="settings-build-adapter"
216
+ .value=${current}
217
+ @change=${onAdapterChange}
218
+ >
219
+ ${BUILD_ADAPTERS.map(
220
+ (adapter) => html`<sp-menu-item value=${adapter.value}>${adapter.label}</sp-menu-item>`,
221
+ )}
222
+ </sp-picker>
223
+ </div>
224
+ </div>
225
+ `;
226
+
227
+ litRender(tpl, container);
228
+ }
229
+
230
+ // ─── Raw JSON ─────────────────────────────────────────────────────────────────
231
+
232
+ /**
233
+ * `project.json` as it is written, and the way into editing it as text.
234
+ *
235
+ * The serialisation is the chokepoint's own (`serializeProjectConfig`), so what this shows is what
236
+ * a save writes — not a second pretty-printer that would disagree with the file by an indent.
237
+ *
238
+ * @param {HTMLElement} container
239
+ */
240
+ export function renderRawJsonSection(container: HTMLElement): void {
241
+ const tpl = html`
242
+ <div class="settings-section">
243
+ <h3 class="settings-section-title">Raw JSON</h3>
244
+ <p class="settings-field-desc">
245
+ The whole of <code>project.json</code>, as it is saved. Editing it as code opens the same
246
+ document in the Code editor — one undo stack, one dirty flag.
247
+ </p>
248
+ <pre class="settings-raw-json">${serializeProjectConfig(config())}</pre>
249
+ <sp-action-button
250
+ size="s"
251
+ class="settings-edit-as-code"
252
+ @click=${() => updateUi(tabOfContainer(container), "canvasMode", CODE_MODE)}
253
+ >
254
+ Edit as code
255
+ </sp-action-button>
256
+ </div>
257
+ `;
258
+
259
+ litRender(tpl, container);
260
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * The Project Settings section registry, and which of its sections the document is showing.
3
+ *
4
+ * `registerSettingsSection` is a **preserved public contract** (specs/extensions.md §9.1): an
5
+ * extension's `project` class declares a `$studio.settings` block and `settings/extension-sections`
6
+ * turns it into one of these records. P6 moved Project Settings out of a modal and into a document
7
+ * in the pane; the registry did not move with it, because a section is a contribution to
8
+ * CONFIGURATION, not to a dialog. Splitting it out is what lets the host be replaced without the
9
+ * contribution point being touched.
10
+ *
11
+ * **This module holds no renderer and imports nothing.** That is load-bearing rather than tidy:
12
+ * `panels/settings-pane.ts` is reached from `canvas/canvas-render.ts`, so anything this module
13
+ * imports becomes part of the canvas's import graph. The section RENDERERS are registered by
14
+ * `settings/settings-document.ts`, which the app loads when it registers its commands.
15
+ *
16
+ * **What is requested and what is displayed are two variables, and unregistering touches neither.**
17
+ * The modal had one, and its `unregisterSettingsSection` reset it to `"general"` — which is half of
18
+ * the deep-link race P6 exists to close. The contribution sync unregisters stale keys before it
19
+ * registers fresh ones, so a section merely being REFRESHED took the caller's requested section
20
+ * down with it, silently, after the awaited readiness promise had already resolved. Here a request
21
+ * outlives its section going away and is satisfied the moment the key comes back.
22
+ */
23
+
24
+ /** A Project Settings section: an inner-nav entry plus a renderer for the content area. */
25
+ export interface SettingsSection {
26
+ key: string;
27
+ label: string;
28
+ /** Nav icon name (reserved for future nav treatments). */
29
+ icon?: string | undefined;
30
+ /** Sort position — lower orders render higher in the inner nav. */
31
+ order: number;
32
+ render: (container: HTMLElement) => void;
33
+ }
34
+
35
+ /** Where a settings document lands when nobody named a section. */
36
+ export const DEFAULT_SETTINGS_SECTION = "overview";
37
+
38
+ const sections = new Map<string, SettingsSection>();
39
+
40
+ const listeners = new Set<() => void>();
41
+
42
+ /**
43
+ * Tell every subscriber something changed.
44
+ *
45
+ * A `Set` tolerates deletion during its own iteration, so a listener that unsubscribes itself (the
46
+ * pane host, on unmount) is safe without copying.
47
+ */
48
+ function announce(): void {
49
+ for (const listener of listeners) {
50
+ listener();
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Redraw the settings document — for a change this module cannot see, such as a command selecting
56
+ * an entry inside the section that is already on screen.
57
+ */
58
+ export function notifySettingsDocument(): void {
59
+ announce();
60
+ }
61
+
62
+ /**
63
+ * Subscribe to everything that changes what the settings document draws: the section set (an
64
+ * extension registering or going away) and the chosen section. Returns the unsubscribe.
65
+ *
66
+ * @param {() => void} listener
67
+ * @returns {() => void}
68
+ */
69
+ export function onSettingsDocumentChanged(listener: () => void): () => void {
70
+ listeners.add(listener);
71
+ return () => {
72
+ listeners.delete(listener);
73
+ };
74
+ }
75
+
76
+ // ─── Registration ─────────────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Register (or replace) a settings section.
80
+ *
81
+ * @param {SettingsSection} section
82
+ */
83
+ export function registerSettingsSection(section: SettingsSection): void {
84
+ sections.set(section.key, section);
85
+ announce();
86
+ }
87
+
88
+ /**
89
+ * Remove a registered section — used when a descriptor-contributed section's extension is disabled
90
+ * (see ./extension-sections). Built-ins are never unregistered.
91
+ *
92
+ * @param {string} key
93
+ */
94
+ export function unregisterSettingsSection(key: string): void {
95
+ if (sections.delete(key)) {
96
+ announce();
97
+ }
98
+ }
99
+
100
+ /** Registered sections sorted by order (registration order breaks ties). */
101
+ export function sortedSettingsSections(): SettingsSection[] {
102
+ return [...sections.values()].toSorted((a, b) => a.order - b.order);
103
+ }
104
+
105
+ /** Every registered section key, built-ins and contributions alike, in display order. */
106
+ export function settingsSectionKeys(): string[] {
107
+ return sortedSettingsSections().map((section) => section.key);
108
+ }
109
+
110
+ /**
111
+ * One section by key, or `undefined`.
112
+ *
113
+ * @param {string} key
114
+ * @returns {SettingsSection | undefined}
115
+ */
116
+ export function settingsSection(key: string): SettingsSection | undefined {
117
+ return sections.get(key);
118
+ }
119
+
120
+ // ─── Which section is on screen ───────────────────────────────────────────────
121
+
122
+ /** The section the last caller ASKED for, kept until it is satisfied. */
123
+ let _requested: string | null = null;
124
+
125
+ /** The section the document is drawing. Only a nav click and a satisfied request move it. */
126
+ let _displayed = DEFAULT_SETTINGS_SECTION;
127
+
128
+ /**
129
+ * The section key the document should render right now.
130
+ *
131
+ * A pending request wins as soon as its section exists. Otherwise the displayed one, and — when
132
+ * that has been unregistered too — the first section there is, so the content area is never blank
133
+ * because of bookkeeping.
134
+ *
135
+ * @returns {string}
136
+ */
137
+ export function settingsDocumentSection(): string {
138
+ if (_requested !== null && sections.has(_requested)) {
139
+ _displayed = _requested;
140
+ _requested = null;
141
+ return _displayed;
142
+ }
143
+ if (sections.has(_displayed)) {
144
+ return _displayed;
145
+ }
146
+ return settingsSectionKeys()[0] ?? _displayed;
147
+ }
148
+
149
+ /**
150
+ * Show a section. A nav click satisfies itself immediately; a deep link to a section that has not
151
+ * registered yet is remembered until it does.
152
+ *
153
+ * @param {string} key
154
+ */
155
+ export function setSettingsSection(key: string): void {
156
+ if (sections.has(key)) {
157
+ _displayed = key;
158
+ _requested = null;
159
+ } else {
160
+ _requested = key;
161
+ }
162
+ announce();
163
+ }
164
+
165
+ /** Forget the chosen section — the project-close and test hook. */
166
+ export function resetSettingsDocumentState(): void {
167
+ _requested = null;
168
+ _displayed = DEFAULT_SETTINGS_SECTION;
169
+ }