@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.
- package/dist/actions/CLAUDE.md +28 -22
- package/dist/auth/CLAUDE.md +4 -4
- package/dist/server/app_server.d.ts +54 -6
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -4
- package/dist/testing/CLAUDE.md +8 -8
- package/dist/ui/AccountSessions.svelte +21 -6
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +32 -25
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -3
- package/dist/ui/AdminInvites.svelte +20 -15
- package/dist/ui/AdminOverview.svelte +19 -21
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminRoleGrantHistory.svelte +3 -3
- package/dist/ui/AdminSessions.svelte +19 -21
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSettings.svelte +1 -3
- package/dist/ui/AdminSettings.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +123 -69
- package/dist/ui/ConfirmButton.svelte +82 -24
- package/dist/ui/ConfirmButton.svelte.d.ts +8 -34
- package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -1
- package/dist/ui/OpenSignupToggle.svelte +6 -4
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/RoleGrantOfferForm.svelte +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte +3 -3
- package/dist/ui/RoleGrantOfferInbox.svelte +10 -6
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +17 -7
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +32 -33
- package/dist/ui/admin_accounts_state.svelte.d.ts +48 -17
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +58 -76
- package/dist/ui/admin_invites_state.svelte.d.ts +14 -7
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +32 -48
- package/dist/ui/admin_sessions_state.svelte.d.ts +15 -8
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +30 -47
- package/dist/ui/app_settings_state.svelte.d.ts +8 -3
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +19 -27
- package/dist/ui/async_slot.svelte.d.ts +173 -0
- package/dist/ui/async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/async_slot.svelte.js +241 -0
- package/dist/ui/audit_log_state.svelte.d.ts +8 -2
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +19 -18
- package/dist/ui/keyed_async_slot.svelte.d.ts +139 -0
- package/dist/ui/keyed_async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/keyed_async_slot.svelte.js +177 -0
- package/dist/ui/role_grant_offers_state.svelte.d.ts +39 -7
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.js +34 -15
- package/dist/ui/table_state.svelte.d.ts +10 -7
- package/dist/ui/table_state.svelte.d.ts.map +1 -1
- package/dist/ui/table_state.svelte.js +11 -8
- package/package.json +1 -1
- package/dist/ui/loadable.svelte.d.ts +0 -60
- package/dist/ui/loadable.svelte.d.ts.map +0 -1
- package/dist/ui/loadable.svelte.js +0 -80
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
<h3>accounts</h3>
|
|
90
90
|
<a href={resolve('/admin/accounts' as any)} class="text_50 font_size_sm">view all →</a>
|
|
91
91
|
</div>
|
|
92
|
-
{#if accounts.loading}
|
|
92
|
+
{#if accounts.list.loading}
|
|
93
93
|
<p class="text_50">loading...</p>
|
|
94
|
-
{:else if accounts.error}
|
|
95
|
-
<p class="color_c_50">{accounts.error}</p>
|
|
94
|
+
{:else if accounts.list.error}
|
|
95
|
+
<p class="color_c_50">{accounts.list.error}</p>
|
|
96
96
|
{:else}
|
|
97
97
|
<div class="baseline-row gap_xs">
|
|
98
98
|
<strong class="font_size_lg">{accounts.account_count}</strong>
|
|
@@ -131,10 +131,10 @@
|
|
|
131
131
|
<h3>sessions</h3>
|
|
132
132
|
<a href={resolve('/admin/sessions' as any)} class="text_50 font_size_sm">view all →</a>
|
|
133
133
|
</div>
|
|
134
|
-
{#if sessions.loading}
|
|
134
|
+
{#if sessions.list.loading}
|
|
135
135
|
<p class="text_50">loading...</p>
|
|
136
|
-
{:else if sessions.error}
|
|
137
|
-
<p class="color_c_50">{sessions.error}</p>
|
|
136
|
+
{:else if sessions.list.error}
|
|
137
|
+
<p class="color_c_50">{sessions.list.error}</p>
|
|
138
138
|
{:else}
|
|
139
139
|
<div class="baseline-row gap_xs">
|
|
140
140
|
<strong class="font_size_lg">{sessions.active_count}</strong>
|
|
@@ -161,10 +161,10 @@
|
|
|
161
161
|
<h3>invites</h3>
|
|
162
162
|
<a href={resolve('/admin/invites' as any)} class="text_50 font_size_sm">view all →</a>
|
|
163
163
|
</div>
|
|
164
|
-
{#if invites.loading}
|
|
164
|
+
{#if invites.list.loading}
|
|
165
165
|
<p class="text_50">loading...</p>
|
|
166
|
-
{:else if invites.error}
|
|
167
|
-
<p class="color_c_50">{invites.error}</p>
|
|
166
|
+
{:else if invites.list.error}
|
|
167
|
+
<p class="color_c_50">{invites.list.error}</p>
|
|
168
168
|
{:else}
|
|
169
169
|
<div class="baseline-row gap_sm">
|
|
170
170
|
<span class="text_50">public signup</span>
|
|
@@ -206,10 +206,10 @@
|
|
|
206
206
|
<h3>recent activity</h3>
|
|
207
207
|
<a href={resolve('/admin/audit-log' as any)} class="text_50 font_size_sm">view all →</a>
|
|
208
208
|
</div>
|
|
209
|
-
{#if audit_log.loading}
|
|
209
|
+
{#if audit_log.list.loading}
|
|
210
210
|
<p class="text_50">loading...</p>
|
|
211
|
-
{:else if audit_log.error}
|
|
212
|
-
<p class="color_c_50">{audit_log.error}</p>
|
|
211
|
+
{:else if audit_log.list.error}
|
|
212
|
+
<p class="color_c_50">{audit_log.list.error}</p>
|
|
213
213
|
{:else if recent_events.length === 0}
|
|
214
214
|
<p class="text_50">no events</p>
|
|
215
215
|
{:else}
|
|
@@ -234,10 +234,10 @@
|
|
|
234
234
|
<h3>security</h3>
|
|
235
235
|
<a href={resolve('/admin/audit-log' as any)} class="text_50 font_size_sm">audit log →</a>
|
|
236
236
|
</div>
|
|
237
|
-
{#if audit_log.loading}
|
|
237
|
+
{#if audit_log.list.loading}
|
|
238
238
|
<p class="text_50">loading...</p>
|
|
239
|
-
{:else if audit_log.error}
|
|
240
|
-
<p class="color_c_50">{audit_log.error}</p>
|
|
239
|
+
{:else if audit_log.list.error}
|
|
240
|
+
<p class="color_c_50">{audit_log.list.error}</p>
|
|
241
241
|
{:else}
|
|
242
242
|
<div class="baseline-row gap_xs">
|
|
243
243
|
<strong class="font_size_lg" class:color_c_50={failed_logins.length > 0}>
|
|
@@ -271,10 +271,10 @@
|
|
|
271
271
|
<div class="panel-header">
|
|
272
272
|
<h3>system</h3>
|
|
273
273
|
</div>
|
|
274
|
-
{#if app_settings.loading}
|
|
274
|
+
{#if app_settings.list.loading}
|
|
275
275
|
<p class="text_50">loading...</p>
|
|
276
|
-
{:else if app_settings.error}
|
|
277
|
-
<p class="color_c_50">{app_settings.error}</p>
|
|
276
|
+
{:else if app_settings.list.error}
|
|
277
|
+
<p class="color_c_50">{app_settings.list.error}</p>
|
|
278
278
|
{:else}
|
|
279
279
|
<div class="baseline-row gap_sm">
|
|
280
280
|
<span class="text_50">public signup</span>
|
|
@@ -308,10 +308,8 @@
|
|
|
308
308
|
await auth_state.logout();
|
|
309
309
|
}}
|
|
310
310
|
title="log out"
|
|
311
|
+
label="log out"
|
|
311
312
|
>
|
|
312
|
-
{#snippet children(_popover, _confirm)}
|
|
313
|
-
log out
|
|
314
|
-
{/snippet}
|
|
315
313
|
{#snippet popover_button_content()}
|
|
316
314
|
<span class="p_md"> log out </span>
|
|
317
315
|
{/snippet}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"AA2UA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
<section>
|
|
41
41
|
<h1>role_grant history</h1>
|
|
42
42
|
|
|
43
|
-
{#if audit_log.loading}
|
|
43
|
+
{#if audit_log.role_grant_history.loading}
|
|
44
44
|
<p class="text_50">loading role_grant history...</p>
|
|
45
|
-
{:else if audit_log.error}
|
|
46
|
-
<p class="color_c_50">{audit_log.error}</p>
|
|
45
|
+
{:else if audit_log.role_grant_history.error}
|
|
46
|
+
<p class="color_c_50">{audit_log.role_grant_history.error}</p>
|
|
47
47
|
{:else}
|
|
48
48
|
<Datatable {columns} rows={audit_log.role_grant_history_events} height="400px" row_key="id">
|
|
49
49
|
{#snippet cell(column, row)}
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
</p>
|
|
41
41
|
{/if}
|
|
42
42
|
|
|
43
|
-
{#if admin_sessions.loading}
|
|
43
|
+
{#if admin_sessions.list.loading}
|
|
44
44
|
<p class="text_50">loading sessions...</p>
|
|
45
|
-
{:else if admin_sessions.error}
|
|
46
|
-
<p class="color_c_50">{admin_sessions.error}</p>
|
|
45
|
+
{:else if admin_sessions.list.error}
|
|
46
|
+
<p class="color_c_50">{admin_sessions.list.error}</p>
|
|
47
47
|
{:else}
|
|
48
48
|
<Datatable {columns} rows={admin_sessions.sessions} height="400px">
|
|
49
49
|
{#snippet cell(column, row)}
|
|
@@ -63,30 +63,28 @@
|
|
|
63
63
|
</span>
|
|
64
64
|
{:else if column.key === 'account_id'}
|
|
65
65
|
{#if admin_sessions.has_rpc}
|
|
66
|
+
{@const revoke_sessions_error = admin_sessions.revoke_sessions.error(row.account_id)}
|
|
67
|
+
{@const revoke_tokens_error = admin_sessions.revoke_tokens.error(row.account_id)}
|
|
66
68
|
<ConfirmButton
|
|
67
|
-
onconfirm={() => admin_sessions.
|
|
69
|
+
onconfirm={() => admin_sessions.submit_revoke_sessions(row.account_id)}
|
|
68
70
|
title="revoke all sessions for {row.username}"
|
|
69
71
|
class="sm"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{/snippet}
|
|
77
|
-
</ConfirmButton>
|
|
72
|
+
label="revoke sessions"
|
|
73
|
+
pending={admin_sessions.revoke_sessions.loading(row.account_id)}
|
|
74
|
+
/>
|
|
75
|
+
{#if revoke_sessions_error}
|
|
76
|
+
<span class="color_c_50 font_size_sm">{revoke_sessions_error}</span>
|
|
77
|
+
{/if}
|
|
78
78
|
<ConfirmButton
|
|
79
|
-
onconfirm={() => admin_sessions.
|
|
79
|
+
onconfirm={() => admin_sessions.submit_revoke_tokens(row.account_id)}
|
|
80
80
|
title="revoke all tokens for {row.username}"
|
|
81
81
|
class="sm"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{/snippet}
|
|
89
|
-
</ConfirmButton>
|
|
82
|
+
label="revoke tokens"
|
|
83
|
+
pending={admin_sessions.revoke_tokens.loading(row.account_id)}
|
|
84
|
+
/>
|
|
85
|
+
{#if revoke_tokens_error}
|
|
86
|
+
<span class="color_c_50 font_size_sm">{revoke_tokens_error}</span>
|
|
87
|
+
{/if}
|
|
90
88
|
{/if}
|
|
91
89
|
{:else if column.format}
|
|
92
90
|
{column.format(row[column.key], row)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"AAoGA,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,aAAa;;kBAA+E,CAAC;AACjF,KAAK,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAC1D,eAAe,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSettings.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSettings.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminSettings.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSettings.svelte"],"names":[],"mappings":"AA8CA,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,aAAa;;kBAA+E,CAAC;AACjF,KAAK,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAC1D,eAAe,aAAa,CAAC"}
|
package/dist/ui/CLAUDE.md
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Frontend subsystem — Svelte 5 components, reactive state classes, and DOM
|
|
4
4
|
utilities. Cookie-based SPA auth; prerendered static HTML served by Hono
|
|
5
|
-
(no SvelteKit SSR for sessions). State classes
|
|
6
|
-
|
|
5
|
+
(no SvelteKit SSR for sessions). State classes hold one or more `AsyncSlot`s
|
|
6
|
+
via composition (one per distinct async operation — e.g. `list` + `create` +
|
|
7
|
+
`revoke`); per-row write ops use `KeyedAsyncSlot<K, T = void, E = string>`
|
|
8
|
+
(supersedes the old `AsyncSlot` + `SvelteSet<id>` pair) so concurrent rows
|
|
9
|
+
don't abort each other and failures surface per-row via `slot.error(key)`.
|
|
10
|
+
Payload lives as `$state.raw` fields on the class. Shared dependencies flow
|
|
7
11
|
through Svelte context, never through props — RPC adapters in particular
|
|
8
12
|
are provisioned once at the admin shell and read by every `Admin*.svelte`.
|
|
9
13
|
|
|
@@ -128,14 +132,18 @@ destructive actions.
|
|
|
128
132
|
- `AdminAccounts.svelte` — accounts + role_grants + pending offers.
|
|
129
133
|
Consumes `admin_accounts_rpc_context`. Per-row actions: grant (+role
|
|
130
134
|
chip with `ConfirmButton`), revoke (`actor_id` + `role_grant_id`),
|
|
131
|
-
retract pending offer.
|
|
132
|
-
`
|
|
135
|
+
retract pending offer. Reads per-row spinner + error state via
|
|
136
|
+
`state.grant.loading(key)` / `state.revoke.loading(role_grant_id)` /
|
|
137
|
+
`state.retract.loading(offer_id)` and their `.error(key)` siblings —
|
|
138
|
+
per-row error displays inline next to the failing button (no
|
|
139
|
+
top-level rollup).
|
|
133
140
|
- `AdminAuditLog.svelte` — audit event stream. Consumes
|
|
134
141
|
`audit_log_rpc_context`. Filter by `event_type`, manual refresh,
|
|
135
142
|
toggle SSE streaming (via `EventSource` — not RPC).
|
|
136
143
|
- `AdminInvites.svelte` — invite CRUD + embeds `OpenSignupToggle`.
|
|
137
|
-
Consumes `admin_invites_rpc_context`.
|
|
138
|
-
`
|
|
144
|
+
Consumes `admin_invites_rpc_context`. Per-row delete reads
|
|
145
|
+
`state.remove.loading(invite_id)` / `state.remove.error(invite_id)`
|
|
146
|
+
with inline per-row error display.
|
|
139
147
|
- `AdminOverview.svelte` — dashboard panels (accounts / sessions /
|
|
140
148
|
invites / recent activity / security / system). Consumes all four
|
|
141
149
|
RPC contexts plus `auth_state_context`; fetches in parallel on mount.
|
|
@@ -185,27 +193,56 @@ destructive actions.
|
|
|
185
193
|
`format_scope?`, `format_role?`. Consumes
|
|
186
194
|
`role_grant_offers_state_context`; caller seeds via
|
|
187
195
|
`RoleGrantOffersState.fetch_history()`.
|
|
188
|
-
- `role_grant_offers_state.svelte.ts` — `RoleGrantOffersState`
|
|
189
|
-
`
|
|
190
|
-
`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
`
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
- `role_grant_offers_state.svelte.ts` — `RoleGrantOffersState` +
|
|
197
|
+
`role_grant_offers_state_context`. Options: `rpc: RoleGrantOffersRpc`,
|
|
198
|
+
`account_id: () => string | null`, `actor_id: () => string | null`.
|
|
199
|
+
The narrow `RoleGrantOffersRpc` interface has six methods: `list`,
|
|
200
|
+
`history`, `create`, `accept`, `decline`, `retract`. Holds six
|
|
201
|
+
`AsyncSlot`s — five `AsyncSlot<void>` for status/error tracking
|
|
202
|
+
(`list` / `list_history` / `accept` / `decline` / `retract`) plus
|
|
203
|
+
one `AsyncSlot<RoleGrantOfferJson>` (`create`) that owns the
|
|
204
|
+
created offer so `submit_create` returns it via the slot's
|
|
205
|
+
supersession-safe `data` path. The `$state.raw` Map cache keyed by
|
|
206
|
+
offer id stays on the class (multiple ops + WS notifications merge
|
|
207
|
+
into it). Methods use the `submit_*` prefix to avoid slot-name
|
|
208
|
+
collisions (`submit_create` / `submit_accept` / `submit_decline` /
|
|
209
|
+
`submit_retract`); the fetch slot is named `list_history` so the
|
|
210
|
+
derived view stays natural as `history`. `$derived.by` views:
|
|
211
|
+
`incoming` (recipient-side pending, soonest-expiry first),
|
|
212
|
+
`outgoing` (grantor-side pending, newest-created first), `history`
|
|
213
|
+
(all known, newest-created first). Reducer `apply_notification`
|
|
214
|
+
handles the six role-grant-offer notification methods;
|
|
215
|
+
`role_grant_revoke` is deliberately ignored here (auth/role_grants
|
|
216
|
+
concern). `reset()` clears every slot + the Map.
|
|
200
217
|
|
|
201
218
|
## State primitives
|
|
202
219
|
|
|
203
|
-
- `
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
- `async_slot.svelte.ts` — `AsyncSlot<T = void, E = string>`. Composable
|
|
221
|
+
reactive container for one async operation. Surface: explicit
|
|
222
|
+
four-value `status` (`'initial' | 'pending' | 'success' | 'failure'`),
|
|
223
|
+
derived `initial` / `loading` / `succeeded` / `failed`, supersession
|
|
224
|
+
via internal `AbortController` (a second `run()` aborts the first
|
|
225
|
+
and silently drops its commit), `AbortSignal` threaded to the
|
|
226
|
+
callback + external-signal hookup via `RunOptions`, per-slot
|
|
227
|
+
`map_error` set once in the constructor, opt-in
|
|
228
|
+
`preserve_error_on_retry`, public `run()` / `abort()` / `set()` /
|
|
229
|
+
`reset()`. Slots are HELD by state classes via composition (one per
|
|
230
|
+
distinct async op), not subclassed. Payload typically lives on the
|
|
231
|
+
state class as `$state.raw` fields; `slot.data` is reserved for
|
|
232
|
+
cases where the slot owns the result.
|
|
233
|
+
- `keyed_async_slot.svelte.ts` — `KeyedAsyncSlot<K, T = void, E = string>`.
|
|
234
|
+
Keyed sibling of `AsyncSlot` — lazily creates a child slot per key
|
|
235
|
+
in a `SvelteMap`, propagating `map_error` / `preserve_error_on_retry`
|
|
236
|
+
to each child. Replaces the `AsyncSlot` + `SvelteSet<id>` pair: each
|
|
237
|
+
key has its own `AbortController`, so a `run(b, ...)` does NOT abort
|
|
238
|
+
an in-flight `run(a, ...)`, and `error(key)` surfaces per-row.
|
|
239
|
+
Reactive sugar: `loading(key)`, `error(key)`, `failed(key)`,
|
|
240
|
+
`succeeded(key)`, `has(key)`, `size`, plus `get(key)` for full slot
|
|
241
|
+
access. Resolved entries persist (no auto-cleanup) so components can
|
|
242
|
+
render per-row error indicators after the run completes; call
|
|
243
|
+
`delete(key)` to dismiss an entry or `reset()` to wipe everything.
|
|
244
|
+
`abort(key)` / `abort_all()` cancel without removing entries.
|
|
245
|
+
`entries()` / `keys()` / `values()` iterate for cross-key views.
|
|
209
246
|
- `auth_state.svelte.ts` — `AuthState`, `auth_state_context`.
|
|
210
247
|
Fields: `verifying`, `verified`, `verify_error`, `account`, `actor`
|
|
211
248
|
(the caller's own `ActorSummaryJson` — surfaced directly so consumers
|
|
@@ -214,11 +251,14 @@ destructive actions.
|
|
|
214
251
|
`needs_bootstrap`. Methods: `check_session()`
|
|
215
252
|
(GET `/api/account/status`), `login`, `bootstrap`, `signup`,
|
|
216
253
|
`logout`. Handles 401/403/409/429 translations inline.
|
|
217
|
-
- `table_state.svelte.ts` — `TableState
|
|
218
|
-
|
|
219
|
-
`total`, `offset`, `limit`
|
|
220
|
-
`primary_key
|
|
221
|
-
`has_next`. Methods:
|
|
254
|
+
- `table_state.svelte.ts` — `TableState`. Paginated DB browser state.
|
|
255
|
+
Holds one `AsyncSlot` (`list`) + payload fields (`table_name`,
|
|
256
|
+
`columns`, `rows`, `total`, `offset`, `limit` capped by
|
|
257
|
+
`TABLE_LIMIT_MAX = 1000`, `primary_key`). Derived
|
|
258
|
+
`showing_start`/`showing_end`/`has_prev`/`has_next`. Methods:
|
|
259
|
+
`fetch`, `go_prev`/`go_next`, `delete_row`. `delete_row` uses
|
|
260
|
+
plain try/catch + scalar `deleting` / `delete_error` fields (no
|
|
261
|
+
slot — error must survive past `list.run()` retries).
|
|
222
262
|
- `form_state.svelte.ts` — `FormState`. Enter-advance between
|
|
223
263
|
focusable elements via `keydown`; per-field `touched` set via
|
|
224
264
|
delegated `focusout`; form-level `attempted` set on submit attempt.
|
|
@@ -231,47 +271,61 @@ destructive actions.
|
|
|
231
271
|
|
|
232
272
|
## Per-domain state modules
|
|
233
273
|
|
|
234
|
-
- `
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
`
|
|
250
|
-
`
|
|
274
|
+
All state classes hold per-op `AsyncSlot`s for the fetch + singular
|
|
275
|
+
write verbs, and `KeyedAsyncSlot`s for per-row write verbs (the
|
|
276
|
+
`SvelteSet<id>` pattern is retired — per-row tracking lives on the
|
|
277
|
+
keyed slot's `loading(key)` / `error(key)` accessors). Method names use
|
|
278
|
+
the `submit_*` prefix where the verb collides with a slot name.
|
|
279
|
+
|
|
280
|
+
- `account_sessions_state.svelte.ts` — `AccountSessionsState` +
|
|
281
|
+
`account_sessions_rpc_context` + narrow `AccountSessionsRpc`
|
|
282
|
+
(`list`, `revoke`, `revoke_all`). Slots: `list` (AsyncSlot),
|
|
283
|
+
`revoke` (`KeyedAsyncSlot<string, void>` keyed by `session_id` for
|
|
284
|
+
per-row independence), `revoke_all` (AsyncSlot). Methods: `fetch`,
|
|
285
|
+
`submit_revoke(id)`, `submit_revoke_all`. Derived `active_count`.
|
|
286
|
+
- `audit_log_state.svelte.ts` — `AuditLogState` +
|
|
287
|
+
`audit_log_rpc_context` + narrow `AuditLogRpc` (`list` +
|
|
288
|
+
`role_grant_history`). Slots: `list`, `role_grant_history`. Fields:
|
|
289
|
+
`events`, `role_grant_history_events`, `connected`. Internal
|
|
290
|
+
`#last_seq` for SSE gap fill on reconnect. Methods:
|
|
291
|
+
`fetch(options?)` (RPC), `fetch_role_grant_history`, `subscribe()`
|
|
292
|
+
(opens `EventSource` at `#stream_url`, default
|
|
293
|
+
`/api/admin/audit/stream`; prepends new events to `events`; refills
|
|
294
|
+
gap via `since_seq`), `disconnect()`. SSE stays on `EventSource` —
|
|
295
|
+
streaming is not an RPC concern.
|
|
296
|
+
- `admin_accounts_state.svelte.ts` — `AdminAccountsState` +
|
|
297
|
+
`admin_accounts_rpc_context` + narrow `AdminAccountsRpc` (seven
|
|
298
|
+
methods: `list_accounts`, `list_sessions`, `create_role_grant`,
|
|
251
299
|
`revoke_role_grant`, `retract_offer`, `session_revoke_all`,
|
|
252
|
-
`token_revoke_all` — the last
|
|
253
|
-
`AdminSessionsState`). `
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
`
|
|
262
|
-
`
|
|
263
|
-
|
|
264
|
-
`
|
|
265
|
-
|
|
266
|
-
`
|
|
267
|
-
`
|
|
268
|
-
`
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
`
|
|
273
|
-
|
|
274
|
-
`
|
|
300
|
+
`token_revoke_all` — the last three are also reused by
|
|
301
|
+
`AdminSessionsState`). Slots: `list` (AsyncSlot), `grant`
|
|
302
|
+
(`KeyedAsyncSlot<string, RoleGrantOfferJson>` — slot owns the
|
|
303
|
+
created offer; key composed by exported
|
|
304
|
+
`grant_key(account_id, role, to_actor_id?)`, 2-segment for
|
|
305
|
+
account-grain, 3-segment when actor-targeted), `revoke`
|
|
306
|
+
(`KeyedAsyncSlot<Uuid, void>` keyed by `role_grant_id`), `retract`
|
|
307
|
+
(`KeyedAsyncSlot<Uuid, void>` keyed by `offer_id`). `submit_revoke`
|
|
308
|
+
takes `actor_id` as the first arg (role_grants are actor-scoped —
|
|
309
|
+
matches `row.actor.id` straight from the listing) with optional
|
|
310
|
+
`reason`.
|
|
311
|
+
- `admin_invites_state.svelte.ts` — `AdminInvitesState` +
|
|
312
|
+
`admin_invites_rpc_context` + narrow `AdminInvitesRpc` (`list`,
|
|
313
|
+
`create`, `delete`). Slots: `list`, `create` (both AsyncSlot),
|
|
314
|
+
`remove` (`KeyedAsyncSlot<Uuid, void>` keyed by `invite_id`).
|
|
315
|
+
Field: `invites`; derived `invite_count`, `unclaimed_count`.
|
|
316
|
+
Methods: `fetch`, `submit_create`, `submit_delete`. (Slot `remove`
|
|
317
|
+
instead of `delete` to avoid keyword shadowing.)
|
|
318
|
+
- `admin_sessions_state.svelte.ts` — `AdminSessionsState`. **Reuses**
|
|
319
|
+
`admin_accounts_rpc_context` / `AdminAccountsRpc` for the listing
|
|
320
|
+
(`list_sessions` wraps `admin_session_list`) and the two revoke-all
|
|
321
|
+
mutations. Slots: `list` (AsyncSlot), `revoke_sessions` /
|
|
322
|
+
`revoke_tokens` (`KeyedAsyncSlot<Uuid, void>` keyed by
|
|
323
|
+
`account_id`). `has_rpc` gates the listing + both revoke controls.
|
|
324
|
+
Methods: `fetch`, `submit_revoke_sessions`, `submit_revoke_tokens`.
|
|
325
|
+
- `app_settings_state.svelte.ts` — `AppSettingsState` +
|
|
326
|
+
`app_settings_rpc_context` + narrow `AppSettingsRpc` (`get`,
|
|
327
|
+
`update`). Slots: `list`, `update`. Field: `settings`. Single
|
|
328
|
+
mutation `update_open_signup(boolean)`.
|
|
275
329
|
- `admin_rpc_adapters.ts` (plain `.ts`, no reactive state) — bundled
|
|
276
330
|
wiring for the four admin RPC contexts. `create_admin_rpc_adapters(api)`
|
|
277
331
|
takes the typed throwing Proxy from `create_frontend_rpc_client` (or
|
|
@@ -6,19 +6,31 @@
|
|
|
6
6
|
* On confirm, calls `onconfirm` and hides the popover (controlled
|
|
7
7
|
* by `hide_on_confirm`). Defaults to `position="left"`.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Trigger content: pass `label` for a simple string, or a `children`
|
|
10
|
+
* snippet for custom content (the two are mutually exclusive — DEV
|
|
11
|
+
* errors when both are set). `pending: boolean` overlays a spinner
|
|
12
|
+
* and disables the trigger, mirroring `PendingButton` semantics so
|
|
13
|
+
* the label stays put while an async operation runs.
|
|
11
14
|
*
|
|
12
15
|
* @example
|
|
13
16
|
* ```svelte
|
|
14
17
|
* <ConfirmButton
|
|
15
18
|
* onconfirm={() => delete_item(item.id)}
|
|
16
19
|
* title="delete item"
|
|
17
|
-
*
|
|
20
|
+
* label="delete"
|
|
21
|
+
* pending={state.remove.loading(item.id)}
|
|
22
|
+
* />
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```svelte
|
|
27
|
+
* <!-- custom trigger content via the children snippet -->
|
|
28
|
+
* <ConfirmButton
|
|
29
|
+
* onconfirm={() => grant(item.id, role)}
|
|
30
|
+
* title="offer {role}"
|
|
31
|
+
* pending={state.grant.loading(key)}
|
|
18
32
|
* >
|
|
19
|
-
* {#snippet children(_popover, _confirm)}
|
|
20
|
-
* {deleting ? 'deleting…' : 'delete'}
|
|
21
|
-
* {/snippet}
|
|
33
|
+
* {#snippet children(_popover, _confirm)}+ {role}{/snippet}
|
|
22
34
|
* </ConfirmButton>
|
|
23
35
|
* ```
|
|
24
36
|
*
|
|
@@ -34,10 +46,12 @@
|
|
|
34
46
|
* @module
|
|
35
47
|
*/
|
|
36
48
|
|
|
49
|
+
import {DEV} from 'esm-env';
|
|
37
50
|
import type {SvelteHTMLElements} from 'svelte/elements';
|
|
38
51
|
import type {ComponentProps, Snippet} from 'svelte';
|
|
39
52
|
import type {OmitStrict} from '@fuzdev/fuz_util/types.js';
|
|
40
53
|
import Glyph from '@fuzdev/fuz_ui/Glyph.svelte';
|
|
54
|
+
import PendingAnimation from '@fuzdev/fuz_ui/PendingAnimation.svelte';
|
|
41
55
|
|
|
42
56
|
import PopoverButton from './PopoverButton.svelte';
|
|
43
57
|
import type {Popover} from './popover.svelte.js';
|
|
@@ -53,6 +67,9 @@
|
|
|
53
67
|
popover_button_content,
|
|
54
68
|
button,
|
|
55
69
|
children,
|
|
70
|
+
label,
|
|
71
|
+
pending = false,
|
|
72
|
+
disabled: disabled_prop,
|
|
56
73
|
...rest
|
|
57
74
|
}: OmitStrict<ComponentProps<typeof PopoverButton>, 'popover_content' | 'children'> &
|
|
58
75
|
OmitStrict<SvelteHTMLElements['button'], 'children'> & {
|
|
@@ -65,21 +82,34 @@
|
|
|
65
82
|
popover_button_content?: Snippet<[popover: Popover, confirm: () => void]> | undefined;
|
|
66
83
|
/** Unlike on `PopoverButton` this has a `confirm` arg */
|
|
67
84
|
children?: Snippet<[popover: Popover, confirm: () => void]> | undefined;
|
|
85
|
+
/** Simple string content for the trigger. Mutually exclusive with `children`. */
|
|
86
|
+
label?: string | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* When `true`, the trigger is disabled and a spinner overlays the
|
|
89
|
+
* content (mirrors `PendingButton`). The label / children stay
|
|
90
|
+
* rendered underneath so the button keeps its size.
|
|
91
|
+
*/
|
|
92
|
+
pending?: boolean | undefined;
|
|
68
93
|
} = $props();
|
|
69
94
|
|
|
70
95
|
// TODO @many type union instead of this pattern?
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
if (DEV) {
|
|
97
|
+
$effect(() => {
|
|
98
|
+
if (popover_content_prop && popover_button_attrs) {
|
|
99
|
+
console.error(
|
|
100
|
+
'ConfirmButton has both popover_content and popover_button_attrs defined - popover_content takes precedence',
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (popover_content_prop && popover_button_content) {
|
|
104
|
+
console.error(
|
|
105
|
+
'ConfirmButton has both popover_content and popover_button_content defined - popover_content takes precedence',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (label !== undefined && children) {
|
|
109
|
+
console.error('ConfirmButton has both label and children defined - pick one');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
83
113
|
|
|
84
114
|
const confirm = (popover: Popover): void => {
|
|
85
115
|
if (hide_on_confirm) popover.hide();
|
|
@@ -92,6 +122,7 @@
|
|
|
92
122
|
{position}
|
|
93
123
|
{button}
|
|
94
124
|
{...rest as any}
|
|
125
|
+
disabled={disabled_prop ?? pending}
|
|
95
126
|
children={button ? undefined : children_default}
|
|
96
127
|
>
|
|
97
128
|
{#snippet popover_content(popover)}
|
|
@@ -103,7 +134,7 @@
|
|
|
103
134
|
class="color_c bg_100"
|
|
104
135
|
class:icon_button={!popover_button_content}
|
|
105
136
|
onclick={() => confirm(popover)}
|
|
106
|
-
title=
|
|
137
|
+
title={rest.title ? `confirm ${rest.title}` : 'confirm'}
|
|
107
138
|
{...popover_button_attrs}
|
|
108
139
|
>
|
|
109
140
|
{#if popover_button_content}
|
|
@@ -117,9 +148,36 @@
|
|
|
117
148
|
</PopoverButton>
|
|
118
149
|
|
|
119
150
|
{#snippet children_default(popover: Popover)}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
<span class="trigger" class:pending>
|
|
152
|
+
<span class="content">
|
|
153
|
+
{#if children}
|
|
154
|
+
{@render children(popover, () => confirm(popover))}
|
|
155
|
+
{:else if label !== undefined}
|
|
156
|
+
{label}
|
|
157
|
+
{:else}
|
|
158
|
+
<Glyph glyph={GLYPH_REMOVE} />
|
|
159
|
+
{/if}
|
|
160
|
+
</span>
|
|
161
|
+
{#if pending}
|
|
162
|
+
<span class="animation">
|
|
163
|
+
<PendingAnimation inline />
|
|
164
|
+
</span>
|
|
165
|
+
{/if}
|
|
166
|
+
</span>
|
|
125
167
|
{/snippet}
|
|
168
|
+
|
|
169
|
+
<style>
|
|
170
|
+
.trigger {
|
|
171
|
+
position: relative;
|
|
172
|
+
}
|
|
173
|
+
.pending .content {
|
|
174
|
+
visibility: hidden;
|
|
175
|
+
}
|
|
176
|
+
.animation {
|
|
177
|
+
position: absolute;
|
|
178
|
+
inset: 0;
|
|
179
|
+
display: flex;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
align-items: center;
|
|
182
|
+
}
|
|
183
|
+
</style>
|