274 lines
9.7 KiB
Markdown
274 lines
9.7 KiB
Markdown
---
|
|||
|
|
phase: 05-multi-wald-system
|
||
|
|
plan: 01
|
||
|
|
type: execute
|
||
|
|
wave: 1
|
||
|
|
depends_on: []
|
||
|
|
files_modified:
|
||
|
|
- src/types.ts
|
||
|
|
- src/forest/scene.ts
|
||
|
|
- src/storage/db.ts
|
||
|
|
- src/styles/main.css
|
||
|
|
- index.html
|
||
|
|
autonomous: true
|
||
|
|
requirements:
|
||
|
|
- MWLD-01
|
||
|
|
- MWLD-02
|
||
|
|
- MWLD-03
|
||
|
|
- MWLD-04
|
||
|
|
|
||
|
|
must_haves:
|
||
|
|
truths:
|
||
|
|
- "renderForestScene zeigt nur 12 Elemente pro Seite (Elemente 0-11 auf Seite 0, 12-23 auf Seite 1, etc.)"
|
||
|
|
- "Progress enthält currentForestPage Feld das nach Reload erhalten bleibt"
|
||
|
|
- "Navigationspfeile und Seitenanzeige sind im DOM vorhanden"
|
||
|
|
- "Bei nur einer Waldseite sind die Pfeile nicht sichtbar"
|
||
|
|
artifacts:
|
||
|
|
- path: "src/types.ts"
|
||
|
|
provides: "Progress interface with currentForestPage"
|
||
|
|
contains: "currentForestPage"
|
||
|
|
- path: "src/forest/scene.ts"
|
||
|
|
provides: "Paginated forest rendering"
|
||
|
|
contains: "page"
|
||
|
|
- path: "index.html"
|
||
|
|
provides: "Navigation arrows and page indicator DOM elements"
|
||
|
|
contains: "forest-nav"
|
||
|
|
key_links:
|
||
|
|
- from: "src/forest/scene.ts"
|
||
|
|
to: "src/types.ts"
|
||
|
|
via: "Progress.currentForestPage"
|
||
|
|
pattern: "currentForestPage"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
Add pagination data model and paginated rendering to the forest scene.
|
||
|
|
|
||
|
|
Purpose: The 4x3 grid (12 slots) fills up as the child completes levels. With 16+ levels, elements overflow. This plan adds the data model and rendering logic to show elements page-by-page.
|
||
|
|
|
||
|
|
Output: Progress type with currentForestPage, renderForestScene accepting page parameter and slicing elements, navigation DOM structure in HTML, navigation CSS styling.
|
||
|
|
</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
|
||
|
|
|
||
|
|
<interfaces>
|
||
|
|
<!-- Key types and contracts the executor needs. -->
|
||
|
|
|
||
|
|
From src/types.ts:
|
||
|
|
```typescript
|
||
|
|
export interface Progress {
|
||
|
|
id: 1; // singleton row
|
||
|
|
currentLevel: number;
|
||
|
|
completedLevels: number[];
|
||
|
|
totalSessions: number;
|
||
|
|
sessionDates: string[];
|
||
|
|
selectedCharacter: CompanionType;
|
||
|
|
selectedLayout: KeyboardLayout;
|
||
|
|
totalCorrect: number;
|
||
|
|
totalErrors: number;
|
||
|
|
errorKeyCounts: Record<string, number>;
|
||
|
|
lastPlayedLevels: number[];
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
From src/forest/scene.ts:
|
||
|
|
```typescript
|
||
|
|
export async function renderForestScene(db: IDBDatabase, newElementId?: number): Promise<void>;
|
||
|
|
export async function initForestScreen(db: IDBDatabase, onStartLesson: (level: number) => void): Promise<void>;
|
||
|
|
```
|
||
|
|
|
||
|
|
From src/storage/db.ts:
|
||
|
|
```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[]>;
|
||
|
|
```
|
||
|
|
</interfaces>
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 1: Extend Progress type and add paginated renderForestScene</name>
|
||
|
|
<files>src/types.ts, src/forest/scene.ts, src/storage/db.ts</files>
|
||
|
|
<read_first>
|
||
|
|
- src/types.ts (current Progress interface — must add field without breaking existing fields)
|
||
|
|
- src/forest/scene.ts (current renderForestScene — must modify signature and slicing logic)
|
||
|
|
- src/storage/db.ts (getProgress, saveProgress — no changes needed but understand pattern)
|
||
|
|
- src/app.ts (calls migrateProgress — must add migration for new field)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
**src/types.ts** — Add `currentForestPage: number` to the `Progress` interface, after `lastPlayedLevels`:
|
||
|
|
```typescript
|
||
|
|
currentForestPage: number; // 0-indexed, default 0
|
||
|
|
```
|
||
|
|
|
||
|
|
**src/app.ts** — In the `migrateProgress` function, add migration line:
|
||
|
|
```typescript
|
||
|
|
if (progress.currentForestPage === undefined) progress.currentForestPage = 0;
|
||
|
|
```
|
||
|
|
|
||
|
|
**src/forest/scene.ts** — Modify `renderForestScene` signature and implementation per D-01, D-02, D-07, D-08:
|
||
|
|
|
||
|
|
1. Change signature to: `renderForestScene(db: IDBDatabase, page: number, newElementId?: number): Promise<void>`
|
||
|
|
|
||
|
|
2. Replace the current slicing logic:
|
||
|
|
```typescript
|
||
|
|
// OLD: const toRender = elements.slice(0, maxSlots);
|
||
|
|
// NEW per D-01: page index determines slice
|
||
|
|
const SLOTS_PER_PAGE = 12;
|
||
|
|
const start = page * SLOTS_PER_PAGE;
|
||
|
|
const end = start + SLOTS_PER_PAGE;
|
||
|
|
const toRender = elements.slice(start, end);
|
||
|
|
```
|
||
|
|
|
||
|
|
3. Keep the rest of the rendering loop unchanged (object URL management, random offset, newElementId animation).
|
||
|
|
|
||
|
|
4. After rendering elements, update the navigation UI:
|
||
|
|
```typescript
|
||
|
|
const totalPages = Math.max(1, Math.ceil(elements.length / SLOTS_PER_PAGE));
|
||
|
|
const navContainer = document.getElementById('forest-nav');
|
||
|
|
if (navContainer) {
|
||
|
|
const prevBtn = document.getElementById('forest-nav-prev') as HTMLButtonElement;
|
||
|
|
const nextBtn = document.getElementById('forest-nav-next') as HTMLButtonElement;
|
||
|
|
const pageText = document.getElementById('forest-nav-text');
|
||
|
|
|
||
|
|
if (totalPages <= 1) {
|
||
|
|
navContainer.style.display = 'none';
|
||
|
|
} else {
|
||
|
|
navContainer.style.display = 'flex';
|
||
|
|
prevBtn.disabled = page <= 0;
|
||
|
|
nextBtn.disabled = page >= totalPages - 1;
|
||
|
|
if (pageText) pageText.textContent = `Wald ${page + 1} von ${totalPages}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Export the constant `SLOTS_PER_PAGE = 12` so Plan 02 can import it.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -30</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- src/types.ts contains `currentForestPage: number`
|
||
|
|
- src/forest/scene.ts contains `export const SLOTS_PER_PAGE = 12`
|
||
|
|
- src/forest/scene.ts function signature contains `page: number`
|
||
|
|
- src/forest/scene.ts contains `page * SLOTS_PER_PAGE`
|
||
|
|
- src/forest/scene.ts contains `forest-nav`
|
||
|
|
- src/app.ts contains `currentForestPage === undefined`
|
||
|
|
- TypeScript compiles without errors (tsc --noEmit exits 0)
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Progress type extended with currentForestPage, renderForestScene accepts page parameter and slices elements accordingly, navigation UI updated from render function, migrateProgress handles missing field</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 2: Add navigation DOM elements and CSS styling</name>
|
||
|
|
<files>index.html, src/styles/main.css</files>
|
||
|
|
<read_first>
|
||
|
|
- index.html (forest screen section — must add nav elements inside .forest, between scene and level-map)
|
||
|
|
- src/styles/main.css (forest styles — must add nav styles following BEM pattern)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
**index.html** — Inside `<section id="screen-forest">`, between the closing `</div>` of `.forest__scene` (line 63) and the `<div class="forest__level-map">` (line 64), add per D-03, D-04:
|
||
|
|
|
||
|
|
```html
|
||
|
|
<div class="forest__nav" id="forest-nav" style="display:none">
|
||
|
|
<button class="forest__nav-btn forest__nav-btn--prev" id="forest-nav-prev" aria-label="Vorheriger Wald">❮</button>
|
||
|
|
<span class="forest__nav-text" id="forest-nav-text">Wald 1 von 1</span>
|
||
|
|
<button class="forest__nav-btn forest__nav-btn--next" id="forest-nav-next" aria-label="Nächster Wald">❯</button>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
**src/styles/main.css** — Add forest navigation styles after the `.forest__element--new` animation block (after the `@keyframes forest-element-appear` block). Per D-04, buttons must be min 44px and pastel-styled:
|
||
|
|
|
||
|
|
```css
|
||
|
|
/* Forest navigation (multi-wald pagination) */
|
||
|
|
.forest__nav {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
gap: 1.5rem;
|
||
|
|
margin: 1rem 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.forest__nav-btn {
|
||
|
|
min-width: 44px;
|
||
|
|
min-height: 44px;
|
||
|
|
border: 2px solid var(--accent-green);
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--bg-cream);
|
||
|
|
color: var(--accent-green);
|
||
|
|
font-size: 1.4rem;
|
||
|
|
font-family: "Quicksand", sans-serif;
|
||
|
|
font-weight: 700;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: background 0.2s, transform 0.2s;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.forest__nav-btn:hover:not(:disabled) {
|
||
|
|
background: var(--bg-forest);
|
||
|
|
transform: scale(1.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.forest__nav-btn:disabled {
|
||
|
|
opacity: 0.3;
|
||
|
|
cursor: default;
|
||
|
|
}
|
||
|
|
|
||
|
|
.forest__nav-text {
|
||
|
|
font-family: "Quicksand", sans-serif;
|
||
|
|
font-size: 1.1rem;
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--text-warm);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/zauberwald && grep -c "forest-nav" index.html && grep -c "forest__nav" src/styles/main.css</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- index.html contains `id="forest-nav"`
|
||
|
|
- index.html contains `id="forest-nav-prev"`
|
||
|
|
- index.html contains `id="forest-nav-next"`
|
||
|
|
- index.html contains `id="forest-nav-text"`
|
||
|
|
- index.html contains `aria-label="Vorheriger Wald"`
|
||
|
|
- src/styles/main.css contains `.forest__nav-btn`
|
||
|
|
- src/styles/main.css contains `min-width: 44px`
|
||
|
|
- src/styles/main.css contains `min-height: 44px`
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Navigation arrows and page indicator are in the DOM between forest scene and level map, styled with pastel colors and 44px minimum touch targets, hidden by default (shown by renderForestScene when multiple pages exist)</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
- `npx tsc --noEmit` passes (no type errors)
|
||
|
|
- `grep "currentForestPage" src/types.ts` finds the new field
|
||
|
|
- `grep "SLOTS_PER_PAGE" src/forest/scene.ts` finds the exported constant
|
||
|
|
- `grep "forest-nav" index.html` finds navigation elements
|
||
|
|
- `npm run dev` starts without errors
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- Progress type has currentForestPage field
|
||
|
|
- renderForestScene accepts page parameter and renders only 12 elements for that page
|
||
|
|
- Navigation DOM exists with prev/next buttons and page text
|
||
|
|
- Navigation hidden when only 1 page of elements
|
||
|
|
- CSS styling follows existing pastel/BEM conventions with 44px touch targets
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
After completion, create `.planning/phases/05-multi-wald-system-wald-pagination-und-navigation-zwischen-w-ldern/05-01-SUMMARY.md`
|
||
|
|
</output>
|