Memoize the reload parse and short-circuit value-equal snapshots
The loader gains a ParseMemo — the previous walk's parsed documents keyed by root-relative path, trusted on the git-index heuristic (mtime + size, no hashing) and passed as an input so the loader stays stateless. A hit skips exactly one file read; schema, order, coercions, dedupe, and every directory listing run fresh, so memoized and cold walks are output- identical (golden-corpus equivalence suite). Entries record only past the schema gate, so a defect can never be answered from the memo. The store skips the snapshot assignment wholesale when the fresh model is value-equal — no @Observable churn, no render pass, no snapshotGeneration bump — and a new landedReloads counter carries walk-completion for the three consumers whose subject is the walk, not the snapshot: the card window's comment thread, the comment search index, and the auto-committer's covering gate (which now counts a completed walk as covering even when nothing changed). Warnings and defects move on their own equality; failed reloads bump neither counter. An injectable ParseCounter makes the single-file-echo claim a test. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -142,14 +142,21 @@ public final class GitAutoCommitter {
|
||||
@ObservationIgnored
|
||||
public var awaitReloadQuiescence: (@MainActor () async -> Void)?
|
||||
|
||||
/// **Which generation the board `currentSnapshot` answers with is at** —
|
||||
/// `BoardStore.snapshotGeneration`, incremented by every landed reload.
|
||||
/// **How many tree walks the board has landed** — `BoardStore.landedReloads`, incremented by
|
||||
/// every reload that completed with a snapshot in hand.
|
||||
///
|
||||
/// **The walk, not the applied snapshot**, and the distinction is load-bearing since 2026-07-31:
|
||||
/// a reload whose tree turned out to be value-equal skips the snapshot assignment and its counter
|
||||
/// (02-architecture.md § Live-reload resilience), and a gate watching *that* counter would sit out
|
||||
/// its whole deadline on a flush whose covering walk had already landed. What covers a flush is a
|
||||
/// walk that started after its writes reached disk, and a
|
||||
/// completed walk covers them whether or not it found anything different to show.
|
||||
///
|
||||
/// `nil` — the closure absent, or answering `nil` because the store has gone — means there is no
|
||||
/// snapshot to be outrun by, and the covering await becomes the no-op it is on every storeless
|
||||
/// committer.
|
||||
@ObservationIgnored
|
||||
public var snapshotGeneration: (@MainActor () -> Int?)?
|
||||
public var landedReloads: (@MainActor () -> Int?)?
|
||||
|
||||
/// **How long an explicit flush waits for its covering reload** before composing from the snapshot
|
||||
/// it already has.
|
||||
@@ -563,7 +570,7 @@ public final class GitAutoCommitter {
|
||||
/// after the later of them — 06's own "practical cushion", doing the job it is enough for — and
|
||||
/// `noteWillWrite()` cannot await at all, being the synchronous flush-before-overwrite.
|
||||
private func awaitCoveringSnapshot() async {
|
||||
guard holdsUncoveredWrites, let read = snapshotGeneration else { return }
|
||||
guard holdsUncoveredWrites, let read = landedReloads else { return }
|
||||
await awaitReloadQuiescence?()
|
||||
// Re-read the gate: the quiescence may itself have been the covering landing.
|
||||
guard holdsUncoveredWrites, let base = read() else { return }
|
||||
|
||||
Reference in New Issue
Block a user