Thanks to visit codestin.com
Credit goes to github.com

Skip to content

perf(toolpath-desktop): memoize markdown rendering per chat turn#46

Merged
eliothedeman merged 1 commit into
mainfrom
eliot/issue-38-memoize-markdown
Apr 22, 2026
Merged

perf(toolpath-desktop): memoize markdown rendering per chat turn#46
eliothedeman merged 1 commit into
mainfrom
eliot/issue-38-memoize-markdown

Conversation

@eliothedeman
Copy link
Copy Markdown
Collaborator

Summary

  • Cache renderMarkdown(text) / renderMarkdown(thinking) output on each ChatTurn at flatten time, so ChatView.svelte drops marked.parse + DOMPurify.sanitize from the {#each turns} hot path.
  • Cache the first-non-empty change[k].raw (path + raw + pre-split lines[]) on the turn so the tool-diff block stops re-running raw.split(\"\n\") on every tick and isExpanded is a property read.
  • Add a module-level Map<id, {text, html}> memo to viz.ts::renderCard so repeat graph renders (selection, branch toggle, show-ts/show-files) don't re-run the markdown pipeline on unchanged card bodies.

In a 1700-step Claude session that's 500+ assistant turns x two expensive calls that used to re-run on every unrelated reactive tick. Svelte 5's $derived doesn't memoize across the loop, so the only way to skip the work is to pre-bake it on the model. ChatTurn is a UI-model type so coupling it to markdown.ts is fine — the issue calls this out explicitly.

Closes #38

Test plan

  • bun run check — 0 errors, 4 pre-existing warnings unrelated to this change
  • bun run build — Vite build green
  • cargo test -p toolpath-desktop — 13 tests pass
  • Manual: open a large Claude session in cargo tauri dev, confirm chat view still renders user text, assistant text, thinking blocks, and tool diffs correctly; confirm tree-query typing no longer feels laggy

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

🔍 Preview deployed: https://d4fbf49c.toolpath.pages.dev

Copy link
Copy Markdown
Collaborator Author

@eliothedeman eliothedeman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Clean, targeted fix for #38. All three asks landed: textHtml/thinkingHtml cached on ChatTurn in tree.ts::flattenChatHead, toolDiff (with pre-split lines[]) cached alongside, and renderCard in viz.ts gets a Map-based memo keyed on id+text.

Notes

  • Sanitization intact: markdown.ts:19 pipes marked.parse through DOMPurify.sanitize, and both flatten-time caches and the viz memo call renderMarkdown — no XSS regression.
  • ChatView.svelte:135,163,168,190,213 all read precomputed fields (t.textHtml, t.thinkingHtml, tool.toolDiff, diff.lines). The local toolDiff() helper is deleted; isExpanded() now reads t.toolDiff directly — consistent.
  • tree.ts:131-152 adds textHtml/thinkingHtml/toolDiff as required fields on ChatTurn (not optional). Defaults are "" / null, which matches existing null-handling in the view. No any introduced.
  • viz.ts:28-36 memo is keyed on id with a stored text for invalidation — correctly re-renders if body text changes for the same step across reloads. Unbounded, but documented and bounded in practice by step count.
  • Scope is tight: no benchmarks (#41), no buildTree caching (#39), no unrelated files. Only the three files called out in the issue.
  • gh pr checks: deploy + test both green.

Verdict

LGTM (commenting since GitHub won't let me approve my own PR). Straightforward perf win that does exactly what the issue asked, preserves sanitization, and stays out of adjacent issues' lanes.

`ChatView.svelte` was calling `renderMarkdown(t.text)` inline inside the
`{#each turns}` block on every reactive tick. In a 1700-step Claude
session that can be 500+ assistant turns x [marked.parse + DOMPurify.
sanitize] per re-render, and Svelte 5's $derived doesn't memoize across
the loop, so typing in the tree filter or toggling view mode retriggered
the full pipeline.

Cache the rendered HTML on the `ChatTurn` itself at flatten time:
  - `textHtml` / `thinkingHtml` precomputed via `renderMarkdown`.
  - `toolDiff` (path + raw + pre-split `lines[]`) precomputed too, so the
    tool-diff block stops re-running `raw.split("\n")` on every tick and
    the expand-default lookup is a property read.

`renderCard` in `viz.ts` gets a module-level `Map<id, {text, html}>`
memo so repeat graph renders (selection / branch toggle / show-ts
changes) skip the markdown pipeline for unchanged bodies.

This couples `ChatTurn` to the markdown module, acceptable given
`ChatTurn` is a UI-model type.

Closes #38
@eliothedeman eliothedeman force-pushed the eliot/issue-38-memoize-markdown branch from a392174 to da7d208 Compare April 22, 2026 21:03
@eliothedeman eliothedeman merged commit 2223f79 into main Apr 22, 2026
2 checks passed
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.

toolpath-desktop: memoize markdown rendering per chat turn

1 participant