Realign undo with the evening rulings — repo-nested and identity anchors
Repo-nested boards bind native undo in every tier (25d2513): the no-undo case is gone, makeHistoryProvider answers git or native, and the native path provably never touches the enclosing repository's .git. Session undo steps anchor by card identity, never by path (9119aa1): HistoryAnchor carries the card UUID (plus comment/draft vocabulary) and apply-time validation resolves the current folder via the same both-container walk writeCardBody uses — a board-side lane or trash move no longer stales the coarse close step, while a genuine field collision still skips it whole. 2448 tests in 423 suites green. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
+31
-20
@@ -291,7 +291,7 @@ public final class AppModel {
|
||||
/// can bind a fake without a second `AppModel` initializer, `@ObservationIgnored` because
|
||||
/// nothing renders from it.
|
||||
///
|
||||
/// ### The three answers, and the one `nil` among them
|
||||
/// ### The two answers, and the `nil` that is no longer one of them
|
||||
///
|
||||
/// - **No `HistoryStore` at all** — the free tier, where `HistoryStore.compose` returns `nil`
|
||||
/// without so much as a `stat`: the **native stack, on every board**. "The free tier binds it
|
||||
@@ -300,17 +300,26 @@ public final class AppModel {
|
||||
/// native undo runs". The absent git state *is* the tier test; nothing here reads a flag.
|
||||
/// - **Mode `git`** (Pro only — no other tier composes a git state) — the git provider: undo as
|
||||
/// forward restore commits over HEAD's first-parent ancestry (06).
|
||||
/// - **Mode `none`** — the **native stack**, exactly as in the free tier. "Gitless boards bind
|
||||
/// the native undo stack in every tier — an upgrade never removes undo" (12).
|
||||
/// - **Mode `repoNested`** — **no provider**, the one no-undo case: a board inside somebody
|
||||
/// else's repository is one the app "leaves strictly alone … so they get **no undo**" (06
|
||||
/// ▸ Rules), and an in-memory stack there would be the app-managed undo journal that rule
|
||||
/// forbids. Edit ▸ Undo/Redo and the toolbar pair disable there and nowhere else but under a
|
||||
/// lock and on an empty stack (03-board-ui.md ▸ Toolbar ▸ Catalog).
|
||||
/// - **Mode `none` and mode `repoNested` alike** — the **native stack**, exactly as in the free
|
||||
/// tier. "Boards without app-managed git — repo-nested included — bind 13-native-undo.md's
|
||||
/// native stack in **every** tier" (03-board-ui.md ▸ Toolbar ▸ Catalog, re-ruled 2026-07-31
|
||||
/// twice; 12 ▸ The provider seam; 13's header).
|
||||
///
|
||||
/// The `HistoryStore` argument is what makes that decidable here, and it is why `beginSession`
|
||||
/// composes the git state *before* the provider: which substrate a board gets is a question about
|
||||
/// its repository, and a root that had to ask the disk itself would be a second detection.
|
||||
/// **The repo-nested no-undo case is gone** (re-ruled 2026-07-31): 06's leave-strictly-alone
|
||||
/// stance "concerns *git*, and this stack never touches git — memory-only, journal-free,
|
||||
/// session-scoped — so what repo-nested denies is app-managed history, never ⌘Z" (13's header).
|
||||
/// It also made the Pro upgrade story exceptional, which was the other half of the same defect:
|
||||
/// the free tier could not tell such a board from a plain one and bound the native stack anyway,
|
||||
/// so subscribing *removed* undo from exactly the boards it left alone. Edit ▸ Undo/Redo and the
|
||||
/// toolbar pair now disable only under a lock and on an empty stack.
|
||||
///
|
||||
/// This closure therefore never answers `nil`, and the seam stays optional for the seam's own
|
||||
/// reason: a test binds a substrate-less board through it (`BoardUndoManager.history`).
|
||||
///
|
||||
/// The `HistoryStore` argument is what makes the git/gitless split decidable here, and it is why
|
||||
/// `beginSession` composes the git state *before* the provider: which substrate a board gets is a
|
||||
/// question about its repository, and a root that had to ask the disk itself would be a second
|
||||
/// detection.
|
||||
///
|
||||
/// ### Consumers
|
||||
///
|
||||
@@ -322,8 +331,7 @@ public final class AppModel {
|
||||
guard let git else { return NativeHistoryProvider() }
|
||||
switch git.mode {
|
||||
case .git: return GitHistoryProvider(boardRoot: store.rootURL)
|
||||
case .none: return NativeHistoryProvider()
|
||||
case .repoNested: return nil
|
||||
case .none, .repoNested: return NativeHistoryProvider()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,11 +357,13 @@ public final class AppModel {
|
||||
/// Which implementation it is, is the tier's answer and nobody else's
|
||||
/// (12-editions.md ▸ The provider seam) — see `AppModel.makeHistoryProvider`.
|
||||
///
|
||||
/// **`nil` is a board with no undo at all** — a repo-nested board under Pro, and nothing else
|
||||
/// (06-history-undo.md ▸ Rules: boards inside an existing repository "get **no undo**").
|
||||
/// Gitless boards bind the native stack in every tier (re-ruled 2026-07-31 — see
|
||||
/// `AppModel.makeHistoryProvider`). The command surface disables through `undoManager`, which
|
||||
/// answers the empty way over an absent substrate.
|
||||
/// **`nil` is a board with no undo at all, and no board the app composes is one any more**
|
||||
/// (re-ruled 2026-07-31 — see `AppModel.makeHistoryProvider`): boards without app-managed git,
|
||||
/// repo-nested included, bind the native stack in every tier, and git boards bind the git
|
||||
/// provider. What keeps the optionality is the seam rather than a board: a test binds a
|
||||
/// substrate-less session through `makeHistoryProvider`, and a store with no session at all
|
||||
/// registers nothing (`BoardStore.registerStep`). The command surface disables through
|
||||
/// `undoManager`, which answers the empty way over an absent substrate.
|
||||
///
|
||||
/// A `var`, unlike `tier` beside it, and for one event only: **add-git**, the design's single
|
||||
/// sanctioned mid-session mode flip, *swaps* the substrate here on the board it flips —
|
||||
@@ -398,8 +408,9 @@ public final class AppModel {
|
||||
/// **Its first consumer is the provider seam** — `makeHistoryProvider` reads exactly this to
|
||||
/// know whether the board has a repository to be an undo stack for, and it is the *mode*
|
||||
/// rather than the tier that decides (re-ruled 2026-07-31): `git` binds the git provider,
|
||||
/// `none` the native stack, `repoNested` nothing. The popover's git section is the other
|
||||
/// reader.
|
||||
/// `none` and `repoNested` alike the native stack. The popover's git section is the other
|
||||
/// reader — and the one place the two gitless modes still differ, since add-git is offered on
|
||||
/// one and explained away on the other.
|
||||
///
|
||||
/// `@MainActor` because the state it reads is: a nested type does not inherit its enclosing
|
||||
/// type's isolation, and everything that asks a session what mode it is in is main-actor
|
||||
|
||||
Reference in New Issue
Block a user