Case Study · Architecture Breakdown
Poliglotter
How a two-person team shipped a validated, type-safe language-learning browser extension — the component graph, the sync model, and the trade-offs behind them.
- Role
- Co-Founder & Fullstack Engineer
- Year
- 2024
- Stack
- TypeScript · React · Chrome Extension APIs (MV3) · Node.js · PostgreSQL
The problem
Language learners spend hours reading real content in a foreign language, but the learning is passive — words seen once and forgotten. Poliglotter turns that passive reading into active, spaced-repetition learning in place, without pulling the user out of their flow.
The component graph
The extension is three cooperating surfaces plus a backend, with a shared type layer binding them together.
Injects UI · viewport-scoped parsing
Messaging · auth · sync queue
Node.js · PostgreSQL
Imported by every surface — one source of truth
- Content script injects UI into the page and runs viewport-scoped parsing.
- Background service worker (MV3) owns messaging, auth, and the sync queue.
- Backend API persists vocabulary and review state to PostgreSQL.
- Shared types package is imported by all of the above — the contract lives in one place.
The trade-off log
The decisions I’d want a senior engineer to interrogate — and my reasoning.
- 01
Real-time page parsing
Chose Incremental IntersectionObserver-driven parsing over Parsing the full DOM on load
Full-DOM parsing on content-heavy pages blocked the main thread and dropped frame rates. Parsing only what enters the viewport kept interaction smooth and scaled to long articles.
- 02
Extension ↔ backend contract
Chose A single shared TypeScript types package over Duplicated hand-written types on each side
One source of truth meant a schema change surfaced as a compile error across the whole stack, not a runtime bug in production.
- 03
State sync
Chose Optimistic local writes with background reconciliation over Round-tripping to the server on every capture
Vocabulary capture had to feel instant. Writing locally first and reconciling in the background kept the UX snappy while staying eventually consistent.
What I’d do next
Extract the parsing engine into a standalone, testable module with a fuzz-tested tokenizer, and add end-to-end tests that drive a real headless browser against a fixture corpus of pages.