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:
@@ -127,6 +127,28 @@ struct AccessibilityPhrasesTests {
|
||||
#expect(AccessibilityPhrases.trashValue(cards: 0) == "0 cards")
|
||||
}
|
||||
|
||||
/// With lane rows in the column the value names both kinds — the rows are what the VoiceOver
|
||||
/// cursor is about to walk into, and a card-only count would understate the container.
|
||||
@Test("The trash's value names its lane rows when it has any")
|
||||
func trashValueCountsRows() {
|
||||
#expect(AccessibilityPhrases.trashValue(cards: 3, lanes: 1) == "3 cards, 1 lane")
|
||||
#expect(AccessibilityPhrases.trashValue(cards: 0, lanes: 2) == "0 cards, 2 lanes")
|
||||
// A trash with no rows of the other kind reads exactly as it always did.
|
||||
#expect(AccessibilityPhrases.trashValue(cards: 3, lanes: 0) == "3 cards")
|
||||
}
|
||||
|
||||
/// "A trashed **lane** is one flattened opaque element — '⟨title⟩, deleted lane, N cards' — never
|
||||
/// a container" (10-accessibility.md ▸ Trash lane), the design's own phrase.
|
||||
@Test("A trashed lane row says what it is and what it is holding")
|
||||
func trashedLaneRow() {
|
||||
#expect(AccessibilityPhrases.trashedLaneLabel(title: "Doing", cards: 5) == "Doing, deleted lane, 5 cards")
|
||||
#expect(AccessibilityPhrases.trashedLaneLabel(title: "Doing", cards: 1) == "Doing, deleted lane, 1 card")
|
||||
#expect(AccessibilityPhrases.trashedLaneLabel(title: nil, cards: 0) == "Untitled, deleted lane, 0 cards")
|
||||
// The untitled placeholder is the one the face draws, not a second spelling.
|
||||
#expect(AccessibilityPhrases.trashedLaneLabel(title: "", cards: 0)
|
||||
== AccessibilityPhrases.trashedLaneLabel(title: nil, cards: 0))
|
||||
}
|
||||
|
||||
/// The announcement states the resulting state rather than the action, so a user who mis-hit the
|
||||
/// toggle learns where the board ended up.
|
||||
@Test("Toggling trash visibility announces the resulting state")
|
||||
|
||||
Reference in New Issue
Block a user