307 lines
12 KiB
Markdown
307 lines
12 KiB
Markdown
---
|
|||
|
|
phase: 05-multi-wald-system
|
||
|
|
plan: 02
|
||
|
|
type: execute
|
||
|
|
wave: 2
|
||
|
|
depends_on:
|
||
|
|
- 05-01
|
||
|
|
files_modified:
|
||
|
|
- src/forest/scene.ts
|
||
|
|
- src/forest/reward.ts
|
||
|
|
- src/app.ts
|
||
|
|
autonomous: false
|
||
|
|
requirements:
|
||
|
|
- MWLD-05
|
||
|
|
- MWLD-06
|
||
|
|
- MWLD-07
|
||
|
|
|
||
|
|
must_haves:
|
||
|
|
truths:
|
||
|
|
- "Kind kann mit Pfeiltasten zwischen Waldseiten navigieren"
|
||
|
|
- "Aktuelle Waldseite wird in Progress gespeichert und nach Reload wiederhergestellt"
|
||
|
|
- "Nach Lesson-Abschluss zeigt der Wald automatisch die Seite mit dem neuen Element"
|
||
|
|
- "Seitenwechsel hat sanfte Fade-Animation"
|
||
|
|
artifacts:
|
||
|
|
- path: "src/forest/scene.ts"
|
||
|
|
provides: "Navigation event handlers, page persistence"
|
||
|
|
contains: "forest-nav-prev"
|
||
|
|
- path: "src/forest/reward.ts"
|
||
|
|
provides: "Correct slot index for paginated forest"
|
||
|
|
contains: "SLOTS_PER_PAGE"
|
||
|
|
- path: "src/app.ts"
|
||
|
|
provides: "Forest init with correct page, post-reward navigation"
|
||
|
|
contains: "currentForestPage"
|
||
|
|
key_links:
|
||
|
|
- from: "src/forest/scene.ts"
|
||
|
|
to: "src/storage/db.ts"
|
||
|
|
via: "saveProgress for page persistence"
|
||
|
|
pattern: "saveProgress"
|
||
|
|
- from: "src/forest/reward.ts"
|
||
|
|
to: "src/forest/scene.ts"
|
||
|
|
via: "SLOTS_PER_PAGE import for slot calculation"
|
||
|
|
pattern: "SLOTS_PER_PAGE"
|
||
|
|
- from: "src/app.ts"
|
||
|
|
to: "src/forest/scene.ts"
|
||
|
|
via: "renderForestScene with page parameter"
|
||
|
|
pattern: "renderForestScene.*db.*page"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
Wire navigation interactions, persist current page, and fix reward flow for paginated forest.
|
||
|
|
|
||
|
|
Purpose: Plan 01 added the data model and rendering. This plan makes it interactive: clicking arrows changes pages, the page persists across sessions, and completing a lesson navigates to the correct page.
|
||
|
|
|
||
|
|
Output: Working multi-forest navigation, persisted page state, correct reward-to-forest flow.
|
||
|
|
</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/ROADMAP.md
|
||
|
|
@.planning/STATE.md
|
||
|
|
@.planning/phases/05-multi-wald-system-wald-pagination-und-navigation-zwischen-w-ldern/05-CONTEXT.md
|
||
|
|
@.planning/phases/05-multi-wald-system-wald-pagination-und-navigation-zwischen-w-ldern/05-01-SUMMARY.md
|
||
|
|
|
||
|
|
<interfaces>
|
||
|
|
<!-- From Plan 01 outputs -->
|
||
|
|
|
||
|
|
From src/types.ts (after Plan 01):
|
||
|
|
```typescript
|
||
|
|
export interface Progress {
|
||
|
|
// ... existing fields ...
|
||
|
|
currentForestPage: number; // 0-indexed, default 0
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
From src/forest/scene.ts (after Plan 01):
|
||
|
|
```typescript
|
||
|
|
export const SLOTS_PER_PAGE = 12;
|
||
|
|
export async function renderForestScene(db: IDBDatabase, page: number, newElementId?: number): Promise<void>;
|
||
|
|
export async function initForestScreen(db: IDBDatabase, onStartLesson: (level: number) => void): Promise<void>;
|
||
|
|
```
|
||
|
|
|
||
|
|
From src/storage/db.ts (unchanged):
|
||
|
|
```typescript
|
||
|
|
export function getProgress(db: IDBDatabase): Promise<Progress | null>;
|
||
|
|
export function saveProgress(db: IDBDatabase, progress: Progress): Promise<void>;
|
||
|
|
export function getForestElements(db: IDBDatabase): Promise<ForestElement[]>;
|
||
|
|
```
|
||
|
|
|
||
|
|
From src/app.ts:
|
||
|
|
```typescript
|
||
|
|
function migrateProgress(progress): void; // adds currentForestPage if missing
|
||
|
|
async function handleLessonComplete(completedLevel, stats): Promise<void>;
|
||
|
|
// calls initRewardScreen which returns elementId
|
||
|
|
// "Zurueck zum Wald" callback: initForestScreen + renderForestScene + showScreen
|
||
|
|
```
|
||
|
|
</interfaces>
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 1: Wire navigation handlers and page persistence in initForestScreen</name>
|
||
|
|
<files>src/forest/scene.ts</files>
|
||
|
|
<read_first>
|
||
|
|
- src/forest/scene.ts (current initForestScreen — must add nav button handlers)
|
||
|
|
- src/storage/db.ts (saveProgress pattern)
|
||
|
|
- src/types.ts (Progress with currentForestPage)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
**src/forest/scene.ts** — Modify `initForestScreen` to:
|
||
|
|
|
||
|
|
1. Import `saveProgress` from `../storage/db` (add to existing import).
|
||
|
|
|
||
|
|
2. Read `progress.currentForestPage` (with fallback to 0) and use it as the initial page for rendering:
|
||
|
|
```typescript
|
||
|
|
const page = progress.currentForestPage ?? 0;
|
||
|
|
await renderForestScene(db, page);
|
||
|
|
```
|
||
|
|
|
||
|
|
3. After the `renderLevelMap` call, wire up navigation buttons per D-05:
|
||
|
|
```typescript
|
||
|
|
const prevBtn = document.getElementById('forest-nav-prev') as HTMLButtonElement;
|
||
|
|
const nextBtn = document.getElementById('forest-nav-next') as HTMLButtonElement;
|
||
|
|
|
||
|
|
let currentPage = progress.currentForestPage ?? 0;
|
||
|
|
|
||
|
|
const navigateToPage = async (newPage: number) => {
|
||
|
|
currentPage = newPage;
|
||
|
|
await renderForestScene(db, currentPage);
|
||
|
|
// Persist page in progress per D-05
|
||
|
|
const freshProgress = await getProgress(db);
|
||
|
|
if (freshProgress) {
|
||
|
|
freshProgress.currentForestPage = currentPage;
|
||
|
|
await saveProgress(db, freshProgress);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (prevBtn) {
|
||
|
|
prevBtn.onclick = () => {
|
||
|
|
if (currentPage > 0) navigateToPage(currentPage - 1);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
if (nextBtn) {
|
||
|
|
nextBtn.onclick = async () => {
|
||
|
|
const elements = await getForestElements(db);
|
||
|
|
const totalPages = Math.max(1, Math.ceil(elements.length / SLOTS_PER_PAGE));
|
||
|
|
if (currentPage < totalPages - 1) navigateToPage(currentPage + 1);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
4. Update all existing calls to `renderForestScene` within this file to pass the page parameter. The `initForestScreen` call becomes `renderForestScene(db, page)` (already done above).
|
||
|
|
|
||
|
|
5. Add a CSS transition for the grid on page change. In `renderForestScene`, before clearing `grid.innerHTML`, add:
|
||
|
|
```typescript
|
||
|
|
grid.style.opacity = '0';
|
||
|
|
grid.style.transition = 'opacity 0.3s ease';
|
||
|
|
```
|
||
|
|
And after the rendering loop completes:
|
||
|
|
```typescript
|
||
|
|
// Trigger fade-in after a frame
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
grid.style.opacity = '1';
|
||
|
|
});
|
||
|
|
```
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- src/forest/scene.ts contains `saveProgress` in import statement
|
||
|
|
- src/forest/scene.ts contains `currentForestPage`
|
||
|
|
- src/forest/scene.ts contains `prevBtn.onclick`
|
||
|
|
- src/forest/scene.ts contains `nextBtn.onclick`
|
||
|
|
- src/forest/scene.ts contains `navigateToPage`
|
||
|
|
- src/forest/scene.ts contains `grid.style.opacity`
|
||
|
|
- TypeScript compiles without errors
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Arrow buttons navigate between forest pages, current page saved to Progress on every navigation, fade animation on page transitions</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 2: Fix reward flow and all renderForestScene call sites for pagination</name>
|
||
|
|
<files>src/forest/reward.ts, src/app.ts</files>
|
||
|
|
<read_first>
|
||
|
|
- src/forest/reward.ts (slotIndex calculation at line 179 — must account for pagination)
|
||
|
|
- src/app.ts (all renderForestScene calls — must pass page parameter; handleLessonComplete "Zurueck zum Wald" callback)
|
||
|
|
- src/forest/scene.ts (new renderForestScene signature with page parameter)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
**src/forest/reward.ts** — Per D-01, fix the slotIndex calculation to work with pagination:
|
||
|
|
|
||
|
|
1. Import `SLOTS_PER_PAGE` from `./scene`:
|
||
|
|
```typescript
|
||
|
|
import { SLOTS_PER_PAGE } from "./scene";
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Replace the slotIndex calculation (currently `const slotIndex = existing.length % 12`):
|
||
|
|
```typescript
|
||
|
|
const slotIndex = existing.length % SLOTS_PER_PAGE;
|
||
|
|
```
|
||
|
|
This ensures the element is placed in the correct grid position on its page.
|
||
|
|
|
||
|
|
**src/app.ts** — Update ALL `renderForestScene` and `initForestScreen` call sites per D-06:
|
||
|
|
|
||
|
|
1. In `handleLessonComplete`, the "Zurueck zum Wald" callback (the `onBackToForest` lambda around line 122-125):
|
||
|
|
```typescript
|
||
|
|
async () => {
|
||
|
|
// Per D-06: navigate to page containing the new element
|
||
|
|
const elements = await getForestElements(db);
|
||
|
|
const newPage = Math.max(0, Math.ceil(elements.length / SLOTS_PER_PAGE) - 1);
|
||
|
|
|
||
|
|
// Update progress with new page
|
||
|
|
const latestProgress = await getProgress(db);
|
||
|
|
if (latestProgress) {
|
||
|
|
latestProgress.currentForestPage = newPage;
|
||
|
|
await saveProgress(db, latestProgress);
|
||
|
|
}
|
||
|
|
|
||
|
|
await initForestScreen(db, startLessonFromForest);
|
||
|
|
await renderForestScene(db, newPage, elementId);
|
||
|
|
showScreen("forest");
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Import `SLOTS_PER_PAGE` from `./forest/scene` and `getForestElements` from `./storage/db` (add to existing imports if not already there).
|
||
|
|
|
||
|
|
3. In the "Weiter ueben" callback from reward (the `onContinue` lambda), no forest rendering happens so no change needed.
|
||
|
|
|
||
|
|
4. In `handleLessonCancel` (line 129-132), `initForestScreen` already reads `currentForestPage` from progress internally, so no change needed here.
|
||
|
|
|
||
|
|
5. In `initApp` returning user flow (line 208), `initForestScreen` already handles page from progress, but the subsequent `renderForestScene` call does not exist separately — `initForestScreen` calls it. No change needed.
|
||
|
|
|
||
|
|
6. In the "Zurueck zum Wald" from parent area (line 182-183), same — `initForestScreen` handles it.
|
||
|
|
|
||
|
|
7. BUT: There is an explicit `renderForestScene(db, elementId)` call on line 123. This must become `renderForestScene(db, newPage, elementId)` as described in step 1 above.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20 && grep -n "SLOTS_PER_PAGE" src/forest/reward.ts src/app.ts</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- src/forest/reward.ts contains `import { SLOTS_PER_PAGE } from "./scene"`
|
||
|
|
- src/forest/reward.ts contains `existing.length % SLOTS_PER_PAGE`
|
||
|
|
- src/app.ts contains `SLOTS_PER_PAGE` import
|
||
|
|
- src/app.ts contains `currentForestPage = newPage`
|
||
|
|
- src/app.ts does NOT contain `renderForestScene(db, elementId)` (old 2-arg call with elementId)
|
||
|
|
- TypeScript compiles without errors
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Reward flow correctly calculates slot position for paginated grid, "Zurueck zum Wald" navigates to the page containing the new element, all renderForestScene calls pass page parameter</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
||
|
|
<name>Task 3: Verify multi-forest navigation works end-to-end</name>
|
||
|
|
<files>n/a</files>
|
||
|
|
<action>
|
||
|
|
Human verification checkpoint for the multi-forest pagination system.
|
||
|
|
|
||
|
|
What was built: Arrow navigation between forest pages, page persistence in Progress, correct page navigation after lesson completion, fade animation on page transitions.
|
||
|
|
|
||
|
|
How to verify:
|
||
|
|
1. Open http://localhost:5173 in browser
|
||
|
|
2. If fresh install: complete onboarding, play through at least 1 lesson to get forest elements
|
||
|
|
3. On the forest screen: if you have fewer than 13 elements, the navigation arrows should be HIDDEN
|
||
|
|
4. To test pagination with many elements: Use browser DevTools console to add fake elements to IndexedDB, OR play through enough lessons to fill 12+ slots
|
||
|
|
5. With 13+ elements: navigation arrows ("Wald 1 von 2") should appear between the forest scene and level map
|
||
|
|
6. Click right arrow — should fade to page 2 showing element 13+
|
||
|
|
7. Click left arrow — should fade back to page 1
|
||
|
|
8. Left arrow should be disabled on page 1, right arrow disabled on last page
|
||
|
|
9. Reload browser — same page should be shown (persistence)
|
||
|
|
10. Complete a lesson — after clicking "Zurueck zum Wald", should show the page with the new element (last page)
|
||
|
|
|
||
|
|
Resume signal: Type "approved" or describe issues.
|
||
|
|
</action>
|
||
|
|
<verify>User confirms visual and functional correctness of multi-forest navigation</verify>
|
||
|
|
<done>User approves that forest pagination, navigation arrows, page persistence, and post-lesson page selection all work correctly</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
- `npx tsc --noEmit` passes
|
||
|
|
- `npm run dev` starts without errors
|
||
|
|
- Navigation arrows hidden when <= 12 elements
|
||
|
|
- Navigation arrows visible when > 12 elements
|
||
|
|
- Page persists in IndexedDB across reloads
|
||
|
|
- New element after lesson appears on correct page
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- Clicking prev/next arrows changes forest page with fade animation
|
||
|
|
- "Wald X von Y" text updates correctly
|
||
|
|
- Arrows disabled at boundaries (prev on page 1, next on last page)
|
||
|
|
- currentForestPage persisted in Progress and restored on reload
|
||
|
|
- After lesson completion, forest shows the page containing the new element
|
||
|
|
- No console errors during normal navigation
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
After completion, create `.planning/phases/05-multi-wald-system-wald-pagination-und-navigation-zwischen-w-ldern/05-02-SUMMARY.md`
|
||
|
|
</output>
|