okengine 0.14.1 → 0.16.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 (285) hide show
  1. package/package.json +3 -4
  2. package/site/content/docs/elements/ai.mdx +2 -1
  3. package/site/content/docs/elements/channel.mdx +1 -0
  4. package/site/content/docs/elements/clock.mdx +3 -0
  5. package/site/content/docs/elements/flow.mdx +9 -0
  6. package/site/content/docs/elements/gate.mdx +80 -22
  7. package/site/content/docs/elements/signal.mdx +4 -4
  8. package/site/content/docs/elements/store.mdx +84 -27
  9. package/site/content/docs/elements/vault.mdx +4 -0
  10. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  11. package/site/content/docs/get-started/index.mdx +5 -0
  12. package/site/content/docs/get-started/introduction.mdx +11 -3
  13. package/site/content/docs/get-started/meta.json +1 -1
  14. package/site/content/docs/get-started/project-structure.mdx +932 -0
  15. package/site/content/docs/reference/cli.md +13 -10
  16. package/site/content/docs/reference/client.mdx +28 -21
  17. package/site/content/docs/reference/fx.mdx +29 -18
  18. package/site/content/docs/reference/security.md +2 -2
  19. package/src/auth/api-key-sql.test.ts +47 -0
  20. package/src/auth/api-key-sql.ts +254 -0
  21. package/src/auth/api-keys.ts +273 -14
  22. package/src/auth/auth.test.ts +56 -0
  23. package/src/auth/bindings.ts +14 -5
  24. package/src/auth/config.ts +5 -5
  25. package/src/auth/cross-plane.ts +10 -3
  26. package/src/auth/index.ts +25 -1
  27. package/src/auth/schema.ts +8 -0
  28. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  29. package/src/cli/ai-setup/apply.ts +32 -1
  30. package/src/cli/attach-host-console.test.ts +40 -2
  31. package/src/cli/attach-host-console.ts +9 -0
  32. package/src/cli/build.test.ts +4 -2
  33. package/src/cli/db-seed.ts +48 -15
  34. package/src/cli/db.test.ts +104 -0
  35. package/src/cli/db.ts +122 -10
  36. package/src/cli/dev-controls.test.ts +20 -0
  37. package/src/cli/dev-controls.ts +13 -0
  38. package/src/cli/dev.test.ts +15 -4
  39. package/src/cli/dev.ts +102 -29
  40. package/src/cli/ensure-drizzle-config.ts +2 -1
  41. package/src/cli/tui/DevLive.tsx +12 -2
  42. package/src/cli/tui/keys.test.ts +2 -1
  43. package/src/cli/tui/keys.ts +3 -2
  44. package/src/compiler/aot.test.ts +1 -1
  45. package/src/compiler/effects-infer.ts +17 -2
  46. package/src/compiler/extract.test.ts +409 -33
  47. package/src/compiler/extract.ts +318 -67
  48. package/src/compiler/flow-path.test.ts +96 -0
  49. package/src/compiler/flow-path.ts +173 -0
  50. package/src/compiler/generate-adopt.test.ts +112 -3
  51. package/src/compiler/generate-adopt.ts +280 -26
  52. package/src/compiler/schema-from-ast.ts +609 -0
  53. package/src/console/server/access.test.ts +116 -8
  54. package/src/console/server/access.ts +70 -23
  55. package/src/console/server/app.ts +1 -0
  56. package/src/console/server/bind.ts +2 -2
  57. package/src/console/server/console.test.ts +3 -0
  58. package/src/console/server/dev-identities.test.ts +127 -0
  59. package/src/console/server/dev-identities.ts +229 -0
  60. package/src/console/server/flows-invoke.test.ts +21 -4
  61. package/src/console/server/flows.ts +233 -98
  62. package/src/console/server/gates.test.ts +24 -0
  63. package/src/console/server/gates.ts +8 -21
  64. package/src/console/server/index.ts +1 -0
  65. package/src/console/server/invoke-user-flow.test.ts +3 -1
  66. package/src/console/server/invoke-user-flow.ts +17 -2
  67. package/src/console/server/iso-at.test.ts +9 -0
  68. package/src/console/server/iso-at.ts +11 -0
  69. package/src/console/server/live.test.ts +26 -1
  70. package/src/console/server/live.ts +4 -2
  71. package/src/console/server/operator-db.ts +2 -0
  72. package/src/console/server/security-headers.ts +6 -3
  73. package/src/console/server/spa-proxy.test.ts +1 -0
  74. package/src/console/server/sql-catalog.ts +147 -7
  75. package/src/console/server/sql-rls.test.ts +16 -0
  76. package/src/console/server/state.ts +37 -55
  77. package/src/console/server/store.test.ts +7 -4
  78. package/src/console/server/store.ts +142 -58
  79. package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
  80. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +1 -0
  81. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
  82. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
  83. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
  84. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
  85. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
  86. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
  87. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  88. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
  89. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
  90. package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
  91. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-Awq7gYdu.js} +1 -1
  92. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
  93. package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
  94. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
  95. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
  96. package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
  97. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  98. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
  99. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
  100. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
  101. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
  102. package/src/console/ui-next/dist/assets/{skeleton-CL_X0GCj.js → skeleton-D-czQJT6.js} +1 -1
  103. package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
  104. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
  105. package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
  106. package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
  107. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-uk4WVboC.js} +1 -1
  108. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
  109. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
  110. package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
  111. package/src/console/ui-next/dist/index.html +12 -10
  112. package/src/console/ui-next/index.html +3 -3
  113. package/src/console/ui-next/src/client.ts +202 -7
  114. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  115. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  116. package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
  117. package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
  118. package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
  119. package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
  120. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  121. package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
  122. package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
  123. package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
  124. package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
  125. package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
  126. package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
  127. package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
  128. package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
  129. package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
  130. package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
  131. package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
  132. package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
  133. package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
  134. package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
  135. package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
  136. package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
  137. package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
  138. package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
  139. package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
  140. package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
  141. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  142. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
  143. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  144. package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
  145. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  146. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  147. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  148. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  149. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  150. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  151. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  152. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  153. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  154. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  155. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  156. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  157. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  158. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  159. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  160. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  161. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  162. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  163. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  164. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  165. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  166. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  167. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  168. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  169. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  170. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  171. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  172. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  173. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  174. package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
  175. package/src/console/ui-next/src/lib/document-meta.ts +2 -1
  176. package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
  177. package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
  178. package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
  179. package/src/console/ui-next/src/lib/shortcut.ts +3 -0
  180. package/src/console/ui-next/src/router.tsx +10 -1
  181. package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
  182. package/src/console/ui-next/ui-next-seed-store.ts +43 -15
  183. package/src/console/xss.gate.test.ts +1 -0
  184. package/src/docker/compose-health.test.ts +27 -0
  185. package/src/docker/compose-health.ts +19 -3
  186. package/src/docker/docker.test.ts +3 -0
  187. package/src/docker/recipes/pgdog.ts +2 -0
  188. package/src/drivers/clock-postgres.ts +13 -9
  189. package/src/drivers/instances-postgres.ts +4 -6
  190. package/src/drivers/journal-postgres.ts +13 -9
  191. package/src/drivers/pg-rls.test.ts +87 -0
  192. package/src/drivers/pg-rls.ts +233 -7
  193. package/src/drivers/pglite.ts +26 -3
  194. package/src/drivers/postgres.test.ts +23 -0
  195. package/src/drivers/postgres.ts +133 -9
  196. package/src/drivers/types.ts +10 -0
  197. package/src/elements/clock/declare.ts +26 -1
  198. package/src/elements/gate/boot.ts +5 -5
  199. package/src/elements/gate/declare.ts +38 -4
  200. package/src/elements/gate/flatten.ts +1 -1
  201. package/src/elements/gate/runtime.ts +1 -1
  202. package/src/elements/store/cache.ts +1 -1
  203. package/src/elements/store/declare.ts +9 -6
  204. package/src/elements/store/emit-drizzle.ts +44 -8
  205. package/src/elements/store/resource.test.ts +1 -1
  206. package/src/elements/store/rls-identity.test.ts +95 -0
  207. package/src/elements/store/rls-identity.ts +116 -0
  208. package/src/elements/store/runtime.ts +18 -0
  209. package/src/elements/store/schema-decl.test.ts +37 -0
  210. package/src/elements/store/schema-decl.ts +260 -41
  211. package/src/elements/store/seed.ts +1 -1
  212. package/src/elements/store/sql-condition.test.ts +12 -0
  213. package/src/elements/store/sql-condition.ts +26 -2
  214. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  215. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  216. package/src/elements/store/sql-select.types.test.ts +71 -0
  217. package/src/elements/store/sql-session.ts +135 -35
  218. package/src/elements/store/upsert-app.test.ts +1 -1
  219. package/src/elements/store.ts +17 -0
  220. package/src/http.ts +20 -3
  221. package/src/index.ts +11 -1
  222. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  223. package/src/kernel/adopt-routes.ts +30 -2
  224. package/src/kernel/api-key-host-persist.test.ts +143 -0
  225. package/src/kernel/app.ts +145 -16
  226. package/src/kernel/auth-resolve.ts +10 -5
  227. package/src/kernel/auto-cache.test.ts +8 -8
  228. package/src/kernel/auto-registry.test.ts +49 -9
  229. package/src/kernel/boot.test.ts +5 -5
  230. package/src/kernel/boot.ts +2 -2
  231. package/src/kernel/effects-stamping.test.ts +3 -3
  232. package/src/kernel/element-registries.ts +12 -5
  233. package/src/kernel/errors.ts +24 -0
  234. package/src/kernel/flow-units.ts +64 -0
  235. package/src/kernel/flow.ts +29 -17
  236. package/src/kernel/fx-auth-keys.test.ts +88 -0
  237. package/src/kernel/fx-auth-keys.ts +198 -0
  238. package/src/kernel/fx.test.ts +17 -1
  239. package/src/kernel/fx.ts +59 -18
  240. package/src/kernel/horizontal-child.ts +4 -4
  241. package/src/kernel/http-path-pending.ts +20 -0
  242. package/src/kernel/http-resource.ts +4 -0
  243. package/src/kernel/http-stream.test.ts +4 -4
  244. package/src/kernel/pipeline-api-key.test.ts +192 -7
  245. package/src/kernel/pipeline.test.ts +2 -2
  246. package/src/kernel/pipeline.ts +4 -2
  247. package/src/kernel/plugin-elements.test.ts +1 -1
  248. package/src/kernel/plugin-needs.test.ts +1 -1
  249. package/src/kernel/ready.test.ts +2 -2
  250. package/src/kernel/resource-mount.test.ts +14 -0
  251. package/src/kernel/run-duration.test.ts +2 -2
  252. package/src/kernel/stamp-http.test.ts +79 -0
  253. package/src/kernel/stamp-http.ts +75 -0
  254. package/src/kernel/triggers.ts +52 -27
  255. package/src/manifest/types.ts +22 -3
  256. package/src/release/build-lib.ts +1 -0
  257. package/src/release/measure.ts +2 -2
  258. package/src/runtime/json-code-block.test.ts +2 -2
  259. package/src/runtime/serve.test.ts +4 -4
  260. package/src/term.test.ts +52 -4
  261. package/src/term.ts +125 -17
  262. package/src/test/reset-element-registries.ts +8 -3
  263. package/src/cli/ask-seed.test.ts +0 -96
  264. package/src/cli/ask-seed.ts +0 -82
  265. package/src/console/ui-next/dist/assets/cache-glyph-BanhLsEY.js +0 -1
  266. package/src/console/ui-next/dist/assets/call-pii-button-bqkxMrJH.js +0 -1
  267. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  268. package/src/console/ui-next/dist/assets/confirm-sheet-DDCRmG62.js +0 -1
  269. package/src/console/ui-next/dist/assets/explorer-empty-HjTnVQoR.js +0 -1
  270. package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
  271. package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
  272. package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
  273. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  274. package/src/console/ui-next/dist/assets/link-DF7SZ9Ek.js +0 -1
  275. package/src/console/ui-next/dist/assets/observability-page-BEZDzyYh.js +0 -4
  276. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  277. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  278. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  279. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  280. package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
  281. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  282. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  283. package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
  284. package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
  285. package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
@@ -1,12 +1,19 @@
1
1
  /**
2
- * Store row detail Sheet — Fields / JSON tabs + fixed destructive footer.
2
+ * Store row detail Sheet — Fields / JSON tabs, in-sheet edit, delete.
3
3
  */
4
4
 
5
5
  import { useEffect, useMemo, useState, type JSX } from "react";
6
- import { Delete02Icon, PencilEdit01Icon } from "@hugeicons/core-free-icons";
6
+ import {
7
+ Cancel01Icon,
8
+ Delete02Icon,
9
+ PencilEdit01Icon,
10
+ Tick02Icon,
11
+ } from "@hugeicons/core-free-icons";
7
12
  import { HugeiconsIcon } from "@hugeicons/react";
8
13
  import { HighlightedJson } from "@/components/highlighted-json";
9
14
  import { Button } from "@/components/ui/button";
15
+ import { Input } from "@/components/ui/input";
16
+ import { SHEET_CONTROL, SheetError } from "@/components/ui/sheet-form.tsx";
10
17
  import {
11
18
  Sheet,
12
19
  SheetContent,
@@ -15,9 +22,15 @@ import {
15
22
  SheetTitle,
16
23
  } from "@/components/ui/sheet";
17
24
  import { cn } from "@/lib/utils";
25
+ import { useStoreEdit } from "../data/use-store-edit.ts";
18
26
  import { RevealCell } from "./reveal-cell.tsx";
19
- import { formatGridCell, type StoreGridModel, type StoreGridRow } from "../lib/grid-model.ts";
20
- import { isStorePiiMask } from "../lib/patch.ts";
27
+ import {
28
+ formatGridCell,
29
+ type StoreGridColumn,
30
+ type StoreGridModel,
31
+ type StoreGridRow,
32
+ } from "../lib/grid-model.ts";
33
+ import { isStorePiiMask, parseStoreCellDraft, sanitizeStorePatch } from "../lib/patch.ts";
21
34
  import { isRtlText } from "../lib/rtl.ts";
22
35
 
23
36
  /** Props for {@link StoreRowDetailSheet}. */
@@ -31,12 +44,13 @@ export interface StoreRowDetailSheetProps {
31
44
  readonly childName: string;
32
45
  readonly tenant?: string | null;
33
46
  readonly masked?: boolean;
34
- readonly onEditRow?: (row: StoreGridRow) => void;
47
+ readonly asGate?: string | null;
48
+ readonly asUserId?: string | null;
35
49
  readonly onDeleteRow?: (row: StoreGridRow) => void;
36
50
  }
37
51
 
38
52
  /**
39
- * Right-side detail for one grid row — Fields (Reveal/edit) and full JSON.
53
+ * Right-side detail for one grid row — Fields (Reveal / edit) and full JSON.
40
54
  *
41
55
  * @param props - Selected row + facet + mutation callbacks
42
56
  */
@@ -50,13 +64,28 @@ export function StoreRowDetailSheet({
50
64
  childName,
51
65
  tenant,
52
66
  masked = false,
53
- onEditRow,
67
+ asGate = null,
68
+ asUserId = null,
54
69
  onDeleteRow,
55
70
  }: StoreRowDetailSheetProps): JSX.Element {
71
+ const edit = useStoreEdit();
56
72
  const [tab, setTab] = useState<"fields" | "json">("fields");
73
+ const [editing, setEditing] = useState(false);
74
+ const [draft, setDraft] = useState<Record<string, string>>({});
75
+ const [error, setError] = useState<string | null>(null);
76
+
77
+ const editableColumns = useMemo(
78
+ () => model.columns.filter((col) => col.editable),
79
+ [model.columns],
80
+ );
81
+ const canEdit =
82
+ model.editable && (facet === "sql" || facet === "kv") && editableColumns.length > 0;
57
83
 
58
84
  useEffect(() => {
59
- if (open) setTab("fields");
85
+ if (!open) return;
86
+ setTab("fields");
87
+ setEditing(false);
88
+ setError(null);
60
89
  }, [open, row]);
61
90
 
62
91
  const json = useMemo(() => {
@@ -70,7 +99,45 @@ export function StoreRowDetailSheet({
70
99
 
71
100
  if (!row) return <></>;
72
101
 
73
- const canEdit = model.editable && (facet === "sql" || facet === "kv") && onEditRow !== undefined;
102
+ const startEdit = (): void => {
103
+ const next: Record<string, string> = {};
104
+ for (const col of editableColumns) {
105
+ next[col.key] = draftFromCell(col.type, row.cells[col.key]);
106
+ }
107
+ setDraft(next);
108
+ setError(null);
109
+ setEditing(true);
110
+ setTab("fields");
111
+ };
112
+
113
+ const save = async (): Promise<void> => {
114
+ setError(null);
115
+ const patch: Record<string, unknown> = {};
116
+ for (const col of editableColumns) {
117
+ const text = draft[col.key] ?? "";
118
+ if (col.pii && text.trim() === "") continue;
119
+ if (text === draftFromCell(col.type, row.cells[col.key])) continue;
120
+ patch[col.key] = parseStoreCellDraft(col.type, text);
121
+ }
122
+ if (Object.keys(patch).length === 0) {
123
+ setEditing(false);
124
+ return;
125
+ }
126
+ try {
127
+ await edit.mutateAsync({
128
+ ref: storeRef,
129
+ ...(facet === "sql" ? { child: childName, id: row.id } : { key: row.id }),
130
+ ...(tenant ? { tenant } : {}),
131
+ ...(asGate ? { asGate } : {}),
132
+ ...(asUserId ? { asUserId } : {}),
133
+ patch: sanitizeStorePatch(patch),
134
+ commit: true,
135
+ });
136
+ setEditing(false);
137
+ } catch (err) {
138
+ setError(err instanceof Error ? err.message : String(err));
139
+ }
140
+ };
74
141
 
75
142
  return (
76
143
  <Sheet open={open} onOpenChange={onOpenChange}>
@@ -79,11 +146,12 @@ export function StoreRowDetailSheet({
79
146
  showOverlay={false}
80
147
  className="gap-0 p-0 sm:max-w-md"
81
148
  data-slot="store-row-detail-sheet"
149
+ data-editing={editing ? "true" : "false"}
82
150
  >
83
151
  <SheetHeader className="gap-2 border-b border-border/50">
84
152
  <div className="flex flex-col gap-0.5 pr-8">
85
153
  <SheetTitle className="text-sm">
86
- Row <span className="font-mono">{row.id}</span>
154
+ {editing ? "Edit" : "Row"} <span className="font-mono">{row.id}</span>
87
155
  </SheetTitle>
88
156
  <SheetDescription className="font-mono text-[11px]">
89
157
  {storeRef}
@@ -106,6 +174,7 @@ export function StoreRowDetailSheet({
106
174
  {model.columns.map((col) => {
107
175
  const value = row.cells[col.key];
108
176
  const maskedPii = col.pii && masked && isStorePiiMask(value);
177
+ const fieldEditable = editing && col.editable;
109
178
  return (
110
179
  <li key={col.key} className="flex items-baseline gap-3 py-1.5">
111
180
  <span className="flex w-32 shrink-0 items-center gap-1.5">
@@ -130,7 +199,30 @@ export function StoreRowDetailSheet({
130
199
  </span>
131
200
  ) : null}
132
201
  </span>
133
- {maskedPii ? (
202
+ {fieldEditable ? (
203
+ col.type === "json" ? (
204
+ <textarea
205
+ value={draft[col.key] ?? ""}
206
+ onChange={(event) =>
207
+ setDraft((prev) => ({ ...prev, [col.key]: event.target.value }))
208
+ }
209
+ aria-label={col.key}
210
+ rows={3}
211
+ className="min-h-16 min-w-0 flex-1 rounded-none border-0 bg-transparent font-mono text-[11px] outline-none"
212
+ />
213
+ ) : (
214
+ <Input
215
+ value={draft[col.key] ?? ""}
216
+ onChange={(event) =>
217
+ setDraft((prev) => ({ ...prev, [col.key]: event.target.value }))
218
+ }
219
+ type={col.type === "integer" || col.type === "number" ? "number" : "text"}
220
+ aria-label={col.key}
221
+ flat
222
+ className={cn(SHEET_CONTROL, "min-w-0 flex-1 px-0 font-mono")}
223
+ />
224
+ )
225
+ ) : maskedPii ? (
134
226
  <RevealCell
135
227
  refName={storeRef}
136
228
  child={childName}
@@ -162,13 +254,42 @@ export function StoreRowDetailSheet({
162
254
  )}
163
255
  </div>
164
256
 
257
+ {error ? <SheetError slot="row-detail-error">{error}</SheetError> : null}
258
+
165
259
  <div className="mt-auto flex items-center justify-between gap-2 border-t border-border/50 px-4 py-2.5">
166
- {canEdit ? (
260
+ {editing ? (
261
+ <div className="flex items-center gap-2">
262
+ <Button
263
+ type="button"
264
+ variant="outline"
265
+ size="sm"
266
+ onClick={() => {
267
+ setEditing(false);
268
+ setError(null);
269
+ }}
270
+ disabled={edit.isPending}
271
+ data-slot="row-detail-cancel"
272
+ >
273
+ <HugeiconsIcon icon={Cancel01Icon} className="size-3.5" aria-hidden />
274
+ Cancel
275
+ </Button>
276
+ <Button
277
+ type="button"
278
+ size="sm"
279
+ onClick={() => void save()}
280
+ disabled={edit.isPending}
281
+ data-slot="row-detail-save"
282
+ >
283
+ <HugeiconsIcon icon={Tick02Icon} className="size-3.5" aria-hidden />
284
+ {edit.isPending ? "Saving…" : "Save"}
285
+ </Button>
286
+ </div>
287
+ ) : canEdit ? (
167
288
  <Button
168
289
  type="button"
169
290
  variant="outline"
170
291
  size="sm"
171
- onClick={() => onEditRow(row)}
292
+ onClick={startEdit}
172
293
  data-slot="row-detail-edit"
173
294
  >
174
295
  <HugeiconsIcon icon={PencilEdit01Icon} className="size-3.5" aria-hidden />
@@ -177,7 +298,7 @@ export function StoreRowDetailSheet({
177
298
  ) : (
178
299
  <span />
179
300
  )}
180
- {onDeleteRow ? (
301
+ {!editing && onDeleteRow ? (
181
302
  <Button
182
303
  type="button"
183
304
  variant="destructive"
@@ -195,6 +316,26 @@ export function StoreRowDetailSheet({
195
316
  );
196
317
  }
197
318
 
319
+ /** Seed / compare an edit draft without turning `null` into the display em dash. */
320
+ function draftFromCell(type: StoreGridColumn["type"], raw: unknown): string {
321
+ if (raw === null || raw === undefined) return "";
322
+ if (isStorePiiMask(raw)) return "";
323
+ if (type === "json") {
324
+ try {
325
+ return JSON.stringify(raw, null, 2);
326
+ } catch {
327
+ return "";
328
+ }
329
+ }
330
+ if (typeof raw === "string") return raw;
331
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
332
+ try {
333
+ return JSON.stringify(raw);
334
+ } catch {
335
+ return "";
336
+ }
337
+ }
338
+
198
339
  function TabButton({
199
340
  active,
200
341
  onClick,
@@ -17,8 +17,6 @@ import {
17
17
  ShieldOff,
18
18
  SourceCodeIcon,
19
19
  TableIcon,
20
- ViewIcon,
21
- ViewOffSlashIcon,
22
20
  ZapIcon,
23
21
  } from "@hugeicons/core-free-icons";
24
22
  import { HugeiconsIcon } from "@hugeicons/react";
@@ -33,7 +31,6 @@ import {
33
31
  EXPLORER_GROUP_ROW_CLASS,
34
32
  EXPLORER_ICON_BUTTON_CLASS,
35
33
  EXPLORER_ICON_CLASS,
36
- EXPLORER_STRIP_TOKEN_ACTIVE_CLASS,
37
34
  EXPLORER_LIST_EMPTY_CLASS,
38
35
  EXPLORER_RAIL_ACTIVE_CLASS,
39
36
  EXPLORER_RAIL_CLASS,
@@ -45,16 +42,6 @@ import {
45
42
  import { ExplorerSearch } from "@/components/explorer/explorer-search.tsx";
46
43
  import { TreeExpandToggle } from "@/components/explorer/tree-expand-toggle.tsx";
47
44
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
48
- import {
49
- DropdownMenu,
50
- DropdownMenuCheckboxItem,
51
- DropdownMenuContent,
52
- DropdownMenuGroup,
53
- DropdownMenuItem,
54
- DropdownMenuLabel,
55
- DropdownMenuSeparator,
56
- DropdownMenuTrigger,
57
- } from "@/components/ui/dropdown-menu";
58
45
  import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
59
46
  import type { ElementHugeIcon } from "@/lib/element-icons.ts";
60
47
  import { cn } from "@/lib/utils";
@@ -65,6 +52,7 @@ import {
65
52
  loadHiddenFacets,
66
53
  saveHiddenFacets,
67
54
  STORE_FACET_SPECS,
55
+ STORE_FACETS,
68
56
  isSingletonStoreLeaf,
69
57
  storeTreeAncestorKeys,
70
58
  storeTreeFacetKey,
@@ -127,7 +115,6 @@ export function StoreTree({
127
115
  const [query, setQuery] = useState("");
128
116
  const [openByKey, setOpenByKey] = useState<Readonly<Record<string, boolean>>>({});
129
117
  const [hiddenFacets, setHiddenFacets] = useState<ReadonlySet<StoreFacet>>(loadHiddenFacets);
130
- const allBands = useMemo(() => bandStoreTree(stores), [stores]);
131
118
  const bands = useMemo(() => bandStoreTree(filterStoreTree(stores, query)), [stores, query]);
132
119
  const visibleBands = useMemo(() => visibleFacetBands(bands, hiddenFacets), [bands, hiddenFacets]);
133
120
  const keys = useMemo(() => storeTreeOpenKeys(visibleBands), [visibleBands]);
@@ -172,15 +159,6 @@ export function StoreTree({
172
159
  });
173
160
  };
174
161
 
175
- const showAllFacets = (): void => {
176
- setHiddenFacets((prev) => {
177
- if (prev.size === 0) return prev;
178
- const next = new Set<StoreFacet>();
179
- saveHiddenFacets(next);
180
- return next;
181
- });
182
- };
183
-
184
162
  const toggleAll = (): void => {
185
163
  const next = !allOpen;
186
164
  setOpenByKey((prev) => {
@@ -201,12 +179,7 @@ export function StoreTree({
201
179
  aria-label="Search stores"
202
180
  data-slot="store-search"
203
181
  />
204
- <FacetVisibilityMenu
205
- bands={allBands}
206
- hiddenFacets={hiddenFacets}
207
- onHiddenChange={setFacetHidden}
208
- onShowAll={showAllFacets}
209
- />
182
+ <FacetIconToggles hiddenFacets={hiddenFacets} onHiddenChange={setFacetHidden} />
210
183
  <TreeExpandToggle
211
184
  allOpen={allOpen}
212
185
  disabled={keys.length === 0}
@@ -229,7 +202,6 @@ export function StoreTree({
229
202
  <FacetBand
230
203
  key={band.facet}
231
204
  band={band}
232
- onHide={() => setFacetHidden(band.facet, true)}
233
205
  open={storeTreeIsOpen(facetKey, openByKey)}
234
206
  onOpenChange={(open) => setKeyOpen(facetKey, open)}
235
207
  storeOpen={(ref) =>
@@ -267,79 +239,53 @@ export function StoreTree({
267
239
  }
268
240
 
269
241
  /**
270
- * Search-bar control to show or hide Store facet bands.
242
+ * Toolbar tokens for the four Store facets — press to show or hide the band.
271
243
  *
272
- * @param props - Present bands + hidden set + toggles
244
+ * @param props - Hidden set + toggle
273
245
  */
274
- function FacetVisibilityMenu({
275
- bands,
246
+ function FacetIconToggles({
276
247
  hiddenFacets,
277
248
  onHiddenChange,
278
- onShowAll,
279
249
  }: {
280
- readonly bands: readonly StoreFacetBand[];
281
250
  readonly hiddenFacets: ReadonlySet<StoreFacet>;
282
251
  readonly onHiddenChange: (facet: StoreFacet, hidden: boolean) => void;
283
- readonly onShowAll: () => void;
284
252
  }): JSX.Element {
285
- const hiddenCount = bands.filter((band) => hiddenFacets.has(band.facet)).length;
286
- const anyHidden = hiddenCount > 0;
287
-
288
253
  return (
289
- <DropdownMenu>
290
- <ToolbarTip label="Show and hide facets" className="flex self-stretch">
291
- <DropdownMenuTrigger
292
- render={(props) => (
254
+ <div
255
+ className="flex h-full shrink-0 items-stretch"
256
+ role="group"
257
+ aria-label="Store facets"
258
+ data-slot="store-tree-facet-toggles"
259
+ >
260
+ {STORE_FACETS.map((facet) => {
261
+ const spec = STORE_FACET_SPECS[facet];
262
+ const visible = !hiddenFacets.has(facet);
263
+ return (
264
+ <ToolbarTip
265
+ key={facet}
266
+ label={visible ? `Hide ${spec.label}` : `Show ${spec.label}`}
267
+ className="flex self-stretch"
268
+ >
293
269
  <button
294
- {...props}
295
270
  type="button"
296
- aria-label="Show and hide facets"
297
- data-slot="store-tree-visibility"
298
- disabled={bands.length === 0}
271
+ aria-label={visible ? `Hide ${spec.label}` : `Show ${spec.label}`}
272
+ aria-pressed={visible}
273
+ data-slot="store-tree-facet-toggle"
274
+ data-facet={facet}
275
+ onClick={() => onHiddenChange(facet, visible)}
299
276
  className={cn(
300
277
  EXPLORER_ICON_BUTTON_CLASS,
301
- anyHidden && EXPLORER_STRIP_TOKEN_ACTIVE_CLASS,
278
+ visible
279
+ ? explorerIconInk(spec.wellClass)
280
+ : "text-muted-foreground/40 hover:text-muted-foreground",
302
281
  )}
303
282
  >
304
- <HugeiconsIcon
305
- icon={anyHidden ? ViewOffSlashIcon : ViewIcon}
306
- className="size-3.5"
307
- aria-hidden
308
- />
283
+ <HugeiconsIcon icon={spec.icon} className={EXPLORER_ICON_CLASS} aria-hidden />
309
284
  </button>
310
- )}
311
- />
312
- </ToolbarTip>
313
- <DropdownMenuContent align="end" className="w-44">
314
- <DropdownMenuGroup>
315
- <DropdownMenuLabel>Facets</DropdownMenuLabel>
316
- {bands.map((band) => {
317
- const spec = STORE_FACET_SPECS[band.facet];
318
- const visible = !hiddenFacets.has(band.facet);
319
- return (
320
- <DropdownMenuCheckboxItem
321
- key={band.facet}
322
- checked={visible}
323
- data-slot="store-tree-visibility-facet"
324
- data-facet={band.facet}
325
- onCheckedChange={(checked) => onHiddenChange(band.facet, checked === false)}
326
- >
327
- <HugeiconsIcon icon={spec.icon} className="size-3.5" aria-hidden />
328
- {spec.label}
329
- </DropdownMenuCheckboxItem>
330
- );
331
- })}
332
- </DropdownMenuGroup>
333
- {anyHidden ? (
334
- <>
335
- <DropdownMenuSeparator />
336
- <DropdownMenuItem data-slot="store-tree-visibility-show-all" onClick={onShowAll}>
337
- Show all
338
- </DropdownMenuItem>
339
- </>
340
- ) : null}
341
- </DropdownMenuContent>
342
- </DropdownMenu>
285
+ </ToolbarTip>
286
+ );
287
+ })}
288
+ </div>
343
289
  );
344
290
  }
345
291
 
@@ -350,7 +296,6 @@ function FacetVisibilityMenu({
350
296
  */
351
297
  function FacetBand({
352
298
  band,
353
- onHide,
354
299
  open,
355
300
  onOpenChange,
356
301
  storeOpen,
@@ -365,7 +310,6 @@ function FacetBand({
365
310
  onOpenPerformance,
366
311
  }: {
367
312
  readonly band: StoreFacetBand;
368
- readonly onHide: () => void;
369
313
  readonly open: boolean;
370
314
  readonly onOpenChange: (open: boolean) => void;
371
315
  readonly storeOpen: (ref: string) => boolean;
@@ -444,7 +388,6 @@ function FacetBand({
444
388
  onOpen={onOpenQuery}
445
389
  />
446
390
  ) : null}
447
- <FacetVisibilityButton label={band.label} onHide={onHide} />
448
391
  <TreeExpandToggle
449
392
  allOpen={storesOpen}
450
393
  disabled={storeRefs.length === 0}
@@ -821,49 +764,6 @@ function PerformanceBandButton({
821
764
  );
822
765
  }
823
766
 
824
- /**
825
- * Hide a facet band from the explorer. Restore it from the search-bar menu.
826
- *
827
- * @param props - Band label + hide
828
- */
829
- function FacetVisibilityButton({
830
- label,
831
- onHide,
832
- }: {
833
- readonly label: string;
834
- readonly onHide: () => void;
835
- }): JSX.Element {
836
- const action = `Hide ${label}`;
837
- return (
838
- <Tooltip>
839
- <TooltipTrigger
840
- render={(props) => (
841
- <button
842
- {...props}
843
- type="button"
844
- aria-label={action}
845
- data-slot="store-facet-visibility"
846
- onClick={(event) => {
847
- event.stopPropagation();
848
- props.onClick?.(event);
849
- onHide();
850
- }}
851
- className={cn(
852
- "inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors",
853
- "hover:bg-muted/80 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:outline-none",
854
- )}
855
- >
856
- <HugeiconsIcon icon={ViewOffSlashIcon} className="size-3.5" aria-hidden />
857
- </button>
858
- )}
859
- />
860
- <TooltipContent side="bottom" className="text-[11px]">
861
- {action}
862
- </TooltipContent>
863
- </Tooltip>
864
- );
865
- }
866
-
867
767
  /**
868
768
  * Chevron well on a facet or store row — reads as the expand control.
869
769
  *
@@ -25,6 +25,7 @@ import {
25
25
  fileNameFromKey,
26
26
  filePreviewMode,
27
27
  formatFilePreviewText,
28
+ previewBlobType,
28
29
  type FileKind,
29
30
  type FilePreviewMode,
30
31
  } from "../lib/files-meta.ts";
@@ -82,7 +83,9 @@ export function FilePreview({
82
83
  const objectUrl = useMemo(() => {
83
84
  if (!object.data || !bytes || object.data.truncated) return null;
84
85
  if (mode !== "image" && mode !== "pdf" && mode !== "video" && mode !== "audio") return null;
85
- const blob = new Blob([bytesToArrayBuffer(bytes)], { type: object.data.contentType });
86
+ const blob = new Blob([bytesToArrayBuffer(bytes)], {
87
+ type: previewBlobType(mode, object.data.contentType),
88
+ });
86
89
  return URL.createObjectURL(blob);
87
90
  }, [object.data, bytes, mode]);
88
91