镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(messages): let attachment renderers open files from the host file card - #2239

Merged
joahg merged 3 commits into
block:mainfrom
ericm-square:ericm/file-attachment-card
Nov 11, 2026
Merged

joahg merged 3 commits into
block:mainfrom
ericm-square:ericm/file-attachment-card

Conversation

@ericm-square

@ericm-square ericm-square commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Lets plugins open file attachments the way #1469 opens Markdown, without bundling a previewer into Buzz.

File attachments in message rows now go through attachment renderers, like images and audio already do. For files, a renderer also receives FileCard: the host's own file card. Rendering it with onPreview makes clicking the card open the preview, while a separate trailing button keeps the host's native or browser download. Plugins can't reach the native download themselves, so this keeps Download working for them on desktop.

Renderers also receive placement ("message" or "composer"), so a plugin can offer the preview only on sent messages and leave the composer's attachment review alone. In message rows they also receive message, the message that holds the attachment, so a preview can name who shared it and where.

The first consumer is an external PDF viewer plugin: clicking a PDF card opens it in the conversation side pane.

Changes

  • contracts.ts (FOUNDATION): adds optional placement, message and FileCard to AttachmentRendererProps, plus FileCardProps.
  • MessageRow.tsx: file attachments go through AttachmentView with placement="message", message and FileCard. Audio and image renderers also get placement.
  • ComposerAttachmentViewer.tsx, ComposerAttachments.tsx: pass placement="composer". The draft viewer also passes FileCard.
  • FileAttachment.tsx, Messages.module.css: the click-to-preview card with a separate Download action, taken from [buzz-app] Preview Markdown and common text attachments #1469 without its summary variant.
  • docs/plugin-architecture.md: documents registerAttachment, placement and FileCard.

With no plugin claiming a file, message rows render exactly as before.

Relationship to #1469

This takes only the host plumbing from #1469 (the renderer routing, placement, and the preview card) and leaves out the bundled File Preview plugin. If #1469 lands later, its File Preview can render FileCard the same way, and the overlapping pieces should rebase cleanly onto this. Co-authored with htian-dev, whose code this is.

Test plan

  • pnpm check passes.
  • vitest run src/features/messages src/features/conversation src/bundled: 6941 passed.
  • New tests:
    • FileAttachment.test.tsx: preview and native download are separate keyboard actions.
    • MessageRow.test.tsx: a plugin renders FileCard in message placement and receives the message; clicking it opens the plugin's target; Download keeps the media address; an unclaimed file keeps the plain download card.

Manual test steps

  1. Run just desktop from this branch and sign in.
  2. Without any plugin, post a PDF and a .zip. Both show the usual file card, and clicking either downloads it.
  3. Install a plugin whose PDF renderer draws FileCard with onPreview opening a side-pane target (Settings → Plugins → Manage plugins). I tested with a PDF viewer plugin built this way.
  4. Click the PDF card. The plugin's preview opens as a tab in the side pane.
  5. Click the download button at the right edge of the PDF card. The file saves to Downloads, and the card shows "Saved to Downloads".
  6. Tab to the card: focus lands on the card first, then on Download. Enter on each does the same as clicking it.
  7. The .zip card still downloads on click.
  8. Attach a PDF in the composer and open it from the draft. The draft view shows the plain card, with no side-pane preview.
  9. Check both themes and a narrow window.

🤖 Generated with Claude Code

@joahg

joahg commented Nov 3, 2026

Copy link
Copy Markdown
Member

🤖 We'd like to build on this. Block's Markdown reader plugin (squareup/agents#8965) would claim .md file cards with it, so clicking the card opens the reader in the side pane. Buzz itself keeps no Markdown code. The contract looks right to us. Could you mark this ready for review?

Two notes:

  1. Attachment renderers resolve by first active match, so when two plugins claim the same file (say, a Markdown reader and a future summarizer), activation order decides. Panels and links already use order for this. I'll open a small follow-up that gives registerAttachment the same order rule.
  2. onPreview only applies to relay-hosted sources. An external https: file keeps its plain link card. Is that intended?

I'm testing this branch with md-reader in the desktop app and will post results here.

@joahg

joahg commented Nov 3, 2026

Copy link
Copy Markdown
Member

🤖 Follow-up for note 1: #2284 gives registerAttachment the same order rule that panels and links use. It doesn't depend on this PR, and either can merge first.

@joahg

joahg commented Nov 3, 2026

Copy link
Copy Markdown
Member

🤖 I tested this end to end with md-reader. I used the buzz-plugin-demos web shell (seeded fictional community), with this PR merged with #2284.

  • md-reader registered an attachment renderer that draws FileCard with onPreview. Clicking the .md card opened the reader as a side-pane tab, and the Download link stayed on the card. With no page errors, md-reader's own "Open in reader" fallback card could step aside.
  • One gap: renderers get the attachment but not the message it belongs to. md-reader's reader header uses the channel, message and author for "shared by … in #channel", so opening from the card drops that context. An optional message prop on AttachmentRendererProps (absent in the composer) would cover it. I'm happy to add it here or as a follow-up.
  • Nit: the default meta reads "MARKDOWN file". That's fine, since plugins can pass typeLabel.

Not yet tried in the native desktop app with a real buzz-media: source. I'll do that next.

@ericm-square
ericm-square marked this pull request as ready for review November 3, 2026 21:03
@ericm-square
ericm-square requested a review from a team as a code owner November 3, 2026 21:03
@ericm-square

Copy link
Copy Markdown
Contributor Author

🤖 @joahg thanks for testing this with md-reader! This is ready for review. I can't request reviewers on this repo from a fork, so could you add yourself, or whoever should review it?

  1. message prop: added in fc7eb11. Message rows now pass message, the message that holds the attachment, to every attachment renderer (file, audio and drawn image). The composer leaves it out. It's documented next to placement in docs/plugin-architecture.md, and the MessageRow test checks it.
  2. External https: files: yes, intended for now. Those cards open the link in the browser, and a plugin would need its own network access to read most external hosts, so the preview stays on relay-hosted files. Happy to widen it if a renderer needs it.
  3. feat(plugins): resolve attachment renderers by order, like panels and links #2284: looks good to us. The two PRs touch nearby lines in contracts.ts and docs/plugin-architecture.md, so whichever lands second may need a small rebase. I'm happy to do it on this side.
  4. "MARKDOWN file": agreed, plugins can pass typeLabel. The PDF viewer passes "PDF".

The PDF viewer that uses this is https://github.com/squareup/agents/pull/8960.

@joahg joahg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Reviewed at fc7eb11 and tested it end to end with md-reader (squareup/agents#9003) plus #2284 in the demo shell. Clicking the card opens the reader with its message context, and Download still works. Approving. None of the following blocks merging:

  1. Files now reach registerAttachment matchers. A plugin whose matcher ignores attachment.kind (for example, a test on the name alone) will now claim file cards too. If its component renders without throwing, the host card disappears. The bundled matchers check kind, so nothing breaks today. A sentence in docs/plugin-architecture.md telling matchers to check attachment.kind would cover it.
  2. The failed-download text sits inside the Preview button (FileAttachment.tsx). The card's meta text reads "Couldn't download · Retry", but clicking that text opens the preview. Retry lives on the trailing icon. Dropping "· Retry" on this path would avoid the confusion.
  3. onClick={onPreview} passes the click event to the callback. onClick={() => onPreview()} keeps a plugin that writes onPreview={open} from receiving a MouseEvent as its target.

joahg added a commit that referenced this pull request Nov 3, 2026
… links (#2284)

## Summary

When several plugins claim the same attachment, a precedence rule now
picks the one that draws it, instead of activation order.
`registerAttachment` accepts the optional `order` that panels and link
renderers already take: a number, or a function of the `Attachment`.
Lower wins, and equal orders fall to the contribution key.

This makes #2239 safe to build on with more than one file
plugin installed. #2239 routes file cards through attachment renderers.
Once it lands, a Markdown reader (squareup/agents#8965) and a future
plugin that also handles `.md` (a summarizer, say) would both match the
same card. Without `order`, whichever activated first wins, and
disabling and re-enabling a plugin can flip the result. This PR applies
the same bands as panels and links: `-10` specialises, `0` is the
default, `100` is a catch-all.

Tracks AGNTOPS-704.

## Changes

- `src/plugins/contributions.ts`: `resolveMatch` and `MatchOrder` take
the target type as a parameter (default `string`), so attachments can
use the same resolver. Existing callers are unchanged.
- `src/features/conversation/contracts.ts` (FOUNDATION; Joah approved
this change): `AttachmentRenderer` gains `order?:
MatchOrder<Attachment>`.
- `src/features/conversation/service.tsx`: `registerAttachment` rejects
an `order` that is neither a number nor a function, as `registerLink`
does.
- `src/features/conversation/AttachmentView.tsx`: picks the renderer
with `resolveMatch` instead of the first match.
- `docs/plugin-architecture.md`: the Resolution order section now covers
attachment renderers.

No bundled renderer sets `order`, so with one renderer per attachment
nothing changes.

## Test plan

- New `AttachmentView.test.tsx` covers four cases:
  - the lowest order wins, whatever the registry order;
  - equal orders fall to the contribution key;
- an `order` function receives the attachment, and a throwing matcher is
skipped;
  - with no match, the host card stays.
- `service.test.tsx`: an invalid `order` is rejected, and a function
`order` registers.
- `vitest run src/features/conversation src/plugins
src/features/messages src/bundled`: 7154 passed, 16 skipped.
- `pnpm check` and the pre-push types-and-related-tests hook pass.

### Manual test steps

1. Run `just desktop` from this branch, with #2239 merged
in so file cards use renderers.
2. Install two folder plugins that both match `.md` file attachments.
One sets `order: -10` and draws a "Reader" label; the other uses the
default and draws "Other".
3. Post a `.md` file. The card shows "Reader".
4. In Settings, disable both plugins, then enable the `order: -10`
plugin last. The card still shows "Reader".
5. Remove `order` from the first plugin and reload it. The card shows
whichever plugin's key sorts first, and toggling the plugins doesn't
change it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
arividar pushed a commit to minniai/minni-buzz-app that referenced this pull request Nov 4, 2026
… links (block#2284)

## Summary

When several plugins claim the same attachment, a precedence rule now
picks the one that draws it, instead of activation order.
`registerAttachment` accepts the optional `order` that panels and link
renderers already take: a number, or a function of the `Attachment`.
Lower wins, and equal orders fall to the contribution key.

This makes block#2239 safe to build on with more than one file
plugin installed. block#2239 routes file cards through attachment renderers.
Once it lands, a Markdown reader (squareup/agents#8965) and a future
plugin that also handles `.md` (a summarizer, say) would both match the
same card. Without `order`, whichever activated first wins, and
disabling and re-enabling a plugin can flip the result. This PR applies
the same bands as panels and links: `-10` specialises, `0` is the
default, `100` is a catch-all.

Tracks AGNTOPS-704.

## Changes

- `src/plugins/contributions.ts`: `resolveMatch` and `MatchOrder` take
the target type as a parameter (default `string`), so attachments can
use the same resolver. Existing callers are unchanged.
- `src/features/conversation/contracts.ts` (FOUNDATION; Joah approved
this change): `AttachmentRenderer` gains `order?:
MatchOrder<Attachment>`.
- `src/features/conversation/service.tsx`: `registerAttachment` rejects
an `order` that is neither a number nor a function, as `registerLink`
does.
- `src/features/conversation/AttachmentView.tsx`: picks the renderer
with `resolveMatch` instead of the first match.
- `docs/plugin-architecture.md`: the Resolution order section now covers
attachment renderers.

No bundled renderer sets `order`, so with one renderer per attachment
nothing changes.

## Test plan

- New `AttachmentView.test.tsx` covers four cases:
  - the lowest order wins, whatever the registry order;
  - equal orders fall to the contribution key;
- an `order` function receives the attachment, and a throwing matcher is
skipped;
  - with no match, the host card stays.
- `service.test.tsx`: an invalid `order` is rejected, and a function
`order` registers.
- `vitest run src/features/conversation src/plugins
src/features/messages src/bundled`: 7154 passed, 16 skipped.
- `pnpm check` and the pre-push types-and-related-tests hook pass.

### Manual test steps

1. Run `just desktop` from this branch, with block#2239 merged
in so file cards use renderers.
2. Install two folder plugins that both match `.md` file attachments.
One sets `order: -10` and draws a "Reader" label; the other uses the
default and draws "Other".
3. Post a `.md` file. The card shows "Reader".
4. In Settings, disable both plugins, then enable the `order: -10`
plugin last. The card still shows "Reader".
5. Remove `order` from the first plugin and reload it. The card shows
whichever plugin's key sorts first, and toggling the plugins doesn't
change it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@joahg

joahg commented Nov 6, 2026

Copy link
Copy Markdown
Member

🤖 @ericm-square #2284 has landed, and this branch now conflicts with main in docs/plugin-architecture.md (that's the only conflict). Could you rebase when you get a chance? I'll re-approve after the push.

ericm-square and others added 2 commits November 9, 2026 16:21
…e card

File attachments in message rows now go through attachment renderers.
Renderers receive placement and the host FileCard, so a plugin can make
the card open a preview while the host keeps native download.

Co-Authored-By: Helen Tian <htian@squareup.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ericm-square
ericm-square force-pushed the ericm/file-attachment-card branch from fc7eb11 to ac20f7d Compare November 9, 2026 22:26
@ericm-square

Copy link
Copy Markdown
Contributor Author

@joahg Can you run the CI checks?

@joahg joahg closed this Nov 10, 2026
@joahg joahg reopened this Nov 10, 2026
@joahg
joahg merged commit 367e4c4 into block:main Nov 11, 2026
70 checks passed
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.

2 participants