Files
au_whisper_web/Decisions.md
T

96 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Decisions
Lightweight ADR log. One entry per decision that shapes the codebase. Statuses:
**Accepted** (act on it), **Proposed** (default choice, revisit before building on it),
**Superseded** (kept for history, points to replacement).
Add new entries at the bottom with the next `D-###` id. Never rewrite an Accepted
entry to mean something else — supersede it.
---
## D-001 — Bun as the only runtime and toolchain
**Status:** Accepted (mandated by CLAUDE.md)
Bun replaces Node, vite/webpack (via `bun build` + HTML imports), jest/vitest
(`bun test`), express (`Bun.serve`), better-sqlite3 (`bun:sqlite`), and dotenv.
**Consequence:** no vite plugin ecosystem; PWA assets (service worker, manifest) are
wired by hand instead of via a plugin.
## D-002 — Transformers.js (`@huggingface/transformers`) for all Whisper inference
**Status:** Accepted (mandated by CLAUDE.md)
Same library in the browser (WebGPU/WASM) and on the server (ONNX Runtime under Bun).
**Why:** one model catalog, one pipeline API, no native whisper.cpp build step.
**Trade-off:** server CPU inference is slower than whisper.cpp; acceptable — the server
engine is a convenience for logged-in users, not the primary path.
## D-003 — Inference runs in a dedicated Web Worker
**Status:** Accepted
Model load + transcription block for seconds-to-minutes; running them on the main
thread would freeze the UI. Communication via a small typed postMessage protocol
(`src/worker/protocol.ts`) shared between UI and worker.
## D-004 — WebGPU with automatic WASM fallback
**Status:** Accepted
Detect via `navigator.gpu?.requestAdapter()` inside the worker; pass
`device: "webgpu" | "wasm"` to the pipeline. The active backend is surfaced in the UI
but users don't have to configure anything. Encoder-heavy Whisper benefits ~10-40× from
WebGPU where available (Chromium); WASM keeps Firefox/Safari working.
## D-005 — Model weights cached via the browser Cache API (Transformers.js default)
**Status:** Accepted
Transformers.js already persists Hub downloads in the Cache API (`useBrowserCache`).
We do **not** re-cache weights in the service worker — the SW precaches only the app
shell. **Consequence:** offline = installed PWA + at least one previously loaded model;
the UI must show per-model "downloaded / not downloaded" state (query
`caches` for hub URLs) so users can prepare for offline use.
## D-006 — Quantized model catalog, curated not exhaustive
**Status:** Proposed
Expose a curated list (`src/lib/models.ts`): `whisper-tiny(.en)`, `base(.en)`,
`small(.en)`, `medium(.en)`, `large-v3-turbo`, each with sensible dtype defaults
(e.g. q4/q8 on WASM, fp16/q4 on WebGPU) and a size estimate shown before download.
**Why not free-form model ids:** predictable UX, tested combinations, honest
size/VRAM warnings. Revisit if users ask for arbitrary Hub ids.
## D-007 — PWA: hand-rolled service worker, app-shell precache
**Status:** Accepted
No Workbox/vite-plugin-pwa (conflicts with D-001 tooling). `bun build` emits hashed
assets to `dist/`; a small build step writes the precache manifest into `sw.ts`.
Strategy: precache app shell, cache-first for static assets, network-only for `/api/*`.
## D-008 — Passport.js via a connect-compat adapter over Bun.serve
**Status:** Accepted (Passport mandated by CLAUDE.md; adapter is the how)
Passport expects connect/express-style `(req, res, next)` and `req.login()`. We are
not allowed to use express (D-001), so `src/server/auth.ts` provides a thin adapter:
wrap Bun's `Request` in a minimal req/res shim, invoke the passport authenticate
chain, translate the outcome back to a `Response`. Local (username/password)
strategy first; other strategies plug into the same adapter later.
**Alternative rejected:** dropping Passport for hand-rolled auth — CLAUDE.md names
Passport explicitly.
## D-009 — Sessions + users in `bun:sqlite`
**Status:** Accepted
Single-file SQLite DB (path from env, default `./data/app.db`). Tables: `users`
(id, username, password_hash — Argon2 via `Bun.password`), `sessions`
(id, user_id, expires_at). Cookie: HttpOnly, SameSite=Lax, Secure in production.
No Redis/Postgres until there's a scaling reason (would be `Bun.redis` / `Bun.sql`).
## D-010 — Engine abstraction: `BrowserEngine` and `ServerEngine` behind one interface
**Status:** Accepted
`src/lib/engine.ts` defines `Engine { load(model), transcribe(audio), onProgress }`.
The UI never branches on engine type beyond the toggle. Keeps the server engine
(milestone M5) from leaking into UI code built in M1–M4.
## D-011 — Server engine transport: streaming HTTP response first, WebSocket if needed
**Status:** Proposed
`POST /api/transcribe` returns a streamed (chunked JSON-lines) response with progress
and partial transcripts. Simpler than WebSockets (no connection lifecycle, works with
plain fetch + ReadableStream). Switch to `Bun.serve` websockets only if we need
bidirectional control (e.g. cancel mid-run) that abort signals can't cover.
## D-012 — Audio decoding on the main thread via Web Audio API
**Status:** Accepted
`decodeAudioData` (handles most audio and audio-in-video containers) → mono downmix →
resample to 16 kHz → transfer Float32Array to worker (zero-copy). No ffmpeg.wasm
unless we hit real container-support gaps; note in `src/lib/audio.ts` if we do.