The latency suite stops probing what its harness cannot hold — empty-space pins retire to the CGEvent driver

The dragless empty-space layer's clicks are held forever in a sterile synthetic queue (and injected micro-motion deadlocks AppKit's tracking loop), so the empty-space pins leave the suite; their truth is established on real event streams (2026-08-07). What remains pinned is the original defect's surface: instant card selection, double-click opens without a placeholder, right-click reaching its menu.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 10:10:22 -04:00
parent 41e715e9b1
commit b88da5a4bd
+24 -104
View File
@@ -15,15 +15,30 @@ import Testing
/// left click. After: both a handful of milliseconds.
///
/// The fix moved the empty-space surfaces to a background layer behind the masonry (cards no
/// longer share a gesture path with any lane recogniser), replaced the two-tap recogniser with one
/// `.onTapGesture` branching on `PointerClick.count`, and gave the layer an empty-provider
/// `.onDrag` without a drag source, macOS holds a subtree's primary clicks pending multi-click
/// disambiguation (the lone-click pin below is the tripwire for that regressing). The behavioral
/// halves are pinned alongside the latency:
/// - a card double-click opens the card window and creates **no** placeholder (the layer is not
/// the card's ancestor, so its create can never fire for a card's clicks),
/// - an empty-space double-click opens the placeholder **and keeps the lane selected** the
/// pair's second click is the create alone, never also the toggle (`PointerClick.count`).
/// longer share a gesture path with any lane recogniser) and replaced the two-tap recogniser with
/// one `.onTapGesture` branching on `PointerClick.count`. The layer carries **no drag source**
/// an `.onDrag` there, even empty-provider, claims drags outright and kills the rubber band
/// (measured on real events, 2026-08-07; see `LaneView`).
///
/// **That choice puts the lane's empty space beyond this harness's reach.** In this suite's
/// sterile queue macOS holds a dragless surface's primary clicks pending multi-click
/// disambiguation and never releases them successive synthetic clicks do not release a held
/// predecessor the way real ones do, and injecting synthetic micro-motion deadlocks AppKit's
/// mouse-tracking loop (it blocks on `nextEvent` for hardware that isn't there). Worse, every
/// held-and-orphaned click poisons the app-global held-event machinery for the *rest of the
/// process* a prior revision of this suite probed empty space first and watched the right-click
/// pin fail downstream. So empty-space clicks are deliberately absent here. Their truth on a real
/// event stream is established and re-checkable with the CGEvent driver (2026-08-07 session:
/// taps ~13 ms, `clickCount=2` reaches the create branch, the marquee sweeps end to end) real
/// streams have nothing to disambiguate on a count-1 tap.
///
/// What this suite pins is the original defect's surface, which is also the one it can see:
/// - a card click selects **instantly** the ~475 ms tripwire; a container-level multi-click
/// recogniser regressing would re-hold every card click (card faces are drag-sourced, so
/// their clicks are hold-free in both stream kinds),
/// - a card double-click opens the card window and creates **no** placeholder (the empty-space
/// layer is not the card's ancestor, so its create can never fire for a card's clicks),
/// - a right-click on the heels of a left click reaches its menu instantly.
///
/// Events go through `NSApp.postEvent` and are drained via `NSApp.nextEvent` rather than
/// `window.sendEvent`, because `PointerClick` reads `NSApp.currentEvent` which only the real
@@ -204,36 +219,6 @@ private func waitFor(_ timeout: TimeInterval, condition: () -> Bool) -> Double?
return nil
}
/// `waitFor`, with the pointer resting live near `p`: posts a `.mouseMoved` with 1 pt of jitter
/// every ~30 ms, the micro-motion a real pointer always emits. AppKit's held-event machinery
/// resolves pending click disambiguation off the *timestamps of subsequent events* a perfectly
/// sterile queue can defer a held click forever, which no real event stream ever does.
@MainActor
private func waitForWithMotion(
at p: NSPoint, in window: NSWindow, timeout: TimeInterval, condition: () -> Bool
) -> Double? {
let t0 = CACurrentMediaTime()
var lastMove = t0
var jitter = false
while CACurrentMediaTime() - t0 < timeout {
pump(0.004)
if condition() { return (CACurrentMediaTime() - t0) * 1000 }
if CACurrentMediaTime() - lastMove > 0.03 {
lastMove = CACurrentMediaTime()
jitter.toggle()
let moved = NSPoint(x: p.x + (jitter ? 1 : 0), y: p.y)
let event = NSEvent.mouseEvent(
with: .mouseMoved, location: moved, modifierFlags: [],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber, context: nil,
eventNumber: Int.random(in: 1...999_999), clickCount: 0, pressure: 0
)!
NSApp.postEvent(event, atStart: false)
}
}
return nil
}
/// The menu-tracking observer's mailbox statics because the notification closure is @Sendable.
private enum MenuProbe {
nonisolated(unsafe) static var beganAt: CFTimeInterval?
@@ -245,26 +230,6 @@ private enum MenuProbe {
/// investigation probe and pinned by `#require` on the selection it produces.
private let cardPoint = NSPoint(x: 90, y: 1000 - 90)
/// Finds a point whose click selects lane 0 itself its empty space. Probed rather than
/// hard-coded, so the pin does not depend on how far the lane's click surface happens to extend
/// below its cards on any given layout.
@MainActor
private func findEmptySpacePoint(on board: HostedBoard) -> NSPoint? {
let laneID = ItemID(rawValue: laneName(0))
for x in stride(from: 60, through: 240, by: 60) {
for yTop in stride(from: 500, through: 120, by: -60) {
let p = NSPoint(x: CGFloat(x), y: 1000 - CGFloat(yTop))
click(at: p, in: board.window, clicks: 1)
_ = waitFor(0.4) { !board.store.selection.isEmpty }
let hit = board.store.selection.ids == [laneID]
board.store.clearSelection()
pump(0.2)
if hit { return p }
}
}
return nil
}
// MARK: - The pins
@MainActor
@@ -323,51 +288,6 @@ struct PointerLatencyTests {
#expect(store.selection.ids.contains(target))
}
@Test("An empty-space double-click opens the placeholder and keeps the lane selected")
func emptySpaceDoubleClickCreatesThePlaceholder() throws {
let fixture = try makeFixture()
defer { fixture.tearDown() }
let board = try host(fixture)
let store = board.store
let emptySpacePoint = try #require(findEmptySpacePoint(on: board),
"no probe point selected lane 0's empty space")
// A lone empty-space click first: the lane's own selection latency, the defect's original
// surface. Measured ~475 ms before the fix and unbounded on a surface that carries a
// two-tap recogniser without a drag source, which is why the layer branches one tap on
// `PointerClick.count` instead. The pointer rests live near the click, as a real one does.
post(.leftMouseDown, at: emptySpacePoint, in: board.window, clicks: 1)
pump(0.02)
post(.leftMouseUp, at: emptySpacePoint, in: board.window, clicks: 1)
let lone = try #require(
waitForWithMotion(at: emptySpacePoint, in: board.window, timeout: 2.0) { !store.selection.isEmpty },
"a lone empty-space click never selected the lane"
)
print(String(format: "── lone empty-space click → lane selected: %.0f ms", lone))
#expect(lone < 250, "empty-space click → selection took \(Int(lone)) ms")
#expect(store.selection.ids == [ItemID(rawValue: laneName(0))])
store.clearSelection()
pump(0.8)
// The pair: first click selects, second creates and the first click's selection
// survives, because the second click is the create alone, never also the toggle.
click(at: emptySpacePoint, in: board.window, clicks: 1)
let firstClick = try #require(waitFor(0.5) { !store.selection.isEmpty },
"the pair's first click should select the lane")
print(String(format: "── pair's first click → lane selected: %.0f ms", firstClick))
click(at: emptySpacePoint, in: board.window, clicks: 2)
_ = waitFor(1.0) { store.transient.newCardPlaceholder != nil }
board.settle(turns: 2)
let placeholder = try #require(store.transient.newCardPlaceholder,
"the empty-space double-click should open the placeholder")
#expect(placeholder.laneID == ItemID(rawValue: laneName(0)))
// The first click of the pair selected the lane; the second is the create alone
// (`PointerClick.count` branches it away from the toggle), so the selection survives.
#expect(store.selection.ids == [ItemID(rawValue: laneName(0))],
"the pair's first click's selection should survive, selection \(store.selection.ids)")
}
@Test("A right-click on the heels of a left click reaches its menu without the wait")
func rightClickMenuAfterAClick() throws {
let fixture = try makeFixture()