The undo command surface rebuilds — app-owned rows and explicit toolbar targets over FocusedValues

Edit ▸ Undo/Redo become the app's own replaced rows and the board toolbar
pair takes explicit targets, both reading the focused session's
BoardUndoManager through FocusedValues.undoStack (board windows publish the
session's manager, card windows their own) — the nil-target route died with
the SwiftUI window latch, 13-native-undo.md ▸ Rules ▸ command surface,
re-ruled 2026-08-08. The rows enact the routing predicate themselves: text
focus routes ⌘Z to the first responder's own manager, title and enablement
included, re-derived at fire time with a beep for the stale window.
NativeHistoryProvider turns @Observable so both surfaces re-derive on stack
changes; a checkpoint-notification ticker covers plain text managers.
.responderAction leaves ToolbarItemSpec with its only user;
windowWillReturnUndoManager stays wired for AppKit's own asks.

Live-probed on the fixture board (21/21): the row retitles to "Undo Add
Lane" and crosses via real ⌘Z key events, ⇧⌘Z redoes via a window-server
chord, the toolbar pair validates and fires, search-field and body-editor
⌘Z stay text undo with board stacks untouched, and a card window crosses
its own stack with no fall-through. 2698 unit tests green.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 18:55:34 -04:00
parent 78c32776d4
commit 1fd19dfb12
13 changed files with 926 additions and 201 deletions
+7 -3
View File
@@ -279,8 +279,10 @@ public struct HistoryStep {
/// the seam is real", 12 written when native undo was the free tier's; the proof it named was
/// two working substrates, which the pivot left standing and the 2026-08-08 excision then narrowed
/// back to one, `strategy/01-git-excision.md` the seam itself is what stays proved either way).
/// AppKit still needs an `UndoManager` to hand the responder chain; that adapter is
/// `BoardUndoManager`, which sits *over* this protocol rather than inside it.
/// The command surface still speaks `UndoManager` the menu rows, the toolbar pair and AppKit's
/// own asks all read one (13-native-undo.md Rules the command-surface bullet, re-ruled
/// 2026-08-08); that adapter is `BoardUndoManager`, which sits *over* this protocol rather than
/// inside it.
/// - **No persistence promise.** The native stack dies with the session (13); a git provider's once
/// survived relaunch because git does, before app-managed git was excised entirely (2026-08-08,
/// `strategy/01-git-excision.md`). Both were honest implementations of these seven members; only
@@ -326,7 +328,9 @@ public protocol HistoryProviding: AnyObject {
func register(_ step: HistoryStep)
/// Whether there is a step to cross. What the Edit menu's Undo row and the toolbar's Undo item
/// enable on, through the same responder-chain answer.
/// enable on both of them through one `BoardUndoManager` over this seam, which is what keeps
/// the two surfaces from being two answers (13-native-undo.md Rules the command-surface
/// bullet, re-ruled 2026-08-08).
var canUndo: Bool { get }
var canRedo: Bool { get }