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:
@@ -2704,26 +2704,23 @@ public final class BoardStore: HealHost {
|
||||
/// "originals stay, cursor shows the copy badge, fresh-GUID duplicates land at the drop").
|
||||
/// `created` survives because a copy is a fork — `CopyStamps.fork`, the same stamps paste uses.
|
||||
///
|
||||
/// **The ranks are placed among the lane's *full* rendered set**, not among the set with the
|
||||
/// dragged members removed — the one place a copy's arithmetic differs from a move's. The
|
||||
/// originals are lifted out of the layout for the duration of the drag whatever the effective
|
||||
/// operation is (⌥ can be pressed and released mid-drag; a layout that re-admitted them on every
|
||||
/// flip would flap the whole board), but they are still *on disk* holding their ranks, and a
|
||||
/// rank chosen in the gap they appear to have vacated would collide with them the instant they
|
||||
/// reappear. So the drop's index is mapped through to the neighbour it names — the card the run
|
||||
/// lands in front of — and the rank is taken there.
|
||||
/// **`index` is counted among the lane's *full* rendered cards — the originals included** — and
|
||||
/// that is the one place a copy's index space differs from a move's (`moveCards` counts among
|
||||
/// the cards its run vacates). It is not a special case bolted on here: a copy's originals stay
|
||||
/// in the source board's resting layout for the whole drag (`DragSession.hiddenMembers`, ruled
|
||||
/// 2026-08-01), so the zones the shadow was resolved against already counted them, and the
|
||||
/// geometry's number is this method's number unrewritten.
|
||||
///
|
||||
/// The ranks fall out of the same fact. The originals are still on disk holding their ranks, so
|
||||
/// a rank chosen in a gap they had apparently vacated would collide with them the instant they
|
||||
/// reappeared — and with the index counted among them there is no such gap to choose in.
|
||||
public func copyCards(_ ids: Set<ItemID>, toLane laneID: ItemID, at index: Int) {
|
||||
guard let destination = snapshot.lanes.first(where: { $0.id == laneID }) else { return }
|
||||
let members = draggedCards(ids)
|
||||
guard !members.isEmpty else { return }
|
||||
|
||||
let rendered = destination.cards
|
||||
let remaining = rendered.filter { !ids.contains($0.id) }
|
||||
let target = min(max(0, index), remaining.count)
|
||||
// The resting-layout index, re-read against the layout the originals are still part of.
|
||||
let placement = target < remaining.count
|
||||
? (rendered.firstIndex { $0.id == remaining[target].id } ?? rendered.count)
|
||||
: rendered.count
|
||||
let placement = min(max(0, index), rendered.count)
|
||||
|
||||
let root = rootURL
|
||||
let laneFolder = ItemPath.lane(laneID).folder(under: root)
|
||||
|
||||
Reference in New Issue
Block a user