Pārlūkot izejas kodu

docs: Add canvas-tui markdown rendering investigation

Documents the intermittent numbered list bug including:
- Problem summary and symptoms
- Architecture diagram
- Five tests performed (regex, logic, full flow, file, compiled)
- Key observations about debug string and bullet regex
- Four hypotheses for root cause
- Recommended next steps

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
0xDarkMatter 3 mēneši atpakaļ
vecāks
revīzija
4b8039cf13
1 mainītis faili ar 225 papildinājumiem un 0 dzēšanām
  1. 225 0
      docs/canvas-tui-markdown-investigation.md

+ 225 - 0
docs/canvas-tui-markdown-investigation.md

@@ -0,0 +1,225 @@
+# Canvas TUI - Markdown Rendering Investigation
+
+**Status:** Open
+**Component:** `canvas-tui/src/hooks/useMarkdown.ts`
+**Date:** 2026-01-09
+
+---
+
+## Problem Summary
+
+Numbered lists in the canvas TUI markdown renderer exhibit intermittent rendering failures where:
+
+1. Items are dropped (e.g., item 2 disappears)
+2. Text from adjacent items merges (e.g., "integrationsrequesting")
+3. Original markdown numbering appears instead of our renumbered output (showing "1, 3" instead of "1, 2, 3")
+
+The bug is intermittent - identical code sometimes works, sometimes fails.
+
+---
+
+## Architecture
+
+```
+Content Flow:
+┌─────────────┐    ┌──────────────────┐    ┌─────────────────┐
+│ Raw Markdown│───▸│ useMarkdown hook │───▸│ Rendered ANSI   │
+└─────────────┘    └──────────────────┘    └─────────────────┘
+                           │
+                   ┌───────┴───────┐
+                   ▼               ▼
+           ┌─────────────┐  ┌─────────────┐
+           │ Numbered    │  │ cli-markdown│
+           │ Lists       │  │ (everything │
+           │ (custom)    │  │  else)      │
+           └─────────────┘  └─────────────┘
+```
+
+**Strategy:** Process content in sections. Detect numbered lists with regex, render them ourselves (to avoid cli-markdown bugs), delegate everything else to cli-markdown.
+
+---
+
+## The Bug
+
+### Symptoms
+
+When the bug manifests, the output shows:
+```
+1. Platform Scalability - Addressing the infrastructure bottlenecks...
+3. Developer Experience - Improving our API...integrationsrequesting
+```
+
+Note:
+- Item 2 ("Enterprise Features") is completely missing
+- "requesting" (end of item 2's text) is merged with item 3
+- Numbers are original (1, 3) not renumbered (1, 2, 3)
+
+### What This Tells Us
+
+Our custom numbered list handler renumbers items as 1, 2, 3 based on array index:
+```typescript
+numberedListItems.map((item, i) => `  ${i + 1}. ${renderInline(item)}`)
+```
+
+If output shows original numbers (1, 3), **cli-markdown is rendering the list, not our code.**
+
+This means either:
+1. Our regex isn't detecting the numbered list items
+2. The numbered list is somehow being passed to cli-markdown
+
+---
+
+## Investigation
+
+### Test 1: Regex Validation
+
+Tested the detection regex against actual file content:
+
+```javascript
+const regex = /^(\s*)(\d+)\.\s+(.+)$/;
+lines.forEach((line, i) => {
+  const match = line.match(regex);
+  console.log(`Line ${i+1}: ${match ? 'MATCH' : 'NO MATCH'}`);
+});
+```
+
+**Result:** All 3 numbered list lines MATCH correctly.
+
+### Test 2: Processing Logic in Isolation
+
+Simulated the exact processing loop outside React:
+
+```javascript
+// ... full processing logic ...
+console.log('FLUSH NUMBERED LIST, items:', numberedListItems.length);
+```
+
+**Result:** Shows "FLUSH NUMBERED LIST, items: 3" - all items captured correctly.
+
+### Test 3: Full Flow Simulation
+
+Ran the complete processing including cli-markdown calls:
+
+```
+=== AFTER POST-PROCESS ===
+  1. **Platform Scalability** - Addressing bottlenecks
+  2. **Enterprise Features** - Delivering compliance
+  3. **Developer Experience** - Improving our API
+```
+
+**Result:** Works perfectly in isolation! All 3 items, correctly renumbered.
+
+### Test 4: File Content Verification
+
+Checked actual file for encoding issues:
+```bash
+cat -A email-draft.md  # Shows $ for line endings
+xxd email-draft.md     # Shows hex bytes
+```
+
+**Result:** Clean LF line endings, no hidden characters, no blank lines between numbered items.
+
+### Test 5: Compiled Output Verification
+
+Verified the TypeScript compiled to correct JavaScript:
+```bash
+grep -A5 "flushNumberedList" dist/hooks/useMarkdown.js
+```
+
+**Result:** Compiled code matches source, includes the `\n` prefix fix.
+
+---
+
+## Key Observations
+
+### The Debug String Mystery
+
+The bug behavior changed based on what string we pushed to outputSections:
+
+| Code | Behavior |
+|------|----------|
+| `outputSections.push('[DEBUG: N items]\n' + listOutput)` | **WORKS** |
+| `outputSections.push('\n' + listOutput)` | **FAILS** (intermittent) |
+| `outputSections.push(listOutput)` | **FAILS** |
+
+The only difference is the string content. Having text before the newline somehow stabilizes the behavior.
+
+### The Bullet Regex Correlation
+
+The bug appeared/worsened after adding this regex:
+```javascript
+rendered = rendered.replace(/\n\n(\s*[•\-\*]\s)/g, '\n$1');
+```
+
+This regex should NOT affect numbered lists (matches `•`, `-`, `*` but not digits). Yet removing it seems to help.
+
+---
+
+## Hypotheses
+
+### H1: Section Boundary Race Condition
+
+When outputSections are joined, the string starting with `\n` might interact poorly with cli-markdown's output which may also end with newlines. The `\n{3,}` normalization could be collapsing something critical.
+
+**Evidence:** Adding text before `\n` (debug string) prevents the issue.
+
+### H2: cli-markdown State Bleeding
+
+cli-markdown might have some internal state that persists between calls, causing content from one section to affect another.
+
+**Evidence:** Processing works in isolation but fails in the app.
+
+### H3: React useMemo Timing
+
+The useMemo hook might be returning stale cached values, or the `width` dependency is causing unexpected re-renders that interact with the processing.
+
+**Evidence:** Bug is intermittent despite identical code.
+
+### H4: Regex Replacement Side Effects
+
+The bullet-stripping regex, despite not matching numbered lists, might be causing side effects through JavaScript's regex engine state (lastIndex, etc.).
+
+**Evidence:** Bug correlates with adding/removing this regex.
+
+---
+
+## Current State
+
+The bullet-stripping regex has been removed. Testing needed to confirm if this resolves the numbered list issue.
+
+If confirmed, we need an alternative approach for bullet list spacing that doesn't interfere with numbered lists.
+
+---
+
+## Recommended Next Steps
+
+1. **Confirm current state works** - Test with bullet regex removed
+2. **If working, isolate bullet regex issue:**
+   - Test regex in isolation
+   - Try alternative regex patterns
+   - Consider post-processing bullets separately from numbered lists
+3. **If still broken, add instrumentation:**
+   - Log outputSections array contents
+   - Log numberedListItems at flush time
+   - Compare React render vs isolated execution
+4. **Consider architectural changes:**
+   - Process all lists (bullet + numbered) with custom renderer
+   - Or use a different markdown library entirely
+
+---
+
+## Code Reference
+
+**File:** `canvas-tui/src/hooks/useMarkdown.ts`
+
+Key sections:
+- Lines 57-73: Main processing loop with numbered list detection
+- Lines 45-55: `flushNumberedList()` function
+- Lines 83-84: Post-processing regex (bullet regex removed)
+
+---
+
+## Related Issues
+
+- cli-markdown has known issues with numbered list rendering (drops/merges items)
+- This is why we implemented custom numbered list handling in the first place