feat(desktop): open message attachments in a resizable file viewer - #5676
feat(desktop): open message attachments in a resizable file viewer#5676carlos-v278 wants to merge 1 commit into
Conversation
Attachments in the timelineGeneric file cards, unchanged in shape. The trailing icon downloads; the card itself now opens the viewer for text-like files. Markdown
Code, second tabOpening a second file adds a tab. Shell script highlighted through Shiki, which is already used for fenced code blocks in messages. Resized, in contextThe panel is the same resizable auxiliary pane as the thread and activity panels: drag the divider, double-click to reset. Three files openThe strip scrolls when tabs overflow; the active tab is scrolled into view and reads as a filled chip. Its scrollbar is capped at a hairline so it cannot crowd the 36px header row. |
Shared attachments could only be downloaded, so reading a .md, a script or a config meant saving it and leaving Buzz. Clicking a viewable attachment now opens it in the right-side auxiliary pane, one tab per file, in channels, DMs and threads alike. Implementation notes: - Reuses the existing auxiliary-pane shell, so the viewer inherits the drag-to-resize handle, width persistence and single-slot behaviour of the thread and activity panels. Opening another panel supersedes the viewer and closing it hands the slot back. - Viewability is decided from the filename extension, not the imeta MIME: text and source files carry no magic bytes, so the relay stores them as application/octet-stream (see validate_file_content). - Highlighting resolves through Shiki's own grammar ids and aliases instead of a hand-maintained language table; Shiki is already a static dependency of the markdown renderer. - Bytes travel over the existing fetch_media_bytes IPC command, because a webview fetch to the relay is refused by Cloudflare Access. - Binary versus text is sniffed from the bytes (NUL in the head), never from the sender-controlled MIME. - Tabs live in a module-level store because the open action fires inside the memoized markdown renderer, where a prop callback would re-render the whole timeline; the store resets on community switch. Non-viewable types keep their previous download-on-click behaviour. Signed-off-by: carlos--33 <carlosvieira278@gmail.com>
a0880b6 to
50d6f5e
Compare
|
Rebased onto current Re-verified on the new base:
Screenshots in the comment above are unaffected (immutable commit-pinned URLs). |





Summary
Attachments were download-only: reading a shared
.md, script or config meant saving it and leaving Buzz. This opens viewable attachments in the right-side auxiliary pane instead, one tab per file, in channels, DMs and threads alike.application/octet-stream(seevalidate_file_contentinbuzz-media)Related issue
Fixes #5675.
Testing
pnpm test— 4,745 desktop unit tests pass, including new coverage for the tab store, the extension/MIME classification, and the byte-level decode (NUL sniff at the sniff-window boundary, preview size cap)pnpm exec playwright test tests/e2e/file-viewer.spec.ts— 4 new specs: open-instead-of-download, tab add/switch/close with neighbour activation, download-on-click preserved for non-viewable types, and the thread ↔ viewer slot round triptests/e2e/file-attachment.spec.ts,messaging.spec.ts,thread-focus-mode.spec.ts,threadpane-ultrawide.spec.ts,channel-controls.spec.ts,terminal-wheel.spec.ts— 75 existing specs pass (the auxiliary-pane chain and the shared scrollbar CSS this touches)pnpm exec tsc --noEmit, Biome, and the file-size / px-text guards are clean on the changed filesjust devagainst a local relay, in a channel and in a threadScreenshots follow in a comment.
Notes for reviewers
desktop/src/shared/ui/markdown/FileCard.tsximports from@/features/fileViewer/*. That follows the existing precedent inmarkdown.tsx, which already imports fromfeatures/agents,features/messagesandfeatures/profile. The open action is invoked from inside the memoized markdown renderer, so routing it through a module-level store rather than a prop callback is deliberate — a callback prop there re-renders the whole timeline (theReact.memonote in AGENTS.md).resetFileViewerStore()is wired intoresetCommunityState()and documented in AGENTS.md, per the community-switching contract.buzz-cli's upload allowlist is images + mp4, so a.mdsniffs asapplication/octet-streamand is rejected client-side before any request. The relay would accept it. That is out of scope here and feat(cli): allow generic file uploads including zip #4880 already addresses it; this PR makes those attachments readable once they can be sent.