Skip to content

feat(desktop): open message attachments in a resizable file viewer - #5676

Open
carlos-v278 wants to merge 1 commit into
block:mainfrom
carlos-v278:feat/attachment-file-viewer
Open

feat(desktop): open message attachments in a resizable file viewer#5676
carlos-v278 wants to merge 1 commit into
block:mainfrom
carlos-v278:feat/attachment-file-viewer

Conversation

@carlos-v278

Copy link
Copy Markdown

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.

  • clicking a text-like attachment (markdown, source, plain text) opens the file-viewer panel; non-viewable types (PDF, archives, binaries) keep downloading on click, with the trailing icon as an explicit download affordance everywhere
  • the panel reuses the auxiliary-pane shell the thread and activity panels use, so it inherits drag-to-resize, width persistence and single-slot behaviour; opening another panel supersedes the viewer, and closing it hands the slot back to whatever was there
  • markdown renders through the existing renderer, code through Shiki; a copy and a download action sit in the panel header
  • viewability is decided from the filename extension rather than the imeta MIME, because text and source files carry no magic bytes and the relay stores them as application/octet-stream (see validate_file_content in buzz-media)
  • languages resolve through Shiki's own grammar ids and aliases instead of a hand-maintained table — Shiki is already a static dependency of the markdown renderer, so this adds no bundle weight

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 trip
  • tests/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 files
  • exercised by hand in just dev against a local relay, in a channel and in a thread

Screenshots follow in a comment.

Notes for reviewers

  • desktop/src/shared/ui/markdown/FileCard.tsx imports from @/features/fileViewer/*. That follows the existing precedent in markdown.tsx, which already imports from features/agents, features/messages and features/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 (the React.memo note in AGENTS.md).
  • The store's resetFileViewerStore() is wired into resetCommunityState() and documented in AGENTS.md, per the community-switching contract.
  • Agents currently cannot attach text or source files at all: buzz-cli's upload allowlist is images + mp4, so a .md sniffs as application/octet-stream and 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.

@carlos-v278
carlos-v278 requested a review from a team as a code owner August 12, 2026 15:44
@carlos-v278

Copy link
Copy Markdown
Author

Attachments in the timeline

Generic file cards, unchanged in shape. The trailing icon downloads; the card itself now opens the viewer for text-like files.

01-file-cards

Markdown

release-notes.md opened in the panel. The tab strip is the only title surface — a separate filename heading would just repeat it.

02-markdown

Code, second tab

Opening a second file adds a tab. Shell script highlighted through Shiki, which is already used for fenced code blocks in messages.

03-code-two-tabs

Resized, in context

The panel is the same resizable auxiliary pane as the thread and activity panels: drag the divider, double-click to reset.

04-resized-wide

Three files open

The 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.

05-three-tabs-overflow

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>
@carlos-v278
carlos-v278 force-pushed the feat/attachment-file-viewer branch from a0880b6 to 50d6f5e Compare August 13, 2026 14:10
@carlos-v278

Copy link
Copy Markdown
Author

Rebased onto current main (the branch was cut 17 commits earlier). No conflicts; single commit, sign-off preserved.

Re-verified on the new base:

  • just desktop-check — clean (exit 0)
  • pnpm exec tsc --noEmit — clean
  • pnpm test — 4,787 desktop unit tests pass
  • Playwright file-viewer, file-viewer-screenshots, file-attachment, thread-focus-mode — 24 pass

Screenshots in the comment above are unaffected (immutable commit-pinned URLs).

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.

Preview shared file attachments without leaving the app

1 participant