--- phase: quick plan: 260329-ton type: execute wave: 1 depends_on: [] files_modified: - src/utils/markdown.ts - src/utils/markdown.test.ts - src/app.ts - src/ui/screens.ts - src/forest/reward.ts autonomous: true requirements: [] must_haves: truths: - "AI-generated companion texts with *italic* render as in the UI" - "AI-generated companion texts with **bold** render as in the UI" - "Plain text without markdown renders unchanged" - "HTML special characters in generated text are escaped (no XSS)" artifacts: - path: "src/utils/markdown.ts" provides: "renderInlineMarkdown() function" exports: ["renderInlineMarkdown"] - path: "src/utils/markdown.test.ts" provides: "Unit tests for markdown rendering" key_links: - from: "src/app.ts" to: "src/utils/markdown.ts" via: "innerHTML = renderInlineMarkdown(text)" pattern: "renderInlineMarkdown" - from: "src/ui/screens.ts" to: "src/utils/markdown.ts" via: "innerHTML = renderInlineMarkdown(introText)" pattern: "renderInlineMarkdown" - from: "src/forest/reward.ts" to: "src/utils/markdown.ts" via: "innerHTML = renderInlineMarkdown(comment)" pattern: "renderInlineMarkdown" --- Render inline Markdown (*italic*, **bold**) in AI-generated companion texts as HTML instead of displaying raw asterisks. Purpose: AI-generated texts (greetings, letter intros, forest comments) contain Markdown formatting like *f* for emphasis, but `.textContent` displays them as plaintext with visible asterisks. Switching to `.innerHTML` with a safe inline markdown renderer fixes this. Output: A `renderInlineMarkdown()` utility function used at all 3 AI-text insertion points. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/STATE.md @src/utils/markdown.ts (will be created) @src/app.ts @src/ui/screens.ts @src/forest/reward.ts Task 1: Create renderInlineMarkdown utility with tests src/utils/markdown.ts, src/utils/markdown.test.ts - renderInlineMarkdown("hello") returns "hello" (plain text unchanged) - renderInlineMarkdown("press *f*") returns "press <em>f</em>" (single asterisks become em) - renderInlineMarkdown("very **important**") returns "very <strong>important</strong>" (double asterisks become strong) - renderInlineMarkdown("**bold** and *italic*") handles both in one string - renderInlineMarkdown("a <script>alert(1)</script>") escapes HTML entities BEFORE markdown processing (XSS safe) - renderInlineMarkdown("2 * 3 * 4") does NOT wrap standalone asterisks with spaces (only wrapping content like *word*) - renderInlineMarkdown("") returns "" (empty string) Create src/utils/markdown.ts exporting a single function renderInlineMarkdown(text: string): string. Implementation approach: 1. First escape HTML special chars: & < > " ' (using string replace chains, no DOM needed) 2. Then apply regex for **bold**: /\*\*(.+?)\*\*/g -> $1 3. Then apply regex for *italic*: /\*(.+?)\*/g -> $1 (Order matters: bold first so ** is not consumed as two single *) No external dependencies. Pure function, no DOM. Create src/utils/markdown.test.ts with vitest tests covering all behaviors above. cd /home/dev/workspace/zauberwald && npx vitest run src/utils/markdown.test.ts All markdown rendering tests pass. Function handles bold, italic, mixed, HTML escaping, and edge cases. Task 2: Replace .textContent with .innerHTML + renderInlineMarkdown at all AI-text insertion points src/app.ts, src/ui/screens.ts, src/forest/reward.ts Import renderInlineMarkdown from "../utils/markdown" (or adjust relative path) in each file and replace .textContent with .innerHTML + renderInlineMarkdown() at these 3 specific locations: 1. src/app.ts line 227: BEFORE: textEl.textContent = text; AFTER: textEl.innerHTML = renderInlineMarkdown(text); 2. src/ui/screens.ts line 422: BEFORE: companionText.textContent = introText; AFTER: companionText.innerHTML = renderInlineMarkdown(introText); 3. src/forest/reward.ts line 207: BEFORE: companionTextEl.textContent = comment; AFTER: companionTextEl.innerHTML = renderInlineMarkdown(comment); IMPORTANT: Do NOT change any other .textContent assignments in these files. The other assignments use hardcoded German strings (no markdown) or set empty strings. Only the 3 lines above receive AI-generated text that may contain markdown. cd /home/dev/workspace/zauberwald && npx vitest run && npx tsc --noEmit All 3 AI-text insertion points use innerHTML + renderInlineMarkdown. TypeScript compiles without errors. Existing tests still pass. - npx vitest run passes (all tests including new markdown tests) - npx tsc --noEmit passes (no type errors) - grep -n "renderInlineMarkdown" src/app.ts src/ui/screens.ts src/forest/reward.ts shows exactly 3 usage sites (plus imports) - AI-generated companion texts with *italic* or **bold** markdown render as styled HTML - Plain text and hardcoded strings are unaffected - HTML in generated text is escaped (no injection risk) - All existing tests continue to pass After completion, create `.planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-SUMMARY.md`