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,5 +1,5 @@
1
1
  /**
2
- * Keyboard key — shadcn Kbd. Tooltip content uses the inverted ink.
2
+ * Keyboard key — shadcn Kbd. Buttons inherit ink; tooltips invert.
3
3
  */
4
4
 
5
5
  import type { ComponentProps, JSX } from "react";
@@ -15,7 +15,7 @@ function Kbd({ className, ...props }: ComponentProps<"kbd">): JSX.Element {
15
15
  <kbd
16
16
  data-slot="kbd"
17
17
  className={cn(
18
- "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-none bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
18
+ "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-none bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-[.bg-primary]:bg-primary-foreground/20 in-[.bg-primary]:text-primary-foreground in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
19
19
  className,
20
20
  )}
21
21
  {...props}
@@ -2,8 +2,10 @@
2
2
  * Flat, full-bleed form primitives for Console sheets.
3
3
  */
4
4
 
5
+ import { HugeiconsIcon } from "@hugeicons/react";
5
6
  import { Children, type ComponentProps, type JSX, type ReactNode } from "react";
6
7
  import { Button } from "@/components/ui/button";
8
+ import type { ElementHugeIcon } from "@/lib/element-icons.ts";
7
9
  import { cn } from "@/lib/utils.ts";
8
10
 
9
11
  /** Borderless control inside a {@link SheetField}. */
@@ -184,25 +186,28 @@ export interface SheetChoiceProps {
184
186
  readonly active: boolean;
185
187
  readonly onClick: () => void;
186
188
  readonly children: string;
189
+ /** Optional leading glyph — plane, kind. Durations stay text. */
190
+ readonly icon?: ElementHugeIcon;
187
191
  }
188
192
 
189
193
  /**
190
194
  * One option in a {@link SheetChoiceRow}.
191
195
  *
192
- * @param props - Active state + label
196
+ * @param props - Active state + label + optional icon
193
197
  */
194
- export function SheetChoice({ active, onClick, children }: SheetChoiceProps): JSX.Element {
198
+ export function SheetChoice({ active, onClick, icon, children }: SheetChoiceProps): JSX.Element {
195
199
  return (
196
200
  <button
197
201
  type="button"
198
202
  aria-pressed={active}
199
203
  onClick={onClick}
200
204
  className={cn(
201
- "inline-flex h-8 min-w-0 flex-1 items-center justify-center px-2 font-mono text-[10px] font-semibold outline-none select-none",
205
+ "inline-flex h-8 min-w-0 flex-1 items-center justify-center gap-1.5 px-2 font-mono text-[10px] font-semibold outline-none select-none",
202
206
  "focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-inset",
203
207
  active ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground",
204
208
  )}
205
209
  >
210
+ {icon ? <HugeiconsIcon icon={icon} className="size-3 shrink-0" aria-hidden /> : null}
206
211
  {children}
207
212
  </button>
208
213
  );
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Access page — API keys as issuer credentials, not a second permission system.
3
+ */
4
+
5
+ import { PlusSignIcon } from "@hugeicons/core-free-icons";
6
+ import { HugeiconsIcon } from "@hugeicons/react";
7
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
+ import { useEffect, useMemo, useState, type JSX } from "react";
9
+ import {
10
+ accessCreateKey,
11
+ accessKeyBlast,
12
+ accessRevokeKey,
13
+ accessRotateKey,
14
+ accessUpdateKey,
15
+ clientErrorText,
16
+ } from "@/client.ts";
17
+ import {
18
+ EXPLORER_ICON_BUTTON_CLASS,
19
+ EXPLORER_PAGE_CLASS,
20
+ EXPLORER_SPLIT,
21
+ EXPLORER_TOOLBAR_CLASS,
22
+ } from "@/components/explorer/explorer-chrome.ts";
23
+ import { ExplorerEmpty } from "@/components/explorer/explorer-empty.tsx";
24
+ import { ExplorerSearch } from "@/components/explorer/explorer-search.tsx";
25
+ import { ExplorerStartToggle } from "@/components/explorer/explorer-start-toggle.tsx";
26
+ import { useExplorerStartPanel } from "@/components/explorer/use-explorer-start-panel.ts";
27
+ import { ConfirmSheet } from "@/components/ui/confirm-sheet.tsx";
28
+ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
29
+ import { ToolbarTip } from "@/components/ui/toolbar-tip.tsx";
30
+ import { ELEMENT_ICONS } from "@/lib/element-icons.ts";
31
+ import { AccessDetail } from "./detail/access-detail.tsx";
32
+ import { ACCESS_LIST_QUERY_KEY, useAccessList } from "./data/use-access.ts";
33
+ import { AccessList } from "./explorer/access-list.tsx";
34
+ import { AccessCreateSheet } from "./sheets/access-create-sheet.tsx";
35
+ import { AccessEditSheet } from "./sheets/access-edit-sheet.tsx";
36
+ import { AccessRefreshSheet } from "./sheets/access-refresh-sheet.tsx";
37
+ import { AccessSecretSheet } from "./sheets/access-secret-sheet.tsx";
38
+ import { useAccessSelection } from "./state/access-selection.ts";
39
+
40
+ /**
41
+ * Access explorer — create, edit, refresh expiry, revoke, rotate, usage, working call.
42
+ */
43
+ export function AccessPage(): JSX.Element {
44
+ const list = useAccessList();
45
+ const qc = useQueryClient();
46
+ const { query, selectedKey, action, setQuery, setSelectedKey, setAction } = useAccessSelection();
47
+ const start = useExplorerStartPanel();
48
+ const [now] = useState(() => Date.now());
49
+ const [secret, setSecret] = useState<string | null>(null);
50
+ const [secretTitle, setSecretTitle] = useState("New API key");
51
+
52
+ const keys = useMemo(() => {
53
+ const user = list.data?.userPlane.keys ?? [];
54
+ const ops = list.data?.operatorPlane.keys ?? [];
55
+ return [...user, ...ops];
56
+ }, [list.data]);
57
+
58
+ const filtered = useMemo(() => {
59
+ const q = query.trim().toLowerCase();
60
+ if (!q) return keys;
61
+ return keys.filter(
62
+ (row) =>
63
+ row.name.toLowerCase().includes(q) ||
64
+ row.id.toLowerCase().includes(q) ||
65
+ row.scopes.some((s) => s.toLowerCase().includes(q)),
66
+ );
67
+ }, [keys, query]);
68
+
69
+ const selected =
70
+ filtered.find((row) => row.id === selectedKey) ?? keys.find((row) => row.id === selectedKey);
71
+
72
+ const blast = useQuery({
73
+ queryKey: ["console.access.blast", selected?.id],
74
+ queryFn: async () => {
75
+ if (!selected) throw new Error("no key");
76
+ const res = await accessKeyBlast(selected.id);
77
+ if (res.error || !res.data) throw new Error(res.error?.message ?? "blast failed");
78
+ return res.data;
79
+ },
80
+ enabled: selected !== undefined,
81
+ });
82
+
83
+ useEffect(() => {
84
+ const onKey = (event: KeyboardEvent) => {
85
+ if (event.key !== "/" || event.metaKey || event.ctrlKey || event.altKey) return;
86
+ const target = event.target;
87
+ if (target instanceof HTMLElement) {
88
+ const tag = target.tagName;
89
+ if (tag === "INPUT" || tag === "TEXTAREA" || target.isContentEditable) return;
90
+ }
91
+ event.preventDefault();
92
+ document.querySelector<HTMLInputElement>("[data-slot='access-search-input']")?.focus();
93
+ };
94
+ window.addEventListener("keydown", onKey);
95
+ return () => window.removeEventListener("keydown", onKey);
96
+ }, []);
97
+
98
+ const invalidate = () => {
99
+ void qc.invalidateQueries({ queryKey: ACCESS_LIST_QUERY_KEY });
100
+ };
101
+
102
+ const create = useMutation({
103
+ mutationFn: accessCreateKey,
104
+ onSuccess: (res) => {
105
+ if (res.error || !res.data)
106
+ throw new Error(res.error ? clientErrorText(res.error) : "create failed");
107
+ setSecretTitle("New API key");
108
+ setSecret(res.data.secret);
109
+ setSelectedKey(res.data.key.id);
110
+ setAction(null);
111
+ invalidate();
112
+ },
113
+ });
114
+
115
+ const rotate = useMutation({
116
+ mutationFn: (input: { readonly confirmation: "ROTATE"; readonly reason: string }) =>
117
+ accessRotateKey({
118
+ keyId: selected!.id,
119
+ confirmation: input.confirmation,
120
+ reason: input.reason,
121
+ }),
122
+ onSuccess: (res) => {
123
+ if (res.error || !res.data)
124
+ throw new Error(res.error ? clientErrorText(res.error) : "rotate failed");
125
+ setSecretTitle("Rotated API key");
126
+ setSecret(res.data.secret);
127
+ setAction(null);
128
+ invalidate();
129
+ },
130
+ });
131
+
132
+ const revoke = useMutation({
133
+ mutationFn: (input: { readonly confirmation: "REVOKE"; readonly reason: string }) =>
134
+ accessRevokeKey({
135
+ keyId: selected!.id,
136
+ confirmation: input.confirmation,
137
+ reason: input.reason,
138
+ }),
139
+ onSuccess: (res) => {
140
+ if (res.error || !res.data)
141
+ throw new Error(res.error ? clientErrorText(res.error) : "revoke failed");
142
+ setAction(null);
143
+ invalidate();
144
+ },
145
+ });
146
+
147
+ const update = useMutation({
148
+ mutationFn: accessUpdateKey,
149
+ onSuccess: (res) => {
150
+ if (res.error || !res.data)
151
+ throw new Error(res.error ? clientErrorText(res.error) : "update failed");
152
+ setAction(null);
153
+ invalidate();
154
+ },
155
+ });
156
+
157
+ const refresh = useMutation({
158
+ mutationFn: (expiresAt: number | null) =>
159
+ accessUpdateKey({
160
+ keyId: selected!.id,
161
+ expiresAt,
162
+ }),
163
+ onSuccess: (res) => {
164
+ if (res.error || !res.data)
165
+ throw new Error(res.error ? clientErrorText(res.error) : "refresh failed");
166
+ setAction(null);
167
+ invalidate();
168
+ },
169
+ });
170
+
171
+ const startToggle = (
172
+ <ExplorerStartToggle
173
+ open={start.open}
174
+ onToggle={start.toggle}
175
+ noun="keys"
176
+ controlsId="access-list"
177
+ dataSlot="access-list-toggle"
178
+ />
179
+ );
180
+
181
+ return (
182
+ <div className={EXPLORER_PAGE_CLASS} data-slot="access-page">
183
+ <ResizablePanelGroup orientation="horizontal" className="min-h-0 flex-1">
184
+ <ResizablePanel
185
+ panelRef={start.panelRef}
186
+ collapsible
187
+ collapsedSize={0}
188
+ defaultSize={EXPLORER_SPLIT.start.defaultSize}
189
+ minSize={EXPLORER_SPLIT.start.minSize}
190
+ onResize={start.onResize}
191
+ className="min-h-0 overflow-hidden"
192
+ >
193
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
194
+ <div className={EXPLORER_TOOLBAR_CLASS}>
195
+ <ExplorerSearch
196
+ data-slot="access-search-input"
197
+ placeholder="Search keys"
198
+ aria-label="Search keys"
199
+ value={query}
200
+ onChange={(e) => setQuery(e.target.value)}
201
+ />
202
+ <ToolbarTip label="Create key" className="flex self-stretch">
203
+ <button
204
+ type="button"
205
+ className={EXPLORER_ICON_BUTTON_CLASS}
206
+ data-slot="access-create"
207
+ aria-label="Create key"
208
+ onClick={() => setAction("create")}
209
+ >
210
+ <HugeiconsIcon icon={PlusSignIcon} className="size-3.5" />
211
+ </button>
212
+ </ToolbarTip>
213
+ </div>
214
+ <AccessList
215
+ rows={filtered}
216
+ selectedKey={selectedKey}
217
+ query={query}
218
+ loading={list.isLoading}
219
+ onSelect={setSelectedKey}
220
+ />
221
+ </div>
222
+ </ResizablePanel>
223
+ {start.open ? <ResizableHandle withHandle /> : null}
224
+ <ResizablePanel
225
+ defaultSize={EXPLORER_SPLIT.end.defaultSize}
226
+ minSize={EXPLORER_SPLIT.end.minSize}
227
+ className="min-h-0 overflow-hidden"
228
+ >
229
+ {selected ? (
230
+ <AccessDetail
231
+ keyRow={selected}
232
+ blast={blast.data}
233
+ now={now}
234
+ leading={startToggle}
235
+ onEdit={() => setAction("edit")}
236
+ onRefresh={() => setAction("refresh")}
237
+ onRevoke={() => setAction("revoke")}
238
+ onRotate={() => setAction("rotate")}
239
+ rotatePending={rotate.isPending}
240
+ />
241
+ ) : (
242
+ <ExplorerEmpty
243
+ icon={ELEMENT_ICONS.gate.icon}
244
+ iconClassName="border-border/70"
245
+ title={
246
+ list.isLoading ? "Loading keys…" : keys.length > 0 ? "Select a key" : "No keys yet"
247
+ }
248
+ leading={startToggle}
249
+ description={
250
+ list.isLoading ? (
251
+ "Reading planes and grantable scopes."
252
+ ) : keys.length > 0 && query.trim().length > 0 ? (
253
+ <>
254
+ Nothing matches <span className="font-mono">{query}</span>.{" "}
255
+ <button
256
+ type="button"
257
+ className="underline underline-offset-2 hover:text-foreground"
258
+ onClick={() => setQuery("")}
259
+ >
260
+ Clear search
261
+ </button>
262
+ </>
263
+ ) : (
264
+ "Create one to mint a secret once."
265
+ )
266
+ }
267
+ />
268
+ )}
269
+ </ResizablePanel>
270
+ </ResizablePanelGroup>
271
+
272
+ <AccessCreateSheet
273
+ open={action === "create"}
274
+ users={list.data?.userPlane.users ?? []}
275
+ userScopes={list.data?.userPlane.grantableScopes ?? []}
276
+ operatorScopes={list.data?.operatorPlane.grantableScopes ?? []}
277
+ pending={create.isPending}
278
+ error={create.error instanceof Error ? create.error.message : null}
279
+ onOpenChange={(open) => {
280
+ if (!open) setAction(null);
281
+ }}
282
+ onSubmit={(input) => create.mutate(input)}
283
+ />
284
+ {selected ? (
285
+ <AccessEditSheet
286
+ open={action === "edit"}
287
+ keyRow={selected}
288
+ users={list.data?.userPlane.users ?? []}
289
+ operators={list.data?.operatorPlane.operators ?? []}
290
+ operatorScopes={list.data?.operatorPlane.grantableScopes ?? []}
291
+ pending={update.isPending}
292
+ error={update.error instanceof Error ? update.error.message : null}
293
+ onOpenChange={(open) => {
294
+ if (!open) setAction(null);
295
+ }}
296
+ onSubmit={(input) => update.mutate({ keyId: selected.id, ...input })}
297
+ />
298
+ ) : null}
299
+ {selected ? (
300
+ <AccessRefreshSheet
301
+ open={action === "refresh"}
302
+ keyRow={selected}
303
+ pending={refresh.isPending}
304
+ error={refresh.error instanceof Error ? refresh.error.message : null}
305
+ onOpenChange={(open) => {
306
+ if (!open) setAction(null);
307
+ }}
308
+ onSubmit={(expiresAt) => refresh.mutate(expiresAt)}
309
+ />
310
+ ) : null}
311
+ {selected ? (
312
+ <ConfirmSheet
313
+ open={action === "revoke"}
314
+ onOpenChange={(open) => {
315
+ if (!open) setAction(null);
316
+ }}
317
+ phrase="REVOKE"
318
+ title={`Revoke ${selected.name}`}
319
+ description="Irreversible. Residual sessions may continue for the access TTL."
320
+ pending={revoke.isPending}
321
+ error={revoke.error instanceof Error ? revoke.error.message : null}
322
+ confirmLabel="Revoke key"
323
+ slot="access-revoke-sheet"
324
+ onConfirm={(input) => revoke.mutate({ confirmation: "REVOKE", reason: input.reason })}
325
+ />
326
+ ) : null}
327
+ {selected ? (
328
+ <ConfirmSheet
329
+ open={action === "rotate"}
330
+ onOpenChange={(open) => {
331
+ if (!open) setAction(null);
332
+ }}
333
+ phrase="ROTATE"
334
+ title={`Rotate ${selected.name}`}
335
+ description="Mints a new secret once. The current secret stops after residual TTL."
336
+ pending={rotate.isPending}
337
+ error={rotate.error instanceof Error ? rotate.error.message : null}
338
+ confirmLabel="Rotate key"
339
+ confirmVariant="ghost"
340
+ slot="access-rotate-sheet"
341
+ onConfirm={(input) => rotate.mutate({ confirmation: "ROTATE", reason: input.reason })}
342
+ />
343
+ ) : null}
344
+ <AccessSecretSheet secret={secret} title={secretTitle} onDismiss={() => setSecret(null)} />
345
+ </div>
346
+ );
347
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * React Query wrapper for `GET /console/access`.
3
+ */
4
+
5
+ import { useQuery } from "@tanstack/react-query";
6
+ import { accessList, type AccessListPayload } from "@/client.ts";
7
+
8
+ /** React Query key for the Access list. */
9
+ export const ACCESS_LIST_QUERY_KEY = ["console.access.list"] as const;
10
+
11
+ /**
12
+ * Fetch Access keys, users, and grantable scopes.
13
+ */
14
+ export function useAccessList() {
15
+ return useQuery({
16
+ queryKey: ACCESS_LIST_QUERY_KEY,
17
+ queryFn: async (): Promise<AccessListPayload> => {
18
+ const res = await accessList();
19
+ if (res.error || !res.data) throw new Error(res.error?.message ?? "Empty access list");
20
+ return res.data;
21
+ },
22
+ });
23
+ }