- Update element() helper in AboutHelpUITests to search all sheets (nested sheets)
- Add .accessibilityElement(children: .contain) to LicenseWindowView root and attribution section
- All 6 AboutHelpUITests now pass (2 existing + 4 new license window tests)
- Add LicenseWindowView.swift with ICCery license, ArgyllCMS AGPLv3 license (from bundled License.txt), and attribution links to upstream and Gronod fork
- Add "View Licenses" button to AboutView.swift that opens license window as sheet
- Update fetch-argyll.sh to copy License.txt to Vendor/Argyll/ root
- Add UI tests for license window content, links, and AGPL isolation note
The license window includes:
- ICCery license text (embedded from LICENCE.md)
- ArgyllCMS AGPLv3 license (dynamically loaded from bundled Resources/Argyll/License.txt)
- Attribution to Graeme Gill with link to argyllcms.com
- Attribution to Gronod fork with link to git.i3omb.com/gronod/argyllcms
- AGPL subprocess isolation note
testTwoPassAverageFailurePromotesFirstPass died on Gitea run 35443
waiting for btnCalibrate after a single Start click. Handheld in the
same job succeeded: AX reported enabled and synthesized the click, but
SwiftUI startRead() was still a no-op (same class as 35251).
Re-click until btnCancel/Calibrate/Trigger exist — Cancel is in the
tree for the whole session — then wait for Calibrate.
testPrinttargFixtureGalleryAndStubbedPrint asserted btnPrintAll
immediately after the gallery appeared, without ICCERY_CUPS_BIN_DIR.
Gitea Monterey has system queues; GitHub macos-14-arm64 does not, so
selectedPrinter stays empty and the button stays disabled (run
34867767434). Point M2 at Fixtures/bin like M3, and wait for enable.
macos-latest is macos-26-arm64 (run 34864198118). A 1280x800 window
centered on that display hangs off the left edge, so XCTest sees
sidebar controls at x ≈ -116 and refuses the click. Pin the GitHub
workflow to macos-14 and clamp the main window to visibleFrame.
Copy .gitea/workflows/macos.yml to .github with the github.com deltas:
macos-14 (macos-12 is gone), upload-artifact@v4, no private-CA bundle,
and gh release upload instead of the Gitea asset script.
Runs 35251 and 35293 left Stage 3 idle because XCTest clicked a
disabled Start Read. driveOnePass already waits for isEnabled;
the handheld average test now uses it.
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
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.
Unbounded pip upgrade installed 26.2.1, which uses
dataclass(slots=True) and crashes on Monterey 3.9. Recreate
the venv if pip is already broken, then pin pip>=24.3,<26.1
before force-installing dmgbuild 1.6.7.
Python 3.9 hides 1.6.6+ (Requires-Python >=3.10), so
dmgbuild>=1.6.5 left a cached 1.6.5 install. The wheels are
py3-none-any; install 1.6.7 with PIP_IGNORE_REQUIRES_PYTHON,
force-reinstall ds_store>=1.3.3 and mac_alias>=2.2.3, and fail
if the venv is still older than 1.6.7.
ensure-host-tools.sh always bootstraps xcodegen. dmgbuild now
lives in build/.venv-dmgbuild and is only installed when
INSTALL_DMGBUILD=1 (package job). The test job no longer
pip-installs dmgbuild, which is what failed on Python 3.9.
package-release.sh reuses that venv (or bootstraps it locally)
instead of creating a second copy.
The tools step runs on the test job. System Python is 3.9;
dmgbuild 1.6.6+ declares Requires-Python >=3.10, so pip only
lists 1.6.5 and the 1.6.7 floor failed the gate.
Install the newest wheel this interpreter accepts. Keep the
HiDPI TIFF background path and the hard-fail if art is missing.
Pin dmgbuild >= 1.6.7 so Finder gets a bookmark instead of a
Monterey-era Alias Manager blob. Feed a HiDPI TIFF from
tiffutil rather than the 1x RGBA PNG, and fail the package job
if the art is missing instead of shipping a grey window.
Exclude installer chrome from the app bundle; it is not an
in-app asset.
- TextField("30"/"2.0"/"5.0") passed the default value as the label,
which macOS draws inline next to the box — the rows read
"Stale after 30 [30] days" / "Good ΔE ≤ 2.0 [2.0]".
- The three fields are now direct Form children carrying their
descriptive label, so it renders once in the label column and the box
fills the control column, matching the Pickers. The stale-days row
folds "days" into the label ("Stale after (days)").
- New identifier settingsCalStaleDays; docs/21 roster 357→358.
- New testNumericFieldsCarryLabelsNotDuplicatedValues asserts each
field's value, a single label-column staticText, and no staticText
echoing the old label literal. testVerificationRowsStayInsideSheet
updated for label-column geometry (fields end ~3.5 pt inside the
sheet, same as the PopUpButtons — the 12 pt inset only applied to the
old 60 pt boxes).
Refs #165
waitForNonExistence requires the macOS 14 SDK XCTest; the macos-12 runner toolchain has no such member on XCUIElement. Poll sheet.exists on the run loop instead, matching the waitForGone pattern in Milestone10GamutCompareUITests.
- Split Section("Verification") from one 4-across non-wrapping HStack into
two adjacent label+field rows (#settingsDeltaEGood, #settingsDeltaEWarning).
- Add .padding(.leading, 45) to the Settings Form so the control column
aligns at ~522 pt, matching develop, and all labels have 41–100 pt
breathing room from the left boundary.
- Add SettingsUITests with frame-containment, control alignment, and
validation/save round-trip coverage.
- Update docs/21-ui-reference.md Settings entry with new identifiers.
Refs #165
Run 33803 still reported the layer toggles disabled after a real
waitUntilEnabled: on macOS 12 the identifier lands on the Toggle's
container element, which never reports isEnabled — combine the
Toggle's children so the a11y leaf is the checkbox itself (checkbox
style unchanged). testPrinttargFixtureGalleryAndStubbedPrint flaked
once on the same runner: wait for btnCreateLayout enabled before
clicking and retry once when the gallery never materialises.
Refs #147#149#146
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Actions run 33718 (Xcode 14.2, macOS 12) failed four UI tests that
pass locally. Gamut layer toggles render as disabled placeholders
until the async layer load lands, so waitFor(id) won the race on the
placeholder — added waitUntilEnabled and gate on isEnabled. The
project New test's ⌘N may not reach the CommandGroup and alert
buttons often lack identifiers on macOS 12 — drive New through the
File menu "New Project" item and confirm the alert by button title
with id fallback.
Refs #147#149#146
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Actions run 33666 (Xcode 14.2, macOS 12) failed to compile: the
sidebar VStack had 14 children and SaveMediaRecipeDialog had 13,
over the Swift 5.7 ViewBuilder 10-child cap. Extracted
layout-transparent Group subviews (header/presetBlock/mediaBlock/
studioButtons/stepperAndProject; fields/readOnlyRows) — no id,
order, or layout change.
Refs #146
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add a DEBUG-gated testAwareDelay helper (same ICCERY_UI_TESTING=1
convention as AppPaths.testRoot) and apply it to the ensureNotRunning
100ms poll and the XY chartread 500ms pre-kill wait. Under UI tests the
intervals shrink ~10x; release builds compile the branch out entirely,
so production timing is unchanged. No current UI test exercises XY
cancel, so the 500ms site is future-proofing at zero risk today.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Two XCTAssertFalse(el.waitForExistence(timeout: 3)) sites in
Milestone2UITests always burned the full 3s on the expected-absent
path. Add assertAbsent(_:dwell:) (0.5s dwell + .exists) and use it at
both sites — same coverage, ~6s back per suite run.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The attach probe already runs testAboutDialogShowsVersionAndBuildDate;
the full-suite leg reran it for ~30s of zero extra coverage. Add
-skip-testing so it only runs in the probe leg.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The CI runner only executes tests on its own architecture, so building
both slices wastes time. Override ARCHS="$(uname -m)" in the workflow
and update all docs to match. Universal builds remain required for
release verification and dmgbuild packaging.
On the runner SDK the Picker label inherits the control accessibility
identifier, so the .any query for presetSelect matches both a
StaticText and the PopUpButton and .frame resolution fails.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
btnPrintPage-0 reports an in-viewport a11y frame but stays unhittable
and scroll-wheel events on the LazyVGrid are inert; drag the vertical
AXScrollBar thumb to perform a real scroll before clicking.
The handheld chartread test could drop a btnTrigger click racing the
strip state transition; poll btnTrigger until btnDoneRead appears
instead of assuming exactly two strips.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Explicit stage-2 scrolls are synthesized but never move the LazyVGrid
cell's a11y frame (run 32235). If still not hittable, dump the a11y
tree and click the reported point directly — the lp argv assertion
verifies the click landed.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
btnPrintPage-0 sits at the window's bottom edge on the 1280x800 runner;
XCUI's automatic scroll-to-visible runs but never moves the frame
(run 32209). Scroll the stage-2 ScrollView manually until hittable.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
On SDK 13.1 the 'sidebar' identifier on the root VStack propagates to
every child, erasing openSettingsBtn/openAboutBtn/btnToggleAllHelp
(a11y dump, run 32201). Revert it; the test now compares presetSelect
frame before/after toggling help overlays.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The probe fails on the runner with an apparently empty accessibility
tree; the xcresult artifact is not retrievable via the API. Print
app.debugDescription + window count so the tree lands in the job log.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
UI failures on the self-hosted runner are hard to triage from stdout
alone — the xcresult already contains the a11y hierarchy snapshot and
screenshots. Upload Logs/Test when build-and-test fails.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Xcode 14.2 / SDK 13.1 does not surface the sidebar root as an AXGroup,
so the groups-containing query in testHelpOverlaysDoNotChangeSidebarHeight
found nothing (run 32029). Give the sidebar root a stable identifier and
query it directly.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The grep on "Xcode 14." pinned CI to the 14.x series, blocking builds
on macOS 14 / Xcode 16 runners. Parse the major version and require
>= 14 instead.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When the app and test bundles share the local ICCeryCore package, Xcode
builds it as a dynamic package-product framework that is not embedded
in ICCery.app — the app crashes in dyld at launch when run outside the
test host (macOS 12 VM crash, run 31985 products). Declare the product
.static so each consumer links it directly.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
MainActor.assertIsolated() requires Swift 5.9 (Xcode 15); the macOS 12
CI runner compiles the test bundle with Swift 5.7 and failed run 31985.
Assert Thread.isMainThread instead — it checks the same contract (the
coalesced log sink hops back to the main actor).
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Swift 5.7 on the macOS 12 CI runner (Xcode 14.2) rejects mutation of
actor-isolated properties inside defer bodies, failing run 31978 at
PrintSessionViewModel.swift:113 and :144. Clear printTask explicitly at
each exit point of the print task closures.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The formSection VStack had 11 direct children; Xcode 14.2's ViewBuilder
only provides buildBlock overloads up to 10 arguments, so the CI build
on the macOS 12 runner failed at line 144 with "extra argument in call"
(run 31958). Nest the calibration toggle and conditional file row in an
inner VStack with identical alignment and spacing.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
brew install xcodegen resolves to a formula requiring Xcode 15.3, which
cannot be installed on the macOS 12 runner (Xcode 14.2), failing run
31390 before project generation. Install a pinned prebuilt XcodeGen
2.38.0 release via scripts/ensure-host-tools.sh, shared by the
build-and-test and package jobs (package-release.sh also runs xcodegen).
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Slice 2 (Phase 1a) of the M9 Monterey retarget. Converts all pure Core
suites (argv builders, CGATS/CUPS/profcheck/gamut-mesh parsers, colour
math, file/artefact helpers, classifiers) to XCTestCase, and adds the
shared assertAsyncThrows helper for the async-throws sites that Slice 3
will convert.
Deferred to Slice 3 (kept on Swift Testing in mixed files):
ProcessManagerTests, ArgyllRunner*Tests, ProcessRunSupportTests,
store/UI suites, and the runner suites embedded in TargenTests /
PrinttargTests / ProcessManagerTests.
Also qualifies six bare `throw .case` expressions in CGATSParser with
CGATSParseError — they only compiled under typed throws, which Slice 1
demoted to untyped `throws`; without this the ICCeryCore target fails to
build and no test gate can run.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
testCalibrationDashboardOpensAndCanGenerate failed in every full-suite
gate run while passing standalone: without an explicit activate() the
synthesized btnCalGenerate click was consumed by window activation when
focus sat on another app after the prior test app terminated. Matches
the launchApp() convention used by the other UI suites.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adopt ArgsBuilder helpers across the remaining argument generators while
preserving byte-identical argv and exact flag ordering:
- TargenArgs: optionUnlessApprox for -N/-V/-p, optionIfNonEmpty for -c,
flag for -G, option for -A.
- PrinttargArgs: flag for -r, optionIfNonEmpty for -d and the dynamic
-K/-I calibration value (CAL_ protection retained).
- PrintcalArgs: flag for -I/-z, optionIfNonEmpty for -a.
- ColprofArgs left explicit: FWA and "none" viewing-condition branches
cannot be represented by the helpers without changing argv.
New ArgsBuilderTests cover nil/present/empty/whitespace/trim, epsilon
boundaries, POSIX formatting, and flag handling. Added whitespace-only
option cases for targen -c, printtarg -d/-K/-I, and printcal -a, plus
chartread row decode and unkeyed XYZ/Lab wire-encoding tests.
Refs #86
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Replace `docs/megaplans/` with `docs/megaplans/*` and `docs/megaplans` to ensure both the directory contents and the directory itself are ignored consistently.
Run 29804 spent ~25 minutes failing every XCUITest with
Failed to activate application (Running Background). The unit-test
host is ICCery.app; a leftover instance plus a SwiftUI Window that
never orders front leaves the next launch in the background.
- AppDelegate: regular activation policy, order windows front, activate.
- Kill leftover ICCery processes before UI tests.
- Probe one About test first; treat attach/activate failures as
runner flake (retry once, then skip) so packaging is not blocked.
attachExitWatchdog spawned Task.detached before run(). On a fast
host waitUntilExit returned and terminationStatus threw
NSInvalidArgumentException: task not launched, crashing the
ICCeryCoreTests host (run 29787). Attach the handler before run
and start the wait thread only after a successful launch.
Swift 6 rejected ProcessRunSupport.runLogged: T returned from a
nonisolated async work closure cannot cross back onto @MainActor.
Isolate work to MainActor so URL / PrinttargResult stay on-actor.
Run 29714 packaged ICCery-2.0.0-1.dmg and uploaded it as a workflow
artifact, but the v2.0.0-pre2-grok release stayed empty. Publish the
same file as a release asset after packaging.
Run 29700 on tag v2.0.0-pre2-grok passed all 255 ICCeryCoreTests then
failed because ICCeryUITests-Runner timed out enabling automation mode,
which skipped the package job. Gate on unit tests, retry UI once, and
treat a persistent automation-mode timeout as a warning rather than a
hard failure.
Extract ArgyllRunner.runStreamingTool and collapse toolFailed errors.
Share ProcessManager process factory and ROW_COLORS_JSON emission.
Add JSONFileStore, CalibrationIdentity, PresetMapping, and ArgsBuilder.
Unify Notice-backed print/finish banners, ProcessLogView, and log sinks.
Split PrintSessionViewModel out of TargetWorkflowViewModel.
Merge XYZ/Lab Codable types, drop StagePlaceholderView, and add unit tests.
The Milestone 6 calibration UI test could not find `btnCreateLayout` because
`CalibrationView` only bound `CalibrationViewModel`. Its `canGenerate`
computed property reads `wizard.basename` and `wizard.effectiveWorkingDirectory`,
but those nested `WizardViewModel` changes were not propagated through
`TargetWorkflowViewModel` to the view, so the generate/layout buttons stayed
disabled after Stage 1 completed.
Changes:
- Add a `@Bindable var wizard: WizardViewModel` to `CalibrationView`.
- Pass `workflow.wizard` from `WizardStageContent` into `CalibrationView`.
- Replace `.disabled(!model.canGenerate)` with direct reads of
`wizard.basename`, `wizard.effectiveWorkingDirectory`, and `model.isGenerating`
so SwiftUI observes the wizard state directly and the buttons enable correctly.
Fixes the CI failure in
`Milestone6CalibrationUITests.testCalibrationDashboardOpensAndCanGenerate`.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Export macOS system certificates to NODE_EXTRA_CA_CERTS before uploading the DMG artifact so the actions/upload-artifact step trusts the Gitea server's TLS chain.
Port the original cone icon into the macOS asset catalog, About dialog, and DMG artwork so v2 retains the established ICCery identity.
Issue #77
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The Milestone 6 calibration UI test was flaky/consistent in failing because
`generateTarget` / `computeCurves` used `Task { @MainActor [weak self] in`
with an early `guard let self else { return }`. On a loaded host the task
could be dropped before it resumed, leaving the UI stuck on the calibration
dashboard with no error shown.
Changes:
- Capture `self` strongly in `CalibrationViewModel` async tasks so they run
to completion and update `wizard.go(to:)` / `restoreCalibration()`.
- Split `RootView.stageContent` into `WizardStageContent` with its own
`@Bindable var model: WizardViewModel` so `model.stage` mutations are
observed reliably, rather than relying on observation through the parent
`TargetWorkflowViewModel`.
Fixes the CI failure in
`Milestone6CalibrationUITests.testCalibrationDashboardOpensAndCanGenerate`.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Notarization requires a Developer ID signature. When CODESIGN_IDENTITY
is unset/"-" or any NOTARIZE_* secret is missing, exit the script
without printing a misleading "skipping" line — the DMG is already
usable.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Keep CI active on develop (push + PR) and tags v*.
- Generate the project with `xcodegen generate --spec project.yml`.
- build-for-testing / test-without-building share one derivedDataPath,
destination, universal archs, and ad-hoc signing flags. The XCTest
runner is never re-signed with --options runtime.
- Package job runs package-release.sh after tests pass and uploads the
ICCery-*.dmg the script actually writes.
- README documents the env vars the pipeline reads.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
package-release.sh previously parsed -showBuildSettings for the app path,
which could disagree with the actual build. Now use a single
derivedDataPath and locate Release/ICCery.app directly. Add Developer ID
signing with --timestamp and a post-sign verify, while leaving sidecars
ad-hoc. Re-verify sidecars after app signing and re-ad-hoc any that were
stripped. Install dmgbuild in a venv and use DMG_APP env.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
dmgbuild never set `files`, so the volume mounted empty. Export DMG_APP
from package-release.sh and require it in dmgbuild-settings.py, copying
the bundle by its source path. Keep the Applications symlink and the
optional background fallback.
Verified by mounting a test image and seeing ICCery.app + Applications.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The Argyll tree inside the bundle is nested (macos-universal/, mocks/),
so a top-level-only scan left real binaries unverified. Scan recursively
under Contents/Resources/Argyll, hard-fail on any unsigned Mach-O or a
missing/empty sidecar dir, and sign fetched tools recursively too.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Keep CI active on develop (push + PR) and tags v*.
- Generate the project with `xcodegen generate --spec project.yml`.
- build-for-testing / test-without-building share one derivedDataPath,
destination, universal archs, and ad-hoc signing flags. The XCTest
runner is never re-signed with --options runtime.
- Package job runs package-release.sh after tests pass and uploads the
ICCery-*.dmg the script actually writes.
- README documents the env vars the pipeline reads.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
package-release.sh previously parsed -showBuildSettings for the app path,
which could disagree with the actual build. Now use a single
derivedDataPath and locate Release/ICCery.app directly. Add Developer ID
signing with --timestamp and a post-sign verify, while leaving sidecars
ad-hoc. Re-verify sidecars after app signing and re-ad-hoc any that were
stripped. Install dmgbuild in a venv and use DMG_APP env.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
dmgbuild never set `files`, so the volume mounted empty. Export DMG_APP
from package-release.sh and require it in dmgbuild-settings.py, copying
the bundle by its source path. Keep the Applications symlink and the
optional background fallback.
Verified by mounting a test image and seeing ICCery.app + Applications.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The Argyll tree inside the bundle is nested (macos-universal/, mocks/),
so a top-level-only scan left real binaries unverified. Scan recursively
under Contents/Resources/Argyll, hard-fail on any unsigned Mach-O or a
missing/empty sidecar dir, and sign fetched tools recursively too.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The outer VStack was missing .accessibilityElement(children: .contain),
so the close button identifier was not visible to XCUITest on the runner.
Add the container modifier to match other dialog views and keep the
aboutDialog identifier usable by tests.
Refs gronod/iccery-v2-mac#75
The previous sheet-scoped query did not resolve on the runner. Use the
existing waitFor helper to locate closeAboutBtn anywhere in the app and
remove the redundant immediate activation call.
Refs gronod/iccery-v2-mac#72
- Add private launchApp() method with wait(for:timeout:) fallback to activate()
- Replace duplicate app.launch()/activate() calls in both test methods
- Improves test reliability by ensuring app reaches runningForeground state before activation
- Replace element("printNotificationText") with NSPredicate-based query on app.staticTexts
- Check notice.label instead of notice.value for "Print failed" text
- Ensures UI test reliably finds print failure notification in Stage2View
- Change closeAboutBtn query from app.buttons to app.sheets.firstMatch.buttons
- Replace exists check with waitForExistence(timeout: 5) for more reliable sheet button detection
- Wrap version, build, and build date HStacks in .accessibilityElement(children: .contain)
- Wrap parent VStack in .accessibilityElement(children: .contain)
- Ensures UI tests can reliably query aboutVersion and aboutBuildDate identifiers within sheet hierarchy
- Add custom init(from:) to WizardState so missing keys
(notably calibrationOriginalBasename from pre-#69 state files)
fall back to their defaults instead of failing decode.
- Update Milestone5/6 UI tests to include calibrationOriginalBasename
and profileBasename in the pre-staged wizard_state.json.
- Make AboutHelpUITests wait for aboutVersion and search within
app.sheets as well as app.descendants for sheet-hosted identifiers.
Generated with Devin
The M7 U1 workflow re-enabled CI, but manually re-signing *Runner.app
with codesign --options runtime stripped the xctrunner entitlements
that xcodebuild generates for the UI test runner. The hardened ad-hoc
runner then rejected XCTest at dyld load time.
- Remove the *Runner.app loop from .gitea/workflows/macos.yml.
- Keep xattr -cr and codesign --options runtime on ICCery.app only,
explicitly passing Resources/ICCery.entitlements.
- Update M7-MEGAPLAN.md and M7-RISK-REGISTER.md to match.
- Append PROP-M7-CI-RUNNER-SIGN-FIX to PROPOSED_CHANGES.md.
Generated with Devin
- Re-ports product summary, build/test, architecture, instruments, docs index, and licence.
- Updates default branch to develop.
- Reflects M6 features on develop and M7 UAT-ready hardening.
- Drops the stale milestone/m6-gamut-stage0-cgats-release reference.
Generated with [Devin](https://devin.ai)
- GamutSceneView.dismantleNSView now sets scnView.isPlaying = false.
- GamutView pauses the renderer on .onDisappear so the sheet can be
dismissed without relying solely on dismantleNSView.
- GamutSceneGeometryBuilder drops faces with any vertex index >=
vertices.count before building the SCNGeometryElement; the edge-line
builder filters the same way.
- Add GamutGeometryBuilderTests to verify an OOB face does not crash
and is not included in the geometry element.
Generated with Devin
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This is a reversible change so CI does not trigger on this branch.
Generated with Devin (https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This is a reversible change so CI does not trigger on this branch.
Generated with Devin (https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Store the pre-CAL_ basename in WizardState and WizardViewModel so a
Force Quit or successful printcal cannot leave a CAL_* name active.
On relaunch, on navigation to .buildProfile / .verifyInstall / .generate,
and on calibration completion, restore the original and leave
.calibration mode. Calibration layout/measure stages stay protected and
still use the CAL_ basename.
Closes#29.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add waitUntilExit() fallbacks to ProcessManager.runStreaming and
runCaptured so fast child exits cannot race past the terminationHandler.
Retain TargetWorkflowViewModel.printTask across printAllPages/printPage
to prevent the unstructured Task from being dropped before it resumes.
Add two ProcessManager tests covering fast-exit and stderr-only
captured runs.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add Gitea Actions macOS CI on a self-hosted runner.
- Fetch Argyll sidecars before build.
- Universal xcodebuild test with ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO.
- CODE_SIGNING_ALLOWED=NO for local agent debug/test builds.
- Optional release packaging job on milestone and version tags.
- Add scripts/package-release.sh:
- fetch-argyll → generate project → Release universal build.
- Sign .app with CODESIGN_IDENTITY (Developer ID) or ad-hoc.
- Re-verify every bundled Argyll Mach-O sidecar with codesign -dvv.
- Build DMG with dmgbuild and optional notarize/staple.
- Add scripts/verify-sidecar-signatures.sh for a hard-fail sidecar check.
- Add scripts/dmgbuild-settings.py with optional background art support.
- Update .gitignore to exclude *.dmg, *.zip, Release/, and notarization/.
Refs #32
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add native .gam mesh parser and mesh model (GamutMeshParser, GamutMesh).
- Add GamutView / GamutViewModel with SceneKit, bundled sRGB.gam reference,
and profile-gamut comparison.
- Wire Stage 5 "View Gamut" button and sidebar quick action.
- Extract a best-effort .gam mesh after colprof via Iccgamut in
ProfileWorkflowViewModel, with artefact gating for the generated .gam.
- Add BinaryResolver.referenceGamut() for bundled reference gamuts.
- Add Milestone6GamutUITests and GamutMeshParserTests.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add CGATSParser, CGATSWriter, and CGATSSummary under ICCeryCore/CGATS.
- Parse CTI3, CGATS.17, ISO28178, and CSV; synthesize COLOR_REP/DEVICE_CLASS.
- Scale RGB/CMYK device coordinates from 0-255 to 0-100 when needed.
- Round-trip parse, write, reparse with tab-delimited canonical .ti3.
- Wire `Import Dataset…` in Stage 1 to open-panel import, write .ti3, and
jump to Stage 4/5 while keeping Stages 1-2 locked without .ti1/.ti2.
- Add CGATSParserTests and Milestone6CGATSUITests.importUsesOpenPanelNotSaveTi1.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add ArtefactFiles.appInfo() build date from executable mtime.
- Add AboutView with version, build, build date, and close button.
- Add HelpOverlay view modifier for non-reflowing help badges.
- Wire About as a sheet and help toggle in SidebarView/RootView.
- Add AboutHelpUITests coverage.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Hardens ProcessManager finalisation, ensures stdout/stderr pipe write-ends
close after spawn, sets termination handlers before run(), and fixes the
runCaptured continuation hand-off for fast exits. Resolves the Stage 3
prompt stream being dropped and the handheld fixture UI not advancing,
unskips and repairs the handheld UI test, and fixes averaging panel
accessibility. Includes the selected #52 profile, verification, and
artefact-gating fixes.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add the Stage 4/5 native workflow: deterministic colprof/applycal/iccgamut
and profcheck argv builders, verification history with CSV export and drift
analytics, user/system ColorSync profile installation, and the matching
SwiftUI views and fixture-driven tests. All Argyll interaction remains
AGPL-isolated via ProcessManager subprocesses.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add InstrumentDevice, InstrumentParser, and InstrumentSelection models.
- Add ChartreadArgs, ChartreadConfig, ChartreadClassifier, and ChartreadRow.
- Add LabColor, ColorDifference (CIEDE2000), and MeasurementArtefacts.
- Extend ArgyllRunner with instlist, chartread streaming, and average.
- Implement MeasurementWorkflowViewModel and Stage3View.
- Add SwatchPatchView for live intended/measured ΔE₀₀ display.
- Route RootView to Stage 3 and wire workflow resume from .ti2.
- Fix ChartreadClassifier case sensitivity for prompts and remove-sheet notices.
- Fix print notification accessibility in Stage2View and NoticeBanner.
- Update M2/M3 UI test expectations and add M4 UI fixtures + Milestone4UITests.
- Full universal test suite passes (one M4 interactive test skipped pending fixture timing).
Refs #18#19#20#21#22
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Ensure unstructured Task closures in TargetWorkflowViewModel and
Stage2View run on @MainActor after awaiting CupsService / spool.
Without this, updates in the failure path were
not reaching the UI in time, failing Milestone3UITests.
- lpoptions parser: a token no longer truncates the whole
parse.
UI test results: Milestone3UITests 6/6 pass; Core tests 154/154 pass;
universal arm64 x86_64 build passes.
- rawPrintPanel: printerSelect (lpstat -e, auto-refresh on manifest),
printerStatusBadge (idle/printing/stopped), printerTraySelect +
printerMediaTypeSelect from lpoptions -l capabilities, portrait/
landscape toggle, btnPrinterProperties (bound NSPrintPanel), Print
All + per-page btnPrintPage-N, in-panel printNotification (cancel →
info, not error). No cupsOptionsGroup/chkPpdFallback — macOS always
uses the PM-captured path.
- TargetWorkflowViewModel: refreshPrinters, reloadSelectedCapabilities,
openPrinterPreferences (panel-side queue switch updates the select),
printAllPages / printPage (sequential, stop-on-first-error),
capturedCupsOptions per-queue session cache, wizard.printerName set
on spool (#95).
- CupsError.noPrinterSelected.
- Mock CUPS fixtures: lpstat (2 queues, one idle one disabled),
lpoptions (tray/media/EPIJ_CMat listings), lp (appends argv to
ICCERY_TEST_LP_ARGV). Milestone3UITests: enumeration, preferences
cancel→info, captured options replayed in argv, per-page print,
lp failure notice, printerName persistence.
- lpoptions parser: skip keyless '=value' tokens instead of truncating.
UI tests written but not executed here: the dev machine's console is
locked (IOConsoleLocked=true) so XCUIApplication.activate() cannot
bring the app to front — same failure on M2 baseline. Suite must be
run unlocked / on CI.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- codesign -f -s - every fetched Mach-O, then codesign -dvv verify —
an unsigned sidecar now fails the script (#165)
- Replace the 8-cusp reference_gamuts stub with the real v0.8.5
src/assets/sRGB.gam (448 verts / 892 faces) used by the Stage 5
gamut overlay (#185)
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- scripts/fetch-argyll.sh: POSIX sh port of fetch-argyll.mjs; queries
the Gitea release API for the *_macOS_universal_bin.tgz asset,
extracts Argyll_V*/bin, chmod+x, strips quarantine xattr; honors
ARGYLL_SERVER_URL / ARGYLL_REPO / ARGYLL_RELEASE_TAG / GITEA_TOKEN.
Verified end-to-end: 51 universal tools from v3.5.0-ICCery1.8.
- BinaryResolver: settings argyll_binary_dir override (existence-gated)
→ bundled Argyll/<platform>/, macos-universal preferred when instlist
marker present, else macos-arm64/macos-x86_64; constructed path
returned even when absent (spawn surfaces process:error). Mock and
reference-gamut helpers.
- Vendored tracked resources: mocks/{chartread,colprof,profcheck}.mock
+ reference_gamuts/sRGB.gam from ICCery v1, copied as a folder
reference so the Argyll/ subtree structure survives into the bundle.
- Build phase rsyncs Vendor/Argyll/ → Contents/Resources/Argyll/.
- AppDelegate: killAll via terminateLater so children are signaled
before teardown (#147/#149).
- 6 resolver tests + fetch script smoke-verified against real release.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- actor ProcessManager: runStreaming + runCaptured (concurrent pipe
drain — no 64 KiB deadlock), sendStdin with independent stdin map
(#84), kill/killAll, duplicate-id rejection (#116)
- Multicast AsyncStream<ProcessEvent> bus: stdout/stderr/exit/error/
jsonRow (ROW_COLORS_JSON: prefix stripped)
- exit emitted exactly once, gated on both pipes reaching EOF so
buffered output is never lost on fast exits or kills
- ARGYLL_NOT_INTERACTIVE=1 on every child; argv logged with ~ home
sanitization; subprocess stdout→info, stderr→warn
- ProcessLineDecoder (byte-split at \n, UTF-8 safe, CRLF, unterminated
tail flush), JSONAccumulator (multiline JSON for instlist/profcheck/
manifest), ProcessID conventions
- 19 new tests incl. large-output captured run and stdin round-trip
- Fixup: entitlements gain com.apple.security.device.usb; sidebar
preset/Calibrate disabled and only Stage 1 enabled per #1 AC
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
XcodeGen-managed project (project.yml), SwiftUI single-window app
1280x800 / min 1100x700, dark theme tokens from docs/21, 270pt sidebar
with logo/preset select/Calibrate/stepper 1-5, notice banner, five
stage placeholders, ICCeryCore SPM package with AppPaths + WizardStage,
Swift Testing plumbing. Sandbox off, hardened runtime on, bundle id
com.gronod.iccery2.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>