ui

45 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 — list for the fetch, revoke_all for the bulk revoke — plus one KeyedAsyncSlot<string, void> (revoke) keyed by session_id for per-row revoke (independent supersession across concurrent rows; per-row error surfacing). Method names use the submit_* 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 a confirmed revoke-all. The row the server marks is_current is this browser's session: revoking it logs out through auth_state.logout, as does a revoke-all that succeeded, so the UI falls back to login instead of acting on a dead session.

  • ui/admin_accounts_state.svelte.ts

    Reactive state for admin account management.

    Holds one fetch AsyncSlot (list) plus three KeyedAsyncSlots — grant (offer creation, keyed by account_id:role or account_id:role:to_actor_id), revoke (role_grant revoke, keyed by role_grant_id), retract (offer retraction, keyed by offer_id). Per-row supersession is correct (clicking row B no longer aborts row A) and error(key) surfaces failure per-row. Method names use the submit_* 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) and create (singular write) — plus one KeyedAsyncSlot<Uuid> (remove) for the per-row delete with correct per-row supersession and per-row error surfacing. Method names use the submit_* prefix to avoid slot-name collisions (delete is 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 thrown Error so form components (e.g. ui/RoleGrantOfferForm.svelte) can match on error.data?.reason via ERROR_ROLE_GRANT_OFFER_* constants — optional chaining is required because JSON-RPC data is spec-level optional. Consumers that need a custom unwrap strategy can construct their own object satisfying AdminRpcApi and pass it directly.

    No .svelte.ts suffix — 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 the admin_session_list RPC method.

    Holds one fetch AsyncSlot (list) plus two KeyedAsyncSlots keyed by account_id — revoke_sessions and revoke_tokens. Per-account concurrent revokes are independent (clicking row B does not abort row A) and per-row errors surface via revoke_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 by actor_id), retract pending offer (role_grant_offer_retract). The grant button offers the role globally, so can_offer_global_role hides it only for a global grant or pending global offer of that role.

  • ui/AdminAuditLog.svelte

    Admin audit log viewer. Consumes audit_log_rpc_context; uses audit_log_list RPC for fetches and a separate EventSource against the SSE stream URL for live tailing (toggle via the stream button). Filter by event_type (applied to the stream too), manual refresh, and live-stream status are surfaced in the header; a stream the server closed offers a retry in place of the stream button, and a failed or truncated gap fill says events may be missing. Changing the filter reopens a live stream with it, but leaves a closed one closed until retried.

  • 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 — summary panels (accounts, sessions, invites, recent activity, security, system) fed by parallel fetch() calls on mount. Consumes the admin RPC contexts plus auth_state_context. Recent activity is the newest slice of the audit log; the security counts come from their own queries (failed logins filtered server-side, role grant changes from the role-grant history), each fetched up to AUDIT_LOG_LIST_LIMIT_MAX rows and counted over the last 24 hours (SECURITY_WINDOW_HOURS) — N+ when the cap may cut the window off.

  • ui/AdminRoleGrantHistory.svelte

    Role grant create/revoke history table. Consumes audit_log_rpc_context, calls audit_log.fetch_role_grant_history() once on mount (the audit_log_role_grant_history RPC). 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. The server must opt in to the route (create_app_server({surface_route: true})), which is admin-only and refused to a narrowed API token.

  • 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) and update (the app_settings_update write). Slots track status/error; the canonical settings lives on the class so consumers don't unwrap slot.data.

  • ui/AppShell.svelte

    Sidebar-and-main app shell. Provisions sidebar_state_context (creating a fresh SidebarState if sidebar_state is 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 the toggle_button snippet) while the sidebar is enabled. Requires the SvelteKit runtime — it closes the overlay from afterNavigate.

    Two modes, split by the state's narrow_query. Outside it the sidebar sits beside the content, padded by --sidebar_offset, following show_sidebar_wide. Inside it the sidebar starts closed and opens as an overlay over a scrim, following show_sidebar_narrow; the content is inert while it's open, and navigation, Escape, a scrim click, or widening out of narrow mode closes it. Escape never prevents the default, so the browser's close requests stay intact; it does nothing while a modal (:modal) is open, when a window keydown listener registered before the overlay opened swallows it (a contextmenu, say), or when a visible Popover's document listener already handled it.

    The open overlay is a modal dialog: the sidebar takes role="dialog", aria-modal, tabindex="-1", and sidebar_label as its name, and focus moves into it, to its first control that takes focus, else to the sidebar itself — the content went inert and dropped it. The attributes key on the open overlay, which reads the viewport — that's what keeps the wide sidebar from ever being a dialog — and the server render matches hydration because the overlay always starts closed, as with the content's inert. aria-modal lets assistive tech hide everything outside the sidebar, the built-in toggle included, so a sidebar whose screen-reader users need a close control beyond Escape and navigation renders its own.

    Closing with Escape, the keyboard shortcut, or a scrim click returns focus to what opened the overlay, or else to the first element whose aria-controls names the sidebar's id and that takes it — when focus was inside the sidebar or had fallen to the body. The id is the state's sidebar_id when set, so a toggle outside the shell can carry aria-controls too; Safari doesn't focus a clicked button, so without it a mouse-opened overlay has no opener to return to.

    The shell's rules sit in two cascade layers nested under fuz_app. fuz_app.app_shell_base, in the scoped style, holds the base and state rules, which apply in both modes; fuz_app.app_shell_narrow overrides some of them from a style element in svelte:head carrying media={narrow_query}, so the server render paints the right mode before hydration and the markup's classes never depend on the viewport. The narrow layer is ordered after the base layer, so it wins whatever the selectors' specificity or the sheets' order. Its rules are global — they match every .app-shell on the page — so shells mounted at the same time must share a narrow_query. Pages served under a strict CSP need style-src-elem 'unsafe-inline' or the hash of that constant style.

    Public class hooks: .app-shell (root, with state classes .wide-open / .narrow-open), .app-shell-content, .app-shell-sidebar, .app-shell-scrim, .app-shell-toggle, .app-shell-toggle-glyph. Any unlayered rule beats both layers at any specificity, so overrides need no specificity games; to layer them instead, order fuz_app first (@layer fuz_app, overrides;) in a sheet that loads before the shell. The same rule means an unlayered reset overrides the shell too — the fuz_css reset zeroes padding, margin, and border on every element — so the layers set none of those. The one they need, the content's wide offset, goes through --sidebar_offset: the layers set it on .app-shell-content (--sidebar_width while the wide sidebar is open, else 0px), and one unlayered rule at (0,0,1) reads it into the content's padding-left. Descendants of the content can read it too. Override the offset by setting --sidebar_offset on .app-shell-content or with a padding-left rule there; a padding shorthand there overrides it as well. Like any unlayered override, these apply in both modes, open or closed; to change only the wide open offset, target .app-shell.wide-open > .app-shell-content inside the narrow query's negation — the state classes don't track the viewport, so .wide-open stays set in narrow mode too. CSS variables: --sidebar_bg, --sidebar_z_index (default 200 in both modes; the toggle sits one above), --sidebar_scrim_bg, --sidebar_width (set from the sidebar_width prop; always the configured width, not the offset), and --sidebar_offset.

  • 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-hoc creating / updating fields beside a single shared loading / error pair.

    Core surface:

    • Explicit four-value status — AsyncStatus from @fuzdev/fuz_util/async.ts: `'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-class submitted / hydrated flag.
    • Owns data: T | undefined — the success payload persists across retries (stale-while-revalidate). The sentinel is undefined (not null) so null stays available as a legitimate success value for nullable Ts. Pass T = void for write-only actions whose response isn't worth keeping.
    • Supersession via internal AbortController — a second run() 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.
    • AbortSignal threaded to the callback — RPC clients that accept a signal (or fetch) get cancellation for free; callers can also pass an external signal via 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 each run()).
    • Per-slot map_error — set once in the constructor ({map_error: to_rpc_error_message}); every run() gets the right normalization without re-passing per call.
    • Public run() — slots are composed, not subclassed, so call sites can invoke state.list.run(...) directly.
  • ui/audit_log_state.svelte.ts

    Reactive state for the audit log viewer.

    Two fetch primitives (fetch for events, fetch_role_grant_history for the grant/revoke shortcut) flow through an injected RPC adapter; the SSE stream continues to use EventSource directly — streams aren't an RPC concern.

    Holds AsyncSlots for list (the main event list), role_grant_history (the dedicated role-grant history endpoint), and gap_fill (the catch-up fetch run each time the stream opens). Data lives on the class so SSE pushes and gap-fill calls update it directly; both merge by seq, so events stays newest-first however they interleave.

  • 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/status which returns the current account (200) or 401 with optional bootstrap_available. Login sends username + password once, then a signed httpOnly cookie handles all subsequent requests.

  • ui/backend_probe.svelte.ts

    Backend liveness probe — whether the app's backend is serving, read from its /health route.

    AuthState.check_session can't tell a backend that's down from a logged-out caller, so a login gate probes /health to say the backend is needed instead of showing a login form that can't work. Only the health reply counts: a static server or an SPA fallback answering every path with a 200 HTML page is not a backend, and neither is a network error. Browser-safe — no server imports.

  • 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 aside on the left, fluid children column on the right. Both columns scroll independently.

    Below a threshold of the layout's own width - a container query, not a media query, so a nested layout decides for itself - the columns stack: the aside collapses behind a toggle button labelled toggle_label, and when opened it sits in flow above the content, pushing it down, until the toggle closes it or a navigation does. Opening scrolls the aside into view, under the toggle, when it would land above the view.

    With a bounded height, the stacked layout scrolls as one column and the toggle sticks to its top; under a parent that grows with its content, the page scrolls and the toggle scrolls away with it.

    The threshold matches fuz_css's sm breakpoint. Above it the toggle is display: none. The switch is CSS alone, so the server render shows the right form at any width and hydration changes nothing.

    The root is an inline-size query container, so its width can't come from its content: a layout placed where it would shrink to fit (an inline-block, an absolutely positioned or floated box without a width, a flex row item that doesn't grow) collapses to zero width. Give it a width from its context, as a block or a stretched or growing flex item.

    Requires the SvelteKit runtime - it closes the aside from afterNavigate. Without JS the stacked aside can't be opened, since the toggle is client state; above the threshold it's always shown.

    The aside column is a plain <div>, not a landmark, as with AppShell's sidebar: render the landmark inside the aside snippet - a <nav aria-label> for navigation links, or an <aside aria-label> for other content.

    Styling hooks: --column_width (set from column_width) and --column_toggle_bg, the stacked toggle bar's opaque background so content scrolls under it unseen (default --shade_00, the page ground).

  • ui/ConfirmButton.svelte

    Confirmation popover wrapping PopoverButton.

    Clicking the trigger opens a popover with a confirm button. On confirm, calls onconfirm and hides the popover (controlled by hide_on_confirm). Defaults to position="left".

    Trigger content: pass label for a simple string, or a children snippet for custom content (the two are mutually exclusive — DEV errors when both are set). pending: boolean overlays a spinner and disables the trigger, mirroring PendingButton semantics so the label stays put while an async operation runs.

  • ui/Datatable.svelte

    Generic CSS-subgrid datatable. Sticky header, pointer-driven column resize, and optional header / cell / empty snippets for custom rendering. Default cell rendering uses column.format(value, row) if present, else format_value. Resize deltas are kept in component-local state, keyed by column.key — they don't outlive the component.

    Rows are keyed by the required row_key accessor, so per-row state (popovers, pending buttons) follows its row across deletes and refetches. A row whose key is null or undefined throws in DEV.

    Exposed as an ARIA table (not grid — there's no grid keyboard model): row / columnheader / cell roles, with the empty state a full-width row of its own.

  • 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 touched state (set on blur via delegated focusout) and form-level attempted state (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.form attachment also handles the Enter key: in an input followed by another input it moves focus to that input (past any button between), and in the last input it submits the form (requestSubmit) — so Enter in a login form's password field logs in, even when the form's button is type="button", as PendingButton is. Hidden inputs don't count, and an Enter that commits IME composition is left alone.

    While a submit is in flight, make the fields readonly rather than disabled (and the button aria-disabled, guarding the handler against re-entry) — disabling the focused element drops focus to <body>. Enter still advances through and submits from readonly inputs.

    The type parameter names the form's fields, so focus, show, touch, and is_touched reject a misspelled field at compile time.

    All trackable inputs must have a name attribute — an error is thrown in dev if an input without name loses focus.

  • ui/format_scope.ts

    Shared format_scope callback contract for role-grant-display components.

    Role grants and offers carry a scope_id that names a consumer-owned resource (e.g. a classroom uuid). The default render is the truncated 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, ...) and run(key_b, ...) calls are independent: a second run() on key_a aborts only key_a's in-flight call, leaving key_b running. 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 SvelteMap keeps entries even after a run() resolves — components can read error(key) to render an inline per-row failure indicator. Call delete(key) to dismiss an entry, or reset() to clear everything (e.g. on page leave).

  • ui/LoginForm.svelte

    Username + password login form. Calls AuthState.login (which posts to POST /api/account/login) via the auth_state_context. On success navigates to redirect_on_login; surfaces 401 / 429 errors via auth_state.verify_error. Companion to BootstrapForm and SignupForm.

  • ui/LogoutButton.svelte

    Pending-aware log-out button. Wraps PendingButton and calls AuthState.logout (POST /api/account/logout) via auth_state_context. If the caller provides onclick and calls e.preventDefault() from it, the logout is skipped — useful for confirm-before-logout flows.

  • ui/MenuLink.svelte

    SvelteKit-aware navigation link. Resolves path via resolve from $app/paths, then derives selected from page.url.pathname — fires for both the exact page (/admin/repos) and any descendant page (/admin/repos/foo/log). Exact matches additionally carry aria-current="page" so CSS / assistive tech can distinguish "this exact page" from "current section" when needed.

    The earlier highlighted flag (which fired only on the prefix-only case) was dropped: in every practical use it sat next to .selected with a near-identical visual treatment and just made callers compute two booleans for what reads to users as one "you are here" state. The highlighted *name* is now free for orthogonal emphasis (recent activity, unread badges, search matches) — that's how fuz_ui's DocsPageLinks already uses it.

  • ui/OpenSignupToggle.svelte

    Single checkbox bound to AppSettings.open_signup. Consumes app_settings_rpc_context; the toggle calls app_settings_update RPC via AppSettingsState.update_open_signup.

    The browser flips the box before the write lands, so once the write settles the box is re-synced to settings — after a failure it shows the server's state again, not the refused one.

  • ui/popover.svelte.ts

    Popover state class with trigger, content, and container attachments.

    Popover manages visibility, positioning, outside-click and Escape dismissal, and ARIA attributes. The trigger, content, and container attachments wire up the DOM via Svelte's {@attach} directive.

    While visible, the trigger carries aria-expanded="true" and aria-controls naming the content (cleared on hide, when the content usually leaves the DOM), and the content — role="dialog" unless it sets its own role — is aria-labelledby the trigger unless it carries its own name. Escape hides the popover while it's visible, returning focus to the trigger when focus was inside the content. The listener is on document, so it runs before window listeners, and it prevents the default — so an enclosing layer that skips handled Escapes, like AppShell's narrow overlay, stays open, and only the popover closes. An Escape another listener already prevented is left alone. With several popovers visible, only the newest handles Escape, so nested ones close innermost first. While a modal (:modal) is open, a popover outside it leaves Escape to the modal; a popover inside the modal still closes first.

    For the common button + popover pattern, see PopoverButton.

  • 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. Pass popover_content for the popover body, and either children for simple button content or button for a fully custom trigger — both receive the Popover instance.

  • 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) and outgoing (grantor-side pending) are derived views; history is 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_id getter pair (typically bound to auth_state). Notification delivery is pull-only via subscribe() — the consumer plumbs their FrontendWebsocketClient / ActionDispatcher receiver to apply_notification.

    Holds one slot per RPC verb: AsyncSlots for list, list_history and create, and KeyedAsyncSlots keyed by offer id for the per-row verbs accept, decline and retract, so each row carries its own pending state and error. The cache #offers lives on the class (multiple ops write into it via #merge_offers / #remove_offer); the create slot is typed AsyncSlot<RoleGrantOfferJson> so submit_create can return the new offer via the slot's supersession-safe data path, but the other slots' data is unused (no single op owns the cache). Each terminal verb stamps the offer it answers into the cache, so history shows it whether or not its WS notification arrives. Method names use the submit_* prefix to avoid slot-name collisions; the history view stayed natural by naming the fetch slot list_history.

  • ui/RoleGrantOfferForm.svelte

    Grantor-side role_grant offer form.

    Caller supplies to_account_id, the subset of roles the grantor may offer (typically filtered by admin-grant-path — RoleSpec.grant_paths includes 'admin'), an optional scope — scope_kind and scope_id together, or neither for a global offer — and an optional on_created callback for post-submit UX. Errors from the RPC surface their distinct reason codes (self-target, role-not-grantable, not-authorized, a scoped builtin role, actor mismatches) so consumers can render them appropriately.

  • ui/RoleGrantOfferHistory.svelte

  • ui/RoleGrantOfferInbox.svelte

  • ui/sidebar_state.svelte.ts

    Reactive sidebar visibility state. Provisioned by ui/AppShell.svelte via sidebar_state_context; consumers read show_sidebar and call toggle_sidebar / activate.

    Visibility is two viewport-free fields — show_sidebar_wide (the persistent sidebar beside the content) and show_sidebar_narrow (the overlay below narrow_query) — so server and client render the same classes and CSS picks which field is in effect. narrow reads the viewport and is only for input handling and ARIA, never for markup that must match SSR.

  • 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 / errors schemas as JSON — each expandable row's first cell is a disclosure <button aria-expanded>, so expanding works from the keyboard. 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 + scalar deleting / delete_error fields (no slot — delete_error must persist past list.run() retries so the failure message stays visible while the user refetches).

  • ui/ui_fetch.ts

    Authenticated fetch helper for cookie-based session auth.

    Wraps the standard fetch with credentials: 'include' so cookies are sent with every request. Use for all API calls in apps that rely on @fuzdev/fuz_app session middleware.

  • ui/ui_format.ts

    Formatting utilities for UI display.

    Value formatting, relative timestamps, uptime display, absolute timestamp formatting, and audit metadata formatting.