Skip to content

feat(review): large-PR pipeline, instant-open checkout, scroll perf, and worker-pool highlighting#893

Merged
backnotprop merged 14 commits into
mainfrom
feat/optimized-prs
Jun 12, 2026
Merged

feat(review): large-PR pipeline, instant-open checkout, scroll perf, and worker-pool highlighting#893
backnotprop merged 14 commits into
mainfrom
feat/optimized-prs

Conversation

@backnotprop

@backnotprop backnotprop commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Started as two PR-review-mode pain points (large GitHub PRs erroring at load; checkout setup blocking the browser), then grew through stress-testing into a broader review-mode hardening pass: platform-withheld diff content, severe all-files scroll jank (pre-existing on main from #885), worker-pool syntax highlighting, and change-type status display.

1. Large GitHub PRs load via a files-API fallback

gh pr diff is still the primary path. When GitHub refuses to render it (HTTP 406), fetchGhPR pages through pulls/{n}/files --paginate and stitches the per-file patches back into a unified diff, mirroring the existing GitLab raw_diffs/diffs fallback.

  • Works from any directory and for any repo — only needs authenticated gh.
  • Path quoting matches git's exact rules; reconstructed renames/copies carry a similarity index line (Pierre's parser keys rename classification off it — without it pure renames rendered as blank changes). GitLab reconstruction got the same fix.
  • Truncation (3000-file cap) is warned about, never silent. parsePaginatedArray moved to packages/shared/cli-pagination.ts.

2. PR reviews open immediately — checkout warms in the background

The --local worktree/clone is no longer awaited before startReviewServer(). The pool is seeded with a ready:false entry plus the in-flight warmup promise; consumers that need real files (agent jobs, full-stack scope, code-nav, semantic diff, AI sessions) await pool.ensure().

  • Pool creations serialized (shared FETCH_HEAD); warmup children tracked and killed at exit + git worktree prune; pool.cleanup() waits out in-flight creations; honest degradation on warmup failure (no confidently-wrong cwd).
  • Bun.serve no longer kills parked requests at its 10s default idleTimeout; AI SSE streams that stall between bytes survive too.
  • The file-badge hook stopped memoizing failed semantic-diff fetches (retries with backoff), and failed sem runs are cooldown-memoized on BOTH client (60s) and server (30s) — request rate can never drive sem execution rate again (scroll-driven badge remounts used to stampede sem processes).

3. Partial-diff upgrade: local recompute when the platform withholds content

On oversized PRs the platform APIs withhold per-file patch content outright (bun#30412: 1,066 of 2,188 files with zeroed counts and no patch). The background clone has the data; the API doesn't.

  • fetchGhPR/fetchGlMR flag patchIncomplete (GitLab uses its explicit too_large/collapsed per-entry flags when present — verified against gitlab.com; older GitLab falls back to a heuristic).
  • The UI shows a "Partial diff · Load full diff" notice (layer scope); the server recomputes the exact layer diff in the checkout — platform merge-base + head SHA, fetch-by-SHA for shallow clones, -l100000 so rename detection never silently degrades — and swaps it in, preserving the reviewer's file. Non-blocking: the request can park minutes behind a cold clone while the reviewer keeps working with the partial diff.
  • PR scope/switch state writes are epoch-guarded (a parked request can't overwrite a newer scope select or pr-switch); draft keys follow the upgraded patch; recompute failures surface in the response. Pi mirrors everything, including an agentCwd fallback for cross-repo pr-switch.
  • Flag gated on the worktree pool — runs without a local checkout never offer an upgrade they can't perform.

4. All-files scroll jank — eliminated (pre-existing on main, from #885)

Profiled and fixed three compounding causes, validated against Pierre 1.2.8 source:

  • Augmentation applied mid-scroll-gesture: lazy full-content swaps grew item heights during scrolling; above the scroll anchor, CodeView's corrective scrollTo killed wheel momentum ("scrolling but nothing changes"). Applies now wait for 150ms of scroll quiet; fetches still start on visibility.
  • Forced reflow per scroll event: reportVisibleFile read raw DOM layout each event; now uses CodeView's cached accessors, coalesced to once per frame.
  • Missing containment CSS: adopted diffshub's production wrapper tokens (contain:strict, overflow-anchor:none, will-change:scroll-position, item-level overflow-clip).
  • __devOnlyValidateItemHeights (per-item getBoundingClientRect per frame) now requires explicit opt-in.

5. Worker-pool syntax highlighting (diffshub parity)

A Performance trace attributed 2.2s of 2.6s main-thread CPU during a few seconds of scrolling to findNextMatchSync — shiki tokenizing on the main thread. Tokenization now runs in Pierre's worker pool (min(cores−1, 3) workers, AST LRU, preloaded languages, theme pair synced, readiness-gated mount with a 5s plaintext-degrading escape hatch). Single-file build constraint solved with Vite ?worker&inline (base64 blob worker, ES format, dynamic imports collapsed); the shiki-js engine avoids bundling a .wasm asset. Bundle +850KB.

6. Change-type status in headers + file tree

DiffFile carries a derived status from the diff's metadata lines. Headers show added/deleted/renamed icons and render renames as old/path → new/path (diffshub's treatment, including its rename blue); the tree shows A/D/R markers. 'modified' is deliberately undecorated so the others pop. Covers header-only pure renames from the large-PR reconstruction.

7. Review-denied prompt unification

The per-runtime defaults map gave OpenCode/Pi a different review-denied suffix than every other runtime ("you must address all of them" — an instruction to start coding immediately). One default for all runtimes now: "This feedback came from review. Please triage it and verify it against the code and then come back to me with your thoughts on the findings. Do not change any code until we've discussed the findings." Config overrides (prompts.review.runtimes.<rt>.denied) still resolve above it.

Verification

  • 1,434 tests pass (new suites: GitHub/GitLab fallback + patchIncomplete detection, runPRLayerLocalDiff, seeded-warmup pool, sem failure-memoization, diff-status derivation).
  • bun run typecheck green incl. Pi (route parity maintained); review/hook/opencode builds pass.
  • Four adversarial review passes (correctness, concurrency/lifecycle, partial-diff upgrade, self-review) — all confirmed findings fixed. External AI review findings triaged; the valid one (GitLab withheld adds) fixed with API-verified flags.
  • Performance-trace-driven: scroll jank and tokenization diagnoses both confirmed by exported Chrome traces before fixing; manually stress-tested against Rewrite Bun in Rust oven-sh/bun#30412 and local reviews.

Known cosmetic trade-off: the agentCwd field in /api/diff may reference the checkout path a few seconds before it exists on disk (server-side consumers all verify existence).

Two PR-mode improvements:

1. Large GitHub PRs no longer fail to load. When `gh pr diff` is refused
   (HTTP 406 for oversized diffs), fetchGhPR pages through the pulls files
   API and stitches the per-file patches into a unified diff — mirroring
   the existing GitLab raw_diffs fallback. Path quoting matches git's
   exact rules (bare spaces unquoted) so downstream parsers round-trip;
   truncation at the API's 3000-file cap is surfaced, never silent.

2. The --local worktree/clone no longer blocks startup. The review server
   opens as soon as the platform diff arrives; the checkout warms in the
   background as a seeded not-ready pool entry. Consumers that need real
   files (agent jobs, full-stack diff, code-nav, semantic diff, AI
   sessions) await pool.ensure(), with creations serialized so concurrent
   fetches can't clobber the shared FETCH_HEAD. Cross-repo clone steps
   converted from spawnSync to async spawns; warmup children are killed
   on exit (plus `git worktree prune`) so aborted sessions can't leak
   stale registrations; failed checkouts degrade honestly (no agent runs
   in the wrong directory claiming local access) with a 30s retry
   cooldown.
…d renames

Stress-testing against oven-sh/bun#30412 (2,188 files) surfaced three bugs:

- Bun.serve's default 10s idleTimeout killed /api/semantic-diff while it
  parked on the background checkout warmup (a clone that can take minutes).
  Disable the idle timeout on all servers — AI SSE streams can also stall
  >10s between bytes while a permission prompt waits.
- The file-badge hook memoized that failed fetch in a module-level cache
  keyed by patch, pinning every badge to empty until a hard refresh. Never
  cache failures; retry with backoff (5s/15s/30s).
- reconstructGhPatch/reconstructPatch omitted the `similarity index` line,
  which Pierre's parser keys rename classification off — pure renames
  rendered as blank plain changes with no old path. Emit 100% for
  patch-less renames/copies (exactly accurate) and a synthetic 99% for
  patched ones (consumers only branch on 100% vs not).
…ated

On oversized PRs the platform APIs withhold per-file patch content entirely
(bun#30412: 1,066 of 2,188 files came back with status added/modified, zeroed
counts, and no patch). Those files rendered as empty stubs with no diff.

- fetchGhPR/fetchGlMR flag the result `patchIncomplete` when patch-less
  non-rename entries exist or the 3000-file cap truncates the listing.
- New runPRLayerLocalDiff (pr-stack.ts) recomputes the exact layer diff in
  the local checkout: platform merge-base + head SHA two-dot diff (three-dot
  vs baseSha fallback), fetch-by-SHA for objects missing from shallow clones,
  -l0 so rename detection doesn't silently degrade on huge PRs.
- The review UI shows a "Partial diff · Load full diff" notice in layer
  scope; clicking re-requests the layer scope and the server swaps in the
  recomputed full diff (waiting out the background clone if needed).
- PR scope/switch state writes are epoch-guarded: a request parked on the
  checkout warmup can no longer overwrite a newer scope select or pr-switch.
- draftKey follows the upgraded patch so annotation drafts survive pr-switch
  round-trips; recompute failures surface in the response error field.
- Pi server mirrors all of it, including an agentCwd fallback so the upgrade
  works for PRs switched-to under a cross-repo clone pool.
@backnotprop backnotprop changed the title feat(review): large GitHub PR diff fallback + non-blocking PR checkout warmup feat(review): large-PR fallback, non-blocking checkout, and local partial-diff upgrade Jun 12, 2026
… detection

External review caught a false negative: a too-large ADDED file comes back
new_file:true with an empty diff — indistinguishable from a legitimately
empty new file under the old heuristic, so the partial-diff upgrade was
never offered for exactly the files that matter most on big MRs.

The REST /diffs endpoint marks withheld content explicitly per entry
(verified against gitlab.com): too_large/collapsed are now authoritative in
both directions — withheld adds/deletes are flagged, binaries and empty
files are never misflagged. Older GitLab without the fields keeps the
empty-diff-on-modification heuristic.
…ff raw feedback

The per-runtime defaults map (#627) gave OpenCode and Pi a different
review-denied suffix than every other runtime; updating one meant the
others silently kept "you must address all of them" — an instruction to
start coding immediately. Claude Code, Amp, Droid, Codex, Copilot, Gemini,
and Kiro were all still on it.

One default for every runtime now: triage the feedback, verify it against
the code, discuss before changing anything. Per-runtime customization
remains available via config (prompts.review.runtimes.<rt>.denied), which
resolves above the built-in default as before.
…xternal AI reviewers'

Review feedback isn't always from AI reviewers or agent jobs; often it's
the human reviewer's own annotations. Neutral wording covers both.
Self-review findings:

- The partial-diff upgrade reused the scope-switch handler, so clicking
  "Load full diff" raised the full-screen PRSwitchOverlay — blocking the
  entire UI, potentially for minutes behind a cold clone, with no text and
  no cancel. The upgrade now has its own loading state: the notice shows a
  spinner ("Loading full diff…") and the reviewer keeps working with the
  partial diff while the request parks. Server-side epoch guards already
  handle scope/PR changes made during the wait.
- GitLab too_large/collapsed: treat explicit null like absent (flags
  inconclusive → legacy heuristic decides) instead of silently exonerating.
- Rename-limit lift uses -l100000 instead of -l0 ("0 = unlimited" only
  holds on git >= 2.29; on older git it could disable detection outright).
…iling

The badge retry change (a2d19a4) cleared the client-side sem cache on
failure so transient errors could recover. But file-header badges mount and
unmount on every scroll in the virtualized all-files view, and each mount
re-requests /api/semantic-diff — and the server only cached SUCCESSFUL runs.
With sem erroring, scrolling spawned a continuous stream of sem processes,
pegging the CPU and making scrolling severely choppy.

Bound retry rate by time, not by mount events:
- client: keep the failed result memoized and expire it after a 60s
  cooldown instead of clearing immediately
- server (Bun + Pi): memoize failed sem runs for 30s in
  SemanticDiffResponseCache — request rate can no longer drive execution
  rate
…rom #885)

The CodeView migration introduced severe scroll chop; scrolling UP could
freeze the viewport entirely ("scrolling but nothing changes"). Three
compounding causes, diagnosed against Pierre 1.2.8 source:

1. Lazy full-content augmentation landed updateItem() mid-scroll-gesture:
   the full-content parse counts collapsed-context regions the raw-patch
   parse doesn't, so the item GROWS — re-render + re-tokenize hitches both
   directions, and when the grown item sat above CodeView's scroll anchor,
   its corrective scrollTo() killed wheel momentum (the up-scroll freeze).
   Fetches still start as items enter the window; the item mutation now
   waits for 150ms of scroll quiet (staleness re-checked at apply time).

2. reportVisibleFile read container.scrollTop/clientHeight/scrollHeight on
   EVERY scroll event — a forced synchronous layout right after each
   frame's DOM writes. Replaced with CodeView's cached accessors and
   coalesced the handler to once per animation frame.

3. Missing containment CSS: Pierre's own production wrapper uses
   contain:strict + will-change:scroll-position so forced layouts stay
   scoped to the scroller instead of the whole document. Adopted.

Also: __devOnlyValidateItemHeights now requires explicit opt-in
(VITE_PIERRE_VALIDATE_HEIGHTS=1) — it runs getBoundingClientRect() per
rendered item per frame and made dev-server scrolling choppy by itself.
Adopts two diffshub practices identified in the architecture comparison:

- DiffFile now carries a derived status (added/deleted/renamed/modified)
  from the chunk's git metadata lines. FileHeader shows a status icon and
  renders renames as "old/path → new/path" (dimmed old, arrow — diffshub's
  treatment, including its rename blue); the file tree shows A/D/R markers.
  'modified' is deliberately undecorated so the others pop. Works in both
  the all-files surface and the single-file panel, including header-only
  pure renames from the large-PR reconstruction.

- CodeView container gains diffshub's remaining perf CSS: overflow-anchor:
  none (native scroll anchoring fights CodeView's own anchor resolution
  whenever item heights change — exactly our augmentation applies),
  overflow-x-clip, and overflow-clip containment on item elements.
A performance trace of scrolling a small local diff attributed 2.2s of
2.6s main-thread CPU to findNextMatchSync — shiki's TextMate regex
scanner tokenizing on the main thread. diffshub avoids this entirely by
running tokenization in Pierre's worker pool; we never opted in.

Wires WorkerPoolContextProvider around the review app (pool size
min(cores-1, 3), 100-entry AST LRU, common languages preloaded), gates
the all-files surface on pool readiness with a 5s escape hatch (a dead
pool degrades to plaintext-then-highlight, never a blank view), and
syncs the UI theme pair into the long-lived pool.

Single-file build constraint solved with Vite's ?worker&inline (base64
blob worker) + worker.format 'es' with inlineDynamicImports — the
worker's lazy import("shiki/wasm") branch collapses into the bundle and
is never taken (shiki-js engine: the win is moving work off the main
thread, with no .wasm asset to smuggle into one HTML file). Bundle
+850KB.
…ions

A failed round-trip recorded the theme as synced and never retried,
pinning the pool to the wrong palette for the session.
@backnotprop backnotprop changed the title feat(review): large-PR fallback, non-blocking checkout, and local partial-diff upgrade feat(review): large-PR pipeline, instant-open checkout, scroll perf, and worker-pool highlighting Jun 12, 2026
…ssing checkout

Dogfood review of this PR (via plannotator itself) caught two valid issues:

- prPatchIncomplete was gated on the worktree pool, so a --no-local session
  showed a truncated diff with no indication at all. Partiality is
  information; upgradability is a capability. The flag is now always
  reported, with a separate prPatchUpgradeAvailable — the UI shows the
  amber notice either way, with the "Load full diff" button only when a
  checkout can exist (otherwise a "re-run with --local" hint).

- After a FAILED checkout warmup, Ask AI sessions and agent jobs fell back
  to process.cwd() (or a wrong revision on Pi) — running in the wrong tree
  instead of failing. Both launch points now refuse with a clear "Local
  PR checkout unavailable — retry shortly" error (503); the job handlers
  surface buildCommand refusals instead of mislabeling them "Invalid
  JSON". Bun and Pi mirrored.

A third finding (sem availability stuck after warmup) was triaged invalid:
the availability probe detects the sem binary, which is cwd-independent.
--local is a CLI remedy; OpenCode sessions have no such flag. Visible
text states the fact, the tooltip carries the CLI guidance.
@backnotprop backnotprop merged commit 6ec1a66 into main Jun 12, 2026
13 checks passed
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.

1 participant