The paper agrees with the code — guide v11, README, DESIGN re-rulings, and the adjudicated sweep

Step 7 of strategy/01-git-excision.md, the companions. The agent guide bumps to v11: the Git section teaches repo-resident etiquette alone (stage only your own paths, commit your own changes, leave app-maintained files to the app) — existing boards heal to the new text on next open. README re-anchors: the four git feature bullets out, tiers say the complete Mac experience is free, and one bullet states the format's git-friendliness promise. The changelog drops the never-shipped git entries. DESIGN re-rules: 06 retired with Undo routing migrated to 13 (now the sole substrate's doc, seam kept open), 07 retired as written pending the ops-service workstream, 14 retired as superseded record, 12 carries the second pivot note, the index reflects all of it; the charter gets a pointer note (the anchors' full re-ruling stays with the user). InertGitTests renames to GitAgnosticStorageTests — the excision restores its original claim app-wide. And the sweep: ~70 comment sites across 36 files adjudicated against the keeper list, every present-tense description of the excised machinery made past tense or repointed, keepers untouched. 2,707 tests green.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 12:31:27 -04:00
parent 8cf1457814
commit 445d035a83
50 changed files with 397 additions and 313 deletions
+3 -1
View File
@@ -1,5 +1,7 @@
# History & Undo
**RETIRED 2026-08-08** (strategy/01-git-excision.md — the excision ruling): app-managed git is removed from Lanework entirely — the git HistoryProvider, auto-commit, branches, restore-as-forward-commit, all of the machinery this doc designed — so nothing below describes the shipping app. Three things outlive it: the **Undo routing** section migrated to 13-native-undo.md before this note landed (the rule was always tier- and substrate-independent, and 13 is its live home), the **commit-message vocabulary** survives as the harvested change narrator (`Kanban/Changes/`, the designated core of the future activity feed / foreign-change journal), and the safety-net value — history of externally delivered changes — routes to the deferred foreign-change journal (01-git-excision.md ▸ Successors). The stack itself is recoverable at tag `pre-git-excision`. Everything below is the record of the design as it stood.
**Tier scope: every tier** (Pivot 2026-08-07 — 12-editions.md: git left the paywall; this line formerly scoped the doc to Lanework Pro, with the free tier shipping mode:none only over the now-retired inert-`.git` posture). This doc is the git HistoryProvider, composed on git-mode boards in every tier; boards without app-managed git bind macOS-native undo (13-native-undo.md). The Undo routing section below was always tier-independent — both substrates dispatch through it.
Git is the undo substrate — on boards that have git. **Git is opt-in per board (a pivot from the pathfinder, which auto-initialized every board): a board may be created without git, and git can be added later** (via the board popover's Git tab — 03-board-ui.md; see 07-sync-collab.md's mode progression). A board without app-managed git binds the **native undo stack in every tier** — repo-nested included (re-ruled 2026-07-31, twice — the provider follows the board, 13-native-undo.md; formerly no-undo under Pro, which made upgrading remove undo from mode-none boards, and the repo-nested no-undo residue retired the same day: the native stack touches no git, so leave-strictly-alone is untouched and no board lacks ⌘Z). (Text editors keep their standard typing undo everywhere; see Undo routing below.) Deletes — card or lane — are recoverable on every board via the materialized trash (03-board-ui.md). Add-git swaps native → git mid-session, discarding the in-session native stack and seeding the git trail — the branch-switch discard-and-reseed precedent. On git-enabled boards, every settled change auto-commits; those mechanics are carried over from the pathfinder with their hard rules intact.
@@ -25,7 +27,7 @@ Git is the undo substrate — on boards that have git. **Git is opt-in per board
## Undo routing
**Routing is by focus** — the platform's first-responder rule, its own section because two undo systems coexist and four docs cite the rule. While a text-editing surface is focused (card title field, body Edit mode, raw source, board inline rename), ⌘Z/⇧⌘Z are that editor's own **text undo** — standard, transient, session-scoped: leaving the editor (mode flip, focus loss, close) ends the session, and from then on that content's undo story is the git trail. Text undo works on **every** board — and since the 2026-07-31 repo-nested re-ruling, so does board-level undo: every board binds a provider (native or git), so "no undo" is no longer a state any board is in. **Control-class text fields route the same way** (settled): the search field (04-interactions.md ▸ Search), the popover's rename field, and the popover's own configuration fields (commit identity, the New Branch… name, and 07's credentials and remote URL when they land) own ⌘Z/⇧⌘Z as field-local text undo while focused — "board menu commands stay enabled" never hands Edit ▸ Undo to git while a text-bearing control has focus; a reflexive undo over a typo must never become a tree checkout. With focus outside every text-bearing surface — editor or control — Edit ▸ Undo/Redo are, **in a card window, that window's own session stack** (13-native-undo.md's two-level model, re-ruled 2026-07-31 — fine-grained window gestures, both tiers; the coarse close unit is the tier-split: one native board step, or one commit), and on board surfaces board history — the board's bound provider, git or native (every board binds one since 2026-07-31; disabling is locks and empty stacks). **No fall-through**: exhausting a focused editor's — or the window's — stack beeps; it never reaches board history.
Migrated to 13-native-undo.md (2026-08-08, at this doc\'s retirement) — the rule was always tier- and substrate-independent, and 13 is its live home; the heading stays here for the docs that cite it.
## Commit messages
+2
View File
@@ -1,5 +1,7 @@
# Sync & Collaboration
**RETIRED 2026-08-08 as written** (strategy/01-git-excision.md): the git-rails sync this doc designed — remotes, pull/push, auth, the mode progression — died with the excision of app-managed git, and none of its remote half was ever built. The successor is the **ops-based sync service** (semantic ops over a server-side worktree API, the phone and eventually the web as worktree clients — 01-git-excision.md ▸ Successors), which arrives with its own workstream and its own docs; the popover's Sync tab (03-board-ui.md) holds the surface until then. The iCloud-Drive warning below outlives the retirement in spirit — iCloud remains unendorsed as a transport. Everything below is the record of the design as it stood.
**Tier scope: Lanework Pro** (12-editions.md). The free tier ships mode:none only — the state machine below never leaves its first state there, `.git` encountered on disk is inert (12), and the Mode: none section's old no-undo caveat is superseded in every tier by native undo (13-native-undo.md — the provider follows the board, re-ruled 2026-07-31). Teams adds tracker-backed sync behind the same seam (deferred).
Every board has exactly one **collab mode** at a time, but the mode is not fixed at creation — it can evolve over the board's lifetime:
+2
View File
@@ -4,6 +4,8 @@ Lanework ships as **one Mac App Store app** — `dev.rzen.indie.Kanban`, free, 2
**PIVOT 2026-08-07 — git leaves the paywall.** Git integration — detection, adoption, auto-commit, git-backed undo/history, branches (06-history-undo.md), and remotes/auth when they ship (07-sync-collab.md) — is **tier-independent**: every tier composes the git stack on git-mode boards exactly as Pro did. The base/Pro feature split is being re-decided, and git isn't going to be it. Until the new split is ruled: the subscription **machinery stays built and tested but dormant** — the entitlement's mechanics (local read, composition-time, offline grace, the recorded session tier) are unchanged and correct for whatever the next split gates, but the Settings Pro section is not rendered and no surface names or sells Pro. The free-only git postures are **retired**: the inert-`.git` stance and the popover's Pro pointer describe a gate that no longer exists — a `.git` at a board root is live in every tier, detection runs at every board open, and every board carries the popover's Git tab (the mode-driven postures: no-repository door, repo-nested, unverifiable, branch). What the pivot does **not** change: git stays **opt-in per board** (06 — creating a local repository is the user's deliberate choice, never auto-initialized), and a board without app-managed git binds conventional native undo/redo (13) exactly as before — the provider still follows the board. The sections below describing the git gate (the tier matrix's git rows, the inert posture, no-grandfathering) stand as record of the pre-pivot design and are not restated; read them through this note.
**PIVOT 2026-08-08 — git excised entirely** (strategy/01-git-excision.md, the second step past the note above): the git stack the first pivot made tier-independent is now removed from the app — engine, UI surfaces, seam bindings, SwiftGitX/libgit2 — with the message engine harvested to `Kanban/Changes/` as the future activity-feed narrator and the pre-excision state tagged `pre-git-excision`. The format's git-friendliness (per-card files, stable UUIDs, byte-fidelity, the seeded `.gitignore`) is the surviving promise, free forever; boards inside user repositories stay first-class and untouched. The tier machinery stays dormant exactly as the first pivot left it; the next split is expected to be "your board beyond your Mac" — the iPhone companion plus the ops-based sync service (the strategy workstream's charter) — and is not ruled by this note. 06 and 14 are retired, 07 is retired as written (06's Undo routing migrated to 13, now the sole substrate's doc); read this doc's git rows, modes, and matrix as record.
**Re-ruled 2026-07-30 — the one-app collapse.** This supersedes the 2026-07-27 two-app split (separate base and Pro targets) and the 2026-07-29 App Group ruling that served it. The split's compile-time purity (base never links libgit2, no network entitlement) dragged permanent coexistence machinery behind it: a shared App Group, per-edition grant slots (security-scoped bookmarks never cross sandboxes), registry freshness stamping between two live processes, UTI-ownership twins, a both-apps-installed rulebook — a tax on every layer that generated a steady stream of design findings, all serving a state (two sandboxed apps sharing app-side state) that existed only because the packaging created it. One app makes that state unrepresentable. Costs accepted with eyes open: libgit2 rides dormant in the free download, and the one app declares the network-client entitlement (exercised only under Pro) — the "free app provably has no network access" story is traded for "no network use until you subscribe," which is honest but weaker.
## The tiers
+12 -6
View File
@@ -1,28 +1,34 @@
# Native Undo
The undo/redo substrate for **every board without app-managed git** (re-ruled 2026-07-31 — the provider follows the board, not the tier alone; formerly free-tier-only, which made a Pro upgrade *remove* undo from mode-none boards). Since the 2026-08-07 pivot (12-editions.md — git left the paywall) the tier axis is gone entirely, and one thing the pivot does **not** change: git remains **opt-in per board** — creating a local repository is the user's deliberate choice (add-git, 06), never something the app initializes for them — so a board whose user never opted in keeps this conventional stack for good. The substrate is the board's mode alone: mode-none **and repo-nested** boards bind it (the repo-nested no-undo case retired 2026-07-31: leave-strictly-alone 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; the upgrade story is thereby exceptionless), switching to the git provider (06-history-undo.md) where the board's own git exists. **Add-git swaps the substrate mid-session** — the commanded flip discards the in-session native stack and seeds the git trail from the root commit, the branch-switch discard-and-reseed precedent applied; a subscription lapse still never interrupts (12). The design problem is not NSUndoManager itself — it is native undo over **files-are-truth**: the disk can change underneath the stack, because the app is not the only writer.
The undo/redo substrate for **every board** (amended 2026-08-08 — strategy/01-git-excision.md: app-managed git is excised, and with it the git provider this doc used to hand off to; the earlier text here described the mode split and the add-git mid-session swap, both gone with the stack). The provider seam survives the excision deliberately — `HistoryProviding` is nil-safe and default-native (01-git-excision.md ▸ Reversibility) — so a future provider (the deferred foreign-change journal, an ops-backed history, or even git again) re-binds without re-plumbing; until one exists, this stack is the only substrate any board binds, repo-nested and user-repo boards included (this stack never touches git — memory-only, journal-free, session-scoped — so leave-strictly-alone holds by construction). The design problem is not NSUndoManager itself — it is native undo over **files-are-truth**: the disk can change underneath the stack, because the app is not the only writer.
## Rules
- **Two levels: one stack per board, one per open card window** (re-ruled 2026-07-31 — the session-coarsening model, superseding the pure one-stack rule): the **board stack** is owned by the board session and shared by board surfaces; a **card window owns its own stack** for the session it represents — every gesture issued in that window (comment post/delete/edit, body Edit sessions, style/details changes, attachment ops where undoable) registers there at fine grain, and `window.undoManager` answers with it (standard per-window AppKit scoping). Disk stays live throughout — files-first untouched; this is history granularity only. **Window close coarsens**: the session's net effect registers on the board stack as **one coarse step named "Changes to '⟨card⟩'"** (ruled 2026-07-31 — the board row reads "Undo Changes to 'Fix login'": plural and scope-flavored, distinct from every fine verb, honest about folding many kinds; the fine body-edit wording never leaks onto the board menu), values-based, whose undo restores the card subtree to its session-start state — deleted comments included — and whose redo reapplies the net effect; a session with no net change registers nothing. The coarse step is transactional at apply time: staleness validation runs per component (the field-level predicate below), and any stale component skips the whole step — never a partial session revert. **Session steps anchor by card identity, never by path** (ruled 2026-07-31): the coarse step — and the window's fine steps it folds — stores the card's UUID plus expected values, and apply-time validation resolves the card's *current* folder exactly the way the window itself always resolves its card (the per-snapshot UUID walk; `writeCardBody` already resolves trash locations on purpose). A tracked relocation — a lane move mid-session or after close, a trash move — therefore never stales the step; only genuine content changes do, which is what the validation exists to catch. A card that resolves nowhere (purged, or moved out of the board) is the honest skip. 06 ▸ Undo routing applies unchanged — text-editing surfaces get their session-scoped text undo above either stack. **Two stacks over one open card are the blessed shape** (2026-07-31): a board-issued gesture on a card whose window is open registers on the board stack while the window's own gestures register on the window stack — no ordering relation between the two, interleaving decided by ⌘Z focus (06 ▸ Undo routing); routing board gestures into the open window's stack was considered and rejected, since board ⌘Z must never see card-session steps.
- **Registration at the Writer boundary.** Every app-mediated mutation already passes through the Writer as a `WriteOperation` (02-architecture.md) — that closed enum is the exact inventory of undoable operations. Each Writer call site registers the inverse operation, computed from the pre-write snapshot the store already holds: move → move back (original lane, original `order`); reorder → restore original `order`; rename → restore title; restyle → restore prior style; resize → restore prior width; Edit-session body save → restore prior body bytes; card or lane delete (⌫) → move back out of `.trash/` (lanes rejoined the trash 2026-07-29 — the recreate-from-capture inverse retires with the last destructive delete); restore-by-move → move back in; create → remove the created folder.
- **What is not undoable** (settled): **Permanently delete** (the trash's Delete, Empty Trash) — `purgeIsUnrecoverable` stays true in base, and the existing confirmation rule (03-board-ui.md) already fires on all base boards, since none have git history: the confirm *is* the safety. **The duplicate-id remint** (01-storage-format.md — a silent scheduled heal since 2026-07-29, formerly the user-gated Repair) — heals aren't user gestures, so nothing enters the stack, and undoing one would recreate the duplicate id it exists to remove. Permanently delete matches its existing "destructive, confirmed, final" posture; the remint sits outside undo as all heals do. **Raw Source Apply** (blessed 2026-07-31): the hatch writes byte-for-byte outside every contract — no `modified` stamp, no attribution clear, and no history step at either level; an Apply-only session folds to no coarse step, and an Apply mixed into a session is invisible to the fold. The hatch's story is "you edited the file," and files-are-truth covers it — on git boards the write commits like any disk change (05-card-window.md's carve-outs are the same statement from the stamping side).
- **What is not undoable** (settled): **Permanently delete** (the trash's Delete, Empty Trash) — `purgeIsUnrecoverable` stays true in base, and the existing confirmation rule (03-board-ui.md) already fires on all base boards, since none have git history: the confirm *is* the safety. **The duplicate-id remint** (01-storage-format.md — a silent scheduled heal since 2026-07-29, formerly the user-gated Repair) — heals aren't user gestures, so nothing enters the stack, and undoing one would recreate the duplicate id it exists to remove. Permanently delete matches its existing "destructive, confirmed, final" posture; the remint sits outside undo as all heals do. **Raw Source Apply** (blessed 2026-07-31): the hatch writes byte-for-byte outside every contract — no `modified` stamp, no attribution clear, and no history step at either level; an Apply-only session folds to no coarse step, and an Apply mixed into a session is invisible to the fold. The hatch's story is "you edited the file," and files-are-truth covers it (05-card-window.md's carve-outs are the same statement from the stamping side).
- **Coalescing follows commit granularity** (settled; window scoping added 2026-07-31): one gesture, one undo step — a multi-card move is one step with a plural title; an Edit session is one step, registered at the Edit→Preview flip (the effective Save — 05-card-window.md) **on the card window's stack**, like every window gesture; the window close registers the one coarse session step on the board stack (Rules above); a styling batch is one step (03's one-gesture-one-commit rule, substrate swapped). The 06 vocabulary supplies menu titles ("Undo Move 3 Cards"), via NSUndoManager's dynamic retitling — the same naming machinery both editions use.
- **Session-only persistence** (settled): the stack lives with the board session and dies at close/quit — standard macOS behavior. Git undo's survive-relaunch property is a Pro difference, stated honestly (12's matrix).
- **Foreign writes never join the stack** (settled): NSUndoManager can only undo what the app mediated. An agent's or hand edit is not a step — the honest capability gap vs Pro (12's matrix). Foreign changes also do not clear the stack wholesale; collisions are handled lazily, per step, by validation:
- **Session-only persistence** (settled): the stack lives with the board session and dies at close/quit — standard macOS behavior. (The excised git substrate's survive-relaunch property went with it; persistence is a future provider's to offer through the same seam.)
- **Foreign writes never join the stack** (settled): NSUndoManager can only undo what the app mediated. An agent's or hand edit is not a step — the honest capability gap, whose designated successor is the deferred foreign-change journal (strategy/01-git-excision.md ▸ Successors). Foreign changes also do not clear the stack wholesale; collisions are handled lazily, per step, by validation:
- **Staleness validation before every apply** (settled): an inverse operation re-checks its target against the disk — a fresh read of the target at ⌘Z time (blessed 2026-07-29: not the store snapshot, which is by construction one reload behind the app's own writes; a rapid ⌘Z run validated against the snapshot would compare pre-state and false-skip every step). **The predicate is field-level** (settled — ruled 2026-07-27): each step registers both sides of its write anyway (the before-value is the inverse; the after-value is what its write set), so validation compares the targeted field's current value against the expected after-value — nearly free, and truer to never-surprise-the-file than an existence-only check (an inverse rename must not clobber a foreign rename on a still-existing card; body steps compare bytes). Target folder gone, or the field no longer holding the step's after-value → the step is **skipped, not applied**: popped from the stack with an info-tone banner ("Undo skipped — 'Fix login' changed outside Lanework"), and ⌘Z falls through to the next step. Never apply a stale inverse on top of someone else's newer write. **Delete steps validate their undo by existence only** (blessed 2026-07-31): a delete's forward write sets nothing but the `modified` stamp (the arrival rank mint retired 2026-07-31), and a clock reading is not a choosable after-value — pinning it would false-skip the restore whenever an agent touched the trashed card; the undo therefore expects only that the trashed folder still exists, while the redo stays field-level via the restore's `order` write. **Invalidation is lazy** (settled — ruled 2026-07-27): staleness is discovered at ⌘Z time, never by background pruning — the EchoLedger's foreign diffs do not eagerly drop colliding steps. The stack always looks full; with the field-level predicate a skip fires only on a genuine per-field collision, and a skipped step's banner explains itself, where eager pruning would shrink the stack invisibly mid-session.
- **Locks disable the stack** (settled): every read-only lock (vanished root, failed reload after wholesale ops, unwritable location — 02-architecture.md) disables Undo/Redo with the other mutating commands; the stack itself survives the lock and resumes when it clears. Steps landed before a lock validate like any other at apply time.
## Undo routing
(Migrated here 2026-08-08 from 06-history-undo.md at that doc's retirement — the rule was always tier- and substrate-independent, and this is its live home now; four docs cite it by this heading.)
**Routing is by focus** — the platform's first-responder rule. While a text-editing surface is focused (card title field, body Edit mode, raw source, board inline rename), ⌘Z/⇧⌘Z are that editor's own **text undo** — standard, transient, session-scoped: leaving the editor (mode flip, focus loss, close) ends the session, and from then on that content's undo story is board history. Text undo works on **every** board, and so does board-level undo: every board binds a provider (the native stack today; the seam admits future providers), so "no undo" is not a state any board is in. **Control-class text fields route the same way** (settled): the search field (04-interactions.md ▸ Search), the popover's rename field, and the popover's other configuration fields own ⌘Z/⇧⌘Z as field-local text undo while focused — "board menu commands stay enabled" never hands Edit ▸ Undo to board history while a text-bearing control has focus; a reflexive undo over a typo must never become a board-level restore. With focus outside every text-bearing surface — editor or control — Edit ▸ Undo/Redo are, **in a card window, that window's own session stack** (the two-level model in Rules above — fine-grained window gestures; the coarse close unit is one board step), and on board surfaces board history — the board's bound provider (disabling is locks and empty stacks). **No fall-through**: exhausting a focused editor's — or the window's — stack beeps; it never reaches board history.
## Interaction with the trash
⌫'s undo is the move back — a delete is a move into `.trash/` (cards resettled 2026-07-28; lanes rejoined 2026-07-29), so its undo is the ordinary inverse move, returning a card to its source lane and rank, a lane to its strip position (subtree intact — it never left the folder); a restore-by-move undoes the same way in reverse. The stack and the trash never conflict — they are the same folder moves addressed by recency instead of by selection. The old lane-delete recreate-from-capture inverse is **retired** — no destructive delete remains outside a trash, so nothing needs byte capture. A **permanent delete registers no step** — the trash's Delete and Empty Trash are not undoable (Rules above), lanes and their freight included; the confirm is the safety.
**Comments keep the no-capture rule true — on the window stack** (re-ruled 2026-07-31, superseding the board-stack routing): a comment delete is a move into the card's `comments/.trash/` (01-storage-format.md ▸ Enhanced schema — the materialized-trash pattern one level down), its inverse the ordinary move back, and the step lives on the **card window's own stack** (Rules above) — the board stack never carries a granular comment step, so the old stale-after-close skip scenario cannot arise. **The purge of `comments/.trash/` defers with the coarse step** (re-ruled 2026-07-31, superseding purge-at-close): the coarse close step's undo restores deleted comments, so their backing lives as long as the step does — the purge runs when the coarse step leaves the board stack **cleanly** — undone-and-superseded, or dropped off the end — or when the board session ends; **a stale-skipped step's backing instead survives to board-session end** (ruled 2026-07-31, decoupling skip from purge): the skip banner says nothing was applied, and an irreversible purge riding that gesture would be surprise loss — the skip is exactly when the user may want to inspect what the collision left; crash residue still sweeps at the next card-window open — **and residue is defined by the purge-deferral condition itself** (ruled 2026-07-31): `comments/.trash/` content referenced by a live coarse step on the board stack is a step's backing, not residue — the open-time sweep consults the stack and skips owned content, re-arming when the owning step leaves the stack (which is exactly when the deferred purge wanted to run; one condition, two consumers). Reopening a window can therefore never destroy its prior session's undo backing. Unowned content sweeps as before, armed-then-cleared like every heal memo. **Every purge of `comments/.trash/` is per-entry behind the ownership gate** (ruled 2026-08-06 — the container-whole retirement purge retires): a step's retirement and a no-step close remove only entries no live step still backs — the same `backedContent` inventory the sweep consults, making it one condition, *three* consumers. The container-whole purge assumed one owning step per card's comment trash, and two sessions over the same card broke it: the second step's retirement — or a mere reopen-and-close that registered nothing — emptied the first step's backing out from under it, silently killing an undo the stack still promised. Under the gate a purge cannot stale a live step by construction; an entry that outlives its owner is collected by whichever consumer runs next (the next retirement, close, open-time sweep, or session end — convergence, not a leak). One carve-out: **an open card window is itself an owner of its card's comment trash** — a retirement firing while the card's window is open defers its purge to that window's close, because entries deleted in the live session are backed by the window's fine steps, which the board-stack inventory cannot see; the close then settles by the same gate (its coarse step becomes the owner, or the no-step close purges the unowned). On Pro the substrate is history: the close commit nets delete-plus-purge to a removal, revert restores it, so purge rides the close flush there as before — purge timing follows the undo substrate's need.
**Comments keep the no-capture rule true — on the window stack** (re-ruled 2026-07-31, superseding the board-stack routing): a comment delete is a move into the card's `comments/.trash/` (01-storage-format.md ▸ Enhanced schema — the materialized-trash pattern one level down), its inverse the ordinary move back, and the step lives on the **card window's own stack** (Rules above) — the board stack never carries a granular comment step, so the old stale-after-close skip scenario cannot arise. **The purge of `comments/.trash/` defers with the coarse step** (re-ruled 2026-07-31, superseding purge-at-close): the coarse close step's undo restores deleted comments, so their backing lives as long as the step does — the purge runs when the coarse step leaves the board stack **cleanly** — undone-and-superseded, or dropped off the end — or when the board session ends; **a stale-skipped step's backing instead survives to board-session end** (ruled 2026-07-31, decoupling skip from purge): the skip banner says nothing was applied, and an irreversible purge riding that gesture would be surprise loss — the skip is exactly when the user may want to inspect what the collision left; crash residue still sweeps at the next card-window open — **and residue is defined by the purge-deferral condition itself** (ruled 2026-07-31): `comments/.trash/` content referenced by a live coarse step on the board stack is a step's backing, not residue — the open-time sweep consults the stack and skips owned content, re-arming when the owning step leaves the stack (which is exactly when the deferred purge wanted to run; one condition, two consumers). Reopening a window can therefore never destroy its prior session's undo backing. Unowned content sweeps as before, armed-then-cleared like every heal memo. **Every purge of `comments/.trash/` is per-entry behind the ownership gate** (ruled 2026-08-06 — the container-whole retirement purge retires): a step's retirement and a no-step close remove only entries no live step still backs — the same `backedContent` inventory the sweep consults, making it one condition, *three* consumers. The container-whole purge assumed one owning step per card's comment trash, and two sessions over the same card broke it: the second step's retirement — or a mere reopen-and-close that registered nothing — emptied the first step's backing out from under it, silently killing an undo the stack still promised. Under the gate a purge cannot stale a live step by construction; an entry that outlives its owner is collected by whichever consumer runs next (the next retirement, close, open-time sweep, or session end — convergence, not a leak). One carve-out: **an open card window is itself an owner of its card's comment trash** — a retirement firing while the card's window is open defers its purge to that window's close, because entries deleted in the live session are backed by the window's fine steps, which the board-stack inventory cannot see; the close then settles by the same gate (its coarse step becomes the owner, or the no-step close purges the unowned). Purge timing follows the undo substrate's need — a future provider with durable history may ride its own flush instead, through the same seam.
## Out of scope
- **Attachment operations, v1** (deferred — ratified 2026-07-27): attach → remove is a clean inverse, but remove-attachment → re-add requires the removed file to survive somewhere (a staging area with a lifecycle — App Support, bounded, its own cleanup rules; possibly shared with 04 ▸ Clipboard's staging). The deferral is the ruling: attachment add/remove registers **no undo step** in v1 (the operations remain, as today, confirmed-or-benign); the staging design pass reopens post-2.0.
- **EchoLedger-synthesized foreign undo** (deferred, wishlist — WISHLIST.md item 6): the ledger already classifies foreign diffs for announcements; it could synthesize inverse operations and push foreign steps onto the stack, narrowing the gap to Pro. Real design needed (ordering vs app steps, attribution, user expectations) — not assumed by this doc.
- **EchoLedger-synthesized foreign undo** (deferred, wishlist — WISHLIST.md item 6): the ledger already classifies foreign diffs for announcements; it could synthesize inverse operations and push foreign steps onto the stack, narrowing the foreign-change gap (now the deferred journal's charter — strategy/01-git-excision.md ▸ Successors). Real design needed (ordering vs app steps, attribution, user expectations) — not assumed by this doc.
## Open questions
+2
View File
@@ -1,5 +1,7 @@
# Git Operations — Extracted Conclusions
**RETIRED 2026-08-08** (strategy/01-git-excision.md): a superseded record — the operations these conclusions shaped were built (forward-restore, the safe checkout, housekeeping), shipped in no release, and excised whole; the stack is recoverable at tag `pre-git-excision`, and the forward-restore *principle* — history only ever moves forward — survives as precedent for whatever durable history the deferred foreign-change journal or the ops service one day offers. Everything below is the record of the extraction as it stood.
**Tier scope: Lanework Pro** (12-editions.md). This doc extracts the settled conclusions from the pathfinder's git-operations analysis (`../../Kanban/AI-ANALYSIS-git-operations.md`, 2026-07-23) so the git milestone has a citable in-repo source. It resolves the design corpus's outstanding tbd — the git-operations doc extraction the Implementation board's root index names as one of two TBDs graduating into work items. The source file catalogued issues and deliberately made no decisions; the decisions were made in 06-history-undo.md and 07-sync-collab.md, and this doc records which of the source's conclusions those docs build on — and which of its leanings later design deliberately settled otherwise. Issue tags (A1, C3, …) are the source file's.
## The forward-restore model (C3, C9) — the load-bearing extraction
+5 -5
View File
@@ -18,15 +18,15 @@ Each document covers one aspect of the design. Within each:
| [03-board-ui.md](03-board-ui.md) | Board window: layout, lanes, cards, styling, templates |
| [04-interactions.md](04-interactions.md) | Selection, drag & drop, keyboard, clipboard, search |
| [05-card-window.md](05-card-window.md) | The card window: Markdown preview/edit, attachments |
| [06-history-undo.md](06-history-undo.md) | Git-backed undo/redo and history — **Pro tier** |
| [07-sync-collab.md](07-sync-collab.md) | Board modes: local-only, git; iCloud Drive warned against — **Pro tier** |
| [06-history-undo.md](06-history-undo.md) | Git-backed undo/redo and history — **retired 2026-08-08** (git excised, strategy/01-git-excision.md; Undo routing migrated to 13) |
| [07-sync-collab.md](07-sync-collab.md) | Board modes and git-rails sync — **retired 2026-08-08 as written**; successor: the ops-based sync service workstream |
| [08-agent-integration.md](08-agent-integration.md) | AI agents as first-class users of the board |
| [09-templates.md](09-templates.md) | Board templates: inventory and definition format |
| [10-accessibility.md](10-accessibility.md) | VoiceOver, text scaling, visual accommodations |
| [11-command-nexus.md](11-command-nexus.md) | The command Nexus — every command and action: bindings, contexts, customizability |
| [12-editions.md](12-editions.md) | The tiers (free / Pro subscription / Teams deferred): one-app distribution, entitlement, provider seam, feature matrix |
| [13-native-undo.md](13-native-undo.md) | macOS-native undo/redo — the free tier's history substrate |
| [14-git-operations.md](14-git-operations.md) | Extracted pathfinder git-operations conclusions: the forward-restore model — **Pro tier** |
| [12-editions.md](12-editions.md) | The tiers: one-app distribution, entitlement, provider seam — the git rows are record (pivot 2026-08-07, excision 2026-08-08) |
| [13-native-undo.md](13-native-undo.md) | macOS-native undo/redo — the history substrate (sole since 2026-08-08); owns Undo routing |
| [14-git-operations.md](14-git-operations.md) | Extracted pathfinder git-operations conclusions**retired 2026-08-08** (superseded record) |
## Deferred design iterations