fix(desktop,host-service): stop the Changes pane from hanging on large diffs

Merged
AV
3mo ago
+653-131

Summary

  • Replace per-file diff fetching (one git.getDiff tRPC call per changed file) with a new git.getDiffBulk endpoint — one request per (category, base) group instead of one per file, with bounded server-side concurrency instead of unbounded parallel git show spawns.
  • 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.getStatus and 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) takes paths: string[] instead of one path, resolves category refs (merge-base, from/to commit) once per request, and runs the per-file git show/readFile pairs with bounded concurrency (8 at a time). git.getDiff is untouched for single-file callers; both share the same extracted resolveDiffCategoryRefs/loadFileDiffContent helpers.
  • useDiffCodeViewItems groups 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. A FileDiffMetadata cache keyed by the owning group's dataUpdatedAt skips 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 (WorkspaceClientProvider and the separate device-wide host-service-client.ts, which serves calls like ports.getAll/terminalAgents.listByWorkspace across every workspace in the sidebar), and allowMethodOverride: true server-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.invalidate call sites updated to also invalidate getDiffBulk)
  • 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) — clean
  • bunx biome check on all touched files — clean
  • bun test packages/host-service/src/trpc/router/git — 104/104 pass, including a new get-diff-bulk.test.ts that exercises a real 300-file changeset through gitRouter.getDiffBulk in one call (correct content, matches getDiff'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/allowMethodOverride commits 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.getDiffBulk with shared ref resolution and bounded concurrency inside a worker task (off the host-service event loop); caps one call at 2000 paths; keeps git.getDiff for single-file callers.
  • Groups files by (category, base) and issues one bulk query per group; maps results by path; memoizes parsed FileDiffMetadata per item keyed by a per-file content revision hash; sets cacheKey for @pierre/diffs to reuse highlight ASTs across remounts.
  • Rejects absolute/traversal paths in git.getDiff and git.getDiffBulk.
  • Forces POST via methodOverride: "POST" in both desktop host-service clients and enables allowMethodOverride: true server-side; GET still works for other callers.
  • Updates all invalidation paths to include git.getDiffBulk; adds get-diff-bulk.test.ts covering 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.