import Foundation /// **Where app-side state lives** — the board registry, the clipboard's staging snapshots, the user /// template store (02-architecture.md § Per-board app state, "App-wide state has the same home"). /// /// ### One app, one sandbox, one home /// /// There is one application (12-editions.md ▸ The target, re-ruled 2026-07-30), so there is one /// sandbox container and nothing to share state *with*. `Library/Application Support` inside that /// container is the whole answer: the sandbox already scopes it per app, which is why there is **no /// bundle-id subfolder** — a subfolder inside a container that is already this app's alone would be /// ceremony naming the app twice. /// /// Scalars are not here. A window size or a toggle goes to `UserDefaults.standard` /// (`AppPreferences`), which the sandbox scopes on exactly the same terms; this type is only about /// the *file* stores. /// /// ### Why the type exists at all rather than three copies of four lines /// /// Three stores answer "where do I live" and they must answer it identically: the registry, the /// clipboard staging root and the template store are neighbours by design, and a test that asserts /// they are neighbours (`BoardRegistryTests`) is asserting something real. One name for the home is /// what keeps them moving together the day it moves. public enum AppStateHome { /// The directory every app-side file store lives in. /// /// In a shipped app this is `productionDirectory`. **In a unit-test host it is a scratch /// directory** (`isUnitTestHost`) — see that property for why the default has to move for a test /// host when everything else about testing is injection. public static var directory: URL { isUnitTestHost ? unitTestDirectory : productionDirectory } /// What a shipped app uses: `Library/Application Support` inside the sandbox container. /// /// The `NSHomeDirectory()` fallback covers the case where `FileManager` answers with no domain /// at all — not a state a shipped app is in, but this file must not be the thing that throws. public static var productionDirectory: URL { FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) .appendingPathComponent("Library/Application Support", isDirectory: true) } // MARK: - The unit-test host /// Whether this process is hosting a unit-test bundle. /// /// ### Why the app has to know /// /// The unit-test host **is the app** (`KanbanTests` is a hosted bundle), so `KanbanApp.init()` /// runs for real on every test launch and builds an `AppModel` over whatever the defaults resolve /// to. Every *object* a test constructs takes its storage by injection — that is the seam, and it /// is untouched — but the host's own launch has no injection point, and the thing it reaches for /// is the developer's own state: its launch sweep would collect real staged clipboard trees, and /// its `refreshRecents()` would resolve, refresh and rewrite real records. /// /// So the *default* moves for a test host, which is the one place a default can be wrong in a way /// injection cannot fix. /// /// ### Why this variable and not a launch flag /// /// `UITestLaunch.fixtureFlag` is the flag-shaped answer and remains the right one for the UI /// suites, which launch the app themselves and can pass arguments. A *unit*-test host is launched /// by the test runner, which passes nothing of ours — but it does set these variables, and it has /// set them for as long as XCTest has existed. Three spellings are checked because Apple has used /// each at some point and a missed one would silently mean "not a test". /// /// It cannot fire in a shipped app: nothing sets these but a test runner. public static var isUnitTestHost: Bool { let environment = ProcessInfo.processInfo.environment return environment["XCTestConfigurationFilePath"] != nil || environment["XCTestBundlePath"] != nil || environment["XCTestSessionIdentifier"] != nil } /// The scratch home a test host uses instead. Inside the app's own container /// (`NSTemporaryDirectory` sandboxes there), so nothing outside this app can see it and the OS /// reclaims it. /// /// One fixed folder rather than one per run: the suites do not depend on it being empty — they /// inject their own paths for anything they assert on — and a stable name keeps it inspectable /// when something writes there that should not have. public static var unitTestDirectory: URL { URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) .appendingPathComponent("LaneworkUnitTestState", isDirectory: true) } }