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
141 lines
7.2 KiB
Swift
141 lines
7.2 KiB
Swift
import SwiftUI
|
||
import os
|
||
|
||
/// The launch-time restoration pass, wearing a window because that is the only place SwiftUI lets
|
||
/// work like this run.
|
||
///
|
||
/// ### Why a window at all
|
||
///
|
||
/// Restoration has to open windows, and opening a window needs `openWindow`, which is only readable
|
||
/// from a view. An `App.init()` cannot do it and `AppDelegate` has no environment. So the app
|
||
/// presents one throwaway window at launch — 1×1, plain, ordered straight back out, absent from the
|
||
/// Window menu — whose only job is to run the pass and then dismiss itself. It exists for a few
|
||
/// hundred milliseconds and never draws.
|
||
///
|
||
/// It is presented at **every** launch — it is the app's one reliable presenter (see `KanbanApp`'s
|
||
/// bootstrap scene for the macOS 26 behavior that forced this), so even the plain launch-to-welcome
|
||
/// path runs through it: the pass finds nothing flagged and opens welcome itself.
|
||
///
|
||
/// ### What the pass does
|
||
///
|
||
/// Reads the registry's flagged records in `lastOpened` order (`BoardRegistry.restorables()`), opens
|
||
/// the available ones, and records the unavailable ones as failures — 02 § Launch and window
|
||
/// lifecycle: "Other restorations proceed unaffected — never a launch-time modal chain, never a
|
||
/// silent drop." Welcome comes up only if nothing was even attempted; a board that *was* attempted
|
||
/// and then failed to load opens welcome from its own host, which is the same rule applied one layer
|
||
/// down and keeps this pass from having to wait on loads it did not perform.
|
||
///
|
||
/// ### And one other pass, for the same reason
|
||
///
|
||
/// The accessibility audit suite's fixture board (`UITestLaunch`) is built and opened here too. It is
|
||
/// the same job with a different source — filesystem work that must happen before the first real
|
||
/// window, needing `openWindow` to finish — and giving it a second throwaway window would be a second
|
||
/// copy of everything this file explains. Which pass runs is `plan`'s to say and nothing else's.
|
||
struct RestoreBootstrapView: View {
|
||
|
||
/// Decided in `KanbanApp.init()`; this view only dispatches on it.
|
||
let plan: LaunchPlan
|
||
|
||
@Environment(AppModel.self) private var appModel
|
||
@Environment(\.openWindow) private var openWindow
|
||
@Environment(\.dismissWindow) private var dismissWindow
|
||
|
||
@State private var windowController = HostedWindowController()
|
||
|
||
private static let logger = Logger(subsystem: "dev.rzen.indie.Kanban", category: "launch")
|
||
|
||
var body: some View {
|
||
Color.clear
|
||
.frame(width: 1, height: 1)
|
||
.background(WindowAccessor(controller: windowController))
|
||
.onAppear {
|
||
// Out of sight before it can be seen. `orderOut` rather than a hidden style because
|
||
// the scene must still exist — a window SwiftUI never presents never runs its task.
|
||
windowController.onAttach = { window in
|
||
window.alphaValue = 0
|
||
window.orderOut(nil)
|
||
}
|
||
if let window = windowController.window {
|
||
windowController.onAttach?(window)
|
||
}
|
||
}
|
||
.task { await restore() }
|
||
}
|
||
|
||
private func restore() async {
|
||
// Captured directly rather than waiting for `CaptureOpenWindow`'s `onAppear`: this task is
|
||
// the app's first act, and `openBoard` needs the action now. The count is a cold Finder-open
|
||
// that arrived before this window did — a board already on its way to the screen, which the
|
||
// pass below must count as an open or it would put welcome up beside the user's document.
|
||
let replayedOpens = appModel.captureWindowActions(open: openWindow, dismiss: dismissWindow)
|
||
|
||
switch plan {
|
||
case .uiTestFixture:
|
||
openFixtureBoard()
|
||
case .restoreBoards, .welcome:
|
||
// `.welcome` arrives here by design — this window presents at every launch, because it is
|
||
// the app's one reliable presenter (see `KanbanApp`'s bootstrap scene) — and the pass is
|
||
// its answer: nothing is flagged, so it shows welcome, which is what `.welcome` asked
|
||
// for.
|
||
restoreFlaggedBoards(openedAlready: replayedOpens)
|
||
}
|
||
|
||
dismissWindow(id: WindowID.restoreBootstrap)
|
||
}
|
||
|
||
private func restoreFlaggedBoards(openedAlready: Int) {
|
||
var attempted = openedAlready
|
||
for board in appModel.boardRegistry.restorables() {
|
||
switch board {
|
||
case let .available(_, url):
|
||
// **The one restored open in the app** (01-storage-format.md § Malformed input, the
|
||
// decision surface): a board that fails here keeps today's retire-to-welcome-row
|
||
// landing — "launch never chains dialogs", and nobody is sitting in front of a
|
||
// restoration waiting to repair four boards at once. The row's retry click is the
|
||
// attended open that then shows the surface.
|
||
appModel.openBoard(at: url, origin: .restored)
|
||
attempted += 1
|
||
case let .unavailable(record):
|
||
Self.logger.error("a flagged board could not be restored — its bookmark no longer resolves")
|
||
appModel.recordLaunchFailure(
|
||
path: record.lastKnownPath,
|
||
message: "This board is unavailable. Its volume may be offline, or it may have been moved or deleted."
|
||
)
|
||
}
|
||
}
|
||
|
||
if attempted == 0 {
|
||
appModel.showWelcome()
|
||
}
|
||
}
|
||
|
||
/// The UI suites' board: built here, opened through the same `openBoard` every other path uses,
|
||
/// so it registers, bookmarks and titles itself exactly like a board the user opened.
|
||
///
|
||
/// **Which board is the launch arguments' to say** (`UITestLaunch.variant`), and this method does
|
||
/// not care: the malformed variant is built and opened exactly like the other two, and its
|
||
/// failure arrives one layer down as the *loader's*. It opens **attended**, like every other
|
||
/// board a person asks for, so its refusal transforms the loading window into the decision surface
|
||
/// (`BoardWindowHost.handleWalkFailure`) rather than retiring — which is precisely what the
|
||
/// fail-fast UI pass is there to see. Special-casing it here would replace the behaviour under
|
||
/// test with a behaviour about the fixture.
|
||
///
|
||
/// **A failure to *build* lands on welcome as an ordinary launch failure**, with the fixture's own
|
||
/// path on it. That is deliberate: a suite whose fixture failed to build would otherwise audit an
|
||
/// empty screen and pass, which is the one outcome an accessibility gate must never produce.
|
||
private func openFixtureBoard() {
|
||
let variant = UITestLaunch.variant
|
||
do {
|
||
let url = try UITestLaunch.materializeFixtureBoard(variant)
|
||
appModel.openBoard(at: url)
|
||
} catch {
|
||
Self.logger.error("the UI-test fixture board could not be built: \(error.localizedDescription, privacy: .public)")
|
||
appModel.recordLaunchFailure(
|
||
path: UITestLaunch.fixtureBoardURL(for: variant).path,
|
||
message: "The UI-test fixture board could not be built: \(error.localizedDescription)"
|
||
)
|
||
appModel.showWelcome()
|
||
}
|
||
}
|
||
}
|