summaryrefslogtreecommitdiff
path: root/test/macos-snapshots
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-10 09:58:31 -0300
committerGabriel Schneider <[email protected]>2026-08-11 09:58:59 -0300
commiteb11ab331b4e13e2b9e5a673a4c012d22fdd1d9c (patch)
treecc993ad239451adb6f23645ee5ca001802832ed0 /test/macos-snapshots
parent38e9919a9ea9055538409b388d580c4e4c838434 (diff)
downloadpardes-eb11ab331b4e13e2b9e5a673a4c012d22fdd1d9c.tar.gz
pardes-eb11ab331b4e13e2b9e5a673a4c012d22fdd1d9c.zip
macos: the AppKit shell, its icon, and the offscreen e2e harness
Diffstat (limited to 'test/macos-snapshots')
-rw-r--r--test/macos-snapshots/boot.golden58
-rw-r--r--test/macos-snapshots/boot.snap23
-rw-r--r--test/macos-snapshots/cwd.golden100
-rw-r--r--test/macos-snapshots/cwd.snap32
-rw-r--r--test/macos-snapshots/font.golden179
-rw-r--r--test/macos-snapshots/font.snap49
-rw-r--r--test/macos-snapshots/keys.golden325
-rw-r--r--test/macos-snapshots/keys.snap59
-rw-r--r--test/macos-snapshots/rotate.golden156
-rw-r--r--test/macos-snapshots/rotate.snap63
-rw-r--r--test/macos-snapshots/trackpad.golden311
-rw-r--r--test/macos-snapshots/trackpad.snap91
12 files changed, 1446 insertions, 0 deletions
diff --git a/test/macos-snapshots/boot.golden b/test/macos-snapshots/boot.golden
new file mode 100644
index 00000000..28e5a1de
--- /dev/null
+++ b/test/macos-snapshots/boot.golden
@@ -0,0 +1,58 @@
+== snap boot grid=80x24 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/boot/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== draw boot 720x408 nonblank
+== snap resized grid=100x30 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/boot/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== draw resized 900x510 nonblank
diff --git a/test/macos-snapshots/boot.snap b/test/macos-snapshots/boot.snap
new file mode 100644
index 00000000..e3d4ba14
--- /dev/null
+++ b/test/macos-snapshots/boot.snap
@@ -0,0 +1,23 @@
+# The whole boot path in one script: pardes_init at the grid the offscreen
+# window actually measured, the first shell reaching its prompt, and the
+# CoreText pass painting. `draw` is the only command in the suite that touches
+# the drawing code at all — everything else reads the core's cell buffer, which
+# a draw(_:) that returned on its first line would leave perfectly intact.
+#
+# The macOS backend takes no argv, so it always boots the way a bare
+# `start 30 140` does in test/snapshots: one terminal pane in raw tty mode,
+# prompt visible. `$ ` is the prompt the hermetic .bashrc pins, and no chrome
+# row carries a `$`, so waiting on one is waiting on the shell.
+start 24 80
+wait 8000 New Newcol
+wait 8000 $
+stable 700 20000
+snap boot
+draw boot
+# The window changing size is the other half of the boot contract: the core
+# reflows, the view re-measures, and the second `draw` proves the new size is
+# the size that actually got painted.
+resize 30 100
+stable 700 20000
+snap resized
+draw resized
diff --git a/test/macos-snapshots/cwd.golden b/test/macos-snapshots/cwd.golden
new file mode 100644
index 00000000..748d523d
--- /dev/null
+++ b/test/macos-snapshots/cwd.golden
@@ -0,0 +1,100 @@
+== snap spawned grid=100x24 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/cwd/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap moved grid=100x24 cursor=4,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp New Del
+| $ cd /tmp
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap moved-again grid=100x24 cursor=4,4
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ / New Del
+| $ cd /tmp
+| $ cd /
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap idle grid=100x24 cursor=4,4
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ / New Del
+| $ cd /tmp
+| $ cd /
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/macos-snapshots/cwd.snap b/test/macos-snapshots/cwd.snap
new file mode 100644
index 00000000..87f6f9ef
--- /dev/null
+++ b/test/macos-snapshots/cwd.snap
@@ -0,0 +1,32 @@
+# The pane tag has to follow the shell around, because a relative `Look` is
+# resolved against it: with a stale cwd, clicking `README.md` after a `cd`
+# looks for it in the directory the pane was SPAWNED in and finds nothing.
+#
+# On linux the host re-reads /proc/<pid>/cwd every frame. macOS asks libproc,
+# and asks it only for a pane that just produced output — a `cd` is a command
+# and a shell that ran a command prints its next prompt, so nothing else can
+# have moved one. This script is what proves that gating is not too clever:
+# the tag must be right AFTER the cd and must stay right when nothing happens.
+start 24 100
+wait 8000 $
+stable 700 20000
+# The hermetic world puts the shell in <base>/work, so the boot tag names it
+# and no absolute path from this machine can leak into the golden.
+snap spawned
+# `cd` writes nothing but the next prompt: the one and only signal the lazy
+# refresh gets. Two of them, because the second has to survive the first
+# having already cleared the flag.
+text cd /tmp
+key enter
+wait 8000 $
+stable 700 20000
+snap moved
+text cd /
+key enter
+wait 8000 $
+stable 700 20000
+snap moved-again
+# ...and an idle pass must not lose it again: the refresh clears its own flag,
+# so a tick with no output must leave the tag exactly where it was.
+stable 700 20000
+snap idle
diff --git a/test/macos-snapshots/font.golden b/test/macos-snapshots/font.golden
new file mode 100644
index 00000000..e61abecd
--- /dev/null
+++ b/test/macos-snapshots/font.golden
@@ -0,0 +1,179 @@
+== snap boot grid=100x24 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/font/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap menlo grid=100x24 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/font/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap unchanged grid=100x24 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/font/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap bigger grid=81x19 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/font/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap reset grid=100x24 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/font/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap floor grid=225x58 cursor=4,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/font/cwd New Del
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/macos-snapshots/font.snap b/test/macos-snapshots/font.snap
new file mode 100644
index 00000000..71049b55
--- /dev/null
+++ b/test/macos-snapshots/font.snap
@@ -0,0 +1,49 @@
+# The font picker and the zoom, which are the two things this shell decides
+# entirely on its own. Neither is visible in a cell buffer — the core has no
+# font and no idea what a point is — so what a snapshot CAN see is the second
+# effect of both: a cell that changed size fits a different number of columns
+# into the same window, and the core reflows to it.
+#
+# `font` asserts the face by PostScript name, which is the only direct look at
+# the thing under test.
+start 24 100
+wait 8000 $
+stable 700 20000
+snap boot
+
+# Menlo ships with every macOS and ships inside a .ttc, so this is also the
+# end-to-end proof of the collection branch in src/fonts.zig: the core walks
+# the font directories, matches the name, hands over a path, and the host has
+# to pull face 0 out of a file holding four.
+command Font Menlo
+stable 700 20000
+font Menlo-Regular
+snap menlo
+
+# A name that resolves to nothing must leave the screen alone rather than
+# fall back to something. `Font` finds no file, sets no path, and the host is
+# never asked to do anything at all.
+command Font NoSuchFaceExistsHere
+stable 700 20000
+font Menlo-Regular
+snap unchanged
+
+# Zoom is a pure host property: no builtin, no core round trip, just a bigger
+# cell and the reflow that follows it. Four points is enough to move the grid
+# at any starting size.
+zoom 4
+stable 700 20000
+font Menlo-Regular
+snap bigger
+
+# ...and back, which must land on exactly the grid it started from — the whole
+# point of "actual size" being a fixed number rather than an undo stack.
+zoom reset
+stable 700 20000
+snap reset
+
+# Small enough to be worth checking the clamp does not run away: the range
+# floor is 6pt, so this asks for far past it and must simply stop.
+zoom -40
+stable 700 20000
+snap floor
diff --git a/test/macos-snapshots/keys.golden b/test/macos-snapshots/keys.golden
new file mode 100644
index 00000000..cff9484e
--- /dev/null
+++ b/test/macos-snapshots/keys.golden
@@ -0,0 +1,325 @@
+== snap typed grid=80x24 cursor=16,2
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/keys/cwd New Del
+| $ echo al''pha
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap ran grid=80x24 cursor=4,4
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/keys/cwd New Del
+| $ echo al''pha
+| alpha
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap fixed grid=80x24 cursor=16,4
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/keys/cwd New Del
+| $ echo al''pha
+| alpha
+| $ echo bra''vo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap second grid=80x24 cursor=4,6
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+|$ /private/tmp/pardes-macos-e2e/keys/cwd New Del
+| $ echo al''pha
+| alpha
+| $ echo bra''vo
+| bravo
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap normal grid=80x24 cursor=4,6
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap moved grid=80x24 cursor=7,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap home grid=80x24 cursor=2,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap end grid=80x24 cursor=6,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap pagedown grid=80x24 cursor=2,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap pageup grid=80x24 cursor=2,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap deleted grid=80x24 cursor=2,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap escaped grid=80x24 cursor=2,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap tabbed grid=80x24 cursor=2,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/keys/cwd New Del
+|
+| alpha
+|
+| bravo
+|
+| bash: bravo: command not found
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/macos-snapshots/keys.snap b/test/macos-snapshots/keys.snap
new file mode 100644
index 00000000..b81b88b7
--- /dev/null
+++ b/test/macos-snapshots/keys.snap
@@ -0,0 +1,59 @@
+# Both halves of pardes_key's vocabulary through PardesView.typeKey: the four
+# ASCII controls, the private-use navigation block, and a Ctrl chord. Markers
+# are split with '' so the echoed command line can never be the thing a `wait`
+# matched — the output has to be what arrived.
+start 24 80
+wait 8000 New Newcol
+wait 8000 $
+stable 700 20000
+# raw tty mode: characters and Enter travel to the shell through the pty
+text echo al''pha
+stable 400 5000
+snap typed
+key enter
+wait 8000 alpha
+stable 700 10000
+snap ran
+# backspace before Enter — readline owns the line while the pane is raw
+text echo bra''vv
+key backspace
+text o
+stable 400 5000
+snap fixed
+key enter
+wait 8000 bravo
+stable 700 10000
+snap second
+# Ctrl-b leaves raw tty mode. From here the same keys are the editor's, which
+# is the half of the keyboard the pty never sees.
+key c-b
+stable 600 8000
+snap normal
+key up up left
+stable 400 5000
+snap moved
+key home
+stable 400 5000
+snap home
+key end
+stable 400 5000
+snap end
+key pagedown
+stable 400 5000
+snap pagedown
+key pageup
+stable 400 5000
+snap pageup
+key delete
+stable 400 5000
+snap deleted
+# Escape in normal mode is Last, and with one pane there is nowhere to go —
+# which is exactly the no-op worth pinning: a regression here starts jumping.
+key escape
+stable 400 5000
+snap escaped
+# Tab falls through to Exec on the word under the cursor; parked where there is
+# no word, this pins the empty case rather than spawning anything.
+key tab
+stable 700 10000
+snap tabbed
diff --git a/test/macos-snapshots/rotate.golden b/test/macos-snapshots/rotate.golden
new file mode 100644
index 00000000..54f0d217
--- /dev/null
+++ b/test/macos-snapshots/rotate.golden
@@ -0,0 +1,156 @@
+== snap results grid=100x30 cursor=4,8
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/rotate/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/rotate/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap forward grid=100x30 cursor=7,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/rotate/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/rotate/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap forward2 grid=100x30 cursor=7,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/rotate/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/rotate/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap back grid=100x30 cursor=7,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/rotate/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/rotate/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap forward_by_key grid=100x30 cursor=7,7
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/rotate/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/rotate/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== draw rotate 900x510 nonblank
diff --git a/test/macos-snapshots/rotate.snap b/test/macos-snapshots/rotate.snap
new file mode 100644
index 00000000..17b239f4
--- /dev/null
+++ b/test/macos-snapshots/rotate.snap
@@ -0,0 +1,63 @@
+# pardes_rotate spends a two-finger twist as the search-step keys.
+#
+# AppKit reports rotation counterclockwise-positive and the core reads clockwise
+# as `n`, so a NEGATIVE delta is the FORWARD step. That inversion is exactly the
+# kind of thing a unit test on the accumulator cannot catch and a golden can:
+# what the screen does is the assertion.
+#
+# One notch is 20 degrees (rotation_notch_degrees in src/macos.zig) and the
+# remainder is banked, which makes the pair below exact rather than approximate:
+# -25 spends one notch and banks -5, then +25 lands on 20 and spends one back.
+start 30 100
+wait 8000 New Newcol
+wait 8000 $
+stable 700 20000
+text printf 'x MA''RK a\nzz\nx MA''RK b\nzz\nx MA''RK c\n'
+key enter
+wait 10000 MARK c
+stable 700 10000
+key c-b
+stable 600 8000
+# `/` types the pattern into the tag; Enter searches the pane's scrollback and
+# writes the three hits into a +Search buffer, which is what n/N walk.
+text /
+stable 400 5000
+text MARK
+stable 400 5000
+key enter
+wait 10000 @p0:
+stable 700 15000
+snap results
+# A gesture beginning re-zeros the dial, so travel left over from an earlier
+# twist cannot make the first degree of this one jump a match.
+rotate 0
+# Clockwise: one notch of `n`, forward onto the first hit and selecting it.
+rotate -25
+stable 700 15000
+snap forward
+# ...and again, onto the second. Two steps out rather than one, because the
+# first hit is where the list already starts: stepping BACK from it has nowhere
+# to go, and a golden that cannot move is a golden that proves nothing about
+# which way the dial turns.
+rotate -25
+stable 700 15000
+snap forward2
+# A new gesture, because the dial banks its remainder exactly like the scroll
+# accumulator does: two 25-degree notches leave -10 behind, so reversing INSIDE
+# the same twist would have to spend that first and 25 back would move nothing.
+# That hysteresis is wanted — it is what stops a thumb resettling on a notch
+# boundary from flip-flopping between two matches — and lifting the fingers is
+# how a hand clears it.
+rotate 0
+# Counterclockwise: `N`. This must land back exactly on `forward`.
+rotate 25
+stable 700 15000
+snap back
+# And the other half of the contract: a twist IS the keystroke, so typing the
+# key the dial claims to send must go the same way the dial went. `n` here must
+# reproduce `forward2` — if the dial were sending something else, or the sign
+# were inverted, these two would part company.
+text n
+stable 700 15000
+snap forward_by_key
+draw rotate
diff --git a/test/macos-snapshots/trackpad.golden b/test/macos-snapshots/trackpad.golden
new file mode 100644
index 00000000..82edf68b
--- /dev/null
+++ b/test/macos-snapshots/trackpad.golden
@@ -0,0 +1,311 @@
+== snap ready grid=100x30 cursor=4,8
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap adapter grid=100x30 cursor=7,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap look grid=100x30 cursor=7,5
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap dragged grid=100x30 cursor=9,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap hover grid=100x30 cursor=9,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap scrolled grid=100x30 cursor=9,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap notched grid=100x30 cursor=9,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap exec grid=100x30 cursor=7,10
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+| /tmp/pardes-macos-e2e/trackpad/tmp/pardes-XXXXXX Save New Del
+| 1
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Search New Del
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap force grid=100x30 cursor=54,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New D /private/tmp/pardes-macos-e2e/trackpad/cwd New D
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+| /tmp/pardes-macos-e2e/trackpad/tmp/pardes-XXXXXX
+| 1
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Sear
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== snap select grid=100x30 cursor=4,3
+|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
+| /private/tmp/pardes-macos-e2e/trackpad/cwd New D /private/tmp/pardes-macos-e2e/trackpad/cwd New D
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+| /tmp/pardes-macos-e2e/trackpad/tmp/pardes-XXXXXX
+| 1
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| /private/tmp/pardes-macos-e2e/trackpad/cwd/+Sear
+| 1 @p0:2:3-6 x MARK a
+| 2 @p0:4:3-6 x MARK b
+| 3 @p0:6:3-6 x MARK c
+== draw trackpad 900x510 nonblank
diff --git a/test/macos-snapshots/trackpad.snap b/test/macos-snapshots/trackpad.snap
new file mode 100644
index 00000000..05f46e1f
--- /dev/null
+++ b/test/macos-snapshots/trackpad.snap
@@ -0,0 +1,91 @@
+# The trackpad features, asserted without a trackpad, plus the rest of the
+# pointer vocabulary.
+#
+# Trackpad.button(fingers:) is pure policy with no NSEvent in it, so a script
+# can name how many fingers were resting and get back the button a hand would
+# have produced: two is acme's button 3 (Look), three or more is button 2
+# (Exec), and a deep press is button 2 by another route. pardes_take_haptic is
+# the other half — it is how the pulse the core armed is read back on a machine
+# with nothing to feel it with.
+#
+# Coordinates are 0-based cells, which is what pardes_mouse takes. The tty
+# suite's press/release are 1-based SGR and so read one higher.
+start 30 100
+wait 8000 New Newcol
+wait 8000 $
+stable 700 20000
+# something to look at; split so the echoed command line is not the match
+text printf 'x MA''RK a\nzz\nx MA''RK b\nzz\nx MA''RK c\n'
+key enter
+wait 10000 MARK c
+stable 700 10000
+# A raw pane hands clicks to the program on the other end. Normal mode is where
+# the three buttons mean Select, Exec and Look.
+key c-b
+stable 600 8000
+snap ready
+# THE ADAPTER ITSELF, before any of the decoded paths below. `nsclick` builds a
+# real NSEvent and hands it to the view's own rightMouseDown, so this is the one
+# command that exercises the code between AppKit and the entry points. It is
+# here because an adapter that silently dropped every mouse event once passed
+# this entire suite: everything else calls the entry points directly.
+nsclick right 4 3
+wait 10000 @p0:
+stable 700 15000
+haptic look
+snap adapter
+# Back to a clean slate for the finger-count cases below.
+haptic none
+# TWO FINGERS RESTING = button 3 = Look. MARK names no file, so looking at it
+# searches, and the hits land in a +Search buffer split under the shell.
+fingers 2 4 3
+wait 10000 @p0:
+stable 700 15000
+haptic look
+snap look
+# ...and taking the pulse cleared it
+haptic none
+# The three press phases one at a time rather than through `click`, which is
+# what proves press, drag and release are separate entry points and not a lump.
+mouse left press 2 3
+mouse left drag 9 3
+mouse left release 9 3
+stable 700 10000
+snap dragged
+# ponytail: `y` here would yank that selection into the core's register and
+# fire set_clipboard, which is what the harness's `clipboard <text>` asserts.
+# It is not scripted because the expected text has to be READ off this golden
+# first — a guessed span would hand the next person a failing script instead of
+# a capture. Add the two lines once `snap dragged` shows what got selected.
+# A button-less hover: the core tracks it for the link and definition underline.
+mouse none motion 4 5
+stable 400 5000
+snap hover
+# A trackpad's precise scroll is fractional rows the core accumulates into whole
+# wheel presses; a real mouse notch skips the smoothing and arrives as an
+# ordinary button. Both spellings, so the two paths stay distinguishable.
+scroll -3 4 5
+stable 700 10000
+snap scrolled
+wheel wheeldown 4 5
+stable 700 10000
+snap notched
+# THREE FINGERS = button 2 = Exec. `New` in the top tag is a builtin, so this
+# spawns a pane rather than running something the machine would have to own.
+fingers 3 1 0
+stable 700 20000
+haptic exec
+snap exec
+# A DEEP PRESS is the same button 2 by another route; `Newcol` proves it went
+# somewhere different from the click above.
+force 5 0
+stable 700 20000
+haptic exec
+snap force
+# One finger is still an ordinary left press, and selecting is not a verb the
+# hand should feel.
+fingers 1 4 3
+stable 700 10000
+haptic none
+snap select
+draw trackpad