Realign code with the 2026-07-31 rulings

The trash sorts by modified descending — the arrival rank mint retires
(Ranks.isOrderedForTrash one comparator, loader + merged order agree;
the legacy deleted: migration stamps modified from the tombstone
timestamp where parseable; delete undo steps validate existence-only;
agent guide v8). Trash selection goes kind-blind — ranges, marquee,
Select All, and the successor walk sweep both kinds; the guard moves to
the exits (mixed-payload drop refusal, copy/cut validation). The copy
stamping preflight widens back to comment depth (load-scoped posture —
the board always loads, the gesture refuses whole). Fixes a latent
no-op: trashed-lane drag restore never fired (DragSession.beginLanes
hard-coded the board container).

2403 tests in 413 suites green.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-07-31 18:35:07 -04:00
parent 542ab169a3
commit bec75e4282
37 changed files with 1200 additions and 551 deletions
+10 -2
View File
@@ -356,12 +356,19 @@ struct CardFaceView: View {
/// every other card drop uses, committed by the same `moveCards`.
///
/// **Multi-drag carries the whole trash selection**, in the column's own order the order the
/// rows are drawn in, which is `order` ascending like any lane's.
/// rows are drawn in, which since 2026-07-31 is `modified` descending (03 § Trash).
///
/// **A kind-blind selection can span both kinds, and the session says so** (04-interactions.md
/// The trash, ruled 2026-07-31): the lane rows in it cannot ride a `.cards` session, so rather
/// than let them fall silently out of the payload the flag travels and the *drop* refuses with
/// the notice (`DragSession.mixesKinds`). Pickup stays allowed the selection is legal.
private func startTrashCardDrag() -> NSItemProvider {
guard !store.isReadOnly, !store.isEditingInline else { return NSItemProvider() }
let ids = draggedIDs
let rows = store.snapshot.trash.filter { ids.contains($0.id) }
guard !rows.isEmpty else { return NSItemProvider() }
let mixesKinds = SelectionGrammar.mixesKinds(
ItemReferenceSet(ids: ids, container: .trash), in: store.snapshot)
let root = store.rootURL
let payload = DragPayload(
@@ -381,7 +388,8 @@ struct CardFaceView: View {
folders: payload.folders,
heights: rows.map { drops.registry.heights[$0.id] ?? LaneDropRegistry.nominalCardHeight },
container: .trash,
source: store
source: store,
mixesKinds: mixesKinds
)
return payload.itemProvider()
}