feat(desktop): mark files viewed and count their threads and lines on Files changed's tree rows
Merged
DA
2d ago+571+571+012345678901234567890123456789-33-33-01234567890123456789
Summary
T1 of the "File tree: viewed and counts" stack, which brings stage-cli's tree rows to Files changed. Each file row in the tree beside Files changed now ends in:
- Comment count: a speech mark with how many review threads are on the file. Like stage-cli, every thread counts, resolved or not.
- Lines added and removed: a compact
+N −M, shown only where the row has room for it. - Viewed toggle: a circle that writes through the same
useViewedFilesdoor the header's Viewed box and V use, so it shows pending and refusal the same way. A press shows at once. If GitHub refuses, the mark goes back and the write note under the bar says why. Marking a file viewed folds it in the list, as the box does.- The toggle doesn't take room of its own. On a hovered or keyboard-focused row, it sits over the end of the marks, in their place.
- A viewed row always shows its check in that same place, and the lane keeps room for it so the name is cut before the check rather than under it.
- Pressing the toggle doesn't open the file or move the tree's keyboard focus.
- Dimming: viewed rows are dimmed, except the file currently shown.
Folder rows are unchanged. No new GitHub request: the data comes from the viewed read, the review threads, and the changes answer.
Stacked on #3794.
How it's drawn
- Contract extended, not drawn over.
FileMark(file-tree/contract.ts) gains optionallines,threadsandviewed, andFileTreePropsgainsonViewed. The tree bundle draws them through Pierre's ownrenderRowDecoration, as one run of four parts in a fixed order. The newfile-tree/row-marks.tsholds the decoration, the sheet that styles it, and how a press is matched to the toggle. - The name wins. The lane only gets the room the name leaves, as Pierre lays it out, so a mark never cuts a name short. Pierre cuts a name only when the row itself is too narrow, using its own middle ellipsis that keeps the extension. Where the leftover room runs out, the run gives up its parts from the end:
−M, then+N, then the count's number, then the speech mark. The run is one line of a wrapping flex box with its second line clipped. - CSP fix along the way. Pierre writes
unsafeCSSinto an inline<style>, which the panel'sstyle-src 'self'refuses. So the tree's existing thin-scrollbar rule never applied. The tree's rules are now a constructed sheet adopted by its shadow root, which the policy allows (as for the terminal,terminal/adopted-styles.ts). It's adopted again whenever Pierre moves its element. The thin scrollbar therefore now applies, as its comment always said it would. - Redraws. Pierre redraws rows only when the git status changes, so
mark()asks it to redraw (setComposition) whenever a viewed mark or count changes.
Screenshots
PR #3645 drawn in the Files changed harness (Chromium, the panel's real CSP and stylesheet). The pointer rests on changes-list.tsx. Four files are marked viewed and six review threads sit on three files.
| before | after | |
|---|---|---|
| dark 900 | ![]() | ![]() |
| light 900 | ![]() | ![]() |
| dark 600 | ![]() | ![]() |
| light 600 | ![]() | ![]() |
Tests
file-tree/index.test.ts(new) mounts the real bundle and draws real Pierre rows in jsdom:- each file row's run and its hint; folder rows and bare marks draw nothing;
- a press on the toggle marks the file and opens nothing, while a press on the rest of the row opens it;
- marks handed again redraw the rows;
- a mark with no
vieweddraws no toggle.
agent-changes-tab.test.ts:- the tree is handed each file's lines, thread count (resolved threads included) and viewed mark;
- the tree's toggle writes
mark-file-viewed, ticks the header box and folds the file; - no toggle is drawn before the marks are read.
Evidence
pnpm check:affectedran out of memory indiscarded-effect. Its other steps passed, so I ran the fallback list:tsc --noEmitfor panel and desktop: clean;vitest --project panel: 197 files, 2116 tests passed;./scripts/oxlint.sh: 0 errors;pnpm knip: clean;./scripts/repository-checks.sh: passed;biome check: no errors.
cd apps/desktop && node scripts/build.mjs: passed, bundle budget included.
Invariants
- Checks green locally, as listed above.
- JSON Schema goldens unchanged.
Made with Luke







