Skip to content

fix: 5 message-rendering bugs (frozen tool cards, diff OOM, CRLF, markdown)#132

Merged
dzianisv merged 1 commit into
mainfrom
fix/rendering-bugs
Jul 18, 2026
Merged

fix: 5 message-rendering bugs (frozen tool cards, diff OOM, CRLF, markdown)#132
dzianisv merged 1 commit into
mainfrom
fix/rendering-bugs

Conversation

@dzianisv

Copy link
Copy Markdown
Owner

Five verified correctness bugs in the rendering path — where users see the agent's work. typecheck clean, 199 tests (2 new).

High impact

  1. Tool cards froze during streaming. MessageBubble's memo comparator only compared the last part's .text; tool parts have none, so undefined === undefined made every status change (running→done) and every output/diff arrival a no-op until an unrelated re-render. A user watching a bash/edit tool saw a spinner that never resolved. Now a reference sweep over all parts + the message.
  2. DiffView could OOM/hang the app. computeDiff built an unbounded m×n LCS table and rendered every line unvirtualized — a multi-thousand-line edit (agents do whole-file rewrites) = ANR/crash. Now guarded (size caps + truncated fallback), logic extracted to a tested diff-compute.ts.
  3. CRLF files showed every line as changed. No \r normalization, so a CRLF-vs-LF mismatch made the LCS find zero matches and render the whole file red+green — misrepresenting the agent's edit. Now splits on /\r?\n/.

Medium
4. Markdown used a module-singleton renderer whose slugger never reset → unstable React keys on every streamed token → subtree remounts (code-block scroll position reset, layout thrash). Now a fresh renderer per value.
5. Markdown theme keys (heading1/listItem) didn't match the library's schema (h1/li), so custom heading sizes were dropped and ## Heading rendered as oversized bordered GitHub-style headers inside chat bubbles. Renamed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T12AhSnQVrSxNnvwfCx2z6

- MessageBubble: memo comparator only checked the last part's `.text`,
  which is always undefined for tool parts, so tool-state/token/cost
  updates never re-rendered when a tool part was last. Replace with a
  full reference-equality sweep over message + all parts (the store
  always replaces changed refs, so this catches every real change).
- DiffView: computeDiff's O(a.length*b.length) LCS table was unbounded,
  risking OOM/ANR on large diffs, and the rendered line list was
  unbounded too. Add a size-guarded fallback (simple truncated
  remove/add diff) and cap the normal path's rendered lines, both with
  a truncation marker. Extracted computeDiff into a plain
  diff-compute.ts module (mirrors src/lib/scroll-config.ts) so it's
  unit-testable with node:test, which can't render .tsx components.
- DiffView: normalize line endings (\r?\n) before diffing so a CRLF
  vs LF mismatch doesn't show a whole file as changed.
- Markdown: the module-scope singleton CustomRenderer's github-slugger
  never reset, so useMarkdown's keys climbed on every streamed token,
  remounting the whole subtree. Scope the renderer per `children` via
  useMemo instead.
- Markdown: theme objects used heading1/heading2/heading3/listItem,
  but react-native-marked's MarkedStyles expects h1/h2/h3/li, so the
  custom heading/list styling was silently dropped. Rename the keys in
  both themes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T12AhSnQVrSxNnvwfCx2z6
@dzianisv
dzianisv merged commit 1a069b5 into main Jul 18, 2026
7 checks passed
@dzianisv
dzianisv deleted the fix/rendering-bugs branch July 18, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant