Skip to content

fix(desktop): authorize remote mentions at publication - #7124

Open
loganj wants to merge 9 commits into
split/owned-agent-discoveryfrom
split/remote-mention-routing
Open

fix(desktop): authorize remote mentions at publication#7124
loganj wants to merge 9 commits into
split/owned-agent-discoveryfrom
split/remote-mention-routing

Conversation

@loganj

@loganj loganj commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

🤖

Summary

In Buzz Desktop, you could own an agent running on another device but be unable to mention it in a channel where it had not yet joined: it was filtered out before you could invite it. A selected agent could also disappear from the message's recipients when permissions changed. This lets you select an eligible agent in the existing @ menu, invite it from the message composer, and send to that agent—or see an error and keep your draft rather than silently sending without it.

Where the experience changes

Screen / control Before → after
A channel's Message #… composer, or a message's Reply in thread to … composer Type @ (or use the existing @ button), choose your agent, write the message and press Send message. An owned agent not yet in the channel can now reach the existing “Mention people outside this channel?” dialog when its response settings allow you to address it.
That dialog's Invite button Previously the membership requirement could block the agent before the invitation. Now Invite checks permission to add it, adds it as an agent member of the channel (not just the thread), then rechecks membership and response permission before sending the waiting message. An agent already in the channel needs no invitation.
Existing direct message, or the new-message screen with the To: field A mention is checked against the conversation the message will actually enter, including a newly created direct message, rather than the old or not-yet-created destination. This does not add an Invite control to direct messages.
Editing a message / sending attachments The selected agent remains part of the send or edit attempt through attachment upload and the final permission check. Lost permission produces a visible error instead of dropping that recipient.

Invite is not the only chat choice. The existing Do nothing button sends the message without inviting or notifying the nonmembers; their names remain references in the text. Where you cannot invite, that choice is labelled Send anyway. To abandon the send instead, dismiss the dialog with Escape. Invitation actions are disabled while preparation is pending, preventing duplicate clicks.

Leaving and returning must not resurrect a cancelled send. Switching threads or leaving the composer cancels its pending invitation, even if you return to the same thread. Cancellation before dispatch sends no message; an accepted membership change cannot be automatically undone. An ordinary send without a pending invitation remains bound to its original destination rather than following you into another conversation.

Failed sends must not overwrite your next draft. If you leave a thread, return and replace or deliberately clear its draft while an older send is pending, the older failure cannot restore deleted text, recipients or files; success cannot erase the newer draft—even if its text is identical. An untouched draft cleared automatically for sending remains recoverable on failure. This protection also covers reopening the composer and starting a newer send.

The channel timeline also keeps its existing new-messages / Jump to latest button available when newer messages are waiting to be displayed. For example, after sharing a reply to the channel and closing the thread panel, you can click the catch-up button to reveal buffered messages. Closing the thread does not guarantee the shared row appears automatically or force you away from reading history.

Related issue

Built on #7122, base branch split/owned-agent-discovery, which lets Desktop find and verify owned agents independently of this device. Finding an agent is not channel membership, online status or a promise of a reply. This PR changes what the existing message controls can do with those agents; it adds no profile, presence, cloud marker or remote start/stop UI.

Standalone forum post/reply Invite / Cancel is added separately in #7125; here those composers only gain visible authorization errors. Same-name selection/binding fixes (#7133) and mention spacing (#7128) are not included.

Extracted from #7114 (historical source 98fe33ec). Behavior and draft-recovery contract · Originating discussion.

Testing

Channel composer after Send: RemoteScout is not a channel member; Invite adds it before sending, while Do nothing sends without inviting or notifying it

Earlier candidate, mock desktop browser: the existing channel dialog now reachable for an eligible owned agent on another device. The two buttons have different send outcomes; Do nothing is not Cancel. Success and denial captures · Pending-state capture. These show the relevant UI, not live agent availability, native authorization or the later draft-storage/catch-up repairs. No before-state screenshot is available.

Existing coverage exercises exact recipients, invitation rejection/cancellation, new direct-message destinations, uploads, edits, thread re-entry and stored-draft deletion. The timeline regression checks the shared reply becomes visible using the available catch-up action. CI passed for this head. No product tests were rerun for this description-only edit.

To try it: in a channel or thread, select an owned nonmember agent, Send, then Invite or Escape and retry. Deny the add or revoke its response permission before sending: expect a visible error and recoverable draft, not a message missing the agent. During a pending send, return to the source thread, edit or clear the draft, then leave again: late completion must not overwrite that choice.

Limits: permission checks and sending are separate operations; cancellation cannot retract a dispatched message. Draft protection is same-window, not new cross-window deletion synchronization. Standalone forum transport failure can still restore text/media without the exact selected recipients. Native compatibility is inherited: open-source builds may still recognize a valid legacy, self-declared agent already in the channel when verified ownership is absent or rejected; that does not establish ownership or unlock this owned-nonmember invitation path. Invalid policy from a verified owner is still rejected. No agent response is guaranteed.

Extract preparation, exact destination checks and retained recipient intent from #7114, independently of labels and profile presentation.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj added 2 commits August 31, 2026 13:46
Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj added a commit that referenced this pull request Aug 31, 2026
@loganj

loganj commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

Remote-owned mention routing — PR7

Mock IPC browser capture at 467e9869e923099798c2bd50db2b25062bb9a2b4. The remote workflow suite checks exact recipient p-tags, authorized addition, new/existing DM destination checks, failed adds, and revocation. Images show rendered UI, not native signature-verification evidence.

Invite an owned relay agent without local inventory

Preparation offers the existing chat Invite/reference-only choice; ownership alone is not publication authorization.

remote-invite

Publish after current membership and policy checks

The sent message retains the exact RemoteScout recipient.

remote-sent

Unauthorized add preserves the draft

An actor authorization error stays visible and sends no message.

remote-error-actor

Policy rejection preserves the draft

The channel-add policy error stays visible without silently dropping the recipient.

remote-error-policy-nobody

Keep invitation intent alive through preparation, add, and publication, while cancelling dismissal, navigation and replacement. Retain cleared drafts on late cancellation and cover async boundaries with real React and browser regressions.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj

loganj commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

B1: preparation is visibly pending and cancellable

At repair cdb35b1021ad24cf1dfa118a42f16d3df197cde7, the actual preparation IPC is deferred: Inviting... and reference-only Do nothing are disabled for the whole attempt. Escape dismisses without adding/sending and retains the draft; retry sends exactly once. Mock-browser evidence, not native authorization certification. Earlier success/error screenshots remain applicable to those unchanged states.

b1-invite-pending

loganj added a commit that referenced this pull request Aug 31, 2026
loganj and others added 3 commits August 31, 2026 15:03
Invalidate same-channel thread changes and returning visits before late invitation continuations can clear or recover over another draft. Separate authored editor revisions from optimistic clear, capture exact selections before preparation, and bind resolved persona refs to that captured draft. Cover retained-host navigation, recovery, media, and persona preparation; sanitize screenshot stems only.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Bind authored revisions to captured lifecycle visits instead of the visible editor. Apply source authority to cancellation and preflight recovery and sent-draft cleanup, while keeping editor ownership separate. Cover reviewer deletion reproduction, queued files, supersession, return visits, and actual thread-switch persistence.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Use the captured submission revision for sent-draft storage authority, including ordinary destination-bound sends that leave before optimistic clear. Add a discriminating regression for that lifecycle boundary.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj and others added 2 commits August 31, 2026 16:05
Keep authored intent in the scoped draft store independently of value deletion. Supersede recovery, sent cleanup and lifecycle persistence across reentry and remount while preserving ordinary destination-bound sends and untouched optimistic recovery. Keep editability and automatic prefix restoration programmatic. Exercise the real store, React lifecycle transition matrix and retained browser editor.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
A frozen logical tail can report physical bottom before buffered live rows are released. Keep the existing catch-up action visible while pending rows remain so a shared thread message can enter the parent timeline after closing the thread. Preserve scroll anchoring and explicit catch-up behavior.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj
loganj marked this pull request as ready for review August 31, 2026 21:17
@loganj
loganj requested a review from a team as a code owner August 31, 2026 21:17
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.

1 participant