Realign code with the 2026-07-29 findings-resolution rulings
Nine rulings land as code. Reorders don't stamp — one container-change predicate (WriteOperation.rewritesOrderOnly): within-container reorders and the renumber rescale rewrite only order, while cross-lane, cross-board, and trash moves stamp modified and clear modified-by; no trash special case exists, and the m8 undo inverses conform through the same seam. Copies are transactions: the root-strict/nested-lenient split retires for a whole-subtree stampability preflight that refuses loudly naming the offender, and every item-level copy severs remote/remote-state at every level (whole-board forks carry them verbatim). Paste refuses, never degrades: the embedded-index.md materialization and its loss row retire; a missing staged snapshot produces nothing and posts an error-tone one-shot named from manifest metadata. Coerce-tier fallbacks log through the Defect stream with path context attached loader-side. Displacement is level-uniform: a file squatting attachments inside a card heals by the same rename ladder as board-root squatters; comments stays tolerated. Delete Immediately joins card and lane context menus as Delete's ⌥-alternate with its own VO custom action, routed through an explicit container so the menu target outranks standing selection. Agent guide v7 teaches the stamp discipline and the card-level attachments claim, and sheds two stale v6 lines (lanes trash now; kind is taught). Verified conformant, unchanged: edition-aware Undo/Redo disable, trash marquee full-height backdrop. Both schemes 1854 tests / 318 suites green; verify-editions 30/30. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -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 — 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 — with Delete Immediately as its
|
||||
/// ⌥-alternate — 11-command-nexus.md ▸ Context menus' Lane row, in its order, complete as of m5.
|
||||
///
|
||||
/// ### The card face
|
||||
///
|
||||
@@ -65,6 +65,11 @@ 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
|
||||
@@ -298,14 +303,27 @@ 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)
|
||||
@@ -317,6 +335,8 @@ 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
|
||||
@@ -331,6 +351,15 @@ 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() {
|
||||
@@ -389,6 +418,24 @@ 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))
|
||||
@@ -669,7 +716,7 @@ struct LaneView: View {
|
||||
CardFaceView(
|
||||
store: store,
|
||||
card: card,
|
||||
role: .board(openCard: openCard),
|
||||
role: .board(openCard: openCard, confirmations: confirmations),
|
||||
marquee: marquee,
|
||||
drops: drops
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user