Skip to main content
강홍재/ James
← Work
MVP2026 · Solo Builder· Started(First Commit date)

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_target constraint 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.

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