clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/prf-provision-custody
lofi / package / preact / DeviceStatus.tsx
DeviceStatus.tsx 6.1K
1import type { JSX, VNode } from "preact";2import { useEffect, useState } from "preact/hooks";3// Package-owned optional diagnostics UI.4import { useDeviceCapabilities } from "./use-device-capabilities.ts";5import { settleUiMutation } from "../runtime/ui-mutation.ts";6import { getPwaState, type PwaState, subscribePwaState } from "../runtime/pwa.ts";7import { PwaActions } from "./PwaActions.tsx";8import {9 getRuntimeDiagnostics,10 runtimeRecreatedEvent,11 subscribeRuntimeDiagnostics,12} from "../runtime/runtime.ts";13import { readSession, type Session } from "../runtime/session.ts";14import { RuntimeRecovery } from "./RuntimeRecovery.tsx";1516/**17 * The Device gate: every subsystem's live status, grouped by the system it18 * belongs to. It doubles as a map of where to hook in — each category names the19 * module that owns it, so a reading of the deployment is also a reading of the20 * framework's seams.21 */2223// One label/value row inside a category's definition list.24function Row({ label, value }: { label: string; value: string }): JSX.Element {25 return (26 <div>27 <dt>{label}</dt>28 <dd>{value}</dd>29 </div>30 );31}3233const available = (present: boolean) => (present ? "available" : "missing");3435/**36 * Renders live storage, sync, auth, and PWA capability diagnostics.37 *38 * @example39 * ```tsx40 * import { DeviceStatus } from "@nzip/lofi/preact";41 *42 * export function SettingsPage() {43 * return <DeviceStatus />;44 * }45 * ```46 *47 * @returns The device diagnostics panel, grouped by owning subsystem.48 */49export default function DeviceStatus(): VNode {50 const { report, requestPersistence } = useDeviceCapabilities();51 const [pwa, setPwa] = useState<PwaState>(getPwaState());52 const [session, setSession] = useState<Session | null>(null);53 const [runtimeDiagnostics, setRuntimeDiagnostics] = useState(getRuntimeDiagnostics());5455 useEffect(() => subscribePwaState(setPwa), []);56 useEffect(57 () => subscribeRuntimeDiagnostics(() => setRuntimeDiagnostics(getRuntimeDiagnostics())),58 [],59 );60 useEffect(() => {61 const refresh = () => setSession(readSession());62 refresh();63 // Re-read after electing to sync or restoring an account recreates the runtime.64 globalThis.addEventListener(runtimeRecreatedEvent, refresh);65 return () => globalThis.removeEventListener(runtimeRecreatedEvent, refresh);66 }, []);6768 if (!report) return <p class="device-status">Checking device capabilities…</p>;6970 const synced = Boolean(session?.syncAvailable);71 const sinkState = session?.sink72 ? session.sink.source === "declared"73 ? `declared — ${session.sink.label ?? session.sink.host}`74 : "configured (build default)"75 : "not configured";76 const syncState = session?.syncing77 ? "syncing to your account"78 : session?.syncAvailable79 ? "available — not yet backed up"80 : "local-only";81 const backupState = session?.backedUp82 ? "recovery phrase"83 : session?.syncAvailable84 ? "not backed up"85 : "local-only";8687 return (88 <section class="device-status" aria-labelledby="device-status-title">89 <header>90 <p class="eyebrow">Device gate</p>91 <h2 id="device-status-title">Deployment status &amp; integration points</h2>92 </header>9394 <div class="device-category">95 <h3>Shared worker &amp; durable storage</h3>96 <p class="device-hook">97 Hook in: <code>@nzip/lofi</code> device capabilities and runtime98 </p>99 <dl>100 <Row101 label="Durable driver"102 value={report.durableDriverSupported ? "supported" : "blocked"}103 />104 <Row label="Secure context" value={report.secureContext ? "secure" : "not secure"} />105 <Row label="OPFS" value={available(report.opfs)} />106 <Row label="SharedWorker" value={available(report.sharedWorker)} />107 <Row label="Web Locks" value={available(report.webLocks)} />108 <Row label="MessageChannel" value={available(report.messageChannel)} />109 <Row label="Storage persistence" value={report.persistentPermission} />110 <Row111 label="Runtime startup"112 value={runtimeDiagnostics.startupFailure?.code ??113 (runtimeDiagnostics.storageState === "persistent-driver-open" ? "ready" : "opening")}114 />115 </dl>116 <RuntimeRecovery failure={runtimeDiagnostics.startupFailure} />117 <button type="button" onClick={() => void settleUiMutation(requestPersistence())}>118 Request storage persistence119 </button>120 <p>121 OPFS capability and eviction protection are separate. A declined persistence request does122 not mean the Jazz driver is using memory.123 </p>124 </div>125126 <div class="device-category">127 <h3>Data sync</h3>128 <p class="device-hook">129 Hook in: <code>@nzip/lofi</code> session and runtime130 </p>131 <dl>132 <Row label="Sync" value={syncState} />133 <Row label="Sync location" value={sinkState} />134 </dl>135 {!synced && (136 <p>137 Set <code>JAZZ_APP_ID</code> and <code>JAZZ_SERVER_URL</code> in <code>.env</code>{" "}138 and rebuild, or enroll a sync ticket at runtime, to replicate writes to your account.139 </p>140 )}141 </div>142143 <div class="device-category">144 <h3>Auth</h3>145 <p class="device-hook">146 Hook in: <code>@nzip/lofi</code> auth and session147 </p>148 <dl>149 <Row label="Identity" value="local-first account" />150 <Row label="Backup" value={backupState} />151 <Row label="WebAuthn" value={available(report.webAuthn)} />152 <Row label="PRF (at-rest key)" value={report.prf} />153 </dl>154 </div>155156 <div class="device-category">157 <h3>PWA</h3>158 <p class="device-hook">159 Hook in: <code>@nzip/lofi</code> PWA state160 </p>161 <dl>162 <Row label="Display mode" value={report.displayMode} />163 <Row label="Install" value={pwa.install} />164 <Row label="Service worker" value={pwa.worker} />165 <Row label="Update" value={pwa.update} />166 </dl>167 <PwaActions title="Install & updates" />168 </div>169 </section>170 );171}