Skip to content

feat(ui): banner above commits when current branch is behind upstream#1014

Merged
gfargo merged 1 commit into
mainfrom
feat/history-upstream-ahead-banner
May 20, 2026
Merged

feat(ui): banner above commits when current branch is behind upstream#1014
gfargo merged 1 commit into
mainfrom
feat/history-upstream-ahead-banner

Conversation

@gfargo
Copy link
Copy Markdown
Owner

@gfargo gfargo commented May 20, 2026

Adds a one-line banner to the history view that announces "the remote has work you don't" for the current branch. Fills a real gap: the chip work in 0.52.0 made remote refs visually distinct when they appear in the row set, but on a behind branch the upstream-only commits aren't reachable from local HEAD, so chips alone never signal it.

What it looks like

Behind-only (current branch is N commits behind, no local commits ahead):

Commits *                                  143/143 commits | compact graph | loaded
↓ 2 commits behind origin/main · F fetch · U pull
●─  feat: foo                  [HEAD -> main]
●─  refactor: bar

Diverged (both ahead AND behind — fast-forward pull is impossible):

Commits *                                  145/145 commits | compact graph | loaded
↑2 ↓2 diverged from origin/main · F fetch · U pull --rebase
●─  feat: local-only-A         [HEAD -> main]
●─  feat: local-only-B

Design choices

Question Choice Why
Color Warning yellow Same semantic as the remote-tracking chip kind from 0.52.0
Behind-only wording ↓ N commits behind <upstream> Matches git status for familiarity
Diverged wording ↑N ↓N diverged from <upstream> Reuses the ↑N ↓N symbols from formatBranchDivergence
Pull hint when diverged U pull --rebase Fast-forward pull is impossible with local work; rebase keeps history linear
Show on ahead-only? No Nothing inbound to pull. Push affordances live on the branches sidebar
Show on synced / no-upstream / detached HEAD? No Nothing inbound. Detached HEAD has no currentBranch ref
Change row set / auto-fetch? No Pure chrome. Surface the affordance, not the action

ASCII fallback mirrors formatBranchDivergence: v for , +N/-N for ↑N ↓N, . for ·, no color.

Implementation

  • New helper formatUpstreamAheadBanner in src/workstation/chrome/iconography.ts (~30 LOC). Two variants, plural-correct, ASCII-fallback, returns undefined when there's nothing to surface.
  • One render branch in src/workstation/surfaces/history/index.ts injecting the banner between the panel header and the existing filter indicator. Reads context.branches.localBranches.find(b => b.current) for the branch ref.
  • No new actions / keybindings. The F and U hints point at the existing fetch-remotes and pull-current-branch global workflows.

Tests

12 new in iconography.test.ts:

  • undefined branch (detached HEAD) → no banner
  • no upstream → no banner
  • behind === 0 (synced or ahead-only) → no banner
  • behind-only: N commits, singular noun for behind === 1, arbitrary upstream names, ASCII fallback
  • diverged: ↑N ↓N format, pull --rebase hint, ASCII fallback

Test plan

  • npm run test:jest -- src/workstation src/commands/log — 985 pass (was 973 + 12 new)
  • npm run lint — clean on touched files
  • Manual: npm run scenario create branch-sync-showcase -- --run-ui — HEAD is on main which is 2 behind origin/main. Banner should read ↓ 2 commits behind origin/main · F fetch · U pull in warning yellow above the commit list. Press F or U and verify the existing workflows fire.
  • Manual: from the same scenario, git checkout feat/diverged and re-run. Banner should read ↑2 ↓2 diverged from origin/feat/diverged · F fetch · U pull --rebase.
  • Manual: git checkout feat/synced and re-run. No banner (synced).

Pairs with

When origin/main is ahead of local main (or any tracked remote is
ahead of its local), the workstation's history view shows no signal
that upstream work exists. The chip work in 0.52.0 made remote refs
visually distinct WHEN they appear in the row set, but on a behind
branch the upstream-only commits aren't reachable from local HEAD,
so the chips alone never fire.

This PR adds a single-line banner between the panel header and the
first commit row, rendered when the current branch has a non-zero
behind count. Two wording variants:

  Behind-only (behind > 0, ahead === 0):
    ↓ 2 commits behind origin/main · F fetch · U pull

  Diverged (behind > 0 && ahead > 0):
    ↑2 ↓2 diverged from origin/main · F fetch · U pull --rebase

The diverged variant uses the same ↑N ↓N symbols already defined in
formatBranchDivergence for the branches sidebar, and suggests
\`pull --rebase\` because fast-forward pull is impossible when local
has work. Behind-only uses git status's own "N commits behind X"
wording for familiarity.

Color: warning yellow for both variants — same semantic as the
remote-tracking chip kind from 0.52.0. ASCII fallback uses \`v\` for
\`↓\`, \`+N/-N\` for \`↑N ↓N\`, and \`.\` for \`·\` (mirrors the existing
\`formatBranchDivergence\` ASCII pattern).

The banner does NOT:
  - change the log row set (no --all expansion)
  - auto-fetch (network side effect; surface the affordance, not the
    action)
  - fire on synced / ahead-only / no-upstream / detached HEAD

Hotkey hints (\`F\`, \`U\`) match existing global workflows in
inkWorkflows.ts. No new keybindings.

12 new tests in iconography.test.ts pin both variants (behind-only +
diverged), the singular/plural noun, the ASCII fallback for each,
and the no-banner cases (undefined branch, no upstream, behind === 0).
985 workstation + log tests pass.

Manual fixture: \`npm run scenario create branch-sync-showcase --
--run-ui\`. The scenario checks out \`main\` which is 2 behind
\`origin/main\` — the banner should render in warning yellow above
the commit list.

Co-pairs with the upcoming branches-sidebar work (task #4) which
will use the same scenario.
@gfargo gfargo merged commit 0c957c6 into main May 20, 2026
8 checks passed
@gfargo gfargo deleted the feat/history-upstream-ahead-banner branch May 20, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant