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
+17
View File
@@ -535,6 +535,23 @@ struct BoardDropContext {
cancelDrop()
return false
}
// **A mixed-kind drag never leaves the trash** (04-interactions.md The trash, ruled
// 2026-07-31 with kind-blind trash selection): "pickup is allowed the selection is legal
// but every out-of-trash drop target refuses the mixed payload, and the release surfaces a
// notice explaining the rule the refused drag ends like any refusal, rows staying put".
//
// One guard for every out-of-trash target, because every one of them funnels through this
// commit the lane masonries, the strip, the cross-board arrivals, and the window fallback
// alike. A *within*-trash release cannot reach it: the column declines `.trash`-container
// sessions outright (`TrashDrop.accepts`), so a trash-origin drag never holds a `.trash`
// proposal, and "within-trash drops stay inert" needs no clause here.
//
// The notice is the destination board's strip, which is where the user is looking.
guard !(session.container == .trash && session.mixesKinds) else {
store.banners.postMixedTrashDrag()
cancelDrop()
return false
}
let ids = survivors.map { session.members[$0] }
let folders = survivors.map { session.folders[$0] }
let within = DragLocality.isSameBoard(sourceRoot, store.rootURL)