Skip to content

feat(review): collapse and expand files like a GitHub diff#493

Open
narthur wants to merge 5 commits into
modem-dev:mainfrom
narthur:feat/collapse-files
Open

feat(review): collapse and expand files like a GitHub diff#493
narthur wants to merge 5 commits into
modem-dev:mainfrom
narthur:feat/collapse-files

Conversation

@narthur

@narthur narthur commented Jun 29, 2026

Copy link
Copy Markdown

Why

Long agent changesets mix files you still need to read with ones you've already reviewed, and there was no way to set a file aside while scrolling the stream. This adds GitHub-style per-file collapse so you can fold away what you're done with.

This is the manual, user-driven counterpart to the prior collapse attempts, designed to sidestep the feedback they ran into:

User-visible

  • x collapses/expands the selected file.
  • Shift+X collapses or expands every file.
  • Clicking the chevron (/) in any file header toggles that file; clicking the rest of the header still selects/jumps as before.
  • A collapsed file shows just its header plus a one-line "Collapsed" placeholder; sidebar stats are unchanged.
  • Help dialog and the Navigate menu document the new controls.

Collapse state is session-only and resets on relaunch (it survives watch-mode reloads).

What it does

  • A collapsed file is swapped for a zero-hunk placeholder variant in buildReviewState (src/ui/lib/fileCollapse.ts), so the existing empty-file render (PierreDiffView), geometry (bodyHeight: 1), and hunk-cursor paths handle it with no new renderer flags or row kinds. Hunk navigation ([/]) naturally skips collapsed files because they expose no hunks.
  • useReviewController owns a collapsedFileIds set with toggleFileCollapsed / toggleAllFilesCollapsed, reconciled in the same filesSnapshot block as gap expansion. Toggling re-pins the file header to the top so a height change can't scroll it out of view.
  • The placeholder variant is cached per source file in a WeakMap so its object identity stays stable across renders (the geometry cache keys on the DiffFile object).

Tests

  • src/ui/lib/fileCollapse.test.ts — variant swap empties hunks while preserving stats/identity, returns a stable object, swaps only collapsed ids, and prunes ids that left the changeset.
  • src/ui/hooks/useReviewController.test.tsx — toggling collapses to a zero-hunk variant, skips hunk navigation, expands back, and collapse-all/expand-all round-trip.
  • test/pty/collapse.test.ts — real PTY: x collapses the file to the placeholder (with the chevron) and x again restores the diff.
  • Updated AppHost.interactions, ui-components (Help dialog), and ui-lib (menus) for the chevron and new entries.
  • bun run typecheck, bun run lint, and bun run test:tty-smoke clean.

🤖 Generated with Claude Code

Long agent changesets force you to scroll past files you've already read.
This adds GitHub-style per-file collapse: `x` collapses the selected file to
its header, `Shift+X` collapses or expands every file, and the chevron in each
file header toggles it by mouse.

Collapse is a pure derivation: a collapsed file is swapped for a zero-hunk
placeholder variant in buildReviewState, so the existing empty-file render,
geometry, and hunk-cursor paths handle it with no new renderer flags. State is
session-only and reconciled across watch reloads alongside gap expansion, so
there is no on-disk persistence layer to gate by repo root or validate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

PR author is not in the allowed authors list.

@narthur narthur marked this pull request as draft June 29, 2026 21:53
narthur and others added 4 commits June 29, 2026 17:40
Back collapsed-file state with a fileId->true Record instead of a Set so it
reconciles through the existing removeKeys stale-id pruning, deleting the
parallel pruneCollapsedFileIds helper. Extract anchorFileHeaderTop so a bulk
Shift+X collapse re-pins the selected file's header like the single-file toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guard the two behaviors the collapse refactor changed: bulk Shift+X re-pins the
selected file's header (not just single-file x), and collapse state keyed by an
old patch is pruned through removeKeys when a reload swaps the file's fetcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the two coverage gaps the review flagged on the feature: Shift-X must
collapse/expand every file (distinguished from single-file x by asserting the
second file also collapses), and the header chevron toggles collapse while its
stopPropagation keeps a chevron click from also firing the header select.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…face

The collapse map is internal: the UI reads collapse state through the swapped-in
zero-hunk variant (isCollapsed), never the id map. Keep it as private state and
assert collapse in tests through the observable visibleFiles stream instead of
widening the public ReviewController contract for tests alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@narthur narthur marked this pull request as ready for review June 29, 2026 23:01
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