@jxsuite/studio 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-2c188n41.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-hhj92gcr.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-ygzav7z5.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-n9zmpxjg.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +23711 -35185
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -120
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +75 -2
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +113 -0
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-2c188n41.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-hhj92gcr.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -174,6 +174,7 @@ function render(showFilter: boolean) {
174
174
  html`
175
175
  <sp-popover
176
176
  open
177
+ data-jx-region="overlay.menu:slash-menu"
177
178
  ${ref((el) => {
178
179
  _popoverEl = (el as HTMLElement | undefined) || null;
179
180
  })}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Elements.ts — what belongs in a document's (or a project's) `$elements`, decided once.
3
+ *
4
+ * `$elements` is the list of component modules a document may use. Three surfaces wrote it, each
5
+ * with its own idea of what "already imported" means, and plan §11.2 names all three: the Packages
6
+ * panel's cherry-pick checkboxes, its "Add component…" picker, and the automatic push when a
7
+ * component is dropped on the canvas. "One `enableElement()` / `disableElement()` service behind
8
+ * all three. Today the same array is written three ways with no shared UI and no shared
9
+ * validation."
10
+ *
11
+ * They really did disagree. The canvas drop matched a local component by `ref.endsWith(basename)`,
12
+ * so `./components/card.json` counted `./vendor/card.json` as already imported and the drop
13
+ * silently produced an element the page could not resolve. The picker checked nothing at all and
14
+ * pushed a duplicate `$ref` every time you chose the same component twice. Only the checkbox knew
15
+ * that a legacy full-package entry (`@acme/ui`) satisfies a cherry-picked one (`@acme/ui/card`), so
16
+ * enabling one component through the picker left the package-wide import beside it.
17
+ *
18
+ * **Pure functions over the array.** They return the new list rather than writing it, because the
19
+ * two levels persist differently and genuinely should: a document's `$elements` goes through
20
+ * `transact` and is undoable, and the project's goes through `updateSiteConfig` and is not. What
21
+ * they must not differ on is WHICH entries the list should hold.
22
+ */
23
+
24
+ import { computeRelativePath } from "./components";
25
+ import type { ComponentEntry } from "./components";
26
+ import type { JxElement } from "@jxsuite/schema/types";
27
+
28
+ /** One `$elements` entry: an npm specifier, or a `$ref` to a file in this project. */
29
+ export type ElementsEntry = string | JxElement | { $ref: string };
30
+
31
+ /** The `$ref` of an entry, when it has one. */
32
+ function refOf(entry: ElementsEntry): string | null {
33
+ return typeof entry === "object" && entry !== null && typeof entry.$ref === "string"
34
+ ? entry.$ref
35
+ : null;
36
+ }
37
+
38
+ /**
39
+ * The npm specifier a component is imported by — its cherry-picked subpath, or the bare package.
40
+ *
41
+ * `null` for a project-local component, which is imported by path instead.
42
+ */
43
+ export function npmSpecifier(comp: ComponentEntry): string | null {
44
+ if (comp.source !== "npm" || !comp.package) {
45
+ return null;
46
+ }
47
+ return comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
48
+ }
49
+
50
+ /**
51
+ * The entry that imports `comp` into a document at `fromPath`.
52
+ *
53
+ * `null` when the component can be named neither way — an npm entry with no package, or a local one
54
+ * with no path — which is a registry the caller should not be writing from.
55
+ */
56
+ export function elementsEntryFor(
57
+ comp: ComponentEntry,
58
+ fromPath: string | null,
59
+ ): ElementsEntry | null {
60
+ const specifier = npmSpecifier(comp);
61
+ if (specifier) {
62
+ return specifier;
63
+ }
64
+ return comp.path ? { $ref: computeRelativePath(fromPath, comp.path) } : null;
65
+ }
66
+
67
+ /**
68
+ * Is `comp` already imported by this list?
69
+ *
70
+ * ONE rule, and it is the union of what the three call sites each knew separately:
71
+ *
72
+ * - An npm component matches its cherry-picked specifier **or** a legacy whole-package entry;
73
+ * - A local component matches a `$ref` that resolves to the same file, compared on the resolved path
74
+ * rather than on its last segment — `endsWith("card.json")` called two different cards the same
75
+ * component, and the drop that trusted it produced an unresolvable element.
76
+ */
77
+ export function hasElement(
78
+ elements: readonly ElementsEntry[],
79
+ comp: ComponentEntry,
80
+ fromPath: string | null,
81
+ ): boolean {
82
+ const specifier = npmSpecifier(comp);
83
+ if (specifier) {
84
+ return elements.some(
85
+ (entry) => typeof entry === "string" && (entry === specifier || entry === comp.package),
86
+ );
87
+ }
88
+ if (!comp.path) {
89
+ return false;
90
+ }
91
+ const wanted = normalizeRef(computeRelativePath(fromPath, comp.path));
92
+ return elements.some((entry) => {
93
+ const ref = refOf(entry);
94
+ return ref !== null && normalizeRef(ref) === wanted;
95
+ });
96
+ }
97
+
98
+ /** A `$ref` reduced to a comparable form: `./a/b.json`, `a/b.json` and `././a/b.json` are one file. */
99
+ function normalizeRef(ref: string): string {
100
+ return ref.replace(/^(?:\.\/)+/, "");
101
+ }
102
+
103
+ /**
104
+ * Import `comp`, and return the new list. Idempotent, and it drops what the new entry supersedes.
105
+ *
106
+ * Enabling a cherry-picked npm component removes the whole-package entry it replaces — otherwise
107
+ * the page keeps pulling in every component of that package while the panel shows one ticked.
108
+ */
109
+ export function enableElement(
110
+ elements: readonly ElementsEntry[],
111
+ comp: ComponentEntry,
112
+ fromPath: string | null,
113
+ ): ElementsEntry[] {
114
+ const entry = elementsEntryFor(comp, fromPath);
115
+ if (!entry) {
116
+ return [...elements];
117
+ }
118
+ const specifier = npmSpecifier(comp);
119
+ // The legacy whole-package import goes when a cherry-picked one arrives — but never when the
120
+ // Component IS the package (`modulePath` absent), where the two are the same entry.
121
+ const kept =
122
+ specifier && comp.modulePath ? elements.filter((e) => e !== comp.package) : [...elements];
123
+ return hasElement(kept, comp, fromPath) ? kept : [...kept, entry];
124
+ }
125
+
126
+ /** Remove `comp`'s import, and return the new list. Idempotent. */
127
+ export function disableElement(
128
+ elements: readonly ElementsEntry[],
129
+ comp: ComponentEntry,
130
+ fromPath: string | null,
131
+ ): ElementsEntry[] {
132
+ const specifier = npmSpecifier(comp);
133
+ if (specifier) {
134
+ // Both spellings: a component ticked off must not stay imported through the package entry.
135
+ return elements.filter(
136
+ (entry) => !(typeof entry === "string" && (entry === specifier || entry === comp.package)),
137
+ );
138
+ }
139
+ if (!comp.path) {
140
+ return [...elements];
141
+ }
142
+ const wanted = normalizeRef(computeRelativePath(fromPath, comp.path));
143
+ return elements.filter((entry) => {
144
+ const ref = refOf(entry);
145
+ return ref === null || normalizeRef(ref) !== wanted;
146
+ });
147
+ }
148
+
149
+ /** Remove every entry belonging to `pkg` — what uninstalling a package leaves behind. */
150
+ export function removePackageElements(
151
+ elements: readonly ElementsEntry[],
152
+ pkg: string,
153
+ ): ElementsEntry[] {
154
+ return elements.filter(
155
+ (entry) => typeof entry !== "string" || !(entry === pkg || entry.startsWith(`${pkg}/`)),
156
+ );
157
+ }
158
+
159
+ /** Remove the entry naming `ref` exactly — the Packages panel's per-row × on a local import. */
160
+ export function removeElementRef(elements: readonly ElementsEntry[], ref: string): ElementsEntry[] {
161
+ const wanted = normalizeRef(ref);
162
+ return elements.filter((entry) => {
163
+ const found = refOf(entry);
164
+ return found === null || normalizeRef(found) !== wanted;
165
+ });
166
+ }
@@ -1,20 +1,33 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * File Operations — open, save, export documents.
3
+ * File Operations — open, save, export documents, and confirm the destructive ones.
4
4
  *
5
5
  * All functions read/write directly from/to `activeTab.value` (the reactive tab). `.json` is the
6
6
  * native document format; every other extension dispatches through the project's format registry
7
7
  * (parse to open, serialize to save).
8
+ *
9
+ * {@link confirmFileDelete} and {@link renamePromptMessage} live here because a delete and a rename
10
+ * are the same question asked twice — "what does this break?" — and the answer was missing from
11
+ * both. Every surface that removes or moves a file routes its confirmation through them, so no
12
+ * caller can ship a destructive dialog that grades only by reversibility again.
13
+ *
14
+ * @docs studio/projects/pages-layouts-components
8
15
  */
9
16
 
17
+ import { html, nothing } from "lit-html";
18
+ import { loadUsages, usageWarning } from "../services/references";
19
+ import { isMediaFile } from "./media-upload";
20
+ import { loadMediaUsages } from "./media-usage";
21
+ import { showConfirmDialog } from "../ui/layers";
10
22
  import { locateDocument } from "../services/code-services";
11
23
  import { errorMessage } from "@jxsuite/schema/parse";
12
- import { statusMessage } from "../panels/statusbar";
24
+ import { noteDocumentSaved } from "../panels/statusbar";
25
+ import { notify } from "../services/notify";
13
26
  import { validateComponentSlots } from "../services/cem-export";
14
27
  import { getPlatform } from "../platform";
15
28
  import { getGridController } from "../grid/grid-controller";
16
29
  import { activeTab, openTab } from "../workspace/workspace";
17
- import { collabSave } from "../collab/collab-session";
30
+ import { collabReadOnly, collabSave } from "../collab/collab-session";
18
31
  import { flushCanvasEdits } from "../canvas/iframe-host";
19
32
  import {
20
33
  defaultContentFormat,
@@ -29,6 +42,7 @@ import {
29
42
  } from "../format/format-host";
30
43
  import type { StudioFormat } from "../format/format-host";
31
44
  import type { Tab } from "../tabs/tab.js";
45
+ import { mediaTypeEssence } from "@jxsuite/schema/media-type";
32
46
 
33
47
  /**
34
48
  * Parse a format-class source string into document + frontmatter + mode per the format's
@@ -73,7 +87,9 @@ export async function openFile() {
73
87
  ...formats
74
88
  .filter((f) => f.capabilities.parse)
75
89
  .map((f) => ({
76
- accept: { [f.mediaType ?? "text/plain"]: f.extensions },
90
+ // The essence, not the declared type: a File System Access `accept` key is a bare
91
+ // MIME type, and `text/markdown; variant=GFM` is not one.
92
+ accept: { [mediaTypeEssence(f.mediaType) ?? "text/plain"]: f.extensions },
77
93
  description: f.name,
78
94
  })),
79
95
  ];
@@ -102,7 +118,8 @@ export async function openFile() {
102
118
  } else {
103
119
  throw noFormatError(name);
104
120
  }
105
- statusMessage(`Opened ${name}`);
121
+ // Opening a file is stated permanently by the tab strip and the status bar's DOCUMENT
122
+ // Field; it does not need a message that erases itself.
106
123
  };
107
124
 
108
125
  if ("showOpenFilePicker" in window) {
@@ -128,37 +145,67 @@ export async function openFile() {
128
145
  input.click();
129
146
  }
130
147
  } catch (error) {
148
+ // A cancelled file picker is not a failure — the user withdrew the request.
131
149
  if (!(error instanceof Error && error.name === "AbortError")) {
132
- statusMessage(`Error: ${errorMessage(error)}`);
150
+ notify.error("Could not open the file.", {
151
+ detail: errorMessage(error),
152
+ source: "Open File",
153
+ });
133
154
  }
134
155
  }
135
156
  }
136
157
 
137
158
  /**
138
- * Report a slot-validation warning for component documents (root tagName with a hyphen). The save
139
- * still proceeds — the warning replaces the plain success message.
159
+ * Record a successful write, and raise the slot-validation warning component documents can carry.
140
160
  *
141
- * @param {Tab} tab
142
- * @param {string} savedMsg
161
+ * The success half no longer notifies at ALL. "Saved" is ambient state: the status bar's DOCUMENT
162
+ * field says "Saved 2m ago" for as long as it is true, which is strictly more information than a
163
+ * message that said it once and erased itself — and it is the field a reader looks at to ask the
164
+ * question in the first place.
165
+ *
166
+ * The warning half became a PROBLEM. A component whose slots do not line up is a thing to fix, and
167
+ * it was previously shown for six seconds in the same grey as the word "Saved".
143
168
  */
144
- function savedMessage(tab: Tab, savedMsg: string) {
169
+ function reportSaved(tab: Tab) {
170
+ noteDocumentSaved(tab.documentPath);
145
171
  const doc = tab.doc.document;
146
172
  const warning =
147
173
  typeof doc.tagName === "string" && doc.tagName.includes("-")
148
174
  ? validateComponentSlots(doc)
149
175
  : null;
150
176
  if (warning) {
151
- statusMessage(`${savedMsg} — Warning: ${warning}`, 6000);
152
- } else {
153
- statusMessage(savedMsg);
177
+ notify.warn(warning, {
178
+ key: `slots:${tab.documentPath ?? tab.id}`,
179
+ ...(tab.documentPath === null ? {} : { path: tab.documentPath }),
180
+ source: "Components",
181
+ tier: "problem",
182
+ });
154
183
  }
155
184
  }
156
185
 
157
- /** Save the current document back to its source location. */
158
- export async function saveFile() {
159
- const tab = activeTab.value;
186
+ /**
187
+ * Save a document back to its source location. Defaults to the focused tab.
188
+ *
189
+ * Two things here are for the tab strip's Save-on-close, and both are what a caller that must
190
+ * decide something afterwards needs:
191
+ *
192
+ * - `tab` is a PARAMETER. The × on a tab closes that tab, focused or not, and reading
193
+ * `activeTab.value` would have saved a different document than the one being closed.
194
+ * - The return value says whether the work reached disk. Every failure in here is reported to the
195
+ * user and swallowed, which is right for ⌘S and useless to a caller that would otherwise close
196
+ * the tab on top of the failure.
197
+ *
198
+ * **A read-only collaborator has no save target, and that is a refusal rather than a fallback.**
199
+ * Their edits never reached the Y-doc, so there is nothing for the provider to flush; and the local
200
+ * file is the ROOM's file, so writing it here would fork the shared document behind the owner's
201
+ * back. Both "saved" and "written anyway" are wrong answers, so the answer is `false` with the
202
+ * reason said out loud.
203
+ *
204
+ * @returns Whether the document was persisted.
205
+ */
206
+ export async function saveFile(tab: Tab | null = activeTab.value): Promise<boolean> {
160
207
  if (!tab) {
161
- return;
208
+ return false;
162
209
  }
163
210
  // Text reaches the document on an idle tick, so the words still sitting in the caret's block have
164
211
  // To be committed before anything serializes it. This used to be `if (isEditing()) stopEditing()`,
@@ -166,17 +213,40 @@ export async function saveFile() {
166
213
  // Realm's copy of that module, where a session never starts. Saving mid-sentence silently wrote
167
214
  // The file without the sentence.
168
215
  await flushCanvasEdits(tab.id);
216
+ /* THE REFUSAL COMES FIRST, and it used to come second.
217
+ `ensureCollab` attaches a session to every tab with a `documentPath` except `project.json` —
218
+ `.csv` included, and a `.csv` tab is exactly the kind `grid-panel.ts` provisions a controller
219
+ for. So a read-only collaborator on a co-edited sheet took the grid branch above this line,
220
+ `grid.save()` wrote the ROOM's file to disk behind the owner's back, and `!tab.doc.dirty`
221
+ reported it as a save — the precise outcome the paragraph above says this function prevents.
222
+ A refusal that any earlier branch can step in front of is not a refusal, so nothing may
223
+ precede it except `flushCanvasEdits`, which commits the caret's block and writes nothing. */
224
+ if (collabReadOnly(tab)) {
225
+ notify.warn(
226
+ `You have read access to this session — changes to ${tab.documentPath ?? "this document"} ` +
227
+ `stay in your browser and cannot be saved.`,
228
+ {
229
+ action: "file.save",
230
+ key: `save.readOnly:${tab.documentPath ?? tab.id}`,
231
+ ...(tab.documentPath === null ? {} : { path: tab.documentPath }),
232
+ source: "Collaboration",
233
+ },
234
+ );
235
+ return false;
236
+ }
169
237
  // Grid tabs batch-save through their controller (per-source commit semantics, not a doc write).
170
238
  const grid = getGridController(tab);
171
239
  if (grid) {
172
240
  await grid.save();
173
- return;
241
+ // A batch commit keeps failed rows dirty and mirrors that onto the tab, so the buffer's own
242
+ // Verdict is the answer — there is nothing more honest to report here.
243
+ return !tab.doc.dirty;
174
244
  }
175
245
  try {
176
246
  // A co-edited tab persists through its provider (a direct file write would reset the room).
177
247
  if (await collabSave(tab)) {
178
- savedMessage(tab, "Synced");
179
- return;
248
+ reportSaved(tab);
249
+ return true;
180
250
  }
181
251
  const output = await serializeDocument(tab);
182
252
 
@@ -184,8 +254,10 @@ export async function saveFile() {
184
254
  const platform = getPlatform();
185
255
  await platform.writeFile(tab.documentPath, output);
186
256
  tab.doc.dirty = false;
187
- savedMessage(tab, "Saved");
188
- } else if (tab.fileHandle && "createWritable" in tab.fileHandle) {
257
+ reportSaved(tab);
258
+ return true;
259
+ }
260
+ if (tab.fileHandle && "createWritable" in tab.fileHandle) {
189
261
  const writable =
190
262
  await /**
191
263
  * @type {{
@@ -198,15 +270,22 @@ export async function saveFile() {
198
270
  await writable.write(output);
199
271
  await writable.close();
200
272
  tab.doc.dirty = false;
201
- savedMessage(tab, "Saved");
202
- } else {
203
- statusMessage("No save target — use Export");
273
+ reportSaved(tab);
274
+ return true;
204
275
  }
276
+ notify.warn("This document has no save target — use Export.", { key: "save.noTarget" });
205
277
  } catch (error) {
206
278
  if (!(error instanceof Error && error.name === "AbortError")) {
207
- statusMessage(`Save error: ${errorMessage(error)}`);
279
+ notify.error(`Could not save ${tab.documentPath ?? tab.id}.`, {
280
+ action: "file.save",
281
+ detail: errorMessage(error),
282
+ key: `save:${tab.documentPath ?? tab.id}`,
283
+ ...(tab.documentPath === null ? {} : { path: tab.documentPath }),
284
+ source: "Save",
285
+ });
208
286
  }
209
287
  }
288
+ return false;
210
289
  }
211
290
 
212
291
  /** The output format for a tab: its source format, or the default content format. */
@@ -227,7 +306,9 @@ export async function exportFile() {
227
306
  await loadFormats();
228
307
  const format = tabFormat(tab);
229
308
  const output = await serializeDocument(tab);
230
- const mimeType = format ? (format.mediaType ?? "text/plain") : "application/json";
309
+ const mimeType = format
310
+ ? (mediaTypeEssence(format.mediaType) ?? "text/plain")
311
+ : "application/json";
231
312
  const ext = format ? format.extensions[0] : ".json";
232
313
  const description = format ? format.name : "Jx Component";
233
314
  const fallbackName = format ? `content${ext}` : "component.json";
@@ -246,7 +327,8 @@ export async function exportFile() {
246
327
  await writable.write(output);
247
328
  await writable.close();
248
329
  tab.doc.dirty = false;
249
- savedMessage(tab, `Exported as ${handle.name}`);
330
+ reportSaved(tab);
331
+ notify.success(`Exported as ${handle.name}.`);
250
332
  } else {
251
333
  // Fallback: download
252
334
  const blob = new Blob([output], { type: mimeType });
@@ -257,11 +339,15 @@ export async function exportFile() {
257
339
  a.click();
258
340
  URL.revokeObjectURL(url);
259
341
  tab.doc.dirty = false;
260
- savedMessage(tab, "Downloaded");
342
+ reportSaved(tab);
343
+ notify.success(`Downloaded ${fallbackName}.`);
261
344
  }
262
345
  } catch (error) {
263
346
  if (!(error instanceof Error && error.name === "AbortError")) {
264
- statusMessage(`Export error: ${errorMessage(error)}`);
347
+ notify.error("Could not export the document.", {
348
+ detail: errorMessage(error),
349
+ source: "Export",
350
+ });
265
351
  }
266
352
  }
267
353
  }
@@ -303,3 +389,64 @@ export async function serializeDocument(tab: Tab): Promise<string> {
303
389
  }
304
390
  return JSON.stringify(tab.doc.document, null, 2);
305
391
  }
392
+
393
+ // ─── Destructive confirmations ───────────────────────────────────────────────
394
+
395
+ /**
396
+ * The reference sentence a destructive dialog carries, or `nothing` when the host cannot count.
397
+ *
398
+ * The query is awaited BEFORE the dialog opens rather than rendered into it and filled in later: a
399
+ * confirm button that becomes truthful two frames after the user has already pressed it is the same
400
+ * defect as not counting at all.
401
+ *
402
+ * **A media file is asked about through the media index, and that is not a refinement.** The
403
+ * generic sweep compares each authored reference's RESOLVED path to the path it was asked about,
404
+ * and a media file's authored ref usually resolves somewhere else: `public/hero.jpg` is written
405
+ * `/hero.jpg` and resolves to `hero.jpg`; an asset inside a collection whose `source` is not
406
+ * already `content/<type>/` is addressed at its asset mount and resolves under that prefix. Asking
407
+ * the generic index about the file on disk returns a confident zero for both, which is the one
408
+ * answer a delete dialog must never invent. `media-usage.ts` asks every authored form and unions
409
+ * the answers — and reports **unknown** when a lane cannot be counted, rather than totalling the
410
+ * lanes that could.
411
+ *
412
+ * @param path — the file about to be deleted or renamed.
413
+ * @param verb — which way the references go. A rename repairs them; a delete breaks them.
414
+ */
415
+ async function usageLine(path: string, verb: "delete" | "rename") {
416
+ const state = isMediaFile(path) ? await loadMediaUsages(path) : await loadUsages({ path });
417
+ const sentence = usageWarning(state, verb);
418
+ return sentence === null ? nothing : html`<p class="dialog-consequence">${sentence}</p>`;
419
+ }
420
+
421
+ /**
422
+ * Confirm deleting a file, stating what it breaks and what survives.
423
+ *
424
+ * @param file — its display name and project-relative path.
425
+ * @returns Whether the user confirmed.
426
+ */
427
+ export async function confirmFileDelete(file: { name: string; path: string }): Promise<boolean> {
428
+ const consequence = await usageLine(file.path, "delete");
429
+ // `showDialog`'s generic widens to unknown through the confirm wrapper; the dialog only ever
430
+ // Resolves true/false, and Boolean() is the narrowing that says so without a cast.
431
+ return Boolean(
432
+ await showConfirmDialog(
433
+ "Delete File",
434
+ html`<span>Delete <strong>${file.name}</strong>? This cannot be undone.</span>${consequence}`,
435
+ { confirmLabel: "Delete", destructive: true },
436
+ ),
437
+ );
438
+ }
439
+
440
+ /**
441
+ * The explanatory copy above a rename field — where the file's references go when it moves.
442
+ *
443
+ * A rename is not a delete and must not read like one: the refactor pass rewrites every reference
444
+ * it finds, so the honest sentence is "N references will be updated automatically", and the count
445
+ * is there to say how much work is silently being done on the user's behalf.
446
+ *
447
+ * @param path — the file about to be renamed.
448
+ */
449
+ export async function renamePromptMessage(path: string) {
450
+ const consequence = await usageLine(path, "rename");
451
+ return consequence === nothing ? undefined : html`${consequence}`;
452
+ }