Every open passes through a real loading window — the walk moves off the main actor

Phase 2 of the decision surface: the pre-snapshot loading state ruled
2026-07-29 (02 § Launch and window lifecycle), built. The board window
appears immediately at its saved frame, titled with the registry
record's cached name, its content a centered spinner behind an
injectable ~200ms grace — no skeletons, and the first snapshot snaps in
place. The tree walk runs off-main via BoardStoreRegistry.acquireOffMain
(per-board single-flight keyed by file identity — concurrent opens of
one root share a walk, restoration of many boards is genuinely
parallel), landing in BoardStore's new designated init(rootURL:loaded:);
the self-walking init survives as a convenience for its ~470 callers.

⌘W during the walk is real: configureWindow split into a loading half
(frame restore, frame tracking, close interception — installed before
the walk) and a store half (toolbar, widget, hideTitle, undo — installed
at the snap), and the walk lives in an explicitly held BoardOpenWalk so
the user's close and SwiftUI's teardown end in one cancel().
Cancellation is discard-on-completion: nil from acquireOffMain means
nothing was built, nothing retained, and no open-now flag was ever set.
Failure keeps today's sequence exactly: record the launch failure,
welcome's row carries it, the window retires.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 09:53:26 -04:00
parent ba1726fa77
commit 0933ac1b01
8 changed files with 795 additions and 41 deletions
+22 -3
View File
@@ -589,8 +589,12 @@ public final class BoardStore: HealHost {
/// banner, the lock, "a failed reload never replaces a good snapshot" exists only *because*
/// this one succeeded.
///
/// The walk is synchronous because the caller has nothing to render until it lands; the
/// asynchronous, off-main pipeline starts with the first reload.
/// The walk runs here, on whatever actor the caller is on the storeless shape, and the one
/// every test and every synchronous consumer uses. The **window's** open no longer takes it:
/// 02-architecture.md's pre-snapshot loading state (ruled 2026-07-29) gave the caller something
/// to render before the snapshot exists, which retired this init's old rationale ("the caller
/// has nothing to render until it lands"), so `BoardStoreRegistry.acquireOffMain` walks off the
/// main actor and hands the result to `init(rootURL:loaded:)` below.
///
/// **It writes nothing, the opened board's defects included.** `defects` is recorded here and
/// acted on by whoever wired this store up `BoardStoreRegistry.acquire` calls
@@ -598,8 +602,23 @@ public final class BoardStore: HealHost {
/// with a reload behind it rather than a write into a board nothing is watching yet. A store
/// built directly (a test, a storeless consumer) heals when it is asked to, and on every reload
/// thereafter.
public init(rootURL: URL) throws(BoardLoadFailure) {
public convenience init(rootURL: URL) throws(BoardLoadFailure) {
let result = try BoardLoader.load(boardRoot: rootURL)
self.init(rootURL: rootURL, loaded: result)
}
/// The same board, from a walk that already happened somewhere else.
///
/// **The designated init, and the only one that is not a load**: `LoadResult` is `Sendable` and
/// `BoardLoader` is stateless statics, so the walk can run anywhere and only this assignment has
/// to be on the main actor exactly the split `startReload` has used for every reload since the
/// beginning, now available to the *first* load too. It cannot fail, because failing is the
/// walk's job and the walk is over: a caller holding a `LoadResult` holds a board that loaded.
///
/// `rootURL` is passed rather than read off `result.model` for the reason the property's own doc
/// comment gives the store's root follows an absorbed rename ahead of the snapshot that will
/// carry it.
public init(rootURL: URL, loaded result: LoadResult) {
self.rootURL = rootURL
self.snapshot = result.model
self.loadWarnings = result.warnings