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
This commit is contained in:
@@ -166,17 +166,23 @@ struct CardAttachmentsSection: View {
|
||||
)
|
||||
.contentShape(Rectangle())
|
||||
// Double-click opens, a single click selects (05 ▸ Attachments; the window's click grammar,
|
||||
// where clicking selects and never edits). The two-count gesture is declared first so
|
||||
// SwiftUI gives it the chance to claim the second click.
|
||||
.onTapGesture(count: 2) {
|
||||
isFocused = true
|
||||
attachments.selected = name
|
||||
attachments.open(name)
|
||||
}
|
||||
// where clicking selects and never edits). The two-count gesture rides `simultaneousGesture`
|
||||
// rather than stacking as a second `.onTapGesture` — a sequential pair makes the single
|
||||
// click wait out the double-click interval before selecting (`CardFaceView`'s arrangement;
|
||||
// the 2026-08-06 latency fix). Simultaneity stays instant *here* because the row carries
|
||||
// `.onDrag` below, which forces immediate click delivery — a surface without a drag source
|
||||
// must branch one recogniser on `PointerClick.count` instead (`LaneView`'s empty space).
|
||||
// The first click of a pair selects, the second opens; the open re-asserting focus and
|
||||
// selection is idempotent.
|
||||
.onTapGesture {
|
||||
isFocused = true
|
||||
attachments.selected = name
|
||||
}
|
||||
.simultaneousGesture(TapGesture(count: 2).onEnded {
|
||||
isFocused = true
|
||||
attachments.selected = name
|
||||
attachments.open(name)
|
||||
})
|
||||
// **Rows drag out their file URL** (05 ▸ Attachments; 11-command-nexus.md ▸ Pointer-only
|
||||
// affordances) — which is what makes drag-to-Finder and drag-into-another-app work with no
|
||||
// export path of this app's own. An empty provider for a row whose file has gone refuses the
|
||||
|
||||
Reference in New Issue
Block a user