ui
44 modules
ui/account_sessions_state.svelte.ts
Reactive state for managing the authenticated account's auth sessions on a settings page. Reads and mutations flow through a narrow RPC adapter backed by auth/account_actions.ts.
Holds two AsyncSlots β
listfor the fetch,revoke_allfor the bulk revoke β plus oneKeyedAsyncSlot<string, void>(revoke) keyed bysession_idfor per-row revoke (independent supersession across concurrent rows; per-row error surfacing). Method names use thesubmit_*prefix to avoid slot-name collisions.ui/AccountSessions.svelte
Self-serve session list for the logged-in account. Instantiates an AccountSessionsState against account_sessions_rpc_context and renders a Datatable with per-row revoke and an optional revoke-all. Calling
revoke_allclearsauth_state.verifiedso the UI falls back to login.ui/admin_accounts_state.svelte.ts
Reactive state for admin account management.
Holds one fetch AsyncSlot (
list) plus three KeyedAsyncSlots βgrant(offer creation, keyed byaccount_id:roleoraccount_id:role:to_actor_id),revoke(role_grant revoke, keyed byrole_grant_id),retract(offer retraction, keyed byoffer_id). Per-row supersession is correct (clicking row B no longer aborts row A) anderror(key)surfaces failure per-row. Method names use thesubmit_*prefix to avoid slot-name collisions.ui/admin_invites_state.svelte.ts
Reactive state for admin invite management.
Flows every operation through an injected AdminInvitesRpc adapter β the class stays decoupled from the concrete RPC client so tests can inject plain-function stubs. Mirrors AdminAccountsRpc / AuditLogRpc.
Holds two AsyncSlots β
list(fetch) andcreate(singular write) β plus oneKeyedAsyncSlot<Uuid>(remove) for the per-row delete with correct per-row supersession and per-row error surfacing. Method names use thesubmit_*prefix to avoid slot-name collisions (deleteis reserved at top-level positions; renamed for symmetry).ui/admin_rpc_adapters.ts
Admin RPC adapter helpers for consumer UIs.
Bridges a typed throwing RPC client to the four narrow admin RPC interfaces the state classes consume β AdminAccountsRpc, AdminInvitesRpc, AuditLogRpc, AppSettingsRpc. Two calls at the admin shell layout wire everything.
Intentionally admin-only despite the backend-side create_standard_rpc_actions rename (admin + role-grant-offer + account). Account-surface methods flow through account_sessions_rpc_context (wired at the self-service layout), and role-grant-offer methods that surface in the admin UI (
role_grant_offer_create,role_grant_revoke,role_grant_offer_retract) live inside the AdminAccountsRpc interface β they belong to the admin UX, not a separate wire pairing. The UI side and backend factory names diverge by design.// `api` is the typed throwing Proxy from `create_frontend_rpc_client`. provide_admin_rpc_contexts(create_admin_rpc_adapters(api));The throwing Proxy spreads the JSON-RPC
{code, message, data?}onto the thrownErrorso form components (e.g. ui/RoleGrantOfferForm.svelte) can match onerror.data?.reasonviaERROR_ROLE_GRANT_OFFER_*constants β optional chaining is required because JSON-RPCdatais spec-level optional. Consumers that need a custom unwrap strategy can construct their own object satisfying AdminRpcApi and pass it directly.No
.svelte.tssuffix β this module holds no reactive state, only method-name mappings.ui/admin_sessions_state.svelte.ts
Reactive state for admin session overview.
Both the listing and the two revoke-all mutations flow through the shared AdminAccountsRpc adapter (
list_sessions,session_revoke_all,token_revoke_all); the listing wraps theadmin_session_listRPC method.Holds one fetch AsyncSlot (
list) plus two KeyedAsyncSlots keyed byaccount_idβrevoke_sessionsandrevoke_tokens. Per-account concurrent revokes are independent (clicking row B does not abort row A) and per-row errors surface viarevoke_sessions.error(account_id)/revoke_tokens.error(account_id).ui/AdminAccounts.svelte
Admin accounts table β users with their role_grants and pending offers. Consumes admin_accounts_rpc_context (read via AdminAccountsState) and format_scope_context for label rendering. Per-row actions: grant role (
role_grant_offer_create), revoke role_grant (role_grant_revoke, keyed byactor_id), retract pending offer (role_grant_offer_retract).ui/AdminAuditLog.svelte
Admin audit log viewer. Consumes audit_log_rpc_context; uses
audit_log_listRPC for fetches and a separateEventSourceagainst the SSE stream URL for live tailing (toggle via the stream button). Filter byevent_type, manual refresh, and live-stream connection status are surfaced in the header.ui/AdminInvites.svelte
Admin invites manager β list, create (
invite_create), and delete (invite_delete) RPC actions through admin_invites_rpc_context. Embeds OpenSignupToggle so the same surface controls both the invite-only and open-signup flows.ui/AdminOverview.svelte
Admin dashboard β six summary panels (accounts, sessions, invites, recent activity, security, system) fed by parallel
fetch()calls on mount. Consumes all four admin RPC contexts plus auth_state_context; derivesrole_counts,failed_logins, androle_grant_changesfrom the audit log slice.ui/AdminRoleGrantHistory.svelte
Role grant create/revoke history table. Consumes audit_log_rpc_context, calls
audit_log.fetch_role_grant_history()once on mount (theaudit_log_role_grant_historyRPC). Uses format_scope_context to render scope ids as human labels.ui/AdminSessions.svelte
Cross-account active session list with per-account revoke-all controls for sessions and tokens. Listing (
admin_session_list) and both revoke-all mutations (admin_session_revoke_all,admin_token_revoke_all) reuse admin_accounts_rpc_context β a single adapter backs both AdminSessionsState and AdminAccountsState.ui/AdminSettings.svelte
Admin settings shell β composes OpenSignupToggle, the logged-in account line, and a confirm-protected logout button. No direct RPC calls; reads auth_state_context and delegates settings to its children.
ui/AdminSurface.svelte
Attack-surface viewer. Fetches
GET /api/surface(REST β not RPC, since the surface dump exists outside the action surface) and delegates rendering to SurfaceExplorer. Surfaces a retry button on fetch failures.ui/app_settings_state.svelte.ts
Reactive state for admin app settings management.
Flows every operation through an injected AppSettingsRpc adapter β mirrors AdminInvitesRpc / AuditLogRpc. Tests can inject plain-function stubs and consumers adapt their typed RPC client to the same shape.
Holds two AsyncSlots β
list(the initial fetch) andupdate(theapp_settings_updatewrite). Slots track status/error; the canonicalsettingslives on the class so consumers don't unwrapslot.data.ui/AppShell.svelte
Sidebar-and-main app shell. Provisions sidebar_state_context (creating a fresh SidebarState if
sidebar_stateis not supplied) so descendants can read sidebar visibility and toggle it. Optionally binds a global keyboard shortcut and renders a built-in toggle button (or a custom one via thetoggle_buttonsnippet).ui/async_slot.svelte.ts
Composable async-operation slot for Svelte 5 reactive state classes.
A state class HOLDS one or more AsyncSlots via composition β one slot per distinct async operation (e.g.
list+create+revoke). Each slot tracks the status, payload, and error of its operation independently, so state classes with multiple write paths don't accumulate ad-hoccreating/updatingfields beside a single sharedloading/errorpair.Core surface:
- Explicit four-value
statusβAsyncStatusfrom@fuzdev/fuz_util/async.js: `'initial' | 'pending' | 'success' | 'failure'.loading: false, error: null` would be ambiguous between "never tried" and "succeeded once and now resting"; the four-value status removes the need for a per-classsubmitted/hydratedflag. - Ownsdata: T | undefinedβ the success payload persists across retries (stale-while-revalidate). The sentinel isundefined(notnull) sonullstays available as a legitimate success value for nullableTs. PassT = voidfor write-only actions whose response isn't worth keeping. - Supersession via internalAbortControllerβ a secondrun()aborts the first, and superseded results are silently discarded without writing to state. Removes the "in-flight call resolves after the locator advanced" race that locator-style state classes would otherwise need to compensate for. -AbortSignalthreaded to the callback β RPC clients that accept a signal (orfetch) get cancellation for free; callers can also pass an externalsignalvia {@link RunOptions} to bind the slot's lifetime to a component / page. -preserve_error_on_retryβ opt-in to keeping the previous error visible while a retry is pending (default clears at the start of eachrun()). - Per-slotmap_errorβ set once in the constructor ({map_error: to_rpc_error_message}); everyrun()gets the right normalization without re-passing per call. - Publicrun()β slots are composed, not subclassed, so call sites can invokestate.list.run(...)directly.@example
class CellsState { readonly list = new AsyncSlot<{cells: ReadonlyArray<CellJson>}>(); readonly create = new AsyncSlot<{cell: CellJson}>({map_error: to_rpc_error_message}); async fetch() { await this.list.run((signal) => this.#api.cell_list({}, {signal})); } async submit_new(input: CellCreateInput) { const result = await this.create.run(() => this.#api.cell_create(input)); if (result) await this.fetch(); } }ui/audit_log_state.svelte.ts
Reactive state for the audit log viewer.
Two fetch primitives (
fetchfor events,fetch_role_grant_historyfor the grant/revoke shortcut) flow through an injected RPC adapter; the SSE stream continues to useEventSourcedirectly β streams aren't an RPC concern.Holds two AsyncSlots β
list(the main event stream) androle_grant_history(the dedicated role-grant history endpoint). Data lives on the class so SSE pushes and gap-fill calls update it directly.ui/auth_state.svelte.ts
Reactive state for cookie-based authentication.
SPA auth pattern: prerendered static HTML served by Hono, no SvelteKit server for SSR sessions. On load, fetches
GET /api/account/statuswhich returns the current account (200) or 401 with optionalbootstrap_available. Login sends username + password once, then a signed httpOnly cookie handles all subsequent requests.@example
<script lang="ts"> import {AuthState, auth_state_context} from '@fuzdev/fuz_app/ui/auth_state.svelte.js'; const auth = new AuthState(); auth_state_context.set(auth); auth.check_session(); </script> {#if auth.verifying} <p>checking sessionβ¦</p> {:else if auth.needs_bootstrap} <BootstrapForm /> {:else if !auth.verified} <LoginForm /> {:else} <p>logged in as {auth.account?.username}</p> <button onclick={() => auth.logout()}>logout</button> {/if}ui/BootstrapForm.svelte
First-keeper bootstrap form β used once on a fresh deployment to claim the bootstrap token and create the initial admin account.
Calls
AuthState.bootstrap(POST /api/account/bootstrap) via auth_state_context. Validates token + Username schema + PASSWORD_LENGTH_MIN + password match client-side; submit focuses the first invalid field. Once an account exists, the bootstrap path is disabled server-side and this form should not be mounted.ui/ColumnLayout.svelte
Two-column layout β fixed-width
asideon the left, fluidchildrencolumn on the right. Both columns scroll independently.ui/ConfirmButton.svelte
Confirmation popover wrapping PopoverButton.
Clicking the trigger opens a popover with a confirm button. On confirm, calls
onconfirmand hides the popover (controlled byhide_on_confirm). Defaults toposition="left".Trigger content: pass
labelfor a simple string, or achildrensnippet for custom content (the two are mutually exclusive β DEV errors when both are set).pending: booleanoverlays a spinner and disables the trigger, mirroringPendingButtonsemantics so the label stays put while an async operation runs.@example
<ConfirmButton onconfirm={() => delete_item(item.id)} title="delete item" label="delete" pending={state.remove.loading(item.id)} />@example
<!-- custom trigger content via the children snippet --> <ConfirmButton onconfirm={() => grant(item.id, role)} title="offer {role}" pending={state.grant.loading(key)} > {#snippet children(_popover, _confirm)}+ {role}{/snippet} </ConfirmButton>@example
<!-- custom confirm button content --> <ConfirmButton onconfirm={handle_revoke} class="icon_button plain" title="revoke"> revoke {#snippet popover_button_content()}revoke{/snippet} </ConfirmButton>ui/Datatable.svelte
ui/datatable.ts
Types and constants for the Datatable component.
ui/form_state.svelte.ts
Reactive form state for controlling when field errors appear and focusing invalid fields.
Tracks per-field
touchedstate (set on blur via delegatedfocusout) and form-levelattemptedstate (set on submit attempt). Errors show after a field is blurred or after a submit attempt, avoiding premature validation while the user is still typing.The
FormState.formattachment also handles Enter key advancing between focusable elements.All trackable inputs must have a
nameattribute β an error is thrown in dev if an input withoutnameloses focus.@example
<script> const form_state = new FormState(); let username = $state.raw(''); const username_valid = $derived(Username.safeParse(username).success); const can_submit = $derived(username.trim() && username_valid); const handle_submit = async () => { form_state.attempt(); if (!can_submit) { if (!username.trim() || !username_valid) form_state.focus('username'); return; } // submit... }; </script> <form {@attach form_state.form()} onsubmit={(e) => { e.preventDefault(); void handle_submit(); }}> <input name="username" bind:value={username} /> {#if form_state.show('username') && username && !username_valid} <p>error message</p> {/if} <PendingButton onclick={handle_submit}>submit</PendingButton> </form>ui/format_scope.ts
Shared
format_scopecallback contract for role-grant-display components.Role grants and offers carry a
scope_idthat names a consumer-owned resource (e.g. a classroom uuid). The default render is the raw uuid. Consumers wire a FormatScope via context to render a human label without per-page lookup or forking the components.ui/keyed_async_slot.svelte.ts
Keyed sibling of AsyncSlot β fans the per-instance supersession machinery out across an open set of keys.
Each key gets its own lazily-created AsyncSlot, so concurrent
run(key_a, ...)andrun(key_b, ...)calls are independent: a secondrun()onkey_aaborts onlykey_a's in-flight call, leavingkey_brunning. The keyed shape replaces the AsyncSlot +SvelteSet<id>pair that state classes previously carried for per-row in-flight tracking, with two genuine wins:- Cross-key supersession is correct β clicking row B while row A is in flight no longer aborts A; each row has its own AbortController. - Per-key error surfacing β
error(key)carries the failure for that key only, instead of the last-error-wins shape of a shared slot.The backing
SvelteMapkeeps entries even after arun()resolves β components can readerror(key)to render an inline per-row failure indicator. Calldelete(key)to dismiss an entry, orreset()to clear everything (e.g. on page leave).@example
class AdminInvitesState { readonly remove = new KeyedAsyncSlot<Uuid>(); async submit_delete(id: Uuid): Promise<void> { const ok = await this.remove.run(id, () => this.#rpc().delete({invite_id: id})); if (ok !== undefined) await this.fetch(); } } // In a template: // <button disabled={state.remove.loading(row.id)}> // {state.remove.loading(row.id) ? 'deletingβ¦' : 'delete'} // </button> // {#if state.remove.error(row.id)}<p>{state.remove.error(row.id)}</p>{/if}ui/LoginForm.svelte
Username + password login form. Calls
AuthState.login(which posts toPOST /api/account/login) via the auth_state_context. On success navigates toredirect_on_login; surfaces 401 / 429 errors viaauth_state.verify_error. Companion to BootstrapForm and SignupForm.ui/LogoutButton.svelte
Pending-aware log-out button. Wraps
PendingButtonand callsAuthState.logout(POST /api/account/logout) via auth_state_context. If the caller providesonclickand callse.preventDefault()from it, the logout is skipped β useful for confirm-before-logout flows.ui/MenuLink.svelte
SvelteKit-aware navigation link. Resolves
pathviaresolvefrom$app/paths, then derivesselected(exact match) andhighlighted(current path is belowpath) frompage.url.pathname.ui/OpenSignupToggle.svelte
Single checkbox bound to
AppSettings.open_signup. Consumes app_settings_rpc_context; the toggle callsapp_settings_updateRPC viaAppSettingsState.update_open_signup. Hides gracefully whenhas_rpcisfalse(renders an "rpc adapter not wired" notice).ui/popover.svelte.ts
Popover state class with trigger, content, and container attachments.
Popover manages visibility, positioning, outside-click dismissal, and ARIA attributes. The
trigger,content, andcontainerattachments wire up the DOM via Svelte's{@attach}directive.For the common button + popover pattern, see PopoverButton.
@example
<script lang="ts"> import {Popover} from '@fuzdev/fuz_app/ui/popover.svelte.js'; const popover = new Popover(); </script> <div class="position:relative" {@attach popover.container}> <button type="button" {@attach popover.trigger({position: 'bottom', align: 'center'})}> toggle </button> {#if popover.visible} <div {@attach popover.content({position: 'bottom', align: 'center'})}> <button type="button" onclick={() => popover.hide()}>close</button> </div> {/if} </div>@example
// programmatic control with callbacks const popover = new Popover({ position: 'right', align: 'start', offset: '8px', disable_outside_click: true, onshow: () => console.log('opened'), onhide: () => console.log('closed'), }); popover.show(); popover.toggle(); popover.hide();ui/PopoverButton.svelte
Button + popover composition for the common toggle-on-click pattern.
Wraps a Popover instance with a
<button>, a positioned content area, and scale transitions. Spreads extra props onto the button element. Passpopover_contentfor the popover body, and eitherchildrenfor simple button content orbuttonfor a fully custom trigger β both receive the Popover instance.@example
<PopoverButton position="bottom" align="center"> {#snippet popover_content(popover)} <div class="box p_md"> <button type="button" onclick={() => popover.hide()}>close</button> </div> {/snippet} open menu </PopoverButton>@example
<!-- custom trigger via the `button` snippet --> <PopoverButton position="right" align="start" disable_outside_click> {#snippet popover_content(popover)} <form onsubmit={() => popover.hide()}> <input name="search" /> <button type="submit">go</button> </form> {/snippet} {#snippet button(popover)} <button type="button" class="icon_button" {@attach popover.trigger()}> search </button> {/snippet} </PopoverButton>@see ConfirmButton for a higher-level wrapper with confirmation semantics
ui/position_helpers.ts
CSS position calculation helpers for popovers and floating UI elements.
ui/role_grant_offers_state.svelte.ts
Reactive state for the consentful-role-grants offer flow.
Maintains one offer cache keyed by id, seeded by the RPC list/history actions and kept live by the six role-grant-offer WebSocket notifications.
incoming(recipient-side pending) andoutgoing(grantor-side pending) are derived views;historyis the full cache ordered newest-first for the grantor/admin history view.Wiring is transport-agnostic: the ctor accepts a narrow RPC interface the consumer adapts from their typed client, plus an
account_id/actor_idgetter pair (typically bound toauth_state). Notification delivery is pull-only viasubscribe()β the consumer plumbs their FrontendWebsocketClient / ActionPeer receiver toapply_notification.Holds six AsyncSlots β one per RPC verb. The cache
#offerslives on the class (multiple ops write into it via#merge_offers/#remove_offer); thecreateslot is typedAsyncSlot<RoleGrantOfferJson>sosubmit_createcan return the new offer via the slot's supersession-safedatapath, but the other slots'datais unused (no single op owns the cache). Method names use thesubmit_*prefix to avoid slot-name collisions; thehistoryview stayed natural by naming the fetch slotlist_history.ui/RoleGrantOfferForm.svelte
ui/RoleGrantOfferHistory.svelte
ui/RoleGrantOfferInbox.svelte
ui/sidebar_state.svelte.ts
Reactive sidebar visibility state. Provisioned by
AppShell.sveltevia sidebar_state_context; consumers readshow_sidebarand calltoggle_sidebar/activate.ui/SignupForm.svelte
Account signup form β username + password (+ optional email).
Calls
AuthState.signup(POST /api/account/signup) via auth_state_context. Validates the Username schema and PASSWORD_LENGTH_MIN client-side; surfaces 403 (no invite), 409 (duplicate), and 429 (rate limited) inline. Submit focuses the first invalid field. Companion to LoginForm and BootstrapForm.ui/SurfaceExplorer.svelte
Read-only AppSurface renderer. Tables routes, middleware, environment variables, events, and diagnostics. Routes can be filtered by auth type and expanded to dump
params/query/input/output/errorsschemas as JSON. Pure presentational β AdminSurface handles the fetch.ui/table_state.svelte.ts
Reactive state for database table pagination and data fetching.
Holds one AsyncSlot β
list(the paginated row fetch). Per-row delete uses plain try/catch + scalardeleting/delete_errorfields (no slot βdelete_errormust persist pastlist.run()retries so the failure message stays visible while the user refetches).@example
const table = new TableState(); await table.fetch('accounts', 0, 50); // pagination if (table.has_next) table.go_next(); await table.fetch(table.table_name, table.offset, table.limit); // deletion const deleted = await table.delete_row(table.rows[0]);@example
<script lang="ts"> import {TableState} from '@fuzdev/fuz_app/ui/table_state.svelte.js'; const table = new TableState(); table.fetch('accounts'); </script> {#if table.list.loading} <p>loadingβ¦</p> {:else if table.list.error} <p>{table.list.error}</p> {:else} <p>showing {table.showing_start}β{table.showing_end} of {table.total}</p> {/if}ui/ui_fetch.ts
Authenticated fetch helper for cookie-based session auth.
Wraps the standard
fetchwithcredentials: 'include'so cookies are sent with every request. Use for all API calls in apps that rely on@fuzdev/fuz_appsession middleware.ui/ui_format.ts
Formatting utilities for UI display.
Value formatting, relative timestamps, uptime display, absolute timestamp formatting, and audit metadata formatting.