@jxsuite/studio 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +21080 -32585
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +7 -0
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +221 -245
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -53
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +30 -1
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +7 -1
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
  363. package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
  364. package/dist/chunks/files-j8a1t06k.js +0 -50
  365. package/dist/chunks/files-j8a1t06k.js.map +0 -9
  366. package/dist/chunks/git-panel-xtq5z81x.js +0 -28
  367. package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
  368. package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
  369. package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
  370. package/dist/chunks/settings-modal-17ttr96g.js +0 -29
  371. package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-0xgs7xrs.js +0 -429
  381. package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
  382. package/dist/chunks/studio-4d8hzcz5.js +0 -3427
  383. package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
  384. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  385. package/dist/chunks/studio-9smfzqhc.js +0 -19
  386. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  387. package/dist/chunks/studio-jm0z592w.js.map +0 -115
  388. package/dist/chunks/studio-m2t4mdg5.js +0 -1854
  389. package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
  390. package/dist/chunks/studio-n4p11dfy.js +0 -853
  391. package/dist/chunks/studio-n4p11dfy.js.map +0 -12
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-sjh1wt1a.js +0 -3744
  394. package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
  395. package/dist/chunks/studio-v7jkjqng.js.map +0 -46
  396. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  397. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  398. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  399. package/dist/chunks/studio-yfcstdzy.js +0 -399
  400. package/dist/chunks/studio-yfcstdzy.js.map +0 -12
  401. package/dist/chunks/studio-zp7jncs8.js +0 -469
  402. package/dist/chunks/studio-zp7jncs8.js.map +0 -14
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -13,18 +13,21 @@
13
13
  */
14
14
  import { effect, onScopeDispose, reactive, toRaw } from "../reactivity";
15
15
  import { getPlatform } from "../platform";
16
+ import { notify } from "../services/notify";
16
17
  import { setHistoryDelegate } from "../tabs/transact";
17
18
  import { isRecentLocal } from "../files/fs-events";
18
- import { statusMessage } from "../panels/statusbar";
19
19
  import { showConfirmDialog } from "../ui/layers";
20
20
  import { showProgressModal } from "../ui/progress-modal";
21
21
  import { errorMessage } from "@jxsuite/schema/parse";
22
22
  import { createEditBuffer } from "./edit-buffer";
23
+ import { cellToText } from "./schema-columns";
23
24
  import type { EditBuffer } from "./edit-buffer";
25
+ import type { GridSortSpec } from "./grid-layout";
24
26
  import type {
25
27
  CommitResult,
26
28
  GridCellValue,
27
29
  GridColumn,
30
+ GridColumnKind,
28
31
  GridEditBatch,
29
32
  GridQuery,
30
33
  GridRow,
@@ -41,6 +44,21 @@ export interface GridControllerState {
41
44
  saving: boolean;
42
45
  error: string | null;
43
46
  query: GridQuery;
47
+ /**
48
+ * The field whose value gathers rows into contiguous groups, or null.
49
+ *
50
+ * Grouping is a ROW ORDER, not a second data set: {@link GridController.effectiveRows} emits the
51
+ * groups back to back in first-appearance order, so every layer above — the engine, the
52
+ * clipboard, Fill Down — sees one list and needs to know nothing about groups.
53
+ */
54
+ grouping: string | null;
55
+ }
56
+
57
+ /** One group of the current grouping, in the order {@link GridController.effectiveRows} emits them. */
58
+ export interface GridGroup {
59
+ /** The group field's text value; `""` for rows where it is empty. */
60
+ value: string;
61
+ count: number;
44
62
  }
45
63
 
46
64
  /** What the engine wrapper exposes back to the controller for out-of-band data changes. */
@@ -60,6 +78,20 @@ export interface GridController {
60
78
  save: () => Promise<void>;
61
79
  /** Query change (connector paging/sorting) — reloads rows. */
62
80
  setQuery: (query: GridQuery) => Promise<void>;
81
+ /**
82
+ * Set (or clear) the row order.
83
+ *
84
+ * ONE sort contract for every source. `GridQuery.orderBy`/`dir` has always been the way a source
85
+ * is asked for an order, and a remote-sorting source still answers it by reloading; a source that
86
+ * does not sort remotely is now sorted here instead of the query being silently ignored, which is
87
+ * what let a saved view record a sort nothing applied. A header click in the engine is a
88
+ * separate, ad-hoc re-sort of what is already loaded and is deliberately not captured by a view.
89
+ */
90
+ setSort: (sort: GridSortSpec | null) => Promise<void>;
91
+ /** Group rows contiguously by a field's value, or ungroup with null. */
92
+ setGrouping: (field: string | null) => void;
93
+ /** The groups the current grouping produces, in emitted order. Empty when ungrouped. */
94
+ groups: () => GridGroup[];
63
95
  /** Plain row objects (committed + pending overlay + pending inserts) for the grid engine. */
64
96
  effectiveRows: () => Record<string, GridCellValue>[];
65
97
  /** Current file text including pending edits, for source-mode display (file grids only). */
@@ -91,6 +123,7 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
91
123
  const state: GridControllerState = reactive({
92
124
  columns: [] as GridColumn[],
93
125
  error: null as string | null,
126
+ grouping: null as string | null,
94
127
  loading: false,
95
128
  query: {} as GridQuery,
96
129
  rows: [] as GridRow[],
@@ -140,6 +173,64 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
140
173
  return { cells, deletes: [], inserts };
141
174
  };
142
175
 
176
+ // ─── Row order: grouping first, then sort ───────────────────────────────────
177
+
178
+ /** Whether a cell has nothing in it. Blanks sort LAST in both directions — a hole is not a value. */
179
+ const isBlank = (value: GridCellValue) =>
180
+ value === null || value === "" || (Array.isArray(value) && value.length === 0);
181
+
182
+ const compareCells = (a: GridCellValue, b: GridCellValue, kind: GridColumnKind | undefined) => {
183
+ if (kind === "number") {
184
+ return (typeof a === "number" ? a : 0) - (typeof b === "number" ? b : 0);
185
+ }
186
+ if (kind === "boolean") {
187
+ return (a === true ? 1 : 0) - (b === true ? 1 : 0);
188
+ }
189
+ return cellToText(a).localeCompare(cellToText(b));
190
+ };
191
+
192
+ /**
193
+ * Committed row keys in display order: grouped, then sorted within each group.
194
+ *
195
+ * Sorting is skipped for a source that sorts remotely — it already answered the query, and
196
+ * re-sorting its answer locally would reorder one PAGE of a result set by a rule the other pages
197
+ * were not subject to. Pending inserts are never in here: a row you just added stays at the
198
+ * bottom where you added it, rather than sorting itself out of sight while you are still typing
199
+ * into it.
200
+ */
201
+ const orderedKeys = (): string[] => {
202
+ let keys = state.rows.map((row) => row.key);
203
+ const { dir, orderBy } = state.query;
204
+ if (orderBy && !source.capabilities.remoteSort) {
205
+ const kind = state.columns.find((column) => column.field === orderBy)?.kind;
206
+ const sign = dir === "desc" ? -1 : 1;
207
+ keys = keys.toSorted((ka, kb) => {
208
+ const a = buffer.effectiveValue(ka, orderBy);
209
+ const b = buffer.effectiveValue(kb, orderBy);
210
+ if (isBlank(a) !== isBlank(b)) {
211
+ return isBlank(a) ? 1 : -1;
212
+ }
213
+ return sign * compareCells(a, b, kind);
214
+ });
215
+ }
216
+ return state.grouping ? [...groupBuckets(keys).values()].flat() : keys;
217
+ };
218
+
219
+ /** Group key → row keys, in first-appearance order. Called only when `state.grouping` is set. */
220
+ const groupBuckets = (keys: string[]): Map<string, string[]> => {
221
+ const buckets = new Map<string, string[]>();
222
+ for (const key of keys) {
223
+ const value = cellToText(buffer.effectiveValue(key, state.grouping!));
224
+ const bucket = buckets.get(value);
225
+ if (bucket) {
226
+ bucket.push(key);
227
+ } else {
228
+ buckets.set(value, [key]);
229
+ }
230
+ }
231
+ return buckets;
232
+ };
233
+
143
234
  let viewBinding: GridViewBinding | null = null;
144
235
  const syncView = () => viewBinding?.refreshData();
145
236
 
@@ -217,13 +308,23 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
217
308
  }
218
309
  return cells;
219
310
  };
220
- const rows = state.rows.map((row) => rowObject(row.key));
311
+ const rows = orderedKeys().map((key) => rowObject(key));
221
312
  for (const [tempKey] of buffer.state.inserts) {
222
313
  rows.push(rowObject(tempKey));
223
314
  }
224
315
  return rows;
225
316
  },
226
317
 
318
+ groups() {
319
+ if (!state.grouping) {
320
+ return [];
321
+ }
322
+ return [...groupBuckets(orderedKeys())].map(([value, keys]) => ({
323
+ count: keys.length,
324
+ value,
325
+ }));
326
+ },
327
+
227
328
  async load() {
228
329
  state.loading = true;
229
330
  state.error = null;
@@ -261,13 +362,17 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
261
362
  const batch = buffer.buildBatch();
262
363
  const changeCount = batch.cells.length + batch.inserts.length + batch.deletes.length;
263
364
  if (changeCount === 0) {
264
- statusMessage("No grid changes to save");
365
+ notify.info("No grid changes to save.", { key: "grid.save" });
265
366
  return;
266
367
  }
267
368
  const violations = requiredViolations(batch);
268
369
  if (violations) {
269
370
  buffer.applyCommitResult(violations);
270
- statusMessage("Required cells are empty — fix the marked rows and save again", 5000);
371
+ notify.warn("Required cells are empty — fix the marked rows, then save again.", {
372
+ key: "grid.save",
373
+ source: "Data",
374
+ tier: "problem",
375
+ });
271
376
  return;
272
377
  }
273
378
  if (batch.deletes.length > 0) {
@@ -316,14 +421,22 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
316
421
  const failed = changeCount - okCount;
317
422
  const staleCount =
318
423
  result.cells.filter((r) => r.stale).length + result.deletes.filter((r) => r.stale).length;
319
- statusMessage(
320
- failed === 0
321
- ? `Saved ${okCount} change(s)`
322
- : `Saved ${okCount} · ${failed} failed${staleCount ? ` (${staleCount} stale)` : ""} — kept pending`,
323
- failed === 0 ? 3000 : 6000,
324
- );
424
+ if (failed === 0) {
425
+ notify.success(`Saved ${okCount} change(s).`, { key: "grid.save" });
426
+ } else {
427
+ // A partial commit leaves rows pending on disk: a state to fix, not a line to read.
428
+ notify.error(
429
+ `Saved ${okCount} · ${failed} failed${staleCount ? ` (${staleCount} stale)` : ""} — kept pending.`,
430
+ { action: "file.save", key: "grid.save", source: "Data" },
431
+ );
432
+ }
325
433
  } catch (error) {
326
- statusMessage(`Save error: ${errorMessage(error)}`, 6000);
434
+ notify.error("Could not save the grid.", {
435
+ action: "file.save",
436
+ detail: errorMessage(error),
437
+ key: "grid.save",
438
+ source: "Data",
439
+ });
327
440
  } finally {
328
441
  state.saving = false;
329
442
  progress?.done();
@@ -351,6 +464,30 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
351
464
  syncView();
352
465
  },
353
466
 
467
+ setGrouping(field: string | null) {
468
+ state.grouping = field;
469
+ syncView();
470
+ },
471
+
472
+ async setSort(sort: GridSortSpec | null) {
473
+ const query: GridQuery = { ...state.query };
474
+ if (sort) {
475
+ query.orderBy = sort.field;
476
+ query.dir = sort.dir;
477
+ } else {
478
+ delete query.orderBy;
479
+ delete query.dir;
480
+ }
481
+ // A local sort is a reordering of rows already in hand: reloading would re-walk a directory
482
+ // Or re-parse a file to receive the same rows back in the same order.
483
+ if (!source.capabilities.remoteSort) {
484
+ state.query = query;
485
+ syncView();
486
+ return;
487
+ }
488
+ await this.setQuery(query);
489
+ },
490
+
354
491
  source,
355
492
  state,
356
493
  tab,
@@ -396,7 +533,10 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
396
533
  if (buffer.isDirty()) {
397
534
  const keys = hitKeys.has("*") ? state.rows.map((row) => row.key) : [...hitKeys];
398
535
  buffer.markStale(keys);
399
- statusMessage("Grid rows changed on disk — marked stale (refresh to reload)", 5000);
536
+ notify.warn("Grid rows changed on disk — marked stale; refresh to reload.", {
537
+ key: "grid.stale",
538
+ source: "Data",
539
+ });
400
540
  syncView();
401
541
  } else {
402
542
  void controller.refresh();
@@ -1,19 +1,71 @@
1
1
  /**
2
- * Grid column-layout persistence — widths and order, per grid id, in localStorage.
2
+ * Grid view state — column layout, filter, sort, grouping — and the NAMED views that snapshot it.
3
3
  *
4
- * Pure besides the storage read/write: applyGridLayout reorders/undoes nothing destructive, it just
5
- * returns a new column-def array with saved widths applied and saved order first (columns unknown
6
- * to the saved layout keep their relative position at the end; saved fields that no longer exist
7
- * are ignored — schemas drift).
4
+ * One store, not two. Column widths and order have persisted per grid id in `localStorage` since
5
+ * the grid shipped; a saved view is the same record with three more facets and a name, written to
6
+ * the same key. The alternative — a second `jx-grid-views:` store beside this one — is how a grid
7
+ * ends up with a column order that a view cannot capture and a view that a resize does not update,
8
+ * which is the class of bug plan §12 P7.2 asks this item to avoid by construction.
9
+ *
10
+ * **The working layout is the truth; a view is a copy of it.** Every control writes the working
11
+ * layout (`saveGridLayout`), so a grid remembers how you left it whether or not you ever name a
12
+ * view. {@link saveViewAs} copies the working layout under a name; {@link applySavedView} copies it
13
+ * back and records which name is applied. That is why {@link activeViewModified} can answer
14
+ * honestly — the applied name is a bookmark, not a mode, and a resize after applying one leaves the
15
+ * two legitimately different rather than silently rewriting the view.
16
+ *
17
+ * **Per collection, for free.** The key is the grid id, and a grid id IS
18
+ * `makeGridTabId({kind:"collection", name})` for a collection, the file path for a CSV, and
19
+ * `grid://redirects` for the redirect table. Nothing here knows what a collection is.
20
+ *
21
+ * Pure besides the storage read/write. {@link applyGridLayout} returns a new column-def array with
22
+ * saved widths applied, hidden fields withheld, and saved order first (columns unknown to the saved
23
+ * layout keep their relative position at the end; saved fields that no longer exist are ignored —
24
+ * schemas drift).
8
25
  */
9
26
 
27
+ /** One sort, as a saved view records it. The grid is single-key sorted; a second key is not saved. */
28
+ export interface GridSortSpec {
29
+ field: string;
30
+ dir: "asc" | "desc";
31
+ }
32
+
33
+ /**
34
+ * The view state of one grid.
35
+ *
36
+ * Every field is optional and `undefined` means "leave this facet alone" on the way into
37
+ * {@link saveGridLayout}. `sort` and `groupBy` clear with an explicit `null`, because "no sort" is a
38
+ * choice a view must be able to record and `undefined` already means something else.
39
+ */
10
40
  export interface GridLayout {
11
41
  /** Column order as field names, leftmost first. */
12
42
  order?: string[];
13
43
  /** Field → width in px. */
14
44
  widths?: Record<string, number>;
45
+ /** Fields withheld from the table. The DATA still loads — only the column is not drawn. */
46
+ hidden?: string[];
47
+ /** Row order, applied by the controller (see `grid-controller.ts`), or null for source order. */
48
+ sort?: GridSortSpec | null;
49
+ /** Field whose value gathers rows into contiguous groups, or null for ungrouped. */
50
+ groupBy?: string | null;
51
+ /** The toolbar's text filter. */
52
+ filter?: string;
53
+ }
54
+
55
+ /** A named snapshot of {@link GridLayout}. The name is its identity within one grid. */
56
+ export interface SavedGridView extends GridLayout {
57
+ name: string;
15
58
  }
16
59
 
60
+ /** What one grid id holds in storage: its working layout, its named views, and which one is on. */
61
+ interface GridLayoutRecord extends GridLayout {
62
+ views?: SavedGridView[];
63
+ active?: string | null;
64
+ }
65
+
66
+ /** The facets a view captures, in a fixed order — the comparison and the copy both walk this. */
67
+ const FACETS = ["order", "widths", "hidden", "sort", "groupBy", "filter"] as const;
68
+
17
69
  const STORAGE_PREFIX = "jx-grid-layout:";
18
70
 
19
71
  function storage(): Storage | null {
@@ -24,50 +76,203 @@ function storage(): Storage | null {
24
76
  }
25
77
  }
26
78
 
27
- /** The saved layout for a grid id, or null. */
28
- export function loadGridLayout(gridId: string): GridLayout | null {
79
+ /** The whole stored record for a grid id, or null. */
80
+ function readRecord(gridId: string): GridLayoutRecord | null {
29
81
  const raw = storage()?.getItem(STORAGE_PREFIX + gridId);
30
82
  if (!raw) {
31
83
  return null;
32
84
  }
33
85
  try {
34
- const parsed = JSON.parse(raw) as GridLayout;
86
+ const parsed = JSON.parse(raw) as GridLayoutRecord;
35
87
  return typeof parsed === "object" && parsed !== null ? parsed : null;
36
88
  } catch {
37
89
  return null;
38
90
  }
39
91
  }
40
92
 
41
- /** Merge a partial layout change into the saved layout for a grid id. */
93
+ function writeRecord(gridId: string, record: GridLayoutRecord): void {
94
+ storage()?.setItem(STORAGE_PREFIX + gridId, JSON.stringify(record));
95
+ }
96
+
97
+ /** Just the view-state facets of a record — what a view copies and what a comparison compares. */
98
+ function facetsOf(source: GridLayout): GridLayout {
99
+ const out: GridLayout = {};
100
+ for (const facet of FACETS) {
101
+ if (source[facet] !== undefined) {
102
+ Object.assign(out, { [facet]: source[facet] });
103
+ }
104
+ }
105
+ return out;
106
+ }
107
+
108
+ /** Comparable rendering of a layout's facets — key order is fixed by {@link FACETS}. */
109
+ function facetKey(layout: GridLayout): string {
110
+ return JSON.stringify(FACETS.map((facet) => layout[facet] ?? null));
111
+ }
112
+
113
+ /** The working layout for a grid id, or null when nothing is stored. */
114
+ export function loadGridLayout(gridId: string): GridLayout | null {
115
+ const record = readRecord(gridId);
116
+ return record ? facetsOf(record) : null;
117
+ }
118
+
119
+ /**
120
+ * Merge a partial view-state change into the working layout for a grid id.
121
+ *
122
+ * Widths MERGE (one column resize must not forget the other nine); every other facet is replaced
123
+ * when present. Named views and the active name are untouched.
124
+ */
42
125
  export function saveGridLayout(gridId: string, change: GridLayout): void {
43
126
  const store = storage();
44
127
  if (!store) {
45
128
  return;
46
129
  }
47
- const current = loadGridLayout(gridId) ?? {};
48
- const next: GridLayout = {
49
- ...((change.order ?? current.order) ? { order: change.order ?? current.order } : {}),
50
- widths: { ...current.widths, ...change.widths },
51
- };
52
- store.setItem(STORAGE_PREFIX + gridId, JSON.stringify(next));
130
+ const record = readRecord(gridId) ?? {};
131
+ const next: GridLayoutRecord = { ...record, widths: { ...record.widths, ...change.widths } };
132
+ for (const facet of FACETS) {
133
+ if (facet !== "widths" && change[facet] !== undefined) {
134
+ Object.assign(next, { [facet]: change[facet] });
135
+ }
136
+ }
137
+ writeRecord(gridId, next);
53
138
  }
54
139
 
55
- /** Drop the saved layout for a grid id. */
140
+ /** Drop everything stored for a grid id — working layout AND its named views. */
56
141
  export function clearGridLayout(gridId: string): void {
57
142
  storage()?.removeItem(STORAGE_PREFIX + gridId);
58
143
  }
59
144
 
60
- /** Apply a saved layout to column defs: saved widths win; saved order leads, unknowns follow. */
145
+ /**
146
+ * Drop the working layout, keeping the named views.
147
+ *
148
+ * The "Reset" verb. Deleting the views too would make Reset a destructive action wearing the label
149
+ * of a harmless one — the author asked to see the grid as it comes, not to lose four saved views.
150
+ */
151
+ export function resetGridLayout(gridId: string): void {
152
+ const record = readRecord(gridId);
153
+ if (!record) {
154
+ return;
155
+ }
156
+ writeRecord(gridId, { active: null, ...(record.views ? { views: record.views } : {}) });
157
+ }
158
+
159
+ // ─── Named views ──────────────────────────────────────────────────────────────
160
+
161
+ /** The named views for a grid id, in save order. */
162
+ export function listSavedViews(gridId: string): SavedGridView[] {
163
+ const views = readRecord(gridId)?.views;
164
+ return Array.isArray(views) ? views.filter((view) => typeof view?.name === "string") : [];
165
+ }
166
+
167
+ /** The name of the view last applied to this grid, or null when the layout is unnamed. */
168
+ export function activeViewName(gridId: string): string | null {
169
+ return readRecord(gridId)?.active ?? null;
170
+ }
171
+
172
+ /**
173
+ * Whether the working layout has drifted from the view whose name it carries.
174
+ *
175
+ * False when no view is applied — an unnamed layout cannot be modified relative to anything. This
176
+ * is what lets the toolbar print "Recent posts •" instead of pretending the applied view already
177
+ * holds a column the author has since hidden.
178
+ */
179
+ export function activeViewModified(gridId: string): boolean {
180
+ const record = readRecord(gridId);
181
+ const name = record?.active;
182
+ if (!record || !name) {
183
+ return false;
184
+ }
185
+ const view = listSavedViews(gridId).find((candidate) => candidate.name === name);
186
+ return view ? facetKey(facetsOf(view)) !== facetKey(facetsOf(record)) : false;
187
+ }
188
+
189
+ /**
190
+ * Snapshot the working layout under a name, replacing a view of the same name.
191
+ *
192
+ * Replacing rather than refusing is what makes this both "Save as…" and "Update": the author who
193
+ * types an existing name means the second one, and a confirmation for overwriting a view you can
194
+ * re-save in one click is chrome for its own sake. Returns null when the name is blank or storage
195
+ * is unavailable — the caller has nothing to select and must not pretend otherwise.
196
+ */
197
+ export function saveViewAs(gridId: string, name: string): SavedGridView | null {
198
+ const trimmed = name.trim();
199
+ const store = storage();
200
+ if (trimmed === "" || !store) {
201
+ return null;
202
+ }
203
+ const record = readRecord(gridId) ?? {};
204
+ const view: SavedGridView = { name: trimmed, ...facetsOf(record) };
205
+ const views = listSavedViews(gridId).filter((candidate) => candidate.name !== trimmed);
206
+ views.push(view);
207
+ writeRecord(gridId, { ...record, active: trimmed, views });
208
+ return view;
209
+ }
210
+
211
+ /**
212
+ * Copy a named view onto the working layout and record it as active.
213
+ *
214
+ * Returns the applied layout so the caller can drive the surfaces (the engine remount, the sort,
215
+ * the grouping) from one value, or null when no view of that name exists.
216
+ */
217
+ export function applySavedView(gridId: string, name: string): GridLayout | null {
218
+ const view = listSavedViews(gridId).find((candidate) => candidate.name === name);
219
+ if (!view || !storage()) {
220
+ return null;
221
+ }
222
+ const record = readRecord(gridId) ?? {};
223
+ const layout = facetsOf(view);
224
+ writeRecord(gridId, { active: view.name, views: record.views ?? [], ...layout });
225
+ return layout;
226
+ }
227
+
228
+ /**
229
+ * Delete a named view. Clears the active name when it was the one deleted. Returns whether it was
230
+ * there.
231
+ */
232
+ export function deleteSavedView(gridId: string, name: string): boolean {
233
+ const record = readRecord(gridId);
234
+ if (!record || !storage()) {
235
+ return false;
236
+ }
237
+ const views = listSavedViews(gridId);
238
+ const kept = views.filter((candidate) => candidate.name !== name);
239
+ if (kept.length === views.length) {
240
+ return false;
241
+ }
242
+ writeRecord(gridId, {
243
+ ...record,
244
+ active: record.active === name ? null : (record.active ?? null),
245
+ views: kept,
246
+ });
247
+ return true;
248
+ }
249
+
250
+ // ─── Applying a layout to column definitions ──────────────────────────────────
251
+
252
+ /**
253
+ * Apply a saved layout to column defs: hidden fields are withheld, saved widths win, saved order
254
+ * leads and unknowns follow.
255
+ *
256
+ * Hidden columns are DROPPED rather than flagged, because the engine wrapper is the only consumer
257
+ * and a column it never receives is one it cannot draw, size or move. The row objects still carry
258
+ * every field, so the toolbar's text filter — which reads the controller's columns, not these defs
259
+ * — keeps matching on a hidden column's value.
260
+ */
61
261
  export function applyGridLayout<
62
262
  T extends { field?: string | undefined; width?: number | undefined },
63
263
  >(defs: T[], layout: GridLayout | null): T[] {
64
264
  if (!layout) {
65
265
  return defs;
66
266
  }
67
- const widened = defs.map((def) => {
267
+ const hidden = new Set(layout.hidden);
268
+ const widened: T[] = [];
269
+ for (const def of defs) {
270
+ if (def.field !== undefined && hidden.has(def.field)) {
271
+ continue;
272
+ }
68
273
  const width = def.field ? layout.widths?.[def.field] : undefined;
69
- return width ? { ...def, width } : def;
70
- });
274
+ widened.push(width ? { ...def, width } : def);
275
+ }
71
276
  if (!layout.order?.length) {
72
277
  return widened;
73
278
  }