Files
Zauberwald/.planning/phases/02-gemini-integration-asset-pipeline/02-05-PLAN.md
T

15 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-gemini-integration-asset-pipeline 05 execute 3
02-03
02-04
src/companion/characters.ts
src/ui/screens.ts
src/app.ts
src/styles/main.css
index.html
false
ASST-03
ASST-04
truths artifacts key_links
Welcome screen shows real generated avatar PNG images instead of emoji
If avatar image fails to load, emoji fallback is shown
Companion greeting text appears on forest screen for returning users
Companion avatar is visible in lesson screen companion area
path provides exports
src/companion/characters.ts Extended companion definitions with avatarUrl field
companions
CompanionDefinition
path provides
src/ui/screens.ts Updated welcome screen with img elements
path provides
src/app.ts Greeting text display on forest screen
from to via pattern
src/companion/characters.ts src/assets/companions/*/avatar.png static imports of avatar PNGs for Vite URL resolution import.*avatar.*from
from to via pattern
src/ui/screens.ts src/companion/characters.ts reads avatarUrl from CompanionDefinition for img src avatarUrl
from to via pattern
src/app.ts src/companion/companion.ts calls getGreeting for returning user greeting getGreeting
Wire generated avatars into the welcome screen, add companion greeting to forest screen, and show companion avatar in lesson screen.

Purpose: This is where users see the AI-generated assets for the first time. The welcome screen transforms from emoji placeholders to real character art, and the companion becomes a visible presence. Output: Updated characters.ts, screens.ts, app.ts with avatar images and greeting text.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-gemini-integration-asset-pipeline/02-03-SUMMARY.md @.planning/phases/02-gemini-integration-asset-pipeline/02-04-SUMMARY.md

@src/companion/characters.ts @src/ui/screens.ts @src/app.ts @src/styles/main.css

```typescript export interface CompanionDefinition { type: CompanionType; name: string; emoji: string; personality: string; } export const companions: CompanionDefinition[]; ```
export async function getGreeting(characterType: CompanionType, db: IDBDatabase): Promise<string>;
export async function initApp(): Promise<void>;
export function showScreen(name: ScreenName): void;
Task 1: Add avatar URLs to companion definitions and update welcome screen src/companion/characters.ts, src/ui/screens.ts, src/styles/main.css - src/companion/characters.ts (current CompanionDefinition interface and companions array) - src/ui/screens.ts (initWelcomeScreen — renders companion cards with emoji spans) - src/styles/main.css (existing companion-card styles) - src/assets/companions/fee-lila/avatar.png (verify file exists) **Step 1: Update src/companion/characters.ts** (per D-05)
Add static imports for each avatar PNG (Vite resolves these to hashed URLs):
```typescript
import feeLilaAvatar from "../assets/companions/fee-lila/avatar.png";
import einhornStellaAvatar from "../assets/companions/einhorn-stella/avatar.png";
import fuchsFinnAvatar from "../assets/companions/fuchs-finn/avatar.png";
import euleElsaAvatar from "../assets/companions/eule-elsa/avatar.png";
```

Extend `CompanionDefinition` interface:
```typescript
export interface CompanionDefinition {
  type: CompanionType;
  name: string;
  emoji: string;
  personality: string;
  avatarUrl: string;  // NEW — Vite-resolved URL to avatar PNG
}
```

Add `avatarUrl` to each companion entry:
```typescript
{ type: "fee", name: "Lila", emoji: "🧚", personality: "...", avatarUrl: feeLilaAvatar },
{ type: "einhorn", name: "Stella", emoji: "🦄", personality: "...", avatarUrl: einhornStellaAvatar },
{ type: "fuchs", name: "Finn", emoji: "🦊", personality: "...", avatarUrl: fuchsFinnAvatar },
{ type: "eule", name: "Elsa", emoji: "🦉", personality: "...", avatarUrl: euleElsaAvatar },
```

**Step 2: Update welcome screen in src/ui/screens.ts** (per D-05, D-06, ASST-03)

In `initWelcomeScreen`, change the card innerHTML from emoji to img with onerror fallback:

Replace:
```typescript
card.innerHTML = `
  <span class="companion-card__emoji">${c.emoji}</span>
  <span class="companion-card__name">${c.name}</span>
`;
```

With:
```typescript
card.innerHTML = `
  <img class="companion-card__avatar" src="${c.avatarUrl}" alt="${c.name}"
       onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
  <span class="companion-card__emoji" style="display:none">${c.emoji}</span>
  <span class="companion-card__name">${c.name}</span>
`;
```

Per D-06: If the img fails to load, the onerror handler hides the img and shows the emoji fallback.

**Step 3: Add CSS for avatar images in src/styles/main.css**

Add after existing `.companion-card__emoji` styles:
```css
.companion-card__avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: 0.5rem;
}
```

Also add a companion avatar class for the lesson screen:
```css
.companion-area {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  background: var(--bg-cream);
  border-radius: 1rem;
  margin-bottom: 1rem;
}

.companion-area__avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.companion-area__text {
  font-family: 'Nunito', sans-serif;
  font-size: 1rem;
  color: var(--text-warm);
  line-height: 1.4;
}
```
cd /home/dev/workspace/zauberwald && grep -c "avatarUrl" src/companion/characters.ts && grep -c "companion-card__avatar" src/ui/screens.ts && grep -c "companion-card__avatar" src/styles/main.css - `grep "avatarUrl: string" src/companion/characters.ts` matches (interface extended) - `grep -c "import.*avatar.*from" src/companion/characters.ts` returns 4 (one per companion) - `grep "companion-card__avatar" src/ui/screens.ts` shows img element used in card - `grep "onerror" src/ui/screens.ts` shows fallback handler (per D-06) - `grep "companion-card__avatar" src/styles/main.css` shows avatar styling - `grep "companion-area" src/styles/main.css` shows companion area styling for lesson screen Welcome screen renders avatar PNGs with emoji fallback on error. CompanionDefinition has avatarUrl field. CSS styles avatar images at 80px round in cards, 48px round in companion area. Task 2: Add companion greeting to forest screen and avatar to lesson screen src/app.ts, src/ui/screens.ts, index.html - src/app.ts (initApp flow — returning user goes to forest screen) - src/ui/screens.ts (initLessonScreen — current lesson screen setup) - src/forest/scene.ts (initForestScreen — how forest screen is initialized) - src/companion/companion.ts (getGreeting function from Plan 04) - src/companion/characters.ts (updated with avatarUrl from Task 1) - index.html (current HTML structure for all screens) **Step 1: Add companion greeting area to forest screen** (per D-14, COMP-01)
In `index.html`, inside `screen-forest` div, add a greeting container before the level grid:
```html
<div id="forest-greeting" class="companion-area" style="display:none">
  <img id="forest-greeting-avatar" class="companion-area__avatar" src="" alt="">
  <p id="forest-greeting-text" class="companion-area__text"></p>
</div>
```

In `src/app.ts`, update `initApp()` for returning users:
```typescript
import { getGreeting } from "./companion/companion";
import { companions } from "./companion/characters";
```

After `initForestScreen(db, startLessonFromForest)` for returning users, add:
```typescript
// Show companion greeting (per COMP-01, D-14)
const companion = companions.find(c => c.type === progress.selectedCharacter);
if (companion) {
  const greetingEl = document.getElementById("forest-greeting")!;
  const avatarEl = document.getElementById("forest-greeting-avatar") as HTMLImageElement;
  const textEl = document.getElementById("forest-greeting-text")!;

  avatarEl.src = companion.avatarUrl;
  avatarEl.alt = companion.name;
  greetingEl.style.display = "flex";

  // Fetch greeting async (shows immediately with fallback)
  getGreeting(progress.selectedCharacter, db).then(text => {
    textEl.textContent = text;
  });
}
```

**Step 2: Add companion avatar to lesson screen** (per ASST-04)

In `index.html`, inside `screen-lesson` div, add a companion area above the letter-area:
```html
<div id="lesson-companion" class="companion-area" style="display:none">
  <img id="lesson-companion-avatar" class="companion-area__avatar" src="" alt="">
  <p id="lesson-companion-text" class="companion-area__text"></p>
</div>
```

In `src/ui/screens.ts` `initLessonScreen`, after getting the level parameter, look up the selected companion and show avatar:
```typescript
import { companions } from "../companion/characters";
import { getProgress } from "../storage/db";
```

Add near the beginning of `initLessonScreen`:
```typescript
// Show companion avatar (per ASST-04)
getProgress(_db).then(progress => {
  if (!progress) return;
  const companion = companions.find(c => c.type === progress.selectedCharacter);
  if (!companion) return;
  const area = document.getElementById("lesson-companion")!;
  const avatar = document.getElementById("lesson-companion-avatar") as HTMLImageElement;
  avatar.src = companion.avatarUrl;
  avatar.alt = companion.name;
  area.style.display = "flex";
});
```

Note: Per D-14, companion text does NOT appear during typing exercise in Phase 2. Only the avatar is shown. The "Entdecken" phase with letter introduction text comes in Phase 3.
cd /home/dev/workspace/zauberwald && grep -c "getGreeting" src/app.ts && grep -c "forest-greeting" index.html && grep -c "lesson-companion" index.html - `grep "getGreeting" src/app.ts` shows greeting function is called - `grep "forest-greeting" index.html` shows greeting container in forest screen - `grep "lesson-companion" index.html` shows companion area in lesson screen - `grep "companion.avatarUrl" src/app.ts` shows avatar URL is used - `grep "lesson-companion-avatar" src/ui/screens.ts` shows avatar is set in lesson - `grep 'import.*getGreeting' src/app.ts` shows import from companion module - `grep 'import.*companions.*from.*characters' src/ui/screens.ts` shows characters imported Forest screen shows companion avatar + AI/fallback greeting text for returning users. Lesson screen shows companion avatar (no text per D-14 — Phase 3 adds letter intros). Both degrade gracefully without API. Task 3: Visual verification of Phase 2 integration index.html Human verifies the complete Phase 2 integration visually in the browser. All automated work is complete — this checkpoint confirms the visual and functional result.
What was built:
1. Welcome screen now shows real generated avatar images (not emoji) for companion selection
2. Forest screen shows companion greeting text (AI-generated or fallback)
3. Lesson screen shows companion avatar
4. All 9 generated PNG assets (4 character sheets, 4 avatars, 1 style reference) are in the asset directories
5. Fallback SVG images exist for when API is unavailable
6. Full Gemini API client with retry, rate-limiting, and fallback strategy

How to verify:
1. Run `npm run dev` and open `http://{vps-ip}:5173` in browser
2. On welcome screen: Verify 4 companion cards show real avatar images (not emoji)
3. Select a companion and layout, click "Los geht's"
4. On forest screen: Verify a greeting text appears near the top with the companion avatar
5. Start a lesson: Verify the companion avatar appears in the lesson screen
6. Complete the lesson and return to forest: Greeting should still show
7. Refresh the page: Returning user flow should show greeting on forest screen

Fallback test (optional):
8. Rename public/config.json temporarily to config.json.bak
9. Refresh the app — greeting text should show a static fallback (no crash)
10. Rename it back
cd /home/dev/workspace/zauberwald && npm run build 2>&1 | tail -5 User confirms: avatars display on welcome screen, greeting shows on forest screen, companion avatar visible in lesson. App works with and without API key. - `npm run dev` starts without errors - `npm run build` completes without errors - Welcome screen shows avatar images - Forest screen shows companion greeting - Lesson screen shows companion avatar - App works with and without config.json (fallback path)

<success_criteria>

  • Generated avatars replace emoji in welcome screen cards
  • Emoji fallback works when image fails to load
  • Companion greeting appears on forest screen (AI or fallback)
  • Companion avatar visible during lessons
  • No console errors during normal usage </success_criteria>
After completion, create `.planning/phases/02-gemini-integration-asset-pipeline/02-05-SUMMARY.md`