Realign code with the 2026-07-30 findings-resolution rulings

Delete Immediately is removed entirely (ruling ae1dd96, Redesign card
d40bfac1): the delete vocabulary is purely staged — board → trash,
trash → permanent (confirmed on no-git boards), Empty Trash for bulk.
Gone: File ▸ Delete Immediately (⌥⌘⌫) and its validation, both
⌥-alternate context rows (card + the permanently-disabled lane row),
the VO custom action, BoardStore.deleteImmediately,
TrashModel.canDeleteImmediately, TrashConfirmations' .purge action
(zero surviving callers — trash-side Delete always used
.deleteTrashCards), and the pinning tests. purgePrompt drops its
now-single-purpose container parameter (.trash is the only surviving
caller). BoardWriter.purgeItem survives — create-undo rollback still
needs it — with its comment rewritten. README's trash paragraph drops
the ⌥⌘⌫ sentence.

The other 2026-07-30 rulings (2ec2c95 registry freshness stamp,
c741b02 unified-log-as-coerce-consumer) required no code changes —
already conformant.

Both schemes 1844 tests / 318 suites green.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 15:13:31 -04:00
parent 9ca8ed84de
commit 785ef5fe14
15 changed files with 83 additions and 483 deletions
+3 -50
View File
@@ -21,8 +21,8 @@ import SwiftUI
///
/// "The lane has one context menu (settled), invoked on the header or on lane empty space alike"
/// (03-board-ui.md § Lane), so both surfaces attach the *same* `laneMenu`. It carries Rename, Style,
/// the quick-style recents row, the Width stepper and Delete with Delete Immediately as its
/// -alternate 11-command-nexus.md Context menus' Lane row, in its order, complete as of m5.
/// the quick-style recents row, the Width stepper and Delete 11-command-nexus.md Context menus'
/// Lane row, in its order, complete as of m5.
///
/// ### The card face
///
@@ -65,11 +65,6 @@ struct LaneView: View {
/// business knowing about `WindowGroup` keys.
let openCard: (ItemID) -> Void
/// The window's purge-alert host Delete's -alternate needs it to raise the same confirmation
/// File Delete Immediately does (`TrashConfirmations`, `BoardView`'s own copy handed straight
/// down, since a lane has no business owning window-scoped state either).
let confirmations: TrashConfirmations
/// Reduce Motion, for the card transition below (10-accessibility.md). Read from the environment
/// and handed to `Motion`, which owns what "reduced" means.
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@@ -303,27 +298,14 @@ struct LaneView: View {
// Delete: File Delete's exact store path (`store.delete`), on the same widened target set
// Style above reads (`targetIDs`, `styleTarget`'s `Set<ItemID>` sibling below) the
// successor-selection rule is `delete(_:)`'s own, so this row gets it for free.
//
// **Delete Immediately as its -alternate** the same swap the card row wears
// (11-command-nexus.md Context menus' Lane row: "Delete with the same -alternate Delete
// Immediately. The trash needs no alternate"), `modifierKeyAlternate(.option)` again, the
// title matching File Delete Immediately's own verbatim (`TrashCommands`).
Button("Delete") { deleteTargets() }
.disabled(!store.acceptsBoardMutations)
.modifierKeyAlternate(.option) {
Button("Delete Immediately") { requestDeleteImmediately() }
.disabled(!canDeleteImmediately)
}
}
/// The menu's plain rows again, as VoiceOver custom actions (see the `.accessibilityActions`
/// call site). Every one of them calls the *same* private method its menu row does, so the two
/// surfaces cannot drift into meaning different things which is the only way "not a separate
/// design surface" is checkable rather than merely intended.
///
/// Delete Immediately gets its own row rather than riding `modifierKeyAlternate`, `CardFaceView
/// .boardActions`' own reason: the action rotor has no held-key concept, so the alternate needs a
/// first-class custom action to be reachable at all.
@ViewBuilder
private var laneActions: some View {
let units = LaneLayoutMath.displayUnits(of: lane)
@@ -335,8 +317,6 @@ struct LaneView: View {
.disabled(!store.acceptsBoardMutations || units <= 1)
Button("Delete") { deleteTargets() }
.disabled(!store.acceptsBoardMutations)
Button("Delete Immediately") { requestDeleteImmediately() }
.disabled(!canDeleteImmediately)
}
/// Board Rename's store path, seeded with the lane's live title one method, two callers
@@ -351,15 +331,6 @@ struct LaneView: View {
store.delete(targetIDs)
}
/// Delete's -alternate the lane's **permanent** delete, through the window's confirmation host
/// rather than straight to the store (`CardFaceView.requestDeleteImmediately`'s own reason): the
/// alert (or the git-board shrug) is what stands between this row and an unrecoverable loss.
/// `TrashConfirmations.requestBoardPurge` is the shared entry point both rows call, over each
/// one's own `targetIDs`.
private func requestDeleteImmediately() {
confirmations.requestBoardPurge(of: targetIDs, in: store)
}
/// VO-Space's landing: the -click funnel, on this lane. `togglesOnRepeat` stays false because
/// only the *plain* branch reads it the branch is already a toggle, which is the point.
private func toggleLaneSelection() {
@@ -418,24 +389,6 @@ struct LaneView: View {
return store.selection.ids
}
/// Whether Delete Immediately's -alternate has something to purge File Delete Immediately's
/// own predicate (`TrashModel.canDeleteImmediately`), read over this row's `targetIDs`
/// (`CardFaceView.canDeleteImmediately`'s own reason: a context menu names its target by where it
/// was invoked, not by the standing selection).
///
/// **Presently always disabled on a lane-only target**: `canDeleteImmediately` is cards only
/// today "a lane's delete is physical already there is nothing for 'skip the trash' to mean
/// on one" (`TrashModel`) so this row is wired per 11-command-nexus.md's Lane row ahead of the
/// store gaining the capability, the same posture the File-menu command already takes on a
/// lane-only selection.
private var canDeleteImmediately: Bool {
guard store.acceptsBoardMutations else { return false }
return TrashModel.canDeleteImmediately(
selection: ItemReferenceSet(ids: targetIDs, container: .board),
in: store.snapshot
)
}
private var headerContent: some View {
HStack(alignment: .firstTextBaseline, spacing: BoardMetrics.laneHeaderSpacing(bodyPointSize: pointSize)) {
Image(systemName: ItemSymbol.name(lane.icon, fallback: ItemSymbol.lane))
@@ -716,7 +669,7 @@ struct LaneView: View {
CardFaceView(
store: store,
card: card,
role: .board(openCard: openCard, confirmations: confirmations),
role: .board(openCard: openCard),
marquee: marquee,
drops: drops
)