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 restore (`KanbanApp` decides), 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. struct RestoreBootstrapView: View { @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) 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() } dismissWindow(id: WindowID.restoreBootstrap) } }