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.

Browser Content Script

Injects UI · viewport-scoped parsing

MV3 Background Worker

Messaging · auth · sync queue

Server Backend API

Node.js · PostgreSQL

Shared contract TypeScript types package

Imported by every surface — one source of truth

Content script and background worker communicate over the extension messaging bus; the worker syncs with the backend. A shared types package binds all three.
  • 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.

  1. 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.

  2. 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.

  3. 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.