@jxsuite/studio 6.1.1 → 6.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/dist/chunks/{2020-va2wwra4.js → 2020-f2gkpy8d.js} +3 -3
  2. package/dist/chunks/{action-group-2mqby73c.js → action-group-4aan7g6g.js} +3 -3
  3. package/dist/chunks/adapter-4912g339.js +4 -0
  4. package/dist/chunks/cf-account-picker-wccaenh6.js +4 -0
  5. package/dist/chunks/cf-settings-br6105tr.js +4 -0
  6. package/dist/chunks/color-28ksk2qm.js +4 -0
  7. package/dist/chunks/color-area-0fj8a4ga.js +4 -0
  8. package/dist/chunks/color-field-xch8kx2g.js +4 -0
  9. package/dist/chunks/color-slider-jxd8v350.js +4 -0
  10. package/dist/chunks/combobox-p0efwjke.js +4 -0
  11. package/dist/chunks/components-db68vsn7.js +4 -0
  12. package/dist/chunks/content-source-1449qesq.js +4 -0
  13. package/dist/chunks/convert-to-repeater-5h457egj.js +4 -0
  14. package/dist/chunks/dialog-c4x05vyx.js +4 -0
  15. package/dist/chunks/editor-x9jn86a7.js +4 -0
  16. package/dist/chunks/entry-commands-may24cpt.js +4 -0
  17. package/dist/chunks/extension-sections-pvr14vxr.js +4 -0
  18. package/dist/chunks/file-ops-x9v54pcj.js +4 -0
  19. package/dist/chunks/files-j3knqf0s.js +4 -0
  20. package/dist/chunks/format-host-tyre2trb.js +4 -0
  21. package/dist/chunks/fs-events-64ma9htc.js +4 -0
  22. package/dist/chunks/git-panel-84ps5dex.js +4 -0
  23. package/dist/chunks/grid-view-d2wq9ezg.js +51 -0
  24. package/dist/chunks/icons-pnfz478q.js +4 -0
  25. package/dist/chunks/iframe-entry-fvbk89gm.js +5 -0
  26. package/dist/chunks/iframe-entry-jdcktez8.js +5 -0
  27. package/dist/chunks/iframe-entry-x0ygf2yr.js +5 -0
  28. package/dist/chunks/iframe-entry-zap785cw.js +5 -0
  29. package/dist/chunks/iframe-host-ck6vejt5.js +4 -0
  30. package/dist/chunks/index-b8xw3vy9.js +4 -0
  31. package/dist/chunks/{index-2veqmnhr.js → index-zpjmzqfk.js} +3 -3
  32. package/dist/chunks/{javascript-8nbvs12s.js → javascript-m54mnwp6.js} +3 -3
  33. package/dist/chunks/{jsonMode-s8kt9dsj.js → jsonMode-w15cmrcm.js} +8 -8
  34. package/dist/chunks/jx-validate-at3zn8wa.js +4 -0
  35. package/dist/chunks/layers-h4xp1pss.js +4 -0
  36. package/dist/chunks/layers-panel-m5hw1d7g.js +4 -0
  37. package/dist/chunks/listbox-e74qjyq6.js +4 -0
  38. package/dist/chunks/lit-html-8t8t3dnr.js +4 -0
  39. package/dist/chunks/media-picker-2cweexc5.js +4 -0
  40. package/dist/chunks/menu-n6yzswmy.js +4 -0
  41. package/dist/chunks/menu-ynw13y0s.js +4 -0
  42. package/dist/chunks/monaco-binding-7gx83x3r.js +4 -0
  43. package/dist/chunks/monaco-buffer-qzxn567r.js +4 -0
  44. package/dist/chunks/monaco-setup-gxf6656g.js +4 -0
  45. package/dist/chunks/notify-cj5zbrs2.js +4 -0
  46. package/dist/chunks/number-field-y62xq0ty.js +4 -0
  47. package/dist/chunks/parse-b7ddhr8m.js +4 -0
  48. package/dist/chunks/platform-3dfws9js.js +4 -0
  49. package/dist/chunks/popover-jt2gxmzs.js +4 -0
  50. package/dist/chunks/publish-panel-1gvxbj21.js +4 -0
  51. package/dist/chunks/quick-search-0dajrwqg.js +4 -0
  52. package/dist/chunks/references-49qgt404.js +4 -0
  53. package/dist/chunks/right-panel-jm1bwe53.js +4 -0
  54. package/dist/chunks/section-registry-6kwc4fky.js +4 -0
  55. package/dist/chunks/select-rzds841e.js +4 -0
  56. package/dist/chunks/split-n2gfpm5m.js +4 -0
  57. package/dist/chunks/{studio-1q1sh2ax.js → studio-0q988f9w.js} +3 -3
  58. package/dist/chunks/studio-0rt52vpq.js +5 -0
  59. package/dist/chunks/studio-18vgbhs5.js +5 -0
  60. package/dist/chunks/{studio-h46vtzk8.js → studio-1fxkaaea.js} +4 -4
  61. package/dist/chunks/studio-24mccdrj.js +5 -0
  62. package/dist/chunks/studio-2eh507zk.js +9 -0
  63. package/dist/chunks/studio-2hcbccfv.js +5 -0
  64. package/dist/chunks/studio-2j1cb95x.js +5 -0
  65. package/dist/chunks/studio-2jbs3rxv.js +5 -0
  66. package/dist/chunks/{studio-rv11kfwa.js → studio-485046te.js} +7 -7
  67. package/dist/chunks/studio-4h8k4dw9.js +5 -0
  68. package/dist/chunks/studio-5f6n18kx.js +5 -0
  69. package/dist/chunks/studio-5gthpky6.js +5 -0
  70. package/dist/chunks/studio-5ve6bbep.js +5 -0
  71. package/dist/chunks/studio-6ep3js6b.js +9 -0
  72. package/dist/chunks/studio-6wkmf3wz.js +9 -0
  73. package/dist/chunks/studio-841rf3cg.js +5 -0
  74. package/dist/chunks/studio-8gmwgheg.js +5 -0
  75. package/dist/chunks/studio-8qm0ep50.js +5 -0
  76. package/dist/chunks/{studio-8grwxtqk.js → studio-90pq9km1.js} +4 -4
  77. package/dist/chunks/studio-924p1tm1.js +6 -0
  78. package/dist/chunks/{studio-pre0skq6.js → studio-a3g98wjr.js} +4 -4
  79. package/dist/chunks/studio-a3shpz9y.js +5 -0
  80. package/dist/chunks/studio-amsbaaar.js +6 -0
  81. package/dist/chunks/studio-at5spjbc.js +7 -0
  82. package/dist/chunks/studio-b2xv945e.js +5 -0
  83. package/dist/chunks/studio-bn5nkafw.js +5 -0
  84. package/dist/chunks/studio-bw6q5xjf.js +9 -0
  85. package/dist/chunks/studio-c7q0t9x1.js +5 -0
  86. package/dist/chunks/studio-camnhc96.js +6 -0
  87. package/dist/chunks/studio-cffqvnzd.js +5 -0
  88. package/dist/chunks/studio-d81xfh1g.js +5 -0
  89. package/dist/chunks/studio-dgz8d0c1.js +5 -0
  90. package/dist/chunks/studio-efvfrsf8.js +5 -0
  91. package/dist/chunks/studio-er30bpcx.js +5 -0
  92. package/dist/chunks/studio-ezscgr64.js +5 -0
  93. package/dist/chunks/studio-ffrrncvz.js +5 -0
  94. package/dist/chunks/studio-fpphgn8y.js +10 -0
  95. package/dist/chunks/studio-j1kvhdjk.js +4 -0
  96. package/dist/chunks/{studio-vrmcjf9g.js → studio-j430n16g.js} +5 -5
  97. package/dist/chunks/studio-jjkg84nj.js +5 -0
  98. package/dist/chunks/studio-m0q32ars.js +4 -0
  99. package/dist/chunks/studio-m6bdst11.js +5 -0
  100. package/dist/chunks/studio-nv2cptd4.js +64 -0
  101. package/dist/chunks/studio-nwfrc98n.js +5 -0
  102. package/dist/chunks/studio-p1wgvp7r.js +5 -0
  103. package/dist/chunks/studio-pag2wncm.js +5 -0
  104. package/dist/chunks/studio-qvanvzfd.js +5 -0
  105. package/dist/chunks/studio-qwa64kt7.js +5 -0
  106. package/dist/chunks/studio-rk1zc4x3.js +928 -0
  107. package/dist/chunks/studio-se2r3ptc.js +5 -0
  108. package/dist/chunks/studio-sg2a0dh0.js +5 -0
  109. package/dist/chunks/studio-tjzjhe68.js +5 -0
  110. package/dist/chunks/{studio-wszjyh0j.js → studio-tq0rtfwj.js} +4 -4
  111. package/dist/chunks/{studio-kggey1jt.js → studio-v0jzqdjm.js} +4 -4
  112. package/dist/chunks/studio-v2w7q8r1.js +7 -0
  113. package/dist/chunks/studio-v5vjq1h0.js +4 -0
  114. package/dist/chunks/studio-v8aga3vp.js +14 -0
  115. package/dist/chunks/studio-vfcdrfmv.js +5 -0
  116. package/dist/chunks/studio-vmfkgdjf.js +5 -0
  117. package/dist/chunks/studio-wt1816jj.js +5 -0
  118. package/dist/chunks/studio-wz73q5kq.js +4 -0
  119. package/dist/chunks/studio-y04qtxvp.js +7 -0
  120. package/dist/chunks/studio-y21g8zzg.js +5 -0
  121. package/dist/chunks/{studio-zey7zsdw.js → studio-y64hrt8p.js} +7 -7
  122. package/dist/chunks/studio-ynn90f58.js +16 -0
  123. package/dist/chunks/studio-yxz682sx.js +5 -0
  124. package/dist/chunks/studio-zp7snvkq.js +5 -0
  125. package/dist/chunks/studio-zq4z6tm6.js +5 -0
  126. package/dist/chunks/swatch-group-ewh24n1f.js +4 -0
  127. package/dist/chunks/tabs-f5cw3tgp.js +4 -0
  128. package/dist/chunks/textfield-wewc8rq2.js +4 -0
  129. package/dist/chunks/toast-c9fn075p.js +4 -0
  130. package/dist/chunks/toast-host-ke5z4ng7.js +4 -0
  131. package/dist/chunks/{toolbar-2nnzqhrj.js → toolbar-44cm5cta.js} +3 -3
  132. package/dist/chunks/tooltip-5v8ngwj2.js +4 -0
  133. package/dist/chunks/tree-mdq3v5cw.js +4 -0
  134. package/dist/chunks/{tsMode-2frah6h1.js → tsMode-sf49ee9x.js} +7 -7
  135. package/dist/chunks/workspace-jtfahpee.js +4 -0
  136. package/dist/chunks/ws-client-nze0k6mv.js +4 -0
  137. package/dist/iframe-entry.js +16 -16
  138. package/dist/manifest.json +63 -63
  139. package/dist/studio.js +97 -97
  140. package/package.json +6 -6
  141. package/src/about/about-modal.ts +4 -3
  142. package/src/account/action-flow.ts +45 -38
  143. package/src/account-status.ts +7 -2
  144. package/src/browse/library-layouts.ts +8 -5
  145. package/src/browse/library-model.ts +18 -6
  146. package/src/browse/library-pane.ts +37 -21
  147. package/src/browse/library-preview.ts +14 -9
  148. package/src/browse/library-source.ts +1 -1
  149. package/src/canvas/asset-refs.ts +24 -9
  150. package/src/canvas/asset-resolve.ts +19 -23
  151. package/src/canvas/canvas-helpers.ts +7 -6
  152. package/src/canvas/canvas-live-render.ts +44 -53
  153. package/src/canvas/canvas-patcher.ts +30 -30
  154. package/src/canvas/canvas-perf.ts +11 -5
  155. package/src/canvas/canvas-render.ts +62 -41
  156. package/src/canvas/canvas-surface.ts +5 -3
  157. package/src/canvas/canvas-utils.ts +90 -49
  158. package/src/canvas/dialog-path.ts +9 -4
  159. package/src/canvas/dialog-state.ts +1 -2
  160. package/src/canvas/diff-marks.ts +9 -16
  161. package/src/canvas/diff-toolbar.ts +8 -7
  162. package/src/canvas/edit-width-drag.ts +4 -1
  163. package/src/canvas/edit-width.ts +1 -3
  164. package/src/canvas/iframe-channel.ts +27 -13
  165. package/src/canvas/iframe-drop.ts +13 -12
  166. package/src/canvas/iframe-editable-root.ts +29 -16
  167. package/src/canvas/iframe-entry.ts +80 -63
  168. package/src/canvas/iframe-eval.ts +73 -49
  169. package/src/canvas/iframe-host.ts +135 -96
  170. package/src/canvas/iframe-inline-edit.ts +49 -30
  171. package/src/canvas/iframe-insert.ts +5 -4
  172. package/src/canvas/iframe-interaction.ts +30 -25
  173. package/src/canvas/iframe-overlay.ts +1 -1
  174. package/src/canvas/iframe-patch.ts +18 -15
  175. package/src/canvas/iframe-position.ts +12 -6
  176. package/src/canvas/iframe-protocol.ts +12 -16
  177. package/src/canvas/iframe-render.ts +36 -34
  178. package/src/canvas/iframe-slash.ts +24 -11
  179. package/src/canvas/iframe-subtree.ts +6 -2
  180. package/src/canvas/path-mapping.ts +19 -12
  181. package/src/canvas/popover-path.ts +7 -3
  182. package/src/canvas/popover-state.ts +1 -2
  183. package/src/canvas/serialize-scope.ts +13 -5
  184. package/src/canvas/stage-scrollbar.ts +2 -1
  185. package/src/canvas/surface-registry.ts +21 -3
  186. package/src/collab/collab-commands.ts +7 -2
  187. package/src/collab/collab-session.ts +84 -45
  188. package/src/collab/collab-state.ts +4 -3
  189. package/src/collab/monaco-binding.ts +16 -13
  190. package/src/collab/monaco-cursors.ts +5 -2
  191. package/src/commands/budget.ts +3 -3
  192. package/src/commands/command-args.ts +103 -71
  193. package/src/commands/context.ts +22 -20
  194. package/src/commands/keymap.ts +47 -43
  195. package/src/commands/levels.ts +3 -3
  196. package/src/commands/live-context.ts +7 -7
  197. package/src/commands/reference.ts +2 -1
  198. package/src/commands/registry.ts +12 -10
  199. package/src/commands/run-reported.ts +15 -10
  200. package/src/component-props.ts +46 -30
  201. package/src/content/collection-match.ts +5 -4
  202. package/src/content/draft-state.ts +4 -3
  203. package/src/content/entry-commands.ts +3 -2
  204. package/src/content/entry-editor.ts +29 -17
  205. package/src/content/entry-fields.ts +12 -7
  206. package/src/content/entry-model.ts +21 -12
  207. package/src/editor/canvas-context-menu.ts +1 -2
  208. package/src/editor/canvas-slash-bridge.ts +3 -6
  209. package/src/editor/content-slice.ts +16 -14
  210. package/src/editor/context-menu.ts +35 -22
  211. package/src/editor/convert-targets.ts +30 -35
  212. package/src/editor/convert-to-component.ts +11 -5
  213. package/src/editor/convert-to-repeater.ts +34 -22
  214. package/src/editor/editable-tags.ts +17 -2
  215. package/src/editor/file-drop-action.ts +21 -22
  216. package/src/editor/inline-edit-apply.ts +35 -17
  217. package/src/editor/inline-edit.ts +66 -50
  218. package/src/editor/inline-format.ts +59 -56
  219. package/src/editor/inline-link.ts +40 -15
  220. package/src/editor/insert-zone-action.ts +1 -1
  221. package/src/editor/merge-tags.ts +58 -19
  222. package/src/editor/repeater-scope.ts +39 -28
  223. package/src/editor/shortcuts.ts +32 -20
  224. package/src/editor/slash-menu.ts +1 -6
  225. package/src/files/asset-versions.ts +4 -4
  226. package/src/files/components.ts +15 -10
  227. package/src/files/elements.ts +5 -6
  228. package/src/files/file-ops.ts +77 -36
  229. package/src/files/files.ts +90 -95
  230. package/src/files/media-meta.ts +2 -1
  231. package/src/files/media-paths.ts +3 -0
  232. package/src/files/serialize-document.ts +2 -2
  233. package/src/format/constraints.ts +1 -1
  234. package/src/format/convert-file.ts +27 -13
  235. package/src/format/format-choices.ts +2 -1
  236. package/src/format/format-host.ts +76 -50
  237. package/src/github/github-auth-status.ts +27 -3
  238. package/src/github/github-auth.ts +19 -9
  239. package/src/github/github-publish.ts +24 -20
  240. package/src/grid/cell-editors.ts +46 -31
  241. package/src/grid/cell-popovers.ts +5 -3
  242. package/src/grid/edit-buffer.ts +10 -9
  243. package/src/grid/grid-controller.ts +20 -22
  244. package/src/grid/grid-layout.ts +21 -5
  245. package/src/grid/grid-open.ts +3 -6
  246. package/src/grid/grid-panel.ts +11 -4
  247. package/src/grid/grid-source.ts +8 -8
  248. package/src/grid/grid-view.ts +14 -17
  249. package/src/grid/redirects-grid.ts +14 -5
  250. package/src/grid/redirects.ts +16 -14
  251. package/src/grid/schema-columns.ts +25 -19
  252. package/src/grid/sources/connector-source.ts +47 -20
  253. package/src/grid/sources/content-source.ts +25 -24
  254. package/src/grid/sources/csv-file-source.ts +2 -2
  255. package/src/grid/tabulator-tables.d.ts +55 -34
  256. package/src/hosting/stage.ts +7 -7
  257. package/src/i18n/i18n-commands.ts +1 -2
  258. package/src/media/media-pane.ts +4 -1
  259. package/src/new-project/add-repo-modal.ts +20 -19
  260. package/src/new-project/import-tab.ts +10 -14
  261. package/src/new-project/location-fields.ts +2 -1
  262. package/src/new-project/new-project-modal.ts +12 -9
  263. package/src/packages/jxsuite-update.ts +4 -3
  264. package/src/packages/pull-package-sync.ts +26 -25
  265. package/src/page-params.ts +97 -68
  266. package/src/panels/activity-panel.ts +17 -9
  267. package/src/panels/ai-chat/attached-context.ts +13 -7
  268. package/src/panels/ai-chat/chat-view.ts +80 -73
  269. package/src/panels/ai-chat/composer.ts +30 -33
  270. package/src/panels/ai-chat/sessions-view.ts +1 -2
  271. package/src/panels/ai-panel.ts +23 -20
  272. package/src/panels/block-action-bar.ts +46 -35
  273. package/src/panels/bottom-dock.ts +5 -3
  274. package/src/panels/canvas-dnd-bridge.ts +31 -24
  275. package/src/panels/data-explorer.ts +62 -25
  276. package/src/panels/data-grid.ts +14 -11
  277. package/src/panels/dnd.ts +108 -118
  278. package/src/panels/editors.ts +52 -21
  279. package/src/panels/elements-panel.ts +10 -3
  280. package/src/panels/events-panel.ts +84 -69
  281. package/src/panels/formula-workspace.ts +76 -51
  282. package/src/panels/frontmatter-fields.ts +24 -19
  283. package/src/panels/frontmatter-panel.ts +44 -34
  284. package/src/panels/git-diff-open.ts +4 -4
  285. package/src/panels/git-panel.ts +49 -61
  286. package/src/panels/head-panel.ts +131 -111
  287. package/src/panels/i18n-panel.ts +10 -3
  288. package/src/panels/imports-panel.ts +23 -47
  289. package/src/panels/jump-bar.ts +48 -29
  290. package/src/panels/layers-panel.ts +97 -74
  291. package/src/panels/left-panel.ts +3 -3
  292. package/src/panels/pane-context.ts +31 -22
  293. package/src/panels/pane-grid.ts +49 -20
  294. package/src/panels/panel-registry.ts +22 -37
  295. package/src/panels/properties-panel.ts +175 -121
  296. package/src/panels/quick-search.ts +53 -31
  297. package/src/panels/right-panel.ts +3 -3
  298. package/src/panels/seo-modal.ts +13 -17
  299. package/src/panels/settings-menu.ts +15 -15
  300. package/src/panels/signals-panel.ts +153 -131
  301. package/src/panels/statement-editor.ts +230 -140
  302. package/src/panels/style-panel.ts +218 -205
  303. package/src/panels/style-utils.ts +114 -51
  304. package/src/panels/stylebook-doc.ts +57 -54
  305. package/src/panels/stylebook-layers-panel.ts +9 -9
  306. package/src/panels/stylebook-panel.ts +13 -17
  307. package/src/panels/tab-drop.ts +27 -11
  308. package/src/panels/tab-strip.ts +36 -26
  309. package/src/platform-errors.ts +57 -43
  310. package/src/platform.ts +58 -30
  311. package/src/platforms/cloud.ts +345 -175
  312. package/src/platforms/default-platform.ts +10 -4
  313. package/src/platforms/devserver.ts +129 -117
  314. package/src/platforms/read-batcher.ts +16 -8
  315. package/src/project-list.ts +2 -1
  316. package/src/publish/deploy-checklist.ts +8 -2
  317. package/src/publish/pages-service.ts +28 -13
  318. package/src/publish/publish-commands.ts +5 -4
  319. package/src/publish/publish-panel.ts +10 -6
  320. package/src/recent-projects.ts +15 -8
  321. package/src/resize-edges.ts +26 -36
  322. package/src/services/a11y-report.ts +26 -19
  323. package/src/services/agent-seed.ts +23 -11
  324. package/src/services/ai-ask.ts +9 -12
  325. package/src/services/ai-command-tools.ts +68 -62
  326. package/src/services/ai-import-tools.ts +80 -67
  327. package/src/services/ai-models.ts +17 -13
  328. package/src/services/ai-project-tools.ts +68 -32
  329. package/src/services/ai-session-store.ts +19 -10
  330. package/src/services/ai-system-prompt.ts +46 -20
  331. package/src/services/ai-tools.ts +71 -27
  332. package/src/services/ai-write-report.ts +20 -12
  333. package/src/services/ai-writes.ts +4 -4
  334. package/src/services/automation.ts +58 -31
  335. package/src/services/cem-export.ts +149 -58
  336. package/src/services/clock.ts +6 -1
  337. package/src/services/code-services.ts +37 -31
  338. package/src/services/context-manager.ts +14 -10
  339. package/src/services/context-resolver.ts +20 -8
  340. package/src/services/data-service.ts +19 -15
  341. package/src/services/directory-picker.ts +18 -12
  342. package/src/services/document-assistant.ts +39 -19
  343. package/src/services/gated-registry.ts +10 -6
  344. package/src/services/harness/chat-reducer.ts +23 -1
  345. package/src/services/import-client.ts +91 -38
  346. package/src/services/import-seed.ts +8 -2
  347. package/src/services/jx-validate.ts +28 -31
  348. package/src/services/live-preview.ts +0 -0
  349. package/src/services/live-surfaces.ts +6 -3
  350. package/src/services/model-uri.ts +10 -2
  351. package/src/services/module-preloads.ts +12 -8
  352. package/src/services/monaco-buffer.ts +43 -24
  353. package/src/services/monaco-lazy.ts +7 -8
  354. package/src/services/monaco-setup.ts +23 -16
  355. package/src/services/notify.ts +44 -17
  356. package/src/services/popover-report.ts +25 -10
  357. package/src/services/preview-eval.ts +20 -8
  358. package/src/services/profile.ts +13 -6
  359. package/src/services/project-path.ts +13 -10
  360. package/src/services/references.ts +0 -0
  361. package/src/services/render-critic.ts +8 -7
  362. package/src/services/settings/kernel.ts +13 -4
  363. package/src/services/settings/write-queue.ts +9 -9
  364. package/src/services/token-lint.ts +12 -10
  365. package/src/services/tool-executor.ts +17 -5
  366. package/src/services/tool-outcomes.ts +44 -24
  367. package/src/services/trusted-types.ts +24 -7
  368. package/src/settings/contexts-section.ts +11 -8
  369. package/src/settings/contributed-section.ts +79 -60
  370. package/src/settings/css-vars-editor.ts +37 -16
  371. package/src/settings/defs-editor.ts +38 -35
  372. package/src/settings/extension-rows.ts +16 -8
  373. package/src/settings/extension-sections.ts +23 -13
  374. package/src/settings/extensions-section.ts +4 -1
  375. package/src/settings/general-settings.ts +11 -8
  376. package/src/settings/locales-section.ts +20 -12
  377. package/src/settings/preferences-dialog.ts +9 -6
  378. package/src/settings/preferences-keymap.ts +7 -5
  379. package/src/settings/preferences-sections.ts +11 -5
  380. package/src/settings/project-sections.ts +1 -1
  381. package/src/settings/schema-field-ui.ts +9 -0
  382. package/src/settings/settings-document.ts +5 -5
  383. package/src/shell/tree.ts +6 -5
  384. package/src/shell.ts +104 -81
  385. package/src/site-context.ts +37 -22
  386. package/src/state.ts +41 -49
  387. package/src/store.ts +54 -34
  388. package/src/studio.ts +25 -75
  389. package/src/style/live-preview.ts +1 -1
  390. package/src/style/project-styles.ts +6 -7
  391. package/src/style/token-ref.ts +5 -4
  392. package/src/surfaces/about.ts +18 -19
  393. package/src/surfaces/add-repo.ts +17 -19
  394. package/src/surfaces/ai-chat.ts +31 -36
  395. package/src/surfaces/ai-credentials-form.ts +6 -16
  396. package/src/surfaces/ai-managed-connect.ts +3 -4
  397. package/src/surfaces/ai-model-picker.ts +3 -5
  398. package/src/surfaces/block-action-bar.ts +22 -11
  399. package/src/surfaces/boot-failure.ts +4 -5
  400. package/src/surfaces/bottom-dock.ts +9 -18
  401. package/src/surfaces/canvas-stage.ts +12 -11
  402. package/src/surfaces/cf-account-picker.ts +17 -18
  403. package/src/surfaces/commandbar.ts +14 -9
  404. package/src/surfaces/convert-repeater.ts +17 -19
  405. package/src/surfaces/data-actions.ts +9 -7
  406. package/src/surfaces/dialog.ts +26 -28
  407. package/src/surfaces/diff-toolbar.ts +9 -7
  408. package/src/surfaces/doc-header.ts +10 -19
  409. package/src/surfaces/empty-state.ts +7 -10
  410. package/src/surfaces/entry-editor.ts +14 -11
  411. package/src/surfaces/expression-editor.ts +11 -25
  412. package/src/surfaces/files-panel.ts +27 -14
  413. package/src/surfaces/formula-palette.ts +4 -5
  414. package/src/surfaces/git-panel.ts +7 -15
  415. package/src/surfaces/github-auth.ts +16 -20
  416. package/src/surfaces/github-publish.ts +17 -22
  417. package/src/surfaces/grid-cell.ts +30 -31
  418. package/src/surfaces/grid-open.ts +10 -14
  419. package/src/surfaces/grid-panel.ts +13 -9
  420. package/src/surfaces/grid-replace.ts +13 -13
  421. package/src/surfaces/grid-views.ts +15 -18
  422. package/src/surfaces/inspector-dock.ts +13 -12
  423. package/src/surfaces/jump-bar.ts +5 -6
  424. package/src/surfaces/jxsuite-update.ts +16 -22
  425. package/src/surfaces/library-pane.ts +19 -20
  426. package/src/surfaces/logic-panel.ts +17 -30
  427. package/src/surfaces/logic-workspace.ts +13 -15
  428. package/src/surfaces/media-browser.ts +11 -10
  429. package/src/surfaces/media-field.ts +4 -5
  430. package/src/surfaces/media-pane.ts +8 -7
  431. package/src/surfaces/menu.ts +29 -23
  432. package/src/surfaces/navigator-dock.ts +11 -12
  433. package/src/surfaces/new-project.ts +27 -26
  434. package/src/surfaces/pane-context.ts +20 -25
  435. package/src/surfaces/pane-grid.ts +15 -10
  436. package/src/surfaces/panel-activity.ts +5 -6
  437. package/src/surfaces/panel-data.ts +3 -4
  438. package/src/surfaces/panel-deploy-checklist.ts +5 -6
  439. package/src/surfaces/panel-elements.ts +5 -7
  440. package/src/surfaces/panel-i18n.ts +5 -7
  441. package/src/surfaces/panel-imports.ts +6 -13
  442. package/src/surfaces/panel-outline.ts +25 -19
  443. package/src/surfaces/panel-page.ts +11 -18
  444. package/src/surfaces/panel-problems.ts +5 -7
  445. package/src/surfaces/panel-signals.ts +20 -34
  446. package/src/surfaces/panel-stylebook-layers.ts +5 -8
  447. package/src/surfaces/preferences.ts +21 -11
  448. package/src/surfaces/progress-modal.ts +15 -9
  449. package/src/surfaces/properties-panel.json +1 -1
  450. package/src/surfaces/properties-panel.ts +20 -42
  451. package/src/surfaces/publish.ts +18 -18
  452. package/src/surfaces/push-plan.ts +7 -8
  453. package/src/surfaces/rail.ts +13 -13
  454. package/src/surfaces/reference-field.ts +11 -15
  455. package/src/surfaces/schema-builder.ts +16 -22
  456. package/src/surfaces/schema-form.ts +15 -25
  457. package/src/surfaces/secret-field.ts +9 -6
  458. package/src/surfaces/seo.ts +21 -15
  459. package/src/surfaces/settings-contexts.ts +9 -7
  460. package/src/surfaces/settings-contributed.ts +14 -10
  461. package/src/surfaces/settings-css-vars.ts +5 -12
  462. package/src/surfaces/settings-defs.json +2 -2
  463. package/src/surfaces/settings-defs.ts +17 -21
  464. package/src/surfaces/settings-deploy.ts +8 -15
  465. package/src/surfaces/settings-extensions.ts +5 -6
  466. package/src/surfaces/settings-head.ts +33 -39
  467. package/src/surfaces/settings-locales.ts +8 -20
  468. package/src/surfaces/settings-overview.ts +5 -6
  469. package/src/surfaces/settings-packages.ts +8 -7
  470. package/src/surfaces/settings-pane.ts +9 -18
  471. package/src/surfaces/settings-rawjson.ts +5 -6
  472. package/src/surfaces/shell.ts +1 -2
  473. package/src/surfaces/slash-menu.ts +19 -9
  474. package/src/surfaces/statements.ts +13 -18
  475. package/src/surfaces/statusbar.ts +17 -14
  476. package/src/surfaces/style-panel.ts +18 -31
  477. package/src/surfaces/stylebook-chrome.ts +3 -5
  478. package/src/surfaces/tab-strip.ts +14 -13
  479. package/src/surfaces/target-line.ts +6 -7
  480. package/src/surfaces/welcome.ts +5 -7
  481. package/src/tabs/patch-ops.ts +4 -3
  482. package/src/tabs/project-config.ts +32 -21
  483. package/src/tabs/selection.ts +36 -8
  484. package/src/tabs/tab.ts +52 -41
  485. package/src/tabs/transact.ts +98 -53
  486. package/src/types.ts +32 -17
  487. package/src/ui/ai-credentials-form.ts +10 -13
  488. package/src/ui/ai-model-picker.ts +3 -3
  489. package/src/ui/cf-account-picker.ts +7 -9
  490. package/src/ui/color-selector.ts +9 -5
  491. package/src/ui/dynamic-slot.ts +7 -8
  492. package/src/ui/expression-editor.ts +112 -105
  493. package/src/ui/form-controls.ts +57 -48
  494. package/src/ui/formula-catalog.ts +239 -230
  495. package/src/ui/formula-chips.ts +33 -18
  496. package/src/ui/layers.ts +44 -31
  497. package/src/ui/media-picker.ts +18 -9
  498. package/src/ui/popover-surface.ts +20 -12
  499. package/src/ui/progress-modal.ts +13 -8
  500. package/src/ui/regions.ts +7 -6
  501. package/src/ui/schema-form.ts +113 -70
  502. package/src/ui/surface.ts +71 -8
  503. package/src/ui/timing.ts +9 -0
  504. package/src/ui/value-source.ts +44 -31
  505. package/src/ui/virtual-window.ts +3 -2
  506. package/src/utils/base64.ts +3 -1
  507. package/src/utils/edit-display.ts +73 -73
  508. package/src/utils/element-selectors.ts +3 -2
  509. package/src/utils/google-fonts.ts +15 -14
  510. package/src/utils/inherited-style.ts +18 -20
  511. package/src/utils/link-target.ts +1 -1
  512. package/src/utils/preview-format.ts +25 -3
  513. package/src/utils/strip-events.ts +49 -22
  514. package/src/utils/studio-utils.ts +29 -14
  515. package/src/view.ts +2 -3
  516. package/src/workspace/pane-derive.ts +10 -12
  517. package/src/workspace/session.ts +53 -43
  518. package/src/workspace/workspace.ts +40 -36
  519. package/dist/chunks/adapter-f93pafd7.js +0 -4
  520. package/dist/chunks/cf-account-picker-af1h381v.js +0 -4
  521. package/dist/chunks/cf-settings-125tdm0w.js +0 -4
  522. package/dist/chunks/color-area-jqhna8zy.js +0 -4
  523. package/dist/chunks/color-field-embq5ctg.js +0 -4
  524. package/dist/chunks/color-fpezvnkt.js +0 -4
  525. package/dist/chunks/color-slider-1qmw7wqy.js +0 -4
  526. package/dist/chunks/combobox-773hdzyj.js +0 -4
  527. package/dist/chunks/components-ax2f1fnn.js +0 -4
  528. package/dist/chunks/content-source-108aqtk1.js +0 -4
  529. package/dist/chunks/convert-to-repeater-ry5rmppr.js +0 -4
  530. package/dist/chunks/dialog-mtm9fq5t.js +0 -4
  531. package/dist/chunks/editor-pfgdmkb0.js +0 -4
  532. package/dist/chunks/entry-commands-t0k1q0kx.js +0 -4
  533. package/dist/chunks/extension-sections-6mwh2hf8.js +0 -4
  534. package/dist/chunks/file-ops-meej5mqf.js +0 -4
  535. package/dist/chunks/files-44vp8a2s.js +0 -4
  536. package/dist/chunks/format-host-7q1petb9.js +0 -4
  537. package/dist/chunks/fs-events-h2mwnxaz.js +0 -4
  538. package/dist/chunks/git-panel-zngbymvd.js +0 -4
  539. package/dist/chunks/grid-view-sx9vynsp.js +0 -51
  540. package/dist/chunks/icons-p44xhjf0.js +0 -4
  541. package/dist/chunks/iframe-entry-f7mqjwpm.js +0 -5
  542. package/dist/chunks/iframe-entry-haw7jkvz.js +0 -5
  543. package/dist/chunks/iframe-entry-z6qrytv3.js +0 -5
  544. package/dist/chunks/iframe-host-qk3ycq87.js +0 -4
  545. package/dist/chunks/index-wkyfjeqz.js +0 -4
  546. package/dist/chunks/jx-validate-psgd7vsx.js +0 -4
  547. package/dist/chunks/layers-panel-1kq2jyfk.js +0 -4
  548. package/dist/chunks/layers-xd92fg8d.js +0 -4
  549. package/dist/chunks/listbox-s8fkmnq8.js +0 -4
  550. package/dist/chunks/lit-html-ac4cwwr4.js +0 -4
  551. package/dist/chunks/media-picker-0svzkdyb.js +0 -4
  552. package/dist/chunks/menu-37d07z9m.js +0 -4
  553. package/dist/chunks/menu-5c55ea3d.js +0 -4
  554. package/dist/chunks/monaco-binding-z6r70pdt.js +0 -4
  555. package/dist/chunks/monaco-buffer-fz6gq5y7.js +0 -4
  556. package/dist/chunks/monaco-setup-q4axkgj7.js +0 -4
  557. package/dist/chunks/notify-0xgtn8q1.js +0 -4
  558. package/dist/chunks/number-field-gw7sq8rn.js +0 -4
  559. package/dist/chunks/parse-897fvqah.js +0 -4
  560. package/dist/chunks/platform-v1qctbe6.js +0 -4
  561. package/dist/chunks/popover-8htra4er.js +0 -4
  562. package/dist/chunks/publish-panel-ym1fmgq5.js +0 -4
  563. package/dist/chunks/quick-search-a54e6t5n.js +0 -4
  564. package/dist/chunks/references-gmqp82fs.js +0 -4
  565. package/dist/chunks/right-panel-v7t9awbk.js +0 -4
  566. package/dist/chunks/section-registry-vhyf3zsr.js +0 -4
  567. package/dist/chunks/select-n3v7gwas.js +0 -4
  568. package/dist/chunks/split-pff8f3ze.js +0 -4
  569. package/dist/chunks/studio-0bhx5hz6.js +0 -5
  570. package/dist/chunks/studio-0k1ddnyy.js +0 -6
  571. package/dist/chunks/studio-1b04pd0j.js +0 -64
  572. package/dist/chunks/studio-4dwbj6gb.js +0 -5
  573. package/dist/chunks/studio-4tanh828.js +0 -5
  574. package/dist/chunks/studio-55h4mstj.js +0 -4
  575. package/dist/chunks/studio-5tnvjkz4.js +0 -5
  576. package/dist/chunks/studio-60dqgb0c.js +0 -5
  577. package/dist/chunks/studio-61dp09qg.js +0 -9
  578. package/dist/chunks/studio-63epcrj2.js +0 -5
  579. package/dist/chunks/studio-74k29dy9.js +0 -5
  580. package/dist/chunks/studio-7vg6jcd3.js +0 -5
  581. package/dist/chunks/studio-7zx31knw.js +0 -9
  582. package/dist/chunks/studio-8bxk97km.js +0 -5
  583. package/dist/chunks/studio-933e30n4.js +0 -5
  584. package/dist/chunks/studio-98wnwhbg.js +0 -5
  585. package/dist/chunks/studio-9mccjwjh.js +0 -14
  586. package/dist/chunks/studio-a2ke1e4t.js +0 -5
  587. package/dist/chunks/studio-e3a2a6v3.js +0 -4
  588. package/dist/chunks/studio-e81q1vb9.js +0 -5
  589. package/dist/chunks/studio-epgrmqqm.js +0 -5
  590. package/dist/chunks/studio-ery42sqr.js +0 -4
  591. package/dist/chunks/studio-g1shz58g.js +0 -5
  592. package/dist/chunks/studio-gf0fz03c.js +0 -5
  593. package/dist/chunks/studio-gjtpe62q.js +0 -6
  594. package/dist/chunks/studio-gkskhg7m.js +0 -5
  595. package/dist/chunks/studio-gx4dnjzd.js +0 -5
  596. package/dist/chunks/studio-j03zga3y.js +0 -9
  597. package/dist/chunks/studio-jhpxeckz.js +0 -5
  598. package/dist/chunks/studio-k01r7ykp.js +0 -5
  599. package/dist/chunks/studio-kk7h30ne.js +0 -7
  600. package/dist/chunks/studio-kt1zqney.js +0 -5
  601. package/dist/chunks/studio-m3cwhvye.js +0 -5
  602. package/dist/chunks/studio-mcayt6hz.js +0 -5
  603. package/dist/chunks/studio-myjcghv3.js +0 -5
  604. package/dist/chunks/studio-n8bxwc3y.js +0 -4
  605. package/dist/chunks/studio-ngm8gvpb.js +0 -5
  606. package/dist/chunks/studio-nj5a01jy.js +0 -928
  607. package/dist/chunks/studio-pcgz8heb.js +0 -5
  608. package/dist/chunks/studio-qcsq0t15.js +0 -5
  609. package/dist/chunks/studio-qkstvve9.js +0 -5
  610. package/dist/chunks/studio-qv1pwd3c.js +0 -5
  611. package/dist/chunks/studio-rc6dvxjb.js +0 -7
  612. package/dist/chunks/studio-s8dmmz66.js +0 -5
  613. package/dist/chunks/studio-sp5xzbj4.js +0 -6
  614. package/dist/chunks/studio-szsjbj0g.js +0 -5
  615. package/dist/chunks/studio-t1xzawhr.js +0 -16
  616. package/dist/chunks/studio-t6b6qe31.js +0 -5
  617. package/dist/chunks/studio-tdcprbqb.js +0 -5
  618. package/dist/chunks/studio-vyc0b4he.js +0 -10
  619. package/dist/chunks/studio-w0cgf6jg.js +0 -5
  620. package/dist/chunks/studio-w3hskzqk.js +0 -5
  621. package/dist/chunks/studio-xgrgcdyh.js +0 -9
  622. package/dist/chunks/studio-xz2yxfcb.js +0 -5
  623. package/dist/chunks/studio-y6n42tgs.js +0 -5
  624. package/dist/chunks/studio-yb560vxr.js +0 -5
  625. package/dist/chunks/studio-yn9f8vtx.js +0 -5
  626. package/dist/chunks/studio-yw05w8f0.js +0 -7
  627. package/dist/chunks/studio-z16sgavc.js +0 -5
  628. package/dist/chunks/studio-zyqn40w4.js +0 -5
  629. package/dist/chunks/swatch-group-8qh9a3bm.js +0 -4
  630. package/dist/chunks/tabs-jwthg40w.js +0 -4
  631. package/dist/chunks/textfield-spy3hc77.js +0 -4
  632. package/dist/chunks/toast-4d35zd64.js +0 -4
  633. package/dist/chunks/toast-host-8sjmfed3.js +0 -4
  634. package/dist/chunks/tooltip-fd6g9xy3.js +0 -4
  635. package/dist/chunks/tree-h19v1jfz.js +0 -4
  636. package/dist/chunks/workspace-zt81g1ms.js +0 -4
  637. package/dist/chunks/ws-client-n2bh4hkf.js +0 -4
@@ -92,15 +92,16 @@ export function createEditBuffer(opts: EditBufferOptions): EditBuffer {
92
92
  const coalesceMs = opts.coalesceMs ?? 800;
93
93
  const now = opts.now ?? (() => Date.now());
94
94
 
95
- const state: EditBufferState = reactive({
96
- deletes: new Set<string>(),
97
- errors: new Map<string, Map<string, string>>(),
98
- inserts: new Map<string, Record<string, GridCellValue>>(),
99
- pending: new Map<string, Map<string, { value: GridCellValue }>>(),
100
- redoStack: [] as BufferOp[],
101
- stale: new Set<string>(),
102
- undoStack: [] as BufferOp[],
103
- }) as EditBufferState;
95
+ const initial: EditBufferState = {
96
+ deletes: new Set(),
97
+ errors: new Map(),
98
+ inserts: new Map(),
99
+ pending: new Map(),
100
+ redoStack: [],
101
+ stale: new Set(),
102
+ undoStack: [],
103
+ };
104
+ const state: EditBufferState = reactive(initial);
104
105
 
105
106
  let groupOps: BufferOp[] | null = null;
106
107
 
@@ -19,6 +19,7 @@ import { isRecentLocal } from "../files/fs-events";
19
19
  import { showConfirmDialog } from "../ui/layers";
20
20
  import { showProgressModal } from "../ui/progress-modal";
21
21
  import { errorMessage } from "@jxsuite/schema/parse";
22
+ import { isNonNullObject, isNumber, isString } from "@jxsuite/schema/guards";
22
23
  import { createEditBuffer } from "./edit-buffer";
23
24
  import { cellToText } from "./schema-columns";
24
25
  import type { EditBuffer } from "./edit-buffer";
@@ -111,25 +112,26 @@ export interface GridController {
111
112
  /** Row-object key carrying the grid row identity through the engine. */
112
113
  export const ROW_KEY_FIELD = "__key";
113
114
 
114
- const _controllers = new WeakMap<object, GridController>();
115
+ const _controllers = new WeakMap<Tab, GridController>();
115
116
 
116
117
  /** The grid controller for a tab, if it is a grid tab. */
117
118
  export function getGridController(tab: Tab | null): GridController | null {
118
- return tab ? (_controllers.get(toRaw(tab as unknown as object)) ?? null) : null;
119
+ return tab ? (_controllers.get(toRaw(tab)) ?? null) : null;
119
120
  }
120
121
 
121
122
  /** Create and register the controller for a grid tab. One per tab; lives in the tab's scope. */
122
123
  export function createGridController(tab: Tab, source: GridSource): GridController {
123
- const state: GridControllerState = reactive({
124
- columns: [] as GridColumn[],
125
- error: null as string | null,
126
- grouping: null as string | null,
124
+ const initial: GridControllerState = {
125
+ columns: [],
126
+ error: null,
127
+ grouping: null,
127
128
  loading: false,
128
- query: {} as GridQuery,
129
- rows: [] as GridRow[],
129
+ query: {},
130
+ rows: [],
130
131
  saving: false,
131
132
  total: 0,
132
- }) as GridControllerState;
133
+ };
134
+ const state: GridControllerState = reactive(initial);
133
135
 
134
136
  let rowByKey = new Map<string, GridRow>();
135
137
  const indexRows = (rows: GridRow[]) => {
@@ -181,7 +183,7 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
181
183
 
182
184
  const compareCells = (a: GridCellValue, b: GridCellValue, kind: GridColumnKind | undefined) => {
183
185
  if (kind === "number") {
184
- return (typeof a === "number" ? a : 0) - (typeof b === "number" ? b : 0);
186
+ return (isNumber(a) ? a : 0) - (isNumber(b) ? b : 0);
185
187
  }
186
188
  if (kind === "boolean") {
187
189
  return (a === true ? 1 : 0) - (b === true ? 1 : 0);
@@ -280,7 +282,7 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
280
282
  }
281
283
  for (const column of textColumns) {
282
284
  const value = buffer.effectiveValue(rowKey, column.field);
283
- if (typeof value === "string" && value.includes(find)) {
285
+ if (isString(value) && value.includes(find)) {
284
286
  buffer.setCell(rowKey, column.field, value.replaceAll(find, replace));
285
287
  changed += 1;
286
288
  }
@@ -296,17 +298,13 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
296
298
  effectiveRows() {
297
299
  // Detach values from reactive proxies — the grid engine must never hold live proxies.
298
300
  const plain = (value: GridCellValue): GridCellValue =>
299
- Array.isArray(value)
300
- ? [...value]
301
- : value !== null && typeof value === "object"
302
- ? { $ref: value.$ref }
303
- : value;
304
- const rowObject = (key: string) => {
305
- const cells: Record<string, GridCellValue> = { [ROW_KEY_FIELD]: key };
301
+ Array.isArray(value) ? [...value] : isNonNullObject(value) ? { $ref: value.$ref } : value;
302
+ const rowObject = (key: string): Record<string, GridCellValue> => {
303
+ const cells: [field: string, value: GridCellValue][] = [[ROW_KEY_FIELD, key]];
306
304
  for (const column of state.columns) {
307
- cells[column.field] = plain(buffer.effectiveValue(key, column.field));
305
+ cells.push([column.field, plain(buffer.effectiveValue(key, column.field))]);
308
306
  }
309
- return cells;
307
+ return Object.fromEntries(cells);
310
308
  };
311
309
  const rows = orderedKeys().map((key) => rowObject(key));
312
310
  for (const [tempKey] of buffer.state.inserts) {
@@ -547,11 +545,11 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
547
545
 
548
546
  onScopeDispose(() => {
549
547
  setHistoryDelegate(tab, null);
550
- _controllers.delete(toRaw(tab as unknown as object));
548
+ _controllers.delete(toRaw(tab));
551
549
  source.dispose?.();
552
550
  });
553
551
  });
554
552
 
555
- _controllers.set(toRaw(tab as unknown as object), controller);
553
+ _controllers.set(toRaw(tab), controller);
556
554
  return controller;
557
555
  }
@@ -23,6 +23,7 @@
23
23
  * layout keep their relative position at the end; saved fields that no longer exist are ignored —
24
24
  * schemas drift).
25
25
  */
26
+ import { isNonNullObject, isString } from "@jxsuite/schema/guards";
26
27
 
27
28
  /** One sort, as a saved view records it. The grid is single-key sorted; a second key is not saved. */
28
29
  export interface GridSortSpec {
@@ -70,7 +71,7 @@ const STORAGE_PREFIX = "jx-grid-layout:";
70
71
 
71
72
  function storage(): Storage | null {
72
73
  try {
73
- return globalThis.localStorage ?? null;
74
+ return typeof localStorage === "undefined" ? null : localStorage;
74
75
  } catch {
75
76
  return null; // Storage disabled (privacy mode) — layouts just don't persist.
76
77
  }
@@ -83,8 +84,11 @@ function readRecord(gridId: string): GridLayoutRecord | null {
83
84
  return null;
84
85
  }
85
86
  try {
86
- const parsed = JSON.parse(raw) as GridLayoutRecord;
87
- return typeof parsed === "object" && parsed !== null ? parsed : null;
87
+ const parsed: unknown = JSON.parse(raw);
88
+ /* SAFETY: this key is written only by `writeRecord`, which stores a serialized
89
+ GridLayoutRecord, and every reader treats each field as possibly absent (`?.`, `??`, and
90
+ `listSavedViews` drops any entry that is not a named view). A non-object is refused here. */
91
+ return isNonNullObject(parsed) ? (parsed as GridLayoutRecord) : null;
88
92
  } catch {
89
93
  return null;
90
94
  }
@@ -153,7 +157,11 @@ export function resetGridLayout(gridId: string): void {
153
157
  if (!record) {
154
158
  return;
155
159
  }
156
- writeRecord(gridId, { active: null, ...(record.views ? { views: record.views } : {}) });
160
+ const next: GridLayoutRecord = { active: null };
161
+ if (record.views) {
162
+ next.views = record.views;
163
+ }
164
+ writeRecord(gridId, next);
157
165
  }
158
166
 
159
167
  // ─── Named views ──────────────────────────────────────────────────────────────
@@ -161,7 +169,15 @@ export function resetGridLayout(gridId: string): void {
161
169
  /** The named views for a grid id, in save order. */
162
170
  export function listSavedViews(gridId: string): SavedGridView[] {
163
171
  const views = readRecord(gridId)?.views;
164
- return Array.isArray(views) ? views.filter((view) => typeof view?.name === "string") : [];
172
+ return Array.isArray(views) ? views.filter((view) => isSavedGridView(view)) : [];
173
+ }
174
+
175
+ /**
176
+ * Whether a stored `views` entry is a named view. Storage outlives the code that wrote it, so an
177
+ * entry without a string `name` (null, a primitive, an older shape) is skipped rather than shown.
178
+ */
179
+ function isSavedGridView(view: unknown): view is SavedGridView {
180
+ return isNonNullObject(view) && "name" in view && isString(view.name);
165
181
  }
166
182
 
167
183
  /** The name of the view last applied to this grid, or null when the layout is unnamed. */
@@ -19,7 +19,7 @@ import {
19
19
  createPagesSource,
20
20
  } from "./sources/content-source";
21
21
  import { createConnectorSource } from "./sources/connector-source";
22
- import { makeGridTabId, parseGridTabId } from "./grid-source";
22
+ import { GRID_STUB_DOCUMENT, makeGridTabId, parseGridTabId } from "./grid-source";
23
23
  import { libraryTabId } from "../browse/library-source";
24
24
  import { argsSchema, optionalStringArg, stringArg, stringProperty } from "../commands/command-args";
25
25
  import type { GridSource } from "./grid-source";
@@ -27,9 +27,6 @@ import type { GridOpenSurfaceHandle, GridSourceGroup } from "../surfaces/grid-op
27
27
  import type { Tab } from "../tabs/tab";
28
28
  import type { AnyCommand, CommandRegistry } from "../commands/registry";
29
29
 
30
- /** Placeholder document for grid tabs — the grid never reads it; save routes to the controller. */
31
- const GRID_STUB_DOCUMENT = { children: [], tagName: "div" };
32
-
33
30
  /**
34
31
  * Open (or activate) a `.csv` file as a grid tab.
35
32
  *
@@ -187,14 +184,14 @@ function sourceGroups(
187
184
  state: "listed",
188
185
  title: "Project",
189
186
  },
190
- ...connections.map((conn) => ({
187
+ ...connections.map((conn): GridSourceGroup => ({
191
188
  emptyMessage: "No tables — push a schema first.",
192
189
  key: `data:${conn.name}`,
193
190
  rows: conn.tables.map((table) => ({
194
191
  id: makeGridTabId({ connection: conn.name, kind: "data", table }),
195
192
  label: table,
196
193
  })),
197
- state: (conn.tables.length === 0 ? "empty" : "listed") as "empty" | "listed",
194
+ state: conn.tables.length === 0 ? "empty" : "listed",
198
195
  title: `Data · ${conn.name}`,
199
196
  })),
200
197
  ];
@@ -56,6 +56,7 @@ import {
56
56
  saveViewAs,
57
57
  } from "./grid-layout";
58
58
  import { argsSchema, stringArg, stringProperty } from "../commands/command-args";
59
+ import type { CommandArgValues } from "../commands/command-args";
59
60
  import type { EffectScope } from "@vue/reactivity";
60
61
  import type { GridController } from "./grid-controller";
61
62
  import type { GridLayout, GridSortSpec } from "./grid-layout";
@@ -69,7 +70,7 @@ import type {
69
70
  GridPanelView,
70
71
  } from "../surfaces/grid-panel";
71
72
  import type { GridViewsSurfaceHandle, GridViewsView } from "../surfaces/grid-views";
72
- import type { GridReplaceSurfaceHandle } from "../surfaces/grid-replace";
73
+ import type { PopoverSurfaceHandle } from "../ui/popover-surface";
73
74
 
74
75
  interface ActiveGridPanel {
75
76
  /** The pane whose stage this grid is drawn on. */
@@ -85,7 +86,7 @@ interface ActiveGridPanel {
85
86
  /** Where that panel was opened, so a re-projection cannot move it back to the origin. */
86
87
  viewsAt: { x: number; y: number };
87
88
  /** Find & replace while it is open. */
88
- replace: GridReplaceSurfaceHandle | null;
89
+ replace: PopoverSurfaceHandle | null;
89
90
  /**
90
91
  * Rebuild the engine over the same controller.
91
92
  *
@@ -692,6 +693,12 @@ function requireSurface(commandId: string) {
692
693
  return surface;
693
694
  }
694
695
 
696
+ /** A saved view, addressed by the grid it was saved for. */
697
+ interface SavedViewRef {
698
+ gridId: string;
699
+ name: string;
700
+ }
701
+
695
702
  /**
696
703
  * Saved views, as commands.
697
704
  *
@@ -703,10 +710,10 @@ function requireSurface(commandId: string) {
703
710
  * @returns {AnyCommand[]}
704
711
  */
705
712
  export function gridViewCommands(): AnyCommand[] {
706
- const viewNameArg = (commandId: string, args: unknown): { gridId: string; name: string } => {
713
+ const viewNameArg = (commandId: string, args: CommandArgValues): SavedViewRef => {
707
714
  const { controller } = requireSurface(commandId);
708
715
  const gridId = controller.source.id;
709
- const name = stringArg(commandId, args as Record<string, unknown>, "name");
716
+ const name = stringArg(commandId, args, "name");
710
717
  const known = listSavedViews(gridId).map((view) => view.name);
711
718
  if (!known.includes(name)) {
712
719
  throw new RangeError(
@@ -6,6 +6,7 @@
6
6
  * pending edits; they never see the grid engine. Grid tabs over non-file sources are "virtual" —
7
7
  * their tab id is a `grid://` URI rather than a document path.
8
8
  */
9
+ import { isNonNullObject } from "@jxsuite/schema/guards";
9
10
  import type { JsonSchema } from "../ui/schema-form";
10
11
 
11
12
  /** A single cell value. JSON-shaped so batches serialize cleanly. */
@@ -126,14 +127,7 @@ export function cellValuesEqual(a: GridCellValue, b: GridCellValue): boolean {
126
127
  if (Array.isArray(a) && Array.isArray(b)) {
127
128
  return a.length === b.length && a.every((v, i) => v === b[i]);
128
129
  }
129
- if (
130
- a !== null &&
131
- b !== null &&
132
- typeof a === "object" &&
133
- typeof b === "object" &&
134
- !Array.isArray(a) &&
135
- !Array.isArray(b)
136
- ) {
130
+ if (isNonNullObject(a) && isNonNullObject(b) && !Array.isArray(a) && !Array.isArray(b)) {
137
131
  return a.$ref === b.$ref;
138
132
  }
139
133
  return false;
@@ -143,6 +137,12 @@ export function cellValuesEqual(a: GridCellValue, b: GridCellValue): boolean {
143
137
 
144
138
  const GRID_SCHEME = "grid://";
145
139
 
140
+ /**
141
+ * Placeholder document for grid tabs: the grid never reads it, and save routes to the controller.
142
+ * Every tab takes its own `structuredClone` of it.
143
+ */
144
+ export const GRID_STUB_DOCUMENT = { children: [], tagName: "div" };
145
+
146
146
  /**
147
147
  * The virtual sources a tab id can name.
148
148
  *
@@ -37,6 +37,7 @@ import type {
37
37
  ColumnComponent,
38
38
  ColumnDefinition,
39
39
  RowComponent,
40
+ RowData,
40
41
  } from "tabulator-tables";
41
42
  import type { GridCellValue, GridColumn } from "./grid-source";
42
43
  import type { GridController } from "./grid-controller";
@@ -89,7 +90,7 @@ function makeHost(className: string): HTMLElement {
89
90
  return host;
90
91
  }
91
92
 
92
- function sorterForColumn(column: GridColumn): ColumnDefinition["sorter"] {
93
+ function sorterForColumn(column: GridColumn): ColumnDefinition<GridCellValue>["sorter"] {
93
94
  switch (column.kind) {
94
95
  case "number": {
95
96
  return "number";
@@ -98,8 +99,7 @@ function sorterForColumn(column: GridColumn): ColumnDefinition["sorter"] {
98
99
  return "boolean";
99
100
  }
100
101
  case "array": {
101
- return (a: unknown, b: unknown) =>
102
- cellToText(a as GridCellValue).localeCompare(cellToText(b as GridCellValue));
102
+ return (a: GridCellValue, b: GridCellValue) => cellToText(a).localeCompare(cellToText(b));
103
103
  }
104
104
  default: {
105
105
  return "string";
@@ -119,9 +119,9 @@ export function createGridView(host: HTMLElement, controller: GridController): G
119
119
  let built = false;
120
120
  let pendingRefresh = false;
121
121
 
122
- const rowKeyOf = (row: RowComponent) => String(row.getData()[ROW_KEY_FIELD] ?? "");
122
+ const rowKeyOf = (row: RowComponent<GridCellValue>) => String(row.getData()[ROW_KEY_FIELD] ?? "");
123
123
 
124
- const paintRow = (row: RowComponent) => {
124
+ const paintRow = (row: RowComponent<GridCellValue>) => {
125
125
  const rowState = buffer.rowState(rowKeyOf(row));
126
126
  const el = row.getElement();
127
127
  el.classList.toggle("jx-grid-row--pending-delete", rowState === "pending-delete");
@@ -129,7 +129,7 @@ export function createGridView(host: HTMLElement, controller: GridController): G
129
129
  el.classList.toggle("jx-grid-row--stale", rowState === "stale");
130
130
  };
131
131
 
132
- const paintCell = (cell: CellComponent) => {
132
+ const paintCell = (cell: CellComponent<GridCellValue>) => {
133
133
  const cellState = buffer.cellState(rowKeyOf(cell.getRow()), cell.getField());
134
134
  const el = cell.getElement();
135
135
  el.classList.toggle("jx-grid-cell--dirty", cellState === "dirty");
@@ -147,10 +147,11 @@ export function createGridView(host: HTMLElement, controller: GridController): G
147
147
  (column.editable || (column.insertOnly === true && buffer.isInsertKey(rowKey))) &&
148
148
  buffer.rowState(rowKey) !== "pending-delete";
149
149
 
150
- const columnDefs: ColumnDefinition[] = columns.map((column) => {
150
+ const columnDefs: ColumnDefinition<GridCellValue>[] = columns.map((column) => {
151
151
  const baseFormatter = formatterForColumn(column, makeHost);
152
152
  return {
153
- editable: (cell: CellComponent) => cellEditable(column, rowKeyOf(cell.getRow())),
153
+ editable: (cell: CellComponent<GridCellValue>) =>
154
+ cellEditable(column, rowKeyOf(cell.getRow())),
154
155
  // Image/reference cells edit through an anchored popover (dblclick), not an editor session.
155
156
  editor: hasPopoverEditor(column) ? undefined : editorForColumn(column, makeHost),
156
157
  field: column.field,
@@ -254,7 +255,7 @@ export function createGridView(host: HTMLElement, controller: GridController): G
254
255
  };
255
256
  const unregisterProbe = registerGridProbe(probe);
256
257
 
257
- table.on("cellEdited", (cell: CellComponent) => {
258
+ table.on("cellEdited", (cell: CellComponent<GridCellValue>) => {
258
259
  if (suppress) {
259
260
  return;
260
261
  }
@@ -267,7 +268,7 @@ export function createGridView(host: HTMLElement, controller: GridController): G
267
268
  const value = coerceCellInput(cell.getValue(), column);
268
269
  buffer.setCell(rowKey, field, value);
269
270
  // Normalize what the table shows to the typed value (e.g. pasted "42" in a number column).
270
- if (!cellValuesEqual(value, cell.getValue() as GridCellValue)) {
271
+ if (!cellValuesEqual(value, cell.getValue())) {
271
272
  suppress = true;
272
273
  try {
273
274
  cell.setValue(value, true);
@@ -287,7 +288,7 @@ export function createGridView(host: HTMLElement, controller: GridController): G
287
288
  saveGridLayout(gridId, { order: ordered.map((c) => c.getField()) });
288
289
  });
289
290
 
290
- table.on("cellDblClick", (_event: unknown, cell: CellComponent) => {
291
+ table.on("cellDblClick", (_event: MouseEvent, cell: CellComponent<GridCellValue>) => {
291
292
  const column = columnByField.get(cell.getField());
292
293
  const rowKey = rowKeyOf(cell.getRow());
293
294
  if (!column || !hasPopoverEditor(column) || !cellEditable(column, rowKey)) {
@@ -390,12 +391,8 @@ export function createGridView(host: HTMLElement, controller: GridController): G
390
391
  return;
391
392
  }
392
393
  const fields = columns.map((column) => column.field);
393
- table.setFilter((data: Record<string, unknown>) =>
394
- fields.some((field) =>
395
- cellToText(data[field] as GridCellValue)
396
- .toLowerCase()
397
- .includes(query),
398
- ),
394
+ table.setFilter((data: RowData<GridCellValue>) =>
395
+ fields.some((field) => cellToText(data[field]).toLowerCase().includes(query)),
399
396
  );
400
397
  },
401
398
  };
@@ -41,6 +41,7 @@ import {
41
41
  validateRedirects,
42
42
  } from "./redirects";
43
43
  import { optionalStringArg, stringProperty } from "../commands/command-args";
44
+ import { GRID_STUB_DOCUMENT } from "./grid-source";
44
45
  import type { RedirectConfig, RedirectRule, RedirectTarget } from "./redirects";
45
46
  import type {
46
47
  CommitResult,
@@ -67,8 +68,10 @@ export const REDIRECTS_TAB_ID = "grid://redirects";
67
68
  /** Who the Problems list attributes a redirect finding to. Also the key it clears them by. */
68
69
  export const REDIRECTS_PROBLEM_SOURCE = "Redirects";
69
70
 
70
- /** Placeholder document for the virtual tab — the grid never reads it; save routes to the source. */
71
- const GRID_STUB_DOCUMENT = { children: [], tagName: "div" };
71
+ /** The one key a redirect commit patches; `undefined` removes `redirects` from project.json. */
72
+ interface RedirectsPatch {
73
+ redirects: RedirectConfig | undefined;
74
+ }
72
75
 
73
76
  /** Extensions the router treats as pages. Anything else under `pages/` is an asset, not a route. */
74
77
  const PAGE_EXTENSIONS = [".json", ".md", ".html"];
@@ -120,7 +123,7 @@ export function redirectRow(rule: RedirectRule, index: number): GridRow {
120
123
 
121
124
  /** The live redirects map, straight off the configuration document. */
122
125
  function currentConfig(): RedirectConfig | undefined {
123
- return projectState?.projectConfig?.redirects as RedirectConfig | undefined;
126
+ return projectState?.projectConfig?.redirects;
124
127
  }
125
128
 
126
129
  /**
@@ -133,7 +136,10 @@ function parseTarget(raw: string): RedirectTarget {
133
136
  return REWRITE;
134
137
  }
135
138
  const value = Number(trimmed);
136
- return Number.isInteger(value) ? (value as RedirectTarget) : (trimmed as RedirectTarget);
139
+ /* SAFETY: deliberately unchecked, per the doc above. Every rule built from cells reaches
140
+ `ruleErrors` in `commit()`, which tests `status` against REDIRECT_TARGETS and refuses the whole
141
+ batch before `configFromRules` can write an unrecognised target to project.json. */
142
+ return (Number.isInteger(value) ? value : trimmed) as RedirectTarget;
137
143
  }
138
144
 
139
145
  /** A cell bag back to a rule. An unrecognised target is refused by {@link ruleErrors}. */
@@ -327,9 +333,12 @@ export function createRedirectsSource(): GridSource {
327
333
  // `undefined` CLEARS the key (see `commitProjectConfig`): an empty table should leave no
328
334
  // `"redirects": {}` behind. The local type spells the undefined out, which
329
335
  // `exactOptionalPropertyTypes` will not let `Partial<ProjectConfig>` do.
330
- const patch: { redirects: RedirectConfig | undefined } = {
336
+ const patch: RedirectsPatch = {
331
337
  redirects: next.length > 0 ? configFromRules(next) : undefined,
332
338
  };
339
+ /* SAFETY: `commitProjectConfig` spreads the patch onto the document and documents an own
340
+ `undefined` as "clear this key"; the only other value is a RedirectConfig, which is
341
+ structurally ProjectConfig["redirects"]. Only the spelling of "absent" differs. */
333
342
  const commit = await commitProjectConfig(patch as Partial<ProjectConfig>);
334
343
  if (!commit.ok) {
335
344
  // `commitProjectConfig` already filed the Problem; repeating it would list one failure
@@ -26,6 +26,7 @@
26
26
  */
27
27
 
28
28
  import { REDIRECT_STATUSES } from "@jxsuite/schema/defs";
29
+ import { isString } from "@jxsuite/schema/guards";
29
30
  import { parseCsv } from "./csv-codec";
30
31
 
31
32
  /** One redirect rule, flattened out of `project.json`'s two accepted spellings. */
@@ -128,17 +129,17 @@ export function routePattern(route: string): string {
128
129
  /** Flatten `project.json`'s redirects map into rules, in declaration order. */
129
130
  export function rulesFromConfig(config: RedirectConfig | undefined): RedirectRule[] {
130
131
  return Object.entries(config ?? {}).map(([source, target]) => {
131
- if (typeof target === "string") {
132
+ if (isString(target)) {
132
133
  return { destination: target, source, status: DEFAULT_REDIRECT_STATUS };
133
134
  }
134
135
  if ("rewrite" in target) {
135
136
  return { destination: target.destination, source, status: REWRITE };
136
137
  }
137
- return {
138
- destination: target.destination,
139
- source,
140
- status: (target.status ?? DEFAULT_REDIRECT_STATUS) as RedirectTarget,
141
- };
138
+ /* SAFETY: project.json's schema declares `status` as `enum: [...REDIRECT_STATUSES]`, the same
139
+ constant RedirectTarget is built from. A hand-edited file that breaks it is still caught:
140
+ every rule passes the grid's `ruleErrors` status check before anything is written back. */
141
+ const status = (target.status ?? DEFAULT_REDIRECT_STATUS) as RedirectTarget;
142
+ return { destination: target.destination, source, status };
142
143
  });
143
144
  }
144
145
 
@@ -151,7 +152,7 @@ export function rulesFromConfig(config: RedirectConfig | undefined): RedirectRul
151
152
  * level down. A later duplicate source wins, which is what a JSON object means anyway; the grid
152
153
  * refuses duplicates before they get here.
153
154
  */
154
- export function configFromRules(rules: readonly RedirectRule[]): RedirectConfig {
155
+ export function configFromRules(rules: readonly RedirectRule[]) {
155
156
  const config: RedirectConfig = {};
156
157
  for (const rule of rules) {
157
158
  if (rule.status === REWRITE) {
@@ -189,11 +190,14 @@ function ruleFor(rules: readonly RedirectRule[], path: string): RedirectRule | u
189
190
  );
190
191
  }
191
192
 
193
+ /** The path a chain of rules walks, and whether it came back to a source it already visited. */
194
+ interface RedirectWalk {
195
+ path: string[];
196
+ looped: boolean;
197
+ }
198
+
192
199
  /** Follow destinations from a rule until they stop resolving. Returns the path walked. */
193
- function walk(
194
- rules: readonly RedirectRule[],
195
- start: RedirectRule,
196
- ): { path: string[]; looped: boolean } {
200
+ function walk(rules: readonly RedirectRule[], start: RedirectRule): RedirectWalk {
197
201
  const path = [start.source];
198
202
  const seen = new Set([normalizePath(start.source)]);
199
203
  let current = start;
@@ -309,9 +313,7 @@ function parseStatus(token: string | undefined): RedirectTarget | null {
309
313
  return REWRITE;
310
314
  }
311
315
  const value = Number(raw);
312
- return (REDIRECT_STATUSES as readonly number[]).includes(value)
313
- ? (value as RedirectTarget)
314
- : null;
316
+ return REDIRECT_STATUSES.find((status) => status === value) ?? null;
315
317
  }
316
318
 
317
319
  /** Netlify/Cloudflare `_redirects`: `source destination [status]`, `#` comments, blank lines. */
@@ -8,6 +8,14 @@
8
8
  * parser extension's `coerceCSVRows` (currency-stripped numbers, `"true"` booleans, comma-split
9
9
  * arrays) so grid edits round-trip identically to the runtime content loader.
10
10
  */
11
+ import {
12
+ isBoolean,
13
+ isJsonObject,
14
+ isNonNullObject,
15
+ isNumber,
16
+ isString,
17
+ } from "@jxsuite/schema/guards";
18
+ import type { ContentTypeSchema, JsonValue } from "@jxsuite/schema/types";
11
19
  import type { GridCellValue, GridColumn, GridColumnKind } from "./grid-source";
12
20
  import type { JsonSchema } from "../ui/schema-form";
13
21
  import { isMediaFormat } from "../utils/studio-utils";
@@ -93,17 +101,17 @@ export interface ColumnsFromSchemaOptions {
93
101
  * required properties in declaration order, then the rest in declaration order.
94
102
  */
95
103
  export function columnsFromSchema(
96
- schema: { properties?: Record<string, unknown>; required?: string[] } | null | undefined,
104
+ schema: ContentTypeSchema | null | undefined,
97
105
  opts: ColumnsFromSchemaOptions = {},
98
106
  ): GridColumn[] {
99
- const props = Object.entries(schema?.properties ?? {}) as [string, PropSchema][];
107
+ const props: [string, PropSchema][] = Object.entries(schema?.properties ?? {});
100
108
  const required = new Set(schema?.required);
101
109
  const readonly = new Set(opts.readonlyFields);
102
110
 
103
111
  const columns = props.map(([field, prop]): GridColumn => {
104
112
  const kind = readonly.has(field) ? "readonly" : kindForProp(prop);
105
113
  const enumLen = Array.isArray(prop.enum)
106
- ? Math.max(...(prop.enum as unknown[]).map((v) => String(v).length), 4)
114
+ ? Math.max(...prop.enum.map((v) => String(v).length), 4)
107
115
  : 0;
108
116
  return {
109
117
  editable: kind !== "readonly",
@@ -144,9 +152,10 @@ export function inferColumnsFromRows(
144
152
  }
145
153
 
146
154
  return fields.map((field): GridColumn => {
147
- const values = sample
148
- .map((row) => row[field])
149
- .filter((v): v is Exclude<GridCellValue, null> => v !== null && v !== undefined && v !== "");
155
+ const values = sample.flatMap((row) => {
156
+ const value = row[field];
157
+ return value === null || value === undefined || value === "" ? [] : [value];
158
+ });
150
159
  const kind = inferKind(values);
151
160
  const maxLen = Math.max(...values.map((v) => cellToText(v).length), field.length, 4);
152
161
  return {
@@ -167,14 +176,11 @@ function inferKind(values: Exclude<GridCellValue, null>[]): GridColumnKind {
167
176
  return "array";
168
177
  }
169
178
  const texts = values.map(String);
170
- if (
171
- values.every((v) => typeof v === "boolean") ||
172
- texts.every((t) => /^(true|false)$/i.test(t))
173
- ) {
179
+ if (values.every((v) => isBoolean(v)) || texts.every((t) => /^(true|false)$/i.test(t))) {
174
180
  return "boolean";
175
181
  }
176
182
  if (
177
- values.every((v) => typeof v === "number") ||
183
+ values.every((v) => isNumber(v)) ||
178
184
  texts.every((t) => t.trim() !== "" && !Number.isNaN(Number(t.replaceAll(/[$€£¥,\s]/g, ""))))
179
185
  ) {
180
186
  return "number";
@@ -194,13 +200,13 @@ function inferKind(values: Exclude<GridCellValue, null>[]): GridColumnKind {
194
200
  * Coerce editor/clipboard input to the column's typed value. Empty input clears to null. Number and
195
201
  * array semantics mirror the parser extension's coerceCSVRows.
196
202
  */
197
- export function coerceCellInput(raw: unknown, col: GridColumn): GridCellValue {
203
+ export function coerceCellInput(raw: JsonValue | undefined, col: GridColumn): GridCellValue {
198
204
  if (raw === null || raw === undefined) {
199
205
  return null;
200
206
  }
201
207
  switch (col.kind) {
202
208
  case "number": {
203
- if (typeof raw === "number") {
209
+ if (isNumber(raw)) {
204
210
  return Number.isNaN(raw) ? null : raw;
205
211
  }
206
212
  const cleaned = String(raw)
@@ -213,7 +219,7 @@ export function coerceCellInput(raw: unknown, col: GridColumn): GridCellValue {
213
219
  return Number.isNaN(n) ? null : n;
214
220
  }
215
221
  case "boolean": {
216
- if (typeof raw === "boolean") {
222
+ if (isBoolean(raw)) {
217
223
  return raw;
218
224
  }
219
225
  return String(raw).trim().toLowerCase() === "true";
@@ -233,9 +239,9 @@ export function coerceCellInput(raw: unknown, col: GridColumn): GridCellValue {
233
239
  case "reference": {
234
240
  // Relationship VALUES are plain target-entry ids ("jane-doe") — the $ref lives on the
235
241
  // Schema property, not in the data (specs/relationships.md; parser resolveContentTypeRefs).
236
- if (typeof raw === "object" && raw !== null && "$ref" in raw) {
237
- const ref = (raw as { $ref: unknown }).$ref;
238
- return typeof ref === "string" && ref !== "" ? ref : null;
242
+ if (isJsonObject(raw) && "$ref" in raw) {
243
+ const ref = raw.$ref;
244
+ return isString(ref) && ref !== "" ? ref : null;
239
245
  }
240
246
  const text = String(raw).trim();
241
247
  return text === "" ? null : text;
@@ -248,14 +254,14 @@ export function coerceCellInput(raw: unknown, col: GridColumn): GridCellValue {
248
254
  }
249
255
 
250
256
  /** Plain-text projection of a cell value — display, clipboard, and CSV share it. */
251
- export function cellToText(value: GridCellValue): string {
257
+ export function cellToText(value: GridCellValue | undefined): string {
252
258
  if (value === null || value === undefined) {
253
259
  return "";
254
260
  }
255
261
  if (Array.isArray(value)) {
256
262
  return value.join(", ");
257
263
  }
258
- if (typeof value === "object") {
264
+ if (isNonNullObject(value)) {
259
265
  return value.$ref;
260
266
  }
261
267
  return String(value);