@fuzdev/fuz_app 0.60.0 → 0.62.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 (63) hide show
  1. package/dist/actions/CLAUDE.md +28 -22
  2. package/dist/auth/CLAUDE.md +4 -4
  3. package/dist/server/app_server.d.ts +54 -6
  4. package/dist/server/app_server.d.ts.map +1 -1
  5. package/dist/server/app_server.js +32 -4
  6. package/dist/testing/CLAUDE.md +8 -8
  7. package/dist/ui/AccountSessions.svelte +21 -6
  8. package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
  9. package/dist/ui/AdminAccounts.svelte +32 -25
  10. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  11. package/dist/ui/AdminAuditLog.svelte +3 -3
  12. package/dist/ui/AdminInvites.svelte +20 -15
  13. package/dist/ui/AdminOverview.svelte +19 -21
  14. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  15. package/dist/ui/AdminRoleGrantHistory.svelte +3 -3
  16. package/dist/ui/AdminSessions.svelte +19 -21
  17. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  18. package/dist/ui/AdminSettings.svelte +1 -3
  19. package/dist/ui/AdminSettings.svelte.d.ts.map +1 -1
  20. package/dist/ui/CLAUDE.md +123 -69
  21. package/dist/ui/ConfirmButton.svelte +82 -24
  22. package/dist/ui/ConfirmButton.svelte.d.ts +8 -34
  23. package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -1
  24. package/dist/ui/OpenSignupToggle.svelte +6 -4
  25. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  26. package/dist/ui/RoleGrantOfferForm.svelte +4 -4
  27. package/dist/ui/RoleGrantOfferHistory.svelte +3 -3
  28. package/dist/ui/RoleGrantOfferInbox.svelte +10 -6
  29. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
  30. package/dist/ui/account_sessions_state.svelte.d.ts +17 -7
  31. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  32. package/dist/ui/account_sessions_state.svelte.js +32 -33
  33. package/dist/ui/admin_accounts_state.svelte.d.ts +48 -17
  34. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  35. package/dist/ui/admin_accounts_state.svelte.js +58 -76
  36. package/dist/ui/admin_invites_state.svelte.d.ts +14 -7
  37. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  38. package/dist/ui/admin_invites_state.svelte.js +32 -48
  39. package/dist/ui/admin_sessions_state.svelte.d.ts +15 -8
  40. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  41. package/dist/ui/admin_sessions_state.svelte.js +30 -47
  42. package/dist/ui/app_settings_state.svelte.d.ts +8 -3
  43. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  44. package/dist/ui/app_settings_state.svelte.js +19 -27
  45. package/dist/ui/async_slot.svelte.d.ts +173 -0
  46. package/dist/ui/async_slot.svelte.d.ts.map +1 -0
  47. package/dist/ui/async_slot.svelte.js +241 -0
  48. package/dist/ui/audit_log_state.svelte.d.ts +8 -2
  49. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  50. package/dist/ui/audit_log_state.svelte.js +19 -18
  51. package/dist/ui/keyed_async_slot.svelte.d.ts +139 -0
  52. package/dist/ui/keyed_async_slot.svelte.d.ts.map +1 -0
  53. package/dist/ui/keyed_async_slot.svelte.js +177 -0
  54. package/dist/ui/role_grant_offers_state.svelte.d.ts +39 -7
  55. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  56. package/dist/ui/role_grant_offers_state.svelte.js +34 -15
  57. package/dist/ui/table_state.svelte.d.ts +10 -7
  58. package/dist/ui/table_state.svelte.d.ts.map +1 -1
  59. package/dist/ui/table_state.svelte.js +11 -8
  60. package/package.json +1 -1
  61. package/dist/ui/loadable.svelte.d.ts +0 -60
  62. package/dist/ui/loadable.svelte.d.ts.map +0 -1
  63. package/dist/ui/loadable.svelte.js +0 -80
@@ -225,6 +225,24 @@ and `FrontendActionHandlers`.
225
225
  - `compose_gen_file({origin_path, imports, blocks})` — encapsulates the per-`*.gen.ts` boilerplate (banner + `imports.build()` + blocks join + template literal). Returns the full file body. Each consumer producer collapses to one `compose_gen_file` call wrapping the helper invocations.
226
226
  - `create_namespace_qualifier(sources, imports)` — multi-source consumer helper. Takes `ReadonlyArray<{ns, module, specs}>`, registers `import * as ns from module` for each on `imports`, builds the `method_to_ns` lookup with duplicate-method detection, returns `{qualify_spec, all_specs}` ready to thread through the high-level helpers. Closes the per-file boilerplate gap that kept zap + visiones on hand-rolled template strings even after the `qualify_spec?` callback landed (the per-call callback wasn't enough — the import dance + dup-check was the real boilerplate).
227
227
 
228
+ ## Registry compile (`compile_action_registry.ts`)
229
+
230
+ Shared registration loop called by both `create_rpc_endpoint` and
231
+ `register_action_ws`. Validates four invariants and returns the
232
+ `Map<method, RpcAction>` the dispatchers use:
233
+
234
+ 1. Auth-shape biconditional (`assert_route_auth_acting_biconditional` —
235
+ `auth.actor !== 'none' ⟺ input declares acting?: ActingActor`).
236
+ 2. Rate-limit / account axis — `rate_limit: 'account' | 'both'` requires
237
+ `auth.account === 'required'`.
238
+ 3. JSON-RPC §4.2 wire validity — `request_response` specs with a handler
239
+ may not use `z.null()` for input (use `z.void()` for nullary).
240
+ 4. Unique `method` across the array.
241
+
242
+ Only `request_response` specs with a handler reach the dispatch map;
243
+ `remote_notification` / handler-less specs (e.g. WS `cancel`) stay
244
+ registry-only.
245
+
228
246
  ## HTTP bridge (`action_bridge.ts`)
229
247
 
230
248
  Derives transport-specific specs from action specs. HTTP-specific concerns
@@ -256,7 +274,7 @@ surface is published in `library.json` codegen anyway.
256
274
  Shim responsibilities:
257
275
 
258
276
  1. **Parse envelope** — POST body as `JsonrpcRequest` (parse errors → JSON-RPC `parse_error` 400). GET reads `method`, `id`, `params` from query string; missing `method`/`id` → 400 `invalid_request`. Integer `id` normalization: `?id=42` matches `{id: 42}`.
259
- 2. **Lookup method** — `Map<method, RpcAction>`. Unknown method `method_not_found`. Duplicate methods throw at construction. Registration also runs `assert_route_auth_acting_biconditional(spec.auth, {input: spec.input}, ...)` to enforce invariant 2 the helper takes a `{input, query?}` slot set so REST (input + query bi-located) and actions (input-only) share one entry point with surface-appropriate error messages.
277
+ 2. **Lookup method** — `Map<method, RpcAction>` built via `compile_action_registry` (which runs the registry-time invariantssee §Registry compile above). Unknown method `method_not_found`.
260
278
  3. **GET read restriction** — GET is rejected for `side_effects: true` actions (`invalid_request` with "must use POST"). HTTP-only.
261
279
  4. **Build PerformActionInput** — read `account_id` / `credential_type` from `c.var`, resolve `client_ip` via `get_client_ip`, pass `c.req.raw.signal` as `signal`, build a DEV-warn-and-drop `notify`. Test-preset escape hatch reads `TEST_CONTEXT_PRESET_KEY` + `REQUEST_CONTEXT_KEY` and forwards as `preset.request_context`.
262
280
  5. **Call `perform_action`** — runs steps 1–6 of the shared pipeline (see §Shared dispatch core below).
@@ -358,19 +376,8 @@ narrowing — ideal when handlers are stateless free functions. fuz_app's
358
376
  handlers close over factory-captured deps (`log`, `audit`,
359
377
  `options.app_settings`, `options.max_tokens`), so per-pair typing via
360
378
  `rpc_action()` is the right shape here: the binding happens at
361
- construction time and the handler keeps its closure. Applied across
362
- all four registries `admin_actions.ts`,
363
- `role_grant_offer_actions.ts`, `self_service_role_actions.ts` (every
364
- spec there is actor-implying), and `account_actions.ts` (account-grain).
365
- The conditional auto-selects the right tier per spec; consumers don't
366
- pick a binder.
367
-
368
- The earlier two-binder split (`rpc_action` + `rpc_actor_action`) was
369
- collapsed once the symmetric account-grain narrowing landed. Same
370
- runtime; the second symbol no longer added information the spec
371
- literal didn't already carry. Uniform binding keeps a future handler
372
- that gains `ctx.auth.actor` reads from accidentally landing on a
373
- looser narrow — the spec literal drives the type either way.
379
+ construction time and the handler keeps its closure. The conditional
380
+ auto-selects the right tier per spec; consumers don't pick a binder.
374
381
 
375
382
  ## Transports (`transports.ts`, `transports_http.ts`, `transports_ws.ts`, `transports_ws_backend.ts`)
376
383
 
@@ -529,11 +536,11 @@ dispatcher without the origin/auth front-stack.
529
536
 
530
537
  Actions are passed as `ReadonlyArray<Action>` — the composable
531
538
  `{spec, handler?}` tuple shared with `create_rpc_client`. The dispatcher
532
- fans the array into a `spec_by_method` map (drives envelope-shape
533
- validation) and an `action_map: Map<string, RpcAction>` (drives
534
- invocation, only request_response specs with a handler). Specs without
535
- a handler (client-only / dispatcher-handled like `cancel`) miss
536
- `action_map` and surface as `method_not_found` if the wire targets them.
539
+ builds its `action_map: Map<string, RpcAction>` via `compile_action_registry`
540
+ (see §Registry compile above) only `request_response` specs with a
541
+ handler land in the map. Specs without a handler (client-only /
542
+ dispatcher-handled like `cancel`) surface as `method_not_found` if the
543
+ wire targets them.
537
544
 
538
545
  Required deps: `db: Db` (pool-level, used by `perform_action` for both the
539
546
  per-message authorization phase and the transactional dispatch wrap when
@@ -988,9 +995,8 @@ transport; `ActionHandler` is the single handler signature.
988
995
 
989
996
  `RpcAction = Action<RequestResponseActionSpec> & {handler: ActionHandler}`
990
997
  is the narrowing the HTTP RPC dispatcher accepts (`create_rpc_endpoint`)
991
- and the `rpc_action` binder produces (the actor-axis narrowing now lives
992
- in `HandlerForSpec<TSpec>` — there's no longer a separate
993
- `rpc_actor_action`).
998
+ and the `rpc_action` binder produces (the actor-axis narrowing lives
999
+ in `HandlerForSpec<TSpec>`).
994
1000
 
995
1001
  ## Shared dispatch core (`perform_action.ts`)
996
1002
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Auth domain: identity, crypto primitives, schema + DDL, queries, middleware, routes, RPC actions, cleanup.
4
4
 
5
- Forty source files, grouped below by theme. For design rationale and threat
5
+ Grouped below by theme. For design rationale and threat
6
6
  model, see `../../../docs/identity.md` and `../../../docs/security.md`. For the
7
7
  subsystem's place in server assembly and middleware ordering, see
8
8
  `../../../docs/architecture.md` and the root `../../../CLAUDE.md`.
@@ -1530,9 +1530,9 @@ spread `all_self_service_role_action_specs` separately when needed.
1530
1530
 
1531
1531
  `all_fuz_auth_action_spec_registries` — walker/codegen entry for every
1532
1532
  fuz-auth action-spec bundle (`admin`, `role_grant_offer`, `account`,
1533
- `self_service_role`, `actor_lookup`). Not a mounting surface; protocol
1534
- specs are excluded. Iterated by `../../test/auth/action_spec_input_invariants.test.ts`
1535
- and `../../test/auth/all_action_spec_registries.acting_biconditional.test.ts`.
1533
+ `self_service_role`, `actor_lookup`, `actor_search`). Not a mounting
1534
+ surface; protocol specs are excluded. Iterated by registry-wide
1535
+ invariant tests in `../../test/auth/`.
1536
1536
 
1537
1537
  ### `account_action_specs.ts` + `account_actions.ts` — seven self-service RPC actions
1538
1538
 
@@ -137,7 +137,10 @@ export interface AppServerOptions {
137
137
  * listener to `backend.deps.audit.on_event_chain` (composing with the
138
138
  * consumer's `on_audit_event` callback rather than rebuilding `AppDeps`), and
139
139
  * auto-includes `audit_log_event_specs` in the surface. The result is exposed
140
- * on `AppServerContext` (for route factories) and `AppServer` (for the caller).
140
+ * on `AppServerContext` (for route factories) and `AppServer` (for the caller),
141
+ * always typed as `AuditLogSse | null` — when this option is set, the field
142
+ * is non-null. Use `require_audit_sse(ctx)` to assert the invariant in
143
+ * route factories that depend on it.
141
144
  *
142
145
  * Pass `true` for defaults (admin role), or `{role: 'custom'}` for a custom role.
143
146
  * Omit to wire audit SSE manually.
@@ -160,14 +163,27 @@ export interface AppServerOptions {
160
163
  * `create_standard_rpc_actions(ctx.deps, {app_settings: ctx.app_settings})`.
161
164
  */
162
165
  rpc_endpoints?: Array<RpcEndpointSpec> | ((context: AppServerContext) => Array<RpcEndpointSpec>);
163
- /** Env schema for surface generation. Pass `z.object({})` when there are no env vars beyond `BaseServerEnv`. */
164
- env_schema: z.ZodObject;
166
+ /**
167
+ * Env schema for surface generation. Defaults to `BaseServerEnv` —
168
+ * pass an extended schema (typically `BaseServerEnv.extend({...})`)
169
+ * when the consumer adds app-specific env vars.
170
+ */
171
+ env_schema?: z.ZodObject;
165
172
  /** Middleware applied after routes, before static serving. Included in surface. */
166
173
  post_route_middleware?: Array<MiddlewareSpec>;
167
174
  /** Static file serving. Omit if not serving static files. */
168
175
  static_serving?: {
169
176
  serve_static: ServeStaticFactory;
177
+ /** Root directory for static files. Default `'./build'`. */
178
+ root?: string;
179
+ /** Optional SPA fallback path served for client-side routes. */
170
180
  spa_fallback?: string;
181
+ /**
182
+ * Predicate deciding which paths receive the SPA fallback.
183
+ * Default: every path that is not under `/api/`. Only consulted
184
+ * when `spa_fallback` is set.
185
+ */
186
+ is_spa_route?: (path: string) => boolean;
171
187
  };
172
188
  /**
173
189
  * Await all pending fire-and-forget effects before returning the response.
@@ -202,7 +218,11 @@ export interface AppServerContext {
202
218
  action_account_rate_limiter: RateLimiter | null;
203
219
  /** Global app settings (mutable ref — mutated by settings admin route). */
204
220
  app_settings: AppSettings;
205
- /** Factory-managed audit log SSE. `null` when `audit_log_sse` option is not set. */
221
+ /**
222
+ * Factory-managed audit log SSE. Non-null when the `audit_log_sse`
223
+ * option was passed to `create_app_server`, `null` when omitted.
224
+ * Use `require_audit_sse(ctx)` to assert the invariant.
225
+ */
206
226
  audit_sse: AuditLogSse | null;
207
227
  }
208
228
  /** Result of `create_app_server()`. */
@@ -215,11 +235,35 @@ export interface AppServer {
215
235
  app_settings: AppSettings;
216
236
  /** Migration results from `create_app_backend` (auth + any `migration_namespaces` passed there). */
217
237
  migration_results: ReadonlyArray<MigrationResult>;
218
- /** Factory-managed audit log SSE. `null` when `audit_log_sse` option is not set. */
238
+ /**
239
+ * Factory-managed audit log SSE. Non-null when the `audit_log_sse`
240
+ * option was passed to `create_app_server`, `null` when omitted.
241
+ * Use `require_audit_sse(server)` to assert the invariant.
242
+ */
219
243
  audit_sse: AuditLogSse | null;
220
244
  /** Close the database connection. Propagated from `AppBackend`. */
221
245
  close: () => Promise<void>;
222
246
  }
247
+ /**
248
+ * Assert that `audit_sse` was wired by `create_app_server` and return it
249
+ * as a non-null `AuditLogSse`. Throws a labelled error when the
250
+ * `audit_log_sse` option was not passed to `create_app_server`.
251
+ *
252
+ * Use in route factories that depend on factory-managed audit SSE:
253
+ *
254
+ * ```ts
255
+ * create_route_specs: (ctx) => create_audit_log_route_specs({
256
+ * stream: require_audit_sse(ctx),
257
+ * }),
258
+ * ```
259
+ *
260
+ * Preferred over `ctx.audit_sse!` — `!` lies to the type system and
261
+ * produces a downstream cannot-read-property crash if a consumer wires
262
+ * the route without enabling the option.
263
+ */
264
+ export declare const require_audit_sse: (source: {
265
+ audit_sse: AuditLogSse | null;
266
+ }) => AuditLogSse;
223
267
  /** Default maximum request body size: 1 MiB. */
224
268
  export declare const DEFAULT_MAX_BODY_SIZE: number;
225
269
  /**
@@ -231,7 +275,11 @@ export declare const DEFAULT_MAX_BODY_SIZE: number;
231
275
  * pass `migration_namespaces` to `create_app_backend`.
232
276
  *
233
277
  * When `audit_log_sse` is set, the SSE registry's listener is appended to
234
- * `backend.deps.audit.on_event_chain` — no shallow-copy of `AppDeps`.
278
+ * `backend.deps.audit.on_event_chain` — no shallow-copy of `AppDeps`. The
279
+ * `audit_sse` field on the returned `AppServer` (and the
280
+ * `AppServerContext` passed to `create_route_specs`) is non-null in that
281
+ * case; consumers can call `require_audit_sse(ctx)` / `require_audit_sse(server)`
282
+ * to assert the invariant.
235
283
  *
236
284
  * @returns assembled Hono app, backend, surface build, and bootstrap status
237
285
  */
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,IAAI,EAAE,KAAK,OAAO,EAAC,MAAM,MAAM,CAAC;AAGxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAEN,KAAK,cAAc,EAEnB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAGN,KAAK,WAAW,EAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAKN,KAAK,WAAW,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAA2B,KAAK,kBAAkB,EAAC,MAAM,aAAa,CAAC;AAE9E,OAAO,EAEN,KAAK,cAAc,EAEnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAGN,KAAK,eAAe,EACpB,MAAM,6BAA6B,CAAC;AASrC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,2DAA2D;IAC3D,OAAO,EAAE,UAAU,CAAC;IACpB,6CAA6C;IAC7C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,sCAAsC;IACtC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE/B,6BAA6B;IAC7B,KAAK,EAAE;QACN,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;KACtD,CAAC;IAEF;;;;;OAKG;IACH,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACrC;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;;;;OAQG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2DAA2D;IAC3D,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IAEtC,yEAAyE;IACzE,SAAS,CAAC,EAAE;QACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,mEAAmE;QACnE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB;;;WAGG;QACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC9E,CAAC;IAEF;;;OAGG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC;IAEtB;;;OAGG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAEpE,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,KAAK,CAAC,cAAc,CAAC,CAAC;IAE/E;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,IAAI,GAAG;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;IAEvC,gFAAgF;IAChF,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAEjG,gHAAgH;IAChH,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC;IAExB,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAE9C,6DAA6D;IAC7D,cAAc,CAAC,EAAE;QAChB,YAAY,EAAE,kBAAkB,CAAC;QACjC,YAAY,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAExE,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yEAAyE;IACzE,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,iFAAiF;IACjF,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,kFAAkF;IAClF,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,uGAAuG;IACvG,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,0GAA0G;IAC1G,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,2EAA2E;IAC3E,YAAY,EAAE,WAAW,CAAC;IAC1B,oFAAoF;IACpF,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B;AAED,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACzB,GAAG,EAAE,IAAI,CAAC;IACV,wEAAwE;IACxE,YAAY,EAAE,cAAc,CAAC;IAC7B,gBAAgB,EAAE,eAAe,CAAC;IAClC,2EAA2E;IAC3E,YAAY,EAAE,WAAW,CAAC;IAC1B,oGAAoG;IACpG,iBAAiB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAClD,oFAAoF;IACpF,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,mEAAmE;IACnE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAAU,SAAS,gBAAgB,KAAG,OAAO,CAAC,SAAS,CAmRpF,CAAC"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ import { bodyLimit } from 'hono/body-limit';
13
13
  import { z } from 'zod';
14
14
  import { session_cookie_options, } from '../auth/session_cookie.js';
15
15
  import { create_audit_log_sse, audit_log_event_specs, } from '../realtime/sse_auth_guard.js';
16
+ import { BaseServerEnv } from './env.js';
16
17
  import { query_app_settings_load } from '../auth/app_settings_queries.js';
17
18
  import { create_rate_limiter, default_login_account_rate_limit, default_action_account_rate_limit, default_action_ip_rate_limit, } from '../rate_limiter.js';
18
19
  // Side-effect import: augments Hono's ContextVariableMap so consumers
@@ -31,6 +32,29 @@ import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
31
32
  import { create_fuz_authorization_handler } from '../auth/request_context.js';
32
33
  import { ERROR_PAYLOAD_TOO_LARGE } from '../http/error_schemas.js';
33
34
  import { create_rpc_endpoint } from '../actions/action_rpc.js';
35
+ /**
36
+ * Assert that `audit_sse` was wired by `create_app_server` and return it
37
+ * as a non-null `AuditLogSse`. Throws a labelled error when the
38
+ * `audit_log_sse` option was not passed to `create_app_server`.
39
+ *
40
+ * Use in route factories that depend on factory-managed audit SSE:
41
+ *
42
+ * ```ts
43
+ * create_route_specs: (ctx) => create_audit_log_route_specs({
44
+ * stream: require_audit_sse(ctx),
45
+ * }),
46
+ * ```
47
+ *
48
+ * Preferred over `ctx.audit_sse!` — `!` lies to the type system and
49
+ * produces a downstream cannot-read-property crash if a consumer wires
50
+ * the route without enabling the option.
51
+ */
52
+ export const require_audit_sse = (source) => {
53
+ if (!source.audit_sse) {
54
+ throw new Error('audit_sse is null — pass `audit_log_sse: true` (or `{role}`) in `AppServerOptions`');
55
+ }
56
+ return source.audit_sse;
57
+ };
34
58
  /** Default maximum request body size: 1 MiB. */
35
59
  export const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
36
60
  /**
@@ -42,7 +66,11 @@ export const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
42
66
  * pass `migration_namespaces` to `create_app_backend`.
43
67
  *
44
68
  * When `audit_log_sse` is set, the SSE registry's listener is appended to
45
- * `backend.deps.audit.on_event_chain` — no shallow-copy of `AppDeps`.
69
+ * `backend.deps.audit.on_event_chain` — no shallow-copy of `AppDeps`. The
70
+ * `audit_sse` field on the returned `AppServer` (and the
71
+ * `AppServerContext` passed to `create_route_specs`) is non-null in that
72
+ * case; consumers can call `require_audit_sse(ctx)` / `require_audit_sse(server)`
73
+ * to assert the invariant.
46
74
  *
47
75
  * @returns assembled Hono app, backend, surface build, and bootstrap status
48
76
  */
@@ -161,7 +189,7 @@ export const create_app_server = async (options) => {
161
189
  const surface_spec = create_app_surface_spec({
162
190
  middleware_specs: surface_middleware,
163
191
  route_specs,
164
- env_schema: options.env_schema,
192
+ env_schema: options.env_schema ?? BaseServerEnv,
165
193
  event_specs: all_event_specs,
166
194
  rpc_endpoints: resolved_rpc_endpoints,
167
195
  });
@@ -269,8 +297,8 @@ export const create_app_server = async (options) => {
269
297
  }
270
298
  // Static file serving
271
299
  if (options.static_serving) {
272
- const { serve_static, spa_fallback } = options.static_serving;
273
- for (const mw of create_static_middleware(serve_static, { spa_fallback })) {
300
+ const { serve_static, root, spa_fallback, is_spa_route } = options.static_serving;
301
+ for (const mw of create_static_middleware(serve_static, { root, spa_fallback, is_spa_route })) {
274
302
  app.use('/*', mw);
275
303
  }
276
304
  }
@@ -700,14 +700,14 @@ deps — no DB needed.
700
700
  Options: `{build: () => AppSurfaceSpec, roles: Array<string>}`.
701
701
 
702
702
  **Opt-in bundles need their own per-bundle suite file.** Action bundles
703
- not folded into `create_standard_rpc_actions` (today `self_service_role_actions`
704
- and `actor_lookup_actions`) get zero adversarial / round-trip coverage
705
- from `describe_rpc_attack_surface_tests` + `describe_rpc_round_trip_tests`
706
- unless the consumer ships a `<module>.rpc_suites.db.test.ts` mounting the
707
- opt-in factory on the RPC endpoint and calling both suites. See
708
- `../../test/CLAUDE.md` §Composable Test Suites for the obligation note
709
- and `actor_lookup_actions.rpc_suites.db.test.ts` /
710
- `role_grant_offer_actions.rpc_suites.db.test.ts` as templates.
703
+ not folded into `create_standard_rpc_actions` (today `self_service_role_actions`,
704
+ `actor_lookup_actions`, and `actor_search_actions`) get zero adversarial
705
+ / round-trip coverage from `describe_rpc_attack_surface_tests` +
706
+ `describe_rpc_round_trip_tests` unless the consumer ships a
707
+ `<module>.rpc_suites.db.test.ts` mounting the opt-in factory on the RPC
708
+ endpoint and calling both suites. See `../../test/CLAUDE.md` §Composable
709
+ Test Suites for the obligation note; existing
710
+ `../../test/auth/*.rpc_suites.db.test.ts` files are templates.
711
711
 
712
712
  ## Cross-cutting conventions
713
713
 
@@ -25,8 +25,8 @@
25
25
  void account_sessions.fetch();
26
26
 
27
27
  const handle_revoke_all = async (): Promise<void> => {
28
- await account_sessions.revoke_all();
29
- if (!account_sessions.error) {
28
+ await account_sessions.submit_revoke_all();
29
+ if (!account_sessions.revoke_all.error) {
30
30
  auth_state.verified = false;
31
31
  }
32
32
  };
@@ -48,11 +48,15 @@
48
48
  {/if}
49
49
  </h2>
50
50
 
51
- {#if account_sessions.loading}
51
+ {#if account_sessions.list.loading}
52
52
  <p class="text_50">loading sessions...</p>
53
- {:else if account_sessions.error}
54
- <p class="color_c_50">{account_sessions.error}</p>
53
+ {:else if account_sessions.list.error}
54
+ <p class="color_c_50">{account_sessions.list.error}</p>
55
55
  {:else}
56
+ {@const revoke_all_error = account_sessions.revoke_all.error}
57
+ {#if revoke_all_error}
58
+ <p class="color_c_50">{revoke_all_error}</p>
59
+ {/if}
56
60
  {#if account_sessions.active_count > 1}
57
61
  <div class="mb_md">
58
62
  <button type="button" onclick={() => handle_revoke_all()}>revoke all</button>
@@ -76,7 +80,18 @@
76
80
  {format_relative_time(row.expires_at)}
77
81
  </span>
78
82
  {:else if column.key === 'account_id'}
79
- <button type="button" onclick={() => account_sessions.revoke(row.id)}>revoke</button>
83
+ {@const revoking = account_sessions.revoke.loading(row.id)}
84
+ {@const revoke_error = account_sessions.revoke.error(row.id)}
85
+ <button
86
+ type="button"
87
+ disabled={revoking}
88
+ onclick={() => account_sessions.submit_revoke(row.id)}
89
+ >
90
+ {revoking ? 'revoking…' : 'revoke'}
91
+ </button>
92
+ {#if revoke_error}
93
+ <span class="color_c_50 font_size_sm">{revoke_error}</span>
94
+ {/if}
80
95
  {:else if column.format}
81
96
  {column.format(row[column.key], row)}
82
97
  {:else}
@@ -1 +1 @@
1
- {"version":3,"file":"AccountSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AccountSessions.svelte"],"names":[],"mappings":"AAsGA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,eAAe;;kBAA+E,CAAC;AACnF,KAAK,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC;AAC9D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"AccountSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AccountSessions.svelte"],"names":[],"mappings":"AAiHA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,eAAe;;kBAA+E,CAAC;AACnF,KAAK,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC;AAC9D,eAAe,eAAe,CAAC"}
@@ -9,7 +9,11 @@
9
9
  * @module
10
10
  */
11
11
 
12
- import {AdminAccountsState, admin_accounts_rpc_context} from './admin_accounts_state.svelte.js';
12
+ import {
13
+ AdminAccountsState,
14
+ admin_accounts_rpc_context,
15
+ grant_key,
16
+ } from './admin_accounts_state.svelte.js';
13
17
  import ConfirmButton from './ConfirmButton.svelte';
14
18
  import Datatable from './Datatable.svelte';
15
19
  import type {DatatableColumn} from './datatable.js';
@@ -45,10 +49,10 @@
45
49
  </p>
46
50
  {/if}
47
51
 
48
- {#if admin_accounts.loading}
52
+ {#if admin_accounts.list.loading}
49
53
  <p class="text_50">loading accounts...</p>
50
- {:else if admin_accounts.error}
51
- <p class="color_c_50">{admin_accounts.error}</p>
54
+ {:else if admin_accounts.list.error}
55
+ <p class="color_c_50">{admin_accounts.list.error}</p>
52
56
  {:else}
53
57
  <Datatable {columns} rows={admin_accounts.accounts} height="400px">
54
58
  {#snippet cell(column, row)}
@@ -92,16 +96,17 @@
92
96
  {/if}
93
97
  {#if admin_accounts.has_rpc && row.actor}
94
98
  {@const actor_id = row.actor.id}
99
+ {@const revoke_error = admin_accounts.revoke.error(role_grant.id)}
95
100
  <ConfirmButton
96
- onconfirm={() => admin_accounts.revoke_role_grant(actor_id, role_grant.id)}
101
+ onconfirm={() => admin_accounts.submit_revoke(actor_id, role_grant.id)}
97
102
  title="revoke {role_grant.role}"
98
103
  class="sm"
99
- disabled={admin_accounts.revoking_ids.has(role_grant.id)}
100
- >
101
- {#snippet children(_popover, _confirm)}
102
- {admin_accounts.revoking_ids.has(role_grant.id) ? 'revoking…' : 'revoke'}
103
- {/snippet}
104
- </ConfirmButton>
104
+ label="revoke"
105
+ pending={admin_accounts.revoke.loading(role_grant.id)}
106
+ />
107
+ {#if revoke_error}
108
+ <span class="color_c_50 font_size_sm">{revoke_error}</span>
109
+ {/if}
105
110
  {/if}
106
111
  </div>
107
112
  {/each}
@@ -120,16 +125,17 @@
120
125
  </span>
121
126
  {/if}
122
127
  {#if admin_accounts.has_rpc}
128
+ {@const retract_error = admin_accounts.retract.error(offer.id)}
123
129
  <ConfirmButton
124
- onconfirm={() => admin_accounts.retract_offer(offer.id)}
130
+ onconfirm={() => admin_accounts.submit_retract(offer.id)}
125
131
  title="retract offer"
126
132
  class="sm"
127
- disabled={admin_accounts.retracting_ids.has(offer.id)}
128
- >
129
- {#snippet children(_popover, _confirm)}
130
- {admin_accounts.retracting_ids.has(offer.id) ? 'retracting…' : 'retract'}
131
- {/snippet}
132
- </ConfirmButton>
133
+ label="retract"
134
+ pending={admin_accounts.retract.loading(offer.id)}
135
+ />
136
+ {#if retract_error}
137
+ <span class="color_c_50 font_size_sm">{retract_error}</span>
138
+ {/if}
133
139
  {/if}
134
140
  </div>
135
141
  {/each}
@@ -139,24 +145,25 @@
139
145
  {:else if column.key === 'actor'}
140
146
  {#if admin_accounts.has_rpc}
141
147
  {#each admin_accounts.grantable_roles as role (role)}
148
+ {@const key = grant_key(row.account.id, role)}
149
+ {@const grant_error = admin_accounts.grant.error(key)}
142
150
  {#if !row.role_grants.some((p) => p.role === role) && !row.pending_offers.some((o) => o.role === role)}
143
151
  <ConfirmButton
144
- onconfirm={() => admin_accounts.create_role_grant(row.account.id, role)}
152
+ onconfirm={() => admin_accounts.submit_grant(row.account.id, role)}
145
153
  title="offer {role}"
146
154
  class="sm"
147
- disabled={admin_accounts.granting_keys.has(`${row.account.id}:${role}`)}
155
+ label={`+ ${role}`}
156
+ pending={admin_accounts.grant.loading(key)}
148
157
  >
149
- {#snippet children(_popover, _confirm)}
150
- {admin_accounts.granting_keys.has(`${row.account.id}:${role}`)
151
- ? 'offering…'
152
- : `+ ${role}`}
153
- {/snippet}
154
158
  {#snippet popover_content(_popover, do_confirm)}
155
159
  <button type="button" class="color_b bg_100" onclick={() => do_confirm()}>
156
160
  <span class="py_sm">offer '{role}' to @{row.account.username}</span>
157
161
  </button>
158
162
  {/snippet}
159
163
  </ConfirmButton>
164
+ {#if grant_error}
165
+ <span class="color_c_50 font_size_sm">{grant_error}</span>
166
+ {/if}
160
167
  {/if}
161
168
  {/each}
162
169
  {/if}
@@ -1 +1 @@
1
- {"version":3,"file":"AdminAccounts.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminAccounts.svelte"],"names":[],"mappings":"AA+JA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"AdminAccounts.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminAccounts.svelte"],"names":[],"mappings":"AAmKA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -98,10 +98,10 @@
98
98
  {/if}
99
99
  </div>
100
100
 
101
- {#if audit_log.loading}
101
+ {#if audit_log.list.loading}
102
102
  <p class="text_50">loading audit log...</p>
103
- {:else if audit_log.error}
104
- <p class="color_c_50">{audit_log.error}</p>
103
+ {:else if audit_log.list.error}
104
+ <p class="color_c_50">{audit_log.list.error}</p>
105
105
  {:else}
106
106
  <Datatable {columns} rows={audit_log.events} height="500px">
107
107
  {#snippet cell(column, row)}
@@ -25,12 +25,12 @@
25
25
  let invite_username = $state.raw('');
26
26
 
27
27
  const can_create = $derived(
28
- (invite_email.trim() || invite_username.trim()) && !admin_invites.creating,
28
+ (invite_email.trim() || invite_username.trim()) && !admin_invites.create.loading,
29
29
  );
30
30
 
31
31
  const handle_create = async (): Promise<void> => {
32
32
  if (!can_create) return;
33
- const success = await admin_invites.create_invite(
33
+ const success = await admin_invites.submit_create(
34
34
  invite_email.trim() || undefined,
35
35
  invite_username.trim() || undefined,
36
36
  );
@@ -80,7 +80,7 @@
80
80
  type="email"
81
81
  bind:value={invite_email}
82
82
  placeholder="email (optional)"
83
- disabled={admin_invites.creating}
83
+ disabled={admin_invites.create.loading}
84
84
  />
85
85
  </label>
86
86
  <label class="grow">
@@ -89,20 +89,24 @@
89
89
  type="text"
90
90
  bind:value={invite_username}
91
91
  placeholder="username (optional)"
92
- disabled={admin_invites.creating}
92
+ disabled={admin_invites.create.loading}
93
93
  />
94
94
  </label>
95
95
  </fieldset>
96
- <PendingButton pending={admin_invites.creating} disabled={!can_create} onclick={handle_create}>
96
+ <PendingButton
97
+ pending={admin_invites.create.loading}
98
+ disabled={!can_create}
99
+ onclick={handle_create}
100
+ >
97
101
  create invite
98
102
  </PendingButton>
99
103
  </form>
100
104
 
101
- {#if admin_invites.error}
102
- <p class="color_c_50">{admin_invites.error}</p>
105
+ {#if admin_invites.list.error || admin_invites.create.error}
106
+ <p class="color_c_50">{admin_invites.list.error ?? admin_invites.create.error}</p>
103
107
  {/if}
104
108
 
105
- {#if admin_invites.loading}
109
+ {#if admin_invites.list.loading}
106
110
  <p class="text_50">loading invites...</p>
107
111
  {:else}
108
112
  <Datatable {columns} rows={admin_invites.invites} height="400px">
@@ -129,16 +133,17 @@
129
133
  </span>
130
134
  {:else if column.key === 'id'}
131
135
  {#if !row.claimed_at}
136
+ {@const remove_error = admin_invites.remove.error(row.id)}
132
137
  <ConfirmButton
133
- onconfirm={() => admin_invites.delete_invite(row.id)}
138
+ onconfirm={() => admin_invites.submit_delete(row.id)}
134
139
  title="delete invite"
135
140
  class="sm"
136
- disabled={admin_invites.deleting_ids.has(row.id)}
137
- >
138
- {#snippet children(_popover, _confirm)}
139
- {admin_invites.deleting_ids.has(row.id) ? 'deleting...' : 'delete'}
140
- {/snippet}
141
- </ConfirmButton>
141
+ label="delete"
142
+ pending={admin_invites.remove.loading(row.id)}
143
+ />
144
+ {#if remove_error}
145
+ <span class="color_c_50 font_size_sm">{remove_error}</span>
146
+ {/if}
142
147
  {:else}
143
148
  <span class="text_50">-</span>
144
149
  {/if}