feat(panel): a text file opened over the window is drawn on Pierre, as every code is

Merged
DA
12d ago
+119-26

Summary

This is PR 5 of the approved plan Pierre for File tabs and code snippets (plan page). It is stacked on #4283, which adds WholeFile, and retargets to main once that merges.

  • What changes: a text file attached to a message, opened over the window (AttachmentViewer), is drawn through WholeFile (Pierre, virtualized, coloured in the worker pool) instead of CodeLinesView.
  • What it gains:
    • Syntax colours, which it never had: each line was drawn as one plain run.
    • View settings' wrap, line numbers, theme, font, size, line height and ligatures, like every other code in the window.
    • ⌘F over the whole text, through #4283's find source.
  • Line breaks: Windows line breaks are drawn as single breaks, so a \r\n file numbers its lines as GitHub does.
  • Unchanged: the card's header (Copy, Download, the arrows, the way out). The card keeps ph-no-capture.
  • code-lines.tsx stays for now: CodeLinesView is still used by the File tab's refused-file fallback, and CodeLineRows by the Code and inbox cards. PR 4 ("Pierre for Code and inbox cards") moves both, and code-lines.tsx is deleted by whichever of PR 4 and this PR lands last.

No service change.

Screenshots

LightDark
lightdark

Tests

New test in attachment-viewer.test.ts: a text file opened over the window is drawn by Pierre, numbered from its first line. A Windows line break opens no line of its own, and the card stays out of the recording.

Evidence

  • tsc --noEmit (panel): clean.
  • ./scripts/oxlint.sh: 0 errors.
  • biome check: clean.
  • knip: no findings.
  • vitest: attachment-viewer.test.ts and modals.test.ts, 30 passed.

Invariants

  • Every behavior change has the test that fails without it.
  • No new as type assertion, raw async primitive, or Effect run outside the edges.
  • PRIVACY.md unchanged.

Made with Luke