From ed7bc116643c38204597096fb77614211ca07267 Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Sun, 29 Mar 2026 21:27:18 +0200 Subject: [PATCH] docs(quick-260329-ton): Markdown in Companion-Texten zu HTML rendern --- .../260329-ton-PLAN.md | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-PLAN.md diff --git a/.planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-PLAN.md b/.planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-PLAN.md new file mode 100644 index 0000000..c4b31d5 --- /dev/null +++ b/.planning/quick/260329-ton-markdown-in-companion-texten-zu-html-ren/260329-ton-PLAN.md @@ -0,0 +1,139 @@ +--- +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` +