Skip to content

Conversation

@ammar-agent
Copy link
Collaborator

@ammar-agent ammar-agent commented Dec 6, 2025

Summary

Performance improvements and UI polish for the Review panel when viewing large diffs.

Manually verified: Much better performance when viewing large diffs (~3000+ lines).

Performance

  • Defer highlighting: Syntax highlighting waits until a hunk is in/near viewport (600px preload margin)
  • Web Worker: Shiki highlighting runs off-main-thread via Comlink to avoid blocking UI
  • LRU Cache: Results cached by SHA-256 hash of (code, language, theme) — re-scrolling and theme toggles are instant
  • Unified highlighting: Both diff hunks and markdown code blocks now share the same worker + cache

UI Flash Fixes

  • Discriminated union for diff state: Makes invalid states unrepresentable — impossible to show "No changes found" while loading
  • Workspace-aware state: Refresh (Ctrl+R) preserves visible hunks; workspace switch shows loading indicator
  • Sidebar tab sync: Read persisted tab state synchronously to prevent width flash on workspace switch

Refactoring

  • Adopted Comlink for worker communication (removes ~50 lines of boilerplate)
  • Deleted shikiHighlighter.ts, consolidated into highlightWorkerClient.ts
  • Moved buildGitDiffCommand to src/common/utils/git/diffParser.ts
  • Extracted storage keys to constants (RIGHT_SIDEBAR_TAB_KEY, etc.)

State Machine

type DiffState =
  | { status: "loading" }
  | { status: "refreshing"; workspaceId: string; hunks: DiffHunk[]; truncationWarning: string | null }
  | { status: "loaded"; workspaceId: string; hunks: DiffHunk[]; truncationWarning: string | null }
  | { status: "error"; message: string };
Scenario Transition UI
Initial load loadingloaded Loading indicator → content
Refresh loadedrefreshingloaded Hunks stay visible
Workspace switch loaded{ws1}loadingloaded{ws2} Loading indicator (no stale data)

Testing

  • make typecheck
  • make static-check
  • bun test src/browser/utils/highlighting/highlightDiffChunk.test.ts — 18 pass

Generated with mux

@chatgpt-codex-connector
Copy link

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@ammar-agent ammar-agent force-pushed the large-diff-review-performance branch from a38e1df to 53eb3e5 Compare December 7, 2025 23:17
@ammar-agent ammar-agent changed the title 🤖 perf: defer highlighting offscreen hunks 🤖 perf: defer + offload hunk highlighting Dec 7, 2025
@ammar-agent ammar-agent force-pushed the large-diff-review-performance branch 8 times, most recently from ed09674 to f27c96a Compare December 8, 2025 00:30
@ammar-agent ammar-agent force-pushed the large-diff-review-performance branch from f27c96a to 56e5a8f Compare December 8, 2025 00:30
- Migrate MarkdownComponents.tsx to use highlightCode() from worker client
- Delete shikiHighlighter.ts, inline fallback into highlightWorkerClient.ts
- Move MAX_DIFF_SIZE_BYTES to highlightDiffChunk.ts (only consumer)
- All highlighting now shares LRU cache and worker (or fallback)
Combine two CRC32 hashes with different seeds to create a 64-bit key,
reducing collision probability for the LRU cache.
Web Crypto API is built-in and hardware-accelerated, no external deps needed.
Replaces manual message ID tracking, pending request maps, and
postMessage/onmessage boilerplate with Comlink's proxy-based RPC.

- Worker exposes API via Comlink.expose()
- Client wraps worker via Comlink.wrap()
- Exceptions propagate naturally
- ~30 lines removed
Reset hasLoadedOnce and hunks when workspace changes to prevent
showing stale 'No changes found' message from previous workspace.
Replace independent state variables (isLoadingHunks, hasLoadedOnce,
error, hunks, truncationWarning) with a single DiffState union:

  type DiffState =
    | { status: 'loading' }
    | { status: 'loaded'; hunks: DiffHunk[]; truncationWarning: string | null }
    | { status: 'error'; message: string }

This makes invalid states unrepresentable - it's now impossible to
show 'No changes found' while loading because the type system
enforces that hunks only exist in the 'loaded' state.
Add workspaceId to DiffState to distinguish between:
- Refreshing same workspace: show stale data while loading (no flash)
- Switching workspaces: show loading indicator (no stale data)

The state machine now correctly handles:
1. Initial load → 'loading' → 'loaded'
2. Refresh (same workspace) → 'refreshing' (keeps hunks visible) → 'loaded'
3. Workspace switch → 'loading' (clears stale hunks) → 'loaded'
@ammar-agent ammar-agent changed the title 🤖 perf: defer + offload hunk highlighting 🤖 perf: improve Review panel performance and fix UI flashes Dec 8, 2025
…itch

Instead of tracking workspaceId in state and checking it everywhere,
use React's key prop to force remount when workspace changes.

This is simpler and more reliable:
- Component unmounts, clearing all state
- New instance mounts with fresh { status: 'loading' }
- No stale data from previous workspace can leak through

Simplifies DiffState by removing workspaceId from the union.
@ammar-agent ammar-agent force-pushed the large-diff-review-performance branch from 68356e6 to 0e3dbf3 Compare December 8, 2025 01:11
Also adds worker name for DevTools visibility.

TODO: For 20k+ line diffs, the main thread bottleneck is likely
DOM rendering (thousands of HunkViewer components), not highlighting.
Consider adding virtualization (@tanstack/react-virtual) to only
render visible hunks.
- Change main-thread fallback to use dynamic import() so shiki isn't
  bundled with the main thread code path
- Add ESLint rule to enforce shiki is only imported in highlightWorker.ts
- Type-only imports are allowed (erased at compile time)

This ensures the 9.7MB @shikijs/langs package is only loaded in the
web worker, not blocking the main thread during app startup.
@ammario ammario merged commit 7e1ef79 into main Dec 8, 2025
19 checks passed
@ammario ammario deleted the large-diff-review-performance branch December 8, 2025 01:33
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.

2 participants