Redline: OneDrive folder transport as alternative to AirDrop (MMDB-2631) #23

Merged
kua-agent merged 20 commits from feat/redline-onedrive-transport-20260905 into main 2026-09-05 06:17:29 +00:00
2 changed files with 29 additions and 2 deletions
Showing only changes of commit ef0d8712f7 - Show all commits
+18
View File
@@ -132,6 +132,13 @@ public final class AppModel {
} }
func setTransport(_ value: SendTransport) { transport = value } func setTransport(_ value: SendTransport) { transport = value }
func setResolvedOneDriveFolder(_ value: URL?) { resolvedOneDriveFolder = value } func setResolvedOneDriveFolder(_ value: URL?) { resolvedOneDriveFolder = value }
/// Bumped by chooseTransport/chooseOneDriveFolder (SettingsView.swift) before each
/// spawns its async watcher-reconcile Task; that Task checks its own snapshot
/// against the live value before every mutating step, so rapid toggling always
/// lets the LAST choice win instead of applying stale, superseded work. Not
/// `@Observable`-relevant state pure internal bookkeeping, never read by a View.
var reconcileGeneration = 0
func rememberLastComposedPDF(_ url: URL) { lastComposedPDFURL = url } func rememberLastComposedPDF(_ url: URL) { lastComposedPDFURL = url }
/// True when a last-composed PDF path is known this run, or the newest /// True when a last-composed PDF path is known this run, or the newest
@@ -211,6 +218,17 @@ public final class AppModel {
await watcher.setRecordUncommented(transport == .airDrop) await watcher.setRecordUncommented(transport == .airDrop)
do { do {
// BLOCKER fix: reconcile the watcher's internal watchFolder with the live
// watchFolderURL UNCONDITIONALLY, before it ever starts. `paths` (and so the
// watcher's initial folder, set in its own init) now comes from the same
// transport-aware TransportSettings.effectiveFolders() as watchFolderURL, so
// in the normal case this is a no-op but it is the only thing that would
// have caught the old bug (launch paths built AirDrop-only while OneDrive was
// the persisted transport, leaving the watcher's FSEvents stream pointed at a
// stale folder for the whole session) and it stays cheap insurance against
// that class of drift ever recurring. Calling it before start() only updates
// the stored folder no FSEvents stream exists yet to restart.
try await watcher.updateWatchFolder(watchFolderURL)
try await watcher.start { [weak self] _ in try await watcher.start { [weak self] _ in
Task { @MainActor in Task { @MainActor in
guard let self else { return } guard let self else { return }
+11 -2
View File
@@ -50,9 +50,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
Task { await model.bootstrap() } Task { await model.bootstrap() }
} }
private static func makeLaunchModel() -> AppModel { /// Builds the model exactly the way the real app launches: paths come from
/// `TransportSettings.resolvedAppSupportPaths()` transport-aware, so the watcher
/// this feeds is never seeded with a stale AirDrop folder while OneDrive is the
/// persisted transport (that was the BLOCKER this function used to have, when it
/// called the AirDrop-only `FolderSettings.resolvedAppSupportPaths()` instead).
/// `appSupportRoot` exists only so PickerSelfTest's relaunch-simulation sub-step can
/// point this at a temp directory instead of the real
/// ~/Library/Application Support/Shotdeck production always calls this with no
/// argument (the real root). Internal, not private, for that same reason.
static func makeLaunchModel(appSupportRoot: URL? = nil) -> AppModel {
do { do {
let paths = try FolderSettings.resolvedAppSupportPaths() let paths = try TransportSettings.resolvedAppSupportPaths(root: appSupportRoot)
return try makeModel(paths: paths) return try makeModel(paths: paths)
} catch { } catch {
Log.ui.critical( Log.ui.critical(