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:
2026-08-01 11:38:25 -04:00
parent 5e6417e749
commit 988a7245a3
11 changed files with 1085 additions and 70 deletions
+6 -1
View File
@@ -61,9 +61,14 @@ public struct CommentSearchTarget: Sendable, Equatable {
/// 04 says "kept fresh by the same FSEvents stream while a query is active". The store has no
/// changed-path channel `FolderWatcher` reports only *that* the tree changed (02-architecture.md),
/// and the reload seam turns that into two snapshots rather than a path list so the freshness signal
/// available today is `BoardStore.snapshotGeneration`: **while a query is active, a landed reload
/// available today is `BoardStore.landedReloads`: **while a query is active, a landed reload
/// re-sweeps**. That is coarser than the ruling asks for (it re-reads threads a reload may not have
/// touched) and it is bounded by the same thing that bounds the reload itself, the watcher's debounce.
///
/// The counter is the *walk's*, not the applied snapshot's, and it has to be: comments are outside the
/// snapshot, so a comment arriving leaves the model value-equal and a value-equal reload skips its
/// assignment (blessed 2026-07-31). Keyed on the applied counter, the index would sleep through the
/// one kind of change it exists to notice.
/// A changed-path channel would narrow it to the cards whose `comments/` actually moved; until one
/// exists, this is the accepted interim and is written down here rather than discovered later.
///