Skip to content

feat(ui): mark files to hide them from the review stream#277

Open
aldevv wants to merge 1 commit intomodem-dev:mainfrom
aldevv:feat/file-mark
Open

feat(ui): mark files to hide them from the review stream#277
aldevv wants to merge 1 commit intomodem-dev:mainfrom
aldevv:feat/file-mark

Conversation

@aldevv
Copy link
Copy Markdown
Contributor

@aldevv aldevv commented May 10, 2026

Why

Long changesets mix real changes with noise (lockfiles, snapshots, vendored fixtures), and once you've reviewed a file there was no way to set it aside. You scroll past it every time and lose track of what you've already looked at.

User-visible

image

m marks the focused file; M (shift+m) clears every active mark. Marked files disappear from the diff stream but stay in the sidebar dimmed and crossed out, with a <n> hidden footer hint reminding you how many are tucked away. Clicking a marked sidebar row unmarks it instead of trying to jump to a file that isn't there.

The legacy hunk-metadata toggle moved off m (now H) so the mark binding lives in the most reachable spot.

What it does

  • useReviewController owns the markedFileIds set and exposes toggleMarkedFile, clearMarkedFiles, hiddenByMarkCount, and an unmarkedFiles list alongside the existing allFiles/visibleFiles.
  • Visibility derivation moves into buildReviewState, so the review stream, sidebar entries, and stats all read from one source. Subsequent filter and search features can layer on top of unmarkedFiles without re-deriving visibility ad hoc.
  • buildSidebarEntries carries a marked flag per entry. FileListItem reads it to dim the icon/name and apply STRIKETHROUGH, so marked rows stay visible but obviously inert.
  • SidebarPane renders a 1-row footer (<n> hidden) when any file is marked. Clicking a marked row routes through a new onUnmarkFile prop instead of onSelectFile.
  • Help dialog and appMenus gain Mark file (m) / Clear marks (Shift+M) entries; the menu hook wires the keyboard shortcuts to the focused file.

Tests

  • bun test src/ui/lib/reviewState.test.ts — marked ids drop files from unmarkedFiles/visibleFiles, surface in hiddenByMarkCount, and the sidebar entries keep the marked file with marked: true.
  • bun test src/ui/lib/files.test.tsbuildSidebarEntries flags marked rows and tolerates unknown ids.
  • bun test src/ui/hooks/useReviewController.test.tsxtoggleMarkedFile round-trips, clearMarkedFiles is a no-op when empty, and the controller's hiddenByMarkCount/unmarkedFiles track the set.
  • bun test test/pty/marked-files-integration.test.ts — new PTY test: m removes the focused file from the stream, the sidebar footer reports 1 hidden, and M restores it.
  • bun test src/ui/AppHost.interactions.test.tsx, src/ui/components/ui-components.test.tsx, src/ui/lib/ui-lib.test.ts — updated for the new H binding, sidebar footer rendering, and menu wiring.
  • bun run typecheck and bun run lint clean.

Adds m/M keybindings to mark/unmark files. Marked files disappear from
the diff stream and stay in the sidebar dimmed and crossed out, with a
"<n> hidden" footer hint. Visibility derivation moves into
buildReviewState so subsequent filter and search features can layer on.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 10, 2026

Greptile Summary

This PR adds a "mark to hide" feature: pressing m removes the focused file from the diff stream while keeping it dimmed and crossed-out in the sidebar, and M clears all marks. The legacy hunk-metadata shortcut moves from m to H.

  • buildReviewState now owns the full visibility pipeline \u2014 marked files are filtered first, then the active text filter narrows what remains \u2014 so the diff stream, sidebar, stats, and hunk cursors all derive from one source.
  • useReviewController owns markedFileIds state and exposes toggleMarkedFile / clearMarkedFiles; reconcileSelectedFile handles re-selection when the currently active file is marked.
  • SidebarPane gains an onUnmarkFile prop and a <n> hidden footer; FileListItem applies STRIKETHROUGH and muted colours to marked rows.

Confidence Score: 4/5

Safe to merge; the core mark/unmark flow, visibility derivation, and re-selection logic are all correct and well-tested.

The implementation is solid end-to-end with thorough unit and PTY test coverage. The only non-blocking concern is that SidebarPane's click handler silently falls through to onSelectFile for marked rows when onUnmarkFile is not provided, which could show a hidden file's diff in any future caller that omits the prop.

src/ui/components/panes/SidebarPane.tsx — the onUnmarkFile-absent fallthrough is the only item worth a second look before any future reuse of the component.

Important Files Changed

Filename Overview
src/ui/lib/reviewState.ts Adds markedFileIds layer before filter derivation; hiddenByMarkCount, unmarkedFiles, and sidebar marked flags computed cleanly in one pass.
src/ui/hooks/useReviewController.ts State and callbacks for markedFileIds are correct; toggleMarkedFile and clearMarkedFiles follow existing patterns. reconcileSelectedFile handles re-selection when the marked file was the active selection.
src/ui/components/panes/SidebarPane.tsx Footer and click routing added correctly; click fallthrough to onSelectFile when onUnmarkFile is absent could navigate to a hidden file if a consumer omits the prop.
src/ui/components/panes/FileListItem.tsx Strikethrough and muted colour correctly applied to both icon and name for marked rows; no logic changes.
src/ui/hooks/useAppKeyboardShortcuts.ts m/M bindings wired; legacy mH remapping for hunk-metadata toggle is correct and consistent with existing shift-key detection pattern.
src/ui/lib/files.ts buildSidebarEntries extended with optional markedFileIds option; marked field on FileListEntry defaulting to false is backward-compatible.
src/ui/lib/appMenus.ts Mark file / Unmark all menu entries added to the file menu; hint strings updated for the renamed H binding.
src/ui/App.tsx New controller props destructured and plumbed through menus, keyboard shortcuts, and SidebarPane; onUnmarkFile always provided here.
test/pty/marked-files-integration.test.ts New PTY test covers press-m hides file, footer updates, and shift-M restores; covers the round-trip end-to-end.
src/ui/lib/reviewState.test.ts New test file covers marking, filter interaction, and empty-mark baseline for buildReviewState.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User presses m"] --> B["toggleSelectedFileMark()"]
    B --> C["toggleMarkedFile(fileId) in useReviewController"]
    C --> D["setMarkedFileIds — new Set with/without fileId"]
    D --> E["useMemo: buildReviewState re-runs"]
    E --> F["allFiles.filter(!markedFileIds.has) → unmarkedFiles"]
    F --> G["filterReviewFiles(unmarkedFiles, query) → visibleFiles"]
    E --> H["filterReviewFiles(allFiles, query) → sidebarFiles"]
    H --> I["buildSidebarEntries(sidebarFiles, markedFileIds) → sidebarEntries"]
    G --> K["resolveSelectedFile → selectedFile"]
    K --> L{selectedFile in visibleFiles?}
    L -- No --> M["reconcileSelectedFile useEffect → reselectFirstVisibleFile"]
    L -- Yes --> N["Selection unchanged"]
    E --> O["hiddenByMarkCount = allFiles.length - unmarkedFiles.length"]
    O --> P["SidebarPane footer: N hidden"]
    I --> Q["FileListItem: STRIKETHROUGH + muted colour if marked"]
    Q --> R{User clicks marked row}
    R -- onUnmarkFile provided --> S["onUnmarkFile(id) → file restored"]
    R -- onUnmarkFile absent --> T["onSelectFile(id) — navigates to hidden file"]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/ui/components/panes/SidebarPane.tsx:75-83
**Silent fallthrough to `onSelectFile` for marked rows**

When `entry.marked` is true but `onUnmarkFile` is absent, the click falls through to `onSelectFile(entry.id)`, which calls `jumpToFile` on a file that has been intentionally removed from the review stream. The resulting `selectFile` call sets `selectedFileId` to the hidden file, and `resolveSelectedFile` then surfaces it via the `allFiles` fallback — displaying a file the user just asked to hide. A no-op guard when `onUnmarkFile` is absent is safer than silently treating the click as a normal selection.

Reviews (1): Last reviewed commit: "feat(ui): mark files to hide them from t..." | Re-trigger Greptile

Comment on lines +75 to +83
onSelect={() => {
// Clicking a marked row should bring the file back rather than re-select a
// hidden file in the diff stream.
if (entry.marked && onUnmarkFile) {
onUnmarkFile(entry.id);
return;
}
onSelectFile(entry.id);
}}
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.

P2 Silent fallthrough to onSelectFile for marked rows

When entry.marked is true but onUnmarkFile is absent, the click falls through to onSelectFile(entry.id), which calls jumpToFile on a file that has been intentionally removed from the review stream. The resulting selectFile call sets selectedFileId to the hidden file, and resolveSelectedFile then surfaces it via the allFiles fallback — displaying a file the user just asked to hide. A no-op guard when onUnmarkFile is absent is safer than silently treating the click as a normal selection.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/components/panes/SidebarPane.tsx
Line: 75-83

Comment:
**Silent fallthrough to `onSelectFile` for marked rows**

When `entry.marked` is true but `onUnmarkFile` is absent, the click falls through to `onSelectFile(entry.id)`, which calls `jumpToFile` on a file that has been intentionally removed from the review stream. The resulting `selectFile` call sets `selectedFileId` to the hidden file, and `resolveSelectedFile` then surfaces it via the `allFiles` fallback — displaying a file the user just asked to hide. A no-op guard when `onUnmarkFile` is absent is safer than silently treating the click as a normal selection.

How can I resolve this? If you propose a fix, please make it concise.

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