Compare commits

...
Author SHA1 Message Date
gronod 890e7281eb fix(ui): show media Apply failure inside the manage sheet (#170)
macOS CI / build-and-test (push) Skipped
macOS CI / package (pull_request) Canceled after 0s
macOS CI / build-and-test (pull_request) Canceled after 2m31s
Failed Apply kept the sheet open while the window banner sat
behind it, so Monterey XCTest never saw noticeText. Mirror the
failure string in the dialog (manageMediaNotice) and point the
UI test at that identifier.

Fixes #170
2026-09-14 13:36:59 +00:00
gronod ccaad2d940 docs: sync README with Monterey floor, M10, wiki, and packaging
macOS CI / package (pull_request) Canceled after 0s
macOS CI / build-and-test (pull_request) Canceled after 51s
macOS CI / build-and-test (push) Failing after 43m55s
macOS CI / package (push) Skipped
Requirements were still Xcode 15.4 / Swift 6. Point the product
at the user wiki, record host-arch tests, the dmgbuild venv, and
the current develop-first git workflow.
2026-09-14 12:34:16 +00:00
gronod 7e5e20cb69 Merge origin/main into develop (keep README)
macOS CI / package (push) Canceled after 0s
macOS CI / build-and-test (push) Canceled after 16m10s
2026-09-14 13:23:21 +01:00
gronod e72039c123 Delete README.md 2026-09-14 13:11:43 +01:00
gronod fa02e774ca Merge pull request 'fix(packaging): DMG background visible on Sonoma+ (#95)' (#167) from fix/95-dmg-background-sonoma into develop
macOS CI / build-and-test (pull_request) Skipped
macOS CI / build-and-test (push) Successful in 38m21s
macOS CI / package (push) Successful in 4m14s
Reviewed-on: #167
2026-09-14 11:55:39 +01:00
gronod d37dcedfd9 Update README.md 2026-09-09 19:18:43 +01:00
4 changed files with 176 additions and 76 deletions
+143 -51
View File
@@ -1,45 +1,75 @@
# ICCery
Native macOS frontend for printer ICC/ICM profiling. ICCery walks a user from chart generation through measurement, `colprof`, verification, and ColorSync install. It is **not** a colour engine.
Native macOS frontend for printer ICC/ICM profiling. ICCery walks a user from
chart generation through measurement, `colprof`, verification, and ColorSync
install. It is **not** a colour engine.
All measurement, chart generation, and profile mathematics live in the [Gronod ArgyllCMS 3.5.0 fork](https://git.i3omb.com/gronod/argyllcms), spawned as AGPLv3 child processes. The GUI never `dlopen`s or links Argyll.
**End-user guide:** the [repository wiki](https://git.i3omb.com/gronod/iccery-v2-mac/wiki)
covers every screen (Getting Started through Troubleshooting). This README is
for building, packaging, and contributing.
All measurement, chart generation, and profile mathematics live in the
[Gronod ArgyllCMS 3.5.0 fork](https://git.i3omb.com/gronod/argyllcms), spawned
as AGPLv3 child processes. The GUI never `dlopen`s or links Argyll.
| | |
|---|---|
| Product | ICCery v2 for macOS |
| Bundle | `com.gronod.iccery2` |
| Version | 2.0.0 |
| Floor | macOS 12.0 Monterey, universal `arm64` + `x86_64` |
| Toolchain | Xcode 14.2 / Swift 5.7 (project `SWIFT_VERSION` is 5.0) |
| CI | Gitea Actions `macos-12` runner |
| Default branch | `develop` |
| M6 | Stage 0 calibration, CGATS import, SceneKit gamut viewer, packaging — shipped on `develop` |
| M7 | Pre-UAT hardening & baseline consolidation — shipped on `develop` |
| M8 | Deduplication/consolidation contracts & UAT-ready hardening (#79#86) — shipped on `develop` |
| M9 | macOS 12 / Xcode 14.2 retarget — shipped on `develop` (PR #145) |
| M10 | Studio workflow (#146#149) — in flight on `milestone/m10-studio` |
| M6 | Stage 0 calibration, CGATS import, SceneKit gamut viewer, packaging — shipped |
| M7 | Pre-UAT hardening — shipped |
| M8 | Deduplication contracts & UAT-ready hardening (#79#86) — shipped |
| M9 | macOS 12 / Xcode 14.2 retarget (PR #145) — shipped |
| M10 | Studio workflow: gamut compare (#147), Spot Read (#148), project files (#149) shipped on `develop`; media library (#146) is in the tree, issue still open |
| Licence | Proprietary source in [`LICENCE.md`](LICENCE.md); bundled Argyll sidecars remain AGPLv3 |
## What it does
The wizard is artefact-gated:
1. **Stage 0**printer calibration: `printcal` / `applycal` session, `CAL_` basename restore
2. **Stage 1**`targen``.ti1`
3. **Stage 2**`printtarg``.ti2` + TIFF, unmanaged `lp` spool, bound `NSPrintPanel`
4. **Stage 3**`instlist` + streaming `chartread` (strip / XY / handheld) → `.ti3`, multi-pass average, CIEDE2000
5. **Stage 4**`colprof``.icc` / `.icm`; optional `applycal`; `iccgamut` next to the profile
6. **Stage 5**`profcheck`, verification history, ColorSync user/system install
1. **Stage 1**`targen` `.ti1`
2. **Stage 2**`printtarg``.ti2` + TIFF, unmanaged `lp` spool, bound `NSPrintPanel`
3. **Stage 3**`instlist` + streaming `chartread` (strip / XY / handheld) → `.ti3`, multi-pass average, CIEDE2000
4. **Stage 4**`colprof``.icc` / `.icm`; optional `applycal`; `iccgamut` next to the profile
5. **Stage 5**`profcheck`, verification history, ColorSync user/system install
Plus CGATS dataset import (`.ti3` / `.txt` / `.cgats` / `.csv`), SceneKit gamut preview with sRGB overlay, and signed `.dmg` packaging.
Plus:
**Not this product:** display calibration (`dispwin` / `dispread`), i18n, Windows/Linux print trees, in-process Argyll, App Sandbox.
- **Calibrate Printer** — optional `printcal` / `applycal` session under a `CAL_` basename
- **CGATS import** — `.ti3` / `.txt` / `.cgats` / `.csv`
- **Media recipes and presets** — printer + paper + ink bound to a preset and optional `.cal`
- **Spot Read** — live one-patch Lab/XYZ from the instrument
- **Project files** — `.icceryproj` bookmark over folder, basename, recipe, last ΔE
- **Gamut viewer** — SceneKit Lab hull, sRGB overlay, second-profile compare, click-inspect
- **Settings** — default instrument, ΔE good/warning cutoffs, install location, logging
- Signed `.dmg` packaging with a HiDPI Finder background (Monterey through Sonoma)
**Not this product:** display calibration (`dispwin` / `dispread`), i18n,
Windows/Linux print trees, in-process Argyll, App Sandbox.
## Requirements
- macOS 14+
- Xcode 15.4+ with the macOS 14 SDK and Swift 6.0
- [XcodeGen](https://github.com/yonaskolb/XcodeGen)
- Network once, to fetch Argyll sidecars
To **run** a packaged build:
App Sandbox is **off**. Hardened Runtime is **on**. Entitlements live in `ICCery.entitlements`.
- macOS 12.0 Monterey or later (Intel or Apple silicon)
To **build** on the supported CI/host floor:
- macOS 12 with **Xcode 14.2** (macOS 12 SDK, Swift 5.7)
- [XcodeGen](https://github.com/yonaskolb/XcodeGen) **2.38.0** (Homebrews current
formula needs Xcode 15.3; CI installs the pinned zip via
`scripts/ensure-host-tools.sh`)
- Network once, to fetch Argyll sidecars
- For DMGs: Python 3.9+ and `dmgbuild==1.6.7` in `build/.venv-dmgbuild`
(`INSTALL_DMGBUILD=1 scripts/ensure-host-tools.sh`)
App Sandbox is **off**. Hardened Runtime is **on**. Entitlements live in
`ICCery.entitlements`.
## Build
@@ -49,7 +79,7 @@ cd iccery-v2-mac
git checkout develop
make fetch-argyll # Vendor/Argyll/macos-universal/, ad-hoc signed
make test # xcodegen + xcodebuild build test
make test # xcodegen + xcodebuild build test (host arch)
make universal # ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO
```
@@ -62,9 +92,14 @@ xcodebuild test -scheme ICCery \
ARCHS="$(uname -m)"
```
`project.yml` sets `ARCHS: "$(ARCHS_STANDARD)"`, so a plain `xcodebuild test` (and `make test`) builds universal; the `ARCHS="$(uname -m)"` override narrows it to the host slice.
`project.yml` sets `ARCHS: "$(ARCHS_STANDARD)"`. CI and `make test` override
that with `ARCHS="$(uname -m)"` so unit/UI tests build the host slice only.
Fat binaries are `make universal` / `scripts/package-release.sh`.
Sidecars are **not** in git. `scripts/fetch-argyll.sh` pulls the latest (or `ARGYLL_RELEASE_TAG`) macOS-universal release from `gronod/argyllcms`, extracts to `Vendor/Argyll/macos-universal/`, ad-hoc signs every Mach-O, and fails if `codesign -dvv` or the `instlist` marker is missing.
Sidecars are **not** in git. `scripts/fetch-argyll.sh` pulls the latest (or
`ARGYLL_RELEASE_TAG`) macOS-universal release from `gronod/argyllcms`, extracts
to `Vendor/Argyll/macos-universal/`, ad-hoc signs every Mach-O, and fails if
`codesign -dvv` or the `instlist` marker is missing.
```bash
# optional
@@ -74,9 +109,11 @@ export ARGYLL_RELEASE_TAG=… # default: latest
export GITEA_TOKEN=# private releases
```
`make clean` drops `ICCery.xcodeproj`, `DerivedData`, and `Packages/ICCeryCore/.build`.
`make clean` drops `ICCery.xcodeproj`, `DerivedData`, and
`Packages/ICCeryCore/.build`.
Do not open the generated xcodeproj as the source of truth. Edit `project.yml` and regenerate.
Do not open the generated xcodeproj as the source of truth. Edit `project.yml`
and regenerate.
## Release packaging
@@ -84,11 +121,20 @@ Do not open the generated xcodeproj as the source of truth. Edit `project.yml` a
scripts/package-release.sh # fetch → sign → universal build → verify → DMG
```
The script builds with a fixed derived data path (`build/DerivedData`),
locates `Release/ICCery.app` from it, signs the bundle, recursively verifies
every bundled Mach-O sidecar (`scripts/verify-sidecar-signatures.sh`), and
writes `ICCery-${VERSION}-${BUILD_NUM}.dmg` via `dmgbuild`. Sidecars stay
ad-hoc signed inside the bundle — the app is never `codesign --deep`ed.
The script builds with a fixed derived data path (`build/DerivedData`), locates
`Release/ICCery.app` from it, signs the bundle, recursively verifies every
bundled Mach-O sidecar (`scripts/verify-sidecar-signatures.sh`), builds a
HiDPI TIFF from `Resources/dmg-background.png` (+ `@2x`) via `tiffutil`, and
writes `ICCery-${VERSION}-${BUILD_NUM}.dmg` with `dmgbuild==1.6.7`.
Sidecars stay ad-hoc signed inside the bundle — the app is never
`codesign --deep`ed.
`dmgbuild` is **not** a test-job dependency. The package job sets
`INSTALL_DMGBUILD=1` so `scripts/ensure-host-tools.sh` creates
`build/.venv-dmgbuild`. On the Monterey runner (Python 3.9) that install uses
`PIP_IGNORE_REQUIRES_PYTHON=1` and pins `pip>=24.3,<26.1` (pip 26.1+ needs
3.10). Missing background art is a hard fail (#95).
Environment variables read by the pipeline:
@@ -110,30 +156,47 @@ Resources/ assets; Argyll reference files (not the tools)
Vendor/Argyll/ fetched sidecars (gitignored)
Tests/ICCeryCoreTests/ argv goldens, parsers, stores
Tests/ICCeryUITests/ fixture / mock-binary UI tests
scripts/ensure-host-tools.sh
scripts/fetch-argyll.sh
scripts/package-release.sh
docs/ functional spec + v2 ticket plan
```
`ICCeryPrintKit` (issue #16, Quartz / AirPrint / TargetPrint) is v2.1 and is not in this tree.
`ICCeryPrintKit` (issue #16, Quartz / AirPrint / TargetPrint) is v2.1 and is
not in this tree.
## Architecture
- **Spawn, never link.** Tools resolve through `BinaryResolver` inside the bundle / `Vendor` tree. `$PATH` is not searched. `ARGYLL_NOT_INTERACTIVE=1` is always set.
- **`ProcessManager` actor** owns child lifetime. Streaming tools (`chartread`, `printcal`, etc.) use the event bus; one-shot tools use `runCaptured`. Exclusive `ProcessID` leases. Quit path: `q\n`, ~500 ms, kill; `killAll` on terminate.
- **Argv builders** in ICCeryCore (`TargenArgs`, `PrinttargArgs`, `ChartreadArgs`, `ColprofArgs`, `ApplycalArgs`, `IccgamutArgs`, `ProfcheckArgs`, `LpArgs`, …). UI must not concatenate flags.
- **Atomic artefacts.** Writes go to `*.tmp` then `replaceItemAt`. `applycal` must not replace the input profile on cancel or non-zero exit.
- **Concurrency.** View models are `@MainActor`. No blocking I/O on the main actor. SwiftUI `@Observable` for new state.
- **Print.** Unmanaged `lp` with ColorSync suppression (`AP_ColorMatchingMode` / `AP.ColorMatchingMode`). Captured `NSPrintPanel` options win over derived CUPS keys. Never `lp -o raw`.
- **Spawn, never link.** Tools resolve through `BinaryResolver` inside the
bundle / `Vendor` tree. `$PATH` is not searched. `ARGYLL_NOT_INTERACTIVE=1`
is always set.
- **`ProcessManager` actor** owns child lifetime. Streaming tools
(`chartread`, `printtarg`, `colprof`, …) use the event bus; one-shot tools
(`printcal`, `applycal`, CUPS) use `runCaptured`. Exclusive `ProcessID`
leases. Quit path: `q\n`, ~500 ms, kill; `killAll` on terminate.
- **Argv builders** in ICCeryCore (`TargenArgs`, `PrinttargArgs`,
`ChartreadArgs`, `ColprofArgs`, `ApplycalArgs`, `IccgamutArgs`,
`ProfcheckArgs`, `LpArgs`, `SpotReadArgs`, …). UI must not concatenate flags.
- **Atomic artefacts.** Writes go to `*.tmp` then `replaceItemAt`. `applycal`
must not replace the input profile on cancel or non-zero exit.
- **Concurrency.** View models are `@MainActor`. No blocking I/O on the main
actor. Swift 5.7 / macOS 12: `ObservableObject`, not Observation
`@Observable`.
- **Print.** Unmanaged `lp` with ColorSync suppression
(`AP_ColorMatchingMode` / `AP.ColorMatchingMode`). Captured `NSPrintPanel`
options win over derived CUPS keys. Never `lp -o raw`.
- **SwiftUI ViewBuilder.** Xcode 14.2 / Swift 5.7 still has the ten-child
limit. Split large `VStack`/`Group` trees (#146).
## Tests
```bash
# full suite (host arch)
# full suite (host arch) — same as CI
xcodebuild test -scheme ICCery \
-destination 'platform=macOS' \
ARCHS="$(uname -m)"
# to compile-check both slices instead:
# fat compile-check (not the default test path):
# ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO
# examples
@@ -143,9 +206,20 @@ xcodebuild test -scheme ICCery -destination 'platform=macOS' \
-only-testing:ICCeryUITests/Milestone5UITests
```
UI tests need an unlocked console (`IOConsoleLocked=false`). Mock Argyll / CUPS fixtures live under the test bundles; they must not be treated as proof that a real `.gam` / `.icc` was extracted.
CI (`.gitea/workflows/macos.yml`) runs `build-and-test` then `package` on
`develop` and on `v*` tags. Tags whose name contains `prerelease` skip the
test job and still package. `pull_request` is wired for **`develop` only**.
Hardware gates (real instrument, real printer, Gatekeeper-open `.dmg`) are manual and block release, not compile.
UI tests need an unlocked console (`IOConsoleLocked=false`). Mock Argyll /
CUPS fixtures live under the test bundles; they must not be treated as proof
that a real `.gam` / `.icc` was extracted.
Hardware gates (real instrument, real printer, Gatekeeper-open `.dmg`) are
manual and block release, not compile.
`ArgyllRunnerPrinttargTests.testSuccess` can flake if streaming stdout is
dropped on a fast mock exit; that is a `ProcessManager` drain race, not a
missing fixture.
## Instruments
@@ -156,11 +230,18 @@ Detected via bundled `instlist`:
- SpyderPrint (`p3`)
- SpectroScan (`SS`)
- DTP20 / 22 / 41 / 51
- XY tables (SpectroScan, i1iO) when the `instlist` name matches `/spectro\s?scan|i1io/i`
- XY tables (SpectroScan, i1iO) when the `instlist` name matches
`/spectro\s?scan|i1io/i`
## Docs
Normative spec is [`docs/`](docs/README.md). Implementation order:
| Where | Audience |
|---|---|
| [Wiki](https://git.i3omb.com/gronod/iccery-v2-mac/wiki) | End users — screens, workflow, troubleshooting |
| [`docs/`](docs/README.md) | Functional spec (normative for implementers) |
| [`AGENTS.md`](AGENTS.md), [`BUILD-PLAN.md`](BUILD-PLAN.md) | Agent / branch rules |
Implementation order in `docs/`:
| Doc | Topic |
|---|---|
@@ -168,29 +249,40 @@ Normative spec is [`docs/`](docs/README.md). Implementation order:
| [`docs/03-ipc-and-process-manager.md`](docs/03-ipc-and-process-manager.md) | Spawn / stdin / kill |
| [`docs/04-argyll-binaries.md`](docs/04-argyll-binaries.md) | CLI argv |
| [`docs/06-wizard-and-artefacts.md`](docs/06-wizard-and-artefacts.md) | Gating |
| [`docs/23-assets.md`](docs/23-assets.md) | Icons, DMG chrome |
| [`docs/24-issues-invariants.md`](docs/24-issues-invariants.md) | Bugs that must not return |
| [`docs/26-v2-mac-ticket-plan.md`](docs/26-v2-mac-ticket-plan.md) | Gitea tickets |
| [`docs/PREUAT.md`](docs/PREUAT.md) | Pre-UAT tester kit |
Agent / branch rules: [`AGENTS.md`](AGENTS.md), [`BUILD-PLAN.md`](BUILD-PLAN.md).
## Git
```
develop
└── milestone/m10-studio # M10 integration branch
└── feat/<issue>-<slug> # one issue per branch
develop # integration; PRs land here unless a milestone branch is announced
main # protected release line (PR from develop)
feat/<issue>-<slug>
fix/<issue>-<slug>
```
Feature PRs target the current milestone branch, not `develop`. The milestone branch merges to `develop` when its issues are green. Completion PRs for issues #146#149 target `milestone/m10-studio`; `milestone/m10-studio` merges into `develop` once all milestone gates pass. Do not open umbrella "bugfix" branches that mix tickets.
Open feature/fix PRs against **`develop`**. A `milestone/m…` integration
branch is used only while that milestone is assembling; `milestone/m10-studio`
has been merged and deleted. Do not open umbrella “bugfix” branches that mix
tickets.
`main` is push-protected and requires status check
`macOS CI / build-and-test (push)`. Protected **file** patterns on `main`
block PR merges that touch matching paths — do not set that field to `*`.
## Licence
GUI source: © 2026 Gordon Bolton — see [`LICENCE.md`](LICENCE.md). Viewing and personal evaluation only unless a separate grant says otherwise.
GUI source: © 2026 Gordon Bolton — see [`LICENCE.md`](LICENCE.md). Viewing
and personal evaluation only unless a separate grant says otherwise.
ArgyllCMS binaries fetched into `Vendor/Argyll/` are **AGPLv3**. They stay subprocess-isolated (stdin / stdout / stderr only). Linking them, or spawning via `$PATH`, is a licence break.
ArgyllCMS binaries fetched into `Vendor/Argyll/` are **AGPLv3**. They stay
subprocess-isolated (stdin / stdout / stderr only). Linking them, or spawning
via `$PATH`, is a licence break.
## Related
- [User wiki](https://git.i3omb.com/gronod/iccery-v2-mac/wiki)
- [gronod/argyllcms](https://git.i3omb.com/gronod/argyllcms) — Argyll 3.5.0 fork (`-u` JSON, `instlist`)
- [gronod/ICCery](https://git.i3omb.com/gronod/ICCery) — v1 Tauri application (spec source, not this tree)
+9
View File
@@ -220,6 +220,15 @@ struct ManageMediaDialog: View {
.accessibilityIdentifier("mediaLibraryList")
.frame(minHeight: 260)
if let notice = media.manageApplyNotice {
Text(notice)
.font(.callout)
.foregroundStyle(.orange)
.fixedSize(horizontal: false, vertical: true)
.accessibilityIdentifier("manageMediaNotice")
.accessibilityValue(notice)
}
HStack {
Button("Apply selected") {
if let id = selection,
+21 -24
View File
@@ -41,6 +41,9 @@ final class MediaLibraryViewModel: ObservableObject {
@Published var saveMediaApplyCal = false
/// Inline caption inside the capture sheet (no a11y id roster complete).
@Published var saveMediaError: String?
/// Last failed Apply while Manage is open. The window banner sits
/// behind the sheet on Monterey, so the dialog shows this too (#170).
@Published var manageApplyNotice: String?
/// Pure flow flag the manage sheet's "Capture current" asks the
/// sheet's `onDismiss` to open the capture sheet, avoiding a
@@ -120,24 +123,21 @@ final class MediaLibraryViewModel: ObservableObject {
/// with warning; the refusal is permanent so re-clicking can't help).
@discardableResult
func apply(_ recipe: MediaRecipe) async -> Bool {
manageApplyNotice = nil
guard let r = try? recipe.validated() else {
workflow.wizard.showNotice(
"Media recipe is invalid — not applied.", kind: .error)
return false
return failApply("Media recipe is invalid — not applied.", kind: .error)
}
guard let preset = environment.presetStore.all()
.first(where: { $0.id == r.presetID })
else {
workflow.wizard.showNotice(
return failApply(
"Preset \(r.presetID) no longer exists — recipe not applied.",
kind: .error)
return false
}
guard preset.colourSpace.lowercased() == r.colourSpace.lowercased() else {
workflow.wizard.showNotice(
return failApply(
"Recipe colour space does not match its preset — not applied.",
kind: .error)
return false
}
// Existing #82 mapping: presetSelect jumps, Stage 1/2/4 fields.
@@ -145,8 +145,6 @@ final class MediaLibraryViewModel: ObservableObject {
// Literal per issue: displayName, not the queue id.
workflow.wizard.printerName = r.printerDisplayName
var succeeded = true
// Queue: enumerate fresh via the session's serialized path
// listPrinters uses fixed process ids, so an overlapping
// enumeration would throw duplicateID. An empty result is a
@@ -156,16 +154,14 @@ final class MediaLibraryViewModel: ObservableObject {
workflow.print.selectedPrinter = r.printerID
await workflow.print.reloadSelectedCapabilities()
} else {
workflow.wizard.showNotice(
return failApply(
"Printer \(r.printerDisplayName) is not installed.",
kind: .warning)
succeeded = false
}
} else {
workflow.wizard.showNotice(
return failApply(
"Could not enumerate printers — queue left unchanged.",
kind: .warning)
succeeded = false
}
// Calibration the recipe is authoritative and runs after
@@ -195,10 +191,8 @@ final class MediaLibraryViewModel: ObservableObject {
guard FileManager.default.fileExists(atPath: calPath) else {
workflow.profile.applyCalibration = false
workflow.profile.calibrationFile = calPath
workflow.wizard.showNotice(
return failApply(
"Calibration file is missing: \(calPath)", kind: .error)
refreshStaleness()
return false
}
do {
let staleDays = environment.settingsStore.load().calibrationStaleDays
@@ -215,23 +209,26 @@ final class MediaLibraryViewModel: ObservableObject {
}
} catch {
workflow.profile.applyCalibration = false
workflow.wizard.showNotice(
return failApply(
"Could not load calibration: \(error.localizedDescription)",
kind: .error)
refreshStaleness()
return false
}
} else {
workflow.profile.applyCalibration = false
workflow.profile.calibrationFile = calPath
}
if succeeded {
selectedRecipeID = r.id
workflow.wizard.showNotice("Applied \(r.name)")
}
selectedRecipeID = r.id
workflow.wizard.showNotice("Applied \(r.name)")
refreshStaleness()
return succeeded
return true
}
private func failApply(_ text: String, kind: Notice.Kind) -> Bool {
manageApplyNotice = text
workflow.wizard.showNotice(text, kind: kind)
refreshStaleness()
return false
}
// MARK: - Capture
@@ -148,7 +148,9 @@ final class Milestone10MediaLibraryUITests: XCTestCase {
XCTAssertTrue(apply.waitForExistence(timeout: 10))
apply.click()
let notice = waitFor("noticeText")
// The window banner (`noticeText`) sits behind this sheet on
// Monterey (#170). Assert the in-sheet copy instead.
let notice = waitFor("manageMediaNotice", timeout: 15)
let text = (notice.value as? String) ?? notice.label
XCTAssertTrue(
text.contains("is not installed"),