Skip to content

perf: diff pipeline causes severe UI lag on desktop — full-context patches + synchronous parsing on main thread #22650

@cpkt9762

Description

@cpkt9762

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/diff endpoint) — 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:

  1. Backend generates full-file patches with formatPatch(structuredPatch(..., { context: Number.MAX_SAFE_INTEGER })) — expensive git ops + string processing
  2. Frontend parses the patch back into before/after content via parsePatch() in session-diff.ts
  3. Frontend feeds reconstructed content to @pierre/diffs parseDiffFromFile() 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 in items()
  • Only compute FileDiffMetadata when a file is expanded && 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_diff store into summaryBySession / detailByKey
  • Add 150–250ms debounce to SSE diff event handling
  • Replace rAF + getBoundingClientRect() scroll tracking with IntersectionObserver

5. Backend: cache + reduce redundancy

  • Cache Snapshot.diffFull results by (from, to) snapshot pair (immutable)
  • Separate read lock from write lock on snapshot semaphore
  • Combine the two sequential git diff calls (--name-status + --numstat) where possible

6. Remove Number.MAX_SAFE_INTEGER from interactive path

  • Warning: cannot simply change to -U3 without updating the client — session-diff.ts currently 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

Environment

  • OpenCode version: 1.4.x
  • OS: macOS (Apple Silicon)
  • Client: Desktop app
  • Repo size: medium-large with git submodules

Metadata

Metadata

Assignees

Labels

perfIndicates a performance issue or need for optimizationwebRelates to opencode on web / desktop

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions