Drag instrumentation — render counters, hot-path signposts, and the invariants that prove the gates
BoardRenderMetrics (DEBUG-only, the pathfinder's counter bag plus a strip-body discriminator that tells a failed gate from a direct Observation invalidation) counted at BoardView/LaneView/CardFaceView/ TrashLaneView bodies and MasonryLayout's callbacks. DragSignposts wraps dropUpdated, retargetCards, commitDrop, and the commit-to-covering- snapshot release pause; input latency reports honestly against NSApp.currentEvent's mach base or labels itself base=none — no event timestamp rides the drop path. BoardRenderPerformanceTests hosts the real BoardView off-screen: a value-equal reload runs zero lane and zero card bodies, a one-card edit repaints one card of 180. The lane-body budget is <= laneCount with the headerInk chain documented and a two-way tripwire that fails when the fix lands. Methodology in RENDER-INSTRUMENTATION.md. Release build proves it all compiles out. Drag-perf card a450ad09. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -577,6 +577,10 @@ final class DragSession {
|
||||
source: BoardStore,
|
||||
mixesKinds: Bool
|
||||
) {
|
||||
// A new drag's first `sinceLastMs` must not be the gap since the previous drag's last sample.
|
||||
#if DEBUG
|
||||
DragSignposts.resetInputSampling()
|
||||
#endif
|
||||
endHold()
|
||||
restingLayouts.clear()
|
||||
self.kind = kind
|
||||
@@ -664,6 +668,12 @@ final class DragSession {
|
||||
/// that guard reads.
|
||||
func commit(into store: BoardStore) {
|
||||
guard isActive else { return }
|
||||
// **The release pause starts here** (`DragSignposts`): this is the one path that arms a hold,
|
||||
// so every begin has an end — a refused release opens nothing. Closed at `handOff`,
|
||||
// `expire`, or `endHold`, each with its own outcome.
|
||||
#if DEBUG
|
||||
DragSignposts.beginReleasePause()
|
||||
#endif
|
||||
sourceStore?.transient.dragMembers = .empty
|
||||
watchdog?.cancel()
|
||||
watchdog = nil
|
||||
@@ -688,6 +698,9 @@ final class DragSession {
|
||||
/// already been handed off — or replaced by a second drag's — is not this one's to end.
|
||||
func expire(_ hold: CommittedHold) {
|
||||
guard self.hold == hold else { return }
|
||||
#if DEBUG
|
||||
DragSignposts.endReleasePause(outcome: "timeout")
|
||||
#endif
|
||||
withAnimation(Motion.dragReflow(reduced: Motion.prefersReducedMotion)) { end() }
|
||||
}
|
||||
|
||||
@@ -697,10 +710,20 @@ final class DragSession {
|
||||
/// the board it belongs to — a reload on some other board says nothing about this one.
|
||||
func handOff(root: BoardRootKey, generation: Int) {
|
||||
guard let hold, hold.isRetired(byRoot: root, generation: generation) else { return }
|
||||
// The span the release-pause signpost exists for: commit → the covering snapshot.
|
||||
#if DEBUG
|
||||
DragSignposts.endReleasePause(outcome: "echo")
|
||||
#endif
|
||||
end()
|
||||
}
|
||||
|
||||
private func endHold() {
|
||||
// A no-op for the two paths above, which have already closed their interval with a more
|
||||
// specific outcome; the backstop for every other teardown (`end()` from the watchdog, a
|
||||
// cancel, a second drag beginning).
|
||||
#if DEBUG
|
||||
DragSignposts.endReleasePause(outcome: "cleared")
|
||||
#endif
|
||||
hold = nil
|
||||
holdTimeoutTask?.cancel()
|
||||
holdTimeoutTask = nil
|
||||
|
||||
Reference in New Issue
Block a user