Skip to content

feat(desktop): open markdown attachments in an in-app viewer panel - #6731

Open
rileycrane wants to merge 18 commits into
mainfrom
fizz/md-viewer
Open

rileycrane wants to merge 18 commits into
mainfrom
fizz/md-viewer

Conversation

@rileycrane

Copy link
Copy Markdown

Summary

Port of berd's markdown-link viewer into Buzz desktop. Clicking a .md/.markdown/.mdx attachment card now opens a right-side auxiliary panel rendering the document with the same markdown pipeline as chat messages, instead of forcing a download.

What's in it

  • MarkdownDocPanel — right auxiliary panel with Preview / Code toggle, download button, and close. Hosted in ChannelScreen alongside threads/profiles; participates in narrow-layout single-panel mode and Esc-to-close like the other aux panels.
  • FileCard affordance — markdown attachments show an open-panel icon; the card pre-gates on advertised size so a >2 MiB doc never double-fetches just to refuse.
  • Panel state in URL (doc/docName params via useChannelPanelHistoryState) — back/forward and reload restore the open document.
  • Native 2 MiB cap — the viewer fetches through a markdown-specific fetch_markdown_doc_bytes Tauri command whose cap is owned by Rust: oversized Content-Length is refused before the body is read, and the streamed byte count aborts mid-transfer when the header is missing or dishonest (a forged/absent imeta size can no longer buy a 50 MiB download + IPC copy through the generic media path). Both enforcement points are pure, unit-tested helpers. The command deliberately skips MIME sniffing — bytes are strictly UTF-8-decoded and rendered escaped, and a legitimate .md may begin with bytes that sniff as a blocked type.
  • markdownDocFile.ts — strict UTF-8 decode + matching frontend cap; binary-or-oversized files fall back to a friendly error + Download.
  • useChannelPaneOpeners — pane-open handlers extracted from ChannelScreen; upstream's requireThreadEditResolution() guard runs first in every opener (same contract as useChannelProfilePanel).
  • Nullable MarkdownDocViewerProvider — surfaces without a panel host (forum/projects) keep plain download behavior.
  • Aux-pane split (4th commit) — rebasing onto main pushed ChannelPane.tsx/ChannelScreen.tsx over the frozen file-size ratchet, so the agent-session and markdown-doc pane assemblies are extracted into AgentSessionAuxiliaryPanel.tsx + MarkdownDocAuxiliaryPanel.tsx (pure JSX moves, no behavior change; repo policy: split, never bump).
  • Selection-tray crash fix (5th commit, pre-existing upstream bug hit while dogfooding this branch) — tiptap v3's editor.view accessor throws until EditorContent attaches the view, and SelectionFormattingTray's wiring effect read editor.view.dom unguarded; navigating to a channel in a dev build could crash the whole channel route into TanStack Router's error screen. The tray now tracks view availability (probe + tiptap mount/unmount events) and gates its DOM wiring on it, with a regression test pinning the probe.

Why it works differently from berd — the Nostr/relay foundation changed three design points:

  1. Filename is the only identity. The relay content-addresses uploads ({sha256}.bin, application/octet-stream) and markdown has no magic bytes — the ".md-ness" survives only in the NIP-92 imeta filename tag, so classification is filename-based.
  2. Authenticated fetch, not file reads. Relay media URLs 401 in a plain browser (Blossom auth), so content goes through an authenticated Tauri command (SSRF-guarded to the relay /media/ origin). This is also why in-app rendering is the only viable UX — "open in browser" can never work.
  3. Content-addressed = immutable — doc content is cached with staleTime: Infinity per session.

No DOMPurify dance needed: buzz's markdown pipeline has no raw-HTML support (no rehype-raw), so doc HTML renders escaped and javascript: hrefs are stripped.

Known limit (documented in-code): Code view skips syntax highlighting past 150 lines — inherited shiki perf guard shared with chat code blocks.

Related issue

N/A — none found (searched issues/PRs for "markdown viewer": no matches).

Testing

At tip aeffcce25, based on main f6e6617a9 (no file overlap with newer main commits):

  • Tauri Rust suite green (desktop-tauri-checks lane), including 4 new cap tests: over-cap Content-Length refused, at-cap/absent admitted, streamed cutoff at exactly 2 MiB + 1 with nothing past the cap buffered; cargo clippy -D warnings + cargo fmt --check clean
  • Desktop unit suite: 5442 passed / 0 failed (includes 8 markdownDocFile decoder tests + 2 tray view-guard tests)
  • Playwright e2e: markdown-doc-viewer.spec.ts (upload → open panel → Preview/Code → download fallback → non-md regression → reload/back-forward contract → forged-size over-cap fallback) + file-attachment.spec.ts baseline: 21/21 passed
  • desktop-check (biome/px-text/pubkey-truncation), desktop-typecheck, file-size-check: clean
  • All 7 pre-push lanes green at push time

Screenshots: post via scripts/post-screenshots.sh <PR#> ~/.buzz/.scratch/md-viewer after the PR exists (repo policy — no relay media URLs in PR bodies).

@rileycrane
rileycrane requested a review from a team as a code owner August 24, 2026 22:50
rileycrane added a commit that referenced this pull request Aug 24, 2026

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Reviewed head aeffcce256f20e30b45d92d0a8db8216916a31a8 against current base 30d2fc52f96138311f2006627ffc1a6d5ff1865b (merge-base f6e6617a9dcc2308d5039f8afaab974b49fb9577). Requesting changes for one channel-crash regression and one narrow-layout accessibility defect.

I also traced the attachment producer/consumer surfaces, download fallbacks, pane lifecycle, native same-relay/auth boundary, redirect policy, streaming 2 MiB cap, strict UTF-8 decode, and safe markdown rendering. I found no additional blocking defect there. All current GitHub checks pass, but they do not exercise either behavior below faithfully.

Comment thread desktop/src/features/messages/ui/SelectionFormattingTray.tsx Outdated
Comment thread desktop/src/features/channels/ui/MarkdownDocPanel.tsx
rileycrane added a commit that referenced this pull request Aug 24, 2026
Review P1 (#6731): tiptap's pre-mount editor.view is a truthy Proxy —
only reading a property off it throws — so the previous
Boolean(editor.view) probe reported the view as mounted and let the
tray's wiring effect repeat the channel-crashing editor.view.dom read.
Probe editor.view.dom instead, extract the tracking into a shared
useEditorViewMounted hook, and replace the shallow throwing-getter test
with a Proxy-faithful lifecycle suite: pre-mount, mount, unmount,
remount, an already-mounted editor at subscription time, stale events
from a replaced editor, and listener cleanup.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rileycrane added a commit that referenced this pull request Aug 24, 2026
…own row

Review P2 (#6731): in the narrow single-panel layout, opening a
document unmounted the focused attachment card and closing unmounted
the focused panel, dropping keyboard/screen-reader users onto <body>
(then the remounted channel's composer autofocus). The panel now moves
focus to its close control on mount and returns it to the opening card
on unmount, matched by data-doc-url since the original element was
remounted meanwhile; the restore yields to any control that already
claimed focus, overriding only <body> and the composer autofocus. A
narrow-layout e2e covers open-focus and Escape-restore.

Also moves the Preview/Code picker out of the title row onto its own
pinned row below the header (review feedback: the shared row squeezed
the filename out), inside the chrome-padded body since the header band
overlays anything placed after it in the header slot.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rileycrane added a commit that referenced this pull request Aug 25, 2026
@rileycrane

Copy link
Copy Markdown
Author

Updated for the review-feedback commits (view picker moved to its own row below the title).

Preview view

Markdown attachment card with the open-in-panel affordance; the document renders in the right auxiliary panel with the Preview/Code picker on its own row, so the filename keeps the title row.

md-viewer-preview

Code view

Raw source with syntax highlighting; download fallback stays in the header.

md-viewer-code

rileycrane added a commit that referenced this pull request Aug 25, 2026

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Reviewed the follow-up at c4175f4ee50733b071d6123c13c956605308ea44. The Tiptap lifecycle correction is sound: the new probe exercises .dom on the real pre-mount truthy proxy, mount/unmount events match actual view availability, replacement and listener cleanup are covered, and the targeted narrow-layout focus test passes.

The focus-restoration follow-up still has one blocking P2 edge case: a document URL is not a unique opener identity. I reproduced the duplicate-attachment case below in the E2E app; opening the second card and closing the panel focuses the first card. Requesting a stable per-invocation restoration identity and regression coverage.

Comment thread desktop/src/features/channels/ui/markdownDocFocus.ts Outdated
rileycrane added a commit that referenced this pull request Aug 25, 2026
Review P1 (#6731): tiptap's pre-mount editor.view is a truthy Proxy —
only reading a property off it throws — so the previous
Boolean(editor.view) probe reported the view as mounted and let the
tray's wiring effect repeat the channel-crashing editor.view.dom read.
Probe editor.view.dom instead, extract the tracking into a shared
useEditorViewMounted hook, and replace the shallow throwing-getter test
with a Proxy-faithful lifecycle suite: pre-mount, mount, unmount,
remount, an already-mounted editor at subscription time, stale events
from a replaced editor, and listener cleanup.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rileycrane added a commit that referenced this pull request Aug 25, 2026
…own row

Review P2 (#6731): in the narrow single-panel layout, opening a
document unmounted the focused attachment card and closing unmounted
the focused panel, dropping keyboard/screen-reader users onto <body>
(then the remounted channel's composer autofocus). The panel now moves
focus to its close control on mount and returns it to the opening card
on unmount, matched by data-doc-url since the original element was
remounted meanwhile; the restore yields to any control that already
claimed focus, overriding only <body> and the composer autofocus. A
narrow-layout e2e covers open-focus and Escape-restore.

Also moves the Preview/Code picker out of the title row onto its own
pinned row below the header (review feedback: the shared row squeezed
the filename out), inside the chrome-padded body since the header band
overlays anything placed after it in the header slot.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rileycrane and others added 7 commits August 25, 2026 13:29
Shared .md/.markdown/.mdx attachments now open in a right auxiliary
panel with a Preview/Code toggle instead of triggering a download,
mirroring berd's markdown-artifact viewer.

Nostr-specific adaptations vs berd: the relay stores markdown as
application/octet-stream ({sha256}.bin — no magic bytes), so
classification is by imeta filename only; relay media URLs require
Blossom auth (401 in plain browsers), so the panel fetches through the
authenticated fetch_media_bytes Tauri command; content-addressed URLs
allow staleTime: Infinity caching. Panel state is URL-search-param
backed (doc/docName) like the other channel panes. Strict UTF-8 decode
with a 2 MiB cap keeps binary/oversized files on the download path,
and surfaces without a panel host (forum, projects) keep download
behavior via a nullable context.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Extract pane-open handlers (manage channel + markdown doc) into
  useChannelPaneOpeners, bringing ChannelScreen back under the
  1000-line file-size ratchet (1016 -> 984).
- Include the markdown doc panel in hasAuxiliaryPanel so narrow
  layouts enter single-panel view (and Escape-to-close engages)
  the same way sibling panes do, instead of squeezing the timeline.
- Key MarkdownDocPanel by URL so opening a different document resets
  the Preview/Code toggle.
- Pre-gate the open-in-viewer affordance on the advertised imeta size
  so a >2MiB .md is not fetched twice just to be refused (decoder
  still re-checks — imeta size is untrusted).
- Document the 150-line syntax-highlight cap inherited from
  SyntaxHighlightedCode in the Code view.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fetch

Routing the viewer through the generic fetch_media_bytes command meant an
attachment with a forged or absent imeta size could occupy up to the 50 MiB
download cap in memory and IPC before the frontend decoder rejected it. Add
a markdown-specific fetch_markdown_doc_bytes command whose 2 MiB cap is
owned by Rust: an oversized Content-Length is refused before the body is
read, and the streamed byte count aborts mid-transfer when the header is
missing or dishonest. The untrusted imeta pre-gate stays UX-only.

The cap checks are extracted into pure helpers (declared_length_refusal_error,
append_chunk_within_cap) so both enforcement points are unit-tested without
a Tauri State; media_download.rs tests move to a sibling file per the repo's
_tests.rs convention to stay under the per-file line cap. The panel maps the
native refusal onto the existing too-large download fallback, and new e2e
coverage proves the forged-size fallback plus the reload/back-forward
contract. The viewer command skips MIME sniffing deliberately: bytes are
strictly UTF-8-decoded and rendered escaped, and a legitimate .md may begin
with bytes that sniff as a blocked type.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rebasing onto current main tripped the repository file-size ratchet:
main's own growth left ChannelPane.tsx at 1011 lines (frozen) and
ChannelScreen.tsx at 1010, so the viewer's pane wiring no longer fits
in-file. Extract the agent-session and markdown-doc pane assemblies into
AgentSessionAuxiliaryPanel and MarkdownDocAuxiliaryPanel (following the
ChannelManagementAuxiliaryPanel pattern), leaving both files under their
caps. No behavior change.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…own row

Review P2 (#6731): in the narrow single-panel layout, opening a
document unmounted the focused attachment card and closing unmounted
the focused panel, dropping keyboard/screen-reader users onto <body>
(then the remounted channel's composer autofocus). The panel now moves
focus to its close control on mount and returns it to the opening card
on unmount, matched by data-doc-url since the original element was
remounted meanwhile; the restore yields to any control that already
claimed focus, overriding only <body> and the composer autofocus. A
narrow-layout e2e covers open-focus and Escape-restore.

Also moves the Preview/Code picker out of the title row onto its own
pinned row below the header (review feedback: the shared row squeezed
the filename out), inside the chrome-padded body since the header band
overlays anything placed after it in the header slot.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atch

The panel-close focus restore located the opener card by document URL
alone, but the same attachment can appear in several messages — several
cards, one URL — so querySelector always returned the first DOM match
and closing a panel opened from a later duplicate focused the wrong
card.

FileCard now passes the clicked element through the open call, and the
channel pane opener records it as its DOM-order index among the cards
sharing that URL — a per-invocation identity that survives the
narrow-layout unmount/remount where an element reference cannot. The
restore consumes the record (deep-link/reload opens fall back to the
first match, as before), clamps when the recorded card is gone, and
still yields to any control that already claimed focus.

Covered by a JSDOM unit suite for the record/restore contract and a
duplicate-URL narrow-layout keyboard e2e.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ChannelScreen is over the file-size ratchet on main, and wrapping its
140-line channel-pane JSX in MarkdownDocViewerProvider re-indented the
block past the line limit. GuardedChannelPane — the wrapper the channel
branch already mounts and the forum branch never does — is the natural
host: same provider scoping (forum FileCards keep download behavior),
no indent shift, and ChannelScreen shrinks back under its allowance.

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Co-authored-by: Fizz <d4ec90109b43092b0ec94cf136850915a3732a29ab3089a1dec43d2ed43dd6eb@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* origin/main: (38 commits)
  fix(db): disable heartbeat vacuum truncation (#6898)
  chore(deps): update rui314/setup-mold digest to 7e4f20a (#6663)
  chore(deps): update dependency vitest to v4.1.11 (#6667)
  chore(deps): update dependency @tanstack/react-virtual to v3.14.10 (#6666)
  chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
  fix(projects): allow owners to delete agent projects (#6533)
  Fade expanded video controls on hover (#6926)
  fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
  fix(client): resurface hidden DMs from live activity (#6885)
  fix(desktop): keep the draft space when typing right after a mention pick (#6875)
  broker: define the agent-to-broker action contract (#6742)
  fix(desktop): keep project sheets independent from threads (#6901)
  Add gated security reviews (#6816)
  fix(desktop): accent-colored mention badges that count thread mentions (#6900)
  Add Buzz benchmark evaluation layers (#6823)
  fix(desktop): show edited head content in thread panel (#6887)
  fix(desktop-tooltip): increase surface contrast (#6897)
  Deduplicate ACP thread prompt context (#6706)
  Apply access policy when reusing channel agents (#6838)
  feat(sidebar): prioritize unread DMs in overflow navigation (#6842)
  ...

Signed-off-by: Sol <478bb5a31222ea2b28a3d1afb8b1d598940628f19c2a87efc3c4b822299eeec6@buzz.block.builderlab.xyz>

# Conflicts:
#	desktop/src/features/channels/ui/ChannelPane.tsx
#	desktop/src/features/channels/ui/ChannelPane.types.ts
#	desktop/src/features/channels/ui/ChannelScreen.tsx
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 78618804ec86a014524ad7d1fb55928e8f5c3edf...77974773ad7451074ffe3ce6db7d95a1f044691c.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review 77974773ad7451074ffe3ce6db7d95a1f044691c to authorize a new review.
Any previous review applies only to its recorded range.

Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

Reviewed head 263e1aeed116d9bd02839ecbea7289c335f188bb against base 0ccf934b88f610f5f235862ecd51e2dcbae2cb74. Requesting changes for three reproduced P2 UI/accessibility defects below.

The original static duplicate-card and narrow-layout focus cases now pass, and the branch inherits main's Tiptap lifecycle fix. The remaining issues are dynamic opener identity, thread-origin return focus, and project-home auxiliary-pane arbitration.

Validation: source/contract review plus focused Chromium workflows against a fresh current-head E2E build with the mock Tauri/live-event bridge. I independently reran the live-deletion and thread-origin reproductions and reproduced the project-home conflict. This is not native WKWebView or live-relay validation. Focused decoder/attachment tests reported 91 passes; a separate markdown renderer test file could not load because the shared review checkout lacks linkifyjs. No fresh CI or full-suite/native pass is claimed.

Comment thread desktop/src/features/channels/ui/channelPaneAuxiliaryLayout.ts
Comment thread desktop/src/features/channels/ui/markdownDocFocus.ts Outdated
Comment thread desktop/src/features/channels/ui/useChannelPaneOpeners.ts
@wesbillman

Copy link
Copy Markdown
Collaborator
Screenshot 2026-08-29 at 7 30 16 AM

The icon and title here seem a bit misaligned.

Carl added 3 commits August 31, 2026 13:38
* origin/main: (32 commits)
  fix(acp): wake agents from workflow messages (#6953)
  feat: render agent avatars as squircles (#7106)
  fix(ci): salvage Codex review output on PTY-shutdown hang (#7042)
  fix: retrieving cold memories; add regression task (#6950)
  Enforce NIP-OA authorization time bounds (#7004)
  feat(db): configurable writer session timeouts (lock, idle-txn, statement) (#6229)
  feat(desktop): use segmented controls for channel creation (#6845)
  feat(buzz-agent): surface stop reason and silent-turn WARN in telemetry (#7038)
  fix(desktop): surface channel history load failures (#7013)
  fix(composer): polish automatic mentions (#6956)
  fix(desktop): resolve bundled sidecar on cheap path and bound login-shell spawns (#6904)
  perf(mobile): reduce cold startup and channel rendering delays (#6996)
  feat(mobile): push notifications MVP (#6269)
  refactor(db): extract domain stores from database runtime (#6987)
  feat(desktop): add team sharing to community catalog (#3995)
  Refresh mobile utility surfaces and theme picker (#6944)
  fix(desktop): complete project empty and context states (#6980)
  Fix mobile jump-to-latest flicker (#6807)
  refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
  refactor(db): split channel membership store (#6782)
  ...

Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>

# Conflicts:
#	desktop/src/features/channels/ui/ChannelPane.tsx
Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>
Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>
Signed-off-by: Carl <1f967df5817845a2a5d74c82ac3098dea0bb7342665352af6643c5ac5c878dd3@buzz.block.builderlab.xyz>
@delkc

delkc commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

The markdown files aren't getting this functionality from the Inbox view, but seems like a pretty easy fix (my agent patched it locally for me). Ideally it would be great if we can have it work from any view.

Having the file fully replace a thread in that right panel when you open from there is a little jarring, so I wonder if it could either push the thread into the center like it does when you hit the 3 dots overflow menu in the top right OR if it could pop out over view the view like it does when you hit the "Expand thread" action in the top left of the panel?

Signed-off-by: Clay Delk <clay.delk@gmail.com>
@delkc

delkc commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🤖 I tested this branch against the team relay and put the follow-up fixes into one atomic commit for consideration:

Suggested commit: 032d2bd4d

git cherry-pick 032d2bd4d

What this changes

  • Makes the Preview action stable. Removes the renderer-side relay-origin timing gate that caused valid Markdown cards to alternate between Preview and Download. The native fetch command remains the authoritative relay-origin and 2 MiB security boundary.
  • Handles where the file was opened. A document opened from the center timeline uses the normal right panel. Opening from an existing thread preserves that mounted thread rather than replacing it.
  • Adds responsive thread behavior. When the channel container can fit two saved panel widths plus a 300px conversation column, the thread shifts left and the document opens as a third panel. Below that threshold, the document stacks over the thread at the existing right-panel width.
  • Keeps Inbox local. Opening a document from Inbox no longer navigates into the channel route. On wide windows it becomes a third Inbox panel; on constrained windows it stacks over the Inbox detail. Closing restores the same Inbox conversation.
  • Preserves focus and state. Closing returns to the invoking attachment where possible, while the underlying thread/Inbox detail remains mounted with its scroll and reply state intact.
  • Adds regression coverage for thread-reply attachments and responsive pane arbitration.

Verification

  • TypeScript typecheck
  • Repository file-size gate
  • 52 focused unit tests
  • Biome / git diff --check
  • Manual team-relay testing across timeline, thread, DM, and Inbox contexts

I used a commit rather than inline suggestion blocks because the change adds several coordinated files and needs to apply atomically.

* origin/main: (77 commits)
  fix(acp): pace targeted overflow recovery on consumer capacity (#7325)
  fix(link-preview): keep composer fetches user-paced (#7211)
  feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189)
  fix(agent): route GPT-5+ model-service FQNs to Responses (#7358)
  fix(buzz-acp): wake held ACP threads and fence forked sessions (#7340)
  fix(mobile): style inline code with the app mono face (#6631)
  chore(release): release Buzz Desktop version 0.5.23 (#7381)
  fix(desktop): keep packaged frontendDist relative so Windows embeds assets (#7177)
  fix(sidebar): simplify unread indicators and emphasize priority activity (#7134)
  Add generic information-flow control core (#7293)
  feat(buzz-acp): update base prompt; add buzz context and skills to Pi agents (#7335)
  fix(desktop): restore mention chip identity icons (#7338)
  Persist video playback speed preference (#7336)
  Verify ACP relay events before prompt routing (#7010)
  fix(buzz-acp): bound busy-owner hold to prevent cross-channel starvation (#7337)
  feat(desktop): invite owned agents from standalone forums (#7125)
  fix(desktop): authorize remote mentions at publication (#7124)
  fix(acp): rename system tag to agent-instructions (#7332)
  fix(desktop): bind duplicate mention selections to exact recipients (#7133)
  refactor(relay): extract NIP-29 membership authorization (#7285)
  ...

Signed-off-by: Sol <478bb5a31222ea2b28a3d1afb8b1d598940628f19c2a87efc3c4b822299eeec6@buzz.block.builderlab.xyz>

# Conflicts:
#	desktop/src-tauri/src/commands/media_download.rs
#	desktop/src-tauri/src/lib.rs
Sol added 3 commits September 9, 2026 10:14
Signed-off-by: Sol <478bb5a31222ea2b28a3d1afb8b1d598940628f19c2a87efc3c4b822299eeec6@buzz.block.builderlab.xyz>
* origin/main:
  Configure ACP session scope per agent (#7578)
  refactor(buzz-acp): point agents at buzz --help instead of a command table (#7586)
  feat(buzz-cli): render an agent-friendly command tree in --help (#7584)
  fix(avatars): scale agent squircles from normalized paths (#7307)
  fix(mobile): bind same-name mentions to exact selected identities (#7385)
  fix(desktop): isolate quota backoff and reuse channel discovery rosters (#6998)
  test(desktop): isolate login-shell probe measurements (#7570)
  feat(git): add default-branch management to relay and CLI (#7562)
  fix(acp): integrate the Buzz Pi adapter fork (#7552)
  fix(markdown): align mention chip wrapping (#7501)
  fix(relay): reject presence updates when Redis storage fails (#7532)
  fix(desktop): let inbox title and message author names truncate under narrow panes (#7550)
  fix(buzz-acp): report missing models without retrying (#7538)
  fix(desktop): require a Codex adapter with Astra support (#7427)
  fix(desktop): order unnamed roster members by full canonical npub (#7503)
  fix(mobile): standardize public-key identity display on npub (#7493)
  fix(desktop): npub identity controls across profile, agents, and workflows (#7489)
  fix(desktop): npub identity displays for mention, member, and workflow surfaces (#7495)
  fix(desktop): shared npub identity foundation (canonicalNpub, PubKey gate, strict parser) (#7488)
  fix(mobile): render push notification sender identity as npub (#7494)

Signed-off-by: Sol <478bb5a31222ea2b28a3d1afb8b1d598940628f19c2a87efc3c4b822299eeec6@buzz.block.builderlab.xyz>

# Conflicts:
#	desktop/src-tauri/src/commands/media_download.rs
Signed-off-by: Sol <478bb5a31222ea2b28a3d1afb8b1d598940628f19c2a87efc3c4b822299eeec6@buzz.block.builderlab.xyz>
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.

3 participants