The decision surface — a refused open becomes a live repair, in place
Phase 3 of the decision surface, completing the card (01 ▸ Malformed input, settled 2026-07-31). An attended open's fail-fast walk transforms the loading window's content into one aggregated surface — never a sheet, never a chain: defects grouped by class, each class stated once with its files listed (Reveal in Finder + Open in Editor per row), a class-level default preselected, per-item override behind a disclosure. Only honest choices: YAML and malformed-schema get Editor + Re-check (Skip below the root); newer-than-app gets Skip alone and blocks the board at the root; the two root repairs — minted index, schema: 1 stamp — are defaults. Repair and Open applies fixes in one store-less write bracket and re-walks: clean proceeds, remainder re-aggregates into the same surface. Cancel and ⌘W retire to welcome's row; restored opens never see the surface at all (OpenOrigin rides the PendingOpen carrier). Skips are per-open consent that rides the session — the store retains the skip set and every reload passes it — and the opened board posts a warning-tone notice naming what was left out, each item's Reveal riding the banner strip's new reveal control. On Pro boards the repair bracket binds its own EchoLedger, heal-marks everything, and the store adopts it before the committer starts, so repairs land as one separate commit authored Lanework Integrity — pinned end to end. Also fixed en route: a retired loading window left its close interception installed and returned false from windowShouldClose forever, blocking quit. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -197,21 +197,37 @@ public final class BoardStoreRegistry {
|
||||
/// returns `nil` before constructing anything, so there is no store, no watcher, no entry and no
|
||||
/// reference — and the caller's security-scoped access is its own to release.
|
||||
///
|
||||
/// - Parameter skipping: **the decision surface's consented skips** (01-storage-format.md
|
||||
/// § Malformed input, ruled 2026-07-31), passed to the walk and then *retained by the store*
|
||||
/// so every reload of the resulting session runs with the same set (`BoardStore.skippedPaths`).
|
||||
/// Empty — the default — is every board that opens without a surface.
|
||||
///
|
||||
/// - Returns: the board's store, or `nil` if this acquire was cancelled before its walk landed.
|
||||
/// `nil` is not a failure: nothing went wrong and nothing was opened.
|
||||
public func acquireOffMain(_ rootURL: URL) async throws(BoardLoadFailure) -> BoardStore? {
|
||||
public func acquireOffMain(
|
||||
_ rootURL: URL,
|
||||
skipping: Set<String> = []
|
||||
) async throws(BoardLoadFailure) -> BoardStore? {
|
||||
if let store = referenceExistingBoard(at: rootURL) { return store }
|
||||
|
||||
// `nil` for a root that does not exist or whose volume will not answer — there is nothing to
|
||||
// coalesce on, so such an open walks alone and the loader produces the honest error for it.
|
||||
let identity = FileIdentity(of: rootURL)
|
||||
//
|
||||
// **A skip-carrying acquire also walks alone**, deliberately: the single flight exists so
|
||||
// concurrent opens of one board share a walk, and two walks are only shareable when they
|
||||
// would produce the same answer. A skip set changes what the walk *finds*, so joining one
|
||||
// would hand a window a board someone else's consent composed — and a Repair-and-Open's
|
||||
// re-walk could be answered by the very walk that failed. The condition is exactly "an
|
||||
// ordinary open", which is every open the coalescing was built for (restoration, a Finder
|
||||
// open racing it, a card window arriving first) and none of the surface's.
|
||||
let identity = skipping.isEmpty ? FileIdentity(of: rootURL) : nil
|
||||
|
||||
let walk: Task<Result<LoadResult, BoardLoadFailure>, Never>
|
||||
if let identity, let joined = walksInFlight[identity] {
|
||||
Self.logger.debug("acquire: joining the walk already running for this board")
|
||||
walk = joined
|
||||
} else {
|
||||
walk = Self.walk(rootURL)
|
||||
walk = Self.walk(rootURL, skipping: skipping)
|
||||
if let identity { walksInFlight[identity] = walk }
|
||||
}
|
||||
|
||||
@@ -238,7 +254,8 @@ public final class BoardStoreRegistry {
|
||||
case let .failure(failure):
|
||||
throw failure
|
||||
case let .success(result):
|
||||
return try adopt(BoardStore(rootURL: rootURL, loaded: result), rootURL: rootURL)
|
||||
return try adopt(
|
||||
BoardStore(rootURL: rootURL, loaded: result, skipping: skipping), rootURL: rootURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,10 +263,13 @@ public final class BoardStoreRegistry {
|
||||
/// `BoardStore.startReload`'s reason exactly: a task created inside a `@MainActor` method
|
||||
/// inherits that isolation and would run the walk on the main actor, which is the whole thing
|
||||
/// this is avoiding.
|
||||
private static func walk(_ rootURL: URL) -> Task<Result<LoadResult, BoardLoadFailure>, Never> {
|
||||
private static func walk(
|
||||
_ rootURL: URL,
|
||||
skipping: Set<String>
|
||||
) -> Task<Result<LoadResult, BoardLoadFailure>, Never> {
|
||||
Task.detached(priority: .userInitiated) {
|
||||
do throws(BoardLoadFailure) {
|
||||
return .success(try BoardLoader.load(boardRoot: rootURL))
|
||||
return .success(try BoardLoader.load(boardRoot: rootURL, skipping: skipping))
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user