src/switcher/main-window/DragAndDropResolverSpecs.md
DragAndDropResolver is the pure decision layer for drag-and-drop over the switcher: what a drag-over of
the tiles should do (dragOver), when the auto-select timer (re)arms (movedBeyondResetRadius), whether
the global mouse tap must yield the drop's mouse-up (passesThroughMouseUp), and whether a drop is valid
(canDrop). It holds no state and touches no AppKit drag session, timer, or event tap — TilesDocumentView
(the NSDraggingDestination) and CursorEvents (the global tap) turn its decisions into real
NSDragOperations, timers, and tap pass-through (same pattern as AxEventRouting).
NSDraggingDestination owns the drag (hover highlight, the .link
operation, the auto-select timer); the global mouse tap owns ordinary clicks. They conflict only on the
one event that ends a drag — the leftMouseUp. A file drag over the switcher can only have started in
another app BEFORE the switcher showed (the tap swallows any leftMouseDown outside the panel, and tiles
aren't drag sources), so the tap never saw that drag's leftMouseDown. passesThroughMouseUp keys off
exactly that: yield any up whose down the tap didn't see, so AppKit / the source app concludes the drop
instead of the tap swallowing it and leaving the file glued to the cursor. This holds wherever the release
lands — on a tile, on the padding around the tiles, or outside the panel — because none of those are a
down the tap saw. A normal click's down IS seen, so clicks route normally. This is the #5350-regression fix.dragOver reports .inDeadzone (still a valid .link drop, but no
selection and no timer) until the pointer clears the same movement deadzone mouse hover uses.findTarget, which expands each tile
by 1px so the 1px gap between tiles still resolves to a tile. The kernel only sees hasTarget; it never
returns .noTarget while the cursor is over the grid.movedBeyondResetRadius, inclusive at the
radius); a sub-radius jitter within the same tile lets the running timer fire. A nil anchor (timer not
armed yet) counts as "re-arm".Mirrors DragAndDropResolverTests.swift 1:1.
.noTarget..inDeadzone (no grab on appear)..track(restartTimer: true), regardless of distance..track(restartTimer: false)..track(restartTimer: true).nil anchor → re-arm..inDeadzone; after clearing the deadzone → .track..noTarget over the grid).