New phase chained after UPDATE-SELFTEST (so a full SHOTDECK_PICKER_SELFTEST chain now prints all five PASS lines) and also runnable standalone via REDLINE_SELFTEST_PHASE=onedrive, since the harness has no other per-phase selector. Points TransportSettings at a NEW "Redline-selftest-<Dubai timestamp>" folder under the real /Users/.../OneDrive-MMDGROUP sync root (never a fake home tree — that proves the transport against the actual OneDrive file provider), drives a seeded session through the OneDrive branch of send(anchor: nil), asserts the PDF landed, the session archived, and the status starts with "Saved to OneDrive", then confirms the watcher does NOT report the fresh unmarked PDF as returned. It then adds a real PDFKit ink annotation to that PDF in place — what the iPad does — saves it, and confirms the watcher now reports it as commented. Prints "ONEDRIVE-SELFTEST PASS path=<folder>". Never deletes anything under OneDrive; the created folder and PDF are left in place. UserDefaults.standard's transport/oneDriveFolder keys are snapshotted and restored around the phase, the same pattern runRegionPersistPhase already uses for CaptureRegion — there is no separate defaults-suite threading through AppModel/send(), so this is the only way to drive the real send() path without leaving the real app pointed at the selftest folder afterward. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp
85 lines
3.0 KiB
Swift
85 lines
3.0 KiB
Swift
import AppKit
|
|
import SwiftUI
|
|
import ShotdeckCore
|
|
|
|
// SwiftPM treats a file named main.swift as top-level code, which forbids `@main`.
|
|
// App.main() is the equivalent entry point.
|
|
if ProcessInfo.processInfo.environment["SHOTDECK_SNAPSHOT_DIR"] != nil {
|
|
MainActor.assumeIsolated { PanelSnapshot.runIfRequested() }
|
|
}
|
|
if ProcessInfo.processInfo.environment["REDLINE_SELFTEST_PHASE"] == "onedrive" {
|
|
MainActor.assumeIsolated { PickerSelfTest.runOneDriveOnlyIfRequested() }
|
|
}
|
|
if ProcessInfo.processInfo.environment["SHOTDECK_PICKER_SELFTEST"] != nil {
|
|
MainActor.assumeIsolated { PickerSelfTest.runIfRequested() }
|
|
}
|
|
ShotdeckApp.main()
|
|
|
|
struct ShotdeckApp: App {
|
|
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
|
|
|
var body: some Scene {
|
|
MenuBarExtra {
|
|
MenuBarView()
|
|
.environment(appDelegate.model)
|
|
} label: {
|
|
let state = appDelegate.model.iconState
|
|
HStack(spacing: 4) {
|
|
Image(systemName: state.symbolName)
|
|
if let count = state.countText {
|
|
Text(count).font(.system(size: 11, weight: .semibold))
|
|
}
|
|
}
|
|
.accessibilityLabel("Redline")
|
|
}
|
|
.menuBarExtraStyle(.window)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
let model: AppModel
|
|
|
|
override init() {
|
|
NSApplication.shared.setActivationPolicy(.accessory)
|
|
model = AppDelegate.makeLaunchModel()
|
|
super.init()
|
|
}
|
|
|
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
Task { await model.bootstrap() }
|
|
}
|
|
|
|
private static func makeLaunchModel() -> AppModel {
|
|
do {
|
|
let paths = try FolderSettings.resolvedAppSupportPaths()
|
|
return try makeModel(paths: paths)
|
|
} catch {
|
|
Log.ui.critical(
|
|
"AppModel init failed: \(String(describing: error), privacy: .public)"
|
|
)
|
|
let tmp = FileManager.default.temporaryDirectory
|
|
let fallbackRoot = tmp.appendingPathComponent("Shotdeck-fallback", isDirectory: true)
|
|
// Safe: temp-dir creation for a path this process controls cannot legitimately fail.
|
|
let fallback = try! AppSupportPaths(root: fallbackRoot, outbox: tmp, watchFolder: tmp)
|
|
let model = try! makeModel(paths: fallback)
|
|
model.setStatus("Redline could not access its storage folder. Captures will not persist.")
|
|
return model
|
|
}
|
|
}
|
|
|
|
private static func makeModel(paths: AppSupportPaths) throws -> AppModel {
|
|
let ledger = try ReturnLedger(paths: paths)
|
|
return AppModel(
|
|
paths: paths,
|
|
spool: try SpoolStore(paths: paths),
|
|
composer: PDFComposer(),
|
|
capturer: ScreenCapturer(),
|
|
hotkeys: HotkeyCenter(),
|
|
picker: RegionPickerController(),
|
|
ledger: ledger,
|
|
watcher: ReturnWatcher(paths: paths, ledger: ledger)
|
|
)
|
|
}
|
|
}
|