From c25e715fed1150927cb9b5879bbeb027a0a4175d Mon Sep 17 00:00:00 2001 From: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:46:14 -0500 Subject: [PATCH] Say Not supported only for a Mac model the installer refuses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a 14-inch M2 Max (apple,j414c, enabled in the signed edge catalog) installer 2.0.10 showed APPLE M2 MAX · 426 GB FREE · NOT SUPPORTED because an older Omarchy install was still on the disk. The header badged every existing-install page and every stop before the welcome page as Not supported, including too little space and an engine that couldn't run. The same Mac read SUPPORTED as soon as the old install was removed. The session now decides the badge. Not supported means the model was refused (blocked, or missing from the catalog). An existing install or too little space on a supported Mac says Supported, and a stop where the installer couldn't tell shows no support badge. Co-Authored-By: Claude Opus 5.5 --- .../OnePage/OnePageInstallerView.swift | 17 ++---- .../InstallerSession.swift | 17 ++++++ .../InstallerSessionTests.swift | 57 +++++++++++++++++++ 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/Sources/OmarchyAppleInstallerApp/OnePage/OnePageInstallerView.swift b/Sources/OmarchyAppleInstallerApp/OnePage/OnePageInstallerView.swift index b4f649c..819370d 100644 --- a/Sources/OmarchyAppleInstallerApp/OnePage/OnePageInstallerView.swift +++ b/Sources/OmarchyAppleInstallerApp/OnePage/OnePageInstallerView.swift @@ -151,10 +151,10 @@ struct OnePageInstallerView: View { .font(OmarchyTheme.eyebrow) .textCase(.uppercase) .foregroundStyle(OmarchyTheme.accent) - if isBlocked { - StatusBadge(text: PlainLanguage.blockedBadge, kind: .blocked) - } else { - StatusBadge(text: PlainLanguage.supportedBadge, kind: .ok) + if let supported = session.modelSupported { + StatusBadge( + text: supported ? PlainLanguage.supportedBadge : PlainLanguage.blockedBadge, + kind: supported ? .ok : .blocked) } if let channel { StatusBadge(text: PlainLanguage.badge(for: channel), kind: .ok) @@ -169,15 +169,6 @@ struct OnePageInstallerView: View { } } - private var isBlocked: Bool { - switch session.phase { - case .unsupported, .existingInstallRefused: - return true - default: - return false - } - } - // MARK: The middle of the page @ViewBuilder diff --git a/Sources/OmarchyInstallerUXCore/InstallerSession.swift b/Sources/OmarchyInstallerUXCore/InstallerSession.swift index 8a5e4a1..e5e8df4 100644 --- a/Sources/OmarchyInstallerUXCore/InstallerSession.swift +++ b/Sources/OmarchyInstallerUXCore/InstallerSession.swift @@ -136,6 +136,23 @@ return environment.installationBlocked } + /// The header's word on this Mac model: false only for a refused model, so + /// an existing install or too little space never reads as Not supported. + /// Nil when the installer stopped before it could tell. + public var modelSupported: Bool? { + switch phase { + case .inspecting: + return nil + case .existingInstallRefused(let host): + return host.supported ? true : nil + case .unsupported(let failure): + if failure.isBlockedModel { return false } + return failure.device?.supported == true ? true : nil + default: + return true + } + } + public var credentialSheet: CredentialSheetState { if case .awaitingInstall(_, _, let sheet) = phase { return sheet diff --git a/Tests/OmarchyInstallerUXCoreTests/InstallerSessionTests.swift b/Tests/OmarchyInstallerUXCoreTests/InstallerSessionTests.swift index 366cfa9..e1874dc 100644 --- a/Tests/OmarchyInstallerUXCoreTests/InstallerSessionTests.swift +++ b/Tests/OmarchyInstallerUXCoreTests/InstallerSessionTests.swift @@ -1240,6 +1240,63 @@ XCTAssertEqual(environment.prepareCount, 1) } + /// On the M2 Max (apple,j414c, enabled in the signed catalog) the header + /// said NOT SUPPORTED because an older install was still on the disk. Only + /// a refused model says that; a stop for any other reason says Supported + /// when the Mac is, and nothing when the installer couldn't tell. + func testHeaderSaysNotSupportedOnlyForARefusedModel() async { + let supported = MockInstallerEnvironment.supportedHost + let install = ExistingInstallDisplay(sourceIdentifier: "disk0s3", sizeDescription: "256 GB") + let cases: [(String, HostDisplay, Bool, Bool?)] = [ + ("ready", supported, false, true), + ( + "existing install on a supported Mac", + HostDisplay( + chipAndSpace: supported.chipAndSpace, supported: true, existingInstalls: [install]), + false, true + ), + ( + "existing install, engine couldn't tell", + HostDisplay( + chipAndSpace: supported.chipAndSpace, supported: false, existingInstalls: [install]), + false, nil + ), + ( + "too little space", + HostDisplay( + chipAndSpace: supported.chipAndSpace, supported: true, + spaceShortfall: .insufficientSpace( + requiredBytes: 76_562_825_216, availableBytes: 73_528_246_272)), + false, true + ), + ( + "not ready", + HostDisplay( + chipAndSpace: supported.chipAndSpace, supported: false, + blockingReason: "The installer engine is unavailable."), + false, nil + ), + ( + "model not in the catalog", + HostDisplay( + chipAndSpace: "Apple M3 · 400 GB free", supported: false, + unsupportedModel: UnsupportedModelDisplay( + deviceIdentifier: "apple,j504", modelIdentifier: "Mac15,3", + supportedDeviceIdentifiers: ["apple,j314s"])), + false, false + ), + ("blocked model", HostDisplay(chipAndSpace: "Apple M4 Pro", supported: false), true, false), + ] + for (name, host, blocked, expected) in cases { + let environment = MockInstallerEnvironment() + environment.host = host + environment.installationBlocked = blocked + let session = InstallerSession(environment: environment) + await session.inspect() + XCTAssertEqual(session.modelSupported, expected, name) + } + } + func testReinspectResetsDisplayedAndStoredEncryptionChoice() async { let environment = MockInstallerEnvironment() let session = InstallerSession(environment: environment)