Repository navigation
Offer the local clipboard only to the shown session - #68
Merged
dhh merged 5 commits intoOct 24, 2026
Merged
Conversation
When the active host's offer took the local clipboard, watch_local returned before sending anything, so every Bridge kept the previous local offer. File requests are served from the local_files snapshot after a serial check alone, so any connected host, including a background tab, could still request the last locally copied files by index and read their current contents. watch_local now sends RevokeLocal to every session on each clipboard change, before the file listing starts and before the early return for the server's offer. The Bridge clears the current offer, its mime types and its files, and aborts the transfers it is serving through a new Transfers::abort_serving. While the server's offer holds the clipboard no local offer follows, so every request is refused until the next local copy. The serial counter is kept apart from the current offer, so serials keep increasing across revocations and a stale request cannot match a reused one. A file request whose open was in flight during a revocation is refused once the open completes. Transfers::serve also aborts a serve it displaces when a peer reuses a request id. Before, the second serve replaced the first one's abort handle without aborting it, so the first task kept streaming with no handle for abort_serving to reach. The ignored clipboard test that scripts/e2e.sh runs now also offers a copied file to two sessions through watch_local, puts the server's offer on the clipboard with set_remote_offer, checks that both sessions are revoked and get no new local offer, and shows a Bridge refusing a request for the old offer's file. It clears the clipboard before it ends, so it leaves no selection behind for the next e2e step.
Every running session received every local clipboard offer, so the host in a background tab could request each copy without a paste (omacom#29). Local offers now go only to the session the window shows. This builds on the revocation from omacom#63: each local change still revokes the offer in every running session first, and only the offer that follows is limited to the shown session. Switching tabs, or showing no session, withdraws the offer from the session that was shown. It gets RevokeLocal, which refuses further requests and aborts what it is serving, then an empty offer, which tells the host to drop the selection it advertised for us. The newly shown session is then offered the current local selection, since the clipboard reports only changes. watch_local takes the running sessions for revocations and the shown session for offers, and asks for both when it sends, so a slow file listing that finishes after a switch goes only to the shown session. Each local offer carries the UI's offer generation, and the bridge hands it back with every read. When the read starts, the UI checks that the session is still shown and the generation is still the latest, and refuses it otherwise. A request the worker accepted before a switch or a new copy can therefore not read what the clipboard holds by then. The ClipboardRead handler body is serve_local_read, which the tests call too. The offer routing is display-free where it can be (LocalSelection's changed, begin, finish and generation), so the tests drive the same routing functions as the app. The ignored e2e clipboard test now has one shown session and checks that a hidden session is only revoked. --no-clipboard and offers from the host are unchanged. The README says which machine receives local copies, and no longer says nothing is sent until you paste, since a host can ask for the contents without one.
This was referenced Oct 16, 2026
Open
[Medium impact] The PR appears safe to merge; no actionable issues were found. SummaryOffers local clipboard contents only to the shown session.
|
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.
This builds on #63 and includes its commit (f185c4d). Only the last commit is new here, and I will rebase once #63 lands.
Every running session received every local clipboard offer, so the host in a background tab could request each copy without a paste (#29). Local offers now go only to the session the window shows. Each local change first revokes the offer in every running session, as in #63, and only the offer that follows goes to the shown session. Switching tabs, or showing no session, withdraws the offer from the session that was shown: its bridge refuses further requests and aborts what it is serving, and an empty offer tells that host to drop the selection it advertised for us. The newly shown session is then offered the current local selection, since the clipboard reports only changes. Each offer carries the UI's offer generation, so a request accepted before a switch or a new copy is refused when its read starts, instead of reading whatever the clipboard holds by then. The watcher picks its target when an offer is sent, so a slow file listing that finishes after a switch goes only to the shown session.
--no-clipboardand offers from the host are unchanged.This narrows #29 to the shown host and does not close it: the shown host can still request an advertised type without a paste.
Validation:
main.rsdrive the same revoke, offer, switch and read functions the app uses: offers to the shown session only, every session revoked on a local change and on a takeover, a tab switch, a file listing that finishes after a switch, and--no-clipboard. A read test shows that reads queued before a switch or a new copy are refused with no bytes. Each test fails when its check is removed.scripts/e2e.sh(nested Hyprland, AMD Radeon 840M), with Wait for the client's clipboard item in e2e, not a MIME type #65 applied so the clipboard steps wait correctly: every step passed.scripts/check.shpasses.This conflicts with #47 and #80 in the client. I am happy to rebase whichever lands second.
Opus 5.5 high, on behalf of TechLuddite.
Implemented with Claude Opus 5.5 at high effort and reviewed by GPT-6.1 (Codex) at high reasoning effort over four passes, then re-reviewed on 2026-10-19 by Claude, GPT-6.1 and Grok 4.7 at medium effort.