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:
@@ -168,6 +168,15 @@ struct BoardDropContext {
|
||||
/// slot's uncapped reach past the last real lane lands *before* it. The delete a lane drag can
|
||||
/// propose over the column is not a slot at all — it is the column's own target answering
|
||||
/// (`retargetTrash`), and it names `.trash` rather than an index in this list.
|
||||
///
|
||||
/// **These zones never see a re-admitted lane, and that is an invariant rather than an
|
||||
/// accident.** `hiddenMembers` now leaves a copy's originals in the resting layout, but a lane
|
||||
/// can only be re-admitted into the strip it was picked up from, and a lane drag over its own
|
||||
/// board never resolves to `.copy` (`DragLocality.operation`'s first carve-out). A cross-board
|
||||
/// lane copy does re-admit — into the **source** strip, while the cursor is over the foreign
|
||||
/// board, where no zone of this board's is being consulted at all. So the strip's slot list is
|
||||
/// still "the lanes minus the run" every time this function actually runs, and `moveLanes` /
|
||||
/// `receiveLanes` keep the index space they always had.
|
||||
func retargetLanes() {
|
||||
guard session.isDraggingLanes, let cursor = stripCursor() else { return }
|
||||
let hidden = session.hiddenMembers(onBoardRooted: store.rootURL)
|
||||
@@ -191,6 +200,13 @@ struct BoardDropContext {
|
||||
/// The single answer the lane's own drop delegate, the strip's fall-through, and the autoscroll
|
||||
/// driver all go through — "the lane's drop delegate and the autoscroll driver must go through
|
||||
/// one shared retarget, so they can never disagree" (DRAG-REORDER.md § Edge autoscroll).
|
||||
///
|
||||
/// **The zones are built over whatever `hiddenMembers` leaves standing**, which is what makes
|
||||
/// the proposal's index space follow the operation for free: a within-board move counts slots in
|
||||
/// the lane minus the dragged run, a within-board ⌥-copy counts them in the lane *including* the
|
||||
/// originals, because that is the layout the user is looking at. `DropSlotMath` has no opinion
|
||||
/// on the matter — the caller decides what the resting layout contains — and the commit's two
|
||||
/// writers are counted in exactly these two spaces (`BoardStore.moveCards` / `copyCards`).
|
||||
func retargetCards(inLane laneID: ItemID) {
|
||||
guard session.isDraggingCards, let cursor = globalCursor() else { return }
|
||||
guard let lane = store.snapshot.lanes.first(where: { $0.id == laneID }) else {
|
||||
@@ -644,6 +660,12 @@ struct BoardDropContext {
|
||||
// restore *is* the within-board move and a cross-board restore *is* the ordinary
|
||||
// arrival — which is exactly what retiring the restore-specific machinery bought.
|
||||
if within {
|
||||
// **Each writer is counted in the layout its own gesture was showing.** The zones
|
||||
// that produced `target.index` were built over the resting layout for *this*
|
||||
// operation (`retargetCards` → `DragSession.hiddenMembers`): a move's excludes the
|
||||
// dragged run, a copy's includes the originals, because a copy leaves them there.
|
||||
// So the number goes to each writer unrewritten and the drop lands where the shadow
|
||||
// was — the two index spaces differ, and neither writer has to guess which it got.
|
||||
if operation == .copy {
|
||||
store.copyCards(Set(ids), toLane: laneID, at: target.index)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user