Preact
import * as mod from "jsr:@nzip/lofi/preact";
Optional Preact bindings for the lofi runtime: live typed queries
(useLiveQuery), table mutations (useTableMutations), the
per-write sync lifecycle (useWrite, usePendingWrites,
useSyncStatus), first-load progress (useBootProgress),
PWA install/update state (usePwaState), device capabilities
(useDeviceCapabilities), the schema-compatibility gate
(useSchemaCompat), and the storage-container fork guard
(useStorageFork), plus package-owned example components
(DeviceStatus, PwaActions, RuntimeRecovery,
TicketEnrollForm) that application layouts may compose or replace
with UI built on the same public runtime APIs.
DeviceStatus
function
function DeviceStatus(): VNode
Renders the Device gate: every subsystem's live status — storage, sync, auth, and PWA — grouped by the module that owns it, so the panel doubles as a map of where to hook in. Optional: it is built entirely on public runtime APIs (device capabilities, PWA state, runtime diagnostics, the session), so an application can replace it with its own status UI over the same sources.
Returns: The device diagnostics panel, grouped by owning subsystem.
Example
import { DeviceStatus } from "@nzip/lofi/preact";
export function SettingsPage() {
return <DeviceStatus />;
}
Notices
function
function Notices({ ..., ... }: NoticesProps): VNode
The built-in durable-notice surface: renders the live s.notice queue as an
ARIA live region so a message enqueued when a write settled — success or the
"a rejected write still flashed success" case — is announced and dismissable.
Optional and entirely public: it is useNotices plus default markup, so an
app can drop it and render its own (a toast stack, a banner) over the same
hook.
| Parameter | Description |
|---|---|
props | Optional region label and a custom per-notice renderer. |
Returns: The always-mounted live notices region.
Example
import { Notices } from "@nzip/lofi/preact";
export function AppChrome() {
return <Notices />;
}
PwaActions
function
function PwaActions({ ..., ..., ... }: PwaActionsProps): VNode | null
A composable install/update surface that keeps browser event handling package-owned.
| Parameter | Description |
|---|---|
props | An optional controller override and heading text. |
Returns: The install/update section, or null when no action or status is relevant.
Example
import { PwaActions } from "@nzip/lofi/preact";
<PwaActions title="Install this app" />;
pwaFailureMessage
function
function pwaFailureMessage(code: PwaFailureCode): string
Returns actionable, non-technical recovery guidance for a PWA failure.
RuntimeRecovery
function
function RuntimeRecovery({ ..., ... }: RuntimeRecoveryProps): VNode | null
Renders recovery only when another tab is running an incompatible broker version.
| Parameter | Description |
|---|---|
props | The startup failure to inspect and an optional reload override. |
Returns: The recovery prompt, or null when no incompatible-broker failure is present.
Example
import { RuntimeRecovery } from "@nzip/lofi/preact";
<RuntimeRecovery failure={startupFailure} />;
TicketEnrollForm
function
function TicketEnrollForm({ ..., ..., ..., ... }: TicketEnrollFormProps): VNode
The app-connect ticket enrollment form, shaped for password managers: the
ticket is a current-password field with a label-as-username companion, so
the manager offers to save the ticket against this origin on first paste
and autofills it later. That custody is the durable copy on devices that
cannot seal admin capability behind a passkey — the node stores only
hashes, so the manager's copy is the one that survives.
After enrolling a provision-scoped ticket whose capability was split (see
provision.ts), the form offers the passkey-sealing ceremony, and states
the password-manager fallback when the ceremony reports prf-unavailable
or is cancelled.
| Parameter | Description |
|---|---|
props | Optional callbacks, heading, and injectable implementations. |
Returns: The enrollment form and its follow-on custody choices.
Example
import { TicketEnrollForm } from "@nzip/lofi/preact";
<TicketEnrollForm title="Connect to your node" />;
useBootProgress
function
function useBootProgress(): BootProgress
Subscribes a Preact component to first-load progress, so a loading state can distinguish the engine download (with byte progress on a cold first visit) from opening persistent storage.
Returns: The current first-load progress, kept live via subscription.
Example
import { useBootProgress } from "@nzip/lofi/preact";
const boot = useBootProgress();
if (boot.phase === "downloading" && boot.totalBytes) {
const percent = Math.round((boot.loadedBytes / boot.totalBytes) * 100);
return <p>Downloading the app · {percent}%</p>;
}
useDeviceCapabilities
function
function useDeviceCapabilities(): DeviceCapabilitiesState
Reads the browser's device capabilities once on mount and exposes an explicit persistence request that refreshes the report with the browser's verdict.
Returns: The capability report and the persistence request action.
Example
import { useDeviceCapabilities } from "@nzip/lofi/preact";
const { report, requestPersistence } = useDeviceCapabilities();
if (!report) return <p>Checking device capabilities…</p>;
useLiveQuery
function
function useLiveQuery<T extends TableRow>(createQuery: () => QueryBuilder<T>, dependencies: readonly unknown[]): LiveQuerySnapshot<T>
Subscribes a Preact component to any typed Jazz query.
Equivalent mounted queries share one Jazz subscription. The snapshot preserves
the exact row type produced by the builder, including select and include
projections. An empty rows array with status: "ready" is an empty result,
not a loading signal.
| Parameter | Description |
|---|---|
createQuery | Builds the typed Jazz query; re-invoked when dependencies change. |
dependencies | Values that, when changed, release the query and open a replacement. |
Returns: The live snapshot: status, exact typed rows, and error.
Example
import { useLiveQuery } from "@nzip/lofi/preact";
import { app } from "../app.ts";
const records = useLiveQuery(
() => app.schema.records.where({ workspaceId, archived: false }),
[workspaceId],
);
// records.status is "loading" | "ready" | "error"; records.rows is typed.
useNotices
function
function useNotices(): NoticesSurface
Subscribes a Preact component to the durable notice queue. The list stays live across enqueues (including effects that fire at a boot re-arm), dismissals, and TTL retirement.
Returns: The live notices and the dismissal actions.
Example
import { useNotices } from "@nzip/lofi/preact";
const { notices, dismiss } = useNotices();
return notices.map((n) => (
<div key={n.id} data-tone={n.tone}>
{n.message}
<button type="button" onClick={() => dismiss(n.id)}>Dismiss</button>
</div>
));
usePendingWrites
function
function usePendingWrites(): PendingWritesSnapshot
The reload-safe set of writes still waiting to sync, for "N changes waiting to sync" indicators. The set is rebuilt from the durable journal at boot, so it survives a reload with writes still pending.
Returns: The current PendingWritesSnapshot: the count and the
journaled writes still awaiting their sync fate, oldest first.
Example
const pending = usePendingWrites();
return pending.count > 0 ? <p>{pending.count} change(s) waiting to sync</p> : null;
usePwaState
function
function usePwaState(controller: PwaController): PwaState
Subscribes a Preact component to an isolated or shared PWA controller.
| Parameter | Description |
|---|---|
controller | The controller to observe; defaults to the package-wide PWA controller. |
Returns: The current install/update state, kept live via subscription.
Example
import { usePwaState } from "@nzip/lofi/preact";
const pwa = usePwaState();
const updateReady = pwa.update === "ready";
useSchemaCompat
function
function useSchemaCompat(): SchemaCompatState
Subscribes a Preact component to the schema-compatibility gate, so apps can
render their own read-only banner in place of the framework default
(suppress the default with pwa: { updateBanner: "none" } in
defineLofiApp).
Returns: The current compatibility state, kept live via subscription.
Example
import { useSchemaCompat } from "@nzip/lofi/preact";
const compat = useSchemaCompat();
if (compat.state === "data-ahead") return <ReadOnlyBanner message={compat.message} />;
useStorageFork
function
function useStorageFork(guard: StorageForkSurface): StorageForkState
Subscribes a Preact component to the storage-container fork guard, so apps
can render their own install warning or fork notice in place of the
framework default (suppress the default with pwa: { forkNotice: "none" }
in defineLofiApp).
| Parameter | Description |
|---|---|
guard | The guard to observe; defaults to the package-wide guard. |
Returns: The current fork state, kept live via subscription.
Example
import { useStorageFork } from "@nzip/lofi/preact";
const fork = useStorageFork();
if (fork.state === "fork-detected") return <ForkNotice message={fork.message} />;
useSyncStatus
function
function useSyncStatus(row: { id: string } | null | undefined): RowSyncStatus
The sync status of one row for per-row badges: waiting while a journaled
write touching the row has not settled, rejected when the row's latest
settled write was denied, and synced otherwise. waiting is
reload-safe — it derives from the durable journal. rejected outlives the
pruned journal entry only for the current session: after a reload the
badge is gone. For a durable rejection response, declare an onRejected
effect on the verb instead of relying on the badge.
Example
const status = useSyncStatus(task);
{status === "waiting" && <span class="badge">waiting to sync</span>}
useTableMutations
function
function useTableMutations<T extends TableRow, Init>(table: TableProxy<T, Init>): TableMutations<T, Init>
Binds one typed Jazz table to stable insert, update, and remove methods.
Each method returns a thenable WriteHandle: awaiting it resolves after
local durability, and the handle's saved and synced promises track the
write's sync fate. When managed sync is active, global confirmation
continues in the background and updates the shared table-scoped pending,
durability, and error state.
| Parameter | Description |
|---|---|
table | The typed schema table to mutate, e.g. app.schema.records. |
Returns: Stable insert, update, and remove methods plus the shared mutation state.
To attach declared effects to a write, wrap the same table in a verb
(s.mutation(name, s.insert(table), { … })) and observe its
WriteHandle with useWrite.
Example
import { settleUiMutation } from "@nzip/lofi";
import { useTableMutations } from "@nzip/lofi/preact";
import { app } from "../app.ts";
function ArchiveButton({ id }: { id: string }) {
const records = useTableMutations(app.schema.records);
// The handle is thenable: settled at saved, while the sync fate and the
// shared pending/durability state continue in the background.
return (
<button onClick={() => void settleUiMutation(records.update(id, { archived: true }))}>
Archive {records.pending > 0 && "…"}
</button>
);
}
useWrite
function
function useWrite<T>(write: WriteHandle<T> | null | undefined): WriteState
Observes one write's lifecycle. Pass the WriteHandle returned by a
verb or a table mutation; the component re-renders on each
WriteStage change, and because handles are level-triggered a
component that mounts after a transition sees the current stage
immediately — no missed events. reason carries the WriteRejection
once a write settles as rejected.
Example
const [write, setWrite] = useState<WriteHandle<Order> | null>(null);
const { stage, reason } = useWrite(write);
// stage: "saving" | "saved" | "syncing" | "synced" | "rejected" | null
WriteHandle
class
class WriteHandle<T> implements PromiseLike<T> {
constructor(writeId: string);
get writeId(): string;
get batchId(): string | null;
get stage(): WriteStage;
get reason(): WriteRejection | null;
get saved(): Promise<T>;
get synced(): Promise<T>;
then<Fulfilled = T, Rejected = never>(onfulfilled?: ((value: T) => Fulfilled | PromiseLike<Fulfilled>) | null, onrejected?: ((reason: unknown) => Rejected | PromiseLike<Rejected>) | null): Promise<Fulfilled | Rejected>;
subscribe(listener: () => void): () => void;
}
A single write observed through the author-facing lifecycle.
await write (the thenable) resolves at saved with the write's value —
for inserts, the created row. write.synced resolves when the store
confirms the write and rejects with WriteRejectedError when the
store denies it. stage and reason are current-state properties;
subscribe notifies immediately and on every later change.
On a device without managed sync there is no store to confirm anything:
local durability is settlement, and the handle reaches synced as soon as
it is saved. In Preact components, render a handle with useWrite and
the app-wide pending set with usePendingWrites.
Handles are issued by the runtime and are observe-only: the lifecycle mutators live on a controller the ledger keeps at construction, so no consumer of a handle can advance or settle it.
Example
const write = placeOrder({ sku, quantity }); // a verb returns a WriteHandle
const order = await write; // resolves at saved — safe to navigate
write.synced.catch((error) => {
if (error instanceof WriteRejectedError) showDenied(error.message);
});
BootProgress
type
type BootProgress = {
phase: BootProgressPhase;
loadedBytes: number;
totalBytes: number | null;
};
Live first-load progress for application status UI.
BootProgressPhase
type
type BootProgressPhase =
| "pending"
| "downloading"
| "opening"
| "ready"
| "failed";
Phases between a painted shell and an open runtime.
pending— the runtime has not been requested yet.downloading— the engine binary is downloading; on a cold first visit this is the long phase, with byte progress inBootProgress.opening— the engine is instantiating and persistent storage is opening.ready— the runtime is open; live queries answer from local data.failed— the runtime could not open; the cause is in runtime diagnostics (startupFailure).
DeviceCapabilitiesState
type
type DeviceCapabilitiesState = {
report: DeviceCapabilityReport | null;
requestPersistence(): Promise<void>;
};
State returned by useDeviceCapabilities.
DeviceCapabilityReport
type
type DeviceCapabilityReport = {
secureContext: boolean;
opfs: boolean;
sharedWorker: boolean;
webLocks: boolean;
messageChannel: boolean;
durableDriverSupported: boolean;
webAuthn: boolean;
prf: PrfSupport;
persistentPermission: "granted" | "not-granted" | "unavailable" | "error";
displayMode: "standalone" | "browser";
};
Browser capabilities that determine whether lofi can provide its runtime guarantees.
LiveQuerySnapshot
type
type LiveQuerySnapshot<T extends TableRow> = {
status: "loading" | "ready" | "error";
rows: T[];
error: string | null;
};
Honest read state for an arbitrary typed Jazz query.
NoticeEntry
type
type NoticeEntry = {
id: string;
message: string;
tone: NoticeTone;
createdAt: number;
expiresAt: number | null;
};
One durable notice entry.
NoticesProps
type
type NoticesProps = {
label?: string;
children?: (notice: NoticeEntry, dismiss: () => void) => VNode;
};
Props for the built-in Notices surface.
NoticesSurface
type
type NoticesSurface = {
notices: readonly NoticeEntry[];
dismiss: (id: string) => void;
dismissAll: () => void;
};
The live notices and the actions to retire them.
NoticeTone
type
type NoticeTone =
| "info"
| "success"
| "warning"
| "error";
How a notice is classified for rendering.
PendingWritesSnapshot
type
type PendingWritesSnapshot = {
count: number;
writes: readonly PendingWriteSummary[];
};
The reload-safe pending set powering "N changes waiting to sync".
PendingWriteSummary
type
type PendingWriteSummary = {
writeId: string;
verb: string | null;
table: string;
rowId: string;
op: "insert" | "update" | "remove";
createdAt: number;
expired: boolean;
};
One write not yet settled, as shown by pending-writes surfaces.
PwaActionsProps
interface
interface PwaActionsProps {
readonly controller?: PwaController;
readonly fork?: StorageForkSurface;
readonly title?: string;
}
Optional controller, fork guard, and heading text for PwaActions.
PwaController
type
type PwaController = {
getState(): PwaState;
subscribe(subscriber: (state: PwaState) => void): () => void;
requestInstall(): Promise<PwaInstallState>;
checkForUpdate(): Promise<boolean>;
applyUpdate(): boolean;
initialize(): void;
};
Stateful controller for browser installation and service-worker updates.
Most apps use the shared pwaController through the wrapper
functions (getPwaState, applyPwaUpdate) or the Preact
bindings.
PwaFailureCode
type
type PwaFailureCode =
| "registration"
| "installation"
| "install-prompt"
| "update-check"
| "precache"
| "runtime-cache";
Stable categories for recoverable offline/PWA failures.
PwaInstallState
type
type PwaInstallState =
| "installed"
| "available"
| "prompting"
| "accepted"
| "dismissed"
| "manual-ios"
| "manual-browser"
| "unsupported";
Browser installation states exposed to application UI.
installed— running standalone or the browser reports the app installed.available— the browser offered an install prompt;requestPwaInstallopens it.prompting— the deferred prompt is open.accepted/dismissed— the prompt's outcome.manual-ios— install via Share → Add to Home Screen; iOS exposes no prompt API.manual-browser— install is available only through the browser menu.unsupported— no secure context or no service-worker support.
PwaState
type
type PwaState = {
worker: PwaWorkerState;
install: PwaInstallState;
update: PwaUpdateState;
failure?: { code: PwaFailureCode; message: string };
};
Current install, service-worker, and offline-cache state.
PwaUpdateState
type
type PwaUpdateState =
| "idle"
| "checking"
| "installing"
| "ready"
| "applying"
| "failed";
Foreground update-check and waiting-worker states exposed to application UI.
idle— no check running and nothing staged.checking— a bounded update check is in flight.installing— a newly discovered worker is downloading and installing.ready— a new worker is staged and waiting: show the update affordance and callapplyPwaUpdate.applying— covers the swap until controlled tabs reload.failed— the check or installation failed; the cause is inPwaState.failure.
PwaWorkerState
type
type PwaWorkerState =
| "development-disabled"
| "unsupported"
| "registering"
| "ready"
| "failed";
Service-worker lifecycle states exposed to application UI.
development-disabled— registration is skipped outside production builds.unsupported— the environment offers no service-worker container.registering— registration and first activation are in progress.ready— an active worker controls the app and serves the offline shell.failed— registration, installation, or precaching failed;PwaState.failurecarries the cause.
RowSyncStatus
type
type RowSyncStatus = "synced" | "waiting" | "rejected";
Per-row sync state for badges: settled, still waiting, or denied.
RuntimeRecoveryProps
interface
interface RuntimeRecoveryProps {
readonly failure: RuntimeStartupFailure | null;
readonly reload?: () => void;
}
Inputs for Lofi's explicit persistent-runtime recovery action.
RuntimeStartupFailure
type
type RuntimeStartupFailure = {
code: RuntimeStartupFailureCode;
runtimeMode: "local" | "managed";
message: string;
};
Non-sensitive runtime context retained for diagnostics and recovery UI.
RuntimeStartupFailureCode
type
type RuntimeStartupFailureCode =
| "broker-incompatible"
| "configuration-error"
| "reload-loop"
| "storage-startup-failed"
| "unsupported-capabilities";
Stable categories for failures that prevent Lofi's persistent runtime from opening.
SchemaCompatReason
type
type SchemaCompatReason = "schema" | "stale-tab";
Why writes are refused: newer-schema data, or a stale tab after a swap.
SchemaCompatState
type
type SchemaCompatState =
| { state: "unchecked"; reason: "inactive" | "development" | "no-manifest" | "pending" }
| { state: "compatible"; classification: "first-boot" | "equal" | "code-ahead" }
| { state: "data-ahead"; reason: SchemaCompatReason; message: string }
| { state: "updating"; message: string };
The compatibility state exposed through diagnostics and the Preact hook.
SealOutcome
type
type SealOutcome = {
portable: boolean;
};
What a completed sealing ceremony reports about the sealed record.
Session
type
type Session = {
user_id: string | null;
syncAvailable: boolean;
sink: SessionSink | null;
backedUp: boolean;
syncing: boolean;
syncOwnerMismatch: boolean;
phraseGuarded: boolean;
passkeyRecoverable: boolean;
};
A snapshot of the account: what is possible and what the user has chosen.
SessionSink
type
type SessionSink = {
source: "declared" | "default";
host: string;
label?: string;
};
A non-secret description of the sync location in effect.
StorageForkGuard
type
type StorageForkGuard = {
start(): void;
getState(): StorageForkState;
subscribe(listener: (state: StorageForkState) => void): () => void;
dismissFork(): void;
};
The storage-container fork guard for one browsing context.
StorageForkState
type
type StorageForkState =
| { state: "unarmed"; reason: "inactive" | "development" }
| { state: "idle" }
| { state: "browser-data-at-risk" }
| { state: "fork-detected"; message: string };
The storage-container fork state exposed through the guard and the Preact hook.
StorageForkSurface
type
type StorageForkSurface = Pick<StorageForkGuard, "getState" | "subscribe">;
The guard surface a component observes; defaults to the shared guard.
TableMutations
type
type TableMutations<T extends TableRow, Init> = TableMutationSnapshot & { insert(values: Init): WriteHandle<T>; update(id: string, patch: Partial<Init>): WriteHandle<void>; remove(id: string): WriteHandle<void> };
Typed mutation methods plus their shared table-scoped observable state.
TableMutationSnapshot
type
type TableMutationSnapshot = {
pending: number;
durability: WriteDurability;
error: string | null;
};
Observable state shared by every mutation consumer for one table.
TableRow
type
type TableRow = {
id: string;
};
The minimum shape every persisted row exposes to the framework.
TicketEnrollFormProps
interface
interface TicketEnrollFormProps {
readonly onEnrolled?: (session: Session) => void;
readonly title?: string;
readonly enroll?: (ticket: string) => Promise<Session>;
readonly seal?: () => Promise<SealOutcome>;
}
Dependencies TicketEnrollForm accepts for testing and composition.
WriteRejection
type
type WriteRejection = {
cause: "denied" | "expired";
code: string | null;
reason: string;
};
Why a write settled as rejected: the structured cause, code, and reason.
WriteStage
type
type WriteStage =
| "saving"
| "saved"
| "syncing"
| "synced"
| "rejected";
The closed, framework-owned write lifecycle. Stages are monotonic:
saving → saved → syncing → synced | rejected. syncing is reserved for a
runtime that can observe the transport; the current storage engine exposes
no such signal, so today writes move from saved directly to synced or
rejected and no handle ever reports syncing. Branch on saved vs
settled, not on seeing syncing.
WriteState
type
type WriteState = {
stage: WriteStage | null;
reason: WriteRejection | null;
};
The observable state of one write, re-rendered on every stage change.