TinyWords
A vocabulary app that builds a daily word habit with spaced review and streaks - a web/mobile/desktop MVP with AI word generation, sentence coaching, and pronunciation scoring.
- TypeScript
- PWA
- Capacitor
- OpenAI
- Language Learning
Setup
- Problem
Vocabulary learning is really "a little each day + again right before you forget," but most tools don't manage that rhythm (spaced review, streaks) for the learner, so it falls back on willpower.
- Context
A spec-first project: the SSOT docs were written first and the implementation followed them. A single web PWA is wrapped with Capacitor (iOS/Android), with an Electron (macOS) desktop build prepared as a secondary target.
Build
- What I did
- A daily study plan (Today), a spaced-review queue, and streaks
- Speech attempts and pronunciation scoring
- AI word generation and sentence coaching
- A spec-driven vertical slice - domain rules → API (backed by Supabase Postgres) → web/mobile packaging
- Product decisions
- SSOT docs first, code second - pin screens, policy, API, security, and tests in docs and let the code follow
- One codebase, multiple platforms - wrap the PWA with Capacitor and Electron to cover web, app, and desktop at once
- Separate review/streak/speech rules from API and UI - to keep the learning rules in a testable place
- QA considerations
- Does review surface at the right moment - D-1 success promotes to D-3, a failed review re-queues to the next day with no next stage, overdue reviews surface oldest-first (pinned by unit tests)
- Does the streak never miscount - consecutive/gap/same-day cases locked down by unit tests
- The
daily_targetconstraint is guarded twice - a DB CHECK (BETWEEN 3 AND 5) and a real-Supabase integration test (8 →VALIDATION_ERROR) - AI word generation with output validation, one retry, and a fallback word pool keeps learning going even on failure
- Wrapped as one codebase in Capacitor (iOS/Android) and Electron (macOS), the domain-rule behavior is identical across platforms
- Supabase RLS keeps the access boundary so one learner's words, review queue, and streak never leak into another's
- As a web PWA, learning doesn't silently break when the connection drops, and on reconnect the review queue and streak sync back without diverging from server state
Outcome
- Metrics
A two-day build reached live (tinywords-jet.vercel.app). A 0.1.0 MVP backed by Supabase Postgres, also deployable as Cloudflare Pages Functions.
Process (planning → build)
The same template from problem and hypothesis through scope, spec (SSOT), build, the QA gate, and release - how this was planned and driven.
Process (planning → build)
Problem & hypothesis
Vocabulary learning is really "a little each day + again right before you forget," yet most tools push that rhythm (spaced review, streaks) back onto the learner's willpower. The starting hypothesis: if the app itself reliably manages spaced review (D-1/D-3/D-7) and streaks, the small routine holds instead of breaking.
Skills · Problem framing · hypothesis
Scope & priority
Scope was split into Must/Should/Won't in docs/02_MVP_SCOPE.md against one test: does it keep someone learning 3-5 recall-first items a day? The Must core was the four screens (Today, Inbox, History, Settings) plus the review queue, streaks, and AI word generation and sentence coaching; social features, payments/subscriptions, and phoneme-level pronunciation analysis were pushed to Won't.
Skills · Scoping · prioritization
Spec-first (SSOT)
The 25 SSOT docs under docs/ were written first, with docs/SSOT.md pinning the document priority and the "docs then tests then code" change order. Each key implementation file cites its source doc in a header comment (src/domain/review.ts to doc 11, streak.ts to doc 12, migrations to 04_DATA_MODEL) so spec and code cannot silently drift.
Skills · Spec · documentation
Build & iteration
The learning rules were pulled out of the UI and API into src/domain; the backend is Supabase Postgres (four migrations with RLS and CHECK constraints), and word generation calls OpenAI with output validation, one retry, and a fallback word pool (server/src/fallback-words.ts). One web codebase is wrapped with Capacitor (iOS/Android) and Electron (macOS) as a secondary target, reaching live in a two-day, 32-commit build.
Skills · Execution · delivery
Verification (QA gate)
A learning app's trust rests on the accuracy of its domain rules, not its screens, so the review and streak rules are locked down by unit tests (consecutive/gap/same-day streak, a D-1 success promoting to D-3, a failed review re-queuing to the next day with no next stage, overdue reviews surfacing oldest-first). The daily_target constraint is guarded twice over - a DB CHECK (BETWEEN 3 AND 5) and an integration test against real Supabase (daily_target 8 rejected as VALIDATION_ERROR; creating, completing, and reading back the day plan and review queue without error) - and the pre-release regression bar is documented as the Go/No-Go gates in docs/19_TEST_PLAN.md and 20_RELEASE_CHECKLIST.md.
Skills · Quality · risk
Release
v0.1.0 shipped live to tinywords-jet.vercel.app (Vercel), and the same web/ output is also deployable to Cloudflare Pages (wrangler.toml plus functions/_middleware.ts injecting /config.js from runtime env vars). It is a two-day-build MVP whose stop rule - no release with open Blocker/Critical issues - is documented in docs/20_RELEASE_CHECKLIST.md.
Skills · Release
- Tech stack
- TypeScript
- PWA
- Capacitor
- Electron
- OpenAI
- Supabase