385 lines
15 KiB
Markdown
385 lines
15 KiB
Markdown
---
|
||
phase: 01-grundger-st-tippmechanik
|
||
plan: 01
|
||
type: execute
|
||
wave: 1
|
||
depends_on: []
|
||
files_modified:
|
||
- package.json
|
||
- tsconfig.json
|
||
- vite.config.ts
|
||
- biome.json
|
||
- index.html
|
||
- .gitignore
|
||
- src/main.ts
|
||
- src/types.ts
|
||
- src/storage/db.ts
|
||
- src/styles/main.css
|
||
- public/config.example.json
|
||
autonomous: true
|
||
requirements:
|
||
- FNDN-01
|
||
- FNDN-02
|
||
- FNDN-03
|
||
- FNDN-04
|
||
- PLSH-06
|
||
|
||
must_haves:
|
||
truths:
|
||
- "npm run dev startet Vite auf 0.0.0.0:5173 ohne Fehler"
|
||
- "Alle Verzeichnisse aus Spec Abschnitt 13 existieren"
|
||
- "TypeScript-Interfaces sind exportiert und typisiert"
|
||
- "IndexedDB oeffnet mit 5 Stores und schliesst nicht bei reload"
|
||
- "CSS Custom Properties fuer alle Farben und Fonts sind definiert"
|
||
artifacts:
|
||
- path: "package.json"
|
||
provides: "Project dependencies and scripts"
|
||
contains: "vite"
|
||
- path: "src/types.ts"
|
||
provides: "All shared TypeScript interfaces"
|
||
exports: ["Progress", "ForestElement", "CompanionAssets", "Level", "Settings", "StyleReference"]
|
||
- path: "src/storage/db.ts"
|
||
provides: "IndexedDB wrapper with 5 stores"
|
||
exports: ["openDB", "getProgress", "saveProgress", "getSettings", "saveSettings"]
|
||
- path: "src/styles/main.css"
|
||
provides: "CSS palette and typography"
|
||
contains: "--bg-cream"
|
||
key_links:
|
||
- from: "src/storage/db.ts"
|
||
to: "src/types.ts"
|
||
via: "imports Progress, Settings, ForestElement etc."
|
||
pattern: "import.*from.*types"
|
||
- from: "src/main.ts"
|
||
to: "src/storage/db.ts"
|
||
via: "initializes DB on app start"
|
||
pattern: "openDB"
|
||
---
|
||
|
||
<objective>
|
||
Scaffold the complete Vite project, define all TypeScript interfaces, create the IndexedDB wrapper, and establish the CSS design system.
|
||
|
||
Purpose: Every subsequent plan depends on the project existing, types being defined, storage being available, and styles being set.
|
||
Output: A runnable Vite dev server with types, IndexedDB, and CSS custom properties — no visible UI yet beyond a blank styled page.
|
||
</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/01-grundger-st-tippmechanik/01-CONTEXT.md
|
||
@SPEC.md (sections 6.2, 6.3, 7.2, 13)
|
||
</context>
|
||
|
||
<tasks>
|
||
|
||
<task type="auto">
|
||
<name>Task 1: Vite project scaffold + project structure + CSS design system</name>
|
||
<files>
|
||
package.json, tsconfig.json, vite.config.ts, biome.json, index.html,
|
||
.gitignore, src/main.ts, src/styles/main.css, public/config.example.json
|
||
</files>
|
||
<read_first>
|
||
SPEC.md (section 13 for project structure, section 6.2 for tech stack, section 7.2 for CSS palette)
|
||
.planning/phases/01-grundger-st-tippmechanik/01-CONTEXT.md
|
||
</read_first>
|
||
<action>
|
||
1. Initialize Vite project with vanilla-ts template:
|
||
`npm create vite@latest . -- --template vanilla-ts` (or manually create files if directory not empty).
|
||
|
||
2. Install dependencies:
|
||
- `npm install` (vite, typescript)
|
||
- `npm install -D vitest @biomejs/biome`
|
||
|
||
3. Configure vite.config.ts (per D-01, spec section 0):
|
||
```typescript
|
||
import { defineConfig } from 'vite'
|
||
export default defineConfig({
|
||
server: { host: '0.0.0.0', port: 5173 },
|
||
preview: { host: '0.0.0.0', port: 4173 }
|
||
})
|
||
```
|
||
|
||
4. Configure tsconfig.json: `"strict": true`, target ES2020, module ESNext, moduleResolution bundler.
|
||
|
||
5. Configure biome.json: enable formatter and linter, indent with tabs, line width 100.
|
||
|
||
6. Configure package.json scripts:
|
||
```json
|
||
{
|
||
"dev": "vite --host",
|
||
"build": "tsc && vite build",
|
||
"preview": "vite preview --host --port 4173",
|
||
"test": "vitest run",
|
||
"test:watch": "vitest",
|
||
"lint": "biome check .",
|
||
"lint:fix": "biome check --write ."
|
||
}
|
||
```
|
||
|
||
7. Create .gitignore with: node_modules, dist, public/config.json, *.local
|
||
|
||
8. Create ALL directories from spec section 13:
|
||
- src/game/, src/forest/, src/companion/, src/api/, src/storage/, src/ui/, src/styles/
|
||
- src/assets/companions/fee-lila/, src/assets/companions/einhorn-stella/,
|
||
src/assets/companions/fuchs-finn/, src/assets/companions/eule-elsa/
|
||
- src/assets/style-reference/, src/assets/fallback-images/
|
||
- scripts/
|
||
- public/
|
||
Place empty .gitkeep in asset directories that have no files yet.
|
||
|
||
9. Create public/config.example.json:
|
||
```json
|
||
{
|
||
"geminiApiKey": "YOUR_API_KEY_HERE",
|
||
"geminiModel": "gemini-2.5-flash",
|
||
"imageModel": "gemini-3.1-flash-image-preview"
|
||
}
|
||
```
|
||
|
||
10. Create index.html with:
|
||
- DOCTYPE html, lang="de"
|
||
- Google Fonts link: Quicksand (400, 600, 700) + Nunito (400, 600)
|
||
- Link to src/styles/main.css (via Vite)
|
||
- 5 screen sections per D-01: `<section id="screen-welcome" class="screen">`,
|
||
`<section id="screen-forest" class="screen">`,
|
||
`<section id="screen-lesson" class="screen">`,
|
||
`<section id="screen-reward" class="screen">`,
|
||
`<section id="screen-parent" class="screen">`
|
||
- Only screen-welcome gets class `screen--active` initially
|
||
- `<script type="module" src="/src/main.ts"></script>`
|
||
|
||
11. Create src/styles/main.css with ALL CSS custom properties from spec section 7.2:
|
||
```css
|
||
:root {
|
||
--bg-cream: #FFF8F0;
|
||
--bg-forest: #E8F5E4;
|
||
--text-dark: #3D3225;
|
||
--text-warm: #5C4A3A;
|
||
--text-light: #8B7D6B;
|
||
--accent-green: #6DB87D;
|
||
--accent-gold: #E8B84B;
|
||
--accent-pink: #E88BAE;
|
||
--accent-purple: #9B7DC4;
|
||
--accent-blue: #7DB8D4;
|
||
--finger-l-pinky: #E8B4C8;
|
||
--finger-l-ring: #C4A8D4;
|
||
--finger-l-mid: #A8C4E0;
|
||
--finger-l-index: #A8D8B8;
|
||
--finger-r-index: #D4D8A8;
|
||
--finger-r-mid: #E0C4A8;
|
||
--finger-r-ring: #D4A8A8;
|
||
--finger-r-pinky: #C8B4E8;
|
||
--finger-thumb: #D4CFC8;
|
||
}
|
||
```
|
||
Body: font-family 'Nunito', sans-serif; background var(--bg-cream); color var(--text-dark); min font-size 16px; margin 0; padding 0.
|
||
Headings (h1–h3): font-family 'Quicksand', sans-serif.
|
||
Screen base: `.screen { display: none; opacity: 0; transition: opacity 0.3s ease; }` and `.screen--active { display: flex; flex-direction: column; align-items: center; opacity: 1; min-height: 100vh; }`.
|
||
|
||
12. Create src/main.ts as minimal entry: import './styles/main.css', console.log('Zauberwald loaded').
|
||
</action>
|
||
<verify>
|
||
<automated>cd /home/dev/workspace/zauberwald && npm run dev -- --strictPort 2>&1 & sleep 3 && curl -s http://localhost:5173 | grep -q "Zauberwald" || curl -s http://localhost:5173 | grep -q "screen-welcome" && kill %1 && echo "PASS"</automated>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- package.json contains "vite" in devDependencies and scripts "dev", "build", "preview", "test", "lint"
|
||
- vite.config.ts contains `host: '0.0.0.0'` and `port: 5173`
|
||
- tsconfig.json contains `"strict": true`
|
||
- biome.json exists and contains `"formatter"` and `"linter"`
|
||
- .gitignore contains `public/config.json` and `node_modules`
|
||
- index.html contains 5 `<section` tags with ids screen-welcome, screen-forest, screen-lesson, screen-reward, screen-parent
|
||
- index.html contains `class="screen screen--active"` on screen-welcome
|
||
- src/styles/main.css contains `--bg-cream: #FFF8F0` and `--finger-l-pinky: #E8B4C8` and `--finger-thumb: #D4CFC8`
|
||
- src/styles/main.css contains `font-family:` with `Quicksand` and `Nunito`
|
||
- Directories exist: src/game, src/forest, src/companion, src/api, src/storage, src/ui, src/assets/companions
|
||
- public/config.example.json contains `geminiApiKey` and `geminiModel`
|
||
- `npm run dev` starts without errors
|
||
</acceptance_criteria>
|
||
<done>Vite project runs, all directories exist per spec section 13, CSS custom properties match spec section 7.2, index.html has 5 screen sections</done>
|
||
</task>
|
||
|
||
<task type="auto" tdd="true">
|
||
<name>Task 2: TypeScript interfaces + IndexedDB wrapper</name>
|
||
<files>
|
||
src/types.ts, src/storage/db.ts, src/storage/db.test.ts
|
||
</files>
|
||
<read_first>
|
||
SPEC.md (section 6.3 for IndexedDB stores, section 4.1 for level structure, section 5.2 for companion types)
|
||
src/types.ts (after Task 1 creates it — may be empty placeholder)
|
||
</read_first>
|
||
<behavior>
|
||
- Test: openDB() resolves to an IDBDatabase with 5 object store names
|
||
- Test: saveProgress({ currentLevel: 1, ... }) then getProgress() returns same data
|
||
- Test: saveSettings({ audioEnabled: true, ... }) then getSettings() returns same data
|
||
- Test: getProgress() on empty DB returns null (not crash)
|
||
- Test: getSettings() on empty DB returns null (not crash)
|
||
</behavior>
|
||
<action>
|
||
1. Create src/types.ts with ALL interfaces per spec section 6.3 (per FNDN-03):
|
||
|
||
```typescript
|
||
// Companion types
|
||
export type CompanionType = 'fee' | 'einhorn' | 'fuchs' | 'eule';
|
||
export type KeyboardLayout = 'de' | 'ch';
|
||
|
||
// Level status for the forest map
|
||
export type LevelStatus = 'locked' | 'current' | 'completed';
|
||
|
||
// Progress store
|
||
export interface Progress {
|
||
id: 1; // singleton row
|
||
currentLevel: number;
|
||
completedLevels: number[];
|
||
totalSessions: number;
|
||
sessionDates: string[]; // ISO date strings (days only)
|
||
selectedCharacter: CompanionType;
|
||
selectedLayout: KeyboardLayout;
|
||
}
|
||
|
||
// Companion assets store
|
||
export interface CompanionAssets {
|
||
characterType: CompanionType;
|
||
characterSheet: Blob;
|
||
avatarImage: Blob;
|
||
generatedAt: string; // ISO date
|
||
}
|
||
|
||
// Style reference store
|
||
export interface StyleReference {
|
||
id: 1; // singleton
|
||
imageBlob: Blob;
|
||
generatedAt: string;
|
||
}
|
||
|
||
// Forest element store
|
||
export interface ForestElement {
|
||
id?: number; // auto-increment
|
||
levelCompleted: number;
|
||
imageBlob: Blob;
|
||
imagePrompt: string;
|
||
description: string;
|
||
companionText: string;
|
||
position: { x: number; y: number };
|
||
createdAt: string;
|
||
}
|
||
|
||
// Settings store
|
||
export interface Settings {
|
||
id: 1; // singleton
|
||
audioEnabled: boolean;
|
||
apiKey: string;
|
||
}
|
||
|
||
// Level definition (used in game/levels.ts)
|
||
export interface Level {
|
||
level: number;
|
||
newKeys: string[];
|
||
allKeys: string[]; // cumulative keys up to this level
|
||
fingerMap: Record<string, string>; // key -> CSS variable name for finger color
|
||
}
|
||
|
||
// Typing exercise state
|
||
export interface TypingExerciseState {
|
||
currentLetterIndex: number;
|
||
letters: string[];
|
||
intervalMs: number;
|
||
correctStreak: number;
|
||
totalCorrect: number;
|
||
totalErrors: number;
|
||
}
|
||
|
||
// Screen names
|
||
export type ScreenName = 'welcome' | 'forest' | 'lesson' | 'reward' | 'parent';
|
||
```
|
||
|
||
2. Create src/storage/db.ts — IndexedDB wrapper (per D-02, all 5 stores created, only progress + settings fully used):
|
||
|
||
```typescript
|
||
import type { Progress, Settings, ForestElement, CompanionAssets, StyleReference } from '../types';
|
||
|
||
const DB_NAME = 'zauberwald';
|
||
const DB_VERSION = 1;
|
||
|
||
export function openDB(): Promise<IDBDatabase> {
|
||
return new Promise((resolve, reject) => {
|
||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||
request.onupgradeneeded = (event) => {
|
||
const db = (event.target as IDBOpenDBRequest).result;
|
||
if (!db.objectStoreNames.contains('progress')) {
|
||
db.createObjectStore('progress', { keyPath: 'id' });
|
||
}
|
||
if (!db.objectStoreNames.contains('companionAssets')) {
|
||
db.createObjectStore('companionAssets', { keyPath: 'characterType' });
|
||
}
|
||
if (!db.objectStoreNames.contains('styleReference')) {
|
||
db.createObjectStore('styleReference', { keyPath: 'id' });
|
||
}
|
||
if (!db.objectStoreNames.contains('forestElements')) {
|
||
db.createObjectStore('forestElements', { keyPath: 'id', autoIncrement: true });
|
||
}
|
||
if (!db.objectStoreNames.contains('settings')) {
|
||
db.createObjectStore('settings', { keyPath: 'id' });
|
||
}
|
||
};
|
||
request.onsuccess = () => resolve(request.result);
|
||
request.onerror = () => reject(request.error);
|
||
});
|
||
}
|
||
|
||
export async function getProgress(db: IDBDatabase): Promise<Progress | null> { ... }
|
||
export async function saveProgress(db: IDBDatabase, progress: Progress): Promise<void> { ... }
|
||
export async function getSettings(db: IDBDatabase): Promise<Settings | null> { ... }
|
||
export async function saveSettings(db: IDBDatabase, settings: Settings): Promise<void> { ... }
|
||
```
|
||
|
||
Each get/save function wraps IDB transaction in a Promise. Use 'readonly' for gets, 'readwrite' for saves. Store name matches interface.
|
||
|
||
3. Create src/storage/db.test.ts using vitest with fake-indexeddb:
|
||
- `npm install -D fake-indexeddb`
|
||
- Import `import 'fake-indexeddb/auto'` at top of test
|
||
- Test all behaviors listed above
|
||
</action>
|
||
<verify>
|
||
<automated>cd /home/dev/workspace/zauberwald && npx vitest run src/storage/db.test.ts</automated>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- src/types.ts exports: Progress, ForestElement, CompanionAssets, Settings, StyleReference, Level, CompanionType, KeyboardLayout, LevelStatus, ScreenName, TypingExerciseState
|
||
- src/types.ts contains `type CompanionType = 'fee' | 'einhorn' | 'fuchs' | 'eule'`
|
||
- src/types.ts contains `type KeyboardLayout = 'de' | 'ch'`
|
||
- src/storage/db.ts exports: openDB, getProgress, saveProgress, getSettings, saveSettings
|
||
- src/storage/db.ts contains `const DB_NAME = 'zauberwald'`
|
||
- src/storage/db.ts contains all 5 store names: 'progress', 'companionAssets', 'styleReference', 'forestElements', 'settings'
|
||
- src/storage/db.test.ts exists and `npx vitest run src/storage/db.test.ts` exits 0
|
||
- package.json contains `fake-indexeddb` in devDependencies
|
||
</acceptance_criteria>
|
||
<done>All TypeScript interfaces exported, IndexedDB wrapper creates 5 stores, read/write for progress and settings works, all tests pass</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<verification>
|
||
- `npm run dev` starts without errors on port 5173
|
||
- `npx vitest run` passes all DB tests
|
||
- `npx biome check .` shows no errors (or only warnings)
|
||
- All directories from spec section 13 exist
|
||
- index.html has 5 screen sections
|
||
- CSS custom properties match spec section 7.2 exactly
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- Vite dev server accessible at 0.0.0.0:5173
|
||
- TypeScript compiles without errors in strict mode
|
||
- IndexedDB wrapper tested and working with 5 stores
|
||
- CSS design system complete with all palette colors and typography
|
||
- Project structure matches spec section 13
|
||
</success_criteria>
|
||
|
||
<output>
|
||
After completion, create `.planning/phases/01-grundger-st-tippmechanik/01-01-SUMMARY.md`
|
||
</output>
|