~/cases/research-benchLive
ResearchBench
How a one-line take-home prompt ("Build a NotebookLM clone") turned into a local-first browser research workbench with Rust/WASM, in-browser embeddings and cited two-host podcasts.
→Interface & Studio details
→Context
The interview prompt was deliberately open: "Build a NotebookLM clone. Implementation, scope and structure are completely up to you." Instead of putting a thin chat wrapper in front of a backend API, I explored how much of the core NotebookLM workflow can run directly inside the browser.
→Problem
Working with source documents requires knowing which exact passage backs a generated claim without uploading whole document collections to an application server. At the same time, citations need to resolve to exact character spans rather than vague document-level references.
→Architecture
Documents never leave the browser during import or search. A Rust crate paired with pinned Pdfium WASM extracts canonical page text, cuts overlapping chunks, and computes half-open UTF-16 offsets. A Web Worker embeds chunks locally with quantized multilingual-e5-small on ONNX Runtime Web. Notebooks, sources, chunks and vectors are committed with revision checks in IndexedDB.
For answers, summaries, FAQs, study guides and podcast scripts, the browser calls the Mistral EU endpoint or OpenRouter (with ZDR and data-collection-deny filters) using a session-scoped key. Before display, a support gate checks numeric literals and quotations against the transmitted excerpts; for podcasts, an audio Worker decodes, normalizes and encodes speech turns locally into a 24 kHz MP3.
→Trade-offs
Running local-first eliminates any application backend and keeps documents local by default, at the cost of a ~160 MB runtime and model download on first import. Likewise, the lexical support gate catches invented numbers and misquoted phrases, while explicitly stating that it is not a semantic proof of truth.
- PDF, Markdown and TXT files are parsed locally in the browser via Rust and Pdfium WASM into pages, chunks and exact UTF-16 spans.
- An ONNX Runtime Web Worker computes local multilingual-e5-small embeddings and commits them atomically to IndexedDB.
- Chat and Studio run cosine retrieval locally and check generated answers through a deterministic support gate.
- Only selected excerpts are sent with the user session key to Mistral EU or OpenRouter for generation and speech synthesis.
→Decisions & trade-offs
| Decision | Why | Trade-off |
|---|---|---|
| Run parsing and retrieval in Rust/WASM and browser Workers | Visitors can use the live demo immediately without server setup or accounts; import and search send zero document data over the network. | Browser memory and storage quotas bound maximum notebook capacity (50 sources, 500,000 characters, 1,000 chunks). |
| Thread half-open UTF-16 spans from Rust chunking to the UI | JavaScript strings use UTF-16 code units; computing exact spans in the Rust chunker lets every citation click highlight the exact passage in the source viewer. | The source viewer renders canonical extracted text with page anchors rather than a visual PDF canvas overlay. |
| Lexical support gate before display and speech synthesis | Only transmitted context IDs become clickable links, and unmatched numbers or literal quotations are filtered deterministically. | Does not prove semantic entailment and can strip ambiguous number formats. |
| Resumable podcast synthesis with Web Locks and a separate blob store | An episode takes up to 24 sequential TTS requests. Persisting individual turns and locking via Web Locks prevents duplicate cross-tab renders and enables exact resume on failure. | Requires modern desktop browsers with Web Locks and Web Audio API. |
→Result
- Live deployment at research-bench.nachtigall.dev featuring full EN/DE localization, local semantic search, chat, three Studio text generators and a two-host podcast studio.
- 635 automated tests passing in v0.1.0: 489 Vitest unit tests, 10 native Rust tests, 92 Playwright E2E tests and 44 security/CSP tests across Chromium and Firefox.
→Evidence
- Verification
- 635 tests (v0.1.0)
- Local pipeline
- 0 backend
- Source precision
- UTF-16 spans
489 unit · 10 Rust · 92 E2E · 44 security (Chromium & Firefox)
Pdfium WASM, ONNX embeddings and vectors in IndexedDB
Clickable citations and lexical support gate for chat & podcasts
→What I'd change today
Open-ended prompts make it tempting to chase surface-level feature count. Bounding the scope around inspectable source spans, strict IndexedDB revision semantics and explicit trust boundaries produced a much stronger system than a broader, fragile clone.