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:
2026-08-01 09:12:49 -04:00
parent 94e60cd444
commit ba1726fa77
35 changed files with 897 additions and 117 deletions
+11 -7
View File
@@ -345,7 +345,11 @@ struct UITestMalformedFixtureBoardTests {
do {
_ = try BoardLoader.load(boardRoot: root)
Issue.record("the malformed board loaded — the fail-fast pass would audit a board that opens")
} catch let error as BoardLoadError {
} catch let failure as BoardLoadFailure {
// One broken file, so one defect: the fixture breaks a single card's index, and the
// aggregate is what the welcome row reads its caption off.
#expect(failure.defects.count == 1)
let error = failure.primary
// The path is board-relative and names the *file*, which is what the welcome row's
// failure caption carries and what the UI test asserts against.
#expect(error.path.hasSuffix("/\(BoardLoader.indexFileName)"))
@@ -360,10 +364,10 @@ struct UITestMalformedFixtureBoardTests {
}
// The whole sentence, which is what actually reaches the user: file first, then why.
#expect(error.description.contains(BoardLoader.indexFileName))
#expect(error.description.lowercased().contains("yaml"))
#expect(failure.description.contains(BoardLoader.indexFileName))
#expect(failure.description.lowercased().contains("yaml"))
} catch {
Issue.record("expected a BoardLoadError, got \(error)")
Issue.record("expected a BoardLoadFailure, got \(error)")
}
}
@@ -386,10 +390,10 @@ struct UITestMalformedFixtureBoardTests {
do {
_ = try BoardLoader.load(boardRoot: root)
Issue.record("the malformed board loaded")
} catch let error as BoardLoadError {
#expect(error.path.hasSuffix(BoardLoader.indexFileName))
} catch let failure as BoardLoadFailure {
#expect(failure.primary.path.hasSuffix(BoardLoader.indexFileName))
} catch {
Issue.record("expected a BoardLoadError, got \(error)")
Issue.record("expected a BoardLoadFailure, got \(error)")
}
}