---
phase: 03-komplettes-spielerlebnis
plan: 02
type: execute
wave: 2
depends_on: [03-01]
files_modified:
- src/game/typing.ts
- src/game/typing.test.ts
- src/ui/screens.ts
- index.html
autonomous: true
requirements: [TYPE-05, TYPE-06, TYPE-07, LEVL-05, LEVL-06]
must_haves:
truths:
- "Entdecken phase shows companion text and highlights the new key on keyboard"
- "Woerter phase displays whole words with active letter highlighted"
- "Lesson flows through discover -> practice -> words automatically"
- "Every 3rd session is a review mixing letters from last 2-3 levels"
- "After 2x same level, companion shows encouragement with curiosity trigger"
artifacts:
- path: "src/game/typing.ts"
provides: "Extended createExerciseState with mode='letters'|'words', generateWordLetters()"
exports: ["createExerciseState", "generateWordLetters"]
- path: "src/ui/screens.ts"
provides: "Lesson sub-phase state machine: discover -> practice -> words"
contains: "LessonPhase"
key_links:
- from: "src/ui/screens.ts"
to: "src/game/typing.ts"
via: "createExerciseState for practice and word phases"
pattern: "createExerciseState"
- from: "src/ui/screens.ts"
to: "src/companion/companion.ts"
via: "getLetterIntro for discover phase"
pattern: "getLetterIntro"
- from: "src/ui/screens.ts"
to: "src/game/words.ts"
via: "getWordsForLevel for word phase"
pattern: "getWordsForLevel"
---
Build the complete lesson flow with 3 sub-phases (Entdecken -> Ueben -> Woerter) as an internal state machine, plus review mechanic and repetition protection.
Purpose: This is the core game loop — the child experiences discover, practice, then words in one fluid session.
Output: Extended typing engine with word mode, lesson screen with 3-phase state machine, review and repetition logic.
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-komplettes-spielerlebnis/03-01-SUMMARY.md
@src/game/typing.ts
@src/ui/screens.ts
@src/companion/companion.ts
@src/game/words.ts
@src/game/levels.ts
@src/types.ts
@index.html
export type LessonPhase = 'discover' | 'practice' | 'words';
export interface Progress {
id: 1;
currentLevel: number;
completedLevels: number[];
totalSessions: number;
sessionDates: string[];
selectedCharacter: CompanionType;
selectedLayout: KeyboardLayout;
totalCorrect: number;
totalErrors: number;
errorKeyCounts: Record;
lastPlayedLevels: number[];
}
export const wordsByLevel: Record;
export function getWordsForLevel(level: number, count: number): string[];
export async function getLetterIntro(characterType: CompanionType, letter: string, fingerDescription: string, db: IDBDatabase): Promise;
export function createExerciseState(level: number): TypingExerciseState;
export function handleKeyPress(state: TypingExerciseState, pressedKey: string): KeyPressResult;
export function getCurrentLetter(state: TypingExerciseState): string | null;
export function getLevelByNumber(n: number): Level | undefined;
export function getKeysUpToLevel(n: number): string[];
Task 1: Extend typing engine with word mode
src/game/typing.ts, src/game/typing.test.ts
src/game/typing.ts, src/game/words.ts, src/types.ts
- Test: createExerciseState(level) still works as before (letters mode, backwards compatible)
- Test: createWordExerciseState(level, words) creates state with letters from the words joined, e.g. words=["eis","die"] -> letters=["e","i","s","d","i","e"]
- Test: createWordExerciseState stores words and wordBoundaries in state for UI rendering
- Test: handleKeyPress works the same way for word-mode state
- Test: getWordAtIndex(state, letterIndex) returns which word the letter belongs to
Extend `src/game/typing.ts` (per D-03):
1. Add `ExerciseMode` type: `'letters' | 'words'`
2. Extend `TypingExerciseState` interface (in types.ts) with optional word fields:
```typescript
export interface TypingExerciseState {
currentLetterIndex: number;
letters: string[];
intervalMs: number;
correctStreak: number;
totalCorrect: number;
totalErrors: number;
mode: 'letters' | 'words';
words?: string[]; // original words for display
wordBoundaries?: number[]; // indices where each word starts in letters[]
}
```
3. Keep existing `createExerciseState(level)` unchanged but set `mode: 'letters'`, `words: undefined`, `wordBoundaries: undefined`.
4. Add new function:
```typescript
export function createWordExerciseState(words: string[]): TypingExerciseState {
const letters: string[] = [];
const wordBoundaries: number[] = [];
for (const word of words) {
wordBoundaries.push(letters.length);
for (const ch of word) {
letters.push(ch);
}
}
return {
currentLetterIndex: 0,
letters,
intervalMs: 0, // no falling in word mode
correctStreak: 0,
totalCorrect: 0,
totalErrors: 0,
mode: 'words',
words,
wordBoundaries,
};
}
```
5. Add helper: `getCurrentWordIndex(state: TypingExerciseState): number` — returns which word index (0-based) the currentLetterIndex is in, using wordBoundaries.
6. `handleKeyPress` already works generically — no changes needed.
Create `src/game/typing.test.ts` (or extend if exists) with vitest tests for the new functions plus backwards compatibility.
npx vitest run src/game/typing.test.ts
- grep "createWordExerciseState" src/game/typing.ts returns export
- grep "getCurrentWordIndex" src/game/typing.ts returns export
- grep "mode.*letters.*words" src/types.ts returns match showing mode field
- npx vitest run src/game/typing.test.ts exits 0
Typing engine supports word mode with word boundaries tracking. Backwards compatible with letter mode. Tests pass.
Task 2: Lesson screen 3-phase state machine with review and repetition
src/ui/screens.ts, index.html
src/ui/screens.ts, src/game/typing.ts, src/companion/companion.ts, src/game/words.ts, src/game/levels.ts, src/types.ts, index.html
Rewrite `initLessonScreen` in `src/ui/screens.ts` as a 3-phase state machine (per D-01, D-02, D-03):
**Add to index.html** inside `#screen-lesson .lesson`:
```html
```
This shows which phase is active (Entdecken / Ueben / Woerter). Add above the letter-area.
**Signature change:**
```typescript
export function initLessonScreen(
db: IDBDatabase,
level: number,
layout: KeyboardLayout,
onComplete: (level: number, stats: { totalCorrect: number; totalErrors: number; errorKeys: Record }) => void,
onCancel?: () => void,
): () => void
```
The `onComplete` callback now receives exercise stats (per D-16).
**State machine flow:**
1. **DISCOVER phase** (per D-02, TYPE-05):
- Show phase indicator: "Entdecken"
- Get the first new key of this level via `getLevelByNumber(level).newKeys[0]`
- Fetch companion letter intro via `getLetterIntro(characterType, letter, fingerDescription, db)`
- Display intro text in `#lesson-companion-text`
- Highlight the target key on keyboard (already have `highlightKey()`)
- Make key pulse (add class `keyboard__key--discover-pulse` to the key)
- Wait for child to press the correct key ONCE to proceed
- If level has a second newKey, repeat discover for that key too
- Then transition to PRACTICE
2. **PRACTICE phase** (existing behavior, TYPE-01-04):
- Show phase indicator: "Ueben"
- Run existing letter exercise with `createExerciseState(level)`
- On complete, accumulate stats, transition to WORDS
3. **WORDS phase** (per D-03, TYPE-06):
- Show phase indicator: "Woerter"
- Get 3-4 random words via `getWordsForLevel(level, 4)` (fallback to 3 if < 4 available)
- Create word exercise via `createWordExerciseState(words)`
- Display current word fully, highlight active letter:
```html
e
i
s
```
- When word complete, show next word. After all words done, call `onComplete(level, stats)`.
**Review mechanic (per D-05, LEVL-05):**
Add `isReviewSession` parameter to `initLessonScreen`. When `true`:
- Discover phase: skip (reviews don't introduce new letters)
- Practice phase: use mixed letters from last 2-3 levels (combine allKeys from current and previous 1-2 levels)
- Words phase: pick words from last 2-3 levels randomly
The review trigger logic (`totalSessions % 3 === 0`) is handled in `app.ts` (Plan 04), not here. This function just needs to accept the `isReview: boolean` flag.
**Repetition protection (per D-06, LEVL-06):**
Add `showEncouragement` parameter. When `true`:
- Before discover phase, show companion encouragement text: a hardcoded array of 3-4 messages like "Magst du sehen, was als Naechstes kommt? Im Wald gibt es noch so viel zu entdecken!"
- Show for 3 seconds, then proceed to discover (or practice if review).
The trigger logic (checking `lastPlayedLevels` for 2x same) is handled in `app.ts` (Plan 04).
**Cumulative stats across phases:**
Track `totalCorrect`, `totalErrors`, `errorKeys: Record` across all 3 phases. In handleKeyPress wrapper, if `!result.correct`, increment `errorKeys[expectedKey]`. Pass accumulated stats to `onComplete`.
**CSS for phase indicator** — add styles in action:
```css
.lesson__phase-indicator {
text-align: center;
font-family: var(--font-heading);
font-size: 1rem;
color: var(--text-warm);
margin-bottom: 0.5rem;
opacity: 0.7;
}
```
**CSS for word display:**
```css
.word-display {
display: flex;
justify-content: center;
gap: 0.25rem;
font-family: var(--font-heading);
font-size: 2.5rem;
margin: 2rem 0;
}
.word-display__letter {
color: var(--text-light);
transition: color 0.3s;
}
.word-display__letter--active {
color: var(--accent-gold);
font-weight: 700;
}
.word-display__letter--done {
color: var(--accent-green);
}
```
Add CSS for discover pulse:
```css
.keyboard__key--discover-pulse {
animation: discover-pulse 1.5s ease-in-out infinite;
}
@keyframes discover-pulse {
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 var(--accent-gold); }
50% { transform: scale(1.1); box-shadow: 0 0 12px 4px var(--accent-gold); }
}
```
npx tsc --noEmit && npx vitest run
- grep "lesson-phase-indicator" index.html returns a match
- grep "discover.*practice.*words" src/ui/screens.ts shows state machine phases
- grep "getLetterIntro" src/ui/screens.ts shows companion integration in discover
- grep "createWordExerciseState" src/ui/screens.ts shows word mode usage
- grep "getWordsForLevel" src/ui/screens.ts shows word list integration
- grep "isReview" src/ui/screens.ts shows review parameter
- grep "showEncouragement" src/ui/screens.ts shows repetition protection parameter
- grep "word-display" src/styles/main.css returns CSS matches
- grep "discover-pulse" src/styles/main.css returns CSS matches
- npx tsc --noEmit exits 0
Lesson screen runs 3 phases (discover->practice->words) with companion letter intro, word display UI, review flag support, and repetition encouragement. Stats accumulated across phases and passed to onComplete callback.
- `npx tsc --noEmit` — no type errors
- `npx vitest run` — all tests pass
- Manual: lesson screen shows phase indicator, discover phase displays companion text and highlights key, words phase shows word with active letter
1. Typing engine has createWordExerciseState() and getCurrentWordIndex()
2. Lesson screen flows: discover -> practice -> words -> onComplete(level, stats)
3. Discover phase: companion introduces letter, key pulses, child presses once to proceed
4. Words phase: 3-4 words displayed with active letter highlighted, typed letter by letter
5. Review flag skips discover and mixes letters/words from recent levels
6. Encouragement shown when showEncouragement=true
7. Stats (correct, errors, errorKeys) accumulated and passed to onComplete