Wire native undo into menus, toolbar, and command validation
The command surface was already almost entirely platform machinery — this card proves it and pins it. Headless probes established that NSWindow.validateMenuItem answers enablement AND rewrites the row title from the delegate-supplied manager, so 'Undo Move 3 Cards' flows step phrase to Edit menu with no code of ours; under the lock the rows dim and keep their names, the correct reading of the-stack-survives. The toolbar twins validate through validateUserInterfaceItem, which never touches labels — 03's static-label exception proven rather than asserted — and their specs' enablement abstention is pinned so nobody later adds a second, disagreeing answer. The one link a headless run cannot close is the nil-target key-window resolution itself: standard responder-chain behavior with none of our code in it, left as the manual check. Base-edition 'disabled without undo' scaffolding is reworded away — every base board has undo now. New suites cover the trash's two doors (delete-then-undo byte-identical to Put Back's effect), position-preserving restore of a middle card, and the capstone: five gestures forward, five presses back to the origin board, five forward again, the menu phrase asserted after every press. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -194,15 +194,15 @@ public final class AppModel {
|
||||
/// **The composition root for `HistoryProviding`** (12-editions.md ▸ The provider seam): what a
|
||||
/// board session's undo stack is built by, called once per board as its session begins.
|
||||
///
|
||||
/// Base binds the native stack — an `NSUndoManager` over inverse `WriteOperation`s
|
||||
/// (13-native-undo.md) — and that is the default here because it is the *shared* code's
|
||||
/// implementation: both targets compile it, and Pro runs it too until pro-m1 replaces this
|
||||
/// closure with the git provider (06-history-undo.md). Nothing in this file is
|
||||
/// edition-conditional; the edition difference is which closure the root installs.
|
||||
/// Base binds the native stack — a pair of step stacks over the inverses registered at the
|
||||
/// Writer boundary (13-native-undo.md, `NativeHistoryProvider`) — and that is the default here
|
||||
/// because it is the *shared* code's implementation: both targets compile it, and Pro runs it
|
||||
/// too until pro-m1 replaces this closure with the git provider (06-history-undo.md). Nothing in
|
||||
/// this file is edition-conditional; the edition difference is which closure the root installs.
|
||||
///
|
||||
/// It takes the store because that is what a provider is a history *of*: the git provider needs
|
||||
/// the board root it is a repository at, and the native one is about to need the same store's
|
||||
/// snapshots to compute inverses from. A property rather than an initializer argument so a test
|
||||
/// the board root it is a repository at, and the native one's steps are computed from the same
|
||||
/// store's snapshots. A property rather than an initializer argument so a test
|
||||
/// can bind a fake without a second `AppModel` initializer, `@ObservationIgnored` because
|
||||
/// nothing renders from it.
|
||||
@ObservationIgnored
|
||||
|
||||
@@ -152,8 +152,9 @@ struct ToolbarItemSpec {
|
||||
/// - **Undo and Redo have to reach the responder chain.** Their menu rows are the system's own —
|
||||
/// nil-target `undo:`/`redo:` — and "matching their menu items" (03) is literal here: a toolbar
|
||||
/// item with the same nil-target action validates and fires through exactly the same lookup, so
|
||||
/// the pair is disabled today for the same reason the menu rows are, and lights up in m8 with no
|
||||
/// change here. A SwiftUI `Button` cannot express that.
|
||||
/// the pair enables and disables with the menu rows by construction rather than by agreement,
|
||||
/// reading the board window's `BoardUndoManager` through `NSWindow`'s own validation
|
||||
/// (13-native-undo.md). A SwiftUI `Button` cannot express that.
|
||||
/// - **The search item hosts a real `NSSearchField`** with explicit first-responder control, settled
|
||||
/// in m5 for reasons `BoardSearchFieldController` records (⌘F must focus it from a menu item;
|
||||
/// Escape in an empty field must hand the keyboard back to the strip). A toolbar that already
|
||||
|
||||
@@ -143,9 +143,10 @@ public struct HistoryStep {
|
||||
/// "One stack per board, owned by the board session. Not per-window: every window over a board
|
||||
/// (board window, its card windows) shares the store and shares the stack" (13-native-undo.md
|
||||
/// ▸ Rules). `AppModel.BoardSession` is where that ownership lives, and the composition root binds
|
||||
/// which implementation it gets: base binds `NativeHistoryProvider` (an `NSUndoManager` stack over
|
||||
/// inverse `WriteOperation`s), Pro binds the git provider in pro-m1 (undo as forward restore commits
|
||||
/// over HEAD's first-parent ancestry — 06-history-undo.md), Teams inherits Pro's.
|
||||
/// which implementation it gets: base binds `NativeHistoryProvider` (two step stacks over the
|
||||
/// inverses registered at the Writer boundary), Pro binds the git provider in pro-m1 (undo as
|
||||
/// forward restore commits over HEAD's first-parent ancestry — 06-history-undo.md), Teams inherits
|
||||
/// Pro's.
|
||||
///
|
||||
/// ### What this protocol deliberately does not say
|
||||
///
|
||||
|
||||
@@ -34,10 +34,13 @@ extension NSToolbarItem.Identifier {
|
||||
///
|
||||
/// The app ships no Undo/Redo rows of its own: those are the standard Edit-menu items, nil-target
|
||||
/// `undo:`/`redo:` resolved up the responder chain (`KanbanApp.menuCommands`). The toolbar items
|
||||
/// carry the same actions with the same nil target, so "the pair disabled on boards without undo …
|
||||
/// matching their menu items" (03) is not a predicate written here — it is the same validation, and
|
||||
/// on a base-edition board (no undo stack until m8 wires native undo) both are disabled for the same
|
||||
/// reason the menu rows are.
|
||||
/// carry the same actions with the same nil target, so "matching their menu items" (03) is not a
|
||||
/// predicate written here — it is literally the same validation. Both reach the board window, whose
|
||||
/// `windowWillReturnUndoManager` hands back the session's `BoardUndoManager`, and both therefore
|
||||
/// enable exactly when that board has a step to cross and no read-only lock stands
|
||||
/// (13-native-undo.md ▸ Rules). **Every base board has undo**, so there is no edition-shaped
|
||||
/// disablement to write: 03's parenthetical about boards without undo is 06's *git* substrate, which
|
||||
/// only Pro binds.
|
||||
///
|
||||
/// Their labels are the design's one exception to the menu-title rule: `NSUndoManager` rewrites the
|
||||
/// *menu* titles as the stack changes ("Undo Move Card"), which a toolbar label does not track, so
|
||||
|
||||
Reference in New Issue
Block a user