fix(desktop,host-service): stop the Changes pane from hanging on large diffs
Merged
AV
3mo ago+653+653+012345678901234567890123456789-131-131-012345678901234567890123456789
Summary
- Replace per-file diff fetching (one
git.getDifftRPC call per changed file) with a newgit.getDiffBulkendpoint — one request per (category, base) group instead of one per file, with bounded server-side concurrency instead of unbounded parallelgit showspawns. - Memoize parsed diffs per file in the renderer so an unrelated re-render (collapsed state, an annotation) doesn't re-parse every file's diff.
- Fix a transport bug this surfaced during live testing: a bulk request for a large changeset (or a same-tick batch across many workspaces) can produce a GET URL long enough to blow past the host-service server's HTTP header-size limit, failing the CORS preflight before the request is ever seen. This was also intermittently breaking
git.getStatusand other ordinary queries, not just large diffs.
Why / Context
The Changes pane fired one git.getDiff tRPC call per changed file and rebuilt+reparsed the entire item list on every individual query resolution (results stream back independently, so N files meant up to N full rebuilds). For a large PR (hundreds of files) this was effectively O(n²) on the renderer, on top of O(n) host-service round trips that each redundantly recomputed merge-base.
How It Works
git.getDiffBulk(packages/host-service/src/trpc/router/git/git.ts) takespaths: string[]instead of onepath, resolves category refs (merge-base, from/to commit) once per request, and runs the per-filegit show/readFilepairs with bounded concurrency (8 at a time).git.getDiffis untouched for single-file callers; both share the same extractedresolveDiffCategoryRefs/loadFileDiffContenthelpers.useDiffCodeViewItemsgroups changed files by (category, baseBranch/commit) — a pane mixing staged/unstaged/against-base still resolves to only 1-3 groups — and fires one bulk query per group instead of one per file. AFileDiffMetadatacache keyed by the owning group'sdataUpdatedAtskips re-parsing files whose diff hasn't changed.- Fixing the oversized-GET-URL bug required two additive changes:
methodOverride: "POST"on both host-service tRPC client links (WorkspaceClientProviderand the separate device-widehost-service-client.ts, which serves calls likeports.getAll/terminalAgents.listByWorkspaceacross every workspace in the sidebar), andallowMethodOverride: trueserver-side (packages/host-service/src/app.ts) — trpc rejects a POST to a query procedure by default (METHOD_NOT_SUPPORTED) unless the server opts in. Both are additive: GET still works for any caller that doesn't request the override.
Manual QA Checklist
- Large real changeset (327 files, synthetic against-base diff) renders correctly in the Changes pane without hanging
- Real workspaces with 38 and 86 file changesets render full diffs correctly end-to-end
- Confirmed via live CDP: the exact CORS/URL-length failure reproduced before the client fix, and the exact
METHOD_NOT_SUPPORTED("Unsupported POST-request to query procedure") reproduced before the server fix - Confirmed zero CORS errors and all
git.getStatus/workspace.get/etc. batches returning 200 after both fixes - Discard/stage/base-branch-change actions still invalidate the new bulk queries (all 7 existing
getDiff.invalidatecall sites updated to also invalidategetDiffBulk) - Did not verify against a genuinely huge changeset (1000+ files) or a cloud/relay-routed workspace — only local host-service was exercised
Testing
bun run typecheck(host-service, workspace-client, desktop) — cleanbunx biome checkon all touched files — cleanbun test packages/host-service/src/trpc/router/git— 104/104 pass, including a newget-diff-bulk.test.tsthat exercises a real 300-file changeset throughgitRouter.getDiffBulkin one call (correct content, matchesgetDiff's per-file output, completes in seconds)bun test packages/workspace-client— 13/13 pass
Known Limitations
- The transport fix (
methodOverride/allowMethodOverride) changes every desktop↔host-service query, not just diff-related ones — it's additive and confirmed working across several real workspaces, but the blast radius is broader than the original ask. - Ran into an unrelated, pre-existing bug while testing: workspace panes sometimes show stale content from a previously-viewed workspace when switching via the sidebar until a hard reload. Not touched here.
Risks / Rollout
- Risk: the transport change affects every host-service query from the desktop app. Low risk in practice — GET remains valid for any caller that doesn't opt in, and POST-for-queries is trpc's own sanctioned mechanism (
allowMethodOverride), not a custom workaround. - Rollback: revert the
methodOverride/allowMethodOverridecommits independently of the bulk-diff endpoint if needed — they're separate, self-contained commits.
Summary by cubic
Stops the Changes pane from hanging on large diffs by replacing per-file diff fetching with a bulk endpoint and forcing POST for desktop↔host-service queries to avoid URL-size CORS failures. Also hardens diff endpoints against path traversal and upgrades @pierre/diffs to reuse highlighting across remounts.
- Adds
git.getDiffBulkwith shared ref resolution and bounded concurrency inside a worker task (off the host-service event loop); caps one call at 2000 paths; keepsgit.getDifffor single-file callers. - Groups files by (category, base) and issues one bulk query per group; maps results by path; memoizes parsed
FileDiffMetadataper item keyed by a per-file content revision hash; setscacheKeyfor@pierre/diffsto reuse highlight ASTs across remounts. - Rejects absolute/traversal paths in
git.getDiffandgit.getDiffBulk. - Forces POST via
methodOverride: "POST"in both desktop host-service clients and enablesallowMethodOverride: trueserver-side; GET still works for other callers. - Updates all invalidation paths to include
git.getDiffBulk; addsget-diff-bulk.test.tscovering a 300-file changeset and path traversal.
Risks / Rollout
- All desktop queries now use POST; monitor host-service query health. Revert the POST override independently if needed.
- No migration required; existing single-file diff callers are unchanged.
Summary by CodeRabbit
-
New Features
- Added faster bulk loading for file diffs, improving performance when reviewing workspaces with many changed files.
- Large change sets now load more reliably without oversized request URLs.
-
Bug Fixes
- Git status, staging, discard, refresh, and base-branch changes now consistently refresh diff information.
- Improved consistency between bulk and individual file diff results.
- Enhanced diff viewing and highlighting reliability.
- Improved protection against invalid file paths.
-
Tests
- Added coverage for loading and validating diffs across large repositories.