
Ordtegl
Tools
What worked
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 a compact algorithm port 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) generated a vocabulary set that would have been infeasible to hand-author — but required a post-launch cleanup pass when fabricated verb conjugations were discovered stored as separate vocabulary entries.
What broke
API configuration needs production hardening — a deployment blocker that keeps surfacing in review. Backend not yet deployed to Railway. Privacy policy exists locally but isn't at a public URL (App Store will reject). Verb conjugation coverage is only 48.5%. Some sync/network operations use empty catch blocks — silent failures I keep meaning to fix.
Roles
I set the pedagogical direction — FSRS over Anki because the reset-on-failure behavior matches how my daughter learns, WaniKani-style level gating for sequence-based progression, exam pacing so study loads recalculate based on time-to-PD3. Claude Code wrote the SwiftUI screens, the FSRS port, and the content pipeline.
Ordtegl (Danish Vocabulary Learning App)
Overview
Ordtegl ("Word Game" in Danish) is an iOS app for learning Danish vocabulary and grammar using spaced repetition, 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.
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).
What It Does
- FSRS v5 spaced repetition with WaniKani-style 9-stage progression (Apprentice through Burned)
- 32 CEFR-aligned levels with ~3,200 vocabulary items from Danish frequency lists
- Four quiz types: Recognition (multiple choice), Production (EN->DK typing), Gender (en/et), Verb Conjugation
- Exam pacing — Set your exam date; the app calculates daily learning quotas and recalculates as you progress
- Offline-first — Full functionality without network; syncs when online via iCloud + backend
- Rich linguistic data — Nouns with gender/paradigms, verbs with conjugation forms, adjectives with agreement patterns
- AI-generated vocabulary — No official PD3 wordlists exist publicly; content pipeline uses COR corpus + OpenSubtitles + Claude API with validation gates
How It Fits Together
Native iOS app (Swift + SwiftUI + SwiftData, iOS 17+) with a TypeScript/Express/PostgreSQL backend for sync and a TypeScript CLI content pipeline for vocabulary generation. Analytics via PostHog, auth via Sign in with Apple. Deployment targets Railway (backend) and Xcode Cloud (CI/CD). The app works fully offline; the backend is for cross-device sync.
Architecture Decisions
- iOS native over cross-platform — Chose Tsurukame-quality UX over reach; single-platform focus.
- Manual FSRS v5 implementation — The swift-fsrs 5.0.0 package had a broken public API, so Claude ported the algorithm directly from the spec. Maintenance burden tradeoff accepted for correctness.
- AI-generated vocabulary with quality gates — No official PD3 wordlists exist publicly. The pipeline generates from corpus frequency data + translations, but this introduced a class of bugs: fabricated verb conjugations stored as separate items required a cleanup pass post-launch.
- LWW conflict resolution — Simple and predictable for what's effectively single-user sync. Progress-aware: the "more advanced" review state always wins.
- Sign in with Apple — Simpler than email/password, required for App Store compliance anyway.
Iteration and Lessons
The vocabulary went through three major expansions (360 -> 2,330 -> 3,500 items) and one contraction (3,500 -> 3,234) when a post-launch audit found fabricated verb forms. The content pipeline needed validation gates that didn't exist in v1 — conjugation items were being generated as standalone vocabulary entries instead of being stored as fields on parent verb items. This is the canonical AI-generated-content lesson: the generation is fast but the validation infrastructure needs to be built first, not after.
The SRS tuning went through two significant iterations: v2026.1 added acceleration for early levels (levels 1-5 felt too slow for motivated learners) and a WaniKani-style penalty system. v2026.2 added production quizzes (EN->DK typing) and gender quizzes — both surfaced by actually using the app daily and noticing which skills weren't being tested.
The design system went through a full rework in v1.2 — the original look felt clinical. The warm redesign with 19 semantic color tokens and automatic dark mode was a bigger UX improvement than any feature addition.
Weaknesses & Open Questions
- API configuration needs production hardening — a deployment blocker that keeps surfacing in review
- Backend not deployed — No Railway instance running; the app works offline but sync is unavailable
- Privacy policy not at a public URL — App Store submission will be rejected until this is deployed
- Verb conjugation coverage only 48.5% — 380/845 verbs have complete paradigms
- Empty catch blocks in sync/network code — silent failures that mask real problems
- OpenSubtitles example sentences tend toward informal/conversational Danish; may not match exam register
- Open question: Is the manual FSRS implementation worth maintaining long-term, or should I contribute fixes upstream to swift-fsrs?