WIP: M3 PWA/offline implementation, M4 auth (backend + frontend), D-013 faster-whisper

- M3: hand-rolled service worker (precache + runtime /ort caching), precache
  manifest injection, SW registration/update toast, manifest + generated icons,
  production build script, offline indicator in ModelPicker (D-005/D-007)
- M4 backend: bun:sqlite users/sessions (D-009), Passport local strategy behind
  connect-compat adapter (D-008), /api/auth routes wired into dev+prod serving,
  Bun.password Argon2id, 38 auth tests
- M4 frontend: AuthForms + useAuth + framework-free authClient, session-aware
  header, anonymous-first fail-silent offline handling, 18 client tests
- D-013: server engine (M5) will use faster-whisper via Python sidecar;
  browser side stays Transformers.js (CTranslate2 has no WASM build)
- bun test: 83 pass / tsc --noEmit clean
- Note: M3 doc closeout (WorkBreakdown/Architecture/tests/offline.md) still in
  flight from a background agent; follow-up commit will finish docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Fable 5 committed 2026-07-10 04:02:28 +02:00
1 parent eb72a2e646
commit 9f39a82982
38 files changed
+3857 -246

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import { useCallback, useEffect, useState } from "react";
import {
fetchCurrentUser,
login as loginRequest,
logout as logoutRequest,
register as registerRequest,
type AuthUser,
} from "./authClient";
export interface AuthState {
/** The signed-in user, or null while anonymous (the default). */
user: AuthUser | null;
/** Rejects with a UI-safe Error message on failure (e.g. 401). */
login: (username: string, password: string) => Promise<void>;
/** Rejects with a UI-safe Error message on failure (400/409). */
register: (username: string, password: string) => Promise<void>;
/** Always resolves; local state is cleared even if the server is down. */
logout: () => Promise<void>;
}
/**
* Session state for the M4 auth UI. The session cookie is HttpOnly, so the
* only source of truth is GET /api/auth/me, probed once on mount.
* `fetchCurrentUser` never rejects — 401 and network failure (offline, server
* down) both resolve to anonymous, keeping the anonymous-first invariant:
* a failing /me must not surface any error.
*/
export function useAuth(): AuthState {
const [user, setUser] = useState<AuthUser | null>(null);
useEffect(() => {
let cancelled = false;
fetchCurrentUser().then((current) => {
// Guard against StrictMode double-mounts / fast unmounts.
if (!cancelled && current) setUser(current);
});
return () => {
cancelled = true;
};
}, []);
const login = useCallback(async (username: string, password: string) => {
setUser(await loginRequest(username, password));
}, []);
const register = useCallback(async (username: string, password: string) => {
// Register signs the user in immediately (the 201 sets the cookie).
setUser(await registerRequest(username, password));
}, []);
const logout = useCallback(async () => {
await logoutRequest();
setUser(null);
}, []);
return { user, login, register, logout };
}