A mobile-only, single-user read-later app. Save a link. Get it extracted and AI-summarized on-device. Read it later.
Why • Architecture • Project layout • Running • Git workflow
No backend, no account, no server bill. Everything — storage, extraction, AI enrichment, backup — runs on the phone. Built for one person's reading list, not a product.
| Concern | Approach |
|---|---|
| Storage | On-device SQLite (expo-sqlite) — a single content table plus a settings table (theme, etc). |
| Extraction | On-device. Articles via node-html-parser (fetch HTML, pull og:* meta + main content). YouTube via the public oEmbed endpoint — no duration/transcript, since there's no client-side yt-dlp equivalent. |
| AI enrichment | Summary + topics, called directly from the app (OpenAI or Gemini, picked by EXPO_PUBLIC_AI_PROVIDER) using an embedded API key. Acceptable only because this is a private, single-user build — never distribute the APK with a real key baked in. |
| Search & tags | LIKE-based full-text search plus freeform tags, both stored as plain SQLite columns — no FTS5, no new table, sized to a personal library. |
| Reading extras | Scroll-position resume (fraction of content height) and a quote/note highlight list per item — not inline text-selection highlighting. |
| Backup / sync | Mihon-style JSON export of the whole library, and import of either a full backup or a plain [{url, contentType?}] list (e.g. hand-written on a PC, imported via Settings → Import from file). No server, no relay. |
Spec source of truth lives in openspec/specs/. Read it before changing behavior — see CLAUDE.md.
mobile/ Expo Router app (this is what you build/run)
app/ screens (index, capture, content/[id], settings, stats)
src/api/content.ts capture/list/get/search/archive/tags — same interface pre- and post- backend removal
src/db/ sqlite tables + repositories
src/extraction/ article + youtube extractors, reading time
src/ai/enrich.ts AI summary/topics call
src/processing/ pending -> ready pipeline, stuck-row sweep on launch
src/backup/ export/import
backend/ retired; no longer called by the app
openspec/ specs + change history (source of truth for product behavior)
Setup steps
cd mobile
npm install
npx expo run:android # or run:iosRequires mobile/.env (see mobile/.env.example):
EXPO_PUBLIC_AI_PROVIDER=gemini # or "openai"
EXPO_PUBLIC_GEMINI_API_KEY=
EXPO_PUBLIC_OPENAI_API_KEY=Must be UTF-8, not UTF-16. Notepad/PowerShell saves sometimes write UTF-16LE, which Expo can't read.
Each OpenSpec change gets its own branch off main, merged back with --no-ff once done. Never commit feature work directly to main. See CLAUDE.md.
