feat(panel): File tabs and page source on Pierre, Luke's notes as annotations with the minimized chip

Merged
DA
12d ago
+1493-1655

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 mounts mountFile in a ScrollArea. 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: MinimizedChip is generalized out of MinimizedThreads (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, LINES scope notes: true). Page source doesn't show it. Its hint reads "Or press I over the code".
  • I turns 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.tsx and its test; the bundle's colourLines, colour-lines.ts and their tests (off the contract and out of the test stand-ins); the window helpers in file-tab-model.ts; the edge-row CSS; and CodeLinesView's ground, above and below props.
  • Host: the page-source colouring.
  • Unused export: TASK_FILE_BOUNDS is no longer exported, since nothing outside task-wire.ts reads it now.

Coordination with the Code-cards PR

  • Staying for now: code-lines.tsx (CodeLineRows, used by the Code cards and inbox cards, and CodeLinesView for the refused-file snippet fallback and attachments) and the host's highlightFile/highlightCode stay.
  • 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.tsx is 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, wrappedDark, wrapped
lightdark
Minimized: Luke's chip at the line's endChip pressed: the card under it
chipshown
380px, unwrapped and scrolled sideways: the card stays in view (time hidden under 440px)Light, unwrapped, minimized
narrowlight 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 · frames705 ms · 117 ms · 2070 ms · 16.8 ms · 242
Opening ⌘Fdraws all 5,000 lines: 1,121 ms of long taskssearches the text: no drawing
Long tasks on load183 ms0 ms
Lines in the DOM500 → 5,00054–67
First lines paintedafter host colouring of the windowplain, about 150 ms including the bundle import
First colourthe host's per-window colouringlater on big files, off-thread (accepted by the developer)
Readsone per 500-line window, plus one per window as you scroll or find1 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 I turns 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.ts and colour-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/desktop build: passed. diff-view.js is 244,394 gz, panel.js 80,954 gz.

Invariants

  • Every behavior change has the test that fails without it; no change-detector test.
  • No new as type assertion, raw async primitive, or Effect run outside the edges.
  • PRIVACY.md unchanged: the File tab's root keeps ph-no-capture, and nothing new leaves the machine.

Made with Luke


Devin Review