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 **only** when there is something to open (`KanbanApp` decides, via /// `LaunchPlan.presentsBootstrap`), so the ordinary launch-to-welcome path never creates it. /// /// ### 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. appModel.captureWindowActions(open: openWindow, dismiss: dismissWindow) switch plan { case .uiTestFixture: openFixtureBoard() case .restoreBoards, .welcome: // `.welcome` never presents this window, so it cannot arrive here — and if a future // launch path let it, the restoration pass is the harmless answer: it finds nothing // flagged and shows welcome, which is what `.welcome` asked for anyway. restoreFlaggedBoards() } dismissWindow(id: WindowID.restoreBootstrap) } private func restoreFlaggedBoards() { var attempted = 0 for board in appModel.boardRegistry.restorables() { switch board { case let .available(_, url): appModel.openBoard(at: url) 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 audit suite's 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. /// /// **A failure 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() { do { let url = try UITestLaunch.materializeFixtureBoard() 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.path, message: "The UI-test fixture board could not be built: \(error.localizedDescription)" ) appModel.showWelcome() } } }