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
+119
View File
@@ -278,4 +278,123 @@ struct BoardStoreRegistryTests {
}
#expect(registry.openBoardCount == 0)
}
// MARK: The off-main acquire
//
// The board window's open (02-architecture.md § Launch and window lifecycle, ruled 2026-07-29):
// the walk runs off the main actor so the window can be on screen while it does, concurrent asks
// for one board share it, and a cancelled open leaves nothing behind. These are claims about
// *what is registered*, so they are asserted the way the rest of this file asserts object
// identity and the entry count, never a duration.
@Test("An already-open board answers the async acquire without a walk")
func offMainAcquireHitsTheOpenBoard() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let registry = BoardStoreRegistry()
let first = try registry.acquire(fixture.root)
let second = try await registry.acquireOffMain(fixture.root)
#expect(second === first, "an open board is the same store however it is asked for")
#expect(registry.openBoardCount == 1)
// Two references, so the first release must not tear anything down.
registry.release(first)
#expect(registry.liveStore(for: fixture.root) === first)
registry.release(first)
#expect(registry.openBoardCount == 0)
}
@Test("Concurrent async acquires of one board single-flight into one store")
func offMainAcquiresSingleFlight() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let registry = BoardStoreRegistry()
// Both calls are in flight before either can finish: each hops to the main actor, and the
// first one to get there suspends on its walk with the second right behind it.
async let first = registry.acquireOffMain(fixture.root)
async let second = registry.acquireOffMain(fixture.root)
let stores = try await (first, second)
#expect(stores.0 != nil)
#expect(stores.0 === stores.1, "a joined walk must produce one store, not two")
#expect(registry.openBoardCount == 1, "two windows racing one board is still one open board")
// And both callers really are holding it: the refcount took both, so the first release
// leaves the board standing.
guard let store = stores.0 else { return }
registry.release(store)
#expect(registry.liveStore(for: fixture.root) === store)
registry.release(store)
#expect(registry.openBoardCount == 0)
}
@Test("Two boards opening at once are two independent walks")
func offMainAcquiresOfDistinctBoardsDoNotShare() async throws {
let first = try makeBoard()
defer { first.tearDown() }
let second = try makeBoard()
defer { second.tearDown() }
let registry = BoardStoreRegistry()
// Restoration's shape: several windows opening together. The single flight is keyed by file
// identity, so nothing here can queue behind anything else a slow board holds its own key
// and no other.
async let one = registry.acquireOffMain(first.root)
async let two = registry.acquireOffMain(second.root)
let stores = try await (one, two)
#expect(stores.0 != nil)
#expect(stores.1 != nil)
#expect(stores.0 !== stores.1)
#expect(registry.openBoardCount == 2)
if let store = stores.0 { registry.release(store) }
if let store = stores.1 { registry.release(store) }
#expect(registry.openBoardCount == 0)
}
@Test("A cancelled async acquire builds no store and registers nothing")
func cancelledOffMainAcquireLeavesNothingBehind() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let registry = BoardStoreRegistry()
// W during the walk. The walk itself is not cooperatively cancellable it runs to
// completion and its result is discarded so what is asserted here is the discard: no
// store, no entry, no watcher, no reference.
let open = Task { try? await registry.acquireOffMain(fixture.root) }
open.cancel()
let store = await open.value
#expect(store == nil, "a cancelled open answers with nothing rather than a board")
#expect(registry.openBoardCount == 0)
#expect(registry.liveStore(for: fixture.root) == nil)
// And the board is still openable afterwards: a discarded walk must leave no half-state a
// later open could trip over.
let reopened = try await registry.acquireOffMain(fixture.root)
#expect(reopened != nil)
#expect(registry.openBoardCount == 1)
if let reopened { registry.release(reopened) }
}
@Test("The async acquire fails fail-fast like the synchronous one")
func offMainAcquireOfABrokenBoardThrows() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
try fixture.item(Ident.lane1, brokenIndex)
let registry = BoardStoreRegistry()
do throws(BoardLoadFailure) {
_ = try await registry.acquireOffMain(fixture.root)
Issue.record("expected a broken board to fail")
} catch {
#expect(error.primary.path == "\(Ident.lane1)/index.md")
}
// A failed load leaves nothing behind, whichever actor walked it.
#expect(registry.openBoardCount == 0)
}
}