Realign code with the 2026-07-31 findings-resolution rulings
The full bullet list from Implementation card bf080d9a — both ruling batches, including the three appended mid-session by16ef377: - Restore subjects compose the inverse, never nest: crossing "Undo: S" emits "Redo: S" and vice versa; parity, not stack depth, reads a legacy double prefix (GitHistoryProvider.restoreSubject). - Git-operation failures join the one-shot failure banner tier: BannerCenter.GitFailureBanner (undo/redo/branchSwitch/addGit), error tone at failure rank merged with write one-shots by recency; the postLoss compromise is retired at both AppModel wirings. - order/schema optional below the board root: append-at-end reading (ordered siblings first, folder-name tie-break among the order-less), schema reads 1, both coerce-tier logged; the root keeps its requirements. Ranks.resolvedOrders materializes finite ranks so models and placement math stay untouched; first Writer rewrite stamps a real rank on touch, placement against an order-less sibling stamps that sibling inline in the same bracket. Agent guide v10 teaches optional keys and zero-read filing. Hostile-YAML order shapes become coercion tests; Fixtures/Valid/optional-keys.kanban replaces the four retired Malformed boards. - .gitignore is the relocation-heal noise gate: GitignoreRules pure matcher (standard semantics, board-root file only), loader consults it once per walk so matched loose files keep the stray posture; seeded (.DS_Store + .*.lanework-*) at board creation and template instantiation, healed in when missing at open — repo-nested included; empty file honored, existing files never edited; the committer's obedience via libgit2 status is pinned by test. - Comments crash-residue sweep gates on step ownership: HistoryStep derives backing from its own undo expectations, backedContent unions both stacks, the sweep purges per-entry only what no live step owns. - Skip-purge decoupled (16ef377): a stale-skipped coarse step strands whole in NativeHistoryProvider.strandedSteps — still backing, retired only at session end; clean exits purge as before. - Coarse close step named "Changes to '<card>'"; the fine body-edit wording never leaks onto the board menu. - Branch-switch settle clears every open card window's fine stack on Save All and Discard alike; the empty fold registers no coarse step. - Close flush awaits its covering snapshot (quiesce + one generation bump, 1s bound), and an explicit flush now queues behind an in-flight one instead of skipping — the audit-caught interleaving could lose a close flush permanently when the debounce fired inside the close sequence; regression tests force both races. - Commit comment bullets sort chronologically by created, not UUID. - The production-unwired CardBodyEditSession.editSessionDidChange seam is deleted with its seam-only tests. - Composition-root pins: beginSession composes the committer with the store's own EchoLedger and binds the announcer (the miswire class). - Deliberate 06 conformance pass over every 2026-07-31-tagged sentence: fixed Change-custom-key subjects (the retired named generic was the only producer), the unbuilt Replace attachment vocabulary, heal commits now authored Lanework Integrity, the config reader scopes identity to plain [user] sections, add-git re-runs detection at create (a stale mode-none could initialize inside the user's repo), and add-git failures answer at the form or the banner. Structural residue filed on the Redesign board. 2554 tests / 439 suites green. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -235,8 +235,15 @@ struct BoardGitControls: View {
|
||||
// MARK: - Commit identity
|
||||
|
||||
/// **The name and email that repo-local `.git/config` carries** (06-history-undo.md ▸ Interaction
|
||||
/// with external writers: "The board popover's git section exposes name/email fields that write that
|
||||
/// repo-local config — the setting *is* the file, portable to any git client, per-board by nature").
|
||||
/// with external writers: "The board settings sheet's identity section … exposes name/email fields
|
||||
/// that write that repo-local config — the setting *is* the file, portable to any git client,
|
||||
/// per-board by nature").
|
||||
///
|
||||
/// **Its home is the sheet, and the sheet is not built yet.** The 2026-07-31 popover/sheet split moved
|
||||
/// every setup-shaped control — add-git, branch creation, these fields, remote and credentials — out
|
||||
/// of the popover and into a board settings sheet (03-board-ui.md ▸ Board settings sheet), leaving the
|
||||
/// popover the daily face. This view is the fields, hosted where they were; the sheet's card moves
|
||||
/// them, along with the visibility-scoped poll below, which 06 says "rides with the fields".
|
||||
///
|
||||
/// ### The placeholder is the whole of the identity rule made visible
|
||||
///
|
||||
@@ -270,8 +277,11 @@ private struct BoardGitIdentityFields: View {
|
||||
case email
|
||||
}
|
||||
|
||||
/// How often an open popover re-reads the config file. Slow enough to be free, fast enough that a
|
||||
/// terminal `git config user.email …` shows up while the user is still looking at the popover.
|
||||
/// **The fields re-read the config at 2 s while the surface is visible** (06 ▸ Interaction with
|
||||
/// external writers, blessed 2026-07-31): "the watcher never delivers `.git`, so no board event
|
||||
/// can carry a terminal-side config edit — the unfocused-resync courtesy needs its own signal, and
|
||||
/// a visibility-scoped poll is the 15 s paused-state re-read's shape at sheet cadence (a focused
|
||||
/// field keeps its keystrokes; dismissing the sheet stops the poll)."
|
||||
private static let pollInterval: Duration = .seconds(2)
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -424,6 +424,13 @@ private struct BoardGitAddAction: View {
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
// **The form add-git answers at** (06 ▸ Interaction with external writers, ruled 2026-07-31 —
|
||||
// "Form-anchored operations answer at the form first"; its container moved to the board
|
||||
// settings sheet in the 2026-07-31 split, and these two lines are what that sheet re-points).
|
||||
// Appearing claims the inline surface; disappearing gives it up, which both dismisses the
|
||||
// stale error and sends any answer still in flight to the banner instead of to nobody.
|
||||
.onAppear { git.noteFormVisible(true) }
|
||||
.onDisappear { git.noteFormVisible(false) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,18 @@ import Observation
|
||||
/// `NSUndoManager` of its own, which is what makes it session-scoped). What lives here is the other
|
||||
/// half of 05 ▸ Edit's undo sentence: the **session**, whose end is the effective Save.
|
||||
/// `endEditSession()` is that moment — the Edit→Preview flip, raw-source entry, or the window
|
||||
/// closing — and it is deliberately a named call rather than a side effect of `flush()`, because
|
||||
/// pro-m1's auto-commit coalesces exactly here: every debounced tick inside one session rides its
|
||||
/// own `performWrite` bracket, and the committer's rule is one commit per *session*, "never per save
|
||||
/// tick" (06-history-undo.md ▸ Rules ▸ Auto-commit). In the free tier there is no committer, so
|
||||
/// the two calls do the same work today; the seam is what keeps them from having to be pulled apart
|
||||
/// later.
|
||||
/// closing — and it is deliberately a named call rather than a side effect of `flush()`, because it
|
||||
/// is where this session's **one undo step** is registered: every debounced tick inside a session
|
||||
/// rides its own `performWrite` bracket, and 13-native-undo.md's rule is one step per session, never
|
||||
/// per save tick (`registerUndo`).
|
||||
///
|
||||
/// **The commit boundary is no longer here** (06-history-undo.md ▸ Rules ▸ Auto-commit, widened
|
||||
/// 2026-07-31 — "Board history sees card-window sessions, not gestures"): the committer's
|
||||
/// stage-around used to open and close with this flip, and it now opens with the *window* and
|
||||
/// releases when the window's session ends, so a comment posted with the body in Preview is inside
|
||||
/// the same one commit as the body. What this type still owes that model is `isEditing`, read as
|
||||
/// unsaved-content state rather than announced (`CardWindowHost`); the announcement seam it used to
|
||||
/// wire went with the widening.
|
||||
@MainActor
|
||||
@Observable
|
||||
public final class CardBodyEditSession {
|
||||
@@ -110,23 +116,6 @@ public final class CardBodyEditSession {
|
||||
@ObservationIgnored
|
||||
public var registerUndo: ((_ priorBody: String, _ newBody: String) -> Void)?
|
||||
|
||||
/// **The session boundary, announced** — called with `true` when an Edit session opens and
|
||||
/// `false` when it ends, and with nothing in between.
|
||||
///
|
||||
/// **No longer the committer's stage-around boundary** (06-history-undo.md ▸ Rules ▸ Auto-commit,
|
||||
/// widened 2026-07-31 — "Board history sees card-window sessions, not gestures"): the exclusion
|
||||
/// used to open and close with this flip, and it now opens with the *window* and releases when its
|
||||
/// session ends, so a comment posted with the body in Preview is inside the same one commit as the
|
||||
/// body. `CardWindowHost` therefore no longer wires this to anything, and the Edit→Preview flip is
|
||||
/// a save point rather than a commit point.
|
||||
///
|
||||
/// The seam stays, unwired, because it is the only announcement of the boundary this type makes
|
||||
/// and the ordering it carries — that the flush precedes the announcement — is a property worth
|
||||
/// keeping proved (`AutoCommitTests`). A closure for `save`'s reason exactly: this type is a
|
||||
/// buffer and a clock, and it stays testable by having no idea what a repository is.
|
||||
@ObservationIgnored
|
||||
public var editSessionDidChange: ((_ isEditing: Bool) -> Void)?
|
||||
|
||||
/// What disk said before this session's **first** landed save — the step's before-value, held
|
||||
/// from the first write until the session ends.
|
||||
///
|
||||
@@ -194,16 +183,16 @@ public final class CardBodyEditSession {
|
||||
/// because its card's body was empty (`CardBodyMode.opening(body:)`).
|
||||
///
|
||||
/// Idempotent, because the mode can be re-asserted by a menu validation pass or a re-published
|
||||
/// focus value, and a second announcement would register a session that is already registered.
|
||||
/// focus value, and `isEditing` is observed: assigning `true` over `true` would still notify, and
|
||||
/// the notification is a view rebuild for a session that never changed (`adopt(diskBody:)`'s
|
||||
/// equal-assignment note, one property along).
|
||||
public func beginEditSession() {
|
||||
guard !isEditing else { return }
|
||||
isEditing = true
|
||||
editSessionDidChange?(true)
|
||||
}
|
||||
|
||||
/// The end of one Edit session — the flip back to Preview, raw-source entry, or the window
|
||||
/// closing. Flushes, and marks the boundary pro-m1's auto-commit coalesces on (see the type's
|
||||
/// doc comment).
|
||||
/// closing. Flushes, then registers the session's one undo step (see the type's doc comment).
|
||||
@discardableResult
|
||||
public func endEditSession() -> CardBodyWriteOutcome {
|
||||
let outcome = flush()
|
||||
@@ -216,14 +205,9 @@ public final class CardBodyEditSession {
|
||||
registerUndo?(origin, disk)
|
||||
}
|
||||
sessionOriginBody = nil
|
||||
// **Last**, after the flush and after the undo step: the committer's nudge must find the
|
||||
// session's final bytes already on disk, or the commit it arms would carry the file as it
|
||||
// stood one keystroke ago. Guarded on `isEditing` so a window closing from Preview — which
|
||||
// calls this too, and should — announces nothing.
|
||||
if isEditing {
|
||||
isEditing = false
|
||||
editSessionDidChange?(false)
|
||||
}
|
||||
// Guarded on `isEditing` for `beginEditSession`'s reason — a window closing from Preview
|
||||
// calls this too, and should, but has no session flag to move.
|
||||
if isEditing { isEditing = false }
|
||||
return outcome
|
||||
}
|
||||
|
||||
@@ -243,10 +227,7 @@ public final class CardBodyEditSession {
|
||||
cancelPending()
|
||||
text = disk
|
||||
sessionOriginBody = nil
|
||||
if isEditing {
|
||||
isEditing = false
|
||||
editSessionDidChange?(false)
|
||||
}
|
||||
if isEditing { isEditing = false }
|
||||
}
|
||||
|
||||
/// `DirtyBufferGuard`'s `attemptSave`: the same flush, with a real failure raised instead of
|
||||
|
||||
@@ -105,6 +105,14 @@ public final class CardComments {
|
||||
|
||||
/// The crash-residue sweep, run once when the window opens —
|
||||
/// `BoardStore.sweepCommentTrashResidue(inCard:)`.
|
||||
///
|
||||
/// **It is a sweep of *residue*, not of the trash** (13-native-undo.md ▸ Interaction with the
|
||||
/// trash, ruled 2026-07-31): content a live board step still restores from is that step's backing
|
||||
/// and is left standing, so reopening a card can never destroy the last session's undo. **Nor the
|
||||
/// bytes a collision left**, since the same day's skip-purge decoupling: a coarse step popped as
|
||||
/// stale applied nothing and destroys nothing, and what it was holding stands until the board
|
||||
/// session ends. The gate lives store-side, where the stack is; this seam is unchanged and still
|
||||
/// called exactly once, by `open()`.
|
||||
@ObservationIgnored
|
||||
public var sweepTrashResidue: (() -> Void)?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user