diff --git a/.planning/STATE.md b/.planning/STATE.md index 1605b92..38b2958 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,8 +4,8 @@ milestone: v1.0 milestone_name: milestone status: verifying stopped_at: Completed 04-04-PLAN.md -last_updated: "2026-03-29T16:27:27.702Z" -last_activity: 2026-03-29 - Completed quick task 260329-tk2: Üben-Phase kindgerechte Erklärung +last_updated: "2026-03-29T19:26:12Z" +last_activity: 2026-03-29 - Completed quick task 260329-ton: Markdown in Companion-Texten zu HTML rendern progress: total_phases: 4 completed_phases: 4 @@ -126,9 +126,10 @@ None yet. |---|-------------|------|--------|-----------| | 260329-ten | Leertaste-Symbol in Entdecken-Phase erklären | 2026-03-29 | a7afe06 | [260329-ten-leertaste-symbol-in-entdecken-phase-erkl](./quick/260329-ten-leertaste-symbol-in-entdecken-phase-erkl/) | | 260329-tk2 | Ueben/Woerter-Phase kindgerechte Erklaerung hinzugefuegt | 2026-03-29 | 4cb5fa6 | [260329-tk2-ben-phase-kindgerechte-erkl-rung-statt-n](./quick/260329-tk2-ben-phase-kindgerechte-erkl-rung-statt-n/) | +| 260329-ton | Markdown in Companion-Texten zu HTML rendern | 2026-03-29 | d038cc3 | [260329-ton-markdown-in-companion-texten-zu-html-ren](./quick/260329-ton-markdown-in-companion-texten-zu-html-ren/) | ## Session Continuity -Last session: 2026-03-29T16:23:36.950Z -Stopped at: Completed quick task 260329-tk2 +Last session: 2026-03-29T19:26:12Z +Stopped at: Completed quick task 260329-ton Resume file: None diff --git a/.planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-SUMMARY.md b/.planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-SUMMARY.md new file mode 100644 index 0000000..b75f662 --- /dev/null +++ b/.planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-SUMMARY.md @@ -0,0 +1,85 @@ +--- +phase: quick +plan: 260329-ton +subsystem: utils/companion-ui +tags: [markdown, rendering, xss, companion-texts] +dependency_graph: + requires: [] + provides: [renderInlineMarkdown utility] + affects: [src/app.ts, src/ui/screens.ts, src/forest/reward.ts] +tech_stack: + added: [] + patterns: [inline-markdown-rendering, XSS-safe innerHTML] +key_files: + created: + - src/utils/markdown.ts + - src/utils/markdown.test.ts + modified: + - src/app.ts + - src/ui/screens.ts + - src/forest/reward.ts +decisions: + - Bold must be processed before italic so ** is not consumed as two separate * + - HTML escaping happens before markdown processing (prevents XSS injection) + - Italic regex requires non-space characters at asterisk boundaries to avoid false positives on math-like expressions +metrics: + duration: ~5 minutes + completed: "2026-03-29" + tasks_completed: 2 + files_changed: 5 +--- + +# Quick Task 260329-ton: Markdown in Companion Texts Summary + +**One-liner:** Safe inline markdown renderer (`renderInlineMarkdown`) wired to all 3 AI-generated text insertion points so *italic* and **bold** display as styled HTML instead of raw asterisks. + +## Tasks Completed + +| # | Task | Commit | Files | +|---|------|--------|-------| +| 1 | Create renderInlineMarkdown utility with tests (TDD) | e10bdf8 | src/utils/markdown.ts, src/utils/markdown.test.ts | +| 2 | Replace .textContent with .innerHTML + renderInlineMarkdown at all AI-text insertion points | d038cc3 | src/app.ts, src/ui/screens.ts, src/forest/reward.ts | + +## What Was Built + +A `renderInlineMarkdown(text: string): string` pure utility function that: +1. Escapes HTML special characters (`& < > " '`) first to prevent XSS +2. Converts `**bold**` to `bold` +3. Converts `*italic*` to `italic` +4. Leaves standalone asterisks surrounded by spaces untouched + +Used via `innerHTML = renderInlineMarkdown(text)` at: +- `/src/app.ts` line 228: greeting text from `getGreeting()` +- `/src/ui/screens.ts` line 423: letter intro text from `getLetterIntro()` +- `/src/forest/reward.ts` line 208: forest comment from `getForestComment()` + +## Verification + +- `npx vitest run` — 92 tests pass (7 new markdown tests + 85 existing) +- `npx tsc --noEmit` — no type errors +- `grep renderInlineMarkdown` — exactly 3 usage sites (plus imports) across the 3 target files + +## Decisions Made + +1. **Bold before italic in processing order** — `**` must be matched before `*` so double-asterisk isn't consumed as two single asterisks first. +2. **HTML escape before markdown** — ensures any `<`, `>` in AI-generated text is neutralized before regex transforms it to valid HTML. No injection risk even if Gemini outputs HTML tags. +3. **Italic regex boundary rule** — pattern `/\*(\S[^*]*?\S|\S)\*/g` requires non-whitespace at the asterisk boundary, preventing `2 * 3 * 4` from becoming `2 3 4`. + +## Deviations from Plan + +None — plan executed exactly as written. + +## Known Stubs + +None. + +## Self-Check: PASSED + +Files created: +- /home/dev/workspace/zauberwald/src/utils/markdown.ts — FOUND +- /home/dev/workspace/zauberwald/src/utils/markdown.test.ts — FOUND + +Commits: +- 860d671 (test RED) +- e10bdf8 (feat GREEN) +- d038cc3 (feat wiring)