Skip to content

[UI] Show uncommitted work as a row in the stack - #42

Merged
KCaverly merged 2 commits into
mainfrom
ct/diff_view/64abe23f
Jul 26, 2026
Merged

[UI] Show uncommitted work as a row in the stack#42
KCaverly merged 2 commits into
mainfrom
ct/diff_view/64abe23f

Conversation

@KCaverly

@KCaverly KCaverly commented Jul 26, 2026

Copy link
Copy Markdown
Owner

The stack screen could only show you commits, so the work in progress — the
thing most likely to be moving while you are looking at it — was the one
part of the worktree the preview pane could not reach. Reading it meant
leaving for the deck's diff viewer and its separate u scope toggle.

Uncommitted work now appears as a synthetic row trailing the commits, and
behaves like any other: it takes the cursor, drives the preview pane, and
windows with the rest. Commits are bottom-first, so it sits last, which is
where it belongs — above the tip.

The cursor now indexes a row list rather than status.Commits directly, which
is what makes that possible. clampStackCursor counts rows for the same
reason: clamping to the commit count would knock the cursor off the last row
on every passive refresh.

Contents are staged and unstaged together, matching what the deck already
counts as uncommitted, plus untracked file contents — git diff HEAD never
reports those, so a brand-new file would otherwise show as a name with
nothing in it. Untracked rendering is capped at 20 files, well clear of a
realistic worktree, and the surplus is named in the section rule rather than
silently dropped.

Caching splits by row kind. A commit's patch is immutable, so it is fetched
once and reused. Uncommitted work is the opposite — it changes under the
screen, which is the whole reason to watch it — so landing on the row always
revalidates, keeping the previous patch on screen while the new one is in
flight. Only the first landing shows "loading…", and the scroll position
carries across, or the row would be unreadable while an agent writes to the
worktree.

The row is hidden when the tree is clean rather than sitting there dead, and
o degrades to a flash: uncommitted work has no PR and cannot have one.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com


Stack (bottom → top):

  1. [Git] Add DiffUntracked to render untracked file contents #41
  2. [UI] Show uncommitted work as a row in the stack #42 ← this PR
  3. [UI] Name one way out of the stack preview #43
  4. [UI] Give d the preview and esc the way out #44
  5. [UI] Drop the branch-diff jump from the stack screen #45
  6. [UI] Stop the stack preview overrunning a narrow terminal #46
  7. [Git] Pin the diff flags the body parser depends on #47
  8. [UI] Teach both diff viewers the same file-jump key #48
  9. [Diff] Thread the [diff] config through the viewer and zoom with z #49
  10. [Git] Carry diff args and excludes through every diff call #50
  11. [Diff] Add the external diff pager package #51
  12. [Config] Accept a [diff] section with an optional pager #52
  13. [Docs] Document the diff viewer settings #53

KCaverly and others added 2 commits July 26, 2026 15:30
The diff viewer lists untracked files by name in its file index but shows
nothing of what is in them, because `git diff HEAD` never reports a file git
isn't tracking. For a brand-new file that leaves the most interesting part
invisible — the contents are the whole change.

DiffUntracked diffs each path against os.DevNull with `git diff --no-index`,
which renders it as a whole-file addition beneath an ordinary `diff --git`
header. That shape matters: the diff builder indexes those headers for its
file-jump keys and colours the + lines, so new files behave like tracked ones
with no styler changes. Binary files collapse to git's one-line notice rather
than inlining their bytes.

--no-index follows diff(1) and signals "the inputs differ" with exit 1, which
Git treats as failure. Rather than a second runner, Git now delegates to an
internal git() that takes one forgivable exit code; -1 (accept only 0) keeps
every existing caller's behaviour byte-identical. Only an ExitError is
forgiven, so a timeout or a missing binary still fails.

Per-path errors are skipped rather than propagated. An untracked file is by
definition outside git's control and can vanish between the status call that
listed it and this one; losing every other file's diff to that race would be
the wrong trade.

Caller-driven path list: each file costs a subprocess, so the cap belongs
with the caller that knows what it is willing to render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ct-stack-id: 303beec7
The stack screen could only show you commits, so the work in progress — the
thing most likely to be moving while you are looking at it — was the one
part of the worktree the preview pane could not reach. Reading it meant
leaving for the deck's diff viewer and its separate `u` scope toggle.

Uncommitted work now appears as a synthetic row trailing the commits, and
behaves like any other: it takes the cursor, drives the preview pane, and
windows with the rest. Commits are bottom-first, so it sits last, which is
where it belongs — above the tip.

The cursor now indexes a row list rather than status.Commits directly, which
is what makes that possible. clampStackCursor counts rows for the same
reason: clamping to the commit count would knock the cursor off the last row
on every passive refresh.

Contents are staged and unstaged together, matching what the deck already
counts as uncommitted, plus untracked file contents — `git diff HEAD` never
reports those, so a brand-new file would otherwise show as a name with
nothing in it. Untracked rendering is capped at 20 files, well clear of a
realistic worktree, and the surplus is named in the section rule rather than
silently dropped.

Caching splits by row kind. A commit's patch is immutable, so it is fetched
once and reused. Uncommitted work is the opposite — it changes under the
screen, which is the whole reason to watch it — so landing on the row always
revalidates, keeping the previous patch on screen while the new one is in
flight. Only the first landing shows "loading…", and the scroll position
carries across, or the row would be unreadable while an agent writes to the
worktree.

The row is hidden when the tree is clean rather than sitting there dead, and
o degrades to a flash: uncommitted work has no PR and cannot have one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ct-stack-id: 64abe23f
@KCaverly
KCaverly force-pushed the ct/diff_view/303beec7 branch from 8644387 to 88bf444 Compare July 26, 2026 19:30
@KCaverly
KCaverly force-pushed the ct/diff_view/64abe23f branch from ae19b26 to 05c1cfd Compare July 26, 2026 19:30
Base automatically changed from ct/diff_view/303beec7 to main July 26, 2026 19:32
@KCaverly
KCaverly merged commit 397a669 into main Jul 26, 2026
2 checks passed
@KCaverly
KCaverly deleted the ct/diff_view/64abe23f branch July 26, 2026 19:33
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