@jxsuite/studio 6.1.1 → 6.1.2

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 (637) hide show
  1. package/dist/chunks/{2020-va2wwra4.js → 2020-f2gkpy8d.js} +3 -3
  2. package/dist/chunks/{action-group-2mqby73c.js → action-group-4aan7g6g.js} +3 -3
  3. package/dist/chunks/adapter-4912g339.js +4 -0
  4. package/dist/chunks/cf-account-picker-wccaenh6.js +4 -0
  5. package/dist/chunks/cf-settings-br6105tr.js +4 -0
  6. package/dist/chunks/color-28ksk2qm.js +4 -0
  7. package/dist/chunks/color-area-0fj8a4ga.js +4 -0
  8. package/dist/chunks/color-field-xch8kx2g.js +4 -0
  9. package/dist/chunks/color-slider-jxd8v350.js +4 -0
  10. package/dist/chunks/combobox-p0efwjke.js +4 -0
  11. package/dist/chunks/components-db68vsn7.js +4 -0
  12. package/dist/chunks/content-source-1449qesq.js +4 -0
  13. package/dist/chunks/convert-to-repeater-5h457egj.js +4 -0
  14. package/dist/chunks/dialog-c4x05vyx.js +4 -0
  15. package/dist/chunks/editor-x9jn86a7.js +4 -0
  16. package/dist/chunks/entry-commands-may24cpt.js +4 -0
  17. package/dist/chunks/extension-sections-pvr14vxr.js +4 -0
  18. package/dist/chunks/file-ops-x9v54pcj.js +4 -0
  19. package/dist/chunks/files-j3knqf0s.js +4 -0
  20. package/dist/chunks/format-host-tyre2trb.js +4 -0
  21. package/dist/chunks/fs-events-64ma9htc.js +4 -0
  22. package/dist/chunks/git-panel-84ps5dex.js +4 -0
  23. package/dist/chunks/grid-view-d2wq9ezg.js +51 -0
  24. package/dist/chunks/icons-pnfz478q.js +4 -0
  25. package/dist/chunks/iframe-entry-fvbk89gm.js +5 -0
  26. package/dist/chunks/iframe-entry-jdcktez8.js +5 -0
  27. package/dist/chunks/iframe-entry-x0ygf2yr.js +5 -0
  28. package/dist/chunks/iframe-entry-zap785cw.js +5 -0
  29. package/dist/chunks/iframe-host-ck6vejt5.js +4 -0
  30. package/dist/chunks/index-b8xw3vy9.js +4 -0
  31. package/dist/chunks/{index-2veqmnhr.js → index-zpjmzqfk.js} +3 -3
  32. package/dist/chunks/{javascript-8nbvs12s.js → javascript-m54mnwp6.js} +3 -3
  33. package/dist/chunks/{jsonMode-s8kt9dsj.js → jsonMode-w15cmrcm.js} +8 -8
  34. package/dist/chunks/jx-validate-at3zn8wa.js +4 -0
  35. package/dist/chunks/layers-h4xp1pss.js +4 -0
  36. package/dist/chunks/layers-panel-m5hw1d7g.js +4 -0
  37. package/dist/chunks/listbox-e74qjyq6.js +4 -0
  38. package/dist/chunks/lit-html-8t8t3dnr.js +4 -0
  39. package/dist/chunks/media-picker-2cweexc5.js +4 -0
  40. package/dist/chunks/menu-n6yzswmy.js +4 -0
  41. package/dist/chunks/menu-ynw13y0s.js +4 -0
  42. package/dist/chunks/monaco-binding-7gx83x3r.js +4 -0
  43. package/dist/chunks/monaco-buffer-qzxn567r.js +4 -0
  44. package/dist/chunks/monaco-setup-gxf6656g.js +4 -0
  45. package/dist/chunks/notify-cj5zbrs2.js +4 -0
  46. package/dist/chunks/number-field-y62xq0ty.js +4 -0
  47. package/dist/chunks/parse-b7ddhr8m.js +4 -0
  48. package/dist/chunks/platform-3dfws9js.js +4 -0
  49. package/dist/chunks/popover-jt2gxmzs.js +4 -0
  50. package/dist/chunks/publish-panel-1gvxbj21.js +4 -0
  51. package/dist/chunks/quick-search-0dajrwqg.js +4 -0
  52. package/dist/chunks/references-49qgt404.js +4 -0
  53. package/dist/chunks/right-panel-jm1bwe53.js +4 -0
  54. package/dist/chunks/section-registry-6kwc4fky.js +4 -0
  55. package/dist/chunks/select-rzds841e.js +4 -0
  56. package/dist/chunks/split-n2gfpm5m.js +4 -0
  57. package/dist/chunks/{studio-1q1sh2ax.js → studio-0q988f9w.js} +3 -3
  58. package/dist/chunks/studio-0rt52vpq.js +5 -0
  59. package/dist/chunks/studio-18vgbhs5.js +5 -0
  60. package/dist/chunks/{studio-h46vtzk8.js → studio-1fxkaaea.js} +4 -4
  61. package/dist/chunks/studio-24mccdrj.js +5 -0
  62. package/dist/chunks/studio-2eh507zk.js +9 -0
  63. package/dist/chunks/studio-2hcbccfv.js +5 -0
  64. package/dist/chunks/studio-2j1cb95x.js +5 -0
  65. package/dist/chunks/studio-2jbs3rxv.js +5 -0
  66. package/dist/chunks/{studio-rv11kfwa.js → studio-485046te.js} +7 -7
  67. package/dist/chunks/studio-4h8k4dw9.js +5 -0
  68. package/dist/chunks/studio-5f6n18kx.js +5 -0
  69. package/dist/chunks/studio-5gthpky6.js +5 -0
  70. package/dist/chunks/studio-5ve6bbep.js +5 -0
  71. package/dist/chunks/studio-6ep3js6b.js +9 -0
  72. package/dist/chunks/studio-6wkmf3wz.js +9 -0
  73. package/dist/chunks/studio-841rf3cg.js +5 -0
  74. package/dist/chunks/studio-8gmwgheg.js +5 -0
  75. package/dist/chunks/studio-8qm0ep50.js +5 -0
  76. package/dist/chunks/{studio-8grwxtqk.js → studio-90pq9km1.js} +4 -4
  77. package/dist/chunks/studio-924p1tm1.js +6 -0
  78. package/dist/chunks/{studio-pre0skq6.js → studio-a3g98wjr.js} +4 -4
  79. package/dist/chunks/studio-a3shpz9y.js +5 -0
  80. package/dist/chunks/studio-amsbaaar.js +6 -0
  81. package/dist/chunks/studio-at5spjbc.js +7 -0
  82. package/dist/chunks/studio-b2xv945e.js +5 -0
  83. package/dist/chunks/studio-bn5nkafw.js +5 -0
  84. package/dist/chunks/studio-bw6q5xjf.js +9 -0
  85. package/dist/chunks/studio-c7q0t9x1.js +5 -0
  86. package/dist/chunks/studio-camnhc96.js +6 -0
  87. package/dist/chunks/studio-cffqvnzd.js +5 -0
  88. package/dist/chunks/studio-d81xfh1g.js +5 -0
  89. package/dist/chunks/studio-dgz8d0c1.js +5 -0
  90. package/dist/chunks/studio-efvfrsf8.js +5 -0
  91. package/dist/chunks/studio-er30bpcx.js +5 -0
  92. package/dist/chunks/studio-ezscgr64.js +5 -0
  93. package/dist/chunks/studio-ffrrncvz.js +5 -0
  94. package/dist/chunks/studio-fpphgn8y.js +10 -0
  95. package/dist/chunks/studio-j1kvhdjk.js +4 -0
  96. package/dist/chunks/{studio-vrmcjf9g.js → studio-j430n16g.js} +5 -5
  97. package/dist/chunks/studio-jjkg84nj.js +5 -0
  98. package/dist/chunks/studio-m0q32ars.js +4 -0
  99. package/dist/chunks/studio-m6bdst11.js +5 -0
  100. package/dist/chunks/studio-nv2cptd4.js +64 -0
  101. package/dist/chunks/studio-nwfrc98n.js +5 -0
  102. package/dist/chunks/studio-p1wgvp7r.js +5 -0
  103. package/dist/chunks/studio-pag2wncm.js +5 -0
  104. package/dist/chunks/studio-qvanvzfd.js +5 -0
  105. package/dist/chunks/studio-qwa64kt7.js +5 -0
  106. package/dist/chunks/studio-rk1zc4x3.js +928 -0
  107. package/dist/chunks/studio-se2r3ptc.js +5 -0
  108. package/dist/chunks/studio-sg2a0dh0.js +5 -0
  109. package/dist/chunks/studio-tjzjhe68.js +5 -0
  110. package/dist/chunks/{studio-wszjyh0j.js → studio-tq0rtfwj.js} +4 -4
  111. package/dist/chunks/{studio-kggey1jt.js → studio-v0jzqdjm.js} +4 -4
  112. package/dist/chunks/studio-v2w7q8r1.js +7 -0
  113. package/dist/chunks/studio-v5vjq1h0.js +4 -0
  114. package/dist/chunks/studio-v8aga3vp.js +14 -0
  115. package/dist/chunks/studio-vfcdrfmv.js +5 -0
  116. package/dist/chunks/studio-vmfkgdjf.js +5 -0
  117. package/dist/chunks/studio-wt1816jj.js +5 -0
  118. package/dist/chunks/studio-wz73q5kq.js +4 -0
  119. package/dist/chunks/studio-y04qtxvp.js +7 -0
  120. package/dist/chunks/studio-y21g8zzg.js +5 -0
  121. package/dist/chunks/{studio-zey7zsdw.js → studio-y64hrt8p.js} +7 -7
  122. package/dist/chunks/studio-ynn90f58.js +16 -0
  123. package/dist/chunks/studio-yxz682sx.js +5 -0
  124. package/dist/chunks/studio-zp7snvkq.js +5 -0
  125. package/dist/chunks/studio-zq4z6tm6.js +5 -0
  126. package/dist/chunks/swatch-group-ewh24n1f.js +4 -0
  127. package/dist/chunks/tabs-f5cw3tgp.js +4 -0
  128. package/dist/chunks/textfield-wewc8rq2.js +4 -0
  129. package/dist/chunks/toast-c9fn075p.js +4 -0
  130. package/dist/chunks/toast-host-ke5z4ng7.js +4 -0
  131. package/dist/chunks/{toolbar-2nnzqhrj.js → toolbar-44cm5cta.js} +3 -3
  132. package/dist/chunks/tooltip-5v8ngwj2.js +4 -0
  133. package/dist/chunks/tree-mdq3v5cw.js +4 -0
  134. package/dist/chunks/{tsMode-2frah6h1.js → tsMode-sf49ee9x.js} +7 -7
  135. package/dist/chunks/workspace-jtfahpee.js +4 -0
  136. package/dist/chunks/ws-client-nze0k6mv.js +4 -0
  137. package/dist/iframe-entry.js +16 -16
  138. package/dist/manifest.json +63 -63
  139. package/dist/studio.js +97 -97
  140. package/package.json +6 -6
  141. package/src/about/about-modal.ts +4 -3
  142. package/src/account/action-flow.ts +45 -38
  143. package/src/account-status.ts +7 -2
  144. package/src/browse/library-layouts.ts +8 -5
  145. package/src/browse/library-model.ts +18 -6
  146. package/src/browse/library-pane.ts +37 -21
  147. package/src/browse/library-preview.ts +14 -9
  148. package/src/browse/library-source.ts +1 -1
  149. package/src/canvas/asset-refs.ts +24 -9
  150. package/src/canvas/asset-resolve.ts +19 -23
  151. package/src/canvas/canvas-helpers.ts +7 -6
  152. package/src/canvas/canvas-live-render.ts +44 -53
  153. package/src/canvas/canvas-patcher.ts +30 -30
  154. package/src/canvas/canvas-perf.ts +11 -5
  155. package/src/canvas/canvas-render.ts +62 -41
  156. package/src/canvas/canvas-surface.ts +5 -3
  157. package/src/canvas/canvas-utils.ts +90 -49
  158. package/src/canvas/dialog-path.ts +9 -4
  159. package/src/canvas/dialog-state.ts +1 -2
  160. package/src/canvas/diff-marks.ts +9 -16
  161. package/src/canvas/diff-toolbar.ts +8 -7
  162. package/src/canvas/edit-width-drag.ts +4 -1
  163. package/src/canvas/edit-width.ts +1 -3
  164. package/src/canvas/iframe-channel.ts +27 -13
  165. package/src/canvas/iframe-drop.ts +13 -12
  166. package/src/canvas/iframe-editable-root.ts +29 -16
  167. package/src/canvas/iframe-entry.ts +80 -63
  168. package/src/canvas/iframe-eval.ts +73 -49
  169. package/src/canvas/iframe-host.ts +135 -96
  170. package/src/canvas/iframe-inline-edit.ts +49 -30
  171. package/src/canvas/iframe-insert.ts +5 -4
  172. package/src/canvas/iframe-interaction.ts +30 -25
  173. package/src/canvas/iframe-overlay.ts +1 -1
  174. package/src/canvas/iframe-patch.ts +18 -15
  175. package/src/canvas/iframe-position.ts +12 -6
  176. package/src/canvas/iframe-protocol.ts +12 -16
  177. package/src/canvas/iframe-render.ts +36 -34
  178. package/src/canvas/iframe-slash.ts +24 -11
  179. package/src/canvas/iframe-subtree.ts +6 -2
  180. package/src/canvas/path-mapping.ts +19 -12
  181. package/src/canvas/popover-path.ts +7 -3
  182. package/src/canvas/popover-state.ts +1 -2
  183. package/src/canvas/serialize-scope.ts +13 -5
  184. package/src/canvas/stage-scrollbar.ts +2 -1
  185. package/src/canvas/surface-registry.ts +21 -3
  186. package/src/collab/collab-commands.ts +7 -2
  187. package/src/collab/collab-session.ts +84 -45
  188. package/src/collab/collab-state.ts +4 -3
  189. package/src/collab/monaco-binding.ts +16 -13
  190. package/src/collab/monaco-cursors.ts +5 -2
  191. package/src/commands/budget.ts +3 -3
  192. package/src/commands/command-args.ts +103 -71
  193. package/src/commands/context.ts +22 -20
  194. package/src/commands/keymap.ts +47 -43
  195. package/src/commands/levels.ts +3 -3
  196. package/src/commands/live-context.ts +7 -7
  197. package/src/commands/reference.ts +2 -1
  198. package/src/commands/registry.ts +12 -10
  199. package/src/commands/run-reported.ts +15 -10
  200. package/src/component-props.ts +46 -30
  201. package/src/content/collection-match.ts +5 -4
  202. package/src/content/draft-state.ts +4 -3
  203. package/src/content/entry-commands.ts +3 -2
  204. package/src/content/entry-editor.ts +29 -17
  205. package/src/content/entry-fields.ts +12 -7
  206. package/src/content/entry-model.ts +21 -12
  207. package/src/editor/canvas-context-menu.ts +1 -2
  208. package/src/editor/canvas-slash-bridge.ts +3 -6
  209. package/src/editor/content-slice.ts +16 -14
  210. package/src/editor/context-menu.ts +35 -22
  211. package/src/editor/convert-targets.ts +30 -35
  212. package/src/editor/convert-to-component.ts +11 -5
  213. package/src/editor/convert-to-repeater.ts +34 -22
  214. package/src/editor/editable-tags.ts +17 -2
  215. package/src/editor/file-drop-action.ts +21 -22
  216. package/src/editor/inline-edit-apply.ts +35 -17
  217. package/src/editor/inline-edit.ts +66 -50
  218. package/src/editor/inline-format.ts +59 -56
  219. package/src/editor/inline-link.ts +40 -15
  220. package/src/editor/insert-zone-action.ts +1 -1
  221. package/src/editor/merge-tags.ts +58 -19
  222. package/src/editor/repeater-scope.ts +39 -28
  223. package/src/editor/shortcuts.ts +32 -20
  224. package/src/editor/slash-menu.ts +1 -6
  225. package/src/files/asset-versions.ts +4 -4
  226. package/src/files/components.ts +15 -10
  227. package/src/files/elements.ts +5 -6
  228. package/src/files/file-ops.ts +77 -36
  229. package/src/files/files.ts +90 -95
  230. package/src/files/media-meta.ts +2 -1
  231. package/src/files/media-paths.ts +3 -0
  232. package/src/files/serialize-document.ts +2 -2
  233. package/src/format/constraints.ts +1 -1
  234. package/src/format/convert-file.ts +27 -13
  235. package/src/format/format-choices.ts +2 -1
  236. package/src/format/format-host.ts +76 -50
  237. package/src/github/github-auth-status.ts +27 -3
  238. package/src/github/github-auth.ts +19 -9
  239. package/src/github/github-publish.ts +24 -20
  240. package/src/grid/cell-editors.ts +46 -31
  241. package/src/grid/cell-popovers.ts +5 -3
  242. package/src/grid/edit-buffer.ts +10 -9
  243. package/src/grid/grid-controller.ts +20 -22
  244. package/src/grid/grid-layout.ts +21 -5
  245. package/src/grid/grid-open.ts +3 -6
  246. package/src/grid/grid-panel.ts +11 -4
  247. package/src/grid/grid-source.ts +8 -8
  248. package/src/grid/grid-view.ts +14 -17
  249. package/src/grid/redirects-grid.ts +14 -5
  250. package/src/grid/redirects.ts +16 -14
  251. package/src/grid/schema-columns.ts +25 -19
  252. package/src/grid/sources/connector-source.ts +47 -20
  253. package/src/grid/sources/content-source.ts +25 -24
  254. package/src/grid/sources/csv-file-source.ts +2 -2
  255. package/src/grid/tabulator-tables.d.ts +55 -34
  256. package/src/hosting/stage.ts +7 -7
  257. package/src/i18n/i18n-commands.ts +1 -2
  258. package/src/media/media-pane.ts +4 -1
  259. package/src/new-project/add-repo-modal.ts +20 -19
  260. package/src/new-project/import-tab.ts +10 -14
  261. package/src/new-project/location-fields.ts +2 -1
  262. package/src/new-project/new-project-modal.ts +12 -9
  263. package/src/packages/jxsuite-update.ts +4 -3
  264. package/src/packages/pull-package-sync.ts +26 -25
  265. package/src/page-params.ts +97 -68
  266. package/src/panels/activity-panel.ts +17 -9
  267. package/src/panels/ai-chat/attached-context.ts +13 -7
  268. package/src/panels/ai-chat/chat-view.ts +80 -73
  269. package/src/panels/ai-chat/composer.ts +30 -33
  270. package/src/panels/ai-chat/sessions-view.ts +1 -2
  271. package/src/panels/ai-panel.ts +23 -20
  272. package/src/panels/block-action-bar.ts +46 -35
  273. package/src/panels/bottom-dock.ts +5 -3
  274. package/src/panels/canvas-dnd-bridge.ts +31 -24
  275. package/src/panels/data-explorer.ts +62 -25
  276. package/src/panels/data-grid.ts +14 -11
  277. package/src/panels/dnd.ts +108 -118
  278. package/src/panels/editors.ts +52 -21
  279. package/src/panels/elements-panel.ts +10 -3
  280. package/src/panels/events-panel.ts +84 -69
  281. package/src/panels/formula-workspace.ts +76 -51
  282. package/src/panels/frontmatter-fields.ts +24 -19
  283. package/src/panels/frontmatter-panel.ts +44 -34
  284. package/src/panels/git-diff-open.ts +4 -4
  285. package/src/panels/git-panel.ts +49 -61
  286. package/src/panels/head-panel.ts +131 -111
  287. package/src/panels/i18n-panel.ts +10 -3
  288. package/src/panels/imports-panel.ts +23 -47
  289. package/src/panels/jump-bar.ts +48 -29
  290. package/src/panels/layers-panel.ts +97 -74
  291. package/src/panels/left-panel.ts +3 -3
  292. package/src/panels/pane-context.ts +31 -22
  293. package/src/panels/pane-grid.ts +49 -20
  294. package/src/panels/panel-registry.ts +22 -37
  295. package/src/panels/properties-panel.ts +175 -121
  296. package/src/panels/quick-search.ts +53 -31
  297. package/src/panels/right-panel.ts +3 -3
  298. package/src/panels/seo-modal.ts +13 -17
  299. package/src/panels/settings-menu.ts +15 -15
  300. package/src/panels/signals-panel.ts +153 -131
  301. package/src/panels/statement-editor.ts +230 -140
  302. package/src/panels/style-panel.ts +218 -205
  303. package/src/panels/style-utils.ts +114 -51
  304. package/src/panels/stylebook-doc.ts +57 -54
  305. package/src/panels/stylebook-layers-panel.ts +9 -9
  306. package/src/panels/stylebook-panel.ts +13 -17
  307. package/src/panels/tab-drop.ts +27 -11
  308. package/src/panels/tab-strip.ts +36 -26
  309. package/src/platform-errors.ts +57 -43
  310. package/src/platform.ts +58 -30
  311. package/src/platforms/cloud.ts +345 -175
  312. package/src/platforms/default-platform.ts +10 -4
  313. package/src/platforms/devserver.ts +129 -117
  314. package/src/platforms/read-batcher.ts +16 -8
  315. package/src/project-list.ts +2 -1
  316. package/src/publish/deploy-checklist.ts +8 -2
  317. package/src/publish/pages-service.ts +28 -13
  318. package/src/publish/publish-commands.ts +5 -4
  319. package/src/publish/publish-panel.ts +10 -6
  320. package/src/recent-projects.ts +15 -8
  321. package/src/resize-edges.ts +26 -36
  322. package/src/services/a11y-report.ts +26 -19
  323. package/src/services/agent-seed.ts +23 -11
  324. package/src/services/ai-ask.ts +9 -12
  325. package/src/services/ai-command-tools.ts +68 -62
  326. package/src/services/ai-import-tools.ts +80 -67
  327. package/src/services/ai-models.ts +17 -13
  328. package/src/services/ai-project-tools.ts +68 -32
  329. package/src/services/ai-session-store.ts +19 -10
  330. package/src/services/ai-system-prompt.ts +46 -20
  331. package/src/services/ai-tools.ts +71 -27
  332. package/src/services/ai-write-report.ts +20 -12
  333. package/src/services/ai-writes.ts +4 -4
  334. package/src/services/automation.ts +58 -31
  335. package/src/services/cem-export.ts +149 -58
  336. package/src/services/clock.ts +6 -1
  337. package/src/services/code-services.ts +37 -31
  338. package/src/services/context-manager.ts +14 -10
  339. package/src/services/context-resolver.ts +20 -8
  340. package/src/services/data-service.ts +19 -15
  341. package/src/services/directory-picker.ts +18 -12
  342. package/src/services/document-assistant.ts +39 -19
  343. package/src/services/gated-registry.ts +10 -6
  344. package/src/services/harness/chat-reducer.ts +23 -1
  345. package/src/services/import-client.ts +91 -38
  346. package/src/services/import-seed.ts +8 -2
  347. package/src/services/jx-validate.ts +28 -31
  348. package/src/services/live-preview.ts +0 -0
  349. package/src/services/live-surfaces.ts +6 -3
  350. package/src/services/model-uri.ts +10 -2
  351. package/src/services/module-preloads.ts +12 -8
  352. package/src/services/monaco-buffer.ts +43 -24
  353. package/src/services/monaco-lazy.ts +7 -8
  354. package/src/services/monaco-setup.ts +23 -16
  355. package/src/services/notify.ts +44 -17
  356. package/src/services/popover-report.ts +25 -10
  357. package/src/services/preview-eval.ts +20 -8
  358. package/src/services/profile.ts +13 -6
  359. package/src/services/project-path.ts +13 -10
  360. package/src/services/references.ts +0 -0
  361. package/src/services/render-critic.ts +8 -7
  362. package/src/services/settings/kernel.ts +13 -4
  363. package/src/services/settings/write-queue.ts +9 -9
  364. package/src/services/token-lint.ts +12 -10
  365. package/src/services/tool-executor.ts +17 -5
  366. package/src/services/tool-outcomes.ts +44 -24
  367. package/src/services/trusted-types.ts +24 -7
  368. package/src/settings/contexts-section.ts +11 -8
  369. package/src/settings/contributed-section.ts +79 -60
  370. package/src/settings/css-vars-editor.ts +37 -16
  371. package/src/settings/defs-editor.ts +38 -35
  372. package/src/settings/extension-rows.ts +16 -8
  373. package/src/settings/extension-sections.ts +23 -13
  374. package/src/settings/extensions-section.ts +4 -1
  375. package/src/settings/general-settings.ts +11 -8
  376. package/src/settings/locales-section.ts +20 -12
  377. package/src/settings/preferences-dialog.ts +9 -6
  378. package/src/settings/preferences-keymap.ts +7 -5
  379. package/src/settings/preferences-sections.ts +11 -5
  380. package/src/settings/project-sections.ts +1 -1
  381. package/src/settings/schema-field-ui.ts +9 -0
  382. package/src/settings/settings-document.ts +5 -5
  383. package/src/shell/tree.ts +6 -5
  384. package/src/shell.ts +104 -81
  385. package/src/site-context.ts +37 -22
  386. package/src/state.ts +41 -49
  387. package/src/store.ts +54 -34
  388. package/src/studio.ts +25 -75
  389. package/src/style/live-preview.ts +1 -1
  390. package/src/style/project-styles.ts +6 -7
  391. package/src/style/token-ref.ts +5 -4
  392. package/src/surfaces/about.ts +18 -19
  393. package/src/surfaces/add-repo.ts +17 -19
  394. package/src/surfaces/ai-chat.ts +31 -36
  395. package/src/surfaces/ai-credentials-form.ts +6 -16
  396. package/src/surfaces/ai-managed-connect.ts +3 -4
  397. package/src/surfaces/ai-model-picker.ts +3 -5
  398. package/src/surfaces/block-action-bar.ts +22 -11
  399. package/src/surfaces/boot-failure.ts +4 -5
  400. package/src/surfaces/bottom-dock.ts +9 -18
  401. package/src/surfaces/canvas-stage.ts +12 -11
  402. package/src/surfaces/cf-account-picker.ts +17 -18
  403. package/src/surfaces/commandbar.ts +14 -9
  404. package/src/surfaces/convert-repeater.ts +17 -19
  405. package/src/surfaces/data-actions.ts +9 -7
  406. package/src/surfaces/dialog.ts +26 -28
  407. package/src/surfaces/diff-toolbar.ts +9 -7
  408. package/src/surfaces/doc-header.ts +10 -19
  409. package/src/surfaces/empty-state.ts +7 -10
  410. package/src/surfaces/entry-editor.ts +14 -11
  411. package/src/surfaces/expression-editor.ts +11 -25
  412. package/src/surfaces/files-panel.ts +27 -14
  413. package/src/surfaces/formula-palette.ts +4 -5
  414. package/src/surfaces/git-panel.ts +7 -15
  415. package/src/surfaces/github-auth.ts +16 -20
  416. package/src/surfaces/github-publish.ts +17 -22
  417. package/src/surfaces/grid-cell.ts +30 -31
  418. package/src/surfaces/grid-open.ts +10 -14
  419. package/src/surfaces/grid-panel.ts +13 -9
  420. package/src/surfaces/grid-replace.ts +13 -13
  421. package/src/surfaces/grid-views.ts +15 -18
  422. package/src/surfaces/inspector-dock.ts +13 -12
  423. package/src/surfaces/jump-bar.ts +5 -6
  424. package/src/surfaces/jxsuite-update.ts +16 -22
  425. package/src/surfaces/library-pane.ts +19 -20
  426. package/src/surfaces/logic-panel.ts +17 -30
  427. package/src/surfaces/logic-workspace.ts +13 -15
  428. package/src/surfaces/media-browser.ts +11 -10
  429. package/src/surfaces/media-field.ts +4 -5
  430. package/src/surfaces/media-pane.ts +8 -7
  431. package/src/surfaces/menu.ts +29 -23
  432. package/src/surfaces/navigator-dock.ts +11 -12
  433. package/src/surfaces/new-project.ts +27 -26
  434. package/src/surfaces/pane-context.ts +20 -25
  435. package/src/surfaces/pane-grid.ts +15 -10
  436. package/src/surfaces/panel-activity.ts +5 -6
  437. package/src/surfaces/panel-data.ts +3 -4
  438. package/src/surfaces/panel-deploy-checklist.ts +5 -6
  439. package/src/surfaces/panel-elements.ts +5 -7
  440. package/src/surfaces/panel-i18n.ts +5 -7
  441. package/src/surfaces/panel-imports.ts +6 -13
  442. package/src/surfaces/panel-outline.ts +25 -19
  443. package/src/surfaces/panel-page.ts +11 -18
  444. package/src/surfaces/panel-problems.ts +5 -7
  445. package/src/surfaces/panel-signals.ts +20 -34
  446. package/src/surfaces/panel-stylebook-layers.ts +5 -8
  447. package/src/surfaces/preferences.ts +21 -11
  448. package/src/surfaces/progress-modal.ts +15 -9
  449. package/src/surfaces/properties-panel.json +1 -1
  450. package/src/surfaces/properties-panel.ts +20 -42
  451. package/src/surfaces/publish.ts +18 -18
  452. package/src/surfaces/push-plan.ts +7 -8
  453. package/src/surfaces/rail.ts +13 -13
  454. package/src/surfaces/reference-field.ts +11 -15
  455. package/src/surfaces/schema-builder.ts +16 -22
  456. package/src/surfaces/schema-form.ts +15 -25
  457. package/src/surfaces/secret-field.ts +9 -6
  458. package/src/surfaces/seo.ts +21 -15
  459. package/src/surfaces/settings-contexts.ts +9 -7
  460. package/src/surfaces/settings-contributed.ts +14 -10
  461. package/src/surfaces/settings-css-vars.ts +5 -12
  462. package/src/surfaces/settings-defs.json +2 -2
  463. package/src/surfaces/settings-defs.ts +17 -21
  464. package/src/surfaces/settings-deploy.ts +8 -15
  465. package/src/surfaces/settings-extensions.ts +5 -6
  466. package/src/surfaces/settings-head.ts +33 -39
  467. package/src/surfaces/settings-locales.ts +8 -20
  468. package/src/surfaces/settings-overview.ts +5 -6
  469. package/src/surfaces/settings-packages.ts +8 -7
  470. package/src/surfaces/settings-pane.ts +9 -18
  471. package/src/surfaces/settings-rawjson.ts +5 -6
  472. package/src/surfaces/shell.ts +1 -2
  473. package/src/surfaces/slash-menu.ts +19 -9
  474. package/src/surfaces/statements.ts +13 -18
  475. package/src/surfaces/statusbar.ts +17 -14
  476. package/src/surfaces/style-panel.ts +18 -31
  477. package/src/surfaces/stylebook-chrome.ts +3 -5
  478. package/src/surfaces/tab-strip.ts +14 -13
  479. package/src/surfaces/target-line.ts +6 -7
  480. package/src/surfaces/welcome.ts +5 -7
  481. package/src/tabs/patch-ops.ts +4 -3
  482. package/src/tabs/project-config.ts +32 -21
  483. package/src/tabs/selection.ts +36 -8
  484. package/src/tabs/tab.ts +52 -41
  485. package/src/tabs/transact.ts +98 -53
  486. package/src/types.ts +32 -17
  487. package/src/ui/ai-credentials-form.ts +10 -13
  488. package/src/ui/ai-model-picker.ts +3 -3
  489. package/src/ui/cf-account-picker.ts +7 -9
  490. package/src/ui/color-selector.ts +9 -5
  491. package/src/ui/dynamic-slot.ts +7 -8
  492. package/src/ui/expression-editor.ts +112 -105
  493. package/src/ui/form-controls.ts +57 -48
  494. package/src/ui/formula-catalog.ts +239 -230
  495. package/src/ui/formula-chips.ts +33 -18
  496. package/src/ui/layers.ts +44 -31
  497. package/src/ui/media-picker.ts +18 -9
  498. package/src/ui/popover-surface.ts +20 -12
  499. package/src/ui/progress-modal.ts +13 -8
  500. package/src/ui/regions.ts +7 -6
  501. package/src/ui/schema-form.ts +113 -70
  502. package/src/ui/surface.ts +71 -8
  503. package/src/ui/timing.ts +9 -0
  504. package/src/ui/value-source.ts +44 -31
  505. package/src/ui/virtual-window.ts +3 -2
  506. package/src/utils/base64.ts +3 -1
  507. package/src/utils/edit-display.ts +73 -73
  508. package/src/utils/element-selectors.ts +3 -2
  509. package/src/utils/google-fonts.ts +15 -14
  510. package/src/utils/inherited-style.ts +18 -20
  511. package/src/utils/link-target.ts +1 -1
  512. package/src/utils/preview-format.ts +25 -3
  513. package/src/utils/strip-events.ts +49 -22
  514. package/src/utils/studio-utils.ts +29 -14
  515. package/src/view.ts +2 -3
  516. package/src/workspace/pane-derive.ts +10 -12
  517. package/src/workspace/session.ts +53 -43
  518. package/src/workspace/workspace.ts +40 -36
  519. package/dist/chunks/adapter-f93pafd7.js +0 -4
  520. package/dist/chunks/cf-account-picker-af1h381v.js +0 -4
  521. package/dist/chunks/cf-settings-125tdm0w.js +0 -4
  522. package/dist/chunks/color-area-jqhna8zy.js +0 -4
  523. package/dist/chunks/color-field-embq5ctg.js +0 -4
  524. package/dist/chunks/color-fpezvnkt.js +0 -4
  525. package/dist/chunks/color-slider-1qmw7wqy.js +0 -4
  526. package/dist/chunks/combobox-773hdzyj.js +0 -4
  527. package/dist/chunks/components-ax2f1fnn.js +0 -4
  528. package/dist/chunks/content-source-108aqtk1.js +0 -4
  529. package/dist/chunks/convert-to-repeater-ry5rmppr.js +0 -4
  530. package/dist/chunks/dialog-mtm9fq5t.js +0 -4
  531. package/dist/chunks/editor-pfgdmkb0.js +0 -4
  532. package/dist/chunks/entry-commands-t0k1q0kx.js +0 -4
  533. package/dist/chunks/extension-sections-6mwh2hf8.js +0 -4
  534. package/dist/chunks/file-ops-meej5mqf.js +0 -4
  535. package/dist/chunks/files-44vp8a2s.js +0 -4
  536. package/dist/chunks/format-host-7q1petb9.js +0 -4
  537. package/dist/chunks/fs-events-h2mwnxaz.js +0 -4
  538. package/dist/chunks/git-panel-zngbymvd.js +0 -4
  539. package/dist/chunks/grid-view-sx9vynsp.js +0 -51
  540. package/dist/chunks/icons-p44xhjf0.js +0 -4
  541. package/dist/chunks/iframe-entry-f7mqjwpm.js +0 -5
  542. package/dist/chunks/iframe-entry-haw7jkvz.js +0 -5
  543. package/dist/chunks/iframe-entry-z6qrytv3.js +0 -5
  544. package/dist/chunks/iframe-host-qk3ycq87.js +0 -4
  545. package/dist/chunks/index-wkyfjeqz.js +0 -4
  546. package/dist/chunks/jx-validate-psgd7vsx.js +0 -4
  547. package/dist/chunks/layers-panel-1kq2jyfk.js +0 -4
  548. package/dist/chunks/layers-xd92fg8d.js +0 -4
  549. package/dist/chunks/listbox-s8fkmnq8.js +0 -4
  550. package/dist/chunks/lit-html-ac4cwwr4.js +0 -4
  551. package/dist/chunks/media-picker-0svzkdyb.js +0 -4
  552. package/dist/chunks/menu-37d07z9m.js +0 -4
  553. package/dist/chunks/menu-5c55ea3d.js +0 -4
  554. package/dist/chunks/monaco-binding-z6r70pdt.js +0 -4
  555. package/dist/chunks/monaco-buffer-fz6gq5y7.js +0 -4
  556. package/dist/chunks/monaco-setup-q4axkgj7.js +0 -4
  557. package/dist/chunks/notify-0xgtn8q1.js +0 -4
  558. package/dist/chunks/number-field-gw7sq8rn.js +0 -4
  559. package/dist/chunks/parse-897fvqah.js +0 -4
  560. package/dist/chunks/platform-v1qctbe6.js +0 -4
  561. package/dist/chunks/popover-8htra4er.js +0 -4
  562. package/dist/chunks/publish-panel-ym1fmgq5.js +0 -4
  563. package/dist/chunks/quick-search-a54e6t5n.js +0 -4
  564. package/dist/chunks/references-gmqp82fs.js +0 -4
  565. package/dist/chunks/right-panel-v7t9awbk.js +0 -4
  566. package/dist/chunks/section-registry-vhyf3zsr.js +0 -4
  567. package/dist/chunks/select-n3v7gwas.js +0 -4
  568. package/dist/chunks/split-pff8f3ze.js +0 -4
  569. package/dist/chunks/studio-0bhx5hz6.js +0 -5
  570. package/dist/chunks/studio-0k1ddnyy.js +0 -6
  571. package/dist/chunks/studio-1b04pd0j.js +0 -64
  572. package/dist/chunks/studio-4dwbj6gb.js +0 -5
  573. package/dist/chunks/studio-4tanh828.js +0 -5
  574. package/dist/chunks/studio-55h4mstj.js +0 -4
  575. package/dist/chunks/studio-5tnvjkz4.js +0 -5
  576. package/dist/chunks/studio-60dqgb0c.js +0 -5
  577. package/dist/chunks/studio-61dp09qg.js +0 -9
  578. package/dist/chunks/studio-63epcrj2.js +0 -5
  579. package/dist/chunks/studio-74k29dy9.js +0 -5
  580. package/dist/chunks/studio-7vg6jcd3.js +0 -5
  581. package/dist/chunks/studio-7zx31knw.js +0 -9
  582. package/dist/chunks/studio-8bxk97km.js +0 -5
  583. package/dist/chunks/studio-933e30n4.js +0 -5
  584. package/dist/chunks/studio-98wnwhbg.js +0 -5
  585. package/dist/chunks/studio-9mccjwjh.js +0 -14
  586. package/dist/chunks/studio-a2ke1e4t.js +0 -5
  587. package/dist/chunks/studio-e3a2a6v3.js +0 -4
  588. package/dist/chunks/studio-e81q1vb9.js +0 -5
  589. package/dist/chunks/studio-epgrmqqm.js +0 -5
  590. package/dist/chunks/studio-ery42sqr.js +0 -4
  591. package/dist/chunks/studio-g1shz58g.js +0 -5
  592. package/dist/chunks/studio-gf0fz03c.js +0 -5
  593. package/dist/chunks/studio-gjtpe62q.js +0 -6
  594. package/dist/chunks/studio-gkskhg7m.js +0 -5
  595. package/dist/chunks/studio-gx4dnjzd.js +0 -5
  596. package/dist/chunks/studio-j03zga3y.js +0 -9
  597. package/dist/chunks/studio-jhpxeckz.js +0 -5
  598. package/dist/chunks/studio-k01r7ykp.js +0 -5
  599. package/dist/chunks/studio-kk7h30ne.js +0 -7
  600. package/dist/chunks/studio-kt1zqney.js +0 -5
  601. package/dist/chunks/studio-m3cwhvye.js +0 -5
  602. package/dist/chunks/studio-mcayt6hz.js +0 -5
  603. package/dist/chunks/studio-myjcghv3.js +0 -5
  604. package/dist/chunks/studio-n8bxwc3y.js +0 -4
  605. package/dist/chunks/studio-ngm8gvpb.js +0 -5
  606. package/dist/chunks/studio-nj5a01jy.js +0 -928
  607. package/dist/chunks/studio-pcgz8heb.js +0 -5
  608. package/dist/chunks/studio-qcsq0t15.js +0 -5
  609. package/dist/chunks/studio-qkstvve9.js +0 -5
  610. package/dist/chunks/studio-qv1pwd3c.js +0 -5
  611. package/dist/chunks/studio-rc6dvxjb.js +0 -7
  612. package/dist/chunks/studio-s8dmmz66.js +0 -5
  613. package/dist/chunks/studio-sp5xzbj4.js +0 -6
  614. package/dist/chunks/studio-szsjbj0g.js +0 -5
  615. package/dist/chunks/studio-t1xzawhr.js +0 -16
  616. package/dist/chunks/studio-t6b6qe31.js +0 -5
  617. package/dist/chunks/studio-tdcprbqb.js +0 -5
  618. package/dist/chunks/studio-vyc0b4he.js +0 -10
  619. package/dist/chunks/studio-w0cgf6jg.js +0 -5
  620. package/dist/chunks/studio-w3hskzqk.js +0 -5
  621. package/dist/chunks/studio-xgrgcdyh.js +0 -9
  622. package/dist/chunks/studio-xz2yxfcb.js +0 -5
  623. package/dist/chunks/studio-y6n42tgs.js +0 -5
  624. package/dist/chunks/studio-yb560vxr.js +0 -5
  625. package/dist/chunks/studio-yn9f8vtx.js +0 -5
  626. package/dist/chunks/studio-yw05w8f0.js +0 -7
  627. package/dist/chunks/studio-z16sgavc.js +0 -5
  628. package/dist/chunks/studio-zyqn40w4.js +0 -5
  629. package/dist/chunks/swatch-group-8qh9a3bm.js +0 -4
  630. package/dist/chunks/tabs-jwthg40w.js +0 -4
  631. package/dist/chunks/textfield-spy3hc77.js +0 -4
  632. package/dist/chunks/toast-4d35zd64.js +0 -4
  633. package/dist/chunks/toast-host-8sjmfed3.js +0 -4
  634. package/dist/chunks/tooltip-fd6g9xy3.js +0 -4
  635. package/dist/chunks/tree-h19v1jfz.js +0 -4
  636. package/dist/chunks/workspace-zt81g1ms.js +0 -4
  637. package/dist/chunks/ws-client-n2bh4hkf.js +0 -4
@@ -30,6 +30,14 @@ export function modelUriFor(path: string): string {
30
30
  return ENTRY_SCHEMA_FILES.has(path) ? `file:///.jx/generated/${path}` : `file:///${path}`;
31
31
  }
32
32
 
33
+ /** The two Monaco model URIs one pane's comparison of one file uses. */
34
+ export interface DiffModelUris {
35
+ /** The committed (HEAD) side. */
36
+ head: string;
37
+ /** The working-tree side. */
38
+ work: string;
39
+ }
40
+
33
41
  /**
34
42
  * The two model URIs a comparison's Monaco diff editor mounts at.
35
43
  *
@@ -50,9 +58,9 @@ export function modelUriFor(path: string): string {
50
58
  *
51
59
  * @param {string} paneId
52
60
  * @param {string} path - Project-relative file path
53
- * @returns {{ head: string; work: string }}
61
+ * @returns {DiffModelUris}
54
62
  */
55
- export function diffModelUrisFor(paneId: string, path: string): { head: string; work: string } {
63
+ export function diffModelUrisFor(paneId: string, path: string): DiffModelUris {
56
64
  return {
57
65
  head: `file:///.jx/diff/${paneId}/head/${path}`,
58
66
  work: `file:///.jx/diff/${paneId}/work/${path}`,
@@ -19,6 +19,11 @@
19
19
  * studio's own lazy surfaces runs.
20
20
  */
21
21
 
22
+ /** A document head carrying the batcher's install flag. */
23
+ interface BatchedHead extends HTMLHeadElement {
24
+ __modulePreloadsBatched?: boolean;
25
+ }
26
+
22
27
  const QUEUED: HTMLLinkElement[] = [];
23
28
  let frame: number | null = null;
24
29
  let originalAppend: (<T extends Node>(node: T) => T) | null = null;
@@ -53,15 +58,14 @@ function appendChildPatched<T extends Node>(this: Node, node: T): T {
53
58
  }
54
59
 
55
60
  export function installModulePreloadBatcher(): void {
56
- const { head } = document;
57
- if (
58
- !head ||
59
- (head as HTMLElement & { __modulePreloadsBatched?: boolean }).__modulePreloadsBatched
60
- ) {
61
+ /* SAFETY: a widening. The DOM standard declares `document.head` nullable (a document with no head
62
+ element has none) where lib.dom says it is always there; the install flag is an optional expando
63
+ this module alone writes. */
64
+ const head = document.head as BatchedHead | null;
65
+ if (!head || head.__modulePreloadsBatched) {
61
66
  return;
62
67
  }
63
- const patched = head as HTMLElement & { __modulePreloadsBatched?: boolean };
64
68
  originalAppend = Node.prototype.appendChild.bind(head);
65
- patched.__modulePreloadsBatched = true;
66
- head.appendChild = appendChildPatched as unknown as typeof head.appendChild;
69
+ head.__modulePreloadsBatched = true;
70
+ head.appendChild = appendChildPatched;
67
71
  }
@@ -105,8 +105,10 @@
105
105
  * @docs studio/logic/code
106
106
  */
107
107
 
108
+ import { isFunction, isNonNullObject } from "@jxsuite/schema/guards";
108
109
  import { toRaw } from "../reactivity";
109
110
  import { notify } from "./notify";
111
+ import type { NotifyOptions } from "./notify";
110
112
  import type { Tab } from "../tabs/tab";
111
113
  import { view } from "../view";
112
114
  import { allCanvasSurfaces } from "../canvas/surface-registry";
@@ -114,7 +116,7 @@ import { allCanvasSurfaces } from "../canvas/surface-registry";
114
116
  /** The slice of a Monaco editor this module needs. Both of Studio's Monacos satisfy it. */
115
117
  export interface MonacoBuffer {
116
118
  getValue: () => string;
117
- getModel: () => unknown;
119
+ getModel: () => object | null;
118
120
  hasTextFocus: () => boolean;
119
121
  /** The debounced work armed over THIS buffer. Installed by {@link bufferWrites}. */
120
122
  _writes?: BufferWrites;
@@ -199,6 +201,19 @@ export interface BufferWrites {
199
201
  */
200
202
  export const BUFFER_COMMIT = "commit";
201
203
 
204
+ /**
205
+ * Whether a run answered with a promise to wait for, rather than finishing synchronously.
206
+ *
207
+ * Asked of the thenable shape, not of `instanceof Promise`, because that is what `await` itself
208
+ * asks: whatever the armed work handed back is waited on exactly when `await` would wait on it.
209
+ *
210
+ * @param {void | Promise<void>} result What an armed run (or a flush of one) returned.
211
+ * @returns {boolean}
212
+ */
213
+ function isPending(result: void | Promise<void>): result is Promise<void> {
214
+ return isNonNullObject(result) && isFunction(result.then);
215
+ }
216
+
202
217
  /**
203
218
  * Install (and return) the debounce holder for `buffer`.
204
219
  *
@@ -223,10 +238,10 @@ export function bufferWrites(buffer: MonacoBuffer): BufferWrites {
223
238
  * report.
224
239
  */
225
240
  function track(key: string, result: void | Promise<void>): void | Promise<void> {
226
- if (typeof (result as Promise<void> | undefined)?.then !== "function") {
241
+ if (!isPending(result)) {
227
242
  return result;
228
243
  }
229
- const promise = (result as Promise<void>).finally(() => {
244
+ const promise = result.finally(() => {
230
245
  if (inFlight.get(key) === promise) {
231
246
  inFlight.delete(key);
232
247
  }
@@ -340,18 +355,19 @@ function reportDiscarded(
340
355
  const tab = buffer._editingTab ?? null;
341
356
  const where = tab?.documentPath ?? "Untitled";
342
357
  const { holds, reached, source } = SURFACE[surface];
343
- notify.warn(`${holds} was discarded — it was never ${reached} "${where}".`, {
344
- ...(failure ? { detail: describeCommitFailure(failure.error) } : {}),
345
- key: `buffer-discarded:${surface}:${tab?.id ?? "none"}`,
346
- source,
347
- ...(tab?.documentPath ? { path: tab.documentPath } : {}),
348
- });
358
+ const options: NotifyOptions = failure ? { detail: describeCommitFailure(failure.error) } : {};
359
+ options.key = `buffer-discarded:${surface}:${tab?.id ?? "none"}`;
360
+ options.source = source;
361
+ if (tab?.documentPath) {
362
+ options.path = tab.documentPath;
363
+ }
364
+ notify.warn(`${holds} was discarded — it was never ${reached} "${where}".`, options);
349
365
  return false;
350
366
  }
351
367
 
352
368
  /** What a failed commit is worth showing: the stack when there is one, the value otherwise. */
353
- function describeCommitFailure(error: unknown): string {
354
- return error instanceof Error ? (error.stack ?? error.message) : String(error);
369
+ function describeCommitFailure(cause: unknown): string {
370
+ return cause instanceof Error ? (cause.stack ?? cause.message) : String(cause);
355
371
  }
356
372
 
357
373
  /**
@@ -428,21 +444,21 @@ export function commitBufferWrites(
428
444
  ran, and a live 500ms timer was left over an editor whose container lit was about to replace.
429
445
  A timer reading `""` off a detached model is the exact defect this module was created to
430
446
  prevent, so the teardown below must happen whatever the commit does. */
431
- let pending: Promise<void> | undefined;
447
+ let pending: void | Promise<void>;
432
448
  try {
433
449
  // Fire-and-forget on purpose: the disposer is synchronous, and the commit read `getValue()`
434
450
  // Before its own first `await`. What is left to await is the write INTO the document, which
435
451
  // Does not need this editor to still exist.
436
- pending = writes.flush(BUFFER_COMMIT) as Promise<void> | undefined;
452
+ pending = writes.flush(BUFFER_COMMIT);
437
453
  } catch (error) {
438
454
  writes.cancel();
439
455
  return reportDiscarded(buffer, surface, { error });
440
456
  }
441
457
  writes.cancel();
442
- if (typeof pending?.then === "function") {
458
+ if (isPending(pending)) {
443
459
  void pending.then(
444
460
  () => reportDiscarded(buffer, surface, null),
445
- (error: unknown) => reportDiscarded(buffer, surface, { error }),
461
+ (error) => reportDiscarded(buffer, surface, { error }),
446
462
  );
447
463
  return true;
448
464
  }
@@ -512,20 +528,20 @@ export function bufferMovedOn(buffer: MonacoBuffer, expected?: string): boolean
512
528
  * @returns {MonacoBuffer[]}
513
529
  */
514
530
  function buffersForTab(tab: Tab): MonacoBuffer[] {
515
- const raw = toRaw(tab as object);
531
+ const raw = toRaw(tab);
516
532
  /* Every stage's source buffer, plus the dock's one function editor.
517
533
  The source view is per-PANE now: two Code panes are two Monaco instances over two documents,
518
534
  and a close gate that asked only "the" source editor would have missed whichever one it was
519
535
  not holding — which is the same half-answer that let ⌘W close a tab over unprompted typing. */
520
536
  const mounted: (MonacoBuffer | null)[] = [
521
- ...allCanvasSurfaces().map((surface) => surface.monacoEditor as MonacoBuffer | null),
537
+ ...allCanvasSurfaces().map((surface) => surface.monacoEditor),
522
538
  view.functionEditor,
523
539
  ];
524
540
  return mounted.filter(
525
541
  (buffer): buffer is MonacoBuffer =>
526
542
  buffer != null &&
527
543
  buffer._editingTab != null &&
528
- toRaw(buffer._editingTab as object) === raw &&
544
+ toRaw(buffer._editingTab) === raw &&
529
545
  bufferIsLive(buffer),
530
546
  );
531
547
  }
@@ -598,14 +614,17 @@ export async function commitTabBuffers(tab: Tab): Promise<void> {
598
614
  try {
599
615
  await buffer._writes?.flush(BUFFER_COMMIT);
600
616
  } catch (error) {
617
+ const options: NotifyOptions = {
618
+ detail: describeCommitFailure(error),
619
+ key: `buffer-commit:${tab.id}`,
620
+ source: "Editor",
621
+ };
622
+ if (tab.documentPath) {
623
+ options.path = tab.documentPath;
624
+ }
601
625
  notify.error(
602
626
  `Could not save the editor's contents into "${tab.documentPath ?? "Untitled"}".`,
603
- {
604
- detail: error instanceof Error ? (error.stack ?? error.message) : String(error),
605
- key: `buffer-commit:${tab.id}`,
606
- source: "Editor",
607
- ...(tab.documentPath ? { path: tab.documentPath } : {}),
608
- },
627
+ options,
609
628
  );
610
629
  }
611
630
  }
@@ -26,6 +26,7 @@
26
26
  on the object {@link loadMonaco} resolves to — typing this as the root would make five namespaces
27
27
  look available that are not. `./editor` is exactly the editor API surface that import returns. */
28
28
  import type * as monacoNs from "monaco-editor/editor";
29
+ import type { ProjectSchemasResponse } from "../types";
29
30
 
30
31
  export type Monaco = typeof monacoNs;
31
32
 
@@ -41,7 +42,7 @@ let _loading: Promise<Monaco> | null = null;
41
42
  * which is precisely what the lazy path exists to avoid. So the schemas wait here instead, and the
42
43
  * loader applies whatever is pending once an editor actually needs one.
43
44
  */
44
- let _pendingSchemas: { project?: unknown; document?: unknown } | null = null;
45
+ let _pendingSchemas: ProjectSchemasResponse | null = null;
45
46
  let _pendingSchemasDirty = false;
46
47
 
47
48
  /**
@@ -54,9 +55,7 @@ let _pendingSchemasDirty = false;
54
55
  * @returns Settles once the schemas are in force. Fire-and-forget is a valid use — say so with
55
56
  * `void` at the call site rather than having this hide it.
56
57
  */
57
- export function setProjectSchemasForMonaco(
58
- schemas: { project?: unknown; document?: unknown } | null,
59
- ): Promise<void> {
58
+ export function setProjectSchemasForMonaco(schemas: ProjectSchemasResponse | null): Promise<void> {
60
59
  _pendingSchemas = schemas;
61
60
  _pendingSchemasDirty = true;
62
61
  /* Resolves when the schemas have REACHED Monaco, not when they were queued. With Monaco already
@@ -100,7 +99,7 @@ export function loadMonaco(): Promise<Monaco> {
100
99
  import("monaco-editor/editor"),
101
100
  import("./monaco-setup.js"),
102
101
  ]);
103
- _monaco = monaco as unknown as Monaco;
102
+ _monaco = monaco;
104
103
  // The project's schemas may have arrived long before now; register them before the first editor
105
104
  // Mounts so its very first validation pass uses the right rules.
106
105
  await applyPendingSchemas();
@@ -138,15 +137,15 @@ export function loadedMonaco(): Monaco | null {
138
137
  * attached to anything, and no URI briefly claimed by a model nobody holds.
139
138
  *
140
139
  * @param {Element | null | undefined} container Where the editor was going to be created.
141
- * @param {unknown} existing The surface's editor handle (`view.monacoEditor` /
142
- * `view.functionEditor`). Non-null means another mount already won this race.
140
+ * @param {monacoNs.editor.IEditor | null} existing The surface's editor handle (`view.monacoEditor`
141
+ * / `view.functionEditor`). Non-null means another mount already won this race.
143
142
  * @param {() => boolean} stillTargeted Whether the app still wants exactly THIS mount — the same
144
143
  * tab, the same mode, the same editing target. Lazy: the two cheap clauses above answer first.
145
144
  * @returns {boolean}
146
145
  */
147
146
  export function mountStillWanted(
148
147
  container: Element | null | undefined,
149
- existing: unknown,
148
+ existing: monacoNs.editor.IEditor | null,
150
149
  stillTargeted: () => boolean,
151
150
  ): boolean {
152
151
  if (!container?.isConnected || existing) {
@@ -80,18 +80,24 @@ import "monaco-editor/languages/definitions/javascript/register";
80
80
  import { flattenSchema } from "@jxsuite/schema/project-schemas";
81
81
  import jxSchema from "@jxsuite/schema/schema.json";
82
82
  import projectSchema from "@jxsuite/schema/project-schema.json";
83
+ import type { JsonObject } from "@jxsuite/schema/types";
84
+ import type { ProjectSchemasResponse } from "../types";
83
85
  import { bundleUrl } from "./bundle-base";
84
86
 
85
87
  /* Re-exported for the modules that already imported it from here. New callers should reach for
86
88
  `./model-uri` directly — it is Monaco-free, and importing this module loads the editor. */
87
89
  export { modelUriFor } from "./model-uri";
88
90
 
89
- const WORKER_FILES: Record<string, string> = {
90
- editorWorkerService: "editor.worker.js",
91
- javascript: "ts.worker.js",
92
- json: "json.worker.js",
93
- typescript: "ts.worker.js",
94
- };
91
+ /** The worker every label without a language service of its own runs in. */
92
+ const EDITOR_WORKER_FILE = "editor.worker.js";
93
+
94
+ /** Monaco worker label → the pre-bundled worker file that serves it. */
95
+ const WORKER_FILES = new Map([
96
+ ["editorWorkerService", EDITOR_WORKER_FILE],
97
+ ["javascript", "ts.worker.js"],
98
+ ["json", "json.worker.js"],
99
+ ["typescript", "ts.worker.js"],
100
+ ]);
95
101
 
96
102
  /**
97
103
  * Resolve a Monaco worker to the PRE-BUNDLED copy that ships NEXT TO THE ENTRY — `dist/workers/`
@@ -119,7 +125,7 @@ const WORKER_FILES: Record<string, string> = {
119
125
  * @returns {string} Absolute URL of the worker bundle
120
126
  */
121
127
  export function workerUrl(label: string): string {
122
- const file = WORKER_FILES[label] || WORKER_FILES.editorWorkerService!;
128
+ const file = WORKER_FILES.get(label) ?? EDITOR_WORKER_FILE;
123
129
  return bundleUrl(`workers/${file}`);
124
130
  }
125
131
 
@@ -132,10 +138,13 @@ self.MonacoEnvironment = {
132
138
  /* Monaco caches glyph widths when an editor is created. JetBrains Mono is a
133
139
  vendored webfont (index.html @font-face) and may finish loading after the
134
140
  first editor mounts, so remeasure once all document fonts are ready to keep
135
- cursor and selection alignment correct. */
136
- if (typeof document !== "undefined" && document.fonts?.ready) {
141
+ cursor and selection alignment correct. `lib.dom` declares `document.fonts` unconditionally, but
142
+ DOM shims (happy-dom under test) ship without it, so the font set is typed as possibly absent. */
143
+ const documentFonts: FontFaceSet | undefined =
144
+ typeof document === "undefined" ? undefined : document.fonts;
145
+ if (documentFonts?.ready) {
137
146
  // oxlint-disable-next-line unicorn/prefer-top-level-await -- fire-and-forget: awaiting fonts.ready at top level would block module evaluation (and studio startup) until webfonts load
138
- void document.fonts.ready.then(async () => {
147
+ void documentFonts.ready.then(async () => {
139
148
  const monaco = await import("monaco-editor/editor");
140
149
  monaco.editor.remeasureFonts();
141
150
  });
@@ -186,7 +195,7 @@ const DOCUMENT_ENTRY_SCHEMA_ID = "file:///document.schema.json";
186
195
  * worse, resolved to an empty schema, so the fileMatch registration never ran and REAL violations
187
196
  * went unreported behind that one diagnostic.
188
197
  */
189
- function applyJsonSchemas(documentSchema: unknown, projectJsonSchema: unknown) {
198
+ function applyJsonSchemas(documentSchema: JsonObject, projectJsonSchema: JsonObject) {
190
199
  jsonDefaults.setDiagnosticsOptions({
191
200
  allowComments: false,
192
201
  schemas: [
@@ -212,8 +221,8 @@ function applyJsonSchemas(documentSchema: unknown, projectJsonSchema: unknown) {
212
221
  a URI before the `#` by FETCHING it — with `enableSchemaRequest` off that just fails, leaving
213
222
  `$paths` reporting an unresolvable ref. Flattening rewrites those refs onto the shipped defaults
214
223
  embedded in the same document, which is exactly the right fallback semantics. */
215
- const FALLBACK_DOCUMENT_SCHEMA = flattenSchema(jxSchema as Record<string, unknown>);
216
- const FALLBACK_PROJECT_SCHEMA = flattenSchema(projectSchema as Record<string, unknown>);
224
+ const FALLBACK_DOCUMENT_SCHEMA = flattenSchema(jxSchema);
225
+ const FALLBACK_PROJECT_SCHEMA = flattenSchema(projectSchema);
217
226
 
218
227
  // Bundled core schemas register at module init — the offline fallback every platform starts from.
219
228
  applyJsonSchemas(FALLBACK_DOCUMENT_SCHEMA, FALLBACK_PROJECT_SCHEMA);
@@ -232,9 +241,7 @@ applyJsonSchemas(FALLBACK_DOCUMENT_SCHEMA, FALLBACK_PROJECT_SCHEMA);
232
241
  * @param {object | null} schemas - Pre-bundled `{ document, project }` payload
233
242
  * @returns {boolean} True when per-project schemas were applied; false on fallback
234
243
  */
235
- export function applyProjectSchemas(
236
- schemas: { project?: unknown; document?: unknown } | null | undefined,
237
- ): boolean {
244
+ export function applyProjectSchemas(schemas: ProjectSchemasResponse | null | undefined): boolean {
238
245
  const { document, project } = schemas ?? {};
239
246
  if (!document && !project) {
240
247
  return false;
@@ -34,6 +34,7 @@
34
34
  * this module learning it exists.
35
35
  */
36
36
 
37
+ import type { CommandArgValues } from "../commands/command-args";
37
38
  import { reactive } from "../reactivity";
38
39
  import { announce } from "./announce";
39
40
  import { now } from "./clock";
@@ -87,7 +88,7 @@ export interface NotifyOptions {
87
88
  */
88
89
  action?: string;
89
90
  /** Args passed to {@link NotifyOptions.action} when it runs. */
90
- actionArgs?: Record<string, unknown>;
91
+ actionArgs?: CommandArgValues;
91
92
  /** The long form — a stack, a captured log, a validator's output. Rendered in Problems only. */
92
93
  detail?: string;
93
94
  /** Who reported it: "Save", "Source Control", "Canvas", "Assistant". Groups the Problems list. */
@@ -114,7 +115,7 @@ export interface Notification {
114
115
  /** Epoch ms from {@link now}, so a pinned clock pins the "2m ago" a Problems row renders. */
115
116
  readonly at: number;
116
117
  readonly action?: string;
117
- readonly actionArgs?: Record<string, unknown>;
118
+ readonly actionArgs?: CommandArgValues;
118
119
  readonly detail?: string;
119
120
  readonly source?: string;
120
121
  readonly path?: string;
@@ -158,6 +159,45 @@ function storeFor(tier: NotificationTier): Notification[] {
158
159
  return tier === "problem" ? problems : toasts;
159
160
  }
160
161
 
162
+ /** A record's mutable draft: {@link Notification} is frozen to readers, not to its one builder. */
163
+ type NotificationDraft = { -readonly [K in keyof Notification]: Notification[K] };
164
+
165
+ /**
166
+ * Build the record for one outcome, copying only the options the caller actually set — so a host
167
+ * rendering `record.detail` never meets an explicit `undefined` — in the order every record has
168
+ * always carried its keys.
169
+ */
170
+ function recordFor(
171
+ severity: Severity,
172
+ message: string,
173
+ tier: NotificationTier,
174
+ options: NotifyOptions,
175
+ ): Notification {
176
+ const record: NotificationDraft = { at: now(), id: nextId(), message, severity, tier };
177
+ if (options.action !== undefined) {
178
+ record.action = options.action;
179
+ }
180
+ if (options.actionArgs !== undefined) {
181
+ record.actionArgs = options.actionArgs;
182
+ }
183
+ if (options.detail !== undefined) {
184
+ record.detail = options.detail;
185
+ }
186
+ if (options.source !== undefined) {
187
+ record.source = options.source;
188
+ }
189
+ if (options.path !== undefined) {
190
+ record.path = options.path;
191
+ }
192
+ if (options.key !== undefined) {
193
+ record.key = options.key;
194
+ }
195
+ if (tier === "toast") {
196
+ record.timeoutMs = options.timeoutMs ?? TOAST_LIFETIME_MS[severity];
197
+ }
198
+ return record;
199
+ }
200
+
161
201
  /**
162
202
  * Record an outcome and return it.
163
203
  *
@@ -170,20 +210,7 @@ export function notify(
170
210
  options: NotifyOptions = {},
171
211
  ): Notification {
172
212
  const tier = options.tier ?? DEFAULT_TIER[severity];
173
- const record: Notification = {
174
- at: now(),
175
- id: nextId(),
176
- message,
177
- severity,
178
- tier,
179
- ...(options.action === undefined ? {} : { action: options.action }),
180
- ...(options.actionArgs === undefined ? {} : { actionArgs: options.actionArgs }),
181
- ...(options.detail === undefined ? {} : { detail: options.detail }),
182
- ...(options.source === undefined ? {} : { source: options.source }),
183
- ...(options.path === undefined ? {} : { path: options.path }),
184
- ...(options.key === undefined ? {} : { key: options.key }),
185
- ...(tier === "toast" ? { timeoutMs: options.timeoutMs ?? TOAST_LIFETIME_MS[severity] } : {}),
186
- };
213
+ const record = recordFor(severity, message, tier, options);
187
214
 
188
215
  const store = storeFor(tier);
189
216
  if (record.key !== undefined) {
@@ -273,7 +300,7 @@ export interface ProblemFinding {
273
300
  path?: string;
274
301
  key?: string;
275
302
  action?: string;
276
- actionArgs?: Record<string, unknown>;
303
+ actionArgs?: CommandArgValues;
277
304
  }
278
305
 
279
306
  /**
@@ -23,6 +23,7 @@
23
23
 
24
24
  import { activeTab } from "../workspace/workspace";
25
25
  import { clearProblems, notify, problemFindings } from "./notify";
26
+ import type { NotifyOptions } from "./notify";
26
27
  import {
27
28
  findPopoverDefects,
28
29
  popoverDisplayRepair,
@@ -39,7 +40,8 @@ import {
39
40
  } from "../tabs/transact";
40
41
  import { getNodeAtPath } from "../state";
41
42
  import type { AnyCommand, CommandRegistry } from "../commands/registry";
42
- import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
43
+ import type { CommandArgValues } from "../commands/command-args";
44
+ import type { JxElement } from "@jxsuite/schema/types";
43
45
  import type { JxPath } from "../state";
44
46
  import type { PopoverFix, PopoverPath } from "@jxsuite/schema/overlays";
45
47
 
@@ -100,7 +102,7 @@ export function reportPopoverProblems(doc: JxElement, path?: string): number {
100
102
  const defects = findPopoverDefects(doc, KIT_SCOPE);
101
103
  for (const defect of defects) {
102
104
  const action = defect.fix === undefined ? undefined : REPAIR_COMMAND[defect.fix];
103
- notify(defect.severity, defect.message, {
105
+ const options: NotifyOptions = {
104
106
  detail: defect.detail,
105
107
  /* Keyed by RULE AND PATH, not by rule alone: a header with two popovers can carry the same
106
108
  defect twice, and a key that collapsed them would report one and silently drop the other.
@@ -109,32 +111,45 @@ export function reportPopoverProblems(doc: JxElement, path?: string): number {
109
111
  key: problemKey(seen, "popover", defect.rule, defect.path),
110
112
  source: POPOVER_PROBLEM_SOURCE,
111
113
  tier: "problem",
112
- ...(action === undefined ? {} : { action, actionArgs: { path: defect.path } }),
113
- ...(path === undefined ? {} : { path }),
114
- });
114
+ };
115
+ if (action !== undefined) {
116
+ options.action = action;
117
+ options.actionArgs = { path: defect.path };
118
+ }
119
+ if (path !== undefined) {
120
+ options.path = path;
121
+ }
122
+ notify(defect.severity, defect.message, options);
115
123
  }
116
124
  /* The dialog and invoker-command rules (`spec.md` §8.7) file under the same source: one report
117
125
  for everything the platform overlays. They carry no repair button yet — the popover repairs
118
126
  move `display` into `:popover-open`, and a dialog's open rule is a different selector. */
119
127
  const dialogDefects = findDialogDefects(doc, KIT_SCOPE);
120
128
  for (const defect of dialogDefects) {
121
- notify(defect.severity, defect.message, {
129
+ const options: NotifyOptions = {
122
130
  detail: defect.detail,
123
131
  key: problemKey(seen, "dialog", defect.rule, defect.path),
124
132
  source: POPOVER_PROBLEM_SOURCE,
125
133
  tier: "problem",
126
- ...(path === undefined ? {} : { path }),
127
- });
134
+ };
135
+ if (path !== undefined) {
136
+ options.path = path;
137
+ }
138
+ notify(defect.severity, defect.message, options);
128
139
  }
129
140
  return defects.length + dialogDefects.length;
130
141
  }
131
142
 
132
143
  /** Read the path argument a repair command was invoked with, or throw naming the command. */
133
- function repairPath(id: string, args: Record<string, unknown> | undefined): JxPath {
144
+ function repairPath(id: string, args: CommandArgValues | undefined): JxPath {
134
145
  const path = args?.path;
135
146
  if (!Array.isArray(path)) {
136
147
  throw new RangeError(`command "${id}" needs a "path" argument naming the popover`);
137
148
  }
149
+ /* SAFETY: every repair record below declares `path` as an array whose items are
150
+ `["string", "number"]`, which `coerceArgs` reads as a path argument and checks segment by
151
+ segment (`pathArg`) before `run` is called — so an array that reached here holds only string
152
+ keys and numeric indexes. */
138
153
  return path as JxPath;
139
154
  }
140
155
 
@@ -217,7 +232,7 @@ export function popoverCommands(): AnyCommand[] {
217
232
  const id = "document.repairPopoverDisplay";
218
233
  const tab = repairTab(id);
219
234
  const path = repairPath(id, args);
220
- const node = getNodeAtPath(tab.doc.document, path) as JxMutableNode | undefined;
235
+ const node = getNodeAtPath(tab.doc.document, path);
221
236
  const repair = popoverDisplayRepair(node?.style);
222
237
  if (!repair) {
223
238
  throw new RangeError(`command "${id}": nothing to move — this popover sets no display`);
@@ -10,12 +10,15 @@
10
10
  */
11
11
 
12
12
  import { evaluateExpression, isMutating } from "@jxsuite/runtime/expression";
13
+ import { isNonNullObject } from "@jxsuite/schema/guards";
13
14
  import { toRaw } from "../reactivity";
14
15
  // Imported for local use AND re-exported below so existing consumers keep their import site.
15
16
  // oxlint-disable-next-line unicorn/prefer-export-from
16
17
  import { formatPreviewValue } from "../utils/preview-format";
18
+ import type { PreviewValue } from "../utils/preview-format";
17
19
 
18
- import type { ExpressionNode } from "@jxsuite/runtime/expression";
20
+ import type { JxScope } from "@jxsuite/runtime/types";
21
+ import type { JxExpressionNode, JxExpressionOperand } from "@jxsuite/schema/types";
19
22
 
20
23
  export interface ExpressionPreview {
21
24
  /** Path-keyed display values: "" is the root node, "value/target" a nested operand, etc. */
@@ -36,14 +39,14 @@ export { formatPreviewValue };
36
39
  * (canvas not yet rendered) — the editor renders no badges rather than wrong ones.
37
40
  */
38
41
  export function previewExpression(
39
- node: unknown,
40
- scope?: Record<string, unknown> | null,
42
+ node: JxExpressionOperand | undefined,
43
+ scope?: JxScope | null,
41
44
  ): ExpressionPreview | null {
42
- if (!scope || !node || typeof node !== "object" || !("operator" in node)) {
45
+ if (!scope || !isNonNullObject(node) || !("operator" in node)) {
43
46
  return null;
44
47
  }
45
48
 
46
- let state: Record<string, unknown>;
49
+ let state: JxScope;
47
50
  try {
48
51
  // Callers hand over tab.session.canvas.scope, which the reactive session tree wraps in a
49
52
  // Proxy — structuredClone rejects proxies, so unwrap to the raw snapshot first.
@@ -54,11 +57,20 @@ export function previewExpression(
54
57
  }
55
58
 
56
59
  const values = new Map<string, string>();
57
- const expr = node as ExpressionNode;
60
+ /* SAFETY: an operand object carrying `operator` is the expression-node member of the operand
61
+ union (the plain-object member is an options bag, which has no operator), and a node whose
62
+ other members are malformed throws inside `evaluateExpression`, which this preview reports
63
+ as its `error` rather than trusting. */
64
+ const expr = node as JxExpressionNode;
58
65
  let errorMessage: string | null = null;
59
66
  try {
60
- evaluateExpression(expr, state, null, undefined, {
61
- report: (path, value) => values.set(path.join("/"), formatPreviewValue(value)),
67
+ void evaluateExpression(expr, state, null, undefined, {
68
+ report: (path, value) => {
69
+ /* SAFETY: the trace reports raw runtime values, and PreviewValue names every category a JS
70
+ value falls in (primitive, bigint, symbol, function, array, record); formatting relies on
71
+ nothing beyond what JSON.stringify and String() accept for any value. */
72
+ values.set(path.join("/"), formatPreviewValue(value as PreviewValue));
73
+ },
62
74
  });
63
75
  } catch (error) {
64
76
  errorMessage = error instanceof Error ? error.message : String(error);
@@ -40,7 +40,7 @@ export interface StartupProfile {
40
40
  * `default` is not a no-op by omission — it is a named state, so "the app started normally" and
41
41
  * "the profile was unrecognised" are different outcomes and the second one is loud.
42
42
  */
43
- export const STARTUP_PROFILES: Readonly<Record<string, StartupProfile>> = {
43
+ export const STARTUP_PROFILES = {
44
44
  default: {
45
45
  description: "Resume whatever was persisted — layout, recents, settings, chat history.",
46
46
  id: "default",
@@ -51,7 +51,10 @@ export const STARTUP_PROFILES: Readonly<Record<string, StartupProfile>> = {
51
51
  id: "fresh",
52
52
  reset: "app-state",
53
53
  },
54
- };
54
+ } satisfies Readonly<Record<string, StartupProfile>>;
55
+
56
+ /** {@link STARTUP_PROFILES} by id, for a lookup whose id arrives from the query string. */
57
+ const PROFILES_BY_ID = new Map<string, StartupProfile>(Object.entries(STARTUP_PROFILES));
55
58
 
56
59
  export const DEFAULT_PROFILE_ID = "default";
57
60
 
@@ -77,7 +80,8 @@ export function isStudioStorageKey(key: string): boolean {
77
80
  * @returns The keys removed, so a caller (Preferences, a test) can report what it did.
78
81
  */
79
82
  export function clearStudioStorage(): string[] {
80
- const store = globalThis.localStorage as Storage | undefined;
83
+ // `localStorage` is absent outside a browser realm (Bun without a DOM shim), whatever lib.dom says.
84
+ const store = typeof localStorage === "undefined" ? undefined : localStorage;
81
85
  if (!store) {
82
86
  return [];
83
87
  }
@@ -105,10 +109,10 @@ export function clearStudioStorage(): string[] {
105
109
  * `default` would start the app in the state the caller was trying to avoid.
106
110
  */
107
111
  export function applyProfile(id: string): StartupProfile {
108
- const profile = STARTUP_PROFILES[id];
112
+ const profile = PROFILES_BY_ID.get(id);
109
113
  if (!profile) {
110
114
  throw new RangeError(
111
- `unknown startup profile "${id}"; declared: ${Object.keys(STARTUP_PROFILES).join(", ")}`,
115
+ `unknown startup profile "${id}"; declared: ${[...PROFILES_BY_ID.keys()].join(", ")}`,
112
116
  );
113
117
  }
114
118
  if (profile.reset === "app-state") {
@@ -137,7 +141,10 @@ export function applyStartupProfile(search?: string): StartupState {
137
141
  if (_startup) {
138
142
  return _startup;
139
143
  }
140
- const query = new URLSearchParams(search ?? globalThis.location?.search ?? "");
144
+ // `location` is absent outside a browser realm, which is why the tests pass `search` themselves.
145
+ const query = new URLSearchParams(
146
+ search ?? (typeof location === "undefined" ? "" : location.search),
147
+ );
141
148
  const profile = applyProfile(query.get("profile") ?? DEFAULT_PROFILE_ID);
142
149
  const clockParam = query.get("clock");
143
150
  _startup = { clock: clockParam === null ? null : pinClock(clockParam), profile };