Skip to content

Reverse Scrolling: a switch that turns a finger's drag around (#143) - #159

Merged
Saffsanity merged 13 commits into
Saffsanity:mainfrom
ArfiArfath21:reverse-scrolling
Oct 27, 2026
Merged

Saffsanity merged 13 commits into
Saffsanity:mainfrom
ArfiArfath21:reverse-scrolling

Conversation

@ArfiArfath21

Copy link
Copy Markdown
Contributor

Closes #143.

A finger's drag scrolls the Mac's content the way a phone does: it follows the finger. #143 asks for the other way round. This adds Reverse Scrolling to Settings, in the This iPad group (This iPhone on a phone), off by default.

  • Device only: a Sill.reverseScroll preference like the gestures and clipboard switches. Nothing is sent to the Mac, no wire change, no new message kind.
  • Only a finger's drag turns around. A trackpad's or a mouse wheel's scroll events already follow the direction in iPadOS's own settings, so they are left alone, and the footnote says so.
  • The flick's coast after the lift keeps the direction its drag had (the sign is read once, when the scroll begins).
  • ScrollDirection.factor(reversed:finger:) is pure and lives in StreamProtocol, so it needed no project-file entry and is covered by the protocol check (4 checks, 3 mutants, all caught). Tests/checks/README.md has the new counts.

For review

  • InputOverlay.swift: the sign is applied in scrolled(to:at:) and to the coast's velocity in startMomentum. Worth a look that no other path sends a .scroll (I found none).
  • Whether Reverse Scrolling should also apply to a trackpad or wheel is a decision for you; I kept it to the finger, which is what the issue describes.

Verified, and not

  • Tests/checks/run-all.sh protocol passes, and its three new mutants are caught.
  • The iOS target type-checks with my Xcode 26.6 apart from the one error main already has there (DeviceHingeContext in DuoPostureReader.swift, which needs the 27.1 SDK), so I could not run the app. I have not tried the switch on a device or in the simulator.

AI assistance: this change was written by an AI coding assistant (Claude) at my direction. I reviewed the diff and ran the checks above.

…nity#143)

A device-only switch in Settings, This iPad: off by default, never sent to
the Mac. While on, a finger's drag scrolls the content the other way, and
its coast after the lift keeps that direction. A trackpad's and a mouse
wheel's scrolls are left to the device's own system setting.

ScrollDirection.factor is pure, so the protocol check covers it, with
three mutants.
@Saffsanity

Copy link
Copy Markdown
Owner

From a pass over all the open pull requests (nothing is decided yet): #107 adds an @AppStorage line and a row at the same spot in HostSettingsPanel.swift (after the clipboard footnote), so whichever lands second keeps both and picks an order; the protocol check's main.swift and mutants.py also change in #66 (and #72 and #73, built on it), and rows of Tests/checks/README.md in #94, #100, #124, #142 and #158, so the second to land updates the counts. #158's visionOS client scrolls through the same finger pan, so the switch would reach it too.

Saffsanity and others added 6 commits October 20, 2026 00:45
Main gained Saffsanity#153 (the checks' build scripts quote their paths) and Saffsanity#142
(saved Macs keep a typed destination across a key change). Only
Tests/checks/README.md conflicted: the protocol row is this branch's,
the remote-rules row main's.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…round too

The switch reached only a finger's drag over the picture. Two fingers on the
on-screen trackpad of the upright layouts scroll the same way (the content
follows the fingers), so the switch now turns both around, each scroll's
deltas and the coast after its lift, read once as the scroll begins. A
trackpad or mouse connected to the device is left to iPadOS's own Natural
Scrolling, which has already turned its scroll events.

ScrollDirection names the four sources (finger, pad, trackpad, wheel) and
which the switch reaches; the protocol check covers every source with the
switch off and on (205 checks, 32 mutants: 8 for the rule). TrackpadSurface's
two-finger deltas go through one scroll(dx:dy:) that applies the factor, used
by the pan, the DEBUG input test and the new script step.

The footnote says what it turns and, on an iPad, that a connected trackpad or
mouse follows its own Natural Scrolling setting. DEBUG: -SillInputScript gains
drag DX,DY (a finger over the picture) and padscroll DX,DY (two fingers on the
pad), each with its coast; -SillSettingsScroll reverse opens the panel at the
switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SILL_TEST_INPUT_LOG printed every scroll event a synthetic host would post as
"scroll", so a test could not tell which way a device's scroll went. It now
prints the point deltas and where the event sits in a gesture ("Test input:
scroll dy -12 dx 0, gesture changed (none)": positive dy moves the content
down), which is how Reverse Scrolling's direction is checked live. TEST ONLY:
nothing changes without the variable, and only a --synthetic host takes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…What's New

DEVELOPMENT.md gets a section on what the switch turns and what it leaves to
iPadOS, and the harness's new steps; CLAUDE.md's Layout and Build and run name
ScrollDirection, the pad's scroll and the arguments; README's input bullet a
sentence. The site's words go between Reverse Scrolling markers, cut by a perl
line in the release checklist before the release that ships them: the privacy
policy lists the switch among what stays on the device (beside three-finger
gestures and Larger Picture; its date today's), and the support page has a
short section on scrolling the other way. The next What's New draft gains a
paragraph.

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

Copy link
Copy Markdown
Contributor Author

Merged main in (8137184, no rebase). The only conflict was the protocol row of Tests/checks/README.md (now 196 + 8 checks, 27 mutants). The iOS target still type-checks apart from DeviceHingeContext, which needs the 27.1 SDK. Thanks for the overlap notes: whichever of #107 and this lands second keeps both switches and I'll take the order you pick, and the visionOS client gets the switch through the shared finger pan.

Saffsanity and others added 6 commits October 26, 2026 14:44
ArfiArfath21 merged main (4d990e9) into the pull request's branch with no
change to the code; this branch had already merged the same main. Only
Tests/checks/README.md's protocol row conflicted: this branch's row stays
(205 + 8 checks, 32 mutants, the on-screen trackpad's two-finger scroll
among them), since the fork's 196 + 8 and 27 predate the pad's cases.

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

One line of the section ran to 115 columns where the file wraps near 78; the
words are unchanged.

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

TrackpadView.swift and InputOverlay.swift now read StreamClient.reverseScrollOn
and ScrollDirection as each scroll begins, which the rig's scratch app did not
have, so Tests/touchrig/run.sh no longer compiled this branch's surfaces. The
rig compiles StreamProtocol's ScrollDirection.swift beside Input.swift where the
sources have it (a --rev from before the switch has none), and Stubs.swift
gives StreamClient's switch, off: the rig's verdicts compare the surfaces with
those before the switch, whose scrolls go the natural way. DEVELOPMENT.md's
section on the rig says so.

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

Only docs/release-checklist.md conflicted: both branches mark words on the site
for the release that ships them. Both stay: the paragraph names the clipboard's,
the display wake's and Reverse Scrolling's markers and the greps that show each
cut, and the republish's commands keep both perl lines, the display wake's first.

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

The paragraph after the table says which branch each count comes from; the
protocol row's 205 + 8 checks and 32 mutants include this branch's cases for
the four scroll sources and their eight mutants.

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

Three docs files met, each keeping both sides: CLAUDE.md's Layout (the
StreamProtocol paragraph with ScrollDirection beside Saffsanity#93's kind 44, and the
TrackpadView and HEVCDisplayView paragraph beside the keyframe recovery),
the checks README's lede, and the release checklist's paragraph on marked
words, which now names Clipboard, Keyframe, Display wake and Reverse
Scrolling once each, with all four perl lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Saffsanity
Saffsanity merged commit c215514 into Saffsanity:main Oct 27, 2026
2 checks passed
Saffsanity added a commit to addhumal/sill that referenced this pull request Oct 27, 2026
Main brings Saffsanity#159, Reverse Scrolling (issue Saffsanity#143): ScrollDirection in
StreamProtocol, the device's switch in the Settings panel's This iPad group,
a finger's drag over the picture and the portrait trackpad's two-finger scroll
turned around with their coast, InputScript's drag and padscroll steps, its
words in privacy.html and support.html between Reverse Scrolling markers, and
protocol's ScrollDirection cases and mutants.

Where it met Saffsanity#117 (the connect screen's tiles and groups):
- Tests/checks/README.md: the closing sentence names both, `connection-list`
  with Saffsanity#117 (Saffsanity#116) and `protocol`'s Reverse Scrolling cases and mutants with
  Saffsanity#159 (issue Saffsanity#143). The connection-list row (Saffsanity#117), and the protocol row and
  the lede's Reverse Scrolling phrase (Saffsanity#159), merged by themselves.
- docs/release-checklist.md: both sides had rewritten the paragraph on marked
  words the same way and each added its own marker. It now names every marker
  once, Clipboard, Keyframe, Display wake, Tiles and Reverse Scrolling, with
  every grep, and both perl lines stay (Tiles, then Reverse Scrolling). Run on
  a copy of the merged site: the five perl lines leave no marker, the
  clipboard grep prints only privacy.html's log line and the other four print
  nothing, also with either feature's words left unmarked.
- Merged by themselves, each checked against both parents: CLAUDE.md (Layout's
  ScrollDirection, InputOverlay and TrackpadView lines and Build and run's
  lines beside Saffsanity#117's ConnectionList and check list; neither side has a
  Current step entry), docs/DEVELOPMENT.md, site/privacy.html (each item
  inside its own marker), iOSClient/ContentView.swift (Saffsanity#159's harness contract
  lines and InputScript's steps beside Saffsanity#117's connect screen and its contract
  lines) and iOSClient/StreamClient.swift (reverseScrollKey beside macGroups,
  macPreviews and the tile's picture at tearDown). No Swift file conflicted.
  ci.yml's mutants matrix is Saffsanity#117's (Saffsanity#159 left it alone): sorted, with
  connection-list and protocol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to addhumal/sill that referenced this pull request Oct 27, 2026
…sleep

Only Tests/checks/README.md conflicted, two neighbouring rows each side
changed alone: this branch's `policy` row (issue Saffsanity#105's cases, 507 and
201) and main's `protocol` row (Reverse Scrolling's cases, 205 + 8 and
32), both kept as their side wrote them. CLAUDE.md, docs/DEVELOPMENT.md
and iOSClient/StreamClient.swift merged by themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to addhumal/sill that referenced this pull request Oct 27, 2026
On the head with Saffsanity#159 merged in: a clean release build from a git
archive, the iOS app as CI builds it from fresh derived data (the known
StreamClient warning now at line 3904), run-all.sh's 48, goodbye's 89 of
89 mutants again (its check compiles every StreamProtocol source, which
the merge changed), the bare app's ten goodbye runs again, alike. Policy's
201 of 201 stand from 36e7212's merge: the merge leaves DiscoveryPolicy.swift
and the check as they were. The entry names main at c215514.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to Code-Tomato/sill that referenced this pull request Oct 27, 2026
Saffsanity#159's Reverse Scrolling (issue Saffsanity#143) beside the sound: the device's own switch
(Sill.reverseScroll, never sent) turns a finger's drag over the picture and two fingers on the
on-screen trackpad around, coast included (StreamProtocol's ScrollDirection, with protocol's 13
cases and 8 mutants), the touch rig and InputScript's drag and padscroll, and the host's TEST ONLY
scroll log line with its deltas and phase (InputInjector.swift, which this branch never touched).
No wire change, and no host file both sides changed: the sound's StreamServer, StreamCoordinator
and pipeline are as they were.

Six files conflicted, each resolved by keeping both sides:
- iOSClient/HostSettingsPanel.swift, four hunks. The This iPad group merged by itself in Saffsanity#159's
  order (Sound from ‹Mac› first, Larger Picture, Share Clipboard, Reverse Scrolling, Three-Finger
  Gestures). Saffsanity#159's -SillSettingsScroll reverse becomes a case of this branch's startsAt switch
  (reverseScrollRowID) in place of its startsAtReverseScroll and second scrollTo, whose other line
  named startsAtGestures, which this branch had folded into startsAt; one onAppear scrolls to
  whichever the argument names. The type's and the group's doc comments name both sides' rows.
- iOSClient/ContentView.swift: the harness contract names -SillSettingsScroll sound and reverse.
- CLAUDE.md: the Layout's StreamProtocol paragraph has Audio.swift, then ScrollDirection.swift,
  then kind 44's sentence once. Saffsanity#159's Layout and Build and run lines and the Current step merged
  by themselves (Saffsanity#159 has no Current step entry).
- Tests/checks/README.md, three hunks: the opening list names Reverse Scrolling and the sound's
  packets; protocol's row both descriptions, 234 + 8 checks and 42 mutants (the sound's 221 + 8
  and 34 with Reverse Scrolling's 13 and 8, from the merged check's run); the history paragraph
  both clauses, with a sentence on this merge's counts.
- docs/release-checklist.md: the paragraph on marked words names each marker once (Clipboard,
  Keyframe, Display wake, Sound, Reverse Scrolling) with each one's grep, where both sides had
  rewritten main's repeated paragraph; every perl line kept, Sound's, then Reverse Scrolling's.
- docs/app-store-metadata.md: both bullets, the sound's first; the next What's New's draft with
  both paragraphs is 1,322 characters.
site/privacy.html and site/support.html merged by themselves, each pull request's words inside its
own markers; with all five blocks taken out as a republish before the releases would, the
checklist's greps print what it says (one clipboard line, nothing else). README.md,
docs/DEVELOPMENT.md, iOSClient/StreamClient.swift and protocol's main.swift and mutants.py merged
by themselves, each side's lines whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to cinminori/sill that referenced this pull request Oct 27, 2026
Conflicts, each kept whole on both sides:
- Tests/touchrig/run.sh: the surfaces compile with TextComposition.swift
  (this branch) and ScrollDirection.swift (Saffsanity#159), both in the fetch list
  and on the compile line.
- docs/release-checklist.md: the marked words list names other languages
  and dictation (Saffsanity#147) and Reverse Scrolling (Saffsanity#159), with both perl lines
  and both greps; a republish simulated on a copy of the merged site/
  leaves no marker and no hit for any of the greps (the clipboard's only
  privacy.html's log line).
- README.md's How it works: the input methods' and dictation's sentences,
  then Reverse Scrolling's.
- Tests/checks/README.md's opening list: Reverse Scrolling's clause and the
  input methods' both.
- CLAUDE.md's Layout, InputOverlay: the scroll path with Reverse
  Scrolling's direction, then the text input's clause.
InputOverlay.swift, StreamClient.swift, DEVELOPMENT.md, the metadata and
the site merged by themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to cinminori/sill that referenced this pull request Oct 27, 2026
…length

CLAUDE.md's Layout names the rule beside the input view and TextInputRules
(a Return or a Tab typed as text moves the Mac's caret: carriesReturnOrTab).
docs/app-store-metadata.md's dated counts gain the next What's New as the
merge with Saffsanity#159 leaves it: Saffsanity#147's paragraph of 247 characters beside Reverse
Scrolling's, the draft 1,266 characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to scripsweave/sill that referenced this pull request Oct 27, 2026
…affsanity#93, Saffsanity#151) into fix/remote-desktop-qr-code

Main brought Send Audio as a beta (Saffsanity#94), Reverse Scrolling (Saffsanity#159),
SILL_TEST_NO_SCREEN (Saffsanity#227), the Close timeout and the interrupted picture
(Saffsanity#93) and a sleeping display woken for a device (Saffsanity#151). Six files
conflicted, each resolved keeping both sides:

- .github/workflows/ci.yml: the mutants matrix is main's 51 checks with
  pairing-sharing, sorted: 52, exactly the folders of Tests/checks with a
  mutants.py.
- Sources/SillMenuBar/HostSettings.swift: Saffsanity#94's sendAudio beside
  showPairingWindowToScreenSharing in Key and in the registered defaults
  (sendAudio first, a HostConfig knob next to shareClipboard), as Noah's
  comment of 2026-10-19 settled the overlap: whichever lands second keeps
  both. HostConfig's sendAudio, read and saved, and this switch's property
  merged by themselves.
- Sources/SillMenuBar/DebugHooks.swift: -SillSetAfter's list names both
  keys (sendAudio, and showPairingWindowToScreenSharing=0|1; shareClipboard,
  which both sides had added, once); both cases of apply merged by
  themselves.
- Tests/checks/README.md: main's rewrapped introduction with this PR's
  "whether screen sharing shows the pairing window"; the pairing-sharing
  row beside main's phone-portrait and pointer-control rows; the history's
  list of checks and their pull requests ends with pairing-sharing and Saffsanity#157.
- docs/release-checklist.md: the marker paragraph names every marker once
  (Clipboard, Keyframe, Display wake, Sound, Reverse Scrolling, Pairing
  window), the screen sharing grep among those that print nothing, and all
  six perl lines.
- CLAUDE.md: both Current step entries, this PR's first, then main's
  three; Build and run's defaults list with sendAudio and
  showPairingWindowToScreenSharing; this PR's entry names this merge, and
  its verification is this merge's (Tests/checks/run-all.sh, all 52; the
  previews against main's 74d099c, the Devices pane's eight differ, two
  new, the other 136 byte for byte).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to addhumal/sill that referenced this pull request Oct 27, 2026
Main gained the pairing window shown to screen sharing (Saffsanity#157, issue Saffsanity#108:
Settings › Devices' Show the pairing window to screen sharing,
PairingWindowSharing and the pairing-sharing check, README, SECURITY.md,
DEVELOPMENT.md and the plans, support.html's item marked <!-- Pairing window).
Where it met the connect screen's tiles and groups (Saffsanity#117):

- .github/workflows/ci.yml: the mutants matrix is the sorted union,
  connection-list and pairing-sharing both in (53 checks, exactly the
  folders with a mutants.py).
- Tests/checks/README.md: both rows (connection-list after compatibility,
  pairing-sharing after pairing-address); the history's first sentence names
  connection-list with Saffsanity#117 (Saffsanity#116), then Reverse Scrolling's with Saffsanity#159 and
  pairing-sharing with Saffsanity#157 (Saffsanity#108), and main's re-wrapped audio sentences
  follow unchanged. Main's lede phrase merged by itself (this branch has
  none).
- docs/release-checklist.md: the marked-words paragraph names Clipboard,
  Keyframe, Display wake, Sound, Tiles (PR Saffsanity#117), Reverse Scrolling and
  Pairing window (PR Saffsanity#157) once each, with every grep ('tiles|group name'
  and 'screen sharing' among them), one to a line; the perl lines merged by
  themselves, Tiles' and Pairing window's among them, seven in all. On a
  copy of the merged site/, the seven lines leave no marker and the greps
  print what the paragraph says.

Merged by themselves, each differing from the other parent by exactly that
side's hunks: CLAUDE.md (main's Current step entry for Saffsanity#157 at the top, this
branch having none; Layout's PairingWindowSharing beside the tiles'
paragraph; the checks list with connection-list and pairing-sharing; Build
and run's defaults list with showPairingWindowToScreenSharing beside the
tiles' -SillSettingsScript disconnect step) and docs/DEVELOPMENT.md (main's
Through Screen Sharing item and Settings › Devices sentence, the tiles'
paragraphs and harness lines). site/privacy.html (the Tiles items) and
site/support.html (the Pairing window item) changed on one side each,
inside their own markers; the project file on this branch's side only
(plutil OK, each id once).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saffsanity added a commit to andrewgribben/sill that referenced this pull request Oct 29, 2026
Main has moved 253 commits since this branch's base (4d990e9): among them Reverse Scrolling's
ScrollDirection in InputOverlay's scroll path (Saffsanity#159) and the text input work (Saffsanity#147, Saffsanity#110).
Git merged the four files this branch changes by itself; both sides kept.

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

Copy link
Copy Markdown
Owner

Thank you @ArfiArfath21, Reverse Scrolling is in Sill for iPhone and iPad 0.5.5 (5) on TestFlight, in Settings › This iPad (This iPhone on a phone), so you can finally try the switch on a device. It works with the Mac app people have now (0.5.1). Please tell me here how it feels, both the drag and the coast after a flick!

The newest Sill for Mac is the nightly build on GitHub: https://github.com/Saffsanity/sill/releases/tag/nightly. This change works with Sill for Mac 0.5.1 as well.

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.

Feature request: reverse scrolling

2 participants