Skip to content

Offer the local clipboard only to the shown session - #68

Merged
dhh merged 5 commits into
omacom:mainfrom
TechLuddite:fix/clipboard-active-session-offers
Oct 24, 2026
Merged

dhh merged 5 commits into
omacom:mainfrom
TechLuddite:fix/clipboard-active-session-offers

Conversation

@TechLuddite

@TechLuddite TechLuddite commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

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-clipboard and 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:

  • Routing tests in main.rs drive 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.sh passes.
  • The README now says which machine receives local copies and when contents are sent.

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.

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.
@greptile-apps

greptile-apps Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

Offers local clipboard contents only to the shown session.

  • Local clipboard offers go only to the session shown in Gliff.
  • Clipboard changes and tab switches withdraw the old offer.
  • Requests from an older clipboard offer are refused.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Local clipboard changes] --> B[Advance generation]
  B --> C[Revoke offers in every running session]
  C --> D{Local selection?}
  D -->|Yes| E[Read types and list files]
  E --> F{Generation still current?}
  F -->|Yes| G[Offer only to shown session]
  H[Switch tabs] --> I[Revoke old session and send empty offer]
  I --> J[Offer current local selection to new session]
  G --> K[Host requests contents]
  K --> L[Check serial and read generation]
  L --> M[Serve contents]
Loading

Reviews (1) · Last reviewed commit: "Synchronize fixes from the prerequisite ..." · Reviewed by Greptile

@dhh
dhh merged commit f03d6ec into omacom:main Oct 24, 2026
5 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