
Ordtegl
Tools
What worked
6 weeks, 8 milestones, 762 commits, 47 phases of disciplined execution. Claude Code handled the Swift + SwiftUI + SwiftData stack cleanly despite it being the only iOS project in the portfolio. The manual FSRS v5 implementation (swift-fsrs had a broken public API) was 362 lines of algorithm that Claude got right from the spec. The offline-first sync with progress-aware LWW conflict resolution worked the first time. The content pipeline (COR corpus → OpenSubtitles → Claude API translations → enrichment → validation → bundle) generated a 3,500-item vocabulary set that would have been infeasible to hand-author. 132 tests (118 unit + 14 UI).
What broke
APIClient.baseURL is still hardcoded to localhost:3000 — a production blocker that keeps surfacing in review. Backend not yet deployed to Railway. Privacy policy not deployed (App Store will reject). Verb conjugation coverage is only 48.5% (380/845 verbs have complete paradigms). Some sync/network operations use empty catch blocks — silent failures I keep meaning to fix. OpenSubtitles example sentences are conversational and sometimes non-standard.
Roles
I set the pedagogical direction — FSRS over Anki because the reset-on-failure behavior matches how my daughter learns, WaniKani-style level gating because sequence-based progression reduces overwhelm, exam pacing so study loads recalculate based on time-to-PD3. Claude Code wrote the SwiftUI screens, the manual FSRS port, the content pipeline. The decision to use AI-generated vocabulary (no official PD3 wordlists exist publicly) was mine; the quality gates on generation were co-designed.
Ordtegl (Danish Vocabulary Learning App)
Overview
Ordtegl ("Word Game" in Danish) is a polished iOS app for learning Danish vocabulary and grammar using spaced repetition, specifically designed to help learners prepare for the PD3 (Prove i Dansk 3) exam. It uses FSRS-based spaced repetition with WaniKani-style 9-stage progression across 32 CEFR-aligned levels with 3,500 vocabulary items.
Target users: Danish learners at A1-B2 levels preparing for official exams, daily users motivated by structured progression.
Inspiration: WaniKani (level-based progression) + Tsurukame (polished mobile UX).
Key Features
- FSRS v5 SRS Engine with WaniKani-style 9-stage progression (Apprentice 1-4, Guru 1-2, Master, Enlightened, Burned)
- 32 CEFR-aligned levels with 3,500 vocabulary items from Danish frequency lists
- 4 Quiz Types: Recognition (multiple choice), Production (EN->DK typing), Gender (en/et), Verb Conjugation
- Exam Pacing: User sets exam date; app calculates daily learning quotas
- Offline-first Architecture: Full functionality without network; syncs when online
- Rich Linguistic Data: Nouns with gender/paradigms, verbs with conjugation forms, adjectives with agreement patterns
- Dashboard Analytics: Progress forecasts, study streaks, accuracy stats, level unlock ETA
Architecture
Tech Stack
| Layer | Technology |
|---|---|
| iOS App | Swift + SwiftUI (iOS 17+) + SwiftData |
| SRS Algorithm | FSRS v5 (manual implementation) |
| Analytics | PostHog iOS SDK 3.38 |
| Auth | Sign in with Apple (JWT sessions) |
| Backend | TypeScript + Express.js + PostgreSQL + Prisma ORM |
| Validation | Zod schemas |
| Content Pipeline | TypeScript CLI (COR corpus + OpenSubtitles + Claude API) |
| Deployment | Railway (backend), Xcode Cloud (CI/CD) |
App Structure
DanishLesson/
├── App/ # Entry point, lifecycle
├── Data/ # SwiftData models, networking, sync
├── Domain/ # Business logic (SRS, Review, Levels, Pacing, Statistics)
├── Features/ # Feature-specific views and ViewModels
├── Theme/ # Design system (colors, typography, spacing)
├── Resources/ # Assets, vocabulary.json (3,500 items)
└── Tests/ # 132 tests (118 unit + 14 UI)
backend/ # Node.js server
tools/
content-pipeline/ # Vocabulary generation CLI
Key Patterns
- MVVM with @Observable — Views don't hold state; ViewModels use
@Observablemacro - Offline-first sync — Reviews work 100% offline; progress-aware LWW conflict resolution
- Content pipeline — COR lemmas -> Claude API translations -> Enrichment -> Validation -> Bundle
- Asset Catalog-based design system — 19 semantic color tokens, automatic dark mode
Development History
Timeline: 6 weeks, 8 milestones, 762 commits, 47 phases
| Version | Focus |
|---|---|
| v1.0 | MVP: FSRS engine, interactive review, 360-item vocabulary, exam pacing |
| v1.1 | Vocabulary expansion 360->2,330; rich linguistic modeling |
| v1.2 | Frequency-based 3,500 items; warm design system; brand identity |
| v1.3 | App Store compliance; 74 unit + 14 UI tests; PostHog; Sign in with Apple |
| v1.4 | Alternative translations; vocabulary cleanup; vocabulary browser |
| v1.5 | iCloud backup; New Learner Boost; live countdowns; smart notifications |
| v2026.1 | CalVer; SRS acceleration for levels 1-5; WaniKani-style penalty |
| v2026.2 | Production reviews (EN->DK); Gender quizzes; Verb conjugation quizzes |
Architectural Decisions
| Decision | Rationale |
|---|---|
| iOS native over cross-platform | Tsurukame-quality UX, single-platform focus |
| SwiftUI + SwiftData | Modern Apple stack, iOS 17+, @Observable integration |
| Manual FSRS implementation | swift-fsrs 5.0.0 has broken public API |
| LWW conflict resolution | Simple, predictable for single-user sync |
| AI-generated vocabulary | No official PD3 wordlists publicly available |
| Sign in with Apple | Simpler than email/password, App Store compliant |
Strengths
- Clean separation — Data, Domain, Features layers fully independent
- Offline-first — Works 100% without network; iCloud + backend sync redundancy
- Type-safe analytics —
AnalyticsEventenum +AnalyticsProtocolfor mock injection - Rich content pipeline — Automated validation, traceability, reproducible generation
- Comprehensive tests — 132 tests covering FSRS, SwiftData, UI flows
- Exam-aware pacing — Daily workload recalculates based on time-to-exam
Weaknesses & Risks
- APIClient.baseURL hardcoded to localhost:3000 — BLOCKER for production
- Backend not deployed — No Railway instance running
- Privacy policy not deployed — App Store submission will be rejected
- Manual FSRS implementation — 362 lines of maintenance burden
- OpenSubtitles example sentences — Informal/conversational; may include non-standard Danish
- Verb conjugation coverage only 48.5% — 380/845 verbs have complete paradigms
- Empty catch blocks — Some sync/network operations silently fail
Prompting Patterns
- Explicit verification steps — Every plan includes
<verify>sections with bash commands - Context documents —
@references to PROJECT.md, MILESTONES.md, specific source files - Configuration-driven automation —
mode: "yolo"(no human gates),max_concurrent_agents: 3 - Atomic commits —
feat(phase-id): descriptionformat, 762 commits with disciplined branching - Content-as-code — Vocabulary pipeline, SRS retention targets, pacing curves all in versioned code