The lane-resize release gets its own hold — the strip stops two-stepping through the stale snapshot

LaneWidthHold: after the release the session's frozen standard and the
WRITTEN unit count keep governing the strip until a landed snapshot
carries that width back — dividing the already-grown window by the
stale unit total in between was the visible two-step. A deliberately
separate type from CommittedHold: that hold stands in for an
arrangement and any landing retires it; this one stands in for a value
and only a landing that carries it will do. Echo reads through
LaneLayoutMath.displayUnits so the width-1 key-removal case compares
right; the watch rides landedReloads so a value-equal echo still
answers; width writes now return whether bytes reached disk so a
refused or no-op write dissolves the hold instead of arming it; the
1500ms timeout family covers the rest.

Drag-perf card 231e3693.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 19:33:33 -04:00
parent f6105d4389
commit 409f430813
6 changed files with 591 additions and 32 deletions
+8 -3
View File
@@ -18,7 +18,9 @@ struct LaneResizeHandle: View {
let laneID: ItemID
/// The lane's committed unit count the k the session starts from.
/// The lane's committed unit count the k the session starts from. What is **on screen**, which
/// while a release hold stands is the width that release wrote rather than the stale snapshot's
/// (`LaneResizeSession.displayUnits(of:)`).
let committedUnits: Int
/// The strip's standard (1×) width THIS render; captured as the frozen standard the instant the
@@ -60,7 +62,10 @@ struct LaneResizeHandle: View {
.gesture(
DragGesture(minimumDistance: 2, coordinateSpace: .global)
.onChanged { value in
if !session.isResizing(laneID) {
// `isDragging`, not `governs`: a release hold on this very lane is still
// governing the strip, and a new drag begins over it rather than being
// mistaken for the old one still running (`LaneResizeSession.begin`).
if !session.isDragging(laneID) {
// m5-drag: a resize and a card/lane move must not run at once they
// would both mutate the same strip layout. The board's drag state does
// not exist yet; when it does, this is where the `isDragging` guard goes.
@@ -71,7 +76,7 @@ struct LaneResizeHandle: View {
session.update(translation: value.translation.width)
}
.onEnded { _ in
guard session.isResizing(laneID) else { return }
guard session.isDragging(laneID) else { return }
session.end { id, units in store.setLaneWidth(id, units: units) }
popCursor()
}