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
+10 -7
View File
@@ -13,8 +13,9 @@ import Foundation
/// "Duplicate is a full fork, `.trash/` included dropping it would leave the copy's working tree
/// disagreeing with its own copied HEAD". This file does nothing to achieve that: the trash is an
/// ordinary folder under the root, so the tree walk carries it by declining to be clever.
/// - **`.git` comes along** a duplicate of a git board is a fork of its history with only its
/// remote configuration stripped, which is m7's.
/// - **`.git` comes along** a duplicate of a board that has one is a fork of its history
/// verbatim, remote configuration included; a stripped-remote protection was once slated for this
/// (m7-git), never built before the git excision (`strategy/01-git-excision.md`, 2026-08-08).
/// - Timestamps, unknown keys, strays, `CLAUDE.user.md`, attachments: verbatim, for the same reason.
/// **Nothing here reads a board file at all.**
///
@@ -245,11 +246,13 @@ enum BoardDuplicator {
}
BoardTreeCopy.restoreAttributes(from: rootAttributes, onto: destination)
// m7-git: strip the copy's remote configuration "the duplicate keeps `.git` but has its
// remote configuration stripped ... it must not silently push into the original's remote"
// (03-board-ui.md). Remotes only: the repo-local `user.name`/`user.email` survives, so the
// fork keeps its commit identity (06-history-undo.md's identity home). Push-on-commit needs
// nothing here it lives on the registry record, and the copy's record is born fresh.
// Once slated (m7-git) to strip the copy's remote configuration here "the duplicate keeps
// `.git` but has its remote configuration stripped ... it must not silently push into the
// original's remote" (03-board-ui.md) before app-managed git was excised entirely
// (`strategy/01-git-excision.md`, 2026-08-08) with the milestone unbuilt. `.git`, remote
// configuration included, now rides along exactly as verbatim as every other file this walk
// carries; a duplicate of a board sitting in a user's own repo is that user's to manage, same
// as the original was.
return destination
}
+15 -12
View File
@@ -107,9 +107,10 @@ struct BoardWindowHost: View {
/// following walk builds (`BoardRepairRun`, `EchoLedger.adopt`).
///
/// It cannot live anywhere else: the repairs run before a store exists and the receipts have to
/// reach that store's ledger before `beginSession` composes a git board's committer, or the app's
/// own repair commits as `Lanework External`. (Pro's committer, until 12-editions.md PIVOT
/// 2026-08-07 made it every git board's.) Cleared once adopted.
/// reach that store's ledger before the store's own landing reload classifies them arrive late
/// and the repair's own writes would misattribute as foreign, and the announcer would speak about
/// them. (Once fed a git board's auto-committer instead; app-managed git is gone now,
/// `strategy/01-git-excision.md`, 2026-08-08.) Cleared once adopted.
@State private var repairLedger: EchoLedger?
@State private var phase: Phase = .opening
@@ -225,8 +226,10 @@ struct BoardWindowHost: View {
}
// (**The board settings sheet was presented here** between 2026-07-31 and 2026-08-07,
// when the popover/sheet split was reversed: the sheet retired, its contents rehomed
// into the popover's Git tab, and this window has no modal configuration surface at all
// now 03-board-ui.md Board settings sheet, marked retired.)
// into the popover's Git tab itself retired 2026-08-08 with the git excision
// (`strategy/01-git-excision.md`), the git-specific contents gone with it and this
// window still has no modal configuration surface at all, now for either reason
// 03-board-ui.md Board settings sheet, marked retired.)
//
// "The board in front", for the menu items that act on it (`LaneWidthCommands`), and
// beside it the window's own popover flag, which is what File Board Info toggles, its
@@ -382,13 +385,13 @@ struct BoardWindowHost: View {
loading.end()
// **The repair's receipts, into the board's own ledger before the session composes**
// (01: "On Pro boards the repairs drop heal-marked receipts and commit separately as one
// repair commit" every git board's since 12-editions.md PIVOT 2026-08-07, the sentence
// otherwise unchanged). `beginSession` is where that committer is built and started, and the
// committer harvests the ledger it is handed; receipts adopted after that line would be
// receipts the repair commit never sees, and the app's own repair would be authored
// `Lanework External`.
// **The repair's receipts, into the board's own ledger before the session's landing reload
// classifies them.** (01-storage-format.md read, of a now-retired repair-commit path: "On Pro
// boards the repairs drop heal-marked receipts and commit separately as one repair commit"
// every git board's, since 12-editions.md PIVOT 2026-08-07. App-managed git left the app
// entirely on 2026-08-08, `strategy/01-git-excision.md`; there is no repair commit now.)
// Receipts adopted after this line would be receipts the landing reload never classifies, and
// the repair's own writes would misattribute as foreign.
if let repairLedger {
store.echoes.adopt(repairLedger)
self.repairLedger = nil
+11 -10
View File
@@ -195,12 +195,13 @@ final class CardWindowSession: CardSessionFlushing {
func endSession() async {
guard !hasEnded else { return }
hasEnded = true
// pro-m1: this is the boundary the auto-committer coalesces on one commit per Edit
// session, "never per save tick" (06-history-undo.md Rules Auto-commit). The debounced
// saves inside the session are ordinary bracketed writes; what makes them one commit is that
// the committer's own debounce outlives them and this call is where the session is known to
// be over. It is also where the body's *last* fine step joins this window's stack, which is
// why it has to precede the fold below.
// Once the boundary the auto-committer coalesced on too "one commit per Edit session,
// never per save tick" (06-history-undo.md Rules Auto-commit, retired with app-managed
// git, `strategy/01-git-excision.md`, 2026-08-08). What is left is native undo's own version
// of the same idea: the debounced saves inside the session are ordinary bracketed writes, and
// what makes them one step is that `endEditSession` registers exactly one (13-native-undo.md
// Rules) once the session is known to be over. It is also where the body's *last* fine step
// joins this window's stack, which is why it has to precede the fold below.
body.endEditSession()
// **The saves, in the order the comments build fixed**: the inline session's flush, then the
// draft's (`CardComments.endSession`). Both may register their own last fine step, so both
@@ -212,10 +213,10 @@ final class CardWindowSession: CardSessionFlushing {
// This is the one place that knows both halves: the window's stack, which is the session's
// net effect, and the `comments/.trash/` purge, which must not run while a board step's undo
// still restores comments out of it. Registering answers whether the step took the purge on
// and a board whose substrate keeps no steps has already run it by the time that answer comes
// back, which is how a git board keeps purging at the close flush without a word about tiers
// here (and there is none to say since 12-editions.md PIVOT 2026-08-07 the substrate is
// the board's mode, not a subscription).
// and a board whose substrate declines to keep a step (`registerCardSession`'s three `false`
// cases above) has already run the purge by the time that answer comes back. The seam stays
// nil-safe on purpose for whatever substrate registers next (`strategy/01-git-excision.md`
// Reversibility); today the native stack is the only one that ever says yes.
//
// **This window stops owning its card's comment trash first** (ruled 2026-08-06 the
// open-window carve-out). While the window is open the store defers every purge of this
+9 -7
View File
@@ -84,9 +84,10 @@ public extension CardSessionFlushing {
/// call site and supplies the real ones.
///
/// The two flush seams that are `nil` today `editorFlush` and `committerFlush` are named rather
/// than left to be discovered: 02 fixes their relative order ("editor saves before the pending
/// auto-commit"), and the milestone that adds a debounced editor save should have nowhere to put it
/// except the slot that already sits in the right place.
/// than left to be discovered: 02 fixes their relative order (editor saves before any board-level
/// flush), and a future consumer should have nowhere to land except the slot that already sits in
/// the right place. `committerFlush`'s original consumer was the auto-committer, excised 2026-08-08
/// (strategy/01-git-excision.md); the slot outlives it for whatever durable-history flush comes next.
@MainActor
public struct CloseFlushCoordinator {
@@ -121,12 +122,13 @@ public struct CloseFlushCoordinator {
public var storeFlush: () async -> Void
/// The card windows' debounced body saves (05-card-window.md, m6). Runs **before**
/// `committerFlush`: 02 is explicit that editor saves land before the pending auto-commit, so a
/// session's last keystrokes are inside the commit that closes it rather than orphaned in the
/// next one.
/// `committerFlush`: 02 is explicit that editor saves land before any board-level flush, so a
/// session's last keystrokes ride whatever board-level flush follows rather than being orphaned
/// behind it.
public var editorFlush: (() async -> Void)?
/// The pending debounced auto-commit (06-history-undo.md, m7).
/// The board-level flush that ran last the excised auto-committer's slot, permanently `nil`
/// since 2026-08-08 (strategy/01-git-excision.md); see the header for why the slot survives.
public var committerFlush: (() async -> Void)?
// MARK: Step 3 the record
+3 -2
View File
@@ -112,8 +112,9 @@ public struct SettleableSession {
// MARK: - SessionSettleGate
/// **The save-or-discard step**, as one reusable decision procedure (06-history-undo.md Rules
/// Undo restore vs open Edit sessions; Branch switching).
/// **The save-or-discard step**, as one reusable decision procedure 06-history-undo.md Rules
/// Undo restore vs open Edit sessions; Branch switching, retired with the callers that read it
/// (`strategy/01-git-excision.md`, 2026-08-08).
///
/// ### One machinery, every caller, by design
///
+5 -4
View File
@@ -25,10 +25,11 @@ import os
/// rule this file invents:
///
/// - **The two exclusions are top-level only** (`BoardTreeCopy`): `.git` and `.trash/` mean
/// something at a board root and nowhere else. `.git` is skipped so an instantiated board is never
/// silently in git mode (06-history-undo.md's no-silent-auto-init); its actual mode follows 06's
/// nearest-`.git`-wins detection at the destination the save panel chose. `.trash/` is skipped
/// because "a new board isn't born with trash".
/// something at a board root and nowhere else. `.git` is skipped so a template's own repository
/// never rides along into every board it stamps out once phrased as keeping an instantiated
/// board from silently landing in git mode (06-history-undo.md's no-silent-auto-init, retired with
/// app-managed git, `strategy/01-git-excision.md`); there is no mode left to land in today.
/// `.trash/` is skipped because "a new board isn't born with trash".
/// - **Everything else copies verbatim** strays, `CLAUDE.user.md`, a seeded `.gitignore`,
/// attachments, card bodies, unknown keys, line endings (09 Save as Template, "Strays copy
/// through *and* instantiation alike"), and **symlinks as symlinks**, never traversed
+2 -1
View File
@@ -15,7 +15,8 @@ import os
/// touched and what they said before. It also keeps the one rule every step must obey in a single
/// place **an inverse is performed as an ordinary app-mediated write**, through `performWrite`,
/// so an undo brackets the watcher, echoes back through the reload like any other change, refreshes
/// every window on the board, and (on git boards, pro-m1) commits. Undone changes are real writes,
/// every window on the board once it also committed on git boards, pro-m1, before app-managed
/// git was excised (`strategy/01-git-excision.md`, 2026-08-08). Undone changes are real writes,
/// never in-memory reverts.
///
/// ### Values, never live references
+25 -21
View File
@@ -13,13 +13,15 @@ import AppKit
/// exactly how the system's Edit Undo row and the toolbar's nil-target pair (`BoardToolbar`) light
/// up, disable and retitle with no code of the app's own.
///
/// The seam, though, must not be an `NSUndoManager`: a gitless board's stack is one, a Pro git
/// board's is git (12-editions.md The provider seam), and a protocol that vended one could only
/// ever have had a single implementation. So the substrate stays behind `HistoryProviding` and
/// The seam, though, must not be an `NSUndoManager`: a protocol that vended one could only ever have
/// had a single implementation, and the substrate stays behind `HistoryProviding` so a future
/// provider can bind without touching this class (`strategy/01-git-excision.md` Reversibility). So
/// *this* object is the translation one per board session, over whichever provider that session
/// was composed with. A git board inherits the whole command surface (enablement, dynamic titles,
/// Z, the toolbar pair) by binding its provider and changing nothing here, which is what "a user
/// subscribing (or lapsing) relearns nothing" (12) has to mean in code.
/// was composed with. (Once two providers shared the seam this way a gitless board's native stack,
/// a Pro git board's git history, 12-editions.md The provider seam with whichever bound
/// inheriting the whole command surface, enablement, dynamic titles, Z, the toolbar pair, by
/// binding and changing nothing here. App-managed git is gone now, 2026-08-08, and native is the
/// only one left.)
///
/// **And one per open card window**, over that window's own stack (13 Rules two levels, re-ruled
/// 2026-07-31 `CardWindowUndo.manager`): the second level needs precisely the same translation, so
@@ -52,8 +54,8 @@ import AppKit
/// responder chain validates through this object, so answering `false` here disables all of them
/// at once, exactly as the lock's other victims disable through menu validation (02-architecture.md
/// § "The lock's scope"). Putting it in the *provider* would have been the same answer in the wrong
/// place: the stack is not the thing that is locked, the board is, and a Pro session binding the git
/// provider must inherit the rule without reimplementing it.
/// place: the stack is not the thing that is locked, the board is, and whatever provider a session
/// binds a future one included must inherit the rule without reimplementing it.
public final class BoardUndoManager: UndoManager {
/// The substrate this manager is a face for. Strong: the session owns both, and the manager is
@@ -65,7 +67,10 @@ public final class BoardUndoManager: UndoManager {
/// only under locks and on empty stacks the provider follows the board, so 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). The repo-nested board was the last holder of this
/// state and no longer is: that rule was about *git*, and this stack never touches git.
/// state and no longer is: that rule was about *git*, and this stack never touches git. (The
/// distinction the quote draws collapsed entirely with the 2026-08-08 excision,
/// `strategy/01-git-excision.md`: every board is "without app-managed git" now, not just
/// repo-nested ones.)
///
/// The state stays modelled because the seam still admits it a test binds a substrate-less
/// session through `AppModel.makeHistoryProvider` and because an absent substrate is the honest
@@ -73,20 +78,19 @@ public final class BoardUndoManager: UndoManager {
/// toolbar pair, and Z itself go quiet together through the same validation path a lock uses,
/// and nothing can accidentally accumulate in a stack that is not there.
///
/// ### Settable, for exactly one event
/// ### Settable, once for exactly one event now for none
///
/// **Add-git** (06 Rules Detection) is the design's one sanctioned mid-session mode flip:
/// "clicking it flips the open board into git mode immediately". 13's header says what that means
/// here the flip **swaps the substrate**: the mode-none board's native stack is discarded and
/// the git trail seeded from the root commit, the branch-switch discard-and-reseed precedent. The
/// composition root writes the new provider into this property rather than rebuilding this
/// object, so AppKit keeps the identical manager it has already been handed by
/// `windowWillReturnUndoManager` and simply revalidates over a different stack.
/// **Add-git** (06-history-undo.md Rules Detection, retired) was the design's one sanctioned
/// mid-session mode flip: "clicking it flips the open board into git mode immediately", swapping
/// the substrate the mode-none board's native stack discarded for the git trail seeded from the
/// root commit. The composition root wrote the new provider into this property rather than
/// rebuilding this object, so AppKit kept the identical manager it had already been handed by
/// `windowWillReturnUndoManager` and simply revalidated over a different stack.
///
/// (This is *not* a tier flip. 12-editions.md's "an open board finishes with the provider it
/// composed" is about a subscription lapsing, which cannot change a running session's tier at
/// all `BoardSession.tier` is a `let` with no setter. Mode can change, by explicit command,
/// and only in this one direction.)
/// App-managed git left the app entirely on 2026-08-08 (`strategy/01-git-excision.md`), and
/// add-git went with it: nothing left in the app ever assigns this property after composition. It
/// stays a `var` rather than a `let` for the same reason `HistoryProviding` stays a protocol a
/// future substrate swap, mid-session or not, re-binds without re-plumbing this class.
var history: (any HistoryProviding)?
/// Whether the board is refusing writes `BoardStore.isReadOnly`, read through a closure rather
+4 -4
View File
@@ -15,10 +15,10 @@ import Foundation
///
/// **The fine stack**: an ordinary `NativeHistoryProvider`, on *every* board. The steps a card window
/// registers are values-based inverses at the Writer boundary the same shape whatever substrate the
/// board's own history has so a git board's card window still walks its own gestures with the
/// native grammar, and only the *coarse* close unit splits by substrate ("one native board step, or
/// one commit" 06-history-undo.md Undo routing). The split was written as a tier split and is a
/// board split since 12-editions.md PIVOT 2026-08-07; nothing in this type ever read either.
/// board's own history has. (Once only the *coarse* close unit split by substrate "one native board
/// step, or one commit" 13-native-undo.md Undo routing; the split was written as a tier split and
/// became a board split with the 2026-08-07 pivot before the 2026-08-08 excision removed the git half
/// of it outright, `strategy/01-git-excision.md`. Nothing in this type ever read either axis.)
///
/// **The fold**: window close registers "one coarse step ... whose undo restores the card subtree to
/// its session-start state ... and whose redo reapplies the net effect". That net effect is exactly
+23 -18
View File
@@ -80,9 +80,10 @@ public enum HistoryStepOutcome: Equatable, Sendable {
/// holds: move move back ...; rename restore title" and both halves of that write are already
/// in the caller's hands: the before-value *is* the inverse, and the after-value is what the write
/// set (which is also what the staleness predicate compares). A step is therefore that pair, in the
/// design's own terms, and deliberately says nothing about how a stack stores it: a gitless board's
/// stack is `NSUndoManager`-backed and a Pro git board's is git (12-editions.md The provider
/// seam), and neither substrate appears here.
/// design's own terms, and deliberately says nothing about how a stack stores it: the board's stack
/// is `NSUndoManager`-backed, and no substrate appears here. (Once two substrates shared this seam
/// a gitless board's native stack, a Pro git board's git history, 12-editions.md The provider
/// seam before app-managed git left the app entirely, 2026-08-08, `strategy/01-git-excision.md`.)
///
/// ### `name` is the 06 vocabulary, unprefixed
///
@@ -262,27 +263,30 @@ public struct HistoryStep {
/// seam from a window is the one coarse step its close registers.
///
/// `AppModel.BoardSession` is where the board half's ownership lives, and the composition root binds
/// which implementation it gets **following the board, and since the 2026-08-07 pivot the board
/// alone** (re-ruled 2026-07-31; 12-editions.md PIVOT 2026-08-07): a board without app-managed git
/// repo-nested included (re-ruled 2026-07-31) binds `NativeHistoryProvider` (two step stacks over
/// the inverses registered at the Writer boundary), and a git-mode board binds the git provider (undo
/// as forward restore commits over HEAD's first-parent ancestry 06-history-undo.md). Both answers
/// are the same in every tier; the tier used to decide whether git was on the table at all, and no
/// longer does.
/// which implementation it gets **always `NativeHistoryProvider`** now (two step stacks over the
/// inverses registered at the Writer boundary), on every board, in every tier
/// (`AppModel.makeHistoryProvider`). A git-mode board once bound a git provider instead undo as
/// forward restore commits over HEAD's first-parent ancestry, 06-history-undo.md between the
/// 2026-08-07 pivot (12-editions.md PIVOT 2026-08-07, when the tier stopped deciding whether git
/// was on the table at all) and the 2026-08-08 excision that removed the git provider outright
/// (`strategy/01-git-excision.md`).
///
/// ### What this protocol deliberately does not say
///
/// - **No `NSUndoManager`, anywhere in the signature.** It is the native provider's implementation
/// detail, and a seam that vended one would be a seam only one provider could ever satisfy the
/// opposite of the reason the seam exists at all ("the free tier's native undo is the first proof
/// the seam is real", 12 written when native undo was the free tier's; the proof it names is
/// two working substrates, which the pivot leaves standing). AppKit still needs an `UndoManager`
/// to hand the responder chain; that adapter is `BoardUndoManager`, which sits *over* this
/// protocol rather than inside it.
/// - **No persistence promise.** The native stack dies with the session (13); the git provider's
/// survives relaunch because git does (06). Both are honest implementations of these seven members.
/// 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.
/// - **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
/// native's promise is live to keep now.
/// - **No routing.** Which surface Z reaches is focus's answer, not the substrate's
/// (06 Undo routing, tier-independent) `BoardUndoRouting`.
/// (13-native-undo.md Undo routing, tier-independent) `BoardUndoRouting`.
///
/// ### One obligation every implementation shares: retire what you let go of
///
@@ -298,7 +302,8 @@ public struct HistoryStep {
/// nonetheless stands to the session's end, because "the skip banner says nothing was applied, and an
/// irreversible purge riding that gesture would be surprise loss". A substrate that pops stale steps
/// therefore has a place to put them (`NativeHistoryProvider.strandedSteps`); one that never keeps a
/// step is untouched by the distinction, which is why the git provider needed no change.
/// step is untouched by the distinction which is why the git provider, while it existed, needed no
/// change for this rule at all.
///
/// ### And its mirror: say what you are still holding
///
+6 -5
View File
@@ -2,10 +2,10 @@ import Foundation
// MARK: - NativeHistoryProvider
/// The undo substrate of every board without app-managed git: one stack per board session
/// (13-native-undo.md, whose header lost its tier axis with the 2026-08-07 pivot the substrate is
/// the board's mode alone, and git is opt-in per board, so a board nobody opted in keeps this stack
/// for good) **and the substrate of every open card window's stack, on any board** (re-ruled 2026-07-31, the
/// The undo substrate of every board: one stack per board session (13-native-undo.md, whose header
/// lost its tier axis with the 2026-08-07 pivot and then its mode axis with the 2026-08-08 git
/// excision, `strategy/01-git-excision.md` there is no other substrate left to be the board's
/// alternative to) **and the substrate of every open card window's stack, on any board** (re-ruled 2026-07-31, the
/// session-coarsening model): a window's fine-grained gestures are values-based inverses whatever the
/// board's own substrate is, so `CardWindowUndo` holds one of these too. Nothing below knows which of
/// the two it is; both need the same four-line grammar.
@@ -147,7 +147,8 @@ public final class NativeHistoryProvider: HistoryProviding {
public func redo() { cross(.redo) }
/// Session teardown, the add-git substrate swap, a branch reseed every step goes, so every step
/// Session teardown once the add-git substrate swap and a branch reseed too, both gone with
/// app-managed git (`strategy/01-git-excision.md`, 2026-08-08) every step goes, so every step
/// retires: "the purge runs when the coarse step leaves the board stack ... or the board session
/// ends" (13 Interaction with the trash).
///
+12 -10
View File
@@ -12,20 +12,22 @@ import Foundation
/// is explicit about why that is the right shape: "a value-type snapshot comparison is both cheaper
/// and more trustworthy than trying to reconstruct a tree mutation from a stream of paths that may
/// have been coalesced, dropped, or reordered". So this is a comparison of two `BoardModel` values
/// and nothing else no FSEvents paths, no receipts, no git. That is also what makes it work on a
/// no-git board, which 10 requires in as many words ("on no-git boards the same classifier runs
/// without the committer").
/// and nothing else no FSEvents paths, no receipts, no git. That is also what makes it work
/// regardless of git: app-managed git is gone now (`strategy/01-git-excision.md`, 2026-08-08), and 10
/// once required this in as many words for the no-git case specifically ("on no-git boards the same
/// classifier runs without the committer") today there is no other case to distinguish it from.
///
/// ### Why it lives in `LiveStore/` and not beside the announcement
///
/// 10 says announcements "reuse the auto-committer's summarizer" and 06-history-undo.md says the
/// committer synthesizes its commit messages from the same comparison. The committer is Pro-tier
/// work (pro-m1) and does not exist yet, so the summarizer is built here first, in the layer both
/// consumers can reach: the store layer is below the history providers, so it is available to the
/// committer by construction, where a type living inside the git provider would not be reachable
/// from the announcer (12-editions.md The provider seam). It imports
/// 10 once said announcements "reuse the auto-committer's summarizer" and 06-history-undo.md said the
/// committer synthesized its commit messages from the same comparison Pro-tier work (pro-m1) that
/// never shipped before app-managed git was excised entirely (`strategy/01-git-excision.md`,
/// 2026-08-08). The summarizer was built here first anyway, in the layer any future consumer can
/// reach: the store layer sits below the history providers, so it stays available to whatever
/// replaces the committer today the foreign-change journal (`strategy/01-git-excision.md`
/// Successors) by construction. It imports
/// `Foundation` alone and touches no view, no window, and no `NSAccessibility` the phrasing is
/// `AccessibilityPhrases`' job, and pro-m1's message composer will phrase the very same counts
/// `AccessibilityPhrases`' job, and a future message composer will phrase the very same counts
/// differently without either of them knowing about the other.
///
/// The per-category **identity sets** rather than bare tallies are for that second consumer: a
+70 -61
View File
@@ -442,9 +442,10 @@ public final class BoardStore: HealHost {
/// answer to "which ledger is this board's", and a second one would be a second provenance.
///
/// Its consumers today are the announcer's digest and the vanishing-focus sentence, both
/// through `land`. Pro's auto-committer (06-history-undo.md) becomes the second one without
/// this line changing which is why the type lives in `LiveStore/` beside `BoardDiff` rather
/// than inside the git provider.
/// through `land`. The foreign-change journal is slated to become the second one without this
/// line changing (`strategy/01-git-excision.md` Successors) once the auto-committer's job,
/// before app-managed git was excised 2026-08-08 which is why the type lives in `LiveStore/`
/// beside `BoardDiff` rather than inside any one consumer's module.
@ObservationIgnored
public let echoes = EchoLedger()
@@ -668,9 +669,11 @@ public final class BoardStore: HealHost {
/// raised lock above the completion), and the phrase must not survive to be spoken by some
/// later, unrelated reload.
///
/// **`nil` on every free-tier bracket today.** The free tier has no git operations, and the design's
/// examples ("Pulled 3 commits", "Switched to branch 'redesign'") are pro-m1's; the parameter
/// exists so that milestone supplies phrasing rather than re-plumbing the seam.
/// **`nil` on every bracket today.** App-managed git is gone (`strategy/01-git-excision.md`,
/// 2026-08-08) and nothing currently supplies a phrase the design's examples ("Pulled 3
/// commits", "Switched to branch 'redesign'") were git's. The parameter stays so a future
/// wholesale operation (the ops-based sync service `strategy/01-git-excision.md` Successors)
/// can supply one without re-plumbing the seam.
@ObservationIgnored
private var wholesaleCompletion: String?
@@ -1399,12 +1402,13 @@ public final class BoardStore: HealHost {
///
/// - Parameter completion: what to announce when the closing reload lands
/// (10-accessibility.md Live board announcements: "bracketed operations announce once, at
/// completion" "Pulled 3 commits", "Switched to branch 'redesign'"). `nil`, the default, is
/// an operation whose completion is not worth speech, which is **every free-tier bracket
/// today**: no git operations run there, and the two app-initiated writes that do reach disk on
/// their own the loose-file relocation and the legacy-tombstone migration are ordinary
/// `performWrite` calls that already say what they did on the banner strip. The parameter is
/// the seam pro-m1 fills; see `wholesaleCompletion`.
/// completion" "Pulled 3 commits", "Switched to branch 'redesign'", both git's). `nil`, the
/// default, is an operation whose completion is not worth speech, which is **every bracket
/// today**: app-managed git is gone (`strategy/01-git-excision.md`, 2026-08-08), and the two
/// app-initiated writes that do reach disk on their own the loose-file relocation and the
/// legacy-tombstone migration are ordinary `performWrite` calls that already say what they
/// did on the banner strip. The parameter is the seam a future wholesale operation fills; see
/// `wholesaleCompletion`.
///
/// - Throws: `BoardStoreWriteRefusal.readOnlyLocked` if the board is already locked a locked
/// board refuses to *start* wholesale work, not just ordinary writes. Otherwise rethrows
@@ -1484,8 +1488,7 @@ public final class BoardStore: HealHost {
/// Three ways this does nothing, all deliberate: a count below 1 clamps to 1 (a lane spans at
/// least one unit), an id that is not in the snapshot is ignored (the lane vanished under the
/// gesture the reload that removed it is the authority), and a count already equal to what the
/// lane displays writes nothing (a drag that ends where it started must not stamp `modified` or
/// mint a git commit).
/// lane displays writes nothing (a drag that ends where it started must not stamp `modified`).
///
/// Failures are already the banner's: `performWrite` posts every `BoardWriteError` before it
/// rethrows, so the rethrow is swallowed here rather than propagated to a gesture that has no
@@ -1519,7 +1522,7 @@ public final class BoardStore: HealHost {
/// The one commit point every width mechanism shares the edge drag, the context-menu stepper,
/// and the menu items' batch. One `performWrite` bracket whatever the count: one gesture, one
/// app-mediated reload, one commit on git boards (the style batch's rule).
/// app-mediated reload (the style batch's rule).
///
/// **A width landing on 1 removes the `width` key** (03-board-ui.md § Lane, settled the
/// remove-at-default family beside the empty rename's `title` and the None well's
@@ -1674,13 +1677,14 @@ public final class BoardStore: HealHost {
/// row with them.
///
/// **One bracket, whatever the target set's size.** "Choosing a well applies to the whole
/// selection one gesture, one commit on git boards" (§ Controls), so every target's `index.md`
/// is rewritten inside a single `performWrite`: the churn rounds back as one app-mediated reload,
/// and the auto-committer (m7) sees one operation rather than N.
/// selection one gesture, one write" (§ Controls), so every target's `index.md`
/// is rewritten inside a single `performWrite`: the churn rounds back as one app-mediated reload
/// once one operation for the auto-committer too (m7), before app-managed git was excised
/// entirely (`strategy/01-git-excision.md`, 2026-08-08).
///
/// **No-ops are skipped per dimension and per target** `setLaneWidth`'s rule, for its reason: a
/// well clicked twice, or a batch where half the cards are already that colour, must not stamp
/// `modified` or mint a commit on the items that were already right. A dimension whose value is
/// `modified` on the items that were already right. A dimension whose value is
/// already what the gesture asks contributes nothing; a target both of whose dimensions are
/// no-ops is dropped entirely; and a gesture that changes nothing anywhere never opens the
/// bracket at all.
@@ -1883,8 +1887,8 @@ public final class BoardStore: HealHost {
/// The image lands first and the frontmatter second, so a failure to write the picture never
/// leaves the board naming one that is not there. The reverse order would; the two are not atomic
/// together, and this is the ordering that makes the non-atomic half harmless. Both are inside the
/// same bracket, so the churn rounds back as one app-mediated reload and mints one commit on git
/// boards the style batch's rule, one gesture one commit.
/// same bracket, so the churn rounds back as one app-mediated reload the style batch's rule,
/// one gesture one write.
///
/// ### The name is chosen, not minted
///
@@ -2218,7 +2222,7 @@ public final class BoardStore: HealHost {
/// "Committing an empty rename on an existing item removes its `title` key"), rather than
/// writing `title: ""` titles are optional, and the face shows the untitled placeholder.
/// - **An unchanged title writes nothing.** `setLaneWidth`'s rule, for the same reason: an
/// editor opened and dismissed with Return must not stamp `modified` or mint a commit.
/// editor opened and dismissed with Return must not stamp `modified`.
///
/// The folder is re-derived from the *current* snapshot, which is what makes a foreign move
/// mid-rename invisible: the editor follows the UUID, and the write lands wherever the item is
@@ -2319,8 +2323,9 @@ public final class BoardStore: HealHost {
/// Ticks or unticks a Preview task-list checkbox **the app's one write into a card's body**
/// (05-card-window.md Preview), and otherwise an entirely ordinary one: the same
/// `performWrite` bracket, the same banner on failure, the same one-way flow back through the
/// watcher. "A toggle is an ordinary user edit the standard atomic write, auto-committed and
/// undoable on git boards."
/// watcher. "A toggle is an ordinary user edit the standard atomic write." (Once
/// "auto-committed and undoable on git boards" too; app-managed git is gone now,
/// `strategy/01-git-excision.md`.)
///
/// `bodyOffset` is the UTF-8 byte offset the parse handed the renderer (`BodyTask
/// .markerOffset`) and `checked` is the state the user was looking at; both travel to
@@ -2330,9 +2335,10 @@ public final class BoardStore: HealHost {
///
/// **It registers no undo step.** 13-native-undo.md Rules' inventory names the body write it
/// makes undoable precisely "Edit-session body save restore prior body bytes" and a Preview
/// checkbox is not one: it belongs to no session, has no flip to coalesce at, and 05 files it
/// under what is "undoable on git boards", which is the *other* substrate's answer. Registering it
/// here would be extending 13's inventory rather than implementing it.
/// checkbox is not one: it belongs to no session and has no flip to coalesce at. (05 once filed it
/// under "undoable on git boards" instead, the other substrate's answer; app-managed git is gone
/// now, `strategy/01-git-excision.md`, so a checkbox toggle is simply not undoable today.)
/// Registering it here would be extending 13's inventory rather than implementing it.
///
/// **A checkbox in a card that has gone writes nothing** the vanished-target guard every
/// gesture in this file makes, ancestor-walked through `boardItem`: the card window would be
@@ -2356,8 +2362,9 @@ public final class BoardStore: HealHost {
/// flush that closes the window (05-card-window.md Edit).
///
/// An ordinary store write in every mechanical respect: one `performWrite` bracket, so the churn
/// rounds back as a single app-mediated reload (and, on git boards, sits inside the session's
/// one commit see `CardBodyEditSession` for that seam); the banner posts itself on failure;
/// rounds back as a single app-mediated reload (once it also sat inside the session's one commit
/// on git boards see `CardBodyEditSession` for that seam, native-only now since the excision,
/// `strategy/01-git-excision.md`); the banner posts itself on failure;
/// the snapshot is never touched here, because the watcher's reload is what brings the text
/// back.
///
@@ -2605,7 +2612,7 @@ public final class BoardStore: HealHost {
/// name* (§ Board naming) never the "Untitled" placeholder cards and lanes show, and never
/// `title: ""`, which would be a real if blank title with nothing to fall back to.
/// - **An unchanged title writes nothing**, so a popover opened and dismissed with Return
/// neither stamps `modified` nor mints a commit.
/// does not stamp `modified`.
///
/// There is no vanished-target guard, because a board cannot tombstone itself out of its own
/// window (01-storage-format.md § Deletion): the only way this target goes away is the root
@@ -2652,8 +2659,8 @@ public final class BoardStore: HealHost {
/// exactly one file the moved lane's `order`.
///
/// **A drag that ends where it started writes nothing**: `index == from` re-inserts the lane in
/// its own slot, and a no-op must not stamp `modified` or mint a commit the resize drag's
/// rule, and for the same reason.
/// its own slot, and a no-op must not stamp `modified` the resize drag's rule, and for the
/// same reason.
public func moveLane(_ id: ItemID, toIndex index: Int) {
let lanes = snapshot.lanes
guard let from = lanes.firstIndex(where: { $0.id == id }) else { return }
@@ -2730,14 +2737,13 @@ public final class BoardStore: HealHost {
///
/// `moveLane`'s plural, and it exists rather than a loop over it because "one `performWrite`
/// bracket per gesture whatever the set's size" is load-bearing (DRAG-REORDER.md § The drop
/// commits): one app-mediated reload, and on git boards one commit rather than N.
/// commits): one app-mediated reload rather than N.
///
/// The run keeps **board order**, which is the lane level's flatten order a multi-lane drag has
/// no other relative order to preserve.
///
/// A drag that changes nothing writes nothing, stated as the arrangement rather than as a special
/// case: if the strip would render exactly what it renders now, no rank is rewritten and no
/// commit is minted.
/// case: if the strip would render exactly what it renders now, no rank is rewritten.
public func moveLanes(_ ids: Set<ItemID>, toIndex index: Int) {
let lanes = snapshot.lanes
let members = lanes.filter { ids.contains($0.id) }
@@ -2910,8 +2916,7 @@ public final class BoardStore: HealHost {
// produces their `index` is DRAG-REORDER.md's, implemented in `DropSlotMath`).
//
// **One `performWrite` bracket per gesture**, whatever the set's size the style batch's and
// the tombstone batch's rule, for their reason: one gesture, one app-mediated reload, one commit
// on git boards.
// the tombstone batch's rule, for their reason: one gesture, one app-mediated reload.
//
// **`index` always means the same thing**: a position among the destination's *rendered* items
// counted with the dragged run already removed the resting layout's own convention, so the
@@ -2920,7 +2925,7 @@ public final class BoardStore: HealHost {
// must not trap.
//
// **Ranks are inserted, never permuted.** A drop rewrites only the dragged items' `order`, so
// the siblings' files and `modified`, and a git commit stay honest about what actually
// the siblings' files and `modified` stay honest about what actually
// moved. That is the one place these differ from `sortSelection`, which permutes because its
// gesture is a permutation. `Ranks.insertionRanks` answering `nil` is the renumber trigger, and
// the fallback is `moveLane`'s: compact the destination, then place against the fresh ladder.
@@ -2928,8 +2933,8 @@ public final class BoardStore: HealHost {
// **Silent no-ops throughout**, all of them the reload being the authority rather than the
// gesture: a destination lane that is gone or tombstoned (04's "a card is never filed under a
// `deleted:` parent"), a dragged set emptied by a foreign reload, and a drop that lands exactly
// where everything already is (a drag that ends where it started must not stamp `modified` or
// mint a commit the resize drag's rule).
// where everything already is (a drag that ends where it started must not stamp `modified`
// the resize drag's rule).
/// One member of a dragged card set, resolved against the snapshot: **where it is now** a lane,
/// or the board's trash.
@@ -3507,8 +3512,7 @@ public final class BoardStore: HealHost {
/// `FileManager` call in the view: it mutates the card's folder, so the churn has to round back
/// as one *app-mediated* reload (the echo the watcher would otherwise read as a foreign edit),
/// it has to refuse under the read-only lock like every other mutation (`performWrite`'s gate),
/// and its failures have to reach the banner strip like every other write's. On git boards it
/// is also one commit, for free, for the same reason.
/// and its failures have to reach the banner strip like every other write's.
///
/// The guards are `importAttachments`' exactly, and its inverse in every way: **the board
/// container and only it** (`boardItem`), so a trashed card is as unreachable as a deleted one
@@ -3546,12 +3550,13 @@ public final class BoardStore: HealHost {
/// **It registers no undo step**, and unlike its neighbours that is not a deferral: nobody asked
/// for it. The relocation is the app tidying its own house on a reload, not a gesture there is
/// no Z that should follow it, and putting one on the stack would let the next Z undo something
/// the user never did. (It is `renumberVisibleChildren`'s posture: bookkeeping composes no event,
/// 06-history-undo.md Commit messages.)
/// the user never did. (It is `renumberVisibleChildren`'s posture: bookkeeping composes no event
/// once 06-history-undo.md Commit messages' rule, now the change narrator's,
/// `Kanban/Changes/ChangeNarrator.swift`.)
///
/// **One bracket over the whole board's worth of relocation**, so the churn rounds back as a
/// single app-mediated reload and (on git boards) a single commit the style batch's rule,
/// applied to a batch the app started itself. The snapshot is not touched here any more than it
/// single app-mediated reload the style batch's rule, applied to a batch the app started
/// itself. The snapshot is not touched here any more than it
/// is anywhere else: the files move, the watcher notices, the reload lands.
///
/// **The write half re-verifies against disk**: `BoardWriter.relocateLooseFiles` re-reads each
@@ -3728,13 +3733,13 @@ public final class BoardStore: HealHost {
/// Board Move Up / Move Down (/) the within-lane sort (04-interactions.md The map).
///
/// **One `performWrite` bracket**, like every other batch here: one gesture, one app-mediated
/// reload, one commit on git boards.
/// reload.
///
/// **The ranks are permuted, not invented.** The lane's existing `order` values, read in display
/// order, are already a sorted ladder of exactly the right length so the new ordering takes
/// them rung for rung and only the cards whose *position* changed are rewritten. A block stepping
/// past one sibling therefore touches the block plus that sibling and nothing else, which is what
/// keeps `modified` (and, later, a git commit) honest about what actually moved.
/// keeps `modified` honest about what actually moved.
///
/// The one case that ladder cannot serve is **duplicate `order` values**, where display order is
/// decided by the folder-name tie-break (`Ranks.isOrderedForDisplay`) rather than by the rank
@@ -3824,14 +3829,15 @@ public final class BoardStore: HealHost {
/// therefore whether a permanent delete stands an alert between one keystroke and unrecoverable
/// deletion (03-board-ui.md § Trash, "Both confirm exactly where the loss is real").
///
/// **Every board is `true` today**, because every board is history mode *none*: nothing in the
/// app keeps a second copy, so a purge is final everywhere.
/// **Every board is `true`**, because app-managed git is gone (`strategy/01-git-excision.md`,
/// 2026-08-08) and every board is history mode *none*: nothing in the app keeps a second copy, so
/// a purge is final everywhere.
///
// m7-git: git boards answer `false` here "on git boards they act immediately (delete-never-
// forgets)" (06-history-undo.md). Repo-nested boards stay `true` alongside mode none: the app
// manages no history for them either. The named predicate exists now so the committer card
// changes one expression rather than hunting the confirmation logic out of two menu items and an
// alert.
// Once slated for a `false` answer on git boards "on git boards they act immediately
// (delete-never-forgets)" (06-history-undo.md, retired) before the excision closed that door.
// Repo-nested boards stay `true` alongside every other board now: the app manages no history for
// any of them. The predicate stays a predicate rather than a literal at its two call sites
// regardless cheap insurance if a future substrate ever needs a `false` again.
public var purgeIsUnrecoverable: Bool { true }
/// **File Delete and its plain- grammar twin staged by place** (04-interactions.md
@@ -4323,8 +4329,8 @@ public final class BoardStore: HealHost {
/// `comments` is a tolerated stray until the feature consumes the name.
///
/// **One bracket over every displacement the load found**, whatever their levels: the batch is one
/// app-mediated reload and, on git boards, one heal commit the loose-file relocation's rule, and
/// this heal's own memo is board-wide anyway.
/// app-mediated reload the loose-file relocation's rule, and this heal's own memo is board-wide
/// anyway.
///
/// **Displacement, never destruction**, and never a mint: the freed name is left empty and the next
/// gesture that needs it creates the real folder the next delete mints `.trash/`, the next import
@@ -4483,9 +4489,11 @@ public final class BoardStore: HealHost {
// MARK: - The board's noise definition
/// Puts the seeded `.gitignore` on a board that has none, and does nothing at all to a board
/// that has one 06-history-undo.md Repository hygiene's whole scheduling ("a board missing
/// the file gains it by scheduled heal at open (the guide-refresh cadence)", re-ruled
/// 2026-07-31).
/// that has one the scheduled-heal cadence every heal in this file shares (02-architecture.md
/// Components HealScheduler): "a board missing the file gains it by scheduled heal at open (the
/// guide-refresh cadence)", re-ruled 2026-07-31. (06-history-undo.md Repository hygiene once
/// carried this rule under a git-flavored heading; the doc retired with the git excision,
/// `strategy/01-git-excision.md`, 2026-08-08 the noise-gate rule itself did not.)
///
/// **`refreshAgentGuide()`'s twin, deliberately**, down to the shape of this method: the file is
/// a board-root courtesy the app owns, its defect is *presence* rather than anything a tree walk
@@ -4502,7 +4510,8 @@ public final class BoardStore: HealHost {
/// 6), which is what makes a foreign deletion healable: the picture "missing" is restored by the
/// delete, and a standing memo would make that deletion the one thing this could not answer
/// while a failing write still gets exactly one attempt per changed picture rather than one per
/// reload. "Deletion is answered by re-seeding" is 06's own wording.
/// reload. "Deletion is answered by re-seeding" was 06-history-undo.md's own wording; the rule
/// survives the doc's retirement (`strategy/01-git-excision.md`, 2026-08-08).
///
/// **It registers no undo step**, like every heal: nobody asked for it.
public func seedGitignore() {
+3 -2
View File
@@ -320,8 +320,9 @@ extension BoardStore {
/// undone-and-superseded, or dropped off the end or when the board session ends. A **stale
/// skip is not a clean exit** and does not purge (the decoupling ruled 2026-07-31): that step's
/// backing survives to session end instead, where `clear()` retires it and this method finally
/// runs. On a git board the step is never kept, so the retirement fires at the close flush, which
/// is where the purge always ran there ("purge rides the close flush").
/// runs. (On a git board the step was never kept, so the retirement fired at the close flush
/// instead, which is where the purge always ran there "purge rides the close flush"; app-managed
/// git is gone now, `strategy/01-git-excision.md`, so every board takes the native path above.)
///
/// ### Entries, not the container the ownership gate, on this side too
///
+3 -2
View File
@@ -361,8 +361,9 @@ public final class FolderWatcher {
/// reconciling reload of its own nothing is lost by not failing redundantly.
///
/// An unbalanced call `endBracket()` at depth 0 is ignored rather than trapping: the
/// consumer's brackets wrap `do`/`catch` spans over git operations, and a bug there should
/// not take the app down.
/// consumer's brackets wrap `do`/`catch` spans over the write they guard (once git operations
/// among them; app-managed git is gone now, `strategy/01-git-excision.md`), and a bug there
/// should not take the app down.
public func endBracket() {
guard bracketDepth > 0 else { return }
bracketDepth -= 1
+4 -3
View File
@@ -183,9 +183,10 @@ public enum TrashModel {
/// the freight explicitly ("and its 5 cards"), which is 03's own phrasing and says the same
/// thing where the user is already reading.
private static func message(unrecoverable: Bool) -> String {
// m7-git: on a git board the content stays reachable in history, so the second sentence is
// the honest one and the trash's own Delete does not confirm there at all
// (`BoardStore.purgeIsUnrecoverable`).
// Once slated to answer differently on a git board there the content stays reachable in
// history, so the second sentence would have been the honest one before the excision closed
// that door (`strategy/01-git-excision.md`, 2026-08-08) and `unrecoverable` is `true`
// unconditionally (`BoardStore.purgeIsUnrecoverable`).
unrecoverable
? "This can\u{2019}t be undone."
: "The board\u{2019}s history still has them."
+30 -17
View File
@@ -84,7 +84,15 @@ enum AgentGuide {
/// the zero-read minimum (`mkdir` plus one `index.md`, no `order`, no `schema`; it lands at the
/// lane's bottom and the app stamps a real rank on its first touch) while still teaching
/// *writing* `order` as the way to control position, which is the only way to control it.
static let version = 10
/// **v11 rewrites Git for the excision** (strategy/01-git-excision.md, ruled 2026-08-08):
/// app-managed git is gone the app never runs git, and the auto-commit the old section
/// promised no longer exists, so shipping v10's text would document machinery to agents that
/// isn't there. The section now teaches repo-resident etiquette alone the format stays
/// deliberately git-friendly, a board may live in a repository of the user's own, and there an
/// agent stages only its own paths, commits its own changes with clear messages, and leaves the
/// app-maintained files to the app. The stamping section drops its auto-commit clause the same
/// way.
static let version = 11
// MARK: - The version marker
@@ -235,9 +243,8 @@ enum AgentGuide {
/// when the decision called for it.
///
/// **Called inside `BoardStore.performWrite`**, so both halves ride one watcher bracket: the
/// rescue and the guide land as a single app-mediated reload, and (under Pro) as a
/// single honestly-attributed commit rather than a foreign-looking rename followed by an
/// app write (06-history-undo.md Commit messages, "Update agent guide (vN)").
/// rescue and the guide land as a single app-mediated reload rather than as two foreign-looking
/// events.
///
/// The move is `FileManager.moveItem` and nothing else: it preserves the bytes exactly the
/// displaced file may not even be UTF-8 and it **fails rather than overwrite** if
@@ -298,8 +305,8 @@ enum AgentGuide {
}
try BoardWriter.atomicReplace(text: content, at: guideURL, operation: .agentGuide)
// Heal-marked: the guide's refresh is app-initiated work, and its commit is its own
// ("Update agent guide (vN)" already commits alone 06-history-undo.md Commit messages).
// Heal-marked: the guide's refresh is app-initiated work, and the ledger records it as the
// heal it is rather than as anyone's edit.
EchoLedger.current?.markHeal(at: guideURL)
return displaced
}
@@ -420,11 +427,11 @@ enum AgentGuide {
Add `modified-by: <your-name>` (e.g. `modified-by: claude`) to the
frontmatter of every `index.md` you write it attributes the change in the
app and, on git boards, in the auto-commit. The app clears the stamp on its
own writes, so **re-stamp on every write, and after every move**: a bare
folder move rewrites no file, so the moved card arrives unstamped unless you
touch its `index.md` again. When you need exact authorship, commit your
changes yourself instead (see Git below).
app. The app clears the stamp on its own writes, so **re-stamp on every
write, and after every move**: a bare folder move rewrites no file, so the
moved card arrives unstamped unless you touch its `index.md` again. On a
board that lives in a git repository of the user's own, committing your
changes yourself (see Git below) records exact authorship as well.
## Creating a card
@@ -567,14 +574,20 @@ enum AgentGuide {
## Git
Some boards are git repositories because the board lives inside a repo of
yours, or because Lanework Pro manages its history. Two rules when one is:
Lanework itself never runs git: the app manages no repository, makes no
commits, and never reads `.git`. But the format is deliberately
git-friendly one file per card, stable UUID folder names, byte-faithful
rewrites and a board may live inside a repository of the user's own.
When it does:
- **Stage only your own paths** never `git add -A` or `git add .`: a
sweep would commit the user's not-yet-committed changes under your name.
- Committing your changes yourself is fine and gives you exact authorship;
the app follows along. If you don't commit, Lanework Pro auto-commits
your changes as external edits (attributed via `modified-by` when you
stamped it).
- **Commit your own changes, with clear messages** nothing else will
commit them for you, and a semantic message ("Move card 'Fix login' to
Doing") is the history the user will actually read.
- **Leave the app-maintained files to the app** this guide and the
seeded `.gitignore` are rewritten by Lanework when they need to be;
don't edit or delete them, and don't commit changes to the user's other
files that you didn't make.
"""
}
+12 -9
View File
@@ -1060,19 +1060,22 @@ public enum BoardLoader: Sendable {
// MARK: - The earlier-occurrence-wins history seam
/// **Where git path history plugs into the duplicate-id winner rule** (01-storage-format.md
/// **Where git path history once plugged into the duplicate-id winner rule** (01-storage-format.md
/// § Fractal layout Rules: "on git boards, the path history already tracks outranks the
/// newcomer (both tracked: the path that entered history first)").
/// newcomer (both tracked: the path that entered history first)") kept as a seam, unfilled,
/// since app-managed git was excised entirely (`strategy/01-git-excision.md`, 2026-08-08).
///
/// A seam rather than an implementation because the first rung of that ladder is unreachable
/// without git: the free tier runs no git machinery at all (12-editions.md The inert posture),
/// so the loader consults an injected ranker and falls through to birth date and traversal order
/// when there is none which is every free-tier board, and every Pro board without a repo.
/// A seam rather than an implementation because the first rung of that ladder was unreachable
/// without git even before the excision: the free tier ran no git machinery at all
/// (12-editions.md The inert posture), so the loader consults an injected ranker and falls
/// through to birth date and traversal order when there is none which is every board today, no
/// exceptions.
///
/// Deliberately one closure and no protocol: the loader asks one question "how early did this
/// path enter history" and pro-m1's implementation answers it from `git log --diff-filter=A
/// --follow`-shaped plumbing behind the provider seam. `nil` means "untracked, or no history
/// here", which the rule reads as *outranked by anything tracked*.
/// path enter history" and pro-m1's implementation once answered it from `git log
/// --diff-filter=A --follow`-shaped plumbing behind the provider seam, before that plumbing left
/// with the excision. `nil` means "untracked, or no history here", which the rule reads as
/// *outranked by anything tracked*; nothing installs a ranker today, so every path reads `nil`.
///
/// - Parameter rank: keyed by the occurrence's **board-root-relative path**, which is what a
/// repo's path history knows; lower is earlier.
+12 -8
View File
@@ -585,9 +585,9 @@ public enum BoardWriter: Sendable {
///
/// - **`.DS_Store`** the Finder writes one into every folder a user looks at, and on a board
/// that means one per lane and one per card, each churning as icons and window positions move.
/// On a git board it would be committed by the whole-tree stage, forever, under the user's own
/// name; on every board it is now also the plainest thing the loose-file heal must not treat as
/// a card's attachment.
/// Once committed by the whole-tree stage on a git board, forever, under the user's own name;
/// app-managed git is gone now (`strategy/01-git-excision.md`), and on every board it is the
/// plainest thing the loose-file heal must not treat as a card's attachment.
/// - **`.*.lanework-*`** this file's own temp pattern, and the one entry the app is entitled to
/// have an opinion about: `atomicReplace` writes `.<name>.lanework-<uuid>` beside its target,
/// so a crashed write's residue is the app's litter rather than the user's file. The two must
@@ -1890,7 +1890,8 @@ public enum BoardWriter: Sendable {
/// `updateIndex`'s closure cannot. Everything else is identical and deliberately so read
/// fresh from disk, refuse an uneditable frontmatter shape, edit, stamp `modified` and clear
/// `modified-by`, replace atomically. A toggle is "an ordinary user edit the standard atomic
/// write, auto-committed and undoable on git boards" (05), not a special case of anything.
/// write" (05) once "auto-committed and undoable on git boards" too, before app-managed git was
/// excised (`strategy/01-git-excision.md`, 2026-08-08) not a special case of anything.
///
/// ### The offset, and why it is re-checked
///
@@ -2085,7 +2086,7 @@ public enum BoardWriter: Sendable {
///
/// Text identical to what is already on disk writes nothing and returns `false` `writeBody`'s
/// untouched gate, applied to the whole file instead of the body span. Apply on a buffer the user
/// only read must not churn `mtime`, wake every watcher, and (on git boards) mint an empty commit.
/// only read must not churn `mtime` or wake every watcher.
///
/// - Returns: `true` when bytes were written, `false` when the file already read exactly like
/// `text`.
@@ -2591,9 +2592,11 @@ public enum BoardWriter: Sendable {
/// reminting it would change an identity for no reason at all the vanished-duplicate race,
/// read from the surviving side.
///
/// **Heal-marked**, because the app started it on its own: the receipt is what splits the remint
/// **Heal-marked**, because the app started it on its own: once the receipt that split the remint
/// into its own commit on git boards, named for the Repair verb (06-history-undo.md Commit
/// messages). Undo never sees it heals are not gestures (13-native-undo.md).
/// messages, retired with app-managed git, `strategy/01-git-excision.md`) the receipt still
/// exists for whatever consumes it next. Undo never sees it heals are not gestures
/// (13-native-undo.md).
///
/// - Parameter duplicate: the withheld occurrence, `path` relative to `boardRoot` so the write
/// lands wherever the board lives *now*.
@@ -3020,7 +3023,8 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
/// The board-root `.gitignore` being seeded at creation, at instantiation, and by the
/// scheduled heal that gives a board born before the ruling the file it now needs
/// (06-history-undo.md Repository hygiene, re-ruled 2026-07-31; `seedGitignoreIfAbsent`).
/// (06-history-undo.md Repository hygiene, retired with app-managed git,
/// `strategy/01-git-excision.md`; `seedGitignoreIfAbsent`).
///
/// **No payload**, for `.agentGuide`'s reason: there is one such file per board, its name is
/// fixed, and it is not an item with a title. Its own case beside `.agentGuide` rather than a
+11 -7
View File
@@ -4,15 +4,19 @@ import Foundation
/// one, and asked one question: is this board-relative path ignored?
///
/// The file outgrew git on 2026-07-31 (01-storage-format.md § Fractal layout Rules, "`.gitignore`
/// is the noise gate"; 06-history-undo.md Repository hygiene): it is now the one definition of
/// is the noise gate"; 06-history-undo.md Repository hygiene, retired with app-managed git,
/// `strategy/01-git-excision.md`): it is now the one definition of
/// noise the **loose-file relocation heal** obeys, on every board, git or not. A file matching it
/// keeps the ordinary stray posture "skipped, preserved verbatim, logged, never relocated, never
/// announced" and "the exclusion list is exactly that file, nothing hardcoded". On a Pro board the
/// same file governs the committer through libgit2, so ignored noise neither relocates nor commits:
/// one definition, two consumers. This type is the app's half of that, because the load path cannot
/// reach for libgit2 the free tier opens a board without so much as a `fileExists` under `.git`
/// (12-editions.md The free tier and `.git`), and the heal runs on boards that have no repository
/// at all.
/// announced" and "the exclusion list is exactly that file, nothing hardcoded". (On a Pro board the
/// same file once governed the committer through libgit2 too, so ignored noise neither relocated nor
/// committed one definition, two consumers; app-managed git is gone now, so the heal is the only
/// consumer left.) This type is the app's half of that once split from libgit2 because the load
/// path couldn't reach for it while the committer did; libgit2 left the project entirely with the
/// git excision (`strategy/01-git-excision.md`, step 6), so today there is no other implementation to
/// duplicate at all. The free tier always opened a board without so much as a `fileExists` under
/// `.git` (12-editions.md The free tier and `.git`), and the heal runs on boards that have no
/// repository at all.
///
/// **Pure: no I/O, no filesystem, no `URL`.** It parses text and answers about paths. Reading the
/// bytes is `BoardLoader.ignoreRules(atBoardRoot:)`' job, once per walk the loader stays a pure
+8 -4
View File
@@ -878,8 +878,10 @@ public enum IntegrityRules: Sendable {
/// The filesystem birth date (`.creationDateKey`), `nil` when it cannot be read the
/// **second** rung of the earlier-occurrence-wins ladder.
public let birth: Date?
/// Where the git path history places this path, `nil` when the board has no history or the
/// path is untracked the **first** rung, injected through `BoardLoader.IdentityHistoryRanker`.
/// Where a history ranker once placed this path `nil` unconditionally today, since
/// app-managed git was excised entirely (`strategy/01-git-excision.md`, 2026-08-08) and
/// nothing installs `BoardLoader.IdentityHistoryRanker` any more. The **first** rung when
/// populated; nil-safe by design, so the ladder falls straight through to birth date now.
public let historyRank: Int?
public init(
@@ -958,8 +960,10 @@ public enum IntegrityRules: Sendable {
/// folder and often the tracked one, so every other rung would withhold the very card the user
/// just restored and render its ghost instead. The heal remints the trashed occurrence. The same
/// preference governs a trashed lane sharing a live lane's UUID.
/// 1. **Git path history** (`historyRank`): both tracked, the path that entered history first
/// wins; one tracked, it outranks the newcomer outright.
/// 1. **History rank** (`historyRank`): once git path history both tracked, the path that
/// entered history first wins; one tracked, it outranks the newcomer outright. Always `nil`
/// today, no ranker installed since the git excision (`strategy/01-git-excision.md`,
/// 2026-08-08); the ladder falls through to rung 2.
/// 2. **Filesystem birth date** (`birth`): the older folder wins. Only consulted when *both*
/// dates are readable and they differ one unreadable date is no comparison at all.
/// 3. **Deterministic traversal order**, which is `occurrences`' own order and therefore the
+3 -1
View File
@@ -215,7 +215,9 @@ private struct BannerRowView: View {
switch control {
case let .cancel(cancel):
// Cancel appears on safe copies only (02, settled): it means "remove the partial copy,
// nothing lost". Git brackets pass no closure and therefore get no button.
// nothing lost". Wholesale brackets that pass no closure get no button once git's,
// before the excision (`strategy/01-git-excision.md`, 2026-08-08); no caller passes one
// today.
Button(control.label, action: cancel)
.buttonStyle(.link)
.font(.callout)
+6 -4
View File
@@ -93,11 +93,13 @@ extension BoardStore {
/// broader lockdown that already covers these two items.
/// - **The board popover's fields** are covered by disabling while the popover is open at all
/// coarser than per-field focus, but it is a configuration surface (04's carve-out) and no lane
/// move belongs under it. That clause covers **every** configuration field the app has again since
/// move belongs under it. That clause covered **every** configuration field the app had since
/// 2026-08-07: the 2026-07-31 split moved most of them to a board settings sheet, whose flag this
/// function read as a third disjunct, and the reversal brought them back branch creation, commit
/// identity, and pro-m2's remote URL and credentials all live in the popover's Git tab, under the
/// popover's own open-at-all rule.
/// function read as a third disjunct, and the reversal brought them back under the popover's own
/// open-at-all rule branch creation, commit identity, and pro-m2's remote URL and credentials
/// among them, all in the popover's Git tab at the time. The Git tab itself retired with
/// app-managed git (`strategy/01-git-excision.md`, 2026-08-08); the popover's open-at-all rule
/// now guards only `BoardInfoTabView`'s ordinary fields.
/// - **The search field** is per-focus and exact (`BoardSearchPresentation.isFocused`), which it has
/// to be: the field's own rule is that board commands *stay enabled* while it holds the keyboard
/// (04 § Search), so these two are the narrow exception to it and nothing coarser would do.
+4 -3
View File
@@ -43,9 +43,10 @@ extension NSToolbarItem.Identifier {
/// `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 board has undo in every tier**, so there is no tier-shaped
/// disablement to write: 03's parenthetical about boards without undo is 06's *git* substrate, which
/// a board binds by being in git mode on any tier since 12-editions.md PIVOT 2026-08-07, and
/// under a Pro subscription before it.
/// disablement to write: 03's parenthetical about boards without undo was 06's *git* substrate,
/// which a board once bound by being in git mode on any tier since 12-editions.md PIVOT
/// 2026-08-07, and under a Pro subscription before it, until app-managed git left the app entirely
/// (`strategy/01-git-excision.md`, 2026-08-08) and there stopped being a substrate to bind.
///
/// 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
+4 -4
View File
@@ -75,9 +75,9 @@ public final class CardBodyPresentation {
/// is the type every path that *enters* Edit already holds, so attaching the announcement to the
/// flip makes "always" true by construction rather than by three call sites remembering.
///
/// Filled in by the window with its edit session's `beginEditSession()`. Its consumer is pro-m1's
/// auto-committer, which stages around the card's folder for as long as the session stands
/// (06-history-undo.md Rules Auto-commit).
/// Filled in by the window with its edit session's `beginEditSession()`. Its original second
/// consumer the auto-committer's stage-around left with the git excision (2026-08-08,
/// strategy/01-git-excision.md); the session bookkeeping it drives is its own reason now.
public var beginEdits: (() -> Void)?
/// Whether the opening rule has already run for this window.
@@ -97,7 +97,7 @@ public final class CardBodyPresentation {
hasOpened = true
mode = CardBodyMode.opening(body: body)
// A card that opened straight into Edit because its body was empty is in a session exactly
// like one the user pressed E in, and the committer has to stage around it either way.
// like one the user pressed E in, and the session bookkeeping applies either way.
if mode == .edit { beginEdits?() }
return mode
}
+2 -2
View File
@@ -146,8 +146,8 @@ public final class CommentEditSession {
/// **Save, or ** the session's commit point (05 The comments column). Flushes and ends.
///
/// A named call rather than a bare `flush()` for `CardBodyEditSession.endEditSession()`'s reason:
/// this is the boundary a Pro auto-commit coalesces on, one commit per session and never per save
/// tick (06-history-undo.md Rules Auto-commit).
/// this is the session's one settlement boundary the excised auto-committer coalesced on it
/// (one commit per session, never per save tick), and any future history consumer would too.
@discardableResult
public func commit() -> Bool {
guard !hasEnded else { return false }