Commit Graph
2 Commits
Author SHA1 Message Date
rzen 8aefaf23ce The empty provider was never load-bearing — the dragless layer frees the rubber band
Measured on real events 2026-08-07, correcting the 2026-08-06 hosted
finding: a bare count-1 tap on LaneView's empty-space layer fires in
~1-3 ms with no drag source at all — the hold that made the empty
.onDrag look necessary was the sterile NSApp.postEvent stream
over-disambiguating. And the provider was actively harmful: even an
empty drag source claims the mouse-drag at threshold, starving the
marquee's simultaneous DragGesture after one sample — the band froze
and the mouseUp never arrived. The layer goes dragless; drags from
empty space belong wholly to MarqueeControl. PointerClick's and the
layer's comments retell the corrected story. Alongside: openCard is
typed @MainActor throughout, which makes the closure Sendable and
lets CardFaceRole carry it under CardFaceView's nonisolated ==.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-07 12:55:54 -04:00
rzen eef0a4539f Clicks land the instant they happen — the empty-space gestures move behind the masonry
The measured defect: LaneView's empty-space double-click was a second
sequential .onTapGesture(count: 2) stacked over the single tap, and that
recogniser held every click in the lane — its own empty space and every
card face alike — hostage to the system double-click interval while it
disambiguated (~475 ms click-to-selection on a hosted board). The fix is
structural: the empty-space surfaces live on a background layer behind the
masonry, so a card's click never shares a gesture path with a lane
recogniser; one .onTapGesture branches on PointerClick.count (AppKit's own
clickCount, read the way ClickModifier reads the keyboard) — first click
selects, second creates, Finder's cadence with nothing to disambiguate; and
the layer carries a load-bearing empty .onDrag, because without a drag
source macOS holds primary clicks pending multi-click disambiguation
(measured: never fires alone, ~90 ms with one present). A measured viewport
floor makes each lane's blank space actually belong to the layer — a
ScrollView proposes nothing along its scroll axis, so only an explicit
minimum stretches the content — with the trash column as its twin, less the
padding that sits inside its scroll content. The template chooser's stacked
pair collapses to the same one-recogniser branch, and the attachment rows
move their double-click to a simultaneous gesture (instant there, because
the row's real .onDrag forces immediate delivery). PointerLatencyTests pins
the recovery with synthetic pointer events on a hosted board.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-07 11:22:16 -04:00