Redraw a copy's originals at the source — the resting layout follows the operation

The modifier flip (⌥ copy / ⌘ move) now reflows the source board once:
a copy re-admits the dragged originals into the resting layout, standing
dimmed in place, and a move lifts them out as before. This retires
DRAG-REORDER.md's operation-blind carve-out and makes 04-interactions.md's
"originals stay" true in flight, not just at echo. The one seam is
DragSession.hiddenMembers reading the observed operation.

Consequences carried honestly: copyCards now takes its index in the
lane's full rendered space (the zones counted the originals, so the
geometry's number is the writer's number — the old neighbour remap is
deleted); resolveOperation freezes under the committed hold exactly as
propose does, fixing a real bug where a settled within-board ⌥-copy drew
the move arrangement until the echo and visibly re-shuffled. Stationary
flips still wait for the next dropUpdated (their own Backlog card).

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 08:07:54 -04:00
parent adf4fc5973
commit 8206a78ecc
9 changed files with 383 additions and 51 deletions
+8 -5
View File
@@ -428,9 +428,11 @@ struct BoardView: View {
/// The outer frame is always the snapped slot width, so the `HStack` lays the other lanes out
/// 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.
/// A lane being **dragged as a move** is simply absent from the strip: it is lifted out of the
/// resting layout at pickup and stays out until release, while the system drag session carries
/// its replica. A lane being dragged as a **copy** stays in the strip instead, because the copy
/// leaves it there (DRAG-REORDER.md § Resting-layout zones) which on this board means the
/// cross-board case alone, being ignored by a within-board lane drag.
@ViewBuilder
private func laneSlot(_ lane: Lane, standard: CGFloat) -> some View {
let resizing = resize.isResizing(lane.id)
@@ -628,8 +630,9 @@ 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 when the release
/// would be a move, left standing when it would be a copy (`DragSession.hiddenMembers`) with
/// the shadows opened at the proposal.
private var stripSlots: [StripSlot] {
let session = appModel.dragSession
let hidden = session.hiddenMembers(onBoardRooted: store.rootURL)