AI / Product
Saaliheen
A word-by-word Quran reader for web and mobile, in English and Urdu, with grammar, roots, recitation and a Quran Chat that answers only from cited translations and Tafsir.
- Role
- Sole engineer: design, build and ship
- When
- 2026
- Source
- Private · walkthrough on request
- Stack
- Next.js
- Expo / React Native
- Neon Postgres
- pgvector
- Claude API
- Better Auth
- Vercel
- Playwright
- 814 commits between 29 Sep and 15 Nov 2026, across a Next.js website, an Expo app and six shared TypeScript packages in one pnpm monorepo.
- 83 ADR files record each architectural decision, superseded ones included, along with why they changed.
- 229 Gherkin feature files specify the dataset, the website and the published API contract, and run with Vitest and Playwright.
Problem
Most readers without Arabic meet the Quran through a translation. They can’t see which word carries which meaning, or how words share roots. Saaliheen shows the Quran word by word in two scripts (Uthmani and Indo-Pak). Each word has its meaning, root, lemma and grammar, and there’s recitation with word timings, titled passages, themes and a Tafsir, in English and Urdu. It has to work as a website and as a mobile app that reads offline, and its question-answering feature must never answer beyond its sources.
My role
Sole engineer, covering design, build and shipping. Git history shows a single author. I wrote the domain glossary (CONTEXT.md) and the ADRs, built the website, mobile app, data import and admin editor, and set up CI and deployment.
Architecture
It’s a pnpm monorepo: apps/web (Next.js on Vercel), apps/mobile (Expo, React Native), and shared packages for the dataset build, passage rules, sharing, known words, reading places and Quran Chat. Quran text and word analysis come from Tarteel’s Quranic Universal Library (QUL) and the Quranic Arabic Corpus. The sources are committed to the repo, and pnpm db:migrate imports them into Neon Postgres before each deploy. Saaliheen’s own data (passages, phrases, themes, Allah references, supplications) lives only in Neon and is edited in an admin area.
Website pages read Neon on request through one server-side data layer. Each Surah is cached under its own tag, and a script warms the cache after every deploy. The mobile app is another client. It reads versioned static files under /data/v1 (a manifest with each Surah file’s SHA-256) and a versioned JSON API under /api/v1 for accounts, sync and Quran Chat. For Quran Chat, every translation and Tafsir entry is embedded into pgvector, and Claude answers on the server, streaming cited text.
Key decisions
- One database behind a versioned API (ADR 0011). The first design was a static dataset with no database (ADR 0001), and admin edits were Git commits (ADR 0019). Once the app and more editable data arrived, everything moved into Neon. A publish became one Postgres transaction (ADR 0047) with row-level audit (ADR 0022) and undoable revisions (ADR 0049).
- Pages render on request, cached by tag (ADRs 0020, 0046). Edits reach readers without a rebuild. I tracked down a clock-drift bug in Next.js 16.3.6’s tag expiry and patched it in place with
pnpm patch(ADR 0042), pinned so an upgrade forces a re-check. - Offline Surahs are files, refreshed by manifest hash (ADR 0025). Editing one Surah re-downloads that one file, and the rest of the dataset stays put.
- Retrieval stays exact and grounded (ADRs 0056, 0057). Sources are
text-embedding-3-smallvectors stored ashalfvec(1536)and searched exactly with no approximate index, since roughly 25,000 rows search quickly. Only the server calls Claude. The answer streams as NDJSON and cites what it used. - Earlier semantic-search work. Saaliheen grew out of two earlier projects. Ask Quran (
ai-quran, from March 2026) fused pgvector HNSW dense search with Postgres full-text (BM25-style) keyword search through Reciprocal Rank Fusion. Quran Says swapped generated chunks for source-defined passages (its ADR 0002) and kept RRF (k = 60) with a relevance floor. It also required machine-written text to be generated once per passage and reviewed (its ADR 0003). Its themes were imported into Saaliheen once (ADR 0012).
Outcomes
- GitHub Actions checks every push to
mainand deploys it to Vercel, then warms each page and checks for a 200. - Inside v1 the published dataset only gains fields, enforced by scenarios in
published-dataset.feature.