Skip to content

feat(attachments): in-app previews for text, Markdown and CSV/TSV (TASK-4074) - #2310

Merged
xarmian merged 18 commits into
mainfrom
wren/task-4074-attachment-previews
Oct 25, 2026
Merged

xarmian merged 18 commits into
mainfrom
wren/task-4074-attachment-previews

Conversation

@xarmian

@xarmian xarmian commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

In-app previews for text, code, Markdown and CSV/TSV attachments (TASK-4074, slice (a) of IDEA-4073). Until now these types offered only a download.

What's new

  • Renderer registry (surfaceRenderers.ts, TASK-2476). 'text' now covers exactly text/plain, text/markdown, application/json, application/yaml and application/toml. A new 'table' renderer covers exactly text/csv and text/tab-separated-values. Both allowlists are MIME-exact, never matched by category. text/xml, application/xhtml+xml, text/html, text/javascript, image/svg+xml and an unresolved MIME claim no renderer, and a test pins each one.
  • internal/attachments/mime.go is untouched. Its inlineSafe allowlist (the server's Content-Disposition: inline set) is unchanged in meaning and in bytes. The diff touches no Go at all. Previews fetch bytes through the normal attachment API (viewerTextLoader) and never navigate to the raw URL.
  • Size caps, applied before parsing. These use IDEA-2712's two gates: a metadata gate on size_bytes refuses without any request, and the response gate cancels a stream that runs past the cap. Text is capped at 1 MiB (unchanged) and CSV/TSV at 2 MiB.
  • CSV/TSV table (AttachmentTablePreview.svelte, with an in-repo parser in csv.ts and no new dependency). It has a sticky header and text-only cells: no HTML, no links, no formula evaluation. The parser never throws. A malformed file renders what parsed, plus "Could not parse past row N". Blank lines are skipped. A quote that doesn't start a field is literal. "Showing N of M rows · N of M columns" is exact, because the parser counts every record. Rendering is stepped by cell count: the first render and each "Show more rows" step mount at most 5,000 cells (2,500 on the phone sheet). Steps append without re-mounting existing rows, up to a 1000-row × 50-column ceiling, with cells truncated at 500 code points.
  • Markdown renders through the same sanitizeMarkdownHtml that item content uses, with a Rendered/Raw toggle. A test feeds <script>, javascript: links and on*= attributes to both the helper and the mounted arm.
  • Text, code, JSON and logs show in a monospace <pre> with a Wrap toggle. Wrap is on by default for text and off for JSON/YAML/TOML.
  • Mobile and keyboard. Text and table previews fill the phone sheet and scroll by touch. The table's scroll region is focusable and labelled. While it can scroll sideways, Left/Right scroll it instead of paging the gallery.

Benchmark (vitest/jsdom on the dev box, medians of 5). This is the evidence for the cell-stepped cap. jsdom is usually slower than a browser, so these numbers are conservative.

workload median
parse 2 MiB / 50,000 rows, counted to the end 16.9 ms (budget 50 ms)
mount 2,500 cells (50 × 50) 86 ms
mount 5,000 cells (100 × 50) 119 ms (budget 200 ms)
mount 8,000 cells (160 × 50) 192 ms
mount 10,000 cells (200 × 50) 232 ms
mount 50,000 cells (1000 × 50) ~1.1-1.2 s (over budget, hence the stepping)

Follow-ups: PDF (TASK-4075) and spreadsheets (TASK-4076) are separate slices. Sticky preview controls and text-arm arrow keys are TASK-4078. A CLI text preview was deferred.

Evidence: 28 of 28 named mutants were shown red at committed heads, including one for the updated E2E touch-action pin. The CONVE-190 gates passed at the final head, including npm run check, the full vitest suite and npm run build. Four review rounds: three covered the whole diff, and one was scoped to the E2E pin fix.

E2E note: this PR changes the text card's touch-action from pan-y to pan-x pan-y, so wide tables and unwrapped lines can pan sideways by touch. The IDEA-2712 spec e2e/attachment-text-preview.spec.ts is updated to match. No viewer gesture collides with it: pan, pinch and double-tap arm only over a decoded bitmap. A flake seen once in CI, e2e/pane-controller.spec.ts:325, is unrelated and filed as BUG-4080.

https://claude.ai/code/session_017ofcsFmgnoVDPxeHwuxCjg

The card's touch-action is now pan-x pan-y, so a wide table or an
unwrapped line can pan sideways; the IDEA-2712 spec still pinned pan-y.
No viewer gesture collides: pan/pinch arm only over a decoded bitmap
and nothing swipes between attachments. The stage-is-not-none leg
stays.

Claude-Session: https://claude.ai/code/session_017ofcsFmgnoVDPxeHwuxCjg
…n-y (TASK-4074)

Comment-only: two CSS comments still named the pre-TASK-4074 pan-y.

Claude-Session: https://claude.ai/code/session_017ofcsFmgnoVDPxeHwuxCjg
@xarmian
xarmian marked this pull request as ready for review October 25, 2026 08:17
@xarmian
xarmian merged commit e09b11c into main Oct 25, 2026
12 checks passed
@xarmian
xarmian deleted the wren/task-4074-attachment-previews branch October 25, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant