mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
feat/opencode2-native-commands
36 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 |
||
|
|
eb34960da9 |
fix(review): remember the last-used panel view; full-width toggle and cleaner panel chrome (#1273)
* fix(review): remember the last-used panel view across sessions
- Add cookie-only reviewPanelViewLastUsed config (sections | tree, never
commits); the header toggle records it and new sessions resolve
session state, then last-used, then the persisted reviewPanelView.
Explicit Settings/setup-dialog writes through the reviewView setters
sync last-used so a stale memo cannot shadow them.
- Wire the FileTree fallback PanelViewToggle to the real panel-view
selection and route Tree clicks through the shared select funnel
instead of rendering a hardcoded, no-op tree segment.
- Mark the review-setup-seen cookie when the first-run reset runs, not
only on the Got it click, so closing the tab without dismissing no
longer re-stomps preferences every session.
* fix(review): full-width panel toggle, relocated tree controls, click-to-copy diff count
- Give the Git status | Tree | Commits toggle the entire top row in all
three left panels; segments stretch to fill the row width.
- Move the controls that shared that row (staged count, search,
collapse-all, hide-viewed, viewed counter) into a shared
PanelControlsRow rendered below the All files entry, directly above
the file list; the Commits panel's loaded-commit count moves to its
own sub-row.
- Replace the footer Copy button with the diff count itself as the copy
trigger (shared CopyDiffFooter): a keyboard-reachable button with the
"click to copy the entire diff set" tooltip and transient
Copied/Failed feedback in the old button's slot.
- Add a tooltip to the file-row stage button using the same Tooltip
mechanism as the viewed control ("Stage file (git add)").
* fix(review): put Tree first in the panel view toggle
* fix(review): resolve effective panel view for the toggle; keep self-heal off the last-used memo
- Add resolvePanelView() and pass the EFFECTIVE view to FileTree so a
latent sections/commits selection the session cannot offer highlights
Tree instead of a hidden or wrong segment; allFilesOrder reuses it.
Unit test covers the no-segment and wrong-segment cases.
- setReviewPanelView() gains { recordLastUsed?: boolean } (default
true); the App self-heal passes false so repairing a conflicted
persisted pair no longer overwrites the user's last-used memo.
- Update the AGENTS.md since-base section to the new toggle semantics
(last-used memo, resolution order, setter sync, one-time first-run).
- Hardening: fold the diff counts into the copy button's aria-label,
skip the tooltip while the copy button is disabled (hover never fires
on a disabled native button in several browsers), let toggle segments
shrink (min-w-0 + truncate), and rework the config test onto a fresh
ConfigStoreForTest instead of resolving the singleton against a
throwaway backend.
|
||
|
|
caf7ce1ccd | feat(review): install Call Flow automatically in the background on opt-in (#1271) | ||
|
|
3245310aa8 |
feat(review): add optional CallDiff call-flow analysis (#1268)
* feat(review): add optional CallDiff call-flow analysis * fix(review): harden CallDiff integration |
||
|
|
156761a3fa |
feat(review): author suggestions by editing code in place (experimental, flag-gated) (#1193)
* feat(ui): add experimental editSuggestions setting (cookie-only, default off) Registers the flag in the settings registry and surfaces it under a new Experimental section of the review Display tab. Not synced to server config while experimental; when off, no edit UI renders anywhere. * feat(review): edit-to-suggestion core (adapter wall, derivation, session controller) pierreEditAdapter.ts is the single module allowed to reference @pierre/diffs/edit: the editor chunk loads via dynamic import on first use, types are derived structurally, and the EditProvider factory declines attaches until the module is ready (CodeView retries). deriveSuggestions.ts diffs the session's final contents against the pre-edit new-side content with the diff package and emits one minimal hunk per contiguous changed region, expanding pure inserts/deletes to include an unchanged anchor line so originalCode and suggestedCode stay non-empty. CRLF input is normalized. cloneDiff.ts deep-clones FileDiffMetadata before a session starts; Pierre's editor mutates it in place (additionLines, hunks, editSessionDirty), and the clone is what gets republished when the session ends. useEditSession.ts owns the lifecycle: one file at a time, full-content hydration before entry (partial diffs throw in applyDocumentChange), suggestion creation on completion, and session end as ONE combined item write (edit off plus restored pristine fileDiff with a fresh cacheKey), which is upstream's documented commit pattern and avoids racing CodeView's teardown re-render. persistState is deliberately unused (upstream bug, open PR #1048). Existing annotations are projected to editor severity markers best-effort via onAttach. * feat(review): wire edit-to-suggestion into the plain all-files view (flag-gated) The plain all-files dock panel is the only surface that opts in; Guided Review deliberately stays off because its viewport manager evicts CodeViews beyond ~8 mounted, which would destroy an active editor session. When the flag is off the surface renders byte-identical to before: no EditProvider, no edit props, no header buttons. FileHeader gains the Edit entry button (disabled with a tooltip when content cannot be fetched) and the in-session Editing badge with Suggest/Discard controls. Slot portals republish before React commits state, so header rendering reads the session refs, not state. AllFilesCodeView routes every collapse path through finishIfEditing so a collapse ends the session on our one code path, blocks the annotation toolbar and augmentation applies for the file being edited, and drops session state on fileSetKey remounts (diff switches are user-initiated; in-progress edits are discarded, documented v1 behavior). App.tsx converts derived hunks into ordinary suggestion annotations (type comment with suggestedCode/originalCode, side new, anchored to new-side line numbers), the same shape SuggestionModal produces, so rendering, sidebar, drafts, and feedback export work unchanged. The browser never writes files; the agent applies suggestions. * test(review): cover edit-to-suggestion derivation, clone invariant, adapter wall, and flag-off UI deriveSuggestions: no-op edit, single and multi region edits, whole-line add and delete with anchor expansion, insertion at file start, file emptied, CRLF vs LF, trailing newline. cloneDiff: pristine clone stays byte-identical after the live object is mutated the way Pierre's edit session mutates it. adapterWall: only pierreEditAdapter.ts may reference @pierre/diffs/edit, and only via import type or dynamic import. FileHeader DOM tests (DOM_TESTS=1): zero edit UI without the feature, entry button, disabled tooltip, and the Suggest/Discard session controls; registered in test.yml's DOM step. * fix(review): resolve anchor collisions in suggestion derivation deriveSuggestionHunks could emit overlapping hunks when adjacent regions claimed the same anchor line (a file-start region expanding forward into a line the next region's backward expansion also claimed). An agent applying the suggestions against original line numbers would silently drop one or duplicate code. Regions are now emitted left to right with collision-aware anchoring: backward expansion is preferred but only when the preceding line is not already claimed by the previous hunk; otherwise the region anchors forward (always an unchanged line, and later regions see it as claimed); when both directions are unavailable (previous hunk adjacent and region runs to end of file) the region merges into the previous hunk as one spanning suggestion. A defensive fold enforces the non-overlap invariant (lineStart > previous lineEnd) as a safety net. Tests: unit cases for both reproduced collision shapes plus adjacent regions at file start and end, and a 5000-case seeded fuzz asserting zero overlaps, exact originalCode anchors, and full reconstruction of the edited text under a sequential offset-tracking applier. A 20,000-case run of the same generator reports zero overlaps, zero anchor mismatches, and zero round-trip failures. * feat(review): export originalCode as a Replaces block in feedback Suggestions carried originalCode internally but the export never emitted it, so the applying agent had no way to validate an anchor before applying. Each suggestion now exports a fenced Replaces block (the exact current lines being swapped out) ahead of the existing Suggested code block, for both line and file scoped annotations. SuggestionModal-authored and edit-session-derived suggestions flow through the same single format. A deletion-only suggestion (no suggestedCode) still emits its Replaces block so the anchor stays verifiable. Tests assert the block pairing per annotation, the deletion-only case, and that annotations without originalCode are unchanged. * fix(review): never silently discard a dirty edit session on file-set change fileSetKey changes on sort-order and collapse-default flips as well as diff switches and refreshes, and the remount tears the editor down without a completion callback. A dirty session was dropped silently there, destroying in-progress work, while the file-switch path prompted. The session now keeps the FileContents delivered with each change event (its contents getter is lazy and stays readable after editor cleanup), and handleFileSetChange recovers the last-known document text, derives the suggestions, and prompts to keep them, matching the dirty file-switch pattern. The call site is a post-commit effect, so the synchronous confirm is safe. Declining discards; a clean session or a no-op edit still drops without a prompt. New DOM-gated test (registered in test.yml's DOM step) covers the confirm, decline, clean, and no-op paths through the real hook, plus non-DOM units for the pure recovery helper. * fix(review): give editor markers real range width so they render Single-line annotation markers were projected as collapsed ranges (start and end both at character 0 of the same line); upstream's overlay renderer skips zero-width blocks, so markers never painted. Marker ranges now end at character 0 of the line after the last annotated line (exclusive-end LSP form), covering every annotated line in full; the editor's TextDocument clamps a past-the-end position back to end-of-document. Verified via CDP against a live review server: the severity squiggle renders across the annotated line during an active edit session and the hover popover shows the annotation source and message. Also corrects overstating comments: the flag-off claim that the editor module is never imported (the single-file build inlines dynamic imports, so the namespace exists at page load but stays inert) and the createEditor decline-and-retry framing (the React wrapper throws on an undefined return; the controller awaits the chunk before any item enters edit mode, and onAttach delivers once per editor rather than re-firing across virtualization re-attaches). * polish: drop suggestion card left accent, move Edit to far right of file header Suggestion cards keep the colored SUGGESTION header as their sole identity marker; the green left border on the card body is removed for all suggestion cards (inline and sidebar, including modal-authored ones). The Edit entry button moves to the far right of the file header action row, after the Sem badge and adjacent to the file actions dropdown, so the experimental affordance stays out of the everyday Viewed/Add/Comment cluster. Responsive isVeryTight behavior is unchanged (icon-only at narrow widths). * prototype: edit-session HUD strip below the file header While an edit session is active, a slim strip renders directly below the file header (inside the file card, above the content) carrying the session controls and state: Suggest, Discard, a debounced net change count (for example 3 changes), and the experimental label. The header no longer renders in-session controls; the Edit entry button still lives there when no session is active. The HUD rides Pierre's memoized custom-header slot portal, so the change count is delivered through a small external store (useSyncExternalStore) that useEditSession updates on a 250ms debounce per change event; slot height re-measures via the version-bumped updateItem that session start and end already perform. Prototype for design review; revert this commit to drop the HUD. * fix(review): pierce shadow DOM in file-nav keyboard guards Window-level keydown handlers in SectionsPanel and FileTree guarded only against e.target being an input or textarea. Events from inside the Pierre editor's shadow DOM retarget to the host element, so Home/End/arrow keys typed during an edit session fell through to file navigation and switched the visible file mid-edit. Use composedPath()[0] and isContentEditable, matching the guard AllFilesCodeView already uses. * feat(review): Make annotation selection action in edit sessions Selecting text in a Pierre edit session now shows the editor's Selection Action popover with a single Make annotation button (plain DOM, inline styles, colors via inherited theme tokens so light/dark and theme switches work through the shadow boundary). Clicking it snapshots the selection and opens the app's existing CommentPopover anchored at the button rect; the submitted comment becomes a normal line-scoped CodeAnnotation. Anchoring: the selection lives in the edited buffer, but annotations anchor to the rendered diff's new side, which is the session's pre-edit content. mapEditedRangeToPristine (edit/selectionAnchor.ts) maps the selected line range through diffLines(preEdit, edited) at click time. Unedited regions map exactly; ranges overlapping session edits anchor to the pristine lines those edits replace and are flagged approximate; pure insertions anchor to the adjacent pristine line. Pre-edit coordinates are session-invariant, so the anchor stays correct whether the session completes or is discarded. The captured selection text is stored on the annotation (selectedText, plus selectedTextFromEdits when approximate) and exported in feedback as a Highlighted text block with an honesty note for approximate anchors. New comments are re-projected into editor markers mid-session, and the editor selection collapses after submit so the popover does not reopen over the annotated lines. Entry UX note: the comment entry deliberately lives outside the editor's popover. Focusing an input inside it would blur the editor, collapse the selection, and tear the popover down mid-typing, so the popover only snapshots and hands off to the app-side CommentPopover. Tests: anchor-mapping unit tests (exact mapping through shifts, edited and inserted regions, deletions inside a selection, clamping, CRLF), export coverage for the Highlighted text block, and a DOM-gated popover builder test registered in the CI DOM step. Adapter wall unchanged and passing. * refactor(review): rename Display settings tab to Editor and move edit toggle to top * refactor(review): do not project annotations as editor markers Wavy underlines read as errors, which misrepresents comments. Annotations render in their normal slots below the code instead. --------- Co-authored-by: Michael Ramos <ramos@plannotator.ai> |
||
|
|
fa294b12e4 |
feat(review): add PR and MR artifact gallery (#1055)
Adds a first-class artifact gallery and annotation workflow for GitHub pull requests and GitLab merge requests, including images, GIFs, video timestamps, rendered HTML, Markdown, provenance-aware feedback, authenticated provider resources, and reliable CDN/media handling. |
||
|
|
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 |
||
|
|
e8df06db7c |
feat(review): Commits panel — linear history rail with per-commit diffs (#994)
* docs(adr): spec for the commit-list review view (linear history rail)
* feat(review): commit:<sha> diff mode along the shared diff-type seams
A new git-only diff family for reviewing one historical commit against
its first parent (git-show style), threaded through every seam a diff
type crosses:
- runGitDiff: git diff <sha>^ <sha>; a root commit diffs against the
empty tree. Label is 'Commit <shortsha> — <subject>'. The sha is
validated as bare hex (parseCommitDiffType) before it reaches any
argv position, on top of --end-of-options.
- getGitDiffFingerprint: anchored to the sha alone (present/gone), NOT
headSha — new commits landing mid-review don't change this diff and
must not raise the staleness banner. A vanished commit (rebase + gc)
flips present→gone and fires it honestly.
- getFileContentsForDiff: old side <sha>^ (null on root), new side <sha>.
- parseWorktreeDiffType learns worktree:<path>:commit:<sha> (the
sub-type contains a colon, so it needs its own split) — commit review
composes with worktree sessions like every other mode.
- vcs-core: the git provider owns commit:*; staging stays gated off
(canStageFiles allowlist unchanged — nothing in a historical commit
is stageable).
- Ask AI context: a commit-mode inspect string ('git show <sha>', with
an explicit 'historical commit, not the working tree' note).
Also adds listCommitHistory for the upcoming /api/commits endpoint:
one --first-parent page from HEAD (before=<sha> continues at its first
parent, +1 fetch for an honest hasMore), with isHead / isRepoUser /
isPastBase flags. isPastBase is reachability from the base — computed
as the complement of 'rev-list --first-parent HEAD ^base', which is a
prefix of the walk, so the client's single divider is exhaustive. An
unresolvable base degrades to no divider (matches since-base's posture
on such repos).
* feat(review): GET /api/commits — linear history endpoint (Bun + Pi)
One page of the branch's --first-parent history (?limit=&before=),
served by listCommitHistory against the active diff's cwd (worktree-
aware via resolveVcsCwd) and the active base (so the divider tracks
the same baseline the review compares against, including the startup
origin/<default> upgrade).
Gated to plain local git sessions — PR / workspace / jj / p4 return
400, mirroring the client's commitsCapable gate. Both runtimes; the
route-parity test covers the pair.
commit:<sha> switching needs no endpoint changes: /api/diff/switch
already dispatches through runVcsDiff (the git provider owns the new
family), the epoch guard applies as-is, the sections sidecar correctly
stays since-base-only, staging 400s via the canStageFiles allowlist,
and baseRelevantDiffType keeps the behind-GitHub banner suppressed for
commit diffs.
* feat(ui): Commits panel view — the linear history rail
Third left-panel view: 'Git status | Commits | Tree'. The Commits view
is a pure commit list (short sha, subject, relative age compacted to
'2h', author only when it isn't the repo user, HEAD dot, a divider
where the branch meets the resolved base, 'Show more' paging). It
never becomes a file list: clicking a commit switches the diff to
commit:<sha> and the existing needsInitialDiffPanel flow lands the
center dock on the all-files surface; re-clicking the active commit
just re-focuses that panel.
Persistence: reviewPanelView gains 'commits' with NO diff coupling —
the view persists, the diff opens on the user's normal default until a
commit is clicked, and a sha is never persisted (it may not survive a
rebase). The sections⟺since-base coupling is untouched, but the
classic-diff→Tree snap in Settings/ReviewSetupDialog now only fires
when the current view is 'sections', so picking a default diff no
longer stomps a Commits preference. The load-time self-heal ignores
'commits' by construction (it keys on panelView === 'sections').
Gates: commitsCapable = plain local git session (no PR / workspace /
jj / p4, matching the server's /api/commits gate); the segment is
hidden elsewhere. Staging is inert in commit diffs on both ends
(STAGEABLE_DIFF_TYPES and the server allowlist never match commit:*).
Worktree sessions compose — the client parses
worktree:<path>:commit:<sha> and handleDiffSwitch prefixes as usual;
the rail refetches on worktree/base changes but deliberately not on
commit clicks, so paging state survives selection.
Accepted edges (v1): no j/k selection in the rail (each move would run
a full diff switch); toggling Commits→Tree keeps a commit diff active,
where the tree's diff-type dropdown marks nothing — the gitRef header
still names the commit.
* fix(review): self-review cleanups on the commit-list feature
- listCommitHistory: a repo with no commits yet (unborn HEAD) returns
an empty page instead of null — the panel showed a 500-backed 'Retry'
error where 'No commits' is the truth. Every other review surface
already degrades gracefully on a commit-less repo; now this one does.
Only a genuinely unanswerable repo (not a repo at all) stays null.
- Hoist the bare-hex sha rule into one BARE_HEX_SHA_RE shared by
parseCommitDiffType and the before-cursor validation (was written
twice).
- The 'all' diff case now uses the getEmptyTreeSha helper the commit
mode extracted, instead of keeping its own inline copy.
- Drop CommitsPanel's isLoadingDiff prop — declared and passed but
never used.
* feat(ui): commit cards, labeled groups, avatars — review round 1
Four items from the live review round:
- Toggle: Commits moves to the far right (Git status | Tree | Commits)
and the segment text goes 10px → 12px (padding trimmed to keep three
segments inside the 256px header).
- The bare '── origin/main ──' divider confused more than it explained.
Replaced with two labeled groups: 'On this branch' (commits not yet
reachable from the base) and 'In <base>' (shared history), each with
a tooltip spelling out what the boundary means.
- Rows become overview cards: subject (2-line clamp) on top; a meta row
with author avatar, author name (only when it isn't the repo user),
a HEAD badge, short sha, and compact age. Active card gets the
primary border/tint.
- Author avatars, reusing the PR machinery rather than reinventing it:
parseRemoteUrl/parseRemoteHost classify the origin forge; the gh/glab
invocation shape (incl. the --hostname self-hosted convention) and
GitLab's relative-avatar absolutization rule are mirrored from
pr-github/pr-gitlab. What could NOT be reused directly: PR avatars
are keyed by platform login, which local commits don't have — so
GitHub resolves via one repos/{o}/{r}/commits?per_page=100 call per
session building an author-EMAIL → avatar map (unpushed commits by
the same author resolve through it; verified live), and GitLab uses
its per-email /avatar endpoint (capped, deduped). Everything is
memoized per session and fails closed to the initials fallback — a
repo with no remote, an opaque self-hosted host, or an unauthenticated
CLI never delays or errors the commits endpoint.
The Avatar component moves out of PRCommentsTab into its own module,
now shared by the PR timeline and the commit cards. New shared module
commit-avatars.ts is vendored to Pi (vendor.sh) and the enrichment runs
in both runtimes. CommitListEntry gains authorEmail (resolver key) and
avatarUrl (server-enriched).
* ui(review): flush commit rows, prominent base boundary — review round 2
- Cards are gone: rows stack flush (no borders, no margins), hover tint
only. Subject is strictly one line, ellipsized (no clamp/wrap).
- Author name always shown (was: only when it differed from the repo
user), with the avatar, on the compact meta line under the subject.
- The base boundary is a prominent labeled rule ('— In origin/main —',
foreground-weight lines) instead of the faint border under a group
header; the 'On this branch' header above stays quiet.
* docs(adr): evaluate local stack-parent detection — scenario matrix, no build decision
Empirical stress-test of the merge-base-reachability algorithm across 9
scenarios plus this repo itself. Findings: the stacked-ness test is graph
truth (never lied); parent attribution has one content-harmless failure
(same-merge-base sibling labels) and one dangerous, provably graph-
undetectable one (branches pointing inside B's own history win and
silently hide the user's commits). Determination: only viable as a
suggest-and-confirm affordance with per-branch persisted choice — never
silent auto-defaulting. Decision to build deliberately left open.
* fix(ui): Commits view is session-only — never the opening view
Live-review catch: the panel toggle inherited the sections/tree
'last choice becomes the default' cookie write, so clicking Commits in
one session made the NEXT review open on the history rail instead of
the diff. Ruling: a review always opens on the persisted sections/tree
default; Commits is entered via the toggle each session.
- reviewPanelView narrows back to 'sections' | 'tree'; a stale
'commits' cookie is treated as unset (self-heals to the default).
- The live view is now a session overlay in App state: selecting
Commits flips it without touching config; selecting Git status/Tree
clears it and persists as before. Single choke point in
selectPanelView, so every toggle site inherits the rule.
- Settings loses the Commits segment (it configures the OPENING view,
which Commits can no longer be). Spec updated with the revision.
* ui(review): the panel toggle is session-only — it never writes the default
Second live-review ruling on persistence: the header toggle shouldn't
persist ANY choice, not just Commits. Looking at another view mid-review
must not silently change what the next review opens on.
- selectPanelView becomes a pure session override (local state layered
over the persisted value); no configStore writes from the toggle path.
- handleSwitchToSections drops its defaultDiffType write — that write
only existed to keep the persisted view/diff pair consistent, and the
toggle no longer persists a view. Settings and the setup dialog remain
the only reviewPanelView/defaultDiffType writers and keep enforcing
the sections ⟺ since-base coupling; the load-time self-heal still
repairs conflicted pairs those writers may leave.
- Spec + settings-registry comments updated to name the new rule.
* feat(ui): commit description card + collapsed all-files for commit diffs
Clicking a commit now answers 'what and why' before 'which lines':
- New commitInfo sidecar (Bun + Pi, same mode-conditional shape as
sections): when a commit:<sha> diff is active, /api/diff and
/api/diff/switch carry the commit's full metadata — subject, multiline
body, author (+ avatar via the session resolver), sha, age — from a
single 'git show -s' (getCommitDiffInfo in review-core, body as the
trailing format field so newlines survive).
- CommitDescriptionHeader heads the all-files surface: subject, author
row, and the full body rendered as markdown via the same MarkdownBody
the PR viewer uses. Long bodies scroll inside the card (max-h) so the
diff keeps the viewport.
- Commit diffs open FOLDED: AllFilesCodeView gains defaultCollapsed —
items seed collapsed at build time, the seed is part of fileSetKey
(CodeView seeds once per instance, so a seed change must remount),
and the collapse-all toggle state initializes to match. Every other
diff mode keeps opening expanded; leaving the commit diff clears the
card and the folded default together (the sidecar is absent).
Verified live through the compiled binary: switching to a real commit
returns subject/body/avatar, switching back to since-base clears it.
* ui(review): commit description scrolls with the diff — no pinned card
Round feedback: the fixed header with its own inner scrollbar felt
embedded; the description should read as the top of the document and
scroll away with the files.
- AllFilesCodeView gains leadingContent: rendered via a portal INTO
CodeView's scroll container (absolutely positioned at content top,
so it participates in the scrollable overflow), with its measured
height fed into layout.paddingTop so the virtualized items start
below it. CodeView stays the single scroll authority — no nested
scrollers, no wrapper flex row.
- CommitDescriptionHeader shows the full body inline (no inner
OverlayScrollArea). Only genuinely huge bodies (>24 lines / >1800
chars) get a CSS max-height clamp with a fade mask and a Show
more/Show less toggle — CSS clamping keeps the markdown intact, and
the ResizeObserver feeds the expanded height back into paddingTop.
Keyed by sha so the toggle resets per commit.
* ui(review): entering the Commits view auto-opens the HEAD commit
Toggling to Commits used to leave the previous mode's all-files diff on
screen — a rail full of commits next to content that belonged to none
of them. Now entering the view selects the HEAD commit (top of the
rail) so the center immediately shows its diff + description card.
Once per entry, ref-guarded: an already-active commit diff survives a
toggle round-trip, a user click supersedes it, and a failed switch
doesn't retry-loop. When entry races the first log fetch, the effect
fires as soon as the log lands.
* perf/ui(review): fast commit navigation — instant veil + skip context recompute
Rail clicks felt laggy and jumpy: the old diff sat on screen for the
whole switch round-trip and then snapped, and the switch itself was
doing a full getVcsContext recompute (branch + worktree + recent-commit
enumeration — three git walks) that a commit click can never invalidate.
- Same-cwd commit:<sha> switches skip the context recompute in both
runtimes (the client keeps its existing context when the field is
absent — long-standing contract). Measured through the compiled
binary: commit switches now ~40-60ms server-side.
- The center dock shows an immediate 'Loading commit…' veil whenever a
commit switch is in flight (or the Commits view was just entered and
HEAD auto-select hasn't landed) — the stale previous diff never shows
and the click reads as instant. Errors surface through the normal
empty-state, never trapped under the veil.
* fix(ui): PR-994 review round — toggle/settings separation + commit-nav fixes (App)
The headline fix: the load-time settings repair guarded on the live
panelView, which now carries the session toggle override — so clicking
'Git status' once mid-review (saved prefs tree + a classic diff) hit
the healer and silently persisted defaultDiffType='since-base'. The
toggle must never be a settings writer, directly or through a repair
path. Keyed to persistedPanelView now; audited the remaining
configStore.set call sites — no toggle path can reach a write.
Also in this file, same review round:
- Commit-navigation veil gains a real predicate: it drops on a commit-log
fetch error (rail shows Retry; center was stuck under the spinner
forever) AND on a genuinely empty history (zero commits — the second
stuck-veil trigger the automated review missed), while still covering
the log-loading and pre-auto-select frames.
- handleSelectCommit composes the worktree prefix once and calls
fetchDiffSwitch directly (the equality check and the switch previously
used two different composition paths).
- commitsCapable/showCommitsPanel hoisted above the global keyboard
handler; Cmd+F is no longer intercepted in the Commits view (no search
input exists there — the browser find takes over instead of a silent
no-op).
* fix(review): PR-994 review round — commit-log staleness, loading flags, avatar cap
- useCommitLog clears its cached list when the history context changes
(worktree/base switched while the view was away): the HEAD auto-select
acted on the stale rows and opened the previous context's commit. Same
contextKey still keeps the cache (no empty flash on re-entry).
- The hook's cleanup now resets both loading flags: a generation-skipped
finally never cleared them, leaving 'Show more' stuck disabled as
'Loading…' after leaving mid-page.
- commit-avatars memoizes misses only for emails an attempt actually
covered — the GitLab per-call cap (10) was recording every email past
it as permanently unresolvable for the session. Regression test added.
- GitLab host detection tightened from a bare substring to a structured
match (gitlab.com / gitlab.* / *.gitlab.*) — 'mygitlabproxy.example.com'
no longer classifies.
- Collapse-all mirror re-derives from live item state after per-file
toggles: with commit diffs seeding all-collapsed, expanding one file
left the dock button on 'Expand all', and clicking it re-collapsed the
file the user just opened.
- The commit description card element is memoized on commitInfo identity
so the measuring ResizeObserver stops churning on every context render.
* fix(ui): entering the Commits view ends any open search session (self-review)
The Cmd+F guard from the review round only blocked NEW searches in the
Commits view; a search opened in Git status/Tree survived the toggle as
hidden-but-live state — the query kept matching against the commit
diff, marks kept rendering in the all-files body, Enter/F3 kept
stepping matches, and the input to see or edit the query didn't exist
anywhere on screen. handlePanelViewSelect (the single choke point every
toggle site routes through) now clears and closes search on entry to
Commits.
Also re-traced the rest of the round under self-review — veil terminal
states (log error/empty/loaded, switch failure, background refresh with
a commit active), the hook's cleanup-before-rerun flag ordering, the
avatar attempted-set on broken platforms, and both collapse-mirror
paths — no further findings.
* fix(ui): PR-994 round 2 — live rail freshness, non-destructive errors
- The rail now keeps itself fresh: while the Commits view is visible, a
quiet 10s poll head-compares page 1 and adopts it only when history
actually moved. The commit DIFF's sha-anchored fingerprint stays as
designed (a historical commit never goes stale, so the banner
correctly stays quiet) — but the rail no longer freezes while an
agent commits in the background. Adoption bumps the fetch generation
so an in-flight 'Show more' from the old history can't append stale
rows, and transient poll failures disturb nothing.
- A page-1 fetch resets isLoadingMore: a refresh superseding an
in-flight paging request skipped that request's generation-guarded
finally, leaving 'Show more' stuck on 'Loading…' — rare before, a
live race once the poll exists.
- Errors are non-destructive when a list is on screen: a failed page or
background refresh renders as an inline row with Retry under the
list instead of replacing the whole rail; the full-panel error state
is reserved for an empty rail.
* perf/refactor(review): parallel GitLab avatar lookups; drop dead isRepoUser
- The ≤10 per-call GitLab avatar lookups run in parallel instead of
sequentially — they sit on /api/commits' critical path, and serial
subprocess spawns added seconds to the rail's first paint on
multi-author GitLab histories (PR-994 round 2 nit).
- CommitListEntry.isRepoUser removed: v1 showed the author only when it
wasn't the repo user; the live-review pivot to always-shown names left
the field computed (one git config subprocess per page), serialized,
and tested but never read, with a doc comment describing behavior the
panel no longer has. Spec updated to match the revised row design.
* fix(ui): commit-poll adoption clears all superseded fetch state (self-review)
Adopting a new history from the background poll bumps the generation,
which strands any in-flight fetch's generation-guarded finally — the
same stale-flag class just fixed for isLoadingMore, but for isLoading
(slow first load overtaken by the poll) — and a lingering inline error
from the replaced history would otherwise sit under the fresh list.
Adoption now clears isLoading, isLoadingMore, and error together.
* fix(ui)/docs: PR-994 round 3 — worktree switches drop commit diffs; API docs
- handleWorktreeSwitch treats commit:<sha> as non-portable: every other
diff mode recomputes meaningfully against the target worktree, but a
commit diff is context-bound content (worktrees share one object
database — the reviewer's claimed git error doesn't exist, verified
empirically), so 'preserving' it just re-rendered the OLD context's
commit byte-for-byte. It now falls back to the session default (same
option-availability rule resolveInitialDiffType applies). Swept every
other activeDiffBase/diffType carrier: the whitespace toggle,
staleness refresh, and fetch-base deliberately recompute the SAME
diff (valid for commits); handleBaseSelect and the base-picker row
already exclude commit mode; job diff-context labeling and the
guarded diff-type dropdown are display-only. This was the single leak.
- AGENTS.md (CLAUDE.md symlink): /api/commits row added to the Review
Server table; /api/diff and /api/diff/switch now document the
commitInfo sidecar and the commit:<sha> diffType family; the
since-main section describes the three-segment session-only toggle
and the never-persisted Commits view.
* docs: normalize arrow glyphs in the /api/commits table row (self-review)
* fix/refactor(review): PR-994 round 4 — rebase-safe paging, locale-proof ages, shared parsing
- Pagination is rebase-safe: a 'Show more' cursor from a history that was
rewritten mid-session (rebase/force-push) still resolves in the object
store but is no longer on the branch — paging from it walked the
orphaned pre-rewrite chain for the ≤10s window before the freshness
poll adopts the new history. listCommitHistory now ancestor-checks the
cursor (merge-base --is-ancestor) and returns an empty terminal page
for orphaned AND vanished cursors alike; the poll replaces the list
moments later. Regression test rewrites history between pages.
- Ages are locale-proof: git localizes %cr via gettext ('vor 2 Stunden'),
which the English-only compactAge regex silently couldn't shorten.
CommitListEntry/CommitDiffInfo now carry committedAt (epoch ms, %ct);
the rail and description card format it with the existing
formatRelativeTime — compactAge and the third formatter variant are
gone.
- The %x1f over-split repair (fixed head/tail fields, rejoined free-text
middle) was triplicated across listRecentCommits, listCommitHistory,
and getCommitDiffInfo; one splitCommitFormatFields helper (head/tail
counts, tail=0 covers the multiline-body shape) owns the edge case.
- Avatar resets its broken-image state when src changes — latent today
(every caller keys by identity), but it's a shared component and must
be safe for callers that update src in place.
Left alone from this round: hashString/hashFingerprintPart duplication is
pre-existing, and the suggested cross-package import would drag node:path
into the browser bundle — the documented reason client mirrors exist.
* fix(review): PR-994 round 5 — merge-commit prompts, boundary-aware poll, auto-select guard
- Agent prompts for commit:<sha> diffs instruct the exact on-screen
command: git diff <sha>^ <sha> (first parent), explicitly steering
away from git show — whose combined-diff presentation for a MERGE
commit renders a different (often empty) changeset than the one under
review. Root-commit fallback noted. One site covers Ask AI and every
launched reviewer (and Pi via vendoring); prompt test pins the command.
- The rail's freshness poll now adopts on boundary/base movement, not
just a new head: an agent running git fetch advances origin/<base>
while HEAD stays put, which re-partitions isPastBase — previously the
head-only compare skipped adoption and the 'In origin/main' divider
stayed stale until the view was reopened. Boundary is compared over
the page-1 overlap window (a divider paged deeper than the probe can
see re-syncs on the next full reload — accepted micro-edge). The
reviewer's proposed trigger (the Fetch banner) is unreachable in
commit mode; the external-fetch trigger is the real one.
- The HEAD auto-select never fires while any diff switch is in flight —
hardening only: the claimed first-click overwrite isn't reachable
(isLoadingDiff was never a dependency of that effect, and auto-select
settles before a human can click), but the guard makes the invariant
explicit.
* docs(adr): spec the pre-merge commits-view structure refactor
Three behavior-preserving moves scoped for PR #994 before merge, motivated
by the audit finding that every recent review-round bug lived at the
App.tsx <-> useCommitLog seam: (R1) unify the commits-session state
machine (poll, auto-select, veil) in one hook; (R2) lift the commit-rail
block out of the 1.6k-line review-core into commit-history.ts; (R3) share
the isSameCwdCommitSwitch predicate both runtimes inlined. Ownership
tables name what deliberately stays put; execution runs smallest-first
with full gates per commit.
* refactor(review): share isSameCwdCommitSwitch across runtimes (spec R3)
The ~10-line parse-and-compare predicate both /api/diff/switch handlers
inlined moves to review-core beside its inputs (parseWorktreeDiffType /
parseCommitDiffType — the canonical home for diff-type string logic);
both call sites collapse to one line. Unit tests cover plain, worktree,
cross-worktree, and non-commit-target cases. Behavior identical.
* refactor(shared): lift the commit-rail block into commit-history.ts (spec R2)
review-core.ts (1.6k lines) was absorbing the ~230-line Commits-panel
data layer it doesn't need to own: CommitListEntry/CommitHistoryPage/
listCommitHistory and CommitDiffInfo/getCommitDiffInfo move verbatim to
a new commit-history.ts, matching the package's per-concept split
(jj-core, pr-stack, commit-avatars). The commit:<sha> DIFF plumbing
(parseCommitDiffType, the runGitDiff/fingerprint/file-contents cases)
stays in review-core with the other diff types — it participates in the
dispatch; the rail does not.
Wiring: review-core exports its three parsing primitives (COMMIT_FIELD_
SEP, splitCommitFormatFields, BARE_HEX_SHA_RE — listRecentCommits still
uses them, so they can't move); package exports + vendor.sh gain the
module (vendored siblings import relatively, same as pr-provider →
pr-github); types.ts re-exports split by source; both runtimes import
from the new module. History/metadata test suites move to
commit-history.test.ts with the package-style per-file git harness;
commit-DIFF tests stay in review-core.test.ts. Behavior identical —
verified live through the compiled binary (/api/commits + commit
switch + commitInfo sidecar).
* refactor(ui): useCommitsView owns the whole commits-session machine (spec R1)
The list cache, freshness poll, HEAD auto-select, and center-dock veil
were split between useCommitLog and App.tsx — and every sync bug found
across three review rounds (stuck flags, stale auto-select, adoption
races) lived at exactly that seam. The auto-select effect and the veil
derivation move into the hook verbatim (renamed useCommitsView, git mv),
so the machine's invariants are locally checkable in one file.
App now supplies only what it owns — visibility, the active commit,
switch state, and onOpenCommit (its handleSelectCommit: the SAME path
user clicks take, so auto- and user-selection cannot diverge by
construction) — and consumes panel props plus veilActive. Net ~45 lines
of App.tsx's most delicate logic deleted.
One equivalence note: the hook's single "enabled" (showCommitsPanel &&
origin) now gates auto-select/veil where App used bare showCommitsPanel;
the two only differ when origin is unset, which implies no gitContext
(demo mode) and therefore showCommitsPanel === false — identical in all
reachable states.
Deliberately still in App (each guards a flow App owns): capability
gating, handleSelectCommit, the Cmd+F / worktree-fallback /
staleness-refresh / search-clear guards, and render wiring. Behavior
identical; spec: adr/specs/refactor-commits-view-structure-20260703.md.
|
||
|
|
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.
|
||
|
|
e3de938914 |
feat(review): PR Overview panel + description/comment annotations + media (#981)
Combine the PR Summary/Comments/Checks tabs into one PR Overview panel, then make the description and comments annotatable and render their media. - PR Overview panel (one sidebar entry) + comment UI (avatars, filters, hide bots, live context, responsive stacking). - Annotate the PR description (select → comment) and PR comments (Annotate button), with Ask AI; notes show in the Annotations sidebar and ship to the agent. - Split/Unified diff toggle relocated into the dock tab strip. - Render images + video in descriptions and comments (raw HTML + markdown), capped to the card so nothing bleeds. - Review-flow fixes: copy-all feedback, prose-only feedback preamble, no image control on prose notes, GitHub review-body seeding; stronger review trailer. - Add Claude Sonnet 5 as the default Ask AI model. No server, endpoint, or Pi-runtime changes. |
||
|
|
affeaa07fc |
feat(review): custom reviews as Agent Skills + whole-file/general findings (#955)
Custom reviews: pick an Agent Skill and its SKILL.md body becomes the review prompt (read live from global skill folders, never copied; default review byte-identical). Agent findings can be a line, a whole file, or a general review-level comment, and are never silently dropped. Plus: agent-icon engine picker, icon-only file-header opener, semantic diff restored as its own dock panel, and a dead-code sweep. |
||
|
|
2a6fe5c457 |
Open files in external apps + code-review UX pass (#942)
Adds a server-side "open the current file in an external app" control to the code-review and annotate surfaces (split-button with host-detected apps; last-used becomes the default; cross-platform launch mirrored across the Bun and Pi runtimes), plus a code-review UX pass: file-header change letters and line counts, the diff-settings cog grouped with Split/Unified, an all-files collapse/expand-all toggle, and the semantic diff moved to a resizable sidebar accordion. Also fixes Cmd+click code navigation (worker token transformer). Hardened over three adversarial review rounds: shared, tested path-containment for open-in (Bun + Pi), annotate scoping aligned to /api/doc reference roots, strict PR-checkout rooting (never the launch repo), deduped app-catalog and semantic-diff fetches, macOS bundle-only availability, Windows launch fixes (reveal + terminal off cmd's parser), and PR-checkout tracking across switch and pool warmup. |
||
|
|
afac44b940 |
feat(review): migrate all-files code review to Pierre CodeView (#885)
Replaces the all-files renderer with Pierre's virtualized CodeView (one scroller, identical look, scales to huge diffs); deletes the legacy AllFilesDiffView/LazyFileDiff and their flag; single-file panels stay on FileDiff. @pierre/diffs pinned exact 1.2.8 (1.2.9's tree is broken on npm — see #880). Also ships: diff staleness detection ('Diff out of date · Refresh' toolbar notice backed by shared per-VCS fingerprints + GET /api/diff/fresh on both Bun and Pi servers), a stale-content guard so files edited mid-review degrade to raw-patch view instead of breaking virtualization, the Ask AI wrong-file fix (pre-existing), and toolbar/header UI polish (global settings cog, aligned sem badges, no all-files split dragger). Hardened through five review waves; tested with 1384 passing tests incl. real-git fingerprint and patch-consistency suites. Known minor gaps documented in the PR. |
||
|
|
c3ba55e889 |
feat(review): add semantic diff overview (#871)
* feat(review): add semantic diff overview * fix(review): address semantic diff review findings * fix(review): harden semantic diff fallback * fix(review): avoid repo-local sem execution * fix(review): normalize semantic diff cwd * test(review): cover semantic diff local cwd * fix(gitlab): fetch raw MR diffs * fix(review): harden sem path resolution * docs(review): add semantic diff handoff * feat(review): semantic diff cards, header sem badges, jump-to-line - Restyle the semantic panel as real bordered cards (shadcn surface) instead of ASCII box-drawing; centered column, grid-aligned entity rows. - Hide orphan (module-level) changes from the rows, matching sem's default; the count still surfaces in the summary line. - Add a 'sem · N' hover popover to each diff file header showing that file's semantic changes, reusing the same row component as the panel. - Clicking an entity (panel or popover) scrolls the diff to the lines via pierre's [data-selected-line]; centers only when off-screen so manual drag-selection isn't disturbed. - Extract shared rows/helpers into semanticDiffShared; share one cached /api/semantic-diff fetch across header badges. * feat(review): land on All files by default Semantic diff stays available via the file-tree nav entry and header badges, but it's no longer the initial landing view. * chore(review): drop dead change-symbol entries, log badge fetch failures - Remove unreachable 'moved'/'renamed' entries from the changeSymbols table (getChangeSymbol early-returns for those before the lookup). - Log a console.error once per patch when the file-header badge's semantic diff fetch fails or returns a non-ok status, so a systemic failure leaves a trace instead of every badge silently showing nothing. * feat(review): flatten semantic diff panel into grouped list - Dissolve the per-file bordered cards into flat sections: the file path is now a quiet underlined header (single hairline) with entity rows flush beneath, hierarchy from whitespace + hover tint instead of boxes. - Split the path into muted directory + emphasized filename for faster scanning. - Nudge the add/remove glyphs (⊕/⊖) ~15% larger, line-height pinned so rows don't grow; modified/rename/reorder glyphs unchanged. |
||
|
|
6db7ca9184 |
feat(review): pick a commit as the diff base (#709) (#723)
* feat(review): pick a commit as the diff base (#709) Widens the base picker for `branch` and `merge-base` diff modes from "branch only" to any commit-ish. `getGitContext` now returns the last 20 commits on HEAD; the picker shows them in a new "Recent commits" group, and SHA-like or HEAD~N queries get a "Use … as base" affordance for manual entry. Backend changes are minimal because git diff already accepts any commit-ish on the left side of the range. * feat(review): tabbed Branches/Commits in base picker (#709) Replaces the three-section list (Local / Remote / Recent commits) with a Branches | Commits tab strip — commits were below a scroll wall in repos with many branches. SHA-like and HEAD~N queries auto-focus the Commits tab so manual entry stays one keystroke away. |
||
|
|
4272312eb0 |
Add jj evolog diff mode to code review UI (#702)
* review: add jj evolog diff mode to code review UI Adds a new "Evolution diff" diff type for jj repos that shows what changed between two evolutions (amendments) of the current change — i.e. what was amended since a prior state of `@`. Changes: - Add `JjEvoLogEntry` type to `review-core.ts` and export from `shared/types.ts` - Add `getJjEvoLogEntries()` to `jj-core.ts` — runs `jj evolog` with a tab-delimited template to collect commit ID, description, and age for each historical state of `@` - Add `jj-evolog` to the `DiffType` union in `review-core.ts` and to the `JJ_DIFF_TYPES` set in `vcs-core.ts` - Wire evolog mode into `runJjDiff()` and `getJjDiffArgs()` — defaults to the second evolog entry (the most recent prior state) when no explicit base is given; propagates `error` when there's no history - Expose `jjEvologs` on `GitContext` and include the "Evolution diff" option in `diffOptions` only when two or more entries exist - Add `EvoLogPicker.tsx`: a Radix Popover component that lists evolog entries and lets the user pick which prior state to compare against; shows commit ID, description, and age with a "default" badge on the auto-selected entry - Update `FileTree.tsx` to render `EvoLogPicker` instead of `BaseBranchPicker` when `jj-evolog` is the active diff type - Update `App.tsx` to thread `jjEvologs` and `detectedEvoBase` through to the file tree, auto-set the base to the second evolog entry on mode switch, and show an appropriate empty-state message - Update `agent-review-message.ts` with an inspect hint for the evolog diff type - Add unit tests covering: diff args, evolog output parsing, error cases, and the auto-base-selection fallback path * review: fix evolog picker overflow and add evolog test sandbox Fix the EvoLogPicker trigger and popover rows to handle narrow sidebar widths — age strings now truncate instead of bleeding out of the container. Add --with-evolog flag to the JJ manual test sandbox that amends the working-copy change four times to create realistic evolution history. Also writes a create-evolog.sh helper script into the sandbox for on-demand evolog creation without the flag. * fix: restore compare target when switching away from jj-evolog When leaving Evolution diff mode, the evolog commit ID was left in selectedBase and leaked into the next diff request. For jj-line this caused jjLineBaseRevset() to treat the commit ID as a bookmark name, producing a "didn't resolve to any revisions" error. Now explicitly passes the restored default branch as baseOverride to fetchDiffSwitch when leaving evolog, avoiding the React state batching race that made the previous setSelectedBase-only approach insufficient. --------- Co-authored-by: Michael Ramos <mdramos8@gmail.com> |
||
|
|
69ef11bdfb |
feat(review): add jj review workflows (#675)
* feat(review): add jj support for local diffs * feat(review): add jj review workflows * fix(review): tighten jj diff defaults * test(review): add jj manual sandbox * fix(review): share jj agent diff prompts * fix(review): quote jj agent revsets * feat(review): share jj vcs handling with pi * fix(review): tighten jj bookmark and pi pr handling * fix(review): tighten jj defaults and detection * fix(review): harden jj diff and vcs detection --------- Co-authored-by: Michael Ramos <mdramos8@gmail.com> |
||
|
|
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. |
||
|
|
c007d1c6a2 | Use single file tree expand toggle (#633) | ||
|
|
68972aa6a6 |
feat(review): custom base branch for code review (#599) (#602)
Adds a searchable base-branch picker to the code review UI so reviewers can compare against any branch, not just the auto-detected default. Key changes: - Base-branch picker (Radix Popover + search, local/remote groups) - Diff-type picker (Radix DropdownMenu + info tooltips) - Worktree picker (Radix Popover + search) - getDefaultBranch prefers upstream ref (origin/main) for fresher diffs - listBranches populates GitContext.availableBranches - resolveBaseBranch trusts caller verbatim (no silent rewrites) - --end-of-options hardens git argv against flag-injection via base refs - Branch diff / PR Diff always available (removed hide-on-default guard) - merge-base agent prompt runs git itself instead of inlining the patch - Agent jobs snapshot diff context at launch for honest "Copy All" - Feedback export includes diff mode + base header for agent context - Round-trip base through /api/diff so refresh preserves the selection - selectedBase / committedBase split prevents file-content race - Optimistic picker reverts on switch failure - DiffViewer remounts on mode switch to invalidate stale file content - Worktree switch preserves active diff mode - Pi parity: initialBase, currentBase, agent diffContext, vendor.sh - Tooltip wide mode, TooltipProvider mounted, keyboard/touch fixes - GPT-5.5 added to Codex model picker - CLAUDE.md API table updated Closes #599 For provenance purposes, this PR was AI assisted. |
||
|
|
57495ec816 |
feat: Zed-style overlay scrollbars (#509)
* feat(ui): Zed-style overlay scrollbars for plan mode
Wide, translucent, full-length overlay scrollbars replacing the 6px
WebKit rail that users couldn't reliably grab. Click the track to
page-animate toward the click, drag the thumb, no layout shift, Firefox
parity.
Wraps plan-mode scroll containers (main viewer, annotation panel,
sidebar, settings, export modal) in a new <OverlayScrollArea> component
backed by overlayscrollbars-react. The library handles pointer capture,
click-to-jump, hover reveal, auto-hide, RTL, touch, momentum, and
cross-browser consistency. ClickScrollPlugin registered explicitly so
`clickScroll: true` actually pages (otherwise it silently no-ops).
Plumbing:
- New ScrollViewportContext + useScrollViewport() hook so descendants
(TableOfContents, PinpointOverlay, Viewer sticky observer) can reach
the real scrolling element instead of document.querySelector('main'),
which no longer returns the scroll node after wrapping.
- New useOverlayViewport() hook — canonical ref+state+callback pattern
bridging the library viewport into components that need both
imperative access and effect re-runs when the viewport mounts.
- useActiveSection gains an optional scrollElement arg so its
IntersectionObserver root re-attaches when the viewport becomes
available (ref mutations don't retrigger effects).
Theme:
- New .os-theme-plannotator tokens sourced from existing theme CSS
variables (translucent muted-foreground for handle, transparent track
at rest). 10px at rest, 14px on hover. Color + width transitions
only — deliberately does not reintroduce transform/opacity in global
transitions, preserving the
|
||
|
|
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.
|
||
|
|
ada0d5f9c5 | fix(review): expand file tree folders by default on initial render (#473) (#474) | ||
|
|
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 |
||
|
|
bcddced148 |
perf(review): debounce search highlights and O(1) active match swap (#407)
* perf(review): debounce search highlights and O(1) active match swap Split the single search highlight effect into two paths: 1. Full rebuild (debounced 100ms) — fires on query/matches/file/style changes, collapsing rapid keystrokes into one DOM update instead of one per character typed. 2. Active swap (instant) — fires when stepping between matches (Enter/Shift+Enter), changing two elements' background color via querySelector instead of tearing down and rebuilding every <mark> in the shadow DOM. Also removes augmentedDiff from the effect deps (was triggering unnecessary rebuilds on context expansion) and exports clearSearchHighlights for the immediate-clear path on Escape. Closes #405 For provenance purposes, this commit was AI assisted. * perf(review): debounce search query and pre-index searchable lines The search input was laggy on large PRs because findReviewSearchMatches re-parsed every file's patch and scanned all lines synchronously on every keystroke, blocking React from re-rendering the input value. Two complementary fixes: 1. Pre-index: Build SearchableLine[] once when files change (useMemo), with pre-lowercased normalizedText, so matching skips repeated patch parsing and per-line toLowerCase() calls. 2. Debounced query: searchQuery updates instantly (input responsive), debouncedSearchQuery trails by 200ms and drives all expensive work. During the pending window, sidebar shows "Searching…", diff clears stale highlights, match count hides, and stepping is disabled. Also: refactors findReviewSearchMatches to delegate to the new findMatchesInIndex so there is a single matching implementation, removes the now-redundant 100ms DOM debounce in DiffViewer (query debounce handles it upstream), trims query in highlightQuery to prevent whitespace-only edits from breaking snippet highlighting, and restores augmentedDiff in the highlight effect deps. For provenance purposes, this commit was AI assisted. |
||
|
|
232cae265e |
feat(review): add expand/collapse all buttons to file tree sidebar (#403)
Add two icon buttons in the file tree header that allow expanding or collapsing all directory groups at once. |
||
|
|
4b097197a0 |
feat(ai): AI backbone + inline chat for code review (#363)
* feat(ai): add provider-agnostic AI backbone for inline chat Introduces @plannotator/ai — a pluggable provider layer that powers AI features (inline chat, plan Q&A, code review assistance) across all Plannotator surfaces. - ProviderRegistry: class-based, multi-instance provider management - SessionManager: in-memory session tracking with eviction, ID aliasing - Context builders: system prompts and fork preambles for all three modes - SSE streaming endpoints: session create/fork, query, abort, list - Claude Agent SDK provider: fork from parent sessions, read-only tools, per-query abort, cached SDK import, concurrent query guard - 34 passing tests covering all layers For provenance purposes, this commit was AI assisted. * fix(ai): address code review findings from PR #363 - Concurrent query race: reject with session_busy error instead of racing; add query generation counter so stale finally blocks don't clobber newer query state - Text dropped before tool_use: mapSDKMessage now returns AIMessage[] and flushes accumulated text before each tool_use block - defaultProvider returns instance ID: ProviderRegistry.getDefault() returns { id, provider } so capabilities endpoint sends the correct key for multi-instance setups - Fork preamble re-sent on error: use _firstQuerySent boolean instead of _resolvedId to guard preamble injection - Dead abortController params: remove unused abortController from CreateSessionOptions and AIProvider.resumeSession - Unused context in memory: remove context from SessionConfig and baseConfig — already consumed to build systemPrompt/forkPreamble - Fix tool_result mapping: SDK wraps tool results in SDKUserMessage (type "user"), not a "tool_result" type For provenance purposes, this commit was AI assisted. * feat(ai): inline AI chat on code review diffs Add "Ask AI" to the code review toolbar — select lines, ask questions about agent-written code, get streaming responses. Conversation persists across file switches with inline markers on the diff showing where questions were asked. - useAIChat hook: SSE streaming, lazy session creation, multi-turn - AskAIInput: question input with per-selection AI history - InlineAIMarker: compact sparkle markers on diff lines via @pierre - AITab: sidebar conversation grouped by file with collapsible sections - SparklesIcon: shared 3-star sparkle from automations branch - ReviewPanel: AI tab with message count badge, file group flash highlight - Server: mount AI endpoints in review server with graceful fallback - Support three question scopes: line, file, and general - Toolbar ergonomics: typed text submits as AI question, no double popover For provenance purposes, this commit was AI assisted. * fix(ai): toolbar popover ergonomics and visual consistency - X button closes entire toolbar in both modes (no ghost popover) - Comment/Ask AI toggle is inline in the action row, same position in both modes (left side, mirrored layout) - AI history appears below the input, not above - File group flash highlight uses --muted to match file tree hover For provenance purposes, this commit was AI assisted. * fix(ai): visual consistency, shared primitives, markdown rendering Visual consistency: - AI tab file group headers match FileTree pattern (sizing, hover, badges) - Q&A bubbles match annotation card padding and border-radius - Empty state uses circle-bg icon (same as annotations) - General input matches search input styling - Removed redundant CSS classes, moved to Tailwind Shared primitives: - Extract formatTimestamp to shared utility (was duplicated) - CountBadge component for consistent badge styling - CopyButton component with hover-reveal + "Copied" flash Affordances: - Copy button on AI responses (hover-reveal) - Send button on general input with keyboard shortcut hint - Timestamps on Q&A pairs - Keyboard shortcut title on Ask button Markdown rendering: - Add marked dependency for full block-level markdown in AI responses - renderMarkdown utility using marked + DOMPurify - Scoped .ai-markdown CSS for headings, lists, code blocks, blockquotes For provenance purposes, this commit was AI assisted. * feat(ai): permission inheritance + inline approval UI The inline AI agent now inherits the user's existing Claude Code permission rules via settingSources: ['user', 'project']. Tools already approved in ~/.claude/settings.json auto-approve silently. For tools not covered by existing rules, the SDK emits control_request messages. These are mapped to a new AIPermissionRequestMessage type and surfaced as inline approval cards in the AI tab — showing the tool name, command/input, and Allow/Deny buttons. Backend: - Map control_request to AIPermissionRequestMessage in provider - Store Query object on session for sending control_response back - respondToPermission() method on AISession sends allow/deny - POST /api/ai/permission endpoint for frontend → backend decisions - Add settingSources to ClaudeSDKQueryOptions Frontend: - PermissionCard component with Allow/Deny buttons + resolved state - useAIChat tracks permissionRequests array - respondToPermission() POSTs decision to server - AITab renders pending permission cards inline in conversation For provenance purposes, this commit was AI assisted. * fix(ai): streaming reactivity, maxTurns, popover→sidebar linking Streaming fixes: - State updates now create new object references via prev.map() instead of mutating objects inside the array — fixes React not re-rendering the sidebar during streaming - Complete assistant text messages only used as fallback when no streaming deltas received (prevents duplicate text) - Result text used as fallback when response is empty (agent spent all turns on tools) Config: - maxTurns raised to 99 (was 3 — agent ran out of turns reading files and never got to write its answer) Popover → sidebar linking: - Clicking AI history items in comment popover now passes questionId through to handleViewAIResponse, triggering the file group flash highlight in the sidebar (same behavior as clicking inline markers) Debug logging (temporary): - Server-side: [AI STREAM] logs every message the provider yields - Client-side: [AI SSE] logs every message received in the browser For provenance purposes, this commit was AI assisted. * refactor(ai): simplification pass — types, perf, cleanup From the three-agent code review analysis: Quality: - Rename AIMessage → AIChatEntry in review-editor to avoid collision with the wire-protocol AIMessage from packages/ai - Type permissionMode as union (not string) in SessionConfig + QueryOptions - Type _activeQuery with narrow interface instead of any - Make toolUseId optional on AIToolResultMessage (SDK doesn't populate it) - Remove redundant sessionIdRef double-write in useAIChat - Fix handleAskGeneral useCallback dep: [aiChat] → [aiChat.ask] Performance: - Wrap QAPair in React.memo to prevent re-renders on sibling updates - Memoize renderMarkdown output via useMemo (avoid re-parsing on every token) - Fix auto-scroll useEffect: depend on messages.length not messages ref (prevents querySelectorAll on every streaming token) Cleanup: - Remove debug console.log from SSE endpoint and browser parser - Extract isMac/submitHint to shared utils/platform.ts - Extract generateId to shared utils/generateId.ts Net: -2 lines. No behavior changes. For provenance purposes, this commit was AI assisted. * chore: biome lint fixes — import types, button a11y, unused import - Use `import type` where only types are consumed (endpoints, CountBadge, CopyButton, renderMarkdown) - Remove unused AIContext import from session-manager - Template literal instead of string concat in context.ts truncate() - Add type="button" + aria-hidden on CopyButton SVGs For provenance purposes, this commit was AI assisted. * feat(ai): Codex SDK provider + multi-provider backend - Add CodexSDKProvider implementing AIProvider interface - Maps Codex ThreadEvents to AIMessage types - Delta tracking for streaming text from cumulative item.updated events - Supports resume, abort, sandbox modes - Factory self-registration pattern matching Claude provider - Enhance capabilities endpoint with provider models + metadata - Register both providers in review server (graceful degradation) - Add CodexSDKConfig type, models list (GPT-5.4 family) - Add reasoningEffort to CreateSessionOptions (Codex only) - Extract DiffFile interface to shared types.ts - Update CLAUDE.md with build order documentation - 54 tests passing (20+ new for Codex event mapping) For provenance purposes, this commit was AI assisted. * feat(ai): provider settings UI + inline config bar - Settings → AI tab: provider cards with per-provider model dropdowns - Inline config bar in AI chat sidebar (provider, model, reasoning effort) - Provider icons: Claude SVG + Codex PNG from marketing assets - Cookie-based persistence for provider + per-provider model preferences - useAIChat accepts providerId, model, reasoningEffort from props - Session reset on provider/model switch (next question starts fresh) - Reasoning effort selector (Codex only: Low/Medium/High/Max) - Update code review docs with AI provider documentation For provenance purposes, this commit was AI assisted. * fix(ai): review fixes — reasoning props, input blocking, cwd resolution, codex-sdk dep - Add missing selectedReasoningEffort + onReasoningEffortChange props to populated-state AIConfigBar (was only on empty-state, causing crash) - Disable GeneralInput while streaming to prevent concurrent queries - Thread cwd through CreateSessionOptions so AI sessions use the correct working directory (worktree-aware via getCwd resolver) - Add @openai/codex-sdk as a real dependency (was only an optional peer) - Eagerly verify codex-sdk importability at startup to avoid advertising a broken provider For provenance purposes, this commit was AI assisted. * fix(ai): polish config bar, input, and sidebar header - Replace native <select> dropdowns with custom popover menus that show provider icons, model names, and checkmarks on active selection - Remove disabled gear placeholder button - Auto-grow textarea upward on multi-line input (capped at 6 lines) - Push tab icons to far right in sidebar header (w-full on flex container) - Add centered divider between file-scoped and general questions For provenance purposes, this commit was AI assisted. * fix(ai): honest fork capability + permissionMode doc accuracy - Endpoint now checks capabilities.fork before calling forkSession(), falling back to createSession() for providers that can't truly fork - Codex forkSession() now throws as the interface contract requires (was silently faking a fork with no history — misleading UX) - Fixed permissionMode JSDoc: said "plan" but code defaults to "default" For provenance purposes, this commit was AI assisted. * fix(review): move search result count inside input field Count now appears inline to the left of the X button instead of squeezing the input from outside. Dynamic right padding accommodates the count + clear button when a query is active. For provenance purposes, this commit was AI assisted. * fix(ai): use claude_code preset, pass selected code to queries - Claude provider now uses { preset: "claude_code", append } instead of a raw system prompt, so inline chat inherits Claude Code's full built-in capabilities - Stripped custom role/guidelines preamble from context builders — just emit the review context (diff, plan, document) - handleAskAI now extracts selected code via extractLinesFromPatch and sends it with the query, fixing hallucinations on large PRs where the file's diff was truncated out of the system prompt For provenance purposes, this commit was AI assisted. * fix(ai): bundle both SDKs, remove peer dependencies Both @anthropic-ai/claude-agent-sdk and @openai/codex-sdk are now direct imports (no string indirection) so bun build --compile bundles them into the binary. Users don't need to install SDKs separately. Removed peer dependency declarations from packages/ai/package.json — meaningless for a compiled binary. For provenance purposes, this commit was AI assisted. * refactor: canonical platform.ts, file renames, file-level comments - Created packages/ui/utils/platform.ts as the single source for isMac, modKey, altKey, submitHint, isWindows - Consolidated inline navigator.platform checks from 6 files - Renamed renderMarkdown → renderChatMarkdown (distinct from plan editor) - Renamed formatTimestamp → formatRelativeTime (clearer purpose) - Added file-level comments to InlineAIMarker, PermissionCard, generateId For provenance purposes, this commit was AI assisted. * docs(ai): add AI features guide + link from settings New docs page at guides/ai-features covering providers, models, configuration, session behavior, permissions, and available settings. AISettingsTab now links to the docs in both empty and populated states. For provenance purposes, this commit was AI assisted. |
||
|
|
40002e69dc |
feat: GitLab merge request review support (#364)
* feat: GitLab merge request review support Add full GitLab MR review parity with existing GitHub PR review: - Auto-detect platform from URL (github.com vs any GitLab host) - Extract GitHub logic into pr-github.ts, new pr-gitlab.ts implementation - Widen PRRef/PRMetadata to discriminated unions for type safety - Dispatch functions route to correct platform implementation - Platform-aware UI labels (PR/MR, #/!, GitHub/GitLab icons) - Self-hosted GitLab support via --hostname flag - Normalize glab diff output to standard git format - Handle glab CLI differences (no --jq, Content-Type header for --input) - Defensive JSON parsing for GitLab context API responses Tested against gitlab.com with inline comments, multi-line ranges, approval, and PR context tabs (summary, comments, checks). For provenance purposes, this commit was AI assisted. * fix: correct GitLab enum mappings and add shared file path encoding - Map GitLab job statuses to UI-expected enums (failed→FAILURE, canceled→NEUTRAL) - Map GitLab detailed_merge_status to CLEAN/BLOCKED/BEHIND/DIRTY/UNKNOWN - Fix false approval state on repos without required approvers - Add shared encodeApiFilePath helper used by both GitHub and GitLab For provenance purposes, this commit was AI assisted. * fix: align panel headers and refine file tree selection style - Use shared --panel-header-h CSS variable for consistent header heights across file tree search, file header, and annotations panel - Update GitLab icon to use official tanuki SVG paths with currentColor - Replace solid primary fill on active file tree items with 30% tinted background for better readability and semantic color preservation For provenance purposes, this commit was AI assisted. |
||
|
|
0908e234e1 |
feat(review): VS Code-style diff search in file tree sidebar (#347)
* feat(review): add diff search engine and highlight utilities Search data layer for code review: patch parser, match finder, file grouping, snippet extraction, DOM highlight injection with shadow DOM traversal, and state management hook. Co-Authored-By: sercan tor <tor.sercan@gmail.com> * feat(review): wire search into file tree sidebar VS Code-style search UI in the file tree panel. Search input at the top of the sidebar replaces the tree with grouped results when active. Clicking a match navigates to the file and scrolls to the line. Keyboard: Cmd/Ctrl+F focuses input, Enter/F3 steps through matches, Escape clears. In-diff highlights via shadow DOM traversal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(review): correct highlight assignment for duplicate matches and trimmed queries Group search matches by line before highlighting so each DOM occurrence gets the correct match ID. Also trim the query before building the highlight regex to stay consistent with the search engine. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: sercan tor <tor.sercan@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
933f44737e |
feat: split worktree/diff-type into separate controls (#248)
* feat: split worktree/diff-type into separate controls The single dropdown that mixed diff type selection (uncommitted, last-commit, branch) with worktree switching was confusing — entering a worktree silently replaced all dropdown options with no persistent indicator of which worktree was active. Split into two controls: - Context dropdown: switches between main repo and worktrees (only shown when worktrees exist, highlighted when a worktree is active) - View dropdown: always shows the same diff type options regardless of context Server changes: - GitContext now exposes worktrees as a separate field - Removed getWorktreeDiffOptions() and back-to-main sentinel handling - Simplified /api/diff/switch (no more diffOptions replacement) Client changes: - New activeWorktreePath state + handleWorktreeSwitch callback - handleDiffSwitch composes worktree prefix from context automatically - Derived activeDiffBase strips worktree prefix for dropdown display Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: derive activeWorktreePath from diffType instead of separate state Eliminates redundant state — activeWorktreePath and activeDiffBase are now both derived in a single useMemo from the composite diffType string. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: move DiffOption, WorktreeInfo, GitContext to @plannotator/shared Eliminates duplicate interface definitions across server and client. Types now live in packages/shared/types.ts and are imported by both packages/server/git.ts and packages/review-editor/. 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> |
||
|
|
044a50ea2c |
✨ feat: add resizable side panels (#141)
Add drag-to-resize for the annotation panel, table of contents, and file tree sidebar. Panel widths persist in cookie storage across sessions and random-port hook invocations. - Add useResizablePanel hook with left/right side support - Extract ResizeHandle component with spread-friendly handleProps API - Apply to AnnotationPanel, ReviewPanel, TableOfContents, FileTree - Double-click handle to reset to default width - Add min-w-0 on content areas for proper flex shrinking |
||
|
|
34d76b8cf6 |
feat: add GitHub-style viewed file tracking to code review
- Add viewed checkbox in FileTree for manual toggling - Add Viewed button in DiffViewer file header - Add progress indicator (N/M format) - Add option to hide/show viewed files - Remove automatic viewed marking on file selection |
||
|
|
8019f73a44 |
Feat/code review system (#57)
## Summary Complete code review system for reviewing git diffs with annotations. ### Features - Interactive diff viewer with split/unified views - Line-level annotation system - Diff type selector (uncommitted, last commit, vs main branch) - Dynamic default branch detection - Empty state handling - Simplified UX with streamlined feedback flow Closes #51 Closes #56 |