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:
@@ -104,11 +104,31 @@ enum AccessibilityPhrases {
|
||||
/// Trash: one word, never "Hide Trash"-style state in the name).
|
||||
static let trashLabel = "Trash"
|
||||
|
||||
/// The trash container's value: its card count, filtered exactly as the lane labels' are — "the
|
||||
/// The trash container's value: its counts, filtered exactly as the lane labels' are — "the
|
||||
/// shown trash's cards participate in the filter exactly like any other card" (03-board-ui.md §
|
||||
/// Trash), so the column passes the collection its badge counts.
|
||||
static func trashValue(cards count: Int) -> String {
|
||||
cardCount(count)
|
||||
/// Trash), so the column passes the collections its badge counts.
|
||||
///
|
||||
/// **Both kinds are named when both are there** (lanes rejoined the trash 2026-07-29): the
|
||||
/// container's rows are cards *and* opaque lane units, and a value that spoke only the cards
|
||||
/// would understate what the VoiceOver cursor is about to walk into. A trash holding no lane rows
|
||||
/// — overwhelmingly the common case — reads exactly as it always did.
|
||||
///
|
||||
/// It stops at counting, deliberately: the *freight* phrasing ("2 lanes containing 9 more
|
||||
/// cards") belongs to the purge confirmations, where the number is what the user is about to
|
||||
/// lose (`TrashModel.subject(for:)`).
|
||||
static func trashValue(cards: Int, lanes: Int = 0) -> String {
|
||||
guard lanes > 0 else { return cardCount(cards) }
|
||||
return "\(cardCount(cards)), \(laneCount(lanes))"
|
||||
}
|
||||
|
||||
/// A trashed lane row — **one flattened opaque element**, "⟨title⟩, deleted lane, N cards"
|
||||
/// (10-accessibility.md ▸ Trash lane, lanes rejoined 2026-07-29).
|
||||
///
|
||||
/// "Deleted lane" and not "lane" is the whole of what the label has to carry beyond a live lane
|
||||
/// container's: the row is in the trash, it is not a container, and its N is the freight it took
|
||||
/// with it rather than a filtered count of children VoiceOver could enter (`TrashedLane`).
|
||||
static func trashedLaneLabel(title: String?, cards count: Int) -> String {
|
||||
"\(displayTitle(title)), deleted lane, \(cardCount(count))"
|
||||
}
|
||||
|
||||
/// What View ▸ Show Trash announces — "toggling visibility is announced"
|
||||
@@ -125,10 +145,11 @@ enum AccessibilityPhrases {
|
||||
// MARK: - Live board announcements
|
||||
|
||||
/// "3 lanes", "1 lane" — `cardCount`'s twin, and the second half of the digest's plural folding.
|
||||
/// Spelled here rather than borrowed from `TrashModel.phrase` because that one is a cards-only
|
||||
/// container's phrase by design ("Lanes are never trashed").
|
||||
/// Borrowed from `TrashModel.lanePhrase` for `cardCount`'s reason: since lanes rejoined the trash
|
||||
/// (2026-07-29) the purge confirmations count lanes too, and one folding means a spoken count and
|
||||
/// a confirmed one cannot drift.
|
||||
static func laneCount(_ count: Int) -> String {
|
||||
"\(count) lane\(count == 1 ? "" : "s")"
|
||||
TrashModel.lanePhrase(count)
|
||||
}
|
||||
|
||||
/// The digest's opening — and, on its own, the whole sentence for a change no bucket counts.
|
||||
|
||||
Reference in New Issue
Block a user