docs(01): create phase 1 plan — 5 plans across 4 waves
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
---
|
||||
phase: 01-grundger-st-tippmechanik
|
||||
plan: 05
|
||||
type: execute
|
||||
wave: 4
|
||||
depends_on: ["01-02", "01-04"]
|
||||
files_modified:
|
||||
- src/ui/screens.ts
|
||||
- src/forest/scene.ts
|
||||
- index.html
|
||||
- src/styles/main.css
|
||||
- src/app.ts
|
||||
autonomous: false
|
||||
requirements:
|
||||
- FRST-04
|
||||
- FRST-05
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Wald-Uebersicht zeigt Stufenkarte mit Status locked/current/completed"
|
||||
- "Weiter-ueben-Button startet die naechste offene Stufe"
|
||||
- "Nach Lesson-Abschluss kehrt die App zur Wald-Uebersicht zurueck und zeigt aktualisierten Status"
|
||||
- "Gesamter Flow funktioniert: Welcome -> Forest -> Lesson -> Forest (mit Fortschritt)"
|
||||
artifacts:
|
||||
- path: "src/forest/scene.ts"
|
||||
provides: "Forest overview with level map"
|
||||
exports: ["initForestScreen", "renderLevelMap"]
|
||||
key_links:
|
||||
- from: "src/forest/scene.ts"
|
||||
to: "src/storage/db.ts"
|
||||
via: "loads progress to determine level statuses"
|
||||
pattern: "getProgress"
|
||||
- from: "src/forest/scene.ts"
|
||||
to: "src/game/levels.ts"
|
||||
via: "reads level definitions for the map"
|
||||
pattern: "import.*levels"
|
||||
- from: "src/forest/scene.ts"
|
||||
to: "src/app.ts"
|
||||
via: "calls startLesson when user clicks a level or continue button"
|
||||
pattern: "startLesson|navigateTo"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the forest overview screen with the level map (Stufenkarte) showing locked/current/completed states, and wire together the complete app flow: welcome -> forest -> lesson -> forest.
|
||||
|
||||
Purpose: This is the final integration plan that connects all pieces into a playable app. The user can see their progress and start lessons from the forest screen.
|
||||
Output: Complete playable Phase 1 app with the core loop working end-to-end.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/phases/01-grundger-st-tippmechanik/01-CONTEXT.md
|
||||
@.planning/phases/01-grundger-st-tippmechanik/01-01-SUMMARY.md
|
||||
@.planning/phases/01-grundger-st-tippmechanik/01-02-SUMMARY.md
|
||||
@.planning/phases/01-grundger-st-tippmechanik/01-03-SUMMARY.md
|
||||
@.planning/phases/01-grundger-st-tippmechanik/01-04-SUMMARY.md
|
||||
@SPEC.md (section 7.1 for screen structure)
|
||||
|
||||
<interfaces>
|
||||
From src/types.ts:
|
||||
```typescript
|
||||
export type LevelStatus = 'locked' | 'current' | 'completed';
|
||||
export type ScreenName = 'welcome' | 'forest' | 'lesson' | 'reward' | 'parent';
|
||||
export interface Progress {
|
||||
id: 1;
|
||||
currentLevel: number;
|
||||
completedLevels: number[];
|
||||
totalSessions: number;
|
||||
sessionDates: string[];
|
||||
selectedCharacter: CompanionType;
|
||||
selectedLayout: KeyboardLayout;
|
||||
}
|
||||
```
|
||||
|
||||
From src/game/levels.ts:
|
||||
```typescript
|
||||
export const levels: Level[];
|
||||
```
|
||||
|
||||
From src/app.ts:
|
||||
```typescript
|
||||
export function showScreen(name: ScreenName): void;
|
||||
export function getDB(): IDBDatabase;
|
||||
```
|
||||
|
||||
From src/storage/db.ts:
|
||||
```typescript
|
||||
export async function getProgress(db: IDBDatabase): Promise<Progress | null>;
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Forest screen with level map and continue button</name>
|
||||
<files>
|
||||
src/forest/scene.ts, index.html, src/styles/main.css
|
||||
</files>
|
||||
<read_first>
|
||||
src/types.ts
|
||||
src/game/levels.ts
|
||||
src/storage/db.ts
|
||||
src/app.ts
|
||||
src/ui/screens.ts
|
||||
index.html
|
||||
src/styles/main.css
|
||||
</read_first>
|
||||
<action>
|
||||
1. Update index.html — populate forest screen:
|
||||
```html
|
||||
<section id="screen-forest" class="screen">
|
||||
<div class="forest">
|
||||
<h1 class="forest__title">Dein Zauberwald</h1>
|
||||
<div class="forest__level-map" id="level-map">
|
||||
<!-- Level cards rendered dynamically -->
|
||||
</div>
|
||||
<button class="forest__continue-btn" id="continue-btn">Weiter ueben</button>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
2. Create src/forest/scene.ts:
|
||||
```typescript
|
||||
import type { Progress, LevelStatus, ScreenName } from '../types';
|
||||
import { levels } from '../game/levels';
|
||||
import { getProgress } from '../storage/db';
|
||||
|
||||
export function getLevelStatus(levelNum: number, progress: Progress): LevelStatus {
|
||||
if (progress.completedLevels.includes(levelNum)) return 'completed';
|
||||
if (levelNum === progress.currentLevel) return 'current';
|
||||
return 'locked';
|
||||
}
|
||||
|
||||
export function renderLevelMap(
|
||||
container: HTMLElement,
|
||||
progress: Progress,
|
||||
onLevelClick: (level: number) => void
|
||||
): void {
|
||||
container.innerHTML = '';
|
||||
|
||||
levels.forEach(level => {
|
||||
const status = getLevelStatus(level.level, progress);
|
||||
const card = document.createElement('button');
|
||||
card.className = `level-card level-card--${status}`;
|
||||
card.dataset.level = String(level.level);
|
||||
|
||||
const statusEmoji = status === 'completed' ? '✓' : status === 'current' ? '→' : '🔒';
|
||||
const keysDisplay = level.newKeys
|
||||
.filter(k => k !== ' ')
|
||||
.map(k => k.toUpperCase())
|
||||
.join(' ');
|
||||
|
||||
card.innerHTML = `
|
||||
<span class="level-card__number">Stufe ${level.level}</span>
|
||||
<span class="level-card__keys">${keysDisplay}${level.newKeys.includes(' ') ? ' + Leertaste' : ''}</span>
|
||||
<span class="level-card__status">${statusEmoji}</span>
|
||||
`;
|
||||
|
||||
// Only current and completed levels are clickable
|
||||
if (status !== 'locked') {
|
||||
card.addEventListener('click', () => onLevelClick(level.level));
|
||||
} else {
|
||||
card.disabled = true;
|
||||
}
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
export async function initForestScreen(
|
||||
db: IDBDatabase,
|
||||
onStartLesson: (level: number) => void
|
||||
): Promise<void> {
|
||||
const mapContainer = document.getElementById('level-map')!;
|
||||
const continueBtn = document.getElementById('continue-btn') as HTMLButtonElement;
|
||||
const progress = await getProgress(db);
|
||||
if (!progress) return;
|
||||
|
||||
renderLevelMap(mapContainer, progress, onStartLesson);
|
||||
|
||||
// Continue button starts the current level
|
||||
continueBtn.onclick = () => onStartLesson(progress.currentLevel);
|
||||
}
|
||||
```
|
||||
|
||||
3. Update src/app.ts to wire the complete flow:
|
||||
- Import `initForestScreen` from `./forest/scene`
|
||||
- Import `initLessonScreen` from `./ui/screens`
|
||||
- In `initApp()`: after checking progress, if user exists, call `initForestScreen(db, startLesson)` before showing forest screen
|
||||
- `startLesson(level)` function: show lesson screen, call initLessonScreen with onComplete callback
|
||||
- `onComplete` callback: update progress (add to completedLevels, increment currentLevel if applicable, increment totalSessions, add today's date), save to DB, re-init forest screen, show forest screen
|
||||
- After welcome screen's "Los geht's" navigates to forest: also call initForestScreen
|
||||
|
||||
The full flow is:
|
||||
- First visit: Welcome -> select companion/layout -> "Los geht's" -> Forest (level 1 = current)
|
||||
- Click "Weiter ueben" or level 1 card -> Lesson screen (type letters)
|
||||
- Complete lesson -> Forest (level 1 = completed, level 2 = current)
|
||||
- Reload -> Forest (skips welcome, shows current state)
|
||||
|
||||
4. Add CSS for forest and level map to src/styles/main.css:
|
||||
```css
|
||||
.forest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
min-height: 100vh;
|
||||
background: var(--bg-forest);
|
||||
}
|
||||
|
||||
.forest__title {
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
font-size: 2rem;
|
||||
color: var(--text-dark);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.forest__level-map {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.level-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
border: 2px solid transparent;
|
||||
font-family: 'Nunito', sans-serif;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.level-card:hover:not(:disabled) {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.level-card--current {
|
||||
border-color: var(--accent-gold);
|
||||
background: #FFF8E0;
|
||||
box-shadow: 0 2px 8px rgba(232, 184, 75, 0.3);
|
||||
}
|
||||
|
||||
.level-card--completed {
|
||||
border-color: var(--accent-green);
|
||||
background: #E8F5E4;
|
||||
}
|
||||
|
||||
.level-card--locked {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.level-card__number {
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.level-card__keys {
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
color: var(--accent-purple);
|
||||
}
|
||||
|
||||
.level-card__status {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.forest__continue-btn {
|
||||
padding: 1rem 3rem;
|
||||
border-radius: 12px;
|
||||
background: var(--accent-green);
|
||||
color: white;
|
||||
border: none;
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.forest__continue-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit && npx vitest run</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- src/forest/scene.ts exports `initForestScreen`, `renderLevelMap`, `getLevelStatus`
|
||||
- src/forest/scene.ts contains `getLevelStatus` returning 'completed', 'current', or 'locked'
|
||||
- src/forest/scene.ts creates 6 level cards with class `level-card`
|
||||
- src/forest/scene.ts contains `level-card--${status}` for dynamic CSS class
|
||||
- index.html contains `id="level-map"` and `id="continue-btn"`
|
||||
- src/app.ts calls `initForestScreen` and `initLessonScreen` in the correct flow
|
||||
- src/app.ts has lesson completion handler that calls `saveProgress` with updated `completedLevels` and `currentLevel`
|
||||
- src/styles/main.css contains `.level-card--current`, `.level-card--completed`, `.level-card--locked`
|
||||
- src/styles/main.css contains `.forest__continue-btn`
|
||||
- `npx tsc --noEmit` exits 0
|
||||
- `npx vitest run` exits 0
|
||||
</acceptance_criteria>
|
||||
<done>Forest screen shows 6-level map with correct statuses, continue button starts current level, level cards are clickable for completed/current levels, complete app flow works end-to-end</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 2: Verify complete Phase 1 app flow</name>
|
||||
<files>no files modified (verification only)</files>
|
||||
<action>
|
||||
Human verifies the complete Phase 1 app flow in the browser. No code changes in this task.
|
||||
|
||||
Open the app at http://vps-ip:5173 and verify these 7 steps:
|
||||
|
||||
1. Welcome screen: 4 companion cards with emojis and DE/CH toggle visible. Select a companion, click "Los geht's".
|
||||
2. Forest overview: Level map shows Stufe 1 as current (gold border), Stufen 2-6 as locked (grayed). "Weiter ueben" button visible.
|
||||
3. Click "Weiter ueben": Lesson screen appears with a large letter (F, J, or space symbol) and QWERTZ keyboard below. Target key pulses on keyboard.
|
||||
4. Type the shown letter: Letter dissolves, next letter appears. Type all 8-12 letters correctly.
|
||||
5. After completion: Returns to forest. Stufe 1 now shows checkmark, Stufe 2 is current (gold).
|
||||
6. Reload the page: Skips welcome, goes directly to forest with preserved state.
|
||||
7. Wrong key test: During a lesson, press a wrong key — nothing happens to the letter, correct key continues pulsing.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit && npx vitest run && echo "ALL AUTOMATED CHECKS PASS"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- All 7 manual verification steps pass in browser
|
||||
- `npx tsc --noEmit` exits 0
|
||||
- `npx vitest run` exits 0
|
||||
</acceptance_criteria>
|
||||
<done>Human confirmed all 7 verification steps pass: welcome flow, forest map, lesson typing, level progression, persistence across reload, correct error handling</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Complete flow: Welcome -> Forest -> Lesson -> Forest works
|
||||
- Level map shows correct statuses after completing a level
|
||||
- Typing exercise handles correct/wrong input per spec
|
||||
- Progress persists across page reloads
|
||||
- Keyboard shows finger colors for learned keys, gray for unlearned
|
||||
- All tests pass: `npx vitest run`
|
||||
- TypeScript compiles: `npx tsc --noEmit`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Forest overview shows 6 levels with locked/current/completed states
|
||||
- "Weiter ueben" button starts the current level's typing exercise
|
||||
- After completing a lesson, level status updates correctly
|
||||
- Full app loop is playable from fresh start through at least level 1 completion
|
||||
- Progress survives browser reload
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-grundger-st-tippmechanik/01-05-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user