feat(panel): File tabs and page source on Pierre, Luke's notes as annotations with the minimized chip
Summary
This is PR 2 of the approved plan Pierre for File tabs and code snippets (plan page). The developer chose no experimental switch, so the plan's PR 2 (the File tab on Pierre) and PR 3 (notes, chip and I) are folded together here. Each old path is deleted in this same PR, so main never has a File tab without its notes. It builds on #4262 (mountFile), which has merged.
File tab
- Reading: the tab reads its file whole. It first reads one line through the existing read to learn the commit the head is at. It then makes the existing raw read (≤1 MiB, which is already the service's limit) at that commit, so a ⌥⌘K mention and the GitHub link point at exactly the lines drawn. A tab pinned to a commit (inbox code, a snippet with its commit) makes the raw read alone. No service change. Pierre has no line offset, so the 500-line windows, the "Show lines…" edge rows and the find-reads-every-window effect are gone.
- Drawing: the new
whole-file.tsx(WholeFile) loads the diff bundle and mountsmountFilein aScrollArea. It uses the diffs' own look (useDiffLook), so wrap, line numbers, syntax theme, font, size, line height and ligatures now apply natively. #3851's repaint from GitHub colours is gone. - Lit lines: the lines the tab was opened on are lit and centred each time it's opened.
- Luke's notes are Pierre annotations on the shared
AnnotationCard(#4214), under the last line of their range. The card keeps its ⋯ menu (View in conversation, Copy link) and its fold. Pointing at it lights its lines, which swaps Pierre's lit range, the same call Files changed uses. On unwrapped lines, Pierre's own sticky annotation column keeps the card in view. - The minimized chip:
MinimizedChipis generalized out ofMinimizedThreads(agreed with the Code-cards PR agent, so there's only one chip). While Minimize inline comments is on, Luke's note sits behind a chip at the end of its line, showing his wing mark, the count and "Luke's note on lines 3–4, show". Pressing the chip shows the card. The shown notes are forgotten whenever the setting turns. - View settings › Lines now shows "Minimize inline comments" (shared
LinesRows,LINESscopenotes: true). Page source doesn't show it. Its hint reads "Or press I over the code". Iturns the setting while the tab holds the focus and no text field does, as it does in Files changed.- ⌘F: most lines aren't in the DOM, so the tab gives its scope a find source that reads the file's text (
file-find.ts). It counts every match in the file and in the notes' words, scrolls to each match's line, and paints the drawn matches with the existing highlight sheet, repainting as Pierre draws rows. The current match is brought into view, opening a folded card or chip around it. - ⌥⌘K reads
pickedLines(): lines picked by pressing their numbers, or else the lines a text selection covers in the shadow root. - Recording mask: the root keeps
ph-no-capture.
Page source
PageSource draws through WholeFile (page.html, revision pageId@version). The host's page-source act now returns plain lines, because the panel colours the source.
What goes
- Panel:
themed-lines.tsxand its test; the bundle'scolourLines,colour-lines.tsand their tests (off the contract and out of the test stand-ins); the window helpers infile-tab-model.ts; the edge-row CSS; andCodeLinesView'sground,aboveandbelowprops. - Host: the page-source colouring.
- Unused export:
TASK_FILE_BOUNDSis no longer exported, since nothing outsidetask-wire.tsreads it now.
Coordination with the Code-cards PR
- Staying for now:
code-lines.tsx(CodeLineRows, used by the Code cards and inbox cards, andCodeLinesViewfor the refused-file snippet fallback and attachments) and the host'shighlightFile/highlightCodestay. - Who removes what: the Code-cards PR (PR 4) retires the host's colouring and switches the snippet fallback. My PR 5 moves attachments.
code-lines.tsxis deleted by whichever of those lands last.
Screenshots
Harness: the real FileTab over the built styles.css and diff bundle (worker pool on), showing this PR's own file-tab.tsx (380 lines) opened on lines 104–108 with one of Luke's notes.
| Light, wrapped | Dark, wrapped |
![]() | ![]() |
| Minimized: Luke's chip at the line's end | Chip pressed: the card under it |
![]() | ![]() |
| 380px, unwrapped and scrolled sideways: the card stays in view (time hidden under 440px) | Light, unwrapped, minimized |
![]() | ![]() |
Measured
The same harness and session as #4262, with a 5,000-line TSX file:
| today (windows, every line read stays drawn) | this PR | |
|---|---|---|
| Fling: long tasks · longest frame · frames | 705 ms · 117 ms · 207 | 0 ms · 16.8 ms · 242 |
| Opening ⌘F | draws all 5,000 lines: 1,121 ms of long tasks | searches the text: no drawing |
| Long tasks on load | 183 ms | 0 ms |
| Lines in the DOM | 500 → 5,000 | 54–67 |
| First lines painted | after host colouring of the window | plain, about 150 ms including the bundle import |
| First colour | the host's per-window colouring | later on big files, off-thread (accepted by the developer) |
| Reads | one per 500-line window, plus one per window as you scroll or find | 1 line + 1 raw (or the raw alone when pinned) |
Tests
- New
file-tab.test.ts(real diff bundle, jsdom):- The tab reads the first line for the head's commit, then the whole text at that commit, and draws every line.
- A tab pinned to a commit makes one raw read.
- The opened lines are lit, and Luke's note sits under its range's last line and lights its lines under the pointer.
- With Minimize on, the note sits behind Luke's chip, which shows it, and
Iturns the setting. - ⌘F counts every match in the file and its notes and steps through them.
- New
file-find.test.ts: matches on lines and in notes, in drawn order and case-insensitive. A query is matched as typed. Where a find starts. file-tab-model.test.ts: line counting.mention-lines.test.ts: reworked on Pierre. Picking lines by their numbers, then ⌥⌘K, gives a chip pinned to the commit. A selection ending at the next row's start pins one line. jsdom keeps no selection inside a shadow root, so the tab's own selection read is stood in for.file-view-menu.test.ts: View settings lists Minimize, and its rows apply on Pierre's drawing.page-pane.test.ts: the source draws on Pierre, coloured.- Host: the page-source test now expects plain lines.
- Shared helpers:
src/testing/whole-file.ts. - Deleted:
themed-lines.test.ts,colour-lines.test.tsandcolour-pool.test.ts, which test code that no longer exists.
Evidence
tsc --noEmit: panel, host, features and desktop all clean../scripts/oxlint.sh: 0 warnings, 0 errors.biome check: clean.knip: no findings../scripts/repository-checks.sh: passed.- vitest:
- Full panel run: 2,875 tests, one failure (the hint wording), since fixed.
- The touched panel suites again: 18 files, 160 passed.
- Host: 54 files, 463 passed.
apps/desktopbuild: passed.diff-view.jsis 244,394 gz,panel.js80,954 gz.
Invariants
- Every behavior change has the test that fails without it; no change-detector test.
- No new
astype assertion, raw async primitive, or Effect run outside the edges. -
PRIVACY.mdunchanged: the File tab's root keepsph-no-capture, and nothing new leaves the machine.
Made with Luke





