Repository navigation
perf: diff pipeline causes severe UI lag on desktop — full-context patches + synchronous parsing on main thread #22650
Copy link
Copy link
Closed as not planned
Closed as not planned
Copy link
Description
Description
The diff pipeline (both VCS diff and Session diff) causes severe UI jank on the desktop app. Disabling both systems completely eliminates the lag — confirming the diff subsystem as the root cause.
This affects two independent diff systems:
- VCS diff (
/vcs/diffendpoint) — git working tree / branch diff - Session diff (
SessionSummary) — AI-generated file change diffs per session
Root Cause Analysis
The core design issue is using full-context unified patches as the universal transport format between backend and frontend, which creates triple redundant work:
- Backend generates full-file patches with
formatPatch(structuredPatch(..., { context: Number.MAX_SAFE_INTEGER }))— expensive git ops + string processing - Frontend parses the patch back into before/after content via
parsePatch()insession-diff.ts - Frontend feeds reconstructed content to
@pierre/diffsparseDiffFromFile()which re-diffs it — synchronously on the main thread
Bottleneck Ranking (by perceived lag contribution)
| Rank | Bottleneck | Location | Impact |
|---|---|---|---|
| #1 | SessionReview.items() eagerly runs normalize() → parseDiffFromFile() for ALL files synchronously on main thread |
packages/ui/src/components/session-review.tsx + session-diff.ts |
UI freeze |
| #2 | Backend generates Number.MAX_SAFE_INTEGER context patches — oversized payloads |
packages/opencode/src/project/vcs.ts + snapshot/index.ts |
Amplifies #1 |
| #3 | Whole-array reconcile() on every SSE session.diff event + 7 createMemo per file + getBoundingClientRect() per file per scroll frame |
event-reducer.ts + session-review.tsx |
Sustained frame drops |
| #4 | Snapshot.diffFull locked with semaphore(1), git show fallback concurrency: 2 |
snapshot/index.ts |
Throughput bottleneck |
Suggested Fix Strategy
1. Split diff API into summary / detail
- Summary (list view): return only
{ file, status, additions, deletions }— no patches - Detail (per-file, on expand): return
{ before, after, status }for a single file - Both VCS and Session diff share this contract
2. Lazy normalize in SessionReview
- Stop eagerly calling
normalize()for all files initems() - Only compute
FileDiffMetadatawhen a file isexpanded && visible
3. Move parseDiffFromFile() to web worker
- Current worker pool (size=2) only handles Shiki highlighting
- Diff parsing should also be offloaded from the main thread
4. State granularity + debounce
- Split
session_diffstore intosummaryBySession/detailByKey - Add 150–250ms debounce to SSE diff event handling
- Replace
rAF + getBoundingClientRect()scroll tracking withIntersectionObserver
5. Backend: cache + reduce redundancy
- Cache
Snapshot.diffFullresults by(from, to)snapshot pair (immutable) - Separate read lock from write lock on snapshot semaphore
- Combine the two sequential
git diffcalls (--name-status+--numstat) where possible
6. Remove Number.MAX_SAFE_INTEGER from interactive path
- Warning: cannot simply change to
-U3without updating the client —session-diff.tscurrently depends on full-context patch to reconstruct before/after content - For export/share use cases, generate full patches via a separate background path
Related Issues
- VCS diff endpoint returns HTML instead of JSON causing TypeError in Web UI #19291 — VCS diff endpoint returns HTML instead of JSON with git submodules (amplifies lag via retry loops)
- Diff endlessly reloading in web app #22625 — Diff endlessly reloading in web app (same submodule root cause)
Environment
- OpenCode version: 1.4.x
- OS: macOS (Apple Silicon)
- Client: Desktop app
- Repo size: medium-large with git submodules
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
github-actions commentedon Apr 15, 2026
This issue might be a duplicate of (or closely related to) existing issues. Please check:
structuredPatch/ full-context patch design. Consider consolidating or cross-referencing these so fixes can be coordinated.normalize()/ detail-on-expand suggestions here.sim590 commentedon Apr 19, 2026
Additional findings from a TUI-side investigation (originally #23362):
b7fab49b6— refactor(snapshot): store unified patches in file diffs (refactor(snapshot): store unified patches in file diffs #21244). Parent463318486is the last good commit. Identified viagit bisect(9 steps betweenv1.3.3and HEAD).straceconfirms all threads end up inFUTEX_WAIT, 0% CPU. ESC and all input become unresponsive.SessionSummary.summarize()fire-and-forget call inprompt.ts→computeDiff→snapshot.diffFull()→formatPatch(structuredPatch(...))blocks the event loop.formatPatch(structuredPatch(...))withgit diff --no-ext-diff --no-renamessubprocess eliminates the hang. Non-blocking, faster, equivalent output.github-actions commentedon Jul 3, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.