andrewlb notes

Ordtegl

Ordtegl

Tools

Claude CodeSwiftSwiftUISwiftDataFSRS v5PostHogExpressTypeScriptPostgreSQLPrismaZodRailwayXcode Cloud

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

LayerTechnology
iOS AppSwift + SwiftUI (iOS 17+) + SwiftData
SRS AlgorithmFSRS v5 (manual implementation)
AnalyticsPostHog iOS SDK 3.38
AuthSign in with Apple (JWT sessions)
BackendTypeScript + Express.js + PostgreSQL + Prisma ORM
ValidationZod schemas
Content PipelineTypeScript CLI (COR corpus + OpenSubtitles + Claude API)
DeploymentRailway (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 @Observable macro
  • 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

VersionFocus
v1.0MVP: FSRS engine, interactive review, 360-item vocabulary, exam pacing
v1.1Vocabulary expansion 360->2,330; rich linguistic modeling
v1.2Frequency-based 3,500 items; warm design system; brand identity
v1.3App Store compliance; 74 unit + 14 UI tests; PostHog; Sign in with Apple
v1.4Alternative translations; vocabulary cleanup; vocabulary browser
v1.5iCloud backup; New Learner Boost; live countdowns; smart notifications
v2026.1CalVer; SRS acceleration for levels 1-5; WaniKani-style penalty
v2026.2Production reviews (EN->DK); Gender quizzes; Verb conjugation quizzes

Architectural Decisions

DecisionRationale
iOS native over cross-platformTsurukame-quality UX, single-platform focus
SwiftUI + SwiftDataModern Apple stack, iOS 17+, @Observable integration
Manual FSRS implementationswift-fsrs 5.0.0 has broken public API
LWW conflict resolutionSimple, predictable for single-user sync
AI-generated vocabularyNo official PD3 wordlists publicly available
Sign in with AppleSimpler 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 analyticsAnalyticsEvent enum + AnalyticsProtocol for 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 automationmode: "yolo" (no human gates), max_concurrent_agents: 3
  • Atomic commitsfeat(phase-id): description format, 762 commits with disciplined branching
  • Content-as-code — Vocabulary pipeline, SRS retention targets, pacing curves all in versioned code