Lanes delete into the trash — rendering, grammar, drag, clipboard, a11y
Phase 2 completes the lanes-in-trash card. TrashEntry merges the trash's two kinds by rank in exactly ONE place (ItemPath.resolve's own merge deleted in favor of it — the three-merge-points finding shrinks instead of growing). TrashLaneRowView renders the opaque row — tertiary plate, level-default lane glyph never the lane's own icon, title + card count, no accents, no expansion; the column badge counts rendered rows. Selection grammar: kind-homogeneous trash selections — ranges skip the other kind, ⇧-extension stops at the kind boundary, plain arrows walk the merged order, marquee stays card-only (now load-bearing: rows register frames for arrows), Select All card-scoped; successor-on-purge crosses kinds like navigation as the interim for open Gap 7b5cbc90. Drag: TrashDrop accepts lane sessions (drop on shown trash deletes), restoreLanes routes a trash-sourced strip drop as an arrival-ranked within-board move with an undo step. Clipboard: ⌘X/⌘V lane restore via opaque lane subjects; fixed boardRoot(ofLaneFolder:) returning .trash as the root — a same-board restore looked like an import and would have reminted the lane it was restoring (pinned by test). A11y: row = one flattened "title, deleted lane, N cards" element with Delete/Reveal actions; BoardDiff crossings read lanes as deleted/restored, shown-trash churn digested at row level. Agent guide stays v7 — the literal already teaches lanes-trash-by-move and kind stamping; drift-guard pins those lines. README trash paragraph notes lanes. Both schemes 1893 tests / 322 suites green. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -626,8 +626,8 @@ public final class ClipboardStore {
|
||||
///
|
||||
/// **The order is `SelectionGrammar.order`'s**, which is already the right answer for every
|
||||
/// (container, kind) pair: flatten order for board cards, left-to-right for lanes, and the
|
||||
/// trash's own `order` for trash cards. Deriving it here would be a second definition of an order
|
||||
/// the app already states once.
|
||||
/// trash's own rank order for its cards and its lane rows alike. Deriving it here would be a
|
||||
/// second definition of an order the app already states once.
|
||||
///
|
||||
/// **The index text comes from the snapshot, not from disk.** `FrontmatterDocument` edits by line
|
||||
/// span, so `serialized()` on an untouched document returns the file's bytes exactly — which makes
|
||||
@@ -665,6 +665,29 @@ public final class ClipboardStore {
|
||||
for card in snapshot.trash {
|
||||
addCard(card, at: .trashCard(card.id))
|
||||
}
|
||||
// **A trashed lane row copies and cuts like any other lane** (04-interactions.md ▸ The
|
||||
// trash: "a trashed lane pastes after the anchor lane (the lane-paste rule above,
|
||||
// verbatim)"), which makes ⌘X here the keyboard-native restore at the lane level.
|
||||
//
|
||||
// **No `cards` in the entry, and that is the opaque unit showing through**: a trashed
|
||||
// lane's subtree is deliberately not in the snapshot (`TrashedLane`), so there is nothing
|
||||
// here to describe it with — and nothing is lost by that, because the manifest's embedded
|
||||
// text is identification metadata only and the *content* comes from the staged folder,
|
||||
// which is copied whole, cards and all. An entry that guessed at a card list would be the
|
||||
// one place in the app claiming to know what an opaque unit holds.
|
||||
for lane in snapshot.trashedLanes {
|
||||
subjects[lane.id] = Subject(
|
||||
id: lane.id,
|
||||
path: .trashLane(lane.id),
|
||||
entry: ClipboardManifest.Entry(
|
||||
id: lane.id.rawValue,
|
||||
folder: lane.id.rawValue,
|
||||
title: lane.title.value,
|
||||
index: lane.document.serialized(),
|
||||
attachmentCount: 0
|
||||
)
|
||||
)
|
||||
}
|
||||
case .board:
|
||||
for lane in snapshot.lanes {
|
||||
if kind == .lane {
|
||||
|
||||
Reference in New Issue
Block a user