Skip to content

The clipboard both ways: Share Clipboard on the Mac and the device, Paste on the Mac (#64) - #78

Merged
Saffsanity merged 22 commits into
mainfrom
clipboard
Oct 15, 2026
Merged

Saffsanity merged 22 commits into
mainfrom
clipboard

Conversation

@Saffsanity

@Saffsanity Saffsanity commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Fixes #64

Why

Text could go from the iPad to the Mac only by typing (a paste into the stream's keyboard is typed a character at a time and never reaches the Mac's clipboard), and from the Mac to the device not at all, unless both were on the same Apple Account with Universal Clipboard, which does not reach a device on another account or away from home. The triage of 2026-10-13 put #64 next, text first, and noted two problems in #66's clipboard: it sends what a password manager copies, and gives the Mac's owner no switch.

What

The plan, its open questions with the default taken for each, every result and the review's fixes are in docs/clipboard-plan.md.

  • Wire (additive): kind 32 clipboard, both ways, JSON Clipboard {text, image, stop}: Sill Viewer: a Mac controlling another Mac #66's number and shape (image reserved for a later generation), and stop, a device's switch going off. WindowList.clipboard: Int?, 1 for text: a device sends kind 32 only to a host that says it, an older peer skips the kind. {} is text alone: a host sends image only to a subscription that says it takes images (the compatibility floor says so; Sill Viewer: a Mac controlling another Mac #66's host sends one to every subscriber, and follows this as it rebases). Text at most 256 KB of UTF-8, the JSON at most 1 MiB either way. Sill Viewer: a Mac controlling another Mac #66's list key is a Bool, which does not decode as this Int, so it changes as Sill Viewer: a Mac controlling another Mac #66 rebases (never released). Kinds 29 to 31 stay free.
  • Rules (pure, both ends): ClipboardPolicy in StreamProtocol, checked by the new Tests/checks/clipboard (with mutants, in CI's matrix). Never sent: an item marked org.nspasteboard.ConcealedType or org.nspasteboard.TransientType (password managers, one-time codes; and three older markers), one that came from another device through Universal Clipboard (reading it fetches it from there), and the host's own write. Types are judged before any data is read, so a password manager's text is never read. A device's subscriptions and texts are rated 4 a second; its stop never is.
  • Mac: HostClipboard looks at the pasteboard's change count four times a second while a device subscribes, and only while macOS allows every read (NSPasteboard's accessBehavior, macOS 15.4 and later): while macOS would ask (its alert at every copy) or denies, it reads nothing, and nothing copied meanwhile is sent. New text goes to every subscriber; what was on the clipboard when the first device subscribed is never sent. A device's text is written for this Mac only (.currentHostOnly) with a marker naming this launch, and passed to the other subscribers. One log line when a device subscribes or stops, or when macOS's answer changes; none per copy, never contents; clip.sent/clip.recv in the stats line. Settings › General: "Share clipboard with devices" (on, saved as shareClipboard, applied at once), and while macOS asks or denies, why, with "Allow in System Settings…" (Privacy & Security › Paste from Other Apps). The CLI: --no-clipboard. A synthetic host shares a pasteboard of its own, never this Mac's clipboard.
  • Device: Settings › This iPad: Share Clipboard (on, stored on the device), shown while the Mac says it, with Paste on ‹Mac›. The Mac's text is written .localOnly with a 10-minute expiration. Sill never reads the device's clipboard on its own: when it becomes active with new text there (changeCount and hasStrings only, which never prompt), a chip near the bar offers "Paste on ‹Mac›" with the system's Paste control (UIPasteControl via PasteButton, which reads without a prompt) and Dismiss; its 20 s run only while it shows with Sill active, never under VoiceOver, which is told when it first shows. A Paste says "Sent to ‹Mac›’s clipboard" only once a ping sent after the text comes back (the Mac echoes a ping only after reading all before it); otherwise "Didn’t reach ‹Mac›. Try Paste again." and the chip again. Then the person presses ⌘V on the Mac.
  • Docs: privacy.html (what travels each way, what stays, the date) and support.html (the section, and what to do when macOS asks), both inside <!-- Clipboard blocks that a republish before the release cuts (docs/release-checklist.md part 1 §3); README (How it works, Good to know), DEVELOPMENT.md, the App Store description bullet waiting for the next submission, CLAUDE.md.

The review, and what changed

An adversarial review of 7e6468a reran every gate (all held) and found eight things, all taken, one commit per theme:

  1. Should-fix: a stop lost to the rate (c2d7b6a). The rate ran before the message was read, so a {"stop":true} that was the fifth in a second (the switch flipped off, on, off, on, off) was refused and the Mac's copies kept going to a device whose switch was off. Reproduced against 7e6468a on loopback (the copy at 5 s reached the client); with the fix the last stop is taken and nothing goes. Now ClipboardPolicy.hostReceives decides first and rates only subscriptions and texts.
  2. Should-fix: macOS's paste privacy (210ba4b). Sill.app read the general pasteboard at every copy and never checked accessBehavior; NSPasteboard.h says it asks at each programmatic read by default once that is on. Now the host reads only while macOS allows every read, says so once when that changes, and Settings › General explains and opens Paste from Other Apps (first letting macOS ask once while it has not, since Sill is listed there only after that). Today's macOS reads "always allow", so nothing changes until it asks.
  3. The floor's image rule (162b00a): written in CLAUDE.md, Clipboard.swift and the plan.
  4. "Sent" before the Mac had it (768f207): now on the receipt, a ping with a 16-byte random payload after the text; no wire change.
  5. The chip unannounced, and running out unseen under the Settings panel, the tour or for VoiceOver (2eb0fb2, a371cb6).
  6. On by default replaces the device's clipboard, then empties it after 10 minutes: Noah's call, in the plan's open question 1 and the device tests below.
  7. Copy (d5b22a7): Settings › General's footnote no longer promises every password manager's copies stay, and says a device's text comes with Paste; the site says Sill never reads the iPhone or iPad's clipboard by itself.
  8. The site's words before the release (f8355aa): kept here, as Larger Picture on an iPhone held upright: no thumbnails, the picture down to the keyboard (#58) #79's and A window Sill sized goes back to its own frame when it stops streaming #75's are, inside <!-- Clipboard blocks; the release checklist says how a republish before the release cuts them.

Verified

After the review's fixes (code at a371cb6):

  • Checks: Tests/checks/clipboard 259 checks (two random models of the review's cases among them: 5,000 quick sessions of switch flips and Pastes through the rate, never subscribed at the host while the device's switch is off; three devices with macOS's answer changing, never a copy made while it asked or denied) and 72 of 72 mutants (22 new) caught, in CI's mutants matrix. Tests/checks/run-all.sh: all 41 passed (7 min 05 s). CI's "Build and check" passed on the head, 229934f (13 min 05 s).
  • Builds from git archive of a371cb6: swift build -c release 42.9 s, only the CaptureProbe warning; the iOS app Debug and Release for the generic simulator and Debug for a generic device (CODE_SIGNING_ALLOWED=NO, arm64): BUILD SUCCEEDED (18 s, 36 s, 17 s), only the known StreamClient capture warning (StreamClient.swift:3469; main's 3430, the same code).
  • H2: origin/main's CLI against this branch's, both from git archive, --synthetic on loopback with the software encoder: idle 35 s side by side identical masked and sorted (7 lines); a client picking the Desktop for 15 s that never subscribes: identical in order without the [1s] lines (35 lines each), no clipboard line.
  • Live on loopback (synthetic hosts on their own named pasteboards, a probe writing and reading them; never this Mac's clipboard): the review's stop sequence against 7e6468a, the copy reached the client; against this build, a third "stopped sharing" and nothing. Five texts in a second: four set, one ignored, and a stop right after taken. macOS's answer through SILL_TEST_CLIPBOARD_ACCESS (a file changed while a client shared): denied, asks, allowed, not asked, each with its line; copies made while it denied or asked never went, the one made after reads came back went (9.03 s), a device's text was written while not asked.
  • Simulator (a private iPad Pro 13-inch on iOS 27.0, deleted after): a Paste (-SillSettingsScript '3 paste …') gave "its receipt awaited for 4.0 s", then "the Mac has read it", and the host's pasteboard held the text with the launch's marker (also on the final build); through sillrelay.py --blackhole-after 3, a Paste at 3 s gave "no receipt in time" and the chip again, and the host got nothing. The chip under the Settings panel: "waits, 18.3 s left", then after the panel closed "runs, 18.3 s left" and its time up; through an app switch: "waits, 14.6 s left", back "runs, 14.6 s left"; with VoiceOver's stand-in it stayed 30 s; VoiceOver told when it first showed. Harness photos of the chip (sideways, upright), a note, the Settings group and the stream: byte for byte the reviewed build's; the new note sideways and upright.
  • Mac Settings: the final bare binary's previews against the reviewed build's: 130 files byte for byte but the General pane (its footnote), and 10 new: the clipboard section in each of macOS's answers and with the switch off (pane-clipboard-*).
  • Before the review (7e6468a, the plan's Results): XCUITest's real tap on the chip's Paste setting the host's pasteboard, two devices, every marker and Universal Clipboard's, the clear-then-write fix (12 of 12 against 3 of 12), the bare app's switch live, VoiceOver's labels, and CI's "Build and check".
  • Not rerun here: the XCUITest tap (the receipt was driven through the same sendClipboard the control's tap reaches), the bare app's switch, the phone's arrangement and the Duo's poses (no layout changed). macOS's alert itself was never shown: nothing here reads the real clipboard.

Untested, for Noah

On the iPad mini and the iPhone 15 Pro with this branch's builds and this Sill.app:

  1. At home: copy a sentence in TextEdit; it pastes in Notes on the iPad. Copy in Notes, switch back to Sill: the chip "Paste on ‹Mac›" (above the window bar upright, under the bar sideways); tap Paste, then ⌘V in TextEdit. Sill.log has "Clipboard: iPad (…) shares the clipboard." and no line per copy.
  2. Through Tailscale, away: the same.
  3. A password copied in 1Password, and one in Apple's Passwords app, never reaches the iPad (and nothing in Sill.log). If the Passwords app's copy arrives, it does not mark it concealed: say so.
  4. Universal Clipboard (the same Apple Account, Handoff on): copy on the Mac: the iPad has it, and the chip may come once offering it back (known; the device cannot compare without reading); copy on the iPhone: Sill does not pass it on.
  5. Settings › General's switch while the iPad streams: the This iPad group loses its rows and copies stop; on again, they come back.
  6. VoiceOver on the iPad: when the chip shows, "Paste on ‹Mac› is available, near the bar."; the chip's words, Paste with its hint, Dismiss; it stays until Paste or Dismiss; "Sent to ‹Mac›’s clipboard" spoken once the Mac has it.
  7. Split View or a window beside Sill: a copy there brings no chip until Sill becomes active again (known); the Settings group's Paste works any time.
  8. On the iPhone: the chip above row 1, the success haptic once the Mac has the text.
  9. macOS's paste alert: defaults write me.saffer.sill.mac EnablePasteboardPrivacyDeveloperPreview -bool yes, Quit Sill from its menu and open it again, connect the iPad. Settings › General shows "macOS asks before Sill reads the clipboard, so what you copy here stays on this Mac."; a copy in TextEdit brings no alert and does not arrive, and Sill.log has the line once. Click Allow in System Settings…: macOS's alert (note its words and buttons), then Paste from Other Apps with Sill listed; set Allow: the orange line goes, a copy arrives on the iPad, Sill.log says "macOS lets Sill read the clipboard again". Set Deny: Settings says so, copies stay, a Paste from the iPad still sets the Mac's clipboard. Then defaults delete me.saffer.sill.mac EnablePasteboardPrivacyDeveloperPreview and open Sill again.
  10. Copy in Notes, stay away from Sill about 10 s, come back and tap Paste at once: either "Sent to ‹Mac›’s clipboard" and ⌘V pastes it on the Mac, or "Didn’t reach ‹Mac›. Try Paste again." and the chip comes back once the session is up; never "Sent" while ⌘V pastes something else.
  11. On by default: copy a photo on the iPad, then with Sill connected copy text on the Mac: the photo is gone from the iPad's clipboard, and 10 minutes later the iPad's clipboard is empty. Say whether either should change (the plan's open question 1).

Photos are in ~/Downloads/sill-handoff/clipboard/photos/ (from before the review) and its review/ folder (the new note, Sill.app's clipboard section in each state, and the chip under the Settings panel and after it); the live ones show this Mac's windows and name.

🤖 Generated with Claude Code

Saffsanity and others added 11 commits October 14, 2026 07:42
The wire (kind 32, WindowList.clipboard), the rules both ends share (ClipboardPolicy), the host's
side (HostClipboard, Share clipboard with devices, --no-clipboard), the device's (Share Clipboard,
the Paste chip built on the system Paste control), the open questions with the default taken for
each, and the test plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ardPolicy

Kind 32, both ways, JSON Clipboard {text, image, stop} (Clipboard.swift): pull request #66's number
and shape, image reserved for a later generation, and stop, a device's switch going off. The
window list's clipboard (an Int, 1 for text): a device sends kind 32 only to a host that says it;
an older peer skips the kind. Text at most 256 KB of UTF-8, the JSON at most 1 MiB either way.

ClipboardPolicy (pure, both ends): what a device's message asks of the host; which change of the
Mac's pasteboard the host sends (never its own write, never an item marked concealed or transient
by a password manager or another app, whose text is never read); who a device's text goes to; the
rate; the device's subscription; which text from the Mac it writes; when the chip offers the
device's clipboard; every word.

Tests/checks/clipboard: 189 checks with two random models, 47 of 47 mutants, in CI's mutants
matrix. protocol, compatibility and pointer-control read kind 32 (192, 102 and 157 checks);
compatibility's mutants follow WindowList's new parameter (22 of 22).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…evices, --no-clipboard

A device subscribes with any kind 32 and stops with stop, one line each, never one per copy and
never what was copied. While one subscribes the host looks at its pasteboard's change count four
times a second (a Task.sleep loop: the CLI's dispatchMain runs no RunLoop timer) and sends a change
ClipboardPolicy takes to every subscriber; what was on the clipboard when the first subscribed is
never sent. A device's text is written for this Mac only (.currentHostOnly), marked with this
launch's ID, and forwarded to the other subscribers; the look never sends it again. At most four
kind 32 a second per connection; the rest, text over 256 KB and what does not decode ignored with
one line a minute. Stats clip.sent and clip.recv.

HostConfig.shareClipboard, on in standard: Sill.app's Settings > General "Share clipboard with
devices" (saved as shareClipboard), the CLI's --no-clipboard; the Mac's alone, applied at once by
setTarget (every subscription dropped, the lists sent again). A synthetic host shares a pasteboard
of its own (SILL_TEST_PASTEBOARD, TEST ONLY), never this Mac's clipboard; SILL_TEST_CLIPBOARD_LOG=1
prints each decision with sizes, never text.

away-quality: shareClipboard in standard and changes(to:), never moved by a device (71 checks,
19 of 19 mutants).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lipboard

The clipboard's test client flags: the subscription, a device's text (escapes decoded, or N bytes),
a literal kind 32, each kind 32 printed with its size and text, the window list's clipboard=, and
the last kind 32's text checked at exit (clipboard=none: none came).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Mac through the system's Paste control

StreamClient+Clipboard: an empty kind 32 at the session's first window list that says the Mac
shares its clipboard (and on a move's new connection) while this device's switch is on; stop when
it goes off. The Mac's text is written to UIPasteboard with .localOnly and a 10-minute expiration,
its change count kept as handled. Sill never reads the clipboard by itself: when it becomes active
(or a session starts within 15 s of that) and the clipboard's change count is not the handled one
while it has text (changeCount and hasStrings, which never ask), the chip offers it.

ClipboardChip: "Paste on <Mac>", then press Command-V there, with the system's Paste button
(PasteButton, UIPasteControl: the person's tap reads the clipboard without iOS asking) and Dismiss;
"Sent to <Mac>'s clipboard" or "Too long to send" for 3 s after. Under the bar sideways (its
bottom corner while the link's line shows), above the window bar or row 1 upright; hidden while
something is open over the stream. VoiceOver: one label for its words, Paste with a hint, Dismiss.
The Settings panel's This iPad group: Share Clipboard (Sill.shareClipboard, on by default) while
the Mac shares it, Paste on <Mac> under it, a footnote.

The harness: the mock Mac shares its clipboard in every case but legacy; -SillClipboardChip 1 and
-SillClipboardNote sent|long; the mock's chip never follows the simulator's clipboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne look per activation

Found while building: an item another of the person's devices put on the Mac through Universal
Clipboard (com.apple.is-remote-clipboard) is fetched from that device when read, and macOS shows it
pasting from there, for every copy made on the iPhone. The host now skips it from its types alone,
as it does a password manager's; those devices have it already. The check's model and one more
mutant cover it.

The device: the app's and its scene's activation come together, so they make one look.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tore's next version, CLAUDE.md)

site/privacy.html: what travels each way (what you copy on the Mac while Share Clipboard is on,
never a password manager's concealed copies; the device's text only when Paste is tapped, never
read by Sill on its own), what stays on the device (the switch, the change count, the Mac's text on
its clipboard for 10 minutes, not passed on by Universal Clipboard), the log never holding what was
copied; October 14, 2026. site/support.html: copy and paste both ways. README: How it works and Good
to know. DEVELOPMENT.md: the setting, --no-clipboard, the test hooks and the test client's flags.
docs/app-store-metadata.md: a description bullet waiting for the next submission, and the App
Privacy notes. CLAUDE.md: the Current step entry, the compatibility floor (kind 32 and the list's
clipboard), Layout, Build and run. The plan's Results, Untested for Noah and Progress.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… for its contents

Found live: a look between an app's clear of the pasteboard (which moves the change count) and its
write (which does not) took the count as dealt with, and the copy was never sent: a probe writing
50 to 550 ms after its clear lost 9 of 12 copies against the build before, none now
(ClipboardPolicy.hostWaits: a pasteboard with no items is looked at again for up to a second).

The pasteboard is now read and written on a queue of its own (HostClipboard.Board), one call at a
time and in order, so a slow read (an app asked for text it only promised) never holds up the main
actor, where devices' input is handled. A device's write is done before any later look, which finds
it as this host's own.

Tests/checks/clipboard: the settle rule's cases, an app that clears, is looked at and then writes
in the host model, and two more mutants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on, the header up to date

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… CI)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit that referenced this pull request Oct 14, 2026
…uests

The plan's Progress and the Current step entry: CI's Build and check on 807c8ef (run 38489635350),
the private simulators deleted, and the merge order: the pull request merges into main without a
conflict; with #74, #75, #77, #78 and #79 it meets in the docs, and with #79 also in
StreamScreen.swift's DEBUG hooks (keep both).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity and others added 9 commits October 14, 2026 10:52
The review found that HostClipboard applied the 4-a-second rate to every
kind 32 before reading what it asked, so a {"stop":true} that was the fifth
message in a second (the switch flipped off, on, off, on, off) was refused
as "rate": the connection stayed subscribed and every later Mac copy still
went to a device whose Share Clipboard was off.

ClipboardPolicy.hostReceives now decides what the message asks first, then
applies the rate to subscriptions and texts alone (countsAgainstRate): a stop
is always taken, and neither a stop nor a refusal counts against the rate.
Refusal gains `rate`, so the ignored line's counts are the refusals' names.

Tests/checks/clipboard: the review's sequence and the rate's edges (209
checks), and model 1b, 5,000 quick sessions of switch flips, Pastes and the
Mac's setting through hostReceives: never subscribed at the host while the
device's switch is off. Five new mutants (the review's ordering among them),
each caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that Sill.app read NSPasteboard.general at every copy while
a device shared the clipboard, and never checked macOS's paste privacy
(NSPasteboard.accessBehavior, macOS 15.4 and later). NSPasteboard.h says the
general pasteboard asks on every programmatic read by default once that is
on: each Mac copy would bring macOS's alert, and the read would wait for the
answer; denied, every copy was skipped as "no text" with nothing said.

ClipboardPolicy.MacAccess (allowed, notAsked, asks, denied, from the raw
value; a value this build does not know counts as asking): the host reads
only while macOS allows every read (hostLooks). Otherwise HostClipboard's
look reads nothing, not even the change count, and when macOS allows reads
again the count is taken as seen, so nothing copied meanwhile is sent
(accessStep). One line when it changes (accessLine), never at every look. A
device's text is still written: writing needs no leave.

Settings › General: ClipboardSection, with an orange line under the switch
while macOS asks or denies, what to do, and "Allow in System Settings…"
(Privacy & Security › Paste from Other Apps, Privacy_Pasteboard). While macOS
has not asked about Sill yet it is not listed there (NSPasteboard.h), so the
button first makes one read on the person's click (MacClipboard.askOnce, off
the main thread, the text dropped), then opens the pane. PermissionsModel
reads the state with the permissions. Previews: pane-clipboard-* in each state.

TEST ONLY SILL_TEST_CLIPBOARD_ACCESS (a synthetic host): a state, or a file
holding one, read at each look.

Tests/checks/clipboard: the mapping, the steps, the lines and the words (245
checks), and model 2 with macOS's answer changing (copies made while it asks
or denies never sent). Ten new mutants, each caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e does

The review found that Settings › General's footnote said "A password
manager’s copies stay on this Mac", where only copies marked as concealed (or
with ClipboardPolicy's other markers) do, which the plan itself cannot promise
for Apple's Passwords app; and "and theirs here" read as if a device's copies
came by themselves, where they come only with Paste. The support page ended a
paragraph about both directions with "Sill never reads your clipboard by
itself", which Sill for Mac does at every copy while a device shares it.

The footnote (now ClipboardPolicy.Copy.macFootnote, checked): "Text you copy
here can be pasted on a connected iPhone or iPad, and text sent from one with
Paste becomes this Mac’s. Copies a password manager marks as concealed stay on
this Mac." support.html and privacy.html: "Sill never reads your iPhone or
iPad’s clipboard by itself" / "your device’s clipboard". A mutant puts the old
promise back, caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…takes one

The review found that the floor, which holds for good, froze the
subscription as {} and called `image` reserved for a later generation without
saying that a host sends an image only to a device that asked for images. Pull
request #66's host sends {text, image} to every subscriber, so once it
rebases onto this shape every image copied on the Mac would travel to every
iPhone and iPad of this build, which drop it (about 3.5 MB of base64 at #66's
cap, seconds of an away link).

CLAUDE.md's floor, Clipboard.swift and the plan now say it: a subscription
says what the device takes, {} being text alone, and a host sends `image` only
to a connection whose subscription carries a later field saying it takes
images ("accepts":2, say). No code changes: this generation's host never
sends an image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that a Paste said "Sent to ‹Mac›’s clipboard" and counted
the copy as dealt with the moment kind 32 was handed to SessionLink. The
chip's main moment is Sill coming back from another app, when the connection
may be dead without its end read yet (the Mac evicts a suspended device; a
network change or a sleeping Mac sends no FIN, and liveness counts from the
foreground for up to 6 s): the text was lost, the person told it went, and
the chip never offered that copy again.

Now a Paste sends a ping after its text through the same door
(SessionLink, so on the same connection and after it, wherever a move takes
what waits), with a 16-byte random payload (ClipboardPolicy.receiptLength:
never a ping's 8 bytes of time, nor a fence's 8). The Mac echoes a ping only
once it has read all before it, so its pong is the receipt: then "Sent…", the
success haptic, and the copy dealt with (clipboardReceiptCame). No receipt in
max(4 s, 4 × the worst round trip) (receiptWait, as a settings pick waits):
"Didn’t reach ‹Mac›. Try Paste again.", the copy not dealt with, and the chip
offered again; a receipt that comes later still counts. No wire change: hosts
echo any ping, and only a host that shares the clipboard is sent one.

DEBUG: -SillSettingsScript gains `paste TEXT` (sendClipboard as the Paste
control hands TEXT over, under the script's guard: a loopback test host
only); -SillClipboardNote notsent. Tests/checks/clipboard: the receipt's
length and wait, the note (250 checks); three new mutants, each caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that the chip came without an announcement and its 20 s
timer ran whatever covered it: under the Settings panel, the Apps list, the
tour (on a fresh install the first session offers what is on the clipboard
and the tour covers it a second later), a thumbnail's lights or the Aa ruler,
it ran out unseen and counted the copy as dealt with for good; and someone
finding it with VoiceOver had 20 s.

ClipboardPolicy.chipCounts: the time runs only while the chip itself is on
screen (StreamScreen.clipboardChipOnScreen: nothing open over the stream, no
note in its place), Sill is active, and VoiceOver is off; hidden it waits and
then shows for what is left, and with VoiceOver on it stays until Paste or
Dismiss. StreamClient keeps the time spent (updateClipboardChipClock), from
the screen's word (clipboardChipShown), willResignActive, didBecomeActive and
VoiceOver's status. The first time the chip shows for a copy, VoiceOver is
told: "Paste on ‹Mac› is available, near the bar."

DEBUG: -SillSettingsCloseAt <s> closes the panel -SillSettings 1 opened.
Tests/checks/clipboard: the rule's eight cases and the words (259 checks);
three new mutants, each caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that the clipboard's support section, its privacy lines and
the privacy page's date land on main ahead of the release, and a republish
for another fix before it (#69's lines on a sleeping or restarted Mac are
queued, and hold for the builds already out) would publish them for apps
that do not have the feature.

The release checklist already republishes the site only after the release
whose builds a change describes; the words stay with this pull request as the
other features' do, now between `<!-- Clipboard: …` and `<!-- /Clipboard -->`
(support.html's section, privacy.html's three items), as the Remote Access
blocks are. Part 1 §3 says how a republish before that release cuts them
(the perl line beside the rsync, privacy.html's date put back), and that the
release deletes the markers. Checked on a copy: after the cut the pages differ
from main's only in the date and in words true of the builds out too ("what
you copy" in the privacy lede, the log lines' "never what you copy" and
"which devices share the clipboard"), and every page's tags and marker pairs
balance.

support.html also gains the line for macOS's paste privacy: if what you copy
on the Mac doesn't arrive, Settings › General says why and opens Paste from
Other Apps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the chip's time counted only while it shows (the review's fix), a
session that ended with the chip on screen left clipboardChipOnScreen true
until the next stream screen said otherwise, so a chip offered at the next
session's first window list could start its time for a moment before that
screen's first word. resetClipboard now sets it false with the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan's design and open questions now carry the review's changes (the
rate a stop never meets, macOS's paste privacy, a Paste's receipt, the chip's
time and VoiceOver's announcement, the site's words marked for the release),
and open question 1 says what on-by-default does to the device's clipboard,
for Noah. Results has "The review", each of the eight findings with what was
reproduced, what changed and how it was checked, and the final numbers:
`Tests/checks/clipboard` 259 checks and 72 of 72 mutants, run-all all 41, the
host and the iOS app three ways from git archive of a371cb6 with only the
known warnings, H2 against main again, the live runs on loopback and on a
private simulator, and the photos (byte for byte the reviewed build's but the
new note). Untested gains macOS's paste alert through the developer preview,
a Paste right after an app switch, and on-by-default's effect.

CLAUDE.md's entry, Layout and harness lines; DEVELOPMENT.md's clipboard
section (the receipt, the chip's time, macOS's paste privacy and how to try
it, SILL_TEST_CLIPBOARD_ACCESS, `paste` and -SillSettingsCloseAt); the checks'
README row (259 checks, 72 mutants).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lovelymammoth pushed a commit to lovelymammoth/sill-plus that referenced this pull request Oct 14, 2026
Code-Tomato added a commit to Code-Tomato/sill that referenced this pull request Oct 15, 2026
PR Saffsanity#66 (Sill Viewer, with Saffsanity#72 and Saffsanity#73) numbers 29 to 42, its own audio
at 33, and Saffsanity#78 takes 32. 43 is the next number no open pull request
holds. Every comment, script, kind check and mutant that named 29 for
the sound now names 43; the payloads are unchanged. docs/audio-plan.md,
"Kind 43 (2026-10-14)", has the survey and the results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity and others added 2 commits October 15, 2026 23:44
CLAUDE.md keeps every Current step entry, this branch's first; ci.yml's matrix
and the checks' README take every check; README, DEVELOPMENT.md, privacy.html
and support.html keep both sides' sections; HostSettingsPanel's This iPad group
has Larger Picture, then Share Clipboard, then the gestures; ContentView's
harness contract both sides' arguments.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main's paragraph still said no device had run Esc or the trackpad; the
iPad did on 2026-10-15 (docs/hardware-keyboard-plan.md, Results).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ashleigh-hopkins added a commit to ashleigh-hopkins/sill that referenced this pull request Oct 15, 2026
ashleigh-hopkins added a commit to ashleigh-hopkins/sill that referenced this pull request Oct 15, 2026
… the clipboard's kind 32 negotiation and the display teardown work

Conflicts kept both sides: the pill's comment has the bolt and the refresh that restarts captures; the
protocol mutants list both sides' cases; CLAUDE.md's wire list has the Dock bounces and full-speed
flags beside the clipboard's subscription; the plan keeps this branch's notifications line and
mac-client's merge notes.
ashleigh-hopkins added a commit to ashleigh-hopkins/sill that referenced this pull request Oct 15, 2026
…anity#78, the clipboard's kind 32 negotiation and the display teardown work

Side streams keep this branch's capture plan (StreamRate, the crop's pill after the encoder) with
mac-client's rule while display streams deliver nothing: no full-screen display capture and no crop
with its app, the window alone. CLAUDE.md's wire list has the paused and background requests beside
the clipboard's subscription; the checks list and README take both sides (protocol 249 + 8, 41
mutants).
Code-Tomato added a commit to Code-Tomato/sill that referenced this pull request Oct 18, 2026
… side

Main brought PRs Saffsanity#74, Saffsanity#75, Saffsanity#77, Saffsanity#78, Saffsanity#79, Saffsanity#80, Saffsanity#85 and Saffsanity#98. 19 files
conflicted; each kept both sides whole.

- Wire: kind 32 (the clipboard) beside 43 (the sound); 29-31 and 33-42
  stay free for Saffsanity#66.
- Settings: shareClipboard beside sendAudio in HostConfig, the app's
  defaults, the CLI (--no-clipboard, --audio) and sillclient.py.
- Larger Picture (Saffsanity#79): row 1 holds Sound before Settings from 384 pt
  (PhonePortraitLayout.row1Beside); narrower, Sound is the panel's switch.
- Saffsanity#75: a regular-mode window that streams on off the catalog's list keeps
  its sound (StreamCoordinator.streamedPID); before, it stopped.
- Docs and check labels that still called 29 the audio plan's, or said 32
  was free, now say what the merged tree does.

Verified: swift build -c release; iOS Debug and Release for the generic
simulator (only the known StreamClient warning); the CLI's CI paths;
Tests/checks/run-all.sh, all 47; the mutants of the 25 checks that compile
a file both sides changed, 936 of 936 caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to kyleparisi/sill that referenced this pull request Oct 19, 2026
Main moved on since this branch began at 985a733, to d1fa97e (Saffsanity#85, Saffsanity#74,
Saffsanity#77, Saffsanity#75, Saffsanity#79, Saffsanity#80, Saffsanity#78, Saffsanity#98, Saffsanity#153 and Saffsanity#142). None of them touched
Sources/SillHost/VirtualDisplay.swift, so the merge is clean and this pull
request's change applies to main as it is now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to ipavlidakis/sill that referenced this pull request Oct 19, 2026
Main moved on since this branch began at 985a733, to d1fa97e (Saffsanity#85, Saffsanity#74,
Saffsanity#77, Saffsanity#75, Saffsanity#79, Saffsanity#80, Saffsanity#78, Saffsanity#98, Saffsanity#153 and Saffsanity#142). None of them touched
Scripts/make-app.sh, so the merge is clean and this pull request's change
applies to main as it is now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HunterAhlquist pushed a commit to HunterAhlquist/sill that referenced this pull request Oct 19, 2026
privacy.html is on main ahead of every release, and a republish before
the Pencil ships (another fix's words) would tell readers the app sends
their Pencil's pressure and tilt and keeps a Pencil Pressure switch, which
the builds out do not. As the clipboard's words are (PR Saffsanity#78), each Pencil
item now stands alone between `<!-- Pencil: …` and `<!-- /Pencil -->`:
"Your Apple Pencil's strokes, with how hard you press and how you tilt it."
among what the device sends, and "On an iPad, whether Pencil Pressure is
on." among what it keeps. docs/release-checklist.md part 1 §3 gains the
perl line that cuts them before that release, and says which Pencil lines
stay after the cut (index.html's intro, terms.html's trademarks). Checked
on a copy: after the Pencil's cut site/ is main's byte for byte, and with
the clipboard's too `grep -i pencil` prints only those two lines. The page's
date is left for the release to set, as the plan says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to Code-Tomato/sill that referenced this pull request Oct 20, 2026
…re drafts

The PR left the public copy for the release that carries sound, but once
it merges main is that release, and README, the App Store description and
the privacy policy said Sill plays no sound. As the clipboard (Saffsanity#78) did:

- README: How it works gains "Sound from your Mac", a beta off until Send
  Audio (Beta) is turned on; Good to know says Sill plays sound from your
  Mac only as a beta, off by default (the trademark note's wording, not
  "your Mac's sound").
- site/privacy.html (dated October 20, 2026): what the Mac sends while
  Send Audio is on, the device's report gaining how far the sound trails
  and the late packets, the mute kept on the device, and what Screen
  Recording is also for; site/support.html: "Sound from your Mac (beta)",
  one paragraph (turning it on, the Sound button and the panel's switch,
  the silent switch, headphones out, AirPods' delay). Every line inside
  "<!-- Sound: for the release that ships it (PR Saffsanity#94); …" markers.
- docs/release-checklist.md part 1 §3: the Sound markers and their perl
  line. Tried on a copy: every page but privacy.html's date (and a line
  break in one list item, and a blank line in support.html) is main's.
- docs/app-store-metadata.md: for the next version, the description's
  bullet (and "Sill doesn't play sound" out of Good to know: 3,174
  characters), the What's New paragraph, and one review-notes line in
  WHAT TO TRY with a redundant sentence of PURPOSE AND AUDIENCE out (3,995
  bytes of 4,000); the App Privacy answers and Before you submit name the
  sound. "Beta" is always on the feature, never on the app (guideline 2.2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Add Clipboard sharing bi-directionally

1 participant