Skip to content

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

Closed as not planned

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

Activity

  1. added
    perfIndicates a performance issue or need for optimization
    webRelates to opencode on web / desktop
    on Apr 15, 2026
  2. github-actions commented on Apr 15, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of (or closely related to) existing issues. Please check:

  3. sim590 commented on Apr 19, 2026

    @sim590

    Additional findings from a TUI-side investigation (originally #23362):

    • Regression commit: b7fab49b6 — refactor(snapshot): store unified patches in file diffs (refactor(snapshot): store unified patches in file diffs #21244). Parent 463318486 is the last good commit. Identified via git bisect (9 steps between v1.3.3 and HEAD).
    • TUI manifestation: On TUI, this causes a complete freeze (not just lag) — strace confirms all threads end up in FUTEX_WAIT, 0% CPU. ESC and all input become unresponsive.
    • Trigger path: SessionSummary.summarize() fire-and-forget call in prompt.ts → computeDiff → snapshot.diffFull() → formatPatch(structuredPatch(...)) blocks the event loop.
    • Tested fix: Replacing formatPatch(structuredPatch(...)) with git diff --no-ext-diff --no-renames subprocess eliminates the hang. Non-blocking, faster, equivalent output.
  4. removed
    perfIndicates a performance issue or need for optimization
    webRelates to opencode on web / desktop
    on May 3, 2026
  5. github-actions commented on Jul 3, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions