Otobong Okoko
Back to lab
Learning AppStatus: Live

LiD Prep

A free, ad-free study app for Germany's Leben in Deutschland and naturalisation tests. It turns the official 460-question catalogue into 44 bilingual chapters, so you learn the civics behind the answers instead of memorising them.

View project (opens in a new tab)

Tech Stack

  • Next.js 15
  • React 19
  • TypeScript
  • Tailwind 4
  • Drizzle ORM
  • Supabase Postgres
  • Playwright + axe-core
  • Gemini / Claude (content pipeline)

Problem

Anyone applying for permanent residence or citizenship in Germany sits a 33-question test drawn from a public catalogue of 460. Almost every prep app treats that catalogue as flashcards: see the question, memorise the letter, repeat. It works, and you come out knowing nothing about why the Bundesrat exists or what happened in 1953.

I wanted a prep tool that teaches the civics behind each answer, and that puts English next to the German, because reading legal German is its own hurdle for someone still learning the language.

Approach

The main design decision was to treat the catalogue as a curriculum. I grouped all 460 questions into 44 chapters, 28 federal and one for each of the 16 Bundesländer, in four shapes: chronological, institutional, civic and regional. Each chapter is written as a narrative in German and English, with section recaps, glossed terms and audio. The stated goal in my taxonomy doc is a learner who finishes having read a coherent German civics book, not 460 isolated flashcards.

Questions carry a contextual summary that explains why the answer is right and cites the law, event or institution behind it, with phrase-level highlights typed as date, event, concept, person, place or law. Those summaries come from a build pipeline: extract the questions from the official BAMF PDF, merge the answer key, translate, generate context with Gemini or Claude, validate, then seed the database. Generation runs in batches of ten with a human review gate after the first batch and a drift check every five, because a confident wrong explanation of constitutional law is worse than no explanation. 231 of the 460 summaries are live so far. All 44 chapters are complete in both languages.

The mock exam began as one random set of 33 questions. I replaced it with ten curated sets that together cover the entire pool, so finishing all ten means you have attempted every question. Graded runs are owned by the server: it picks the questions, holds the clock and scores from stored answers, and you can pause on one device and resume on another.

Chapters are Markdown files held to an authoring contract that a pre-commit hook and CI both enforce. Themes are JSON compiled to CSS variables, and components never hardcode a colour, font, radius or shadow. Accessibility targets WCAG 2.1 AA, with axe-core and a contrast check running in CI.

One feature is deliberately switched off. Community Insight shows how other learners perform on each question, as aggregates only, with no leaderboard and no names. It stays dark until at least ten learners have data. I read the research on social comparison in learning before designing it, and the floor exists so that nobody is ever compared against a handful of people.

Key Learnings

  • Restructuring content changed the product more than any interface work did. The same 460 questions teach something different when they are ordered as a story.
  • AI-generated explanations need a gate, not a spot check. Reviewing batch one by hand and checking for drift on a schedule caught problems that sampling at the end would have missed.
  • Writing the rules down for the AI pays off. A design-token contract and a chapter authoring contract, both enforced in CI, kept about 100 commits of Claude Code output consistent.
  • Deciding what not to show is design work. The ten-learner floor on Community Insight is a product decision about fairness, written as a feature flag.
  • Server-owned exam attempts cost more to build than local state, and they are the reason pause and resume across devices works at all.