Repository navigation
Expose exact captured-message reads - #3264
Merged
Merged
Conversation
Co-authored-by: Codex <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Codex <[email protected]>
…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 <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Joah Gerstenberg <[email protected]>
Co-authored-by: Codex <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
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 <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
Signed-off-by: Kyle Zemek <[email protected]>
Signed-off-by: Kyle Zemek <[email protected]>
Co-authored-by: Codex <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
Co-authored-by: Codex <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Codex <[email protected]>
Co-authored-by: Codex <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
gilgtm
approved these changes
Nov 11, 2026
gilgtm
left a comment
Contributor
There was a problem hiding this comment.
🤖 No P0/P1 findings in the reviewed comparison at d35017315bd61cf18f3cce298b5dd8d4c2a27a93.
Posted by duel-review skill [driver: codex(high), mode: deep-dual, review synthesized by codex(high)]
kalvinnchau
reviewed
Nov 11, 2026
dr0uleau
reviewed
Nov 11, 2026
Co-authored-by: Codex <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
dr0uleau
reviewed
Nov 11, 2026
Co-authored-by: Codex <[email protected]> Signed-off-by: Kyle Zemek <[email protected]>
dr0uleau
approved these changes
Nov 11, 2026
dr0uleau
left a comment
Contributor
There was a problem hiding this comment.
🤖 no blocking findings in the completed review at eb2a0e836094c06e6f996113dd16ab6e715e7460
joahg
added a commit
to joahg/buzz-app
that referenced
this pull request
Nov 11, 2026
* origin/main: fix(identity): show a backed-up key as backed up, and drop dead retries (block#3223) fix(browser): open Builderlab account connections in the system browser (block#3287) Expose exact captured-message reads (block#3264) docs(agents): keep internal discussion out of public repo content (block#3258) fix(identity): clear a copied private key, name restore failures, fix storage copy (block#3236) fix(channels): list workspaces that load after `#` opens (block#3277) feat(profiles): show a larger avatar in the hover profile preview (block#3232) fix(identity): ask for credentials on the next sign-in after signing out (block#3231) fix(relay): keep saved channels when the community refuses sign-in (block#3215) Signed-off-by: Joah Gerstenberg <[email protected]>
kazemek21
added a commit
that referenced
this pull request
Nov 12, 2026
## TL;DR This supports retained readers that need to undo an explicit read action without overwriting newer unread intent. It offers Undo only for saved local-unread markers removed by that action. ## How Undo checks the current journal revision inside the storage transaction and revalidates access, visit, and captured-message evidence. It restores only markers removed by that exact saved action. ## Boundary and risk Undo never restores forced-message overlays or rewinds or publishes read frontiers. Results omit Undo if the completed read also cleared forced-message overlays, or if force work was queued while the read saved. The handle expires after any later journal save, even if unrelated or quiet. This whole-journal lifetime is deliberately conservative. Older hosts may omit the optional handle. ## Related PRs This change is independent of [#3264](#3264), [#2918](#2918), [#2939](#2939), [#1666](#1666), and [#2956](#2956). Generated with Codex (gpt-6-luna) on behalf of kylez Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Codex (gpt-6-luna) <[email protected]>
joahg
added a commit
to joahg/buzz-app
that referenced
this pull request
Nov 12, 2026
* origin/main: (38 commits) Fix reminder counts with compact dock corner tabs (block#3126) Add conditional undo for explicit reads (block#3291) fix(composer): edit channel links as atomic chips (block#3312) fix(agents): show remote instruction limit (block#3325) NEXUS-4033: Offer session skills in the core task composer (block#2790) feat(github): name pasted repository and commit links (block#3268) Let plugins give standing instructions to every agent turn (block#3285) fix(relay): read a channel's creation once, not per metadata version (block#3244) fix(messages): remove unconfirmed sends from this device (block#3317) fix: expose proven non-delivery for artifacts (block#3300) feat(plugins): let plugins mark a person's name in @mention suggestions (block#3260) fix(mcp): strip metadata from images agents attach (block#3311) fix(identity): name a linked key that isn't here, and keep Restore after a failed backup check (block#3238) feat(changesets): show the files a local agent changed on its reply (block#3214) fix(identity): show a backed-up key as backed up, and drop dead retries (block#3223) fix(browser): open Builderlab account connections in the system browser (block#3287) Expose exact captured-message reads (block#3264) docs(agents): keep internal discussion out of public repo content (block#3258) fix(identity): clear a copied private key, name restore failures, fix storage copy (block#3236) fix(channels): list workspaces that load after `#` opens (block#3277) ...
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