Polish pass on the translate panel: the four export buttons overflowed both transcript cards at 390 px. Also record the headless-Chrome translation run and a re-measured bench in D-WS4b. Co-Authored-By: Claude Opus 5.5 <[email protected]>
8.5 KiB
status
| status |
|---|
| in-progress |
001-4 — Translate: implementation plan
For agentic workers: REQUIRED SUB-SKILL: superpowers:executing-plans (inline, no subagents — coordination plan). Steps use checkbox (
- [ ]) syntax for tracking.
Goal: (4a) a per-model canTranslate flag that hides Whisper's "Translate to English" where it is
unsupported and resets a stale task: "translate"; (4b) on-device Opus-MT text translation of any
finished transcript (da→en, en→da), with timestamps kept for .srt / timestamped .txt exports.
Architecture: canTranslate lives in the catalog (src/lib/models.ts) with a pure
taskForModel() guard used by App on model switch and on every run. Text MT runs in a dedicated
worker (src/worker/translate.worker.ts) — its own onnxruntime instance with full WASM threads,
independent of the Whisper worker's lifetime (cancel/D-018 terminate, D-025 restart) and of its
accelerator (an ort runtime first initialised for WebGPU is pinned single-threaded, D-023). Pure
planning code (src/lib/translation.ts) groups transcript chunks into sentence-ending units, splits
units into sentence pieces under a token budget, and reassembles translated units with the merged
timestamps. UI: a secondary <details> disclosure "Translate transcript…" under the transcript.
Tech Stack: Bun, React 19, Transformers.js 4.2.0 (translation pipeline, MarianMT), onnxruntime-web
(WASM) in the browser, onnxruntime-node for measurement.
Spec: docs/specs/001-transcription-quality-and-speed.md (Goal 4, call "Text translation (4b)
scope"); coordination: docs/plans/001-0-coordination.md.
Global Constraints
- AGENTS.md rules: Bun only; no cross-origin subresources (models come from huggingface.co via
Transformers.js like Whisper; ort from
/ort/); colours as tokens; errors viaclassifyError. - D-020 / anti-goal: the MT model is downloaded only when the user clicks Translate; the panel states the one-time size first. Cached via Transformers.js Cache API (D-005); offline works once cached.
- Nothing leaves the device: translation runs in the browser for every engine (server results too).
- Copy: Whisper's option stays "Translate to English"; the new entry is "Translate transcript…".
canTranslate: false foronnx-community/whisper-large-v3-turboand every.enmodel; true for multilingual tiny/base/small/medium.- Decision ids:
D-WS4a(canTranslate),D-WS4b(text MT). Ports 3140–3149. Heavy jobs underflock /tmp/whisper-heavy.lock.
Review Focus
- Transcript > 512 tokens (Opus-MT input limit): split on sentence boundaries, order preserved,
nothing dropped → Task 3 test
splitForTranslation keeps every word of en_alien in order with every piece under budget+ real-model test (Task 4) on all ofen_alien.txt. - Multi-sentence input to Opus-MT drops later sentences (observed in the probe: "…Copenhagen. How are you?" → only the first sentence) → each sentence is its own piece (Task 3 test).
- Unpunctuated text (live output, or a 2-minute run-on): hard-wrap on commas, then words, so no piece exceeds the budget (Task 3 test).
- Stale
task: "translate"after switching to turbo / a.enmodel / the server auto-switch to turbo → reset to transcribe (Task 1 test ontaskForModel, App wired at every switch + run). - Offline / model not cached → the Translate button still offers the download; a failed download surfaces a classified network error with Retry (Task 5, errors test).
Task 1: canTranslate + stale-task guard (4a)
Files: Modify src/lib/models.ts, src/components/TranscriptionOptions.tsx, src/App.tsx;
Test tests/models.test.ts, new tests/transcriptionOptions.test.tsx.
Interfaces — Produces: WhisperModel.canTranslate: boolean;
taskForModel(modelId: string, task: WhisperTask): WhisperTask;
TranscriptionOptions prop canTranslate: boolean.
- Failing tests:
canTranslatefalse for turbo and every.en, true for the other multilingual ids;taskForModel("onnx-community/whisper-large-v3-turbo", "translate") === "transcribe", unchanged forwhisper-small; unknown id →"transcribe".renderToStaticMarkupofTranscriptionOptionswithcanTranslate={false}contains no "Translate to English" and no Task select; withtruecontains it. - Implement; App:
setTask(t => taskForModel(next, t))inhandleModelSelectand the server auto-switch effect; runs passtask: taskForModel(modelId, task). - Typecheck, fast suite; D-WS4a entry; commit.
Task 2: error stage for translation
Files: src/lib/errors.ts, tests/errors.test.ts.
- Failing test:
classifyError("Out of memory", { stage: "translate" })→ suggestion mentions a shorter transcript / closing tabs, not "smaller model"; network onstage: "translate"retryable. - Add
"translate"toErrorStage; commit with Task 3.
Task 3: pure translation planning (src/lib/translation.ts)
Interfaces — Produces:
TRANSLATION_MODELS: readonly TranslationModel[]—{ id, source: "da"|"en", target, label, approxBytes }forXenova/opus-mt-da-en,Xenova/opus-mt-en-da(q8, ~113 MB each).getTranslationModel(source, target),guessSourceLanguage(text, hint?): "da" | "en".buildTranslationUnits(result: { text; chunks }): TranslationUnit[]—{ text: string; timestamp: [number, number | null] | null }; consecutive chunks merged until the text ends a sentence (cap 8 chunks); no chunks → one unit per non-empty line.splitForTranslation(text, { maxTokens = 200, countTokens = estimateTokens }): string[]— Intl.Segmenter sentences (abbreviation-only fragments merge forward), then comma, then word wrap.assembleTranslation(units, translated: string[]): { text; chunks }.- Tests (
tests/translation.test.ts): unit merging & timestamps, null end, no-chunk fallback, sentence splitting, Review Focus 1–3, assemble length mismatch throws,guessSourceLanguage. - Red → green → commit.
Task 4: translate worker + protocol + measurement
Files: Create src/worker/translate.worker.ts, src/worker/translateProtocol.ts,
tests/translateProtocol.test.ts, tests/translate-real-model.test.ts (skips when the Hub is
unreachable and the model is not cached; run under the lock), scripts/bench-translate.ts.
- Protocol: UI→worker
{ type: "translate"; requestId; modelId; units: string[] }; worker→UItranslate-load-progress(file/loaded/total/status),translate-progress { done; total }(pieces),translate-result { texts: string[] },translate-error { stage: "load" | "translate"; message }; guardsisTranslateRequest/isTranslateWorkerMessage. - Worker:
pipeline("translation", id, { dtype: "q8", device: "wasm", session_options: { graphOptimizationLevel: "basic" } })(D-014 — Marian also ties embeddings), WASM threads as D-023,wasmPaths "./ort/"; one cached pipeline per model id; pieces viasplitForTranslationwith the real tokenizer count; batches ofBATCH_SIZE(measured). - Bundle + serve:
src/index.tsdev route/translate.worker.js,scripts/build.tsemits and precaches it. - Measure en_alien.txt → da and da_festuge → en on onnxruntime-node (batch 1 vs 4 vs 8); record.
- Commit.
Task 5: UI — TranslatePanel
Files: Create src/components/TranslatePanel.tsx; modify src/App.tsx,
src/components/TranscriptView.tsx (title prop + exportSuffix), src/components/StorageManager.tsx
(list MT models), src/index.css.
<details className="translate-panel"><summary>Translate transcript…</summary>with direction select ("Danish → English", "English → Danish", default fromguessSourceLanguage), size line when not cached ("Downloads a translation model (~113 MB, one time)"), Translate / Cancel buttons, progress ("Downloading translation model… 40%", "Translating… 12 of 80 sentences"), error box viaclassifyError(…, { stage: "translate" | "load" })with Retry when retryable.- Result rendered by
TranscriptViewwith title "Translation (English)" and exports named<file>.en.txt/.en.srt. - State resets whenever
resultchanges; Cancel terminates the translate worker. - impeccable polish pass; screenshots light/dark × desktop/mobile; real headless-Chrome run of a translation (WASM) if feasible.
- Commit.
Task 6: docs + wrap-up
- D-WS4b entry (models, licences, numbers); Architecture.md (translate worker), AGENTS.md rule
wording (two workers), WorkBreakdown, README if needed,
tests/offline.mdstep. - typecheck, fast suite, build;
git rmthis plan; commit.