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:
@@ -580,7 +580,7 @@ struct BoardView: View {
|
||||
@ViewBuilder
|
||||
private func laneSlot(_ lane: Lane, standard: CGFloat) -> some View {
|
||||
// **The session governs through the release**, not just the drag: after the release its
|
||||
// frozen standard and the unit count it *wrote* keep answering here until the snapshot
|
||||
// standard and the unit count it *wrote* keep answering here until the snapshot
|
||||
// carries that width back (`LaneWidthHold`). Reading `lane.width` in that window would draw
|
||||
// the pre-drag layout for a round trip.
|
||||
let resizing = resize.governs(lane.id)
|
||||
@@ -625,6 +625,7 @@ struct BoardView: View {
|
||||
committedUnits: units,
|
||||
standard: standard,
|
||||
gap: spacing,
|
||||
totalUnits: stripTotalUnits,
|
||||
window: window
|
||||
)
|
||||
// The read-only lock disables every mutating gesture, not just the menu items
|
||||
@@ -703,10 +704,14 @@ struct BoardView: View {
|
||||
/// `laneDrops.stripFrame.width` instead, which is the same number read at event time — exactly
|
||||
/// the registry's purpose. The two agree because the padded container fills the reader.
|
||||
///
|
||||
/// During a resize session the standard is **frozen** at its drag-start value: the window is
|
||||
/// animating mid-resize, so deriving the standard from the live width would feed that animation
|
||||
/// back into every lane and pulse the whole strip. The window is sized on each tick so this frozen
|
||||
/// value equals what the formula yields once the session ends — the handoff is seamless (see
|
||||
/// During a resize session the standard comes from the session and not from the viewport: the
|
||||
/// window is animating mid-resize, so deriving it from the live width would feed that animation
|
||||
/// back into every lane and pulse the whole strip. Within the screen's fit the session's answer is
|
||||
/// its drag-start value, **frozen** — the window is sized on each tick so it equals what the
|
||||
/// formula yields once the session ends, and the siblings never move. Past the fit the window is
|
||||
/// pinned and the session re-divides instead (settled 2026-08-08, 03-board-ui.md § Lane), so the
|
||||
/// number returned here shrinks with each tick and every lane compresses — which is exactly how
|
||||
/// the drag borrows the stepper's mechanism. Either way the handoff at the end is seamless (see
|
||||
/// `LaneResizeSession`).
|
||||
///
|
||||
/// **"Once the session ends" is the echo, not the release** (`LaneWidthHold`). The equality that
|
||||
@@ -730,15 +735,25 @@ struct BoardView: View {
|
||||
/// (`arrivingLaneUnits`).
|
||||
private func standardWidth(stripWidth: CGFloat) -> CGFloat {
|
||||
if resize.isActive { return resize.standard }
|
||||
var units = LaneLayoutMath.totalUnits(of: boardLanes, trashUnits: isTrashVisible ? 1 : 0)
|
||||
units += arrivingLaneUnits
|
||||
return LaneLayoutMath.standardWidth(
|
||||
stripWidth: stripWidth,
|
||||
totalUnits: units,
|
||||
totalUnits: stripTotalUnits + arrivingLaneUnits,
|
||||
gap: spacing
|
||||
)
|
||||
}
|
||||
|
||||
/// The strip's resting divide: every lane's units plus the trash's fixed one while it is shown.
|
||||
///
|
||||
/// Named because it is asked twice for two purposes. The resting layout divides the viewport by
|
||||
/// it (plus a cross-board arrival's units, which are a hover-only addition and no part of the
|
||||
/// strip itself), and each lane's grab strip hands it to `LaneResizeSession.begin` as the total
|
||||
/// the drag's re-divide works from once the window has spent the screen (03-board-ui.md § Lane).
|
||||
/// The arrival is deliberately absent from the second: a resize refuses to start while a drag
|
||||
/// session is in flight, so there is never an arriving lane at a drag's begin.
|
||||
private var stripTotalUnits: Int {
|
||||
LaneLayoutMath.totalUnits(of: boardLanes, trashUnits: isTrashVisible ? 1 : 0)
|
||||
}
|
||||
|
||||
/// The units a cross-board lane run would add to this strip while its shadow is proposed here;
|
||||
/// zero for a within-board drag, whose lanes are already counted.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user