Render the dropped card at release, and pin the hold's timeout

03's sharpened settle rule: rendering the arrangement means rendering
the card — at release the shadow swaps for the dropped card(s) drawn in
place immediately, the appear never waiting for the echo reload. The
committed hold now carries the landing (ids, payload titles, operation)
and surfaces read one DropLanding seam: within-board moves draw the
real faces at their proposed slots under the arriving card's own key,
so the echo is an invisible content swap; cross-board card arrivals
draw payload-titled faces keyed positionally, so the echo reads as an
ordinary arrival. Cross-board lane arrivals deliberately keep their
shadow until the echo — a lane's face is a whole column with no honest
payload equivalent. The 1500 ms failed-write timeout is now seamed
(injectable duration, extracted expire) and pinned by tests.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-28 07:51:05 -04:00
parent 1487b391ad
commit 3f4125e324
7 changed files with 745 additions and 52 deletions
+31 -7
View File
@@ -383,8 +383,11 @@ struct BoardView: View {
/// off the tidy snapped layout regardless of the live overflow.
///
/// A lane being **dragged** is simply absent from the strip: it is lifted out of the resting
/// layout at pickup and stays out until release, whatever the effective operation is
/// (DRAG-REORDER.md § Resting-layout zones), while the system drag session carries its replica.
/// layout at pickup and stays out for as long as the drag is in flight, whatever the effective
/// operation is (DRAG-REORDER.md § Resting-layout zones), while the system drag session carries
/// its replica. At release it comes straight back at its *landing* slot, drawn by this very
/// function, because a settled reorder renders the lanes rather than their outlines
/// (`stripSlots`, `runSlots`).
@ViewBuilder
private func laneSlot(_ lane: Lane, standard: CGFloat) -> some View {
let resizing = resize.isResizing(lane.id)
@@ -556,18 +559,39 @@ struct BoardView: View {
}
}
/// What the strip lays out: the resting lanes the dragged run lifted out, whatever the
/// effective operation is with the shadows opened at the proposal.
/// What the strip lays out: the resting lanes the dragged run lifted out while the drag is in
/// flight with the run opened at the proposal.
private var stripSlots: [StripSlot] {
let session = appModel.dragSession
let hidden = session.hiddenMembers(onBoardRooted: store.rootURL)
var slots = liveLanes.filter { !hidden.contains($0.id) }.map(StripSlot.lane)
guard let index = stripProposal else { return slots }
let shadows = session.laneUnits.enumerated().map { StripSlot.shadow(index: $0.offset, units: $0.element) }
slots.insert(contentsOf: shadows, at: min(max(0, index), slots.count))
guard let landing = session.laneLanding(onBoardRooted: store.rootURL) else { return slots }
slots.insert(contentsOf: runSlots(landing, session: session), at: min(max(0, landing.index), slots.count))
return slots
}
/// What the strip's run is made of shadows while the drag is in flight, and **the dropped lanes
/// themselves** the instant a within-board reorder settles (03-board-ui.md § Motion: "rendering
/// the arrangement means rendering the card").
///
/// A within-board reorder is the easy half and the whole of what the strip owes: the lanes are
/// still in this snapshot, so the run draws the real `LaneView`s at their landing slot keyed by
/// lane identity, so the echo reload is a content swap inside one element and the lane never
/// loses its scroll position or its masonry to the settle.
///
/// **A cross-board lane arrival keeps its shadow**, deliberately: the destination has no lane to
/// draw yet, and a lane's face is a whole column of cards rather than a title and an icon the
/// card level's payload-title fallback (`DroppedCardFace`) has no honest equivalent here. The
/// shadow stands until the echo, which for an arriving column is the same round trip a create
/// already takes.
private func runSlots(_ landing: DropLanding, session: DragSession) -> [StripSlot] {
if case let .dropped(drop) = landing.run, drop.isLocal {
let lanes = drop.items.compactMap { item in liveLanes.first { $0.id == item.id } }
if lanes.count == drop.items.count { return lanes.map(StripSlot.lane) }
}
return session.laneUnits.enumerated().map { StripSlot.shadow(index: $0.offset, units: $0.element) }
}
// MARK: - Grammar keys
/// **Return**, narrowly (04-interactions.md Grammar): a sole selected live card begins an