The loader collects every fail-fast defect and honors per-open skips
Phase 1 of the decision surface (01 ▸ Malformed input, settled
2026-07-31): BoardLoadFailure aggregates the walk's defects in walk
order — stop-at-first retires. Environmental failures (unreadable root,
not-a-directory) stay immediate single-defect throws: there is no walk
to collect from. A defective root index is recorded and the walk
continues into the children (nothing in the walk consults the parsed
root document — verified); a defective lane, card, or trash-entry index
records and skips its subtree, Re-check's whole-walk re-aggregation
being the designed loop for what hides beneath. load(skipping:) is the
per-open skip channel: a skipped path's item is omitted from the model
and surfaces as LoadWarning.userSkipped; root paths are unskippable by
construction. The reload-breakage banner carries the aggregate ("…and
N more"), single-defect sentences byte-identical to before. Two new
multi-defect fixture boards; suite 2591 green.
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -167,13 +167,13 @@ struct BoardStoreTests {
|
||||
|
||||
// There is nothing to fall back on before the first load, so every later rule — the banner,
|
||||
// the lock, "a failed reload never replaces a good snapshot" — has no meaning here.
|
||||
do throws(BoardLoadError) {
|
||||
do throws(BoardLoadFailure) {
|
||||
_ = try BoardStore(rootURL: fixture.root)
|
||||
Issue.record("expected the initial load to fail")
|
||||
} catch {
|
||||
#expect(error.path == indexPath(Ident.lane1))
|
||||
if case .unparseableYAML = error.reason {} else {
|
||||
Issue.record("expected unparseable YAML, got \(error.reason)")
|
||||
#expect(error.primary.path == indexPath(Ident.lane1))
|
||||
if case .unparseableYAML = error.primary.reason {} else {
|
||||
Issue.record("expected unparseable YAML, got \(error.primary.reason)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ struct BoardStoreTests {
|
||||
await store.awaitQuiescence()
|
||||
|
||||
#expect(store.snapshot == lastGood, "a failed reload never replaces a good snapshot")
|
||||
#expect(store.reloadFailure?.path == indexPath(Ident.lane1, Ident.card1))
|
||||
#expect(store.reloadFailure?.primary.path == indexPath(Ident.lane1, Ident.card1))
|
||||
#expect(store.loadWarnings.contains(.nonUUIDFolderIgnored(path: "notes")), "warnings describe the snapshot on screen, so they stay with it")
|
||||
|
||||
// Transient breakage self-heals: the watcher kept watching and the fix arrives as an
|
||||
@@ -347,7 +347,7 @@ struct BoardStoreTests {
|
||||
|
||||
#expect(store.readOnlyLock == .bracketedReloadFailed)
|
||||
#expect(store.isReadOnly)
|
||||
#expect(store.reloadFailure?.path == indexPath(Ident.lane1, Ident.card1))
|
||||
#expect(store.reloadFailure?.primary.path == indexPath(Ident.lane1, Ident.card1))
|
||||
#expect(store.snapshot == lastGood)
|
||||
|
||||
// Writes are refused, and refused *before* the bracket opens — a refusal must not leave the
|
||||
@@ -404,7 +404,7 @@ struct BoardStoreTests {
|
||||
await store.awaitQuiescence()
|
||||
|
||||
#expect(store.reloadGeneration == 2, "two walks: the one in flight, then the one the operation owed")
|
||||
#expect(store.reloadFailure?.path == indexPath(Ident.lane1, Ident.card1))
|
||||
#expect(store.reloadFailure?.primary.path == indexPath(Ident.lane1, Ident.card1))
|
||||
#expect(store.readOnlyLock == .bracketedReloadFailed)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user