Register inverse operations at the Writer boundary
The store is the Writer boundary, so it computes and registers inverses: a weak history sink bound at session composition, one HistoryStep per gesture at exactly the brackets that were already one performWrite each — multi-card moves, style batches, width pairs, and multi-row restores each undo as one plurally-titled step, and the Edit session registers once at the flip from the bytes disk held before its first landed write, debounce ticks registering nothing. Crossings run through performWrite, so an undo brackets the watcher, echoes through the reload, and reaches every window; every closure captures values, never snapshots. The inventory follows 13 exactly: moves return to origin lane and order, renames restore or remove the title key, restyles and resizes restore field values or absence, tombstones and restores swap with captured timestamps, and an undone create is a real removal — no trace — with redo re-materializing the same UUID from bytes captured at gesture time. Purge, attachments, repair, bookkeeping, checkbox flips, raw Apply, and the whole arrival family register nothing, each exclusion documented where it lives. Step names speak 06's verb vocabulary through the new HistoryPhrase. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -546,6 +546,13 @@ public final class AppModel {
|
||||
// below — the whole of 13-native-undo.md's session-only persistence: "the stack lives with
|
||||
// the board session and dies at close/quit ... standard macOS behavior".
|
||||
let history = makeHistoryProvider(store)
|
||||
// **The binding 13-native-undo.md ▸ Rules' "registration at the Writer boundary" needs**: the
|
||||
// store is that boundary — every app-mediated mutation goes out through one of its write
|
||||
// methods — so it is the store that computes each inverse and registers it. What it cannot
|
||||
// know is *which* stack, because a stack belongs to a session and a store knows nothing about
|
||||
// windows; this line is where the session tells it. Weak on the store's side, so the loop
|
||||
// this closes (provider → step closures → store) is not a retain cycle.
|
||||
store.history = history
|
||||
sessions[ref] = BoardSession(
|
||||
store: store,
|
||||
recordID: recordID,
|
||||
|
||||
@@ -413,6 +413,12 @@ struct CardWindowHost: View {
|
||||
guard let store else { return .vanished }
|
||||
return store.writeCardBody(inCard: cardID, body: text)
|
||||
}
|
||||
// The session's one undo step, at the Edit→Preview flip (13-native-undo.md ▸ Rules). Weakly,
|
||||
// `save`'s rule: a session ending after the board window has gone registers nothing rather
|
||||
// than resurrecting a released store — and the board's stack died with it anyway.
|
||||
session.body.registerUndo = { [weak store] priorBody, newBody in
|
||||
store?.registerBodyEdit(inCard: cardID, priorBody: priorBody, newBody: newBody)
|
||||
}
|
||||
bodyPresentation.flushEdits = { [session] in
|
||||
session.body.endEditSession()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user