feat(panel): Code tab and inbox code cards on Pierre, Luke's note under the lines

Merged
DA
12d ago
+909-832

PR 4 of the approved "Pierre for File tabs and code snippets" plan. Built on #4283 (File tab and page source on Pierre), now merged; this PR targets main.

What it does

  • Code tab cards are drawn by @pierre/diffs (code-snippet.tsx, code-snippet-model.ts).
    • Each card's lines become one context-only hunk starting at the card's first line (@@ -N,M +N,M @@), drawn through the bundle's mount. Review-thread previews already use this path, so line numbers match the file's.
    • Drawn unified and excerpt, with no indicators, backgrounds or inline diff. The lines Luke pointed at are lit through setSelectedLines.
    • Follows View settings: theme, font, size, line height, ligatures, wrap and line numbers.
    • Mounted as the card comes within 800px of the view; until then it holds roughly the height its lines will take.
    • Coloured in the kept worker pool, through a new optional pooled on DiffViewProps. This is additive: mount's default is unchanged, so thread previews are not affected.
  • Luke's note moves under the last pointed line as a Pierre annotation, drawn by #4283's FileNoteAnnotation on the shared AnnotationCard:
    • Luke's mark, his name and the time.
    • ⋯ with View in conversation and Copy link, from LINK_PLACE.TURN.
    • It folds.
    • Hover or keyboard focus lights its lines.
    • With Minimize inline comments on, the note becomes #4283's MinimizedChip floating at the end of its line. Hovering the chip lights the lines too.
    • The card's header (path, copy, open file) stays React, outside Pierre.
  • The inbox code card (inbox-code-card.tsx) uses the same component. So does the File tab's refused-file snippet fallback, which still draws every note that falls in the snippet.
  • Prefetch: while a call stands on the open task, the diff bundle's module starts loading (usePrefetchDiffBundle), so the first card doesn't wait on the import. Only the module is imported; the worker pool starts on the first card's mount.
    • Cost to a call that never shows code: one import of diff-view.js (822 KB minified, about 243 KB gzipped). Here that was about 150 ms of fetch, parse and eval with no long task, plus about 7 MB of heap, once per window.

What retires

  • highlightCode in the host (task-code.ts). TaskCodeShowing.code now carries plain lines (ShownCode), still cut down by codeExcerpt. compose-tasks.ts no longer colours shown code.
  • The old card drawing: CodeLines in code-pane.tsx, the note above the lines (.code-card-note and its CSS), and copiedLines' token joining.
  • notesAfter (file-note.tsx) and useLinesLook (code-lines.tsx): their last caller was the snippet fallback. Their tests now draw FileNoteAnnotation. The two tests that only checked notesAfter's row placement are deleted; placement is covered by this PR's code-snippet.test.ts and #4283's whole-file tests.
  • code-lines.tsx and its test, since #4285 landed first: runColours moves into transcript-markdown.tsx, which still colours fences; the .code-lines*/.code-line* CSS, .file-note's dead margins and the --code-line-number*/--code-pointed tokens go; so do the now-unused CodeType and isPointed exports.
  • Not removed here: highlightFile and the TASKS_FILE colouring step, still used by the File tab's commit probe and the inbox card's read. The File-tab agent's follow-up removes them.

Measured

Throwaway harness, as in the plan: esbuild with production defines, the build's own highlight worker, the built styles.css, headless Chromium, a 720px pane, 10 cards, median of 3. This PR against the base's card drawing (main). Main's colours are computed by the host before the view arrives, so main shows 0 ms of colouring in the window. The host spent 584 ms on the first card (highlighter load) and 1–213 ms on each card after.

mainthis PR
During a call (one card every 400 ms): first card drawn61 ms210 ms (bundle import, without prefetch)
… each later card drawn after it lands0 ms19 ms
… each card coloured after it lands0 ms in window (host: 5–200 ms)115 ms median, 426 ms for the first (cold workers)
Opening the tab with 10 cards: lines drawn125 ms301 ms
Long tasks after the first card0 ms0 ms
Long tasks scrolling the Code tab (60 px/frame, there and back)0 ms0 ms
Longest frame while scrolling29 ms30 ms
panel.js / diff-view.js291.3 KB / 821.9 KB291.3 KB / 822.0 KB

These are Linux headless numbers; the plan measured the import at about 40 ms on a Mac. With the prefetch, the import happens before the first show_code lands.

Screenshots

Headless Chromium on the real built bundle and stylesheet (the harness above). Light and dark, wide (760px) and narrow (380px).

Card, light, wide: note under line 499Card, dark, wide
Card, light, narrow, wrappedCard, dark, narrow, wrapped
Note folded, lightNote folded, dark, narrow
Minimized chip, lightMinimized chip, dark, narrow
Chip pressed, note shown, darkUnwrapped, dark, narrow: the note keeps to the pane
Hover on a whole-file card's note lights every line, lightHover lighting, dark, narrow
Inbox code card, lightInbox code card, dark, narrow

On a card that points at lines, the note is on exactly those lines, which are already lit at rest, so hovering it changes nothing visible there; that stays by design.

Tests

code-snippet.test.ts uses the real bundle in jsdom:

  • the hunk's numbers match the snippet's, and the pointed lines are lit
  • the note sits in the slot under the last pointed line and is said once
  • a whole-file showing's note sits under the last line, and hovering it lights every line
  • ⋯ View in conversation and Copy link work
  • the note folds
  • with Minimize on, the chip takes the note's place and shows it when pressed
  • the inbox card draws the lines it read with the named ones lit
  • the diff bundle starts loading once a call stands, and not before

Host test updated: shown code arrives with plain lines.

Checks run

Per-package tsc (panel, host, voice, features, desktop, web); the panel's tests (190 files under src/tasks, src/diff-view and src/desktop) plus the host's and features' affected tests; biome, ./scripts/oxlint.sh, pnpm knip, ./scripts/repository-checks.sh; node scripts/build.mjs. pnpm check:affected ran out of memory on the panel's typecheck, so that and the others were run one by one.

No service change.

Made with Luke


Devin Review