The drag learns the stepper's trick — past the screen's edge, lane growth re-divides instead of stopping

The right-edge drag's growth was capped at the screen's visible frame,
because each snap tick grows the window; on a window near the screen edge
that left a lane stuck at a tick or two of headroom. Settled 2026-08-08
(03-board-ui.md § Lane, superseding the pathfinder's hard stop): at the
screen the window pins and each further tick re-divides the fixed strip
width across one more unit — siblings compress, the stepper's mechanism
arriving under the drag's fingers. The regimes meet with no pixel jump
(the re-divided standard at the fit IS the frozen standard, by the
exact-fill identity), shrinking mirrors the way back, the rubber band
moves to the strip's own capacity, and a window with no headroom at all —
full screen included — re-divides from the very first snap.

New pure arithmetic in LaneLayoutMath (pinnedStripWidth, resizeStandard,
resizeMaxUnits, resizeWindowDelta, snappedUnits over per-count slots);
LaneResizeSession splits the tick across the regimes and derives its
standard from the live count; the handle and BoardView hand the session
the strip's whole divide. 2709 unit tests green (+11).

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 21:36:56 -04:00
parent 05b1a787de
commit 9a52b795b2
8 changed files with 564 additions and 91 deletions
+9 -1
View File
@@ -28,6 +28,13 @@ struct LaneResizeHandle: View {
let standard: CGFloat
let gap: CGFloat
/// The strip's whole unit total THIS render every lane's units plus the shown trash's fixed
/// one, the same divide the resting layout runs on (`BoardView.stripTotalUnits`). The session
/// needs it because past the screen's fit the drag re-divides the strip rather than growing the
/// window (03-board-ui.md § Lane, settled 2026-08-08), and a re-divide is a fact about the whole
/// strip rather than about this lane.
let totalUnits: Int
/// How the session reaches the host window it resizes. A closure rather than a stored
/// `NSWindow?` because the window attaches asynchronously (`WindowAccessor`), and a value
/// captured in an early body evaluation would be `nil` for the window's whole life.
@@ -75,7 +82,8 @@ struct LaneResizeHandle: View {
// not exist yet; when it does, this is where the `isDragging` guard goes.
pushCursor()
session.begin(laneID: laneID, units: committedUnits,
standard: standard, gap: gap, window: window())
standard: standard, gap: gap,
totalUnits: totalUnits, window: window())
}
session.update(translation: value.translation.width)
}