mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
feat/opencode2-native-commands
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8e7b5ce300 |
feat(review): refine Call Flow navigation and annotations (#1277)
* feat(review): refine Call Flow navigation and annotations * fix(review): align viewed controls with panel navigation * feat(review): add Call Flow path search controls * fix(ui): wrap long tooltip identifiers * feat(review): annotate raw Call Flow output * feat(review): refine call flow lens context * fix(review): keep call flow lens search accessible * fix(review): scope call flow find shortcuts |
||
|
|
463f6ed57c |
fix(ui): fall back to legacy copy in insecure browser contexts (#1174)
* fix(ui): fall back to legacy copy in insecure browser contexts navigator.clipboard only exists in secure contexts. Remote mode serves plain HTTP on a non-localhost host, so every bare navigator.clipboard.writeText call threw TypeError and copy buttons silently broke. Add copyTextToClipboard(text): Promise<boolean> to packages/ui/utils/clipboard.ts: it tries the async Clipboard API (guarded against synchronous throws), falls back to the existing copy-event plus execCommand path, reports success as a boolean, and never throws. copyTextWithFallback now returns whether the copy happened and accepts an optional focusOwner; copyTextPreservingFocus keeps its exported signature and behavior unchanged. Route all bare call sites through the helper, preserving each site's UX: Copied states only flip on success, error toasts and console errors remain for the failure case, fire-and-forget sites stay fire-and-forget. GoalSetupSurface gains the fallback and keeps its error surface for the all-strategies-failed case. Add DOM-gated unit tests for the helper and register them in CI. Closes #1173 * fix(ui): address clipboard fallback review findings Review follow-ups for the insecure-context clipboard fallback: 1. Tag the fallback textarea with data-clipboard-fallback and whitelist it in PopoutDialog's ANNOTATION_SELECTORS so the transient focus shift during a fallback copy no longer closes popout dialogs (TablePopout copy buttons, CodeFilePopout copy contents). 2. Widen AnnotationPanel's onQuickCopy prop to Promise<void | boolean>. A false resolution now suppresses the Copied flash; void resolution stays success so existing hosts keep today's behavior. The editor quick-copy site returns the helper's boolean. 3. In copyTextWithFallback, only flag the copy-event path as success when clipboardData was present and setData actually ran, and only when execCommand also reported success. A null clipboardData no longer calls preventDefault, so the textarea retry still runs. 4. Add tests pinning that the fallback runs synchronously when navigator.clipboard is absent (execCommand fires before the call returns, keeping it inside the user-gesture window), that a copy event without clipboardData is not treated as success, and that the fallback textarea carries the PopoutDialog focus-out marker and is removed after the copy resolves. 5. GoalSetupSurface surfaces the real writeText rejection message when the Clipboard API exists but fails and the fallback also fails; the generic unavailable message is reserved for the API-absent case. |
||
|
|
58f87500f1 |
Migrate packages/review-editor to Base UI (#1014)
* refactor(review-editor): migrate tour QA checkbox to Base UI First component of the Radix→Base UI migration (internal package). @base-ui/react added alongside Radix; Radix deps removed in the final commit. Root typecheck, bun test (1955 pass), build:review all green. Note: bun.lock also absorbs pre-existing manifest drift (the committed lock was stale relative to several package.jsons at HEAD — any install regenerates it identically). * chore(review-editor): give the package its own typecheck gate The package had no tsconfig and is absent from the root typecheck chain (it is typechecked only through apps/review, whose own tsc run is broken for unrelated reasons: it never declares @types/react). Adds a package tsconfig modeled on apps/review's compilerOptions plus @types/react(-dom) devDeps so migration transforms are type-verified. 53 pre-existing errors surfaced (import.meta.env, response-type mismatches) — recorded, untouched; gate for the migration is zero errors in migrated files. * refactor(review-editor): migrate all 7 popover files to Base UI Portal>Content becomes Portal>Positioner>Popup (positioning props and z-index on Positioner), asChild triggers become render props, state classes move to Base UI presence attributes (data-popup-open on triggers), --radix-* vars become --transform-origin, and keyframe enter animations restate as data-starting/ending-style transitions. Focus interception (onOpenAutoFocus) maps to Popup initialFocus, including WorktreePicker's conditional search-input focus. Flagged, not patched: collision defaults differ slightly at viewport edges; exit fade now animates where Radix unmounted instantly. See .migration/popover.md. Tests (1955) and build:review green; zero tsc errors in migrated files. * refactor(review-editor): migrate DiffTypePicker to Base UI Menu Radix DropdownMenu is renamed Menu in Base UI; same restructure as the popovers (Positioner/Popup split, render triggers, --anchor-width for trigger-width matching). Item onSelect becomes onClick. Flagged: focus loop now wraps by default (Base UI default). See .migration/dropdown-menu.md. * refactor(review-editor): migrate FileTreeNode context menu to Base UI Same menu anatomy via @base-ui/react/context-menu (which re-exports Menu's Positioner/Popup/Item parts). Row button keeps left-click select / double-click open; items switch onSelect→onClick. See .migration/context-menu.md for the hand-verify checklist — this is the file tree's core interaction. * refactor(review-editor): drop all Radix deps, finish Base UI migration Removes the 7 @radix-ui/* dependencies — three of them (collapsible, dialog, tooltip) were never imported anywhere and go without a code change. Also fixes two keyboard-nav guards in FileTree/SectionsPanel that matched Radix's [data-radix-popper-content-wrapper] portal attribute (Base UI popups carry ARIA roles directly, so the role selectors already cover every floating surface — verified against the shipped popup implementations). Zero radix references remain in the package (deps, imports, class strings, CSS vars, DOM selectors). Root typecheck, 1955 tests, and build:review all green. packages/ui untouched. Full summary in .migration/project.md. * fix(review-editor): initialFocus fallback semantics in pickers Self-review catch: Base UI's initialFocus function form treats a returned undefined as 'do nothing' (focus stays on the trigger) while null falls back to the default focus. The '?? undefined' I introduced would strand focus on the trigger for short worktree lists — the exact arrow-keys-leak-to-file-tree bug the original Radix code's comment warned about. Returning searchRef.current directly (element or null) matches the original semantics. Also corrects the popover report and adds a Tooltip-prop coordination note for the parallel ui migration. * docs(review-editor): record automated QA results in migration reports Playwright pass against the demo review session: context menu (full flow incl. real clipboard), tour checkboxes (all three toggle paths, incl. the span+hidden-input label association), diff-options popover, and DOM invariants (roles, data-popup-open, no data-state on migrated primitives) all pass. Demo mode never populates gitContext, so the diff-type dropdown and worktree/base pickers don't mount there — their checks remain on the manual list against a real session. * docs(review-editor): annotate QA checklist items untestable from the standalone entrypoint * chore: regenerate bun.lock after rebase onto Base UI ui migration |
||
|
|
795f381ebe |
feat(review): "All changes" git-status review view (#990)
* feat(review): "Since main" git-status review view + correctness fixes
Adds a composite `since-base` diff — merge-base(origin/main, HEAD) vs the
working tree, plus untracked — as the default code-review view, rendered as a
three-section "git status" panel (Committed / Changes / Untracked) with a
Sections|Tree toggle, a first-run setup chooser, and a "baseline behind GitHub"
fetch banner. Everything normalizes to one git patch, so the diff viewer,
annotations, and feedback path are unchanged.
New diff type wired through runGitDiff / fingerprint / file-content / context /
staging / agent-context in packages/shared + both server runtimes (Bun +
Pi mirror). Default flipped from `unstaged` to `since-base`; the old
DiffTypeSetupDialog is replaced by ReviewSetupDialog (view + default-diff
chooser, screenshots, Settings access).
Includes a reviewed batch of correctness fixes:
- P0: baseBehindRemote was permanently true (rev-parse missing --verify)
- fingerprint blind to quoted/unicode untracked paths (unquote)
- sections parser: record both sides of a rename; staged-delete wins over
untracked (rm --cached collision)
- graceful degrade when merge-base can't resolve (trunk/no-remote repos)
- /api/fetch-base re-queries remote (narrow-refspec honesty)
- /api/diff/switch concurrency guard (diffSwitchEpoch) + draft rekey
- decouple remote-staleness probe from initialBase (Pi parity)
- keyboard file nav in the sections view; banner gated to base-relative modes
See adr/decisions/005-since-base-github-view-default-20260701-223706.md
* fix(review): since-base review-round hardening (11 fixes, both runtimes)
Addresses the multi-agent + PR-990 review findings:
- Sections rename parser split ` -> ` on the raw quoted porcelain token, so a
filename containing ` -> ` tore into garbage and a dirty file could render as
"Committed". Now quote-aware (splitPorcelainRename + unit tests).
- /api/diff/switch captured the epoch AFTER await req.json(), letting a
slow-body older switch overwrite a newer confirmed one. Epoch now captured
before any await; hideWhitespace committed only on win. Both runtimes.
- Unresolvable base (trunk / no origin/HEAD) no longer auto-defaults to a
degraded since-base that hides committed work — getGitContext only offers
since-base when the base ref resolves, so the default falls through to
uncommitted. Fingerprint + file-content degrade to HEAD to match the diff.
- git rm --cached no longer yields two diff entries for one path (untracked
files already in the tracked patch are dropped) — fixes wrong-file-open and
j/k nav looping in the Sections panel; also fixes latent uncommitted/unstaged.
- Settings' "Default Diff View" list now preserves the sections<->since-base
coupling (can't leave an invalid sections+classic pair).
- "Behind GitHub" banner only treats origin/* as fetchable; a bare local base
("main") is upgraded to its tracking ref at startup so Fetch can clear it.
- hashUntracked resolves untracked paths against the repo toplevel, not cwd, so
a review launched from a subdirectory isn't blind to untracked edits.
- Agent review instruction now tells agents to enumerate/inspect untracked files.
- Terminology: one "Committed changes" label; the live "Since <base>" label is
dynamic (matches the header); "Since main" kept only as product copy.
- Docs: CLAUDE.md endpoints/fields + since-base view section; ADR recap.
Verified: typecheck (all projects), bun test 1793 pass, and empirical repro of
the rm --cached dedup, trunk->uncommitted fallback, and rename parser.
* fix(review): restore "(PR view)" on the Committed-changes label (unify toward it, not away)
* fix(review): close since-base coverage gaps from PR-990 review round 2
- First-run setup no longer forces since-base on repos where it isn't available
(base ref can't resolve). getGitContext omits since-base there; the first-run
block now checks the same availability before resetting the default or showing
the chooser, so committed work isn't silently hidden on trunk/no-origin repos.
- reviewBase now includes 'since-base', so changing the base while in the
git-status view passes the selected base to /api/file-content — expandable
context is fetched from the right merge-base (was falling back to default).
- Settings now mirror ReviewSetupDialog's coupling exactly: Sections ⇒ force
since-base; Tree ⇒ leave the diff (Tree + since-base is valid and now
saveable); classic diff ⇒ Tree; since-base diff ⇒ leave the view. Removes both
coercions that previously discarded a supported preference.
* fix(review): PR-990 review round 3 — fingerprint/dedup/base-canonicalization
- Fingerprint now uses `git status --porcelain -uall`, so editing a file inside
a brand-new untracked directory changes the fingerprint and the "Diff out of
date" banner fires (was collapsed to `?? dir/` and hashed as unreadable).
- extractTrackedPatchPaths pairs `---`/`+++` and excludes only the file's KEYED
path (new side, or old side for a pure deletion) — no longer drops a recreated
rename-source file (`git mv a b && touch a`). Verified rm --cached still
dedups to one entry and the recreated file still shows.
- resolveReviewBase canonicalizes a bare local default name ("main") to its
tracking ref ("origin/main") on every call, so a client that loaded before the
startup upgrade resolved can't revert the server to the stale local base on the
next refresh/switch. Both runtimes (new Pi resolveReviewBase helper).
- Removed the dead activeDiffLabel prop chain (FileTree -> DiffTypePicker).
- ADR recap: documented the staleness-banner scope limitation (default base only).
Verified: typecheck (all projects), bun test 23/23 review-core+fingerprint pass,
empirical repro of the untracked-dir fingerprint, rm --cached dedup, and
rename-recreate cases.
* fix(review): PR-990 review round 4 — dedup content, staging gate, banner timing
- Dedup rewrite: instead of dropping the untracked side of a same-path collision
(which hid content), drop the tracked DELETION block and keep the untracked
working-tree content. `git rm --cached f` + edit now shows the new content, not
a phantom deletion — still one entry per path (no dock/nav collision). New
stripHeaderPath also strips git's trailing-tab metadata, so space-named files
dedup correctly. Verified: rm --cached+modify shows content (1 entry),
space-named (1 entry), rename+recreate still shows both.
- All-files (and the `a` shortcut on the focused file) now gate staging per-file:
committed files in since-base mode are not stageable, matching SectionsPanel /
FileTreeNode. Shared isPathStageable helper threaded via ReviewStateContext →
AllFilesCodeView. Stops the confusing `git add` no-op that still flipped local
staged/viewed state.
- /api/diff/switch now awaits recomputeBaseBehindRemote before building the
response, so the "Baseline behind GitHub" banner reflects the new base
immediately (no ~5s lag switching in, no stale banner switching away). Both
runtimes.
- ReviewSetupDialog re-applies the recommended default only on first-run dismiss,
not when reopened from the header menu (was snapping a mid-session diff back).
- SectionsPanel "N added" header now counts sidecar-staged files too, so it
matches the staged dots on rows.
Verified: typecheck (all projects), bun test 1830 pass, empirical dedup repro,
both bundles build.
* refactor(review): reuse parsePatchPathToken in removeTrackedDeletions (self-review)
Drop the duplicated stripHeaderPath helper — the shared diff-paths
parsePatchPathToken already strips a/|b/ prefix + C-quoting + git trailing-tab,
and verifies the prefix instead of blindly slicing two chars. Documents the
binary-deletion edge (no --- line, so a binary rm --cached is not deduped).
* fix(review): PR-990 review round 5 — mixed-base + staging/sort/flicker nits
- Atomic base upgrade: the startup origin/* canonicalization swapped currentBase
without rebuilding the patch, so /api/diff could advertise origin/main while
the served hunks came from local main (mixed-base review on origin/HEAD-absent
repos). Now rebuilds the diff for the new base and commits base+patch+ref+
fingerprint together (only if no user switch happened); the fingerprint change
makes the client's freshness poll pick it up. Both runtimes.
- isPathStageable: gate staging OFF when since-base is active but the sidecar
hasn't loaded (was falling through to true, allowing a git-add no-op on a
committed file).
- SectionsPanel: session-staged files now float to the top of Changes — the sort
key was `false ?? stagedFiles.has(...)` which short-circuits; now ORs them.
- /api/diff/fresh: early returns now carry baseBehindRemote, so a snapshot change
mid-probe no longer clears the "behind GitHub" banner for one poll. Both runtimes.
- Removed the now-dead activeLabelFallback prop from DiffTypePicker (FileTree
stopped forwarding it in round 3).
Skipped (agreed): the fetch-base input-validation nit (no realistic attacker)
and the diff-switch TOCTOU (concurrent switches aren't UI-reachable).
Verified: typecheck (all projects), bun test 1830 pass, both bundles build.
* refactor(review): drop redundant staged-OR in SectionsRow (self-review)
item.staged (the grouping key) already ORs in stagedFiles as of the round-5
sort fix, so the row-level `|| stagedFiles.has(...)` is dead weight.
* fix(review): surface the startup base upgrade to already-loaded clients
Review round 6 fixes:
- The startup main -> origin/main upgrade re-baselined the freshness
fingerprint, so a client that fetched /api/diff before the rebuild kept
the old patch and every /api/diff/fresh probe reported fresh (the probe
compares server state to itself, never to what the client renders). Now
the fingerprint is only re-baselined when no client has loaded the
pre-upgrade snapshot; otherwise the stale baseline trips the normal
"Diff out of date - Refresh" banner. Bun + Pi.
- preserveFile refreshes (staleness Refresh, post-Fetch) now adopt the
server's returned base — exactly the paths where the server may have
canonicalized main -> origin/main; keeping the old name sent
/api/file-content and Ask AI context against the wrong base.
- ReviewSetupDialog: clamp the fixed 800px height to the viewport so the
dialog fits on small laptop screens.
- Docs: /api/diff/fresh response also carries baseBehindRemote/agentCwd.
- Documented the committed-deletion + untracked-recreation dedupe edge as
accepted (code comment + ADR) — fixing it needs two same-path diff
entries, which the path-keyed UI cannot represent.
* fix(review): stop the Git-status default from silently reverting to tree
Users with reviewPanelView=sections could keep opening in the tree view on
a stale diff type despite their cookie saying Git status. Three causes:
- The header Sections toggle persisted the view but not defaultDiffType,
creating a conflicted pair (sections + non-since-base default) that every
UI writer is supposed to prevent. It now couples the diff default like
the setup dialog and Settings do.
- configStore's debounced POST /api/config could be lost when a session
closed within 300ms of a change, leaving cookie and config.json split.
Pending writes now flush on pagehide with a keepalive fetch.
- configStore.init() applies config.json over the cookie without the UI
coupling, so a stale server value re-corrupted the pair on every load.
The app now self-heals at load: if the view says sections but the diff
default isn't since-base, it repairs the default (cookie + config.json)
and switches the live session to since-base.
* fix(ui): keep the stage (+) button border visible on the active file row
The button's --border border has no contrast against the active row's 30%
primary tint, so it vanished on the selected row until hovered. Tint the
border with the row's primary color on active rows (both sections and tree
views); the button's own hover border still applies.
* ui(review): spell out the panel view toggle — 'Git status | Tree' text instead of icons
* fix(review): PR-990 review round 7 — export label, diff snapshot race, unicode paths
- exportFeedback describeDiff(): add the missing since-base case — every
feedback export in the new default mode read "**Diff:** since-base".
- /api/diff GET: snapshot patch/base/ref/error BEFORE the sections-sidecar
await and pass the pinned base into buildSectionsSidecar. The startup base
upgrade landing mid-await could pair a rebuilt patch with sections grouped
against the old base, with initialDiffServed still false so no refresh
banner ever came. Bun + Pi.
- SectionsPanel: a session-staged untracked file now moves to the Changes
section immediately (anticipating the server's next sidecar) instead of
sitting in Untracked with a staged dot until refresh.
- unquoteGitPath: real C-style unquoting with octal (UTF-8 byte) escape
decoding. JSON.parse rejects octal escapes, so non-ASCII names kept their
literal \303\251 form — an untracked "café.txt" was silently ABSENT from
the review (git diff --no-index could not access the quoted name) and the
deletion dedupe Set lookup could never match. getUntrackedFileDiffs now
unquotes ls-files output. Unit tests: octal decoding + end-to-end unicode
untracked file in a real repo.
- review-core.test: worktree subtype round-trip now covers since-base and
all (was 6 of 8).
Parked (deliberate): SectionsPanel/FileTree keyboard-nav dedup refactor.
* fix(shared): unquoteGitPath keeps literal unicode intact (self-review)
The byte-collector treated literal non-ASCII code units as single bytes,
which would mojibake headers synthesized by our own quoteGitPath
(JSON.stringify leaves unicode unescaped inside quotes — workspace-mode
prefixed headers round-trip through the same parser). Literal chars now
append as string code units (surrogate-safe); only octal escapes go
through the UTF-8 byte decoder.
* fix(review): PR-990 review round 8 — pre-staged toggles, explicit base, AI context race
- useGitAdd: session Set replaced with a tri-state override map folded over
the sections sidecar; stagedFiles is now the EFFECTIVE staged set (sidecar
+ session stages - session unstages). Fixes pre-staged files: the first
`a` press actually unstages (was a git-add no-op), the sidebar dot clears
after a real unstage (was stuck via sidecar OR), and the All-files header
agrees with the sidebar on load. Overrides reset whenever a fresh sidecar
arrives (switch, preserveFile refresh, PR response) so stale session
intent can't fight new porcelain truth. SectionsPanel drops its own
sidecar OR; stagedCount = effective size.
- Explicit base picks are honored verbatim: the picker sends explicitBase,
and the server permanently disables local-name -> origin/* canonicalization
once set (the local/remote groups are distinct choices). The behind-GitHub
banner also exempts an explicitly-picked local name — Fetch advances
origin/*, so the banner would be un-clearable nagging. Bun + Pi.
- buildCurrentAiReviewContext(patch, base): GET /api/diff builds Ask AI
context from the same served snapshot as the patch — the startup base
upgrade could hand Ask AI a different changeset than the screen. Bun + Pi.
- recomputeBaseBehindRemote: capture remoteDefaultInfo once — a concurrent
refresh nulling it mid-await threw. Bun + Pi.
- Revert stray "Status Update / Updated!" edit to adr/0001 (test debris
swept into the round-2 commit).
- splitPorcelainRename comment corrected (porcelain v1 does NOT quote plain
spaces; a name containing " -> " is ambiguous without -z) + ADR notes for
the index-only-changes semantics and the rename edge.
* fix(review): FileTreeNode uses the effective staged set — round 9
Round 8 made stagedFiles the effective set (sidecar + session overrides)
and removed SectionsPanel's sidecar OR, but missed the same OR in
FileTreeNode's since-base row (sectionStaged). In the Tree fallback a
pre-staged file unstaged this session kept its dot and the next toggle
re-staged it. Grep-swept: this was the last surviving sidecar-staged OR.
* refactor(review): make the staged-display invariant unrepresentable
The round-8/9 bug class (sidecar staged flag ORed over the effective set)
existed because surfaces had a second staging source to reach for. Remove
it: stagedFiles is now a REQUIRED prop on SectionsPanel/FileTree/
FileTreeNode and the optional-prop fallback branches reading
sectionEntry.staged for display are deleted — a future surface cannot
reintroduce the OR because the pattern no longer exists to copy. The
sidecar type's staged field and AGENTS.md now document the invariant at
the point of temptation. Grep for display reads of .staged now hits only
useGitAdd (owner) and the sidecar builder (producer).
* refactor(review): self-review cleanups on the staged-invariant hardening
- orderFilesBySections: document why its snapshot .staged read is safe
(only called at sidecar-fresh moments where snapshot = effective) and
that it must not be reused mid-session — the one remaining display-side
snapshot read the invariant sweep surfaced.
- FileTreeNode: drop the now-pure sectionStaged alias; use isStaged.
* fix(review): PR-990 review round 10 — header staging gate, fingerprint cap, single-writer coupling
- Single-file diff header now uses the per-path staging gate (canStagePath),
closing the last ungated staging trigger: committed-only files in
since-base offered a no-op Git Add that flipped local staged/viewed state.
Full trigger inventory swept: App `a` shortcut, all-files `a` + header,
SectionsPanel rows, FileTreeNode rows, single-file header — all six now
per-path gated or group-gated; no context-menu staging exists.
- Fingerprint circuit-breaker: the freshness poll's `git status --porcelain
-uall` degrades permanently (per cwd, per process) to collapsed -unormal
once its output exceeds 2MB — a forgotten node_modules/ no longer burns
CPU every 5s for the whole session. Costs untracked-dir edit sensitivity
only on such repos; one possibly-spurious staleness banner at the switch.
- The sections ⟺ since-base coupling now has a single writer:
setReviewPanelView/setReviewDefaultDiffType in @plannotator/ui/config.
All five call sites (setup dialog x2, Settings x2, header toggle,
first-run reset, self-heal) converted; grep for direct writes of either
setting now hits only reviewView.ts.
- /api/diff/switch responses pass snapshot args to the AI context builder
(both branches, Bun + Pi) — correct today, now robust against future
awaits between the epoch check and the response.
- Docs: explicitBase in the /api/diff/switch body.
Parked per discussion: SectionsPanel/FileTree nav+search+footer dedup
(extract when the commit-list view adds a third panel), querySelector row
measurement, fetch-base stderr passthrough (standing decision), Pi
hasAgentLocalAccess (pre-existing follow-up list).
* fix(review): PR-990 review round 11 — subdirectory launches, escape decode, settings note
- Repo-root-relative patch paths now resolve against the git toplevel
everywhere they meet a filesystem path or pathspec, via a shared
resolveRepoToplevel helper: file-content working-tree reads (hunk
expansion returned null from a subdirectory launch) and gitAddFile/
gitResetFile (stage/unstage failed with pathspec errors). Both bugs
pre-existed for uncommitted/unstaged; since-base made them the default
experience. The two existing inline toplevel resolutions (untracked
diffs, fingerprint) now use the same helper. Shared code — Pi inherits
via vendoring. Real-repo subdirectory tests for both.
- unquoteGitPath decodes \uXXXX (JSON.stringify emits it for control
chars without a short escape; our synthesized workspace headers
round-trip through this decoder). Malformed \u stays literal. Tests.
- Pi explicitBase guard matches Bun byte-for-byte on empty-string base.
- Settings Git tab notes when the CURRENT repo can't serve the Git-status
view (base ref unresolvable) instead of letting the preference look
silently broken — it's a global preference, so the options stay.
Same-class items left parked (pre-existing, untouched by this PR):
open-in root resolution and code-nav file reads from subdirectory
launches — on the follow-up list with the Pi divergences.
* fix(review): PR-990 review round 12 — per-client freshness, keyboard-operable row controls
- Freshness is now judged PER CLIENT: every patch-carrying response
(/api/diff, /api/diff/switch, pr-switch, pr-diff-scope) includes
snapshotId (the server's draftKey), and the client echoes it on
/api/diff/fresh probes. A mismatch reports stale for THAT client
regardless of the VCS fingerprint. This fixes the round-12 finding —
reloads/second tabs after the startup base upgrade got a permanently
bogus staleness banner from the shared pre-upgrade baseline — and
deletes the round-6 conditional re-baseline hack entirely (the
fingerprint recaptures unconditionally again; the old client's banner
now comes from its snapshot mismatch, not a deliberately stale
baseline). Also gives unfingerprintable modes (P4, PR layer) snapshot-
level staleness for free. Bun + Pi + client hook.
- StageControl and ViewedControl (which had the identical gap) are
keyboard-operable: tabIndex + Enter/Space activation + focus outline.
They're spans inside the row <button> (nested real buttons are invalid
HTML), so they need their own focus stop; the a/v shortcuts remain the
power path.
- Docs: snapshotId on /api/diff, ?snapshot= on /api/diff/fresh.
* fix(review): PR-990 review round 13 — re-key snapshots on scope switch, PR-tab refresh
- The PR scope switch now re-keys draftKey (= snapshotId + draft storage
key) at BOTH commit points, unconditionally — matching every other
snapshot commit site. The full-stack branch previously kept the layer
patch's key: stale layer tabs never got the banner after a cross-tab
scope switch (the exact case snapshotId exists for), and full-stack
drafts collided with layer drafts (pre-existing). The layer branch's
!layerPatchIncomplete conditional is gone — it only stayed consistent
because full-stack never re-keyed. Invariant now: every currentPatch
commit is followed by a re-key. Bun + Pi.
- Refresh works for any stale PR tab: re-selects the CURRENT scope
instead of no-opping for layer (only full-stack could go stale in the
fingerprint-only world; snapshot mismatch changed that). Accepted
residual (documented in code): after a cross-tab PR switch, refresh
updates the patch but not prMetadata — the scope endpoint doesn't
carry it, and the full-rehydrate refactor isn't worth the two-tab edge.
* fix(review): stale incomplete-layer Refresh uses the non-blocking upgrade path (self-review)
Round 13 made Refresh re-select the current scope for stale PR tabs; for
an INCOMPLETE layer patch that POST triggers the server's local recompute,
which can park for minutes behind checkout warmup — and
handlePRDiffScopeSelect renders the full-screen switch overlay the whole
time. Route that case through handleLoadFullDiff (same POST, progress
notice instead of modal), which exists for exactly this slow path.
* fix(review): round 14 — composite snapshot id, fully-pinned /api/diff, "All changes" label
- snapshotId is now content hash + diff type (+ PR scope), built by a
single currentSnapshotId() helper used at every response site and the
freshness compare. A cross-tab MODE switch with a byte-identical patch
(layer vs full-stack on a single-PR stack) now flags old tabs; the base
is deliberately excluded so a same-commit main -> origin/main
canonicalization stays banner-silent (round-12 noise-avoidance kept).
draftKey stays a pure content hash — drafts survive content-identical
round-trips. Bun + Pi.
- GET /api/diff pins ALL served fields (diffType, hideWhitespace,
prDiffScope join patch/base/ref/error/snapshotId) and the sidecar + AI
context builders take the pinned type instead of reading globals — a
concurrent tab switch during the sidecar await can no longer produce a
since-base patch labeled with another mode. Bun + Pi.
- First-user feedback: the since-base label is now plain English —
"All changes since origin/main" (dynamic, follows the picked base);
dialog/Settings short form "All changes"; feedback exports match.
uncommitted reverts to "Uncommitted" where it had borrowed "All
changes", so the two stay distinguishable side by side.
* docs(review): finish the Since-main -> All-changes terminology sweep in comments (self-review)
* fix(review): PR-990 review round 15 — base-revert race, freshness reset, unstage regroup
- resolveReviewBase gains a second, probe-independent rule: a non-explicit
echo of the bare local name of the CURRENT origin/* base stays on the
tracking ref. The existing rule keys off remoteDefaultInfo, which comes
from a second network probe that can lag the startup upgrade by seconds;
in that window a diff-type/whitespace switch echoing "main" committed
the session back onto the stale local branch and set baseEverSwitched,
permanently blocking the upgrade. Bun + Pi.
- useDiffFreshness resets stale/dismissed state on snapshotId too — since
round 14 a new snapshot can reuse identical patch text with a different
id, and the old banner state wrongly carried over until the next poll.
- SectionsPanel: unstaging a PRE-staged add moves it back to Untracked
(mirror of the round-7 stage regroup). Detection deliberately uses the
sidecar's snapshot flag to recognize "was pre-staged"; staged
modifications stay in Changes, staged renames remain a refresh-heals
edge.
* fix(review): PR-990 review round 16 — rename staged count, guarded fetch-base replay
- "N added" no longer double-counts staged renames: the sidecar truthfully
marks BOTH porcelain sides staged, but an above-threshold rename renders
as ONE file — the hidden old path inflated the count and left a phantom
effective-staged entry that unstaging the visible row couldn't clear.
The client now filters sidecar-staged paths to rendered files, which is
correct in both patch shapes (below-threshold renames render delete+add
as two rows and both sides pass the filter). Client-only; the sidecar
stays faithful to porcelain.
- The fetch-base completion only replays the diff refresh if the user's
diff type/base selection is unchanged since the click — a slow fetch no
longer yanks the review back to the view captured at click time.
Declined with reasons (in review thread): first-run persist-before-confirm
(approved forced default, second flagging), explicit-base flag ordering
(early-set preserves user intent; commit-after-win would leave picker and
server disagreeing), base-picker revert target (near-unreachable compound
race, deferred).
* fix(review): PR-990 review round 17 — find origin/main on feature-only clones
getDefaultBranch's chain (origin/HEAD -> local main -> blind "master")
skipped the fetched remote-tracking ref entirely. On checkouts with no
origin/HEAD symref and no local main/master — CI checkouts, `clone
--branch feature`, extra worktrees — it guessed "master", the base
didn't resolve, getGitContext suppressed since-base, and the flagship
"All changes" view silently disabled itself for the whole session even
though origin/main was fetched and diffable. The startup upgrade can't
rescue it either (its guard reads the bogus "master" as a deliberate
non-default base).
Chain is now: origin/HEAD (verified) -> origin/main -> local main ->
origin/master -> "master" — remote-tracking refs preferred, matching the
function's stated prefer-upstream intent. Shared core (Pi inherits);
real-repo test reproducing the exact clone shape.
|
||
|
|
6ec1a66c9b |
feat(review): large-PR pipeline, instant-open checkout, scroll perf, and worker-pool highlighting (#893)
* feat(review): large GitHub PR fallback + non-blocking PR checkout
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.
* fix(review): survive long PR checkout warmups + classify reconstructed 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).
* feat(review): local full-diff upgrade for PRs whose API diff is truncated
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.
* fix(review): use GitLab's too_large/collapsed flags for withheld-diff 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.
* feat(prompts): unify review-denied suffix — triage first, no coding off 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.
* fix(prompts): generalize review-denied suffix — 'from review', not 'external 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.
* fix(review): non-blocking 'Load full diff' + flag-handling hardenings
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).
* fix(review): stop scroll-driven sem stampede when semantic diff is failing
The badge retry change (
|
||
|
|
c23df4db43 |
UI 2.0 visual refresh + HTML-render annotate (strictly UI, off main) (#863)
Extracts the UI 2.0 visual refresh and the HTML-render annotate feature onto main, standalone (no daemon). Faithful copy of feat's UI/HTML logic with the standalone transport kept. |
||
|
|
b0ed4be063 |
feat(review): right-click file tree row to copy path or filename (#650)
* feat(review): right-click file tree row to copy path or filename * feat(review): add "Copy full path" to file tree context menu Resolves to activeWorktreePath ?? agentCwd ?? gitContext.cwd; hidden in PR-review mode where files aren't on local disk. |
||
|
|
0fd9826c2c |
feat(review): all-files diff view with unified scrolling (#640)
* feat(review): all-files diff view with lazy mounting Renders all diffs in a single scrollable pane using individual FileDiff components with sticky file headers, collapse toggles, and viewport-based lazy mounting via IntersectionObserver. For provenance purposes, this commit was AI assisted. * feat(review): convert all-files view to dock panel + fixes - Register AllFilesDiffView as a proper dock panel so it coexists with single-file tabs, AI chat, PR panels - Remove viewMode toggle in favor of dock panel lifecycle - Fix annotation toolbar positioning (mouse tracking) - Fix annotation PR/scope filtering for stacked PRs - Fix stale patch on file switch (deferred toolbar open) - Fix conventional comments boolean comparison - Fix SuggestionModal props to match DiffViewer pattern - Wire reviewBase for correct hunk expansion context - Fix pre-existing CSS bug on .has-annotations background - Add Shift+V/A keyboard shortcuts for viewed/stage toggle - Add [/] keyboard shortcuts for file-to-file scrolling - Add keyboard shortcuts to settings panel - Restore scroll-based file tree highlight via dock panel state For provenance purposes, this commit was AI assisted. * feat(review): sidebar toggles, keyboard shortcuts, click-drag selection - Move sidebar tab buttons (annotations/AI/agents) to header bar - Generalize useSidebar hook for both plan and review editors - Remove internal tab state from ReviewSidebar, receive from parent - Add file tree toggle button at far-left of header - Add click-and-drag line selection in diff content area - Change keyboard shortcuts from Shift+V/A to bare v/a/c v = toggle viewed (+ collapse in all-files mode) a = toggle git add/stage c = collapse/expand file (all-files mode) - Widen DiffOptionsPopover to prevent word-alt line break - Fix Shift+V/A targeting wrong file in all-files mode - Default to all-files panel on initial load - Hide dock headers when all-files panel is the only panel For provenance purposes, this commit was AI assisted. * feat(review): click-and-drag line selection in diff content area Extract shared selection helpers to utils/diffSelection.ts and add mouseup handlers to both DiffViewer and AllFilesDiffView so users can click and drag across code lines to open the annotation toolbar — not just the gutter. For provenance purposes, this commit was AI assisted. * fix(review): address AI review findings - Gate all-files keyboard shortcuts on isActive to prevent double-toggle when a single-file tab is focused - Fix getDiffSelection to iterate all diffs-container elements so click-drag selection works on files beyond the first - Clear needsInitialDiffPanel after opening all-files panel to prevent forced reactivation on diff switches - Clear activeFilePath and collapsedFiles when files change to prevent stale state across PR/diff switches - Add file.patch to LazyFileDiff fetch deps so file contents refetch when the diff context changes - Add toggleCollapse to keyboard effect dependency array For provenance purposes, this commit was AI assisted. * feat(review): add z to undo collapse with scroll-back Press z to reopen the last collapsed file and scroll back to it. Maintains a history stack so repeated z walks back through collapses. Skips already-expanded entries. History clears on diff/PR switch. For provenance purposes, this commit was AI assisted. * fix(review): review findings + sidebar toggles + folder icon - Fix stale context memo: add isAllFilesActive + handleAddAnnotationForFile to useMemo deps - Fix v handler: clear activeFilePath after collapse, only collapse when marking viewed ON (not when toggling OFF) - Fix click-drag copy regression: only trigger annotation toolbar for multi-line selections, preserve single-line/word selection for Cmd+C - Fix LazyFileDiff: clear stale file contents before refetch - Clean up App.tsx v/a handler: early return when isAllFilesActive - Add Cmd+B (file tree) and Cmd+. (sidebar) keyboard shortcuts - Add toggle menu items with kbd badges to Options dropdown - Replace file tree icon with simple folder - Add z (undo collapse) to keyboard shortcuts panel For provenance purposes, this commit was AI assisted. * chore: remove dead code from iterations - Remove unused FileTreeIcon import and component file - Remove unused AnnotationsIcon component from ReviewHeaderMenu - Remove unused onClose prop and close button from FileTree For provenance purposes, this commit was AI assisted. * fix(review): gate v/a on diff panel, keep annotation selection in all-files - Track isDiffPanelActive so v/a shortcuts only fire when viewing a single-file diff, not PR Comments/Summary/Agent panels - Annotation selection stays in all-files mode instead of switching to a single-file tab — lets the scroll-to-annotation effect handle it For provenance purposes, this commit was AI assisted. |
||
|
|
b375a804b2 |
feat(review): AI review agents, local worktree, and UI polish (#491)
* feat(review): add Codex AI review agent with live logs, --local worktree, and panel UI
Hook up Codex as the first AI review agent in the code review system:
- Spawn `codex exec` with Codex's native review prompt and output schema
- Parse structured findings (ReviewOutputEvent) and push as external annotations
- Annotations appear inline in the diff viewer pinned to specific lines
- Review verdict (correct/incorrect + confidence + explanation) displayed in panel
Agent job infrastructure enhancements:
- Server-side command building via `buildCommand` callback (providers don't need frontend commands)
- Result ingestion via `onJobComplete` callback (reads output file, transforms findings)
- `addAnnotations` method on external annotation handler (bypasses HTTP for server-internal producers)
- Live stderr streaming via `job:log` SSE events with 200ms buffer-and-flush
- `cwd` and `summary` fields on AgentJobInfo
PR review with --local worktree:
- `plannotator review <PR_URL> --local` creates a temp git worktree with the PR branch
- Agent gets full local file access without touching the user's working tree
- Hybrid server mode: both prMetadata (platform features) and gitContext (local access)
- Automatic worktree cleanup on session end
- Runtime-agnostic worktree primitives in packages/shared/worktree.ts
Panel UI redesign:
- Findings | Logs tab system with underline-style tabs
- LiveLogViewer component with auto-scroll, truncation, and copy
- Review verdict card (correct/incorrect with confidence and explanation)
- Pending state with labeled "Review Verdict — Pending..." (not skeleton bars)
- Job card click opens detail panel directly (removed separate icon button)
- Dismissed annotation tracking (deleted annotations persist as "dismissed" in panel)
- Copy all annotations as formatted markdown
- Worktree badge in header with info dialog showing path
- CopyButton extended with inline variant for reuse
- ConfirmDialog extended with wide option
For provenance purposes, this commit was AI assisted.
* style(review): UX polish pass — visual quality improvements across code review UI
- VerdictCard: remove AI-template left-border, use background-only tint
- Inline annotations: 6px radius, subtle shadow, hover elevation, action button scale
- File tree: tighter indentation (4 + depth*10), reduced container padding
- Select dropdowns: normalized to 4px border-radius matching pierre diffs
- Dockview tabs: close button pushed to far right with margin-left auto, visible at 0.25 opacity
- PR icons moved from sidebar to header (next to PR link)
- AnnotationRow: translate-x hover feedback
- Border-radius normalized to `rounded` (4px) across all components
- Type scale consolidated: text-[8px]/[9px] → text-[10px], text-[11px] → text-xs
- Sidebar tab hit targets increased (px-2.5 py-1.5, w-4 icons)
- Sticky file group headers in annotation sidebar
- FileTree controls collapsed (worktree + diff selectors share one row)
- Tab micro-animations (transition-all duration-150)
- ScrollFade component for gradient indicators on scrollable containers
- Prose containment: max-w-2xl + px-6 padding on PR Summary/Comments/Checks panels
- MarkdownBody: leading-relaxed for comfortable reading
- PR Comments: surface lift (bg-muted/10), hover feedback, author font-semibold
- PR Checks: link affordance (text-primary, underline on hover, external link icon)
For provenance purposes, this commit was AI assisted.
* feat(review): PR comments panel — search, filter, collapse, navigation, and polish
Comments panel enhancements:
- Search: real-time text filter across author and body, match count display
- Keyboard navigation: j/k to move between comments, scroll-to-selected
- Sort: toggle between oldest/newest first
- Collapsible comments: click header to collapse, collapse/expand all controls
- Author exclusion filter: click authors to hide their comments (not inclusion)
- Comment actions: hover-reveal "View on GitHub" link + copy button (bottom-right)
- Review URLs: PRReview type now includes optional url field, populated from GitHub API
PR Summary fixes:
- Label contrast: use theme foreground color for label text instead of raw GitHub hex
- Linked issues: replaced broken hardcoded SVG with proper GitHub Octicons issue-opened icon
Data plumbing:
- platformUser exposed through ReviewStateContext for "Mine" filtering
- Panel wrapper changed to overflow-hidden for sticky toolbar support
- "Commented" review badge hidden (noise — only show Approved/Changes Requested/Dismissed)
For provenance purposes, this commit was AI assisted.
* feat(review): inline review threads with outdated/resolved state and diff hunk previews
PR Review Threads:
- Fetch inline code review comments via GitHub GraphQL (reviewThreads query)
- PRReviewThread and PRThreadComment types with isResolved, isOutdated, path, line, diffSide
- ThreadCard component: file/line context, Outdated/Resolved badges, nested replies
- Resolved/outdated threads: gradient fade on body with "Show full comment" expand
- GitLab: reviewThreads placeholder (TODO: parse DiffNote positions from notes)
DiffHunkPreview:
- Renders diff hunks using @pierre/diffs FileDiff component (read-only, compact)
- Full theme integration: reads computed CSS vars, injects via unsafeCSS (same as main DiffViewer)
- Respects user font settings from ReviewState context
- Handles bare GitHub diffHunk format (prepends synthetic file headers for pierre parsing)
Comments Panel Polish:
- Comment cards: bg-card + subtle shadow for depth and isolation from panel background
- Hover: shadow elevation (0_2px_6px) for interactive feedback
- Thread cards: dimmed shadow for resolved/outdated, full shadow for active
- Prose padding: px-8 (32px) across all dockview panels (Summary, Comments, Checks, Findings)
For provenance purposes, this commit was AI assisted.
* feat(review): Claude Code agent, cross-repo --local, render fixes
Claude Code review agent:
- claude-review.ts: prompt (adapted from code-review plugin), command builder
(dontAsk + granular allowedTools/disallowedTools), JSONL stream output parser
- Prompt sent via stdin (not argv) to avoid quoting/variadic flag conflicts
- stream-json --verbose for live JSONL streaming + final structured_output
- Same schema as Codex — transformReviewFindings is now provider-agnostic
Agent jobs infrastructure:
- stdout capture (captureStdout option) for providers that return results on stdout
- stdin prompt writing (stdinPrompt option) for providers that read prompt from stdin
- cwd override in buildCommand return for providers without -C flag
- await stdoutDone before onJobComplete to prevent drain race condition
- job.prompt field for transparent prompt display in detail panel
Cross-repo --local:
- Detect same-repo vs cross-repo via parseRemoteUrl comparison
- Cross-repo: shallow clone via gh/glab repo clone (--depth 1 --no-checkout + targeted fetch)
- Cross-repo uses platform diff (gh pr diff) for display, clone for agent file access
- Same-repo: existing worktree path unchanged
- Cleanup: rmSync for clones, worktree remove for same-repo
Performance: jobLogs context split
- Separate JobLogsContext to prevent high-frequency log SSE from re-rendering all panels
- Only ReviewAgentJobDetailPanel subscribes to JobLogsProvider
- Standard React pattern: split contexts by update frequency
Image error handling:
- SafeHtmlBlock component wraps dangerouslySetInnerHTML with img onerror handlers
- Broken images (expired GitHub JWTs) hide on first 404 instead of flickering
- Prevents console 404 flood from re-render retry loops
For provenance purposes, this commit was AI assisted.
* fix(review): decontaminate --local from diff pipeline, fix worktree setup, default local for PRs
The --local flag was setting gitContext from the worktree, which contaminated
the diff rendering pipeline — causing pierre "trailing context mismatch" errors
because /api/file-content read worktree files instead of using the GitHub API.
Root cause: gitContext serves two purposes — diff pipeline (file contents, diff
switching, staging) and agent sandbox (cwd for agent processes). These are now
properly separated via a new agentCwd option on ReviewServerOptions.
Changes:
- Add agentCwd to ReviewServerOptions, independent of gitContext
- Agent handler (getCwd, buildCommand, onJobComplete) prefers agentCwd
- Stop setting gitContext in --local PR path — diff pipeline untouched
- Revert band-aid !isPRMode guards (no longer needed)
- Fix same-repo worktree: fetch origin/<baseBranch> so agents see correct diff
- Fix cross-repo clone: create local branch at baseSha for git diff accuracy
- Fix FETCH_HEAD ordering: fetch base branch before PR head (createWorktree needs PR tip)
- Fix macOS path mismatch: realpathSync(tmpdir()) so agent paths strip correctly
- Change buildCodexReviewUserMessage signature from GitContext to focused options
- Make --local the default for PR/MR reviews (--no-local to opt out)
- Pass agentCwd to client for worktree badge display
For provenance purposes, this commit was AI assisted.
* style(review): unify panel headers, responsive buttons, dockview polish
- Unify all panel headers at 33px via --panel-header-h CSS variable
- FileHeader now uses shared variable instead of hardcoded 30px
- Dockview tab bar height, font size, and padding aligned with sidebars
- FileTree header uses fixed height instead of padding-based sizing
- Consistent border opacity (border-border/50) across all panels
- Consistent font weight (font-semibold) on all header labels
- Remove dockview tab focus outline (::after pseudo-element)
- Dockview tab close button pushed to right edge
- Tab bar void area uses muted background
- Top app header compacted from h-12 to py-1
- Sidebar footer: copy button and diff stats side by side
- FeedbackButton responsive labels (Send/Post at md, full labels at lg)
- Move ReviewAgentsIcon to packages/ui for shared use
- Agent empty state uses shared ReviewAgentsIcon instead of hardcoded SVG
- Sidebar header label truncates when narrow (tabs never clip)
- Detail panel prompt disclosures get proper spacing
- React.memo on PR tab components (PRSummaryTab, PRCommentsTab)
- Inline onerror on img tags for broken GitHub image handling
For provenance purposes, this commit was AI assisted.
* fix: type assertion for Bun stdin FileSink
Bun's proc.stdin is typed as `number | FileSink` but we need to call
.write() and .end() on it. Cast to FileSink to satisfy tsc --noEmit.
For provenance purposes, this commit was AI assisted.
* fix(review): XSS in sanitizeHtml, git flag injection, cross-repo ref mismatch
Security:
- Remove `onerror` from DOMPurify ALLOWED_ATTR — was allowing arbitrary JS
execution via PR descriptions containing `<img onerror="...">`. Replace
with SafeHtml component that attaches error handlers via useEffect + ref.
- Add `--` end-of-options separator to git fetch and git branch calls to
prevent flag injection via crafted branch names from API responses.
Bug fix:
- Cross-repo clones now create both local branch AND remote-tracking ref
(`refs/remotes/origin/<baseBranch>`) at baseSha, so agents can use either
`git diff main...HEAD` or `git diff origin/main...HEAD`.
Polish:
- Add copy button to verdict card in agent detail panel
- Remove hover:translate-x animation from finding rows
- Reduce file tree indent per level, remove extra file offset
- Add pr-action endpoint logging for debugging submit failures
For provenance purposes, this commit was AI assisted.
* chore: upgrade @pierre/diffs from 1.1.0-beta.19 to ^1.1.12
The beta pin was needed for processFile() API (expandable diff context),
which shipped in 1.1.0 stable on March 14. We were 13 releases behind.
Notable fixes in the upgrade path:
- 1.1.5: Fix diffAcceptRejectHunk with partial FileDiffMetadata
- 1.1.6: Patch parsing fix for renames and dotfiles
- 1.1.8: Fix maxLineDiffLength regression
May resolve intermittent "trailing context mismatch" errors in diff rendering.
For provenance purposes, this commit was AI assisted.
* fix(review): remove shell provider, flag injection, Claude log formatting, copy UX
Security:
- Remove shell provider from agent capabilities (unauthenticated RCE vector)
- Move `--` before prRepo in `gh repo clone` to prevent flag injection
Features:
- Wire up formatClaudeLogEvent — Claude live logs now show readable text
instead of raw JSONL
- Sidebar annotations: copy + delete buttons appear on hover (no overlap)
- Agent finding rows: copy button on hover (progressive disclosure)
- Verdict card: copy button pushed to the right
- File tree: reduced indent per level, files aligned with folders
Infra:
- PR action endpoint logging for debugging submit failures
For provenance purposes, this commit was AI assisted.
* fix: validate repo identifier to prevent flag injection in gh repo clone
The `--` separator in `gh repo clone` separates gh args from git args,
not positional args from flags. Using `--` before prRepo would break
the git flags. Instead, validate that the repo identifier doesn't start
with `-` to prevent flag injection via crafted PR URLs.
For provenance purposes, this commit was AI assisted.
* feat(review): Claude-specific review model with severity, reasoning, and multi-agent prompt
Claude review agent now has its own schema, prompt, and transform — separate
from Codex's P0-P3 priority model. Each provider uses its natural review style.
Schema changes:
- Claude findings use severity (important/nit/pre_existing) instead of priority (0-3)
- Flat structure: file, line, end_line instead of nested code_location
- description (single field) instead of title + body
- reasoning field captures the validation chain per finding
- summary with counts instead of overall_correctness/confidence
Prompt: Converges the open-source Claude Code review prompt with the remote
review service model. 4 parallel agents (Bug+Regression at Opus, Security at
Opus, Code Quality at Sonnet, Guideline Compliance at Haiku), validation step,
deduplication, severity classification. CLAUDE.md and REVIEW.md awareness.
UI: Severity markers (colored dots) on finding rows. Collapsible reasoning
section via <details>. New optional severity/reasoning fields on CodeAnnotation
and the external annotation store — backward compatible, only set by Claude.
Transform: transformClaudeFindings normalizes Claude output into the shared
annotation format. Codex path (transformReviewFindings) is completely untouched.
For provenance purposes, this commit was AI assisted.
* fix: use bg-amber-500 for nit severity dot (bg-warning may not be defined)
For provenance purposes, this commit was AI assisted.
* fix: security hardening, debug cleanup, deduplication, Pi mirroring, findings UX
Security:
- Add -- separator to ensureObjectAvailable git fetch (worktree.ts)
- Validate baseBranch against path traversal (..) before git ref operations
- Use process.once('exit') instead of process.on for worktree cleanup
Debug:
- Gate debugLog behind PLANNOTATOR_DEBUG env var (no more unconditional writes)
- Remove PARSE_OUTPUT_RAW dump that logged full JSON output to disk
Deduplication:
- Extract toRelativePath to packages/server/path-utils.ts (was duplicated
in codex-review.ts and claude-review.ts)
Pi extension:
- Remove shell provider from capabilities
- Add buildCommand callback to AgentJobHandlerOptions
- POST handler calls buildCommand for server-side command synthesis
UI:
- Findings sorted by severity (important → nit → pre_existing)
- Severity legend under findings header (colored dots)
- Reasoning always visible (not collapsible) — fixes click navigation bug
where <details> captured click events and broke annotation linkage
- Full finding text shown (removed line-clamp-2 truncation)
- Sidebar annotation hover actions aligned to the right
- DiffHunkPreview: cancel requestAnimationFrame on unmount
For provenance purposes, this commit was AI assisted.
* fix: move toRelativePath import to top of claude-review.ts
For provenance purposes, this commit was AI assisted.
* fix: same-repo detection compares host, platform-aware comment links, remove design docs
- Same-repo detection now compares both owner/repo AND hostname from the
git remote URL against prMetadata.host. Prevents false positives on
GitHub Enterprise where different instances share org/repo names.
- "View on GitHub" label in PR comments tab now shows "View on GitLab"
for GitLab MR comments based on the comment URL.
- Remove internal design docs (PR_LOCAL_WORKTREE.md, AGENT_LIVE_LOGS.md)
that were development artifacts, not user-facing documentation.
For provenance purposes, this commit was AI assisted.
* fix(review): show severity markers and reasoning in inline diff annotations
The severity and reasoning fields from Claude findings were only visible in
the agent detail panel, not in the inline diff annotations. Now:
- DiffAnnotationMetadata carries severity and reasoning fields
- DiffViewer passes them through when mapping annotations
- InlineAnnotation renders colored severity dot and reasoning text
For provenance purposes, this commit was AI assisted.
* fix: prefix Claude findings text with [severity] tag
Findings now show as "[important] description", "[nit] description",
"[pre_existing] description" — consistent with Codex's [P0]/[P1] tags.
For provenance purposes, this commit was AI assisted.
* feat(pi): full agent review mirroring — stdin, stdout, live logs, result ingestion
Pi extension agent-jobs handler now mirrors the Bun server's full capabilities:
- stdin piping for Claude prompt delivery
- stdout capture for Claude JSONL stream parsing
- Live stderr streaming with 200ms buffer-and-flush for job:log events
- Claude JSONL formatting via vendored formatClaudeLogEvent
- onJobComplete callback for result parsing and annotation push
- Full buildCommand integration in POST handler
- jobOutputPaths tracking with cleanup on kill
Pi serverReview.ts now wires buildCommand and onJobComplete with the same
logic as the Bun review server — Codex and Claude commands are built
server-side, results are parsed and transformed into external annotations.
Runtime compatibility: replaced Bun.file/Bun.write in codex-review.ts with
node:fs/promises equivalents (writeFile, readFile, existsSync) that work on
both Bun and Node. Verified Bun build passes.
Vendoring: vendor.sh now copies codex-review.ts, claude-review.ts, and
path-utils.ts from packages/server/ with import path rewriting for the
generated/ layout.
Also: Review Prompt label, px-8 padding on agent detail header/tabs/logs.
For provenance purposes, this commit was AI assisted.
* fix: remove duplicate isPRMode declaration in Pi serverReview.ts
For provenance purposes, this commit was AI assisted.
* fix: include reasoning in all copy and feedback export paths
- exportReviewFeedback: appends **Reasoning:** after finding text
- Per-finding copy button: appends reasoning to copy text
- Sidebar annotation copy: appends reasoning to copy text
This ensures reasoning flows through Copy All, Send Feedback, and
individual copy actions — not just the visual rendering.
For provenance purposes, this commit was AI assisted.
* fix: six verified findings — navigation, dedup, cleanup, copy, diff match, Windows paths
1. openDiffFile: clicking a finding now navigates to the correct file
before selecting the annotation (was silently selecting in wrong file)
2. SEVERITY_STYLES: extracted to packages/ui/types.ts as shared constant,
imported in both ReviewAgentJobDetailPanel and InlineAnnotation
(was duplicated with per-render rebuild in InlineAnnotation)
3. killJob: added jobOutputPaths.delete calls to match Pi's version
(was leaking two strings per killed job)
4. CommentActions: replaced hand-rolled copy with CopyButton inline
variant (was reimplementing useState/clipboard/setTimeout pattern)
5. Branch mode prompt: changed from three-dot to two-dot to match
the UI's actual diff computation (agent was reviewing different diff)
6. toRelativePath: uses path.relative + forward-slash normalization
for Windows compatibility (was string prefix matching with / only)
For provenance purposes, this commit was AI assisted.
* perf: wrap ReviewSidebar in React.memo to prevent re-renders during log streaming
Every job:log SSE event triggers setJobLogs in useAgentJobs, which re-renders
App.tsx. Without memo, the sidebar re-renders on every event (~5/sec) even
though its props (agentJobs.jobs, capabilities, callbacks) haven't changed.
This caused visible flickering when a review tab was open during agent runs.
React.memo shallow-compares props — all sidebar props are stable references
(jobs array only changes on status events, callbacks are useCallback-wrapped),
so the sidebar correctly skips re-renders during log streaming.
For provenance purposes, this commit was AI assisted.
* fix(security): remove find/ls/cat from Claude allowed tools, add glab CLI
Security: Bash(find:*) allowed find -exec to spawn arbitrary subprocesses
that bypassed --disallowedTools. Removed find, ls, and cat — Claude has
Glob, Read, and Grep built-in which cover file access without shell exec.
Feature: Added glab mr view/diff/list and glab api to allowed tools so
Claude can inspect GitLab MR context in remote-mode reviews.
For provenance purposes, this commit was AI assisted.
* fix: Pi addAnnotations, Pi stdout drain, cross-repo exit codes
Pi extension:
- Add addAnnotations() to external-annotations.ts return object —
serverReview.ts calls it when agent jobs complete but the method
was missing (build/runtime error)
- Change proc.on('exit') to proc.on('close') in agent-jobs.ts —
Node's 'exit' fires before stdio streams drain, so stdoutBuf could
be incomplete when onJobComplete parses Claude's JSONL result
Cross-repo --local:
- Check git checkout FETCH_HEAD exit code — throw if it fails so the
outer catch falls back to remote-only with a clear warning
- Log warning if baseSha fetch fails (non-fatal, agents just can't
diff locally)
For provenance purposes, this commit was AI assisted.
* fix: FETCH_HEAD ordering, Pi worktree-aware cwd, SEVERITY_ORDER hoisted
Critical:
- Move ensureObjectAvailable before PR head fetch — it can overwrite
FETCH_HEAD if baseSha needs fetching, causing createWorktree to
check out the base commit instead of the PR head
- Pi serverReview.ts: extract resolveAgentCwd() helper used by getCwd,
buildCommand, and onJobComplete — was bypassing worktree-aware path
resolution, causing agents to run in wrong directory
Cleanup:
- Hoist SEVERITY_ORDER to module scope in ReviewAgentJobDetailPanel
(was recreated inside component body on every render)
For provenance purposes, this commit was AI assisted.
* fix: Bun/Pi parity — provider default, annotation error logging
- Bun agent-jobs: change provider default from "shell" to "" (shell was
removed from capabilities, default should match Pi)
- Pi serverReview: log errors from addAnnotations in onJobComplete
(Bun logs them, Pi was silently ignoring)
For provenance purposes, this commit was AI assisted.
* docs: add AI Code Review Agents guide with full prompt transparency
New docs page covering:
- Overview of Codex and Claude review agents
- How findings work (severity/priority, reasoning, navigation)
- Local worktree behavior (same-repo vs cross-repo)
- Full transparency section with:
- Claude multi-agent pipeline prompt (all 6 steps)
- Claude command and allowed/blocked tools
- Codex review prompt and command
- Both output schemas (Claude severity + Codex priority)
- Security notes (read-only, no network, local execution, no commenting)
- Customization via CLAUDE.md and REVIEW.md
For provenance purposes, this commit was AI assisted.
* docs: add provenance links for Claude and Codex review integrations
Credit Anthropic's Claude Code Review service, the open-source
code-review plugin, and OpenAI's Codex CLI as the foundations
for our review agent integrations.
For provenance purposes, this commit was AI assisted.
* fix: temp clone leak, j/k key conflict, thread header null line
- Cross-repo: clean up localPath in catch block when fetch/checkout
fails after clone succeeds (directory was leaking in /tmp)
- Remove j/k/arrow keyboard navigation from PR comments panel —
these shortcuts belong to the file tree only, both registering
global handlers caused double-navigation
- Thread header null guard: check thread.line before building range
label to prevent "L12–null" for outdated GitHub threads
For provenance purposes, this commit was AI assisted.
* fix: hoist localPath for catch-block scope, validate baseSha format
The previous rmSync(localPath) in the catch block was dead code —
const declarations inside try are not in scope in catch (separate
lexical environments per ECMAScript spec). The ReferenceError was
silently swallowed by the inner try/catch, so temp directories
still leaked on failed fetch/checkout.
Fix: hoist `let localPath` before the try block so it's accessible
in catch. Guard with `if (localPath)` since the error could occur
before assignment.
Also: validate baseSha is a hex SHA (40-64 chars) to prevent git
flag injection via crafted API responses. Validate baseBranch
rejects both '..' and '-' prefixes.
For provenance purposes, this commit was AI assisted.
* docs: rewrite AI Code Review guide for clarity and readability
Restructured for a technical audience: shorter paragraphs, cleaner
tables, removed em-dashes and filler prose, tightened the pipeline
diagram, streamlined security section into scannable single-line items.
For provenance purposes, this commit was AI assisted.
* docs: rewrite transparency section with exact prompts and commands
Replaced summarized/paraphrased transparency section with the actual
prompts, commands, schemas, and tool allowlists as they exist in the
code. One short security note at the top, then raw content.
For provenance purposes, this commit was AI assisted.
* docs: add mini TOC to transparency section
For provenance purposes, this commit was AI assisted.
* fix: stdout drain hang, Codex verdict override, Claude parse logging, memo removal
Critical:
- Race stdoutDone against 2s timeout after proc.exited — prevents
permanent job hang when Bun's ReadableStream doesn't close after
process exit. The process is dead; 2s is a cleanup deadline.
Bug fix:
- Codex verdict: override to "Issues Found" when P0/P1 findings exist,
regardless of the freeform overall_correctness string. Prevents green
"Correct" badge when Codex says "mostly correct but has issues."
P2/P3-only findings still trust Codex's verdict.
Observability:
- Log Claude parse failures with buffer size and last 200 bytes so we
can diagnose empty-findings cases.
Performance:
- Remove React.memo from ReviewSidebar — was blocking legitimate
re-renders (job status, findings) to prevent cosmetic log flickering.
The tradeoff was wrong.
Docs:
- Remove "shell" from CLAUDE.md capabilities table (provider was removed).
For provenance purposes, this commit was AI assisted.
* fix: type assertions for Bun ReadableStream async iteration
Bun's proc.stdout/stderr support for-await at runtime but TypeScript's
ReadableStream type doesn't declare [Symbol.asyncIterator]. Cast through
unknown to AsyncIterable<Uint8Array> — standard Bun workaround, same
pattern as the FileSink cast for stdin.
For provenance purposes, this commit was AI assisted.
|
||
|
|
68418e2a35 |
feat(review): add dockview center workspace (#453)
* chore(review): add dockview-react dependency * feat(review): add dockview center workspace * fix(review): stabilize diff search navigation * feat(review): add progressive file search UI * feat(review): streamline header controls * fix(review): portal annotation toolbar * refactor(review): streamline header and action menus * fix(review): make file header responsive * fix(review): align split diff drag handle * chore(review): hide review agents sidebar * fix(review): remount diff viewer on file switch * fix(review): wrap inline diff comments |
||
|
|
d29dd08c13 |
feat: git add files from code review UI (#257)
* feat: add per-file git add from code review UI (#254) Adds the ability to `git add` individual files directly from the code review interface. Users can stage approved files then switch to the new "Unstaged changes" diff view to see only remaining work. - POST /api/git-add endpoint with worktree support - gitAddFile/gitResetFile utilities in packages/server/git.ts - "Staged changes" and "Unstaged changes" added to diff type dropdown - useGitAdd hook encapsulating all staging state and API logic - "Git Add" / "Added" toggle button in FileHeader (next to Viewed) - Visual indicator (left border) for staged files in sidebar FileTree Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: use green background tint for staged files instead of left border Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: support staged/unstaged diffs in worktrees and display stage errors Adds "staged" and "unstaged" to worktree sub-type allowlists (server parser, client parser, and runGitDiff switch), displays stageError in FileHeader, and documents /api/git-add in CLAUDE.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: clean up git add feature — fix stale closure, timeout leak, dedup validation - useGitAdd: use ref for stagedFiles to stabilize stageFile callback, clear error timeout on reset, drop unnecessary useMemo - App.tsx: wrap onFileViewed in useCallback - FileTree: merge duplicate staged count conditionals - review.ts: reuse exported validateFilePath for /api/file-content Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
47bfc4bfd4 |
feat: hierarchical folder tree in code review sidebar (#246)
Group changed files by directory with collapsible folders, aggregate +/- stats per folder, and auto-expand on keyboard navigation. Single-child directories collapse (e.g., src/utils/ as one node). Falls back to flat list for single-directory diffs. Closes #161 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |