Skip to content

feat(desktop): let the Git changes panel pick its comparison base branch - #5120

Open
faga295 wants to merge 5 commits into
apache:mainfrom
faga295:feat/review_panel_base_branch_change
Open

faga295 wants to merge 5 commits into
apache:mainfrom
faga295:feat/review_panel_base_branch_change

Conversation

@faga295

@faga295 faga295 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

The Changes panel previously compared only against the backend-resolved base branch, so stacked work could include inherited changes with no way to choose another comparison base. This PR adds a searchable base-branch picker beside the current branch (current → base).

  • Explicit selections persist per Session and are sent on subsequent reads. With no explicit selection, reads continue to omit baseBranch and follow the repository default; displaying the resolved default does not pin it. If a saved branch becomes invalid, the panel clears the saved selection and retries once with the dynamic default.
  • Branch options now carry { label, value }: readable names in the UI, fully qualified refs/heads/... or refs/remotes/... values for comparisons and storage. Unique legacy names migrate to canonical refs; ambiguous names and tag-only refs are rejected. Local and remote branches with identical labels remain distinct.
  • Branch context is returned even when computing the diff fails (for example, unrelated histories), keeping the picker available so the user can recover by selecting another branch. A user-initiated switch spins the picker and dims/disables interaction with the old diff until the read finishes. Changing the comparison resets file pagination; refreshing the same comparison preserves it.
  • Oversized unified diffs that exceed the child-process output buffer retain the available output and mark the review as truncated instead of failing the entire review.
  • The picker has no selectable “auto” entry and is hidden when no branch options are available. It displays a branch label when resolved, or the localized field placeholder when unresolved. Its menu is capped at 180px wide with a 288px listbox height; long names ellipsize. Search empty-state and result-count feedback are localized in Simplified and Traditional Chinese.

The backend enumerates full refs instead of relying on %(refname:short), keeps origin/HEAD selectable, and shares the preferred-branch ordering with default resolution. The remote HEAD's resolved target takes precedence; the current branch can also be the base, with staged and unstaged changes still included.

Fixes #5119

Before:
image

After:
image

Verification

Checked against PR head abce32742c4c384acbb87842ade7746bf4f38296 on 2026-09-20:

  • npm --workspace @maka/desktop run build:main — passed.
  • node --test --test-force-exit apps/desktop/dist/main/__tests__/git-review-main.test.js apps/desktop/dist/main/__tests__/session-review-base-branch.test.js apps/desktop/dist/main/__tests__/session-review-panel-recovery.test.js19/19 passed. Covers full-ref and legacy-name resolution, tag/name collisions, default ordering, per-Session persistence, corrupt storage, recovery after invalid selections or failed diffs, switch feedback, and output-buffer truncation.
  • Full desktop tests, all four typecheck configurations, lint/format, renderer build, runtime tests, and browser/Storybook checks were not rerun for this description update. See the PR checks for current CI results.

Earlier verification recorded in this PR (not revalidated against the current head): desktop typecheck, architecture checks, Biome and renderer build passed; desktop tests reported 2461/2462 with a settings-ipc-helpers Tavily failure. Storybook checks covered filtering, localized search feedback, long-name ellipsis, and updating/closing the picker after selection. The screenshots above are retained from that earlier verification.

Review focus

  • The shared Git review contract changes options from strings to { label, value } and can include branch context in failure results. Legacy saved names are accepted only when they identify one enumerated branch.
  • Scoped Astryx CSS caps the popover width and listbox height and allows option labels to shrink. The picker is included in the Astryx surface inventory, and Storybook fixtures use full-ref option values.
  • This branch also stabilizes a runtime PTY test by waiting for the complete READY line and cursor position before taking the PID-observation baseline; it does not change runtime production behavior.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: pi (DeepSeek V4.1 Flash) — implementation and earlier local verification. Codex — PR description refresh and the focused build/test verification listed above.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally (full current-head checks not rerun; focused checks above passed)

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@github-actions github-actions Bot added effort/L Under 1000 readable lines effort/XL Under 2500 readable lines and removed effort/L Under 1000 readable lines labels Sep 9, 2026
@faga295
faga295 force-pushed the feat/review_panel_base_branch_change branch 2 times, most recently from 1850701 to 6487c58 Compare September 16, 2026 11:55
@faga295
faga295 marked this pull request as ready for review September 16, 2026 11:57

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The picker holds up end to end — I traced the value from the Astryx trigger to the git command and found no correctness bug. One behavior I'd like a decision on, plus a few smaller things.

Behavior worth confirming

The resolved default is now persisted, so it stops following the repo. resolveAdoptedBaseBranch returns the backend's resolved branch when nothing is pinned (session-review-base-branch-model.ts:85-97) and the panel writes it to storage (session-review-panel.tsx:143-153). So a Session that never opens the picker pins a branch on its first read and keeps it for the life of that Session, across restarts. Before this PR every read re-resolved, so the panel tracked origin/HEAD. The blast radius is narrow — BASE_BRANCH_PRIORITY ranks refs/remotes/origin/HEAD first (git-review-main.ts:403-410) and that ref survives a rename of the default branch — but in a repo with no origin/HEAD the pin freezes on refs/heads/main and will not follow a later rename to trunk. If the goal is only "the trigger must name a real branch", the resolved snapshot.baseBranch could be displayed without being persisted (persist only an explicit pick) and the dynamic default would survive. Either way, the Summary should say the implicit default gets stored, since that is a behavior change beyond "the choice persists".

Findings

  1. The invalid_base_branch recovery path has no test. The clear-the-pin-and-retry-once branch (session-review-panel.tsx:123-135) is the headline answer to a pinned branch disappearing, but session-review-panel-recovery.test.ts only drives the git_failed case, and the model test stops at resolveAdoptedBaseBranch. A case where review.read returns invalid_base_branch once and then succeeds would cover it: request sequence ['refs/heads/gh-pages', undefined], pin cleared in storage, no error banner.
  2. invalid_base_branch drops branches even though it is in scope. branches is captured at git-review-main.ts:67, but the rejection at :71-73 returns without it, while the contract documents it as "available even when computing the selected branch diff fails" (packages/core/src/git-review.ts:66-67) — unborn_repository and git_failed both include it. Spreading it here too would make the contract uniform and let the panel keep the picker if the retry ever failed.
  3. copy.invalidBaseBranch looks unreachable now. The panel always clears the pin and retries with no selection, and the backend only rejects a non-null baseBranch, so I could not construct a path where the banner at session-review-panel.tsx:233 renders in any of the three locales. Keeping it as a guard is fine; worth a line in the PR so nobody hunts for it.
  4. nit — the picker offers the current branch as a comparison base. Options include it (the updated git-review-main.test.ts fixture has feature/review as both currentBranch and an option). Picking it diffs against merge-base(base, HEAD), i.e. HEAD, so the panel silently becomes "uncommitted changes" while the header still reads feature → feature. resolveBaseBranch deliberately skips the current branch (git-review-main.ts:431); marking that option disabled would close the gap.
  5. nit — visibleFileCount is not reset when the base changes (session-review-panel.tsx:91,216), so a "show 20 more" position from the previous comparison carries into the new one and the button can appear or vanish. Pre-existing for Session switches, newly reachable through the picker.
  6. nit — the buffer-overflow change is missing from the Summary and CHANGELOG. runDiffAllowTruncated (git-review-main.ts:220-245) changes what a huge branch diff produces, and the PR answers "Yes — described under Summary above" to the behavior question. Related: the sibling --name-status call at :175 is not wrapped, and a mid-file cut can leave a listed file with an empty or partial diff — acceptable degradation, but it belongs in the description.
  7. nit — unrelated change: the PTY-output wait in packages/runtime/src/__tests__/shell-run-manager.test.ts is unrelated to this panel; better as its own PR.
  8. nit — copy conventions: the sibling ICU plural uses {count, number} (packages/ui/src/astryx-copy.ts:150, commandPalette.resultCount), the new search.resultCount uses {count}. Also the comment above search ("Selector and MultiSelector render the same two search affordances") is stale — emptySearch/resultCount are Selector-only, MultiSelector has its own catalog keys.
  9. nit — options={[...props.baseBranchOptions]} (session-review-base-branch-picker.tsx:51) allocates per render and defeats Selector's filteredItems memo; a useMemo on the panel side would keep it stable.
  10. nit — process: only the first commit carries Generated-by: pi (…), while CONTRIBUTING.md asks for the trailer on each affected commit, kept through squash; and CONTRIBUTING.md asks for before/after images on UI changes — the Storybook measurements are useful evidence, but nothing is attached.
  11. docs/superpowers/plans/2026-09-10-review-branch-refs.md is the only file under docs/superpowers/; tracked plans elsewhere live in docs/archive/ or docs/. Consider moving or dropping it.
  12. Heads-up: merge state is DIRTY (conflicts with main); the test check is green.

Verified, no issue found

  • Selector.onChange hands back the option value in the non-clearable variant (@astryxdesign/core@0.5.2, Selector.d.ts), so what gets persisted is the canonical ref, not a label; the first read omits baseBranch entirely (reviewBaseBranchRequestValue).
  • Search filters on option.label (Selector.js:260), so short branch names are searchable, and @astryx.selector.emptySearchResults / .resultCount do exist in the Astryx catalog (locales/en.json:1270,1274), so the Chinese overrides land.
  • The %(refname:short) bug is real: git reports refs/remotes/<remote>/HEAD as a bare <remote> (refs/remotes/author/HEAD → author on git 2.50 here), which the old .endsWith('/HEAD') filter let through. Enumerating %(refname) and stripping the namespace is the right fix.
  • Ordering cannot drift: one BASE_BRANCH_PRIORITY list feeds both listBaseBranches (:450) and resolveBaseBranch (:414), and the unranked/label tiebreak reads correctly.
  • Ambiguity is rejected rather than guessed: same-label local vs remote, tag vs branch, and tag-only names all fail closed (resolveRequestedBaseBranch, :440), and the orphan-history and tag-named-main cases behave.
  • The CSS selectors are real (themeProps('popover-surface'), themeProps('item')); the layer renders inline inside the wrapper unless its parent has an unsafe writing context, and the popover is popover="auto" (top layer), so overflow-y: auto on .maka-session-review-panel will not clip it.
  • Contract blast radius is small: baseBranchOptions is only consumed by the desktop app, the IPC already validated baseBranch (runtime-host-workspace-ipc-main.ts:66-79), and both new test files sit under src/main/__tests__ where test:dist (dist/main/**/*.test.js) picks them up.

@faga295
faga295 force-pushed the feat/review_panel_base_branch_change branch from 8b51ef2 to 08c5a2c Compare September 18, 2026 09:11
@faga295
faga295 requested a review from Astro-Han September 18, 2026 11:16
@faga295
faga295 force-pushed the feat/review_panel_base_branch_change branch from 3ef391c to fa20b51 Compare September 20, 2026 06:32
@Astro-Han

Copy link
Copy Markdown
Contributor

Could we have a before after comparison picture in the pr body? It would help clarify things easier. Thanks!

Astro-Han added a commit that referenced this pull request Sep 20, 2026
Remove the composer footer's Git-branch chip and the read pipeline that fed it (#5487): the chip was that pipeline's only consumer, so nothing is left stranded.

The branch is ambient session state the agent owns, not a parameter of the send, so the composer's control row is the wrong home for a read-only value there — and it was the one item on that row drawn as a hand-rolled span instead of an Astryx primitive. The workbar's Review face names the current branch once #5120 lands; this lands first so the two never show the same fact twice.

Migration: none. Between this merge and #5120's there is no surface naming the branch — a deliberate gap over duplicating the readout. #5120 needs a trivial rebase on git-review-main.ts and packages/core/src/git-review.ts to drop the readGitBranch / GitBranchReadResult it inherited from #5487.

Refs #2171, #5487

Generated-by: Maka
@faga295
faga295 force-pushed the feat/review_panel_base_branch_change branch from 5942af5 to abce327 Compare September 20, 2026 13:21
@faga295

faga295 commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han done

@faga295
faga295 force-pushed the feat/review_panel_base_branch_change branch 2 times, most recently from 29afb1b to 951cb66 Compare September 20, 2026 19:12
liuzhaochen03 added 5 commits September 21, 2026 19:59
Let each session select a comparison branch by canonical ref, with searchable localized options and persisted explicit choices. Keep implicit defaults dynamic, recover automatically when a saved branch disappears, and reset pagination when the comparison changes.

Preserve branch options when diff computation fails and show truncated output when a large unified diff exceeds the process buffer. Add recovery and ref-resolution coverage, switch feedback, stories, and surface inventory documentation.

Generated-by: pi (DeepSeek V4.1 Flash)
Generated-by: OpenAI Codex
Regenerate the file-level inventory after 0f11ae7 added a Text usage
to the review base branch picker without regenerating. The markdown row
had drifted from the generator; the .paths list was already in sync.
Keeps the astryx:surface-inventory coverage gate green.

Generated-by: Maka
@faga295
faga295 force-pushed the feat/review_panel_base_branch_change branch from dcc385e to f6ffba1 Compare September 21, 2026 12:11
@faga295

faga295 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han Thanks for the detailed review. Most items are addressed, including keeping implicit defaults dynamic. A few behavior, documentation, and process points remain open:

  • Default persistence — fixed: Only explicit selections are persisted. The resolved default is displayed without pinning it, so subsequent reads continue to follow the repository default.
  • 1 — fixed: Added recovery coverage for invalid_base_branch, verifying the request sequence, cleared storage, restored picker, and absence of an error banner.
  • 2 — fixed: invalid_base_branch now returns branch context, preserving the picker during recovery.
  • 3 — fixed: Removed the dedicated invalid-base-branch banner path.
  • 4 — still open for discussion: The current branch remains selectable, with test coverage and documentation confirming that it shows staged and unstaged changes. Disabling it or clarifying the UI remains a UX decision.
  • 5 — fixed: Pagination resets when the comparison or Session changes, while refreshes preserve expansion.
  • 6 — partially addressed: The Summary documents oversized-diff truncation, but the CHANGELOG and the --name-status/partial-file limitations still need updating.
  • 7 — fixed: The unrelated PTY test change is no longer included.
  • 8–9 — fixed: Updated number formatting and the search comment; memoized the picker options.
  • 10 — addressed: The screenshots have been updated in the PR description.
  • 11 — fixed: The standalone plan file is no longer included.
  • 12 — addressed: All CI checks have passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/XL Under 2500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(desktop): choose the comparison base branch in the Git changes panel

2 participants