Repository navigation
Conversation
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Kyle Zemek <kylez@squareup.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com> Co-authored-by: Codex <noreply@openai.com>
…e card (#2239) ## 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 #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](https://claude.com/claude-code) --------- Co-authored-by: Helen Tian <htian@squareup.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Kyle Zemek <kylez@squareup.com>
Add an optional host capability that saves reads for exactly the captured verified message IDs and clears only their visit-local forced overlays after persistence. Preserve independent shared channel markers and newer read intent. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Kyle Zemek <kylez@squareup.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Kyle Zemek <kylez@squareup.com>
Signed-off-by: Kyle Zemek <kylez@squareup.com> Co-authored-by: Codex <noreply@openai.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
This lets retained readers explicitly clear only a captured set of unread messages without including other messages or newer unread intent.
It adds one optional host method that saves reads for verified message IDs.
Why
Existing read methods can include a subtree or move a timeline boundary. A caller that already has an exact set needs a host operation that keeps that boundary exact.
What
UnreadCapabilitynow has optionalmarkCapturedMessagesRead(messageIds). Older hosts can omit it.How
The method revalidates each message and saves only per-message keys in the captured set. After that save succeeds, it clears only those IDs' visit-local forced overlays and leaves the shared channel force hint untouched.
Risk
No caller or UI changes. Default read behavior remains unchanged.
Testing
No manual testing.
Generated with Codex (gpt-6-luna) on behalf of kylez