TinyWords
하루 단어를 익히고 간격 복습·스트릭으로 이어가는 어휘 학습 앱 - AI 단어 생성·문장 코칭·발음 채점을 얹은 웹·모바일·데스크톱 MVP.
- TypeScript
- PWA
- Capacitor
- OpenAI
- Language Learning
Setup
- Problem
어휘 학습의 핵심은 "하루 조금씩 + 잊을 때쯤 다시"인데, 대부분의 도구가 그 리듬(간격 복습·스트릭)을 학습자 대신 관리해주지 못해 결국 학습자의 의지에 떠넘긴다.
- Context
SSOT 스펙 문서를 먼저 쓰고 그 기준으로 구현한 스펙 우선 프로젝트. 웹 PWA를 Capacitor(iOS·Android)로 감싸고, Electron(macOS) 데스크톱은 2차 타깃으로 준비했다.
Build
- What I did
- 하루 학습 계획(Today), 간격 복습 큐, 스트릭
- 발화(speech) 시도와 발음 채점
- AI 단어 생성·문장 코칭
- 도메인 규칙 → API(Supabase Postgres 기반) → 웹·모바일 패키징까지 스펙 주도로 수직 관통
- Product decisions
- SSOT 문서 먼저, 코드 나중 - 화면·정책·API·보안·테스트를 문서로 못박고 구현이 그걸 따르게
- 한 코드베이스 멀티플랫폼 - PWA를 Capacitor·Electron으로 감싸 웹·앱·데스크톱을 한 번에
- 복습·스트릭·발화 규칙을 API·UI에서 분리 - 학습 규칙을 테스트 가능한 자리에 두기 위해
- QA considerations
- 복습이 정확한 시점에 뜨는가 - D-1 성공 시 D-3 승급, 실패 시 다음날 재큐·다음 단계 미생성, 밀린 복습은 오래된 것 먼저(단위 테스트로 고정)
- 스트릭이 틀리지 않는가 - 연속·공백·같은 날 케이스를 단위 테스트로 못박았는가
daily_target제약을 DB CHECK(BETWEEN 3 AND 5)와 실제 Supabase 통합 테스트로 이중 방어하는가(8 →VALIDATION_ERROR)- AI 단어 생성이 출력 검증·1회 재시도·폴백 단어 풀로 실패해도 학습이 끊기지 않는가
- 한 코드베이스를 Capacitor(iOS·Android)·Electron(macOS)로 감쌌을 때 도메인 규칙 동작이 플랫폼별로 동일한가
- Supabase RLS로 다른 학습자의 단어·복습 큐·스트릭이 서로 새지 않게 데이터 접근 경계가 지켜지는가
- 웹 PWA로 연결이 끊긴 상태에서도 학습이 조용히 깨지지 않고, 재연결 시 복습 큐·스트릭이 서버 상태와 어긋나지 않게 동기화되는가
Outcome
- Metrics
이틀 집중 빌드로 라이브(tinywords-jet.vercel.app)까지. Supabase Postgres 백엔드에, Cloudflare Pages Functions로도 배포 가능한 0.1.0 MVP.
진행 과정 (기획 → 개발)
문제·가설에서 스코프·스펙(SSOT)·개발·QA 게이트·배포까지, 같은 틀로 이 앱을 어떻게 기획하고 끌고 갔는지.
진행 과정 (기획 → 개발)
문제 · 가설
어휘 학습의 핵심은 "하루 조금씩 + 잊을 때쯤 다시"인데, 대부분의 도구가 그 리듬(간격 복습·스트릭)을 학습자의 의지에 떠넘긴다. 앱이 간격 복습(D-1·D-3·D-7)과 스트릭을 대신 정확히 관리해주면 작은 루틴이 흔들리지 않고 이어진다는 가설로 출발했다.
역량 · 문제 정의 · 가설
스코프 · 우선순위
docs/02_MVP_SCOPE.md에서 "하루 3-5개를 회상 중심으로 꾸준히 학습하게 하는가"를 단일 기준으로 Must·Should·Won't를 갈랐다. Must 코어는 화면 4종(Today·Inbox·History·Settings)과 복습 큐·스트릭·AI 단어 생성·문장 코칭이고, 소셜·과금 구독·음소 단위 정밀 발음 분석은 Won't로 뺐다.
역량 · 스코핑 · 우선순위
스펙 우선 (SSOT)
docs/ 아래 25개 SSOT 문서를 먼저 쓰고, docs/SSOT.md가 문서 우선순위와 "문서 → 테스트 → 코드" 변경 순서를 못박았다. 핵심 구현 파일마다 근거 문서를 헤더 주석으로 달아(src/domain/review.ts→11번, streak.ts→12번, 마이그레이션→04_DATA_MODEL) 스펙과 코드가 조용히 어긋나지 못하게 했다.
역량 · 스펙 · 문서화
개발 · 이터레이션
학습 규칙을 UI·API에서 떼어 src/domain으로 분리하고, 백엔드는 Supabase Postgres(마이그레이션 4종, RLS·CHECK 제약)로, 단어 생성은 OpenAI 호출에 출력 검증·1회 재시도·폴백 단어 풀(server/src/fallback-words.ts)을 얹었다. 웹 한 코드베이스를 Capacitor(iOS·Android)로 감싸고 Electron(macOS)을 2차 타깃으로 뒀으며, 이틀·32커밋으로 라이브까지 갔다.
역량 · 실행 · 전달
검증 (QA 게이트)
학습 앱의 신뢰는 화면이 아니라 도메인 규칙의 정확성에 달려 있다고 보고, 복습·스트릭 규칙을 단위 테스트로 못박았다(연속·공백·같은 날 스트릭, D-1 성공 시 D-3 승급, 실패 시 다음날 재큐·다음 단계 미생성, 밀린 복습은 오래된 것 먼저). daily_target 제약은 DB CHECK(BETWEEN 3 AND 5)와 실제 Supabase를 쓰는 통합 테스트(daily_target 8 → VALIDATION_ERROR, DayPlan 생성·완료·복습 큐 조회가 에러 없이 통과)로 이중으로 지키고, 릴리즈 전 회귀 기준은 docs/19_TEST_PLAN.md·20_RELEASE_CHECKLIST.md의 Go/No-Go 게이트로 문서화했다.
역량 · 품질 · 리스크
배포
v0.1.0을 tinywords-jet.vercel.app(Vercel)에 라이브로 올렸고, 같은 web/ 산출물은 Cloudflare Pages(wrangler.toml + functions/_middleware.ts가 런타임 환경변수로 /config.js 주입)로도 배포할 수 있게 뒀다. Blocker·Critical 미해결 시 출시 금지 기준을 docs/20_RELEASE_CHECKLIST.md에 문서화한 이틀 빌드 MVP 상태다.
역량 · 릴리스
- Tech stack
- TypeScript
- PWA
- Capacitor
- Electron
- OpenAI
- Supabase