Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
35 KiB
26 — ICCery v2 Mac: reviewed Gitea ticket plan
Status: reviewed, executable. This is the plan to file — not the draft reviewed in chat.
Product: ICCery v2, macOS-only SwiftUI/AppKit rewrite.
Repo: gronod/iccery-v2-mac on git.i3omb.com.
Spec: chapters 01–25 (vendor these into the new repo; do not treat the live spec site as archival).
Agent: Devin (gitea MCP: label_write, milestone_write, issue_write). Local git for bootstrap files.
Changes from the 2026-09-08 draft
| Draft | Reviewed plan |
|---|---|
macOS 12.0 + @Observable |
macOS 14.0 Sonoma. @Observable is 14+; SwiftUI 12 is not a viable wizard shell. v0.8.5’s 12.0 floor was WKWebView/#225, which is gone. |
Bundle id com.gronod.iccery |
com.gronod.iccery2 until v1 is retired (settings, logs, Launch Services collide). |
M3 exit = lp and Quartz/TargetPrint |
M3 = v0.8.5 lp path only (issues 12–15, 17). TargetPrint is v2.1 (issue 16, milestone Later). Do not mix ColorSync dictionaries. |
| Issue 2 = streaming spawn only | Issue 2 also ships runCaptured (printcal/applycal). |
| Settings dialog in M6 (issue 31) | Settings UI in M1 (issue 5). Issue 31 is About + help overlays only. |
| Calibrate Printer live in issue 1 | Disabled/hidden until issue 29. |
colprof -u / targen -u “document later” |
Do not pass -u in v2.0 (parity; colprof -u collides with upstream white-point -u). |
Skip/Undo s/u “verify later” |
Drop mock keys. Real strip mode treats unknown letters as trigger. |
| Issue 3 refuses unsigned sidecars | Fetch script ad-hoc signs (codesign -s -) then codesign -dvv must succeed (#165). |
Gitea-hosted macos-latest |
Self-hosted Mac runner (or GitHub Actions mirror). xcodebuild -destination macOS is host-arch, not universal. |
| Gamut “SceneKit or Metal” | SceneKit. |
Bootstrap via MCP create_or_update_file |
Git commit locally, then push. Do not also MCP-write the same files. |
Locked product decisions
- Stack: SwiftUI (
@Observable,@MainActorview models) + AppKit for printing/panels. No Tauri, no Rust, no WebView. - Floor: macOS 14.0, universal
arm64+x86_64. - AGPL: never link Argyll. Spawn with piped stdio +
ARGYLL_NOT_INTERACTIVE=1on every child (streaming and captured). - Print v2.0: NSPrintPanel (settings capture, “Use Settings”) →
lpwith both-o AP_ColorMatchingMode=AP_ApplicationColorMatchingand-o AP.ColorMatchingMode=AP_ApplicationColorMatching. Never-o rawon macOS. - Print v2.1:
ICCeryPrintKit(TargetPrint folded), own ColorSync vocabulary (PMColorMatchingMode=APCustomColorMatching). Public API remainsTargetJobJSON +--job. - Identity: product name ICCery, original cone SVG/app icon. Bundle
com.gronod.iccery2. - App Sandbox: OFF. USB instruments, CUPS/
lp, ColorSync install,dlsymof private PrintCore SPI. Hardened Runtime on; entitlements documented in issues 1 and 32. - No Windows/Linux. No display cal (#90). No i18n (#96).
Execution sequence
- In
~/Projects/iccery-v2-mac:git push -u origin main(existing root commit). - Create
developfrommain. CommitAGENTS.md,BUILD-PLAN.md,LICENCE.md, vendoreddocs/(this spec set),brand/ICCery-logo.svg+app-icon.svg. Pushdevelop. - MCP
label_write→ create every label. Save ids. - MCP
milestone_write→ create M1–M6 + Later. Nodue_on. Save ids. - MCP
issue_write→ create issues in the order below. Passmilestoneid +labelsids.issue_readusesissue_number, notindex. - If Gitea numbering does not start at 1, patch
BUILD-PLAN.mddependency numbers to match. - Verify:
milestone_read list(7 milestones), spot-check 3 issue bodies.
Do not create issues until labels and milestones exist.
Repo bootstrap
AGENTS.md
- SwiftUI + AppKit; min 14.0; universal; bundle
com.gronod.iccery2. - AGPL boundary: never link Argyll; spawn only;
ARGYLL_NOT_INTERACTIVE=1. - Two spawn APIs: streaming
ProcessManager(actor) andrunCaptured(printcal/applycal only). - No blocking subprocess I/O on
@MainActor. Do not hop to main per stdout line (colprof dots). - Argyll flag cheat-sheet: spec 25 (
-d/-r/-R/-u/-Ydiffer per tool). - Artefact gating on disk; atomic writes (
.tmp+ rename); user strings via SwiftUITextonly. - Branching:
develop←milestone/mN-<name>←feat/<issue#>-<slug>; PRs via Gitea MCP. - Labels: every issue/PR has
Project/ICCery-v2+ oneFeature/*orBug/*+Priority/*. - Verify:
xcodebuild test -scheme ICCery -destination 'platform=macOS' ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO; sidecarcodesign -dvv. - Private ColorSync SPI: 2-arg
(PMPrintSession, CFStringRef) -> OSStatus. Never pass integer1.
BUILD-PLAN.md
- Milestone map below + mermaid branch taxonomy (quoted node labels).
- Pointer to vendored
docs/. - Each milestone: CI/mock gate and hardware gate listed separately.
LICENCE.md
- GUI: proprietary EULA (copy from v0.8.5).
- Bundled Argyll sidecars: AGPLv3, subprocess-only,
License.txtshipped beside binaries.
Labels
Create exactly these (MacMonitor taxonomy):
| Name | Color | Notes |
|---|---|---|
Project/ICCery-v2 |
#007acc |
every issue/PR |
Feature/Architecture |
#7057ff |
|
Feature/Backend |
#008672 |
|
Feature/UI |
#d73a49 |
|
Feature/DevOps |
#fbca04 |
|
Bug/Architecture |
#b60205 |
future |
Bug/Backend |
#b60205 |
future |
Bug/UI |
#b60205 |
future |
Bug/DevOps |
#b60205 |
future |
Priority/Critical |
#d73a49 |
|
Priority/High |
#e99695 |
|
Priority/Medium |
#fef2c0 |
|
Priority/Low |
#c2e0c6 |
Milestones
Each milestone has an interactive test gate. Split CI/mock vs hardware. Do not start the next milestone until both gates for the current one pass (hardware may be “N/A” only for M1).
M1 — Foundation & process core
CI/mock: app launches; wizard shell; ProcessManager spawn instlist (mock or real) with live log; artefact gating unit tests; settings persist + dialog.
Hardware: none required.
Issues 1–6.
M2 — Target generation & layout (Stages 1–2)
CI/mock: targen → .ti1; printtarg → .ti2 + TIFFs; manifest + gallery; resume; presets. Printing stubbed.
Hardware: none.
Issues 7–11.
M3 — macOS unmanaged printing (lp path)
CI/mock: lpstat/lpoptions parsers; build_lp_args golden vectors (both AP_* always present); option filter; cancel → nil.
Hardware: Preferences opens driver PDE on a real Epson or Canon queue; colour matching off/grayed; printed TIFF measures unmanaged (no ColorSync transform).
Issues 12–15, 17. Not issue 16.
M4 — Measurement (Stage 3)
CI/mock: chartread.mock end-to-end; classifier 39+ fixtures; swatch ΔE₀₀; snapshot/average.
Hardware: Detect real instrument; one strip (or XY) through Done & Save → .ti3.
Issues 18–22.
M5 — Profile, verification & install (Stages 4–5)
CI/mock: colprof → .icc; profcheck JSON parse; history atomic write; install copy into a temp ColorSync-like dir.
Hardware: full .ti1→.icc on a real chart; profile visible in ColorSync Utility.
Issues 23–27.
M6 — Gamut, Stage 0, CGATS, polish & release
CI/mock: .gam parser fixtures; cal argv builders; CGATS round-trip; xcodebuild test; fetch-argyll + ad-hoc sign; dmgbuild.
Hardware: Stage 0 on a real printer; gamut view of a real profile.
Issues 28–32.
Later — Quartz / TargetPrint (v2.1)
Not a v2.0 exit gate. Issue 16 only.
Ticket body template
Every issue body uses this structure (fill all sections):
## Summary
## Spec refs
## Scope
## Implementation notes
## Rewrite invariants
## Dependencies
## Test
- CI/mock:
- Hardware:
## Acceptance criteria
Issue tickets
Create in this order. Titles are the Gitea titles.
M1
Issue 1 — App scaffold & wizard shell
Labels: Project/ICCery-v2, Feature/Architecture, Feature/UI, Priority/Critical
Milestone: M1
- Xcode project
ICCery, bundle idcom.gronod.iccery2, SwiftUI App lifecycle, macOS 14.0, universalarm64+x86_64. - Dark tokens from v0.8.5: bg
#1e1e1e, panel#252526, text#d4d4d4, accent#007acc, border#333. Window 1280×800, min 1100×700. - App Sandbox OFF. Hardened Runtime ON. Entitlements: USB (
com.apple.security.device.usb), Apple Events /openfor ColorSync Utility later, no network client required at M1. Document inTargetPrint.entitlements-style fileICCery.entitlements. - Sidebar 270px: original
ICCery-logo.svg(do not redraw), settings/about buttons, disabled preset select, disabled Calibrate Printer + cal chip (enable in 11 / 29), stepper 1–5. - Main: notification banner + one visible stage. Stage 0 is not in the stepper.
- First paint: native dark
NSAppearance— hidden-until-paint is not required (no WebView). - Spec: 01, 02, 21, 23.
- Invariants: none of #225 WebView workarounds.
- Deps: none.
- Test CI:
xcodebuild build; window min-size; only Stage 1 enabled. Hardware: N/A. - AC: launches dark; stepper 5 stages; logo matches v0.8.5 SVG; Calibrate Printer disabled; sandbox disabled in entitlements plist.
Issue 2 — ProcessManager: spawn / stdin / kill / captured / event bus
Labels: Feature/Architecture, Feature/Backend, Priority/Critical
Milestone: M1
ProcessManageractor.- Streaming:
spawn(id, binary, args, cwd)— reject duplicate live ids; pipe stdin/stdout/stderr;ARGYLL_NOT_INTERACTIVE=1; take handles at spawn; line-split stdout; lines startingROW_COLORS_JSON:(17 chars, space after colon) →jsonRow(prefix stripped), never also asstdout;sendStdin(id, Data)exact bytes + flush;kill(id)close stdin then terminate;killAll(); reap →exit(id, code)with naturalunwrap_or(0)/ killedunwrap_or(1). - Captured:
runCaptured(binary, args, cwd) -> (code, stdout, stderr)— same env, sameresolve_binary, samekillAllcoverage. Used only byprintcal/applycal(issue 29 / 24). No event bus. - Events:
AsyncStream—stdout,stderr,jsonRow,exit,error. Do not hop to@MainActorper line. - ProcessManager does not JSON.parse pretty-printed
instlist/printtarg/profcheckoutput. Callers accumulate stdout. killAllonNSApplication.willTerminateand last-window close (#147, #149).- Log argv with home rewritten to
~. - Spec: 03, 04 §0.3–0.5.
- Invariants: #84 stdin independent of wait; #116 exclusive ids; #134 env var; #147/#149 killAll on quit.
- Deps: 1 (app target), 3 (resolve — may stub paths in tests).
- Test CI: duplicate-id; stdin survives while wait runs; json-row isolation; killAll N children; captured returns full stdout. Hardware: N/A.
- AC: unit tests listed above pass.
Issue 3 — Argyll sidecar fetch & binary resolution
Labels: Feature/DevOps, Feature/Backend, Priority/Critical
Milestone: M1
fetch-argyllpulls Gronod tagv3.5.0-ICCery.1.xintoResources/argyll/{macos-universal,macos-x86_64,macos-aarch64}/+mocks/+reference_gamuts/+License.txt. Not git blobs (#127).- After fetch:
codesign -s -every Mach-O, thencodesign -dvvmust succeed. Fail the script if still unsigned (#165Killed: 9). resolve_binary(name): settingsargyll_binary_dirif the file exists → bundled dir; prefermacos-universalonly when that folder containsinstlist. Do not search$PATH.- Ship real
sRGB.gamfrom v0.8.5src/assets/sRGB.gamas an app resource (not the 8-cusp stub inreference_gamuts/) — consumed by issue 28 (#185). - Mocks (
chartread.mock,colprof.mock,profcheck.mock) are first-class; M4/M5 CI must run them. - Spec: 04 §0.1/§0.6, 05 §8–10.
- Deps: 1.
- Test CI: fetch (or fixture) + resolve override + unsigned binary fails check. Hardware: N/A.
Issue 4 — Wizard state machine & artefact gating
Labels: Feature/Architecture, Feature/Backend, Priority/Critical
Milestone: M1
WizardState:currentStage,basename,cwd,printerName,sessionMode,profileBasename.verifyStageArtefactsfrom disk:.ti1/.ti2/.ti3/.icc|.icm. Forward gated; back always; re-validate on window focus + stage entry (#151).- Basename: no
/,\,... Empty cwd illegal → Documents → Home → app-data (#59). No placeholder basenames (#60). - Stage 4 is not unlocked by
.ti2. Canonical.ti3means accepted measurement only (#109/#110). - Spec: 06, 02.
- Deps: 1.
- Test CI: gating matrix; focus re-lock. Hardware: N/A.
Issue 5 — Settings store, logging & settings dialog
Labels: Feature/Backend, Feature/UI, Priority/High
Milestone: M1
settings.jsonunder app-data forcom.gronod.iccery2(do not read v1’s folder unless a future migration ticket says so).AppSettingsdefaults:argyll_binary_dirnil,default_instrumentnil (stored, never applied to argv),log_levelnil,delta_e_good_max2.0,delta_e_warning_max5.0 (good < warning, both ≥ 0),custom_presets[],enable_i1pro2_ledsfalse,calibration_stale_days30,default_install_locationuser,ask_before_overwrite_profiletrue,open_color_panel_after_installfalse. Invalid JSON → defaults.- Logger:
~/Library/Logs/com.gronod.iccery2/iccery.log, 5 MiB × 5. Level applied at startup and on save (#158). - Settings dialog in this ticket (not M6): argyll dir picker, default instrument (display-only caveat), i1Pro2 LEDs, ΔE fields + inline error, stale days, install location, log level, open-log-folder / copy-path / copy-excerpt.
- Spec: 22, 02 logging, 21 settings ids.
- Deps: 1, 6 (directory picker — 6 may land in parallel; stub if needed).
- Test CI: validation strings; live log-level change; dialog bindings persist. Hardware: N/A.
Issue 6 — File dialogs & artefact helpers
Labels: Feature/Backend, Priority/High
Milestone: M1
- One NSOpenPanel/NSSavePanel wrapper per purpose — never a shared picker:
selectTargetFilesave.ti1selectExistingTargetopen.ti1/.ti2selectProfileFile.icc/.icm/.mpp(not.ti*, #172)selectSpectrumFile.spselectDatasetFileopen.ti3/.txt/.cgats/.csv(#211)selectDirectory,selectCsvSavePath,selectCalFile
readTiffPreviewPng: host-side TIFF→PNG, max edge 1200, high-quality. Never feed TIFF toNSImageViewas the gallery source (#58).parseTi2Header:TARGET_INSTRUMENT,NUMBER_OF_SETS,NUMBER_OF_PAGES, sibling.ti1.resolveSafeCwd,getDefaultWorkingDir,getAppInfo,getProfilePath(existing.icmelse.icc, macOS default.icc, #69).- Spec: 04 §0.2/§13, 24 #103/#210/#211.
- Deps: 1.
- Test CI: filter + mode per picker; TIFF→PNG; ti2 fixtures. Hardware: N/A.
M2
Issue 7 — Stage 1: targen UI & argv builder
Labels: Feature/UI, Feature/Backend, Priority/Critical
Milestone: M2
- Always
-v -d {2|4}. RGB-d 2, CMYK-d 4. Do not passtargen -u. - Patch presets 400 / 800 default / 1500 / custom; honour
-f N(#44 — omitting yields Argyll 836). - White
-e, black-B(RGB default 4, CMYK 0 on colour-space switch). - Advanced:
-g-s-n;-Nskip if ≈0.50;-cprofile picker;-G;-Aeven at 0.10 (no default-skip); algorithm-t|-r|-R|-q|-Q|-i|-Iwithofps/ default = no extra flag; CMYK-only-l1–400;-Vskip ≈1.0;-pskip ≈1.0 and >0. - Basename + cwd required. Process id
targen_{basename}. - Spec: 08, 04 §1.2.
- Deps: 2, 3, 4, 6.
- Test CI: argv golden vectors every flag combo. Hardware: N/A.
- AC: exit 0 →
.ti1→ Stage 2 unlocks.
Issue 8 — Stage 1: resume existing target
Labels: Feature/Backend, Priority/High
Milestone: M2
- Open dialog
.ti1/.ti2(#103, #140). Jump:.ti1→Stage 2,.ti2→Stage 3 + “Resumed from .ti2”. - Spec: 06, 08.
- Deps: 4, 6, 7.
- Test CI: resume fixtures. Hardware: N/A.
Issue 9 — Stage 2: printtarg UI & argv builder
Labels: Feature/UI, Feature/Backend, Priority/Critical
Milestone: M2
-v -u -i {instrument} -p {page} [-r | -R seed] [-d label] {-t|-T} {dpi} [-K|-I cal] basename
- Always
-v -u. Default layout-R 1(#163). Raster is-r(not-R). Custom seed-R NN≥1. -dis the label (fork #19 / ICCery #119), not colour space, not density. Auto:ICCery - {basename} - {printer} - {ink} - {driverPaper} - {actualPaper} - DD/MM/YYYY HH:MM.-K/-Imust exist on the builder in M2 (Stage 0 reuses it). UI toggle may wait for 29. Never-KaCAL_basename.- Process id
printtarg_{basename}. CM warning banner. - Spec: 09, 04 §2.2, 05 §2.3.
- Deps: 2, 4, 7.
- Test CI: argv goldens including deterministic re-run identical
.ti2. Hardware: N/A.
Issue 10 — Stage 2: JSON manifest & TIFF gallery
Labels: Feature/Backend, Feature/UI, Priority/High
Milestone: M2
- Accumulate all stdout;
JSON.parsethe pretty-printed object (not brace-hunting, #68):
{"event":"manifest","pages":[{"filename":"target.tif","patches":800,"width_mm":210,"height_mm":297}]}
- Gallery:
readTiffPreviewPngper page. Per-page Print buttons stubbed until M3. - Spec: 09, 05 §2.3.
- Deps: 6, 9.
- Test CI: single + multi-page fixtures; non-zero exit stays on Stage 2. Hardware: N/A.
Issue 11 — Presets engine & built-ins
Labels: Feature/Backend, Feature/UI, Priority/Medium
Milestone: M2
ProfilingPresetCodable: all Stage 1/2/4 fields in 22.- Built-ins (all i1, FWA D50, seed 1, algorithm
l):preset-std-rgb— 800, A4, 8-bit, 300 dpi, qualitympreset-hq-cmyk— 1500, A3, 16-bit, qualityh, ink 320, black 8preset-draft-rgb— 400, A4, 8-bit, 150 dpi, qualitylpreset-ultra-rgb— 2500, A3, 16-bit, qualityu,-G, white/black 6
- Apply/save/manage/delete-custom/import/export. Built-ins undeletable. Names via
Textonly (#114). DPI must bind (#113). Enable the M1 preset select. - Deps: 5, 7, 9 (Stage 4 fields stored now, applied in 23).
- Test CI: round-trip; draft → 150 dpi; import schema. Hardware: N/A.
M3
Issue 12 — Printer enumeration & capabilities
Labels: Feature/Backend, Priority/Critical
Milestone: M3
lpstat -e/-p/-d;lpoptions -pdisplay name (printer-info);lpoptions -p -ltrays/sizes/media; PPD*Key id/Human:enrichment.- Models as draft (
Printer,PrinterTray,PrinterPaperSize,PrinterMediaType,PrinterCapabilities). - Cite spec 10 + 11. Doc 13 is Linux
lp -o raw— enumeration only, do not copyprint_target. - Deps: 2 (optional CLI spawn) or Foundation
Process. - Test CI: recorded
lpstat/lpoptionsfixtures; empty list = success. Hardware: optional live queue list.
Issue 13 — Native NSPrintPanel bound to selected queue
Labels: Feature/Backend, Feature/UI, Priority/Critical
Milestone: M3
- Preferences →
NSPrintPanelon the AppKit main thread, bound withPMPrinterCreateFromPrinterID(CUPS queue id)→PMSessionSetCurrentPMPrinter→ default settings/page format. FallbackNSPrinter(name: displayName). - Default button "Use Settings" (capture, not print). Cancel →
nil, not error.PMReleaseon every path. - Never System Settings, CUPS web UI, or
NSWorkspaceopen of the printer (#188). - Spec: 11.
- Deps: 12.
- Test CI: cancel path. Hardware: panel shows Epson/Canon driver PDE.
Issue 14 — ColorSync suppression engine
Labels: Feature/Backend, Priority/Critical
Milestone: M3
Six layers (spec 11 roster):
- Session binding (issue 13).
dlsym(RTLD_DEFAULT):PMSessionSetColorMatchingModeLock→PMSessionSetColorMatchingMode→…NoLock. Signature(PMPrintSession, CFStringRef) -> OSStatus. ModesAP_ApplicationColorMatchingthenApplicationColorMatching. First(symbol, mode)returning 0 wins. Never pass integer1(SIGSEGV). Forbidden:AP_ColorSyncMatching,AP_VendorColorMatching.PMPrintSettingsSetValuebothAP_ColorMatchingModeandAP.ColorMatchingMode=AP_ApplicationColorMatching, locked.- Driver bypass from
lpoptions -l, unlocked: CanonCNIJIntent2=4elseCNIJIntent=4; EpsonEPIJ_CCor=0if that key exists elseEPIJ_CMat=3; GutenprintStpColorCorrection=Uncorrected; genericColorCorrection=Uncorrected;EpsonColorMode=Off. - Mirror into
NSPrintInfo.printSettings. - After OK:
PMPrintSettingsToOptions→ filter (dropcom.apple.*, collate/copies/job-sheets, empty values, both AP_ keys* — issue 15 always re-adds them) →capturedCupsOptions+ media type.
- Deps: 12, 13.
- Test CI: injectable dlsym order; filter fixtures. Hardware: PDE colour grayed/off on Epson and Canon.
Issue 15 — lp spool path
Labels: Feature/Backend, Priority/High
Milestone: M3
lp -d <queue> -t "ICCery Target - <file>"
-o AP_ColorMatchingMode=AP_ApplicationColorMatching
-o AP.ColorMatchingMode=AP_ApplicationColorMatching
… captured options …
<tiff>
- Never
-o raw. Raw skips the raster filter that honoursAP_*. - Then captured cups_options (sanitise keys/values — no
;, newlines, or shell metacharacters; pass asProcessargs, never a shell string). Then media_type if absent, then driver bypass if no bypass key yet (not gated on a checkbox), thenorientation-requested=3|4, thenPageSize. - Last argv is the TIFF path.
- Spec: 11
build_lp_args. - Deps: 14.
- Test CI: argv goldens — both AP_* always present; captured wins; sanitise rejects
;. Hardware: one unmanaged page.
Issue 17 — Stage 2 print panel UI
Labels: Feature/UI, Priority/High
Milestone: M3
- After manifest: printer select + refresh, status badge, Preferences, tray, media type, orientation, Print All + per-page, CM banner, per-printer
capturedCupsOptionscache. Cancel → info, not error. No CUPS “PPD fallback” checkbox (Windows/Linux only, #48). - Spec: 09, 10.
- Deps: 10, 12, 13, 15.
- Test CI: captured options replayed in argv. Hardware: layout→Preferences→Print All.
M4
Issue 18 — Instrument detection (instlist)
Labels: Feature/Backend, Priority/Critical
Milestone: M4
- Spawn
instlistno args, idinstlist, cwd inherited. Accumulate stdout; parse pretty JSON{event:"instruments",devices:[{port,name,type}]}. Regex fallback requiring known-instrument tokens. portis 1-based comm port forchartread -c. Port1/ Auto → omit-c(#111). Never pass array index. XY if name/type matches/spectro\s?scan|i1io/i.- Duplicate Detect while running → exclusive-id error (#116).
- Spec: 15, 05 §4.
- Deps: 2, 3.
- Test CI: JSON + regex fixtures; empty list. Hardware: Detect finds a real device.
Issue 19 — chartread session & prompt classifier
Labels: Feature/Backend, Priority/Critical
Milestone: M4
-v -u [-c port] [-Y l] basename
- Process id
chartread_{basename}. Do not passchartread -uomission —-uis required (JSON rows). -Y lonly when settingsenable_i1pro2_leds(default false, #204). Letter L, not-L.- Classifier: 11 states, priority matchers on real C prompt strings (spec 05 §12.4). Sticky
TABLE_*(#93). “Remove last sheet” info-only. - stdin:
" \n"/"\n"trigger/accept;"d\n"done+save (#175, not EOF);"q\n"abort. No Skip/Undo sendings/u. If a back-strip control is needed, map to real keys (f/b/n) after reading forkchartread.c. - XY cancel:
q\n, ~500ms, then kill (#147). - CI must run
chartread.mock. - Spec: 15, 03 stdin table.
- Deps: 2, 4, 18, 5 (LED flag).
- Test CI: port 39 classifier cases + real-prompt fixtures; Done →
.ti3. Hardware: one real strip.
Issue 20 — Live swatch grid & ΔE₀₀
Labels: Feature/UI, Feature/Backend, Priority/High
Milestone: M4
jsonRowevent:row_complete. Grid A→Z, 1→N LTR. 135° split TL intended / BR measured (#178).is_padskip only if no measured and all-zero device (white-estill renders). XYZ 0–100 → /100 before Lab. Traffic lights from settings 2.0 / 5.0; reclassify on settings change. Distinct from Stage 5 bands (#95).- Spec: 15, 05 §3.
- Deps: 19, 5.
- Test CI: ΔE₀₀ vectors; pad/white cases. Hardware: N/A (uses json rows).
Issue 21 — Multi-pass averaging
Labels: Feature/Backend, Priority/High
Milestone: M4
- After chartread exit 0:
snapshot_ti3copies to{basename}_passN.ti3(1-based) and deletes canonical (#109). Stage 4 stays locked (#110). Finish: 1 passpromote_ti3; ≥2average -vrelative names in project cwd then canonical.ti3. - Deps: 2, 4, 19.
- Test CI: snapshot/promote; average argv; gating locked between passes. Hardware: N/A.
Issue 22 — XY-table flow UI
Labels: Feature/UI, Priority/Medium
Milestone: M4
- Badges Place → Align → Scan → Remove from classifier. Cancel parks head (
q\nthen kill). - Spec: 15 #93.
- Deps: 19.
- Test CI: simulated prompt script. Hardware: optional i1iO / SpectroScan.
M5
Issue 23 — Stage 4: colprof UI & argv builder
Labels: Feature/UI, Feature/Backend, Priority/Critical
Milestone: M5
-v -a {l|x|X|m} -q {l|m|h|u} [-t intent] [-f [D50|D65|path.sp]] [-i] [-o] [-c inView] [-d outView] [-D] [-C] basename
- Default
-a l(Lab cLUT),-q m. FWA: none omit / empty → bare-f/ D50 / D65 / custom.spviaselectSpectrumFile(#210, #176). Viewing-c/-dskip"none"— these are not directories/labels. - Do not pass
colprof -u. Progress from stdout text. If someone later adds-u, it must be a bare flag (upstream-u <token>is white-point). - Process id
colprof_{basename}— payload/id parity (#56). ThengetProfilePath(#69). - Spec: 16, 04 §6.
- Deps: 2, 4, 6, 11.
- Test CI: argv goldens; mock colprof; exit 0 → Stage 5. Hardware: N/A (or full pipe).
Issue 24 — Post-profile chain: applycal + iccgamut
Labels: Feature/Backend, Priority/High
Milestone: M5
- If Apply Calibration +
.cal:runCapturedapplycal -v -a {cal} {icc}via{input}.applycal.tmp+ rename.applycal -umeans unapply — never send. - Then
iccgamut -v -d 10 {resolvedProfile}(density, not a directory, #112). cwd = profile parent, idiccgamut_{stem}→{stem}.gam. - Spec: 04 §7/§10, 17.
- Deps: 2 (captured), 23.
- Test CI: applycal argv + tmp; iccgamut
-d 10. Hardware: N/A.
Issue 25 — Stage 5: profcheck verification
Labels: Feature/Backend, Feature/UI, Priority/Critical
Milestone: M5
profcheck -v -k -s -u {ti3} {icc}, idprofcheck_{ti3_path}. Prefer JSONevent:report/*_de2000; legacy text fallback; unparseable → warning, never silent 0.00 (#179).- Bands on avg ΔE₀₀: <1 Excellent, <2 Good, <3.5 Acceptable, ≥3.5 Warning — not swatch 2.0/5.0 (#95).
- Spec: 17, 05 §2.5.
- Deps: 2, 23.
- Test CI: JSON/text/garbage fixtures. Hardware: N/A.
Issue 26 — Verification history & drift analytics
Labels: Feature/Backend, Feature/UI, Priority/High
Milestone: M5
verification_history.json, cap 1000, atomic.tmp+rename (#213). Record fields as spec 17. Consecutive-breach: ≥2 Warning on distinct days or ≥1h apart. RFC-4180 CSV.- Deps: 25, 5.
- Test CI: crash-during-write keeps prior file; cap; CSV quotes; alert logic. Hardware: N/A.
Issue 27 — System profile installation
Labels: Feature/Backend, Feature/UI, Priority/High
Milestone: M5
- Copy, never move. User
~/Library/ColorSync/Profiles; system/Library/ColorSync/Profiles(error text must mention admin rights). Filename{stem}.icc; reject stem.. / \. Source file,.icc/.icm, ≥128 bytes. Collision overwrite | rename{stem}-{epoch}.icc| cancel. Atomic{dest}.iccery-install.tmp+rename.open -a "ColorSync Utility"if requested.InstallResultas spec 19. - Deps: 6, 5, 23.
- Test CI: dest matrix in temp dirs; collision; failed copy does not register. Hardware: profile appears in ColorSync Utility.
M6
Issue 28 — 3D gamut viewer (SceneKit)
Labels: Feature/UI, Feature/Backend, Priority/High
Milestone: M6
- Parser: two CGATS
BEGIN_DATAblocks — verticesVERTEX_NO L a b(discard index, push-order), then faces 0-based.#comments; native faces > convex hull; vertex-only → hull fallback. ASCII only. - SceneKit (not Metal, not Three.js): X=a* ±128, Y=L* 0–100, Z=b* ±128; camera home (180,120,180) lookAt (0,50,0). Bundled real
sRGB.gam. R resets camera on a focusable container (do not port dead-key / opacity-inference traps — spec 18 §10: fix, don’t clone). - Load on Stage 5 entry + post-verify. No WebGL at boot (#225 class).
- Spec: 18, 23 #185.
- Deps: 3 (asset), 24 (
.gam). - Test CI: dual-table / comments / OOB / stub→hull fixtures. Hardware: N/A.
Issue 29 — Stage 0: printer calibration
Labels: Feature/Backend, Feature/UI, Priority/High
Milestone: M6
- Enable Calibrate Printer. Basename
CAL_{run}(never double-prefix). Flow: caltargen(-f 0, steps 11–51 default 21,-e 4, CMYK-l200–400 default 320) → Stage 2 layout/print (never-Kthe cal chart) → Stage 3 measure →runCapturedprintcal -v -e [-I] [-z] [-a] [-m] [-xC…] -o out.cal CAL_{basename}→ Apply toggle drives profilingprinttarg -Kand post-colprofapplycal -a. - Collision Overwrite / Rename
{base}_{ISO}.cal/ Cancel. Stale:calibration_stale_daysor printer mismatch. Curve SVG solid vs dashed identity. - Spec: 07, 04 §1.3/§7/§8. Invariant #224.
- Deps: 2 (captured), 5, 7, 9, 15, 17, 19, 23, 24.
- Test CI: argv builders;
.calparse; toggle gates-K. Hardware: one cal loop on a real printer.
Issue 30 — CGATS dataset interop
Labels: Feature/Backend, Priority/Medium
Milestone: M6
- Import = open dialog +
setTargetbefore jump (#211). Write canonical.ti3Argyll-strict. Device 0–255→0–100. Jump Stage 4/5; Stages 1–2 stay locked without.ti1/.ti2. User strings viaTextonly. - Spec: 20 #94/#211.
- Deps: 4, 6.
- Test CI: parse→write→reparse. Hardware: N/A.
Issue 31 — About & help chrome
Labels: Feature/UI, Priority/Medium
Milestone: M6
- About: version + build date from
getAppInfo. Global help: overlay tooltips that do not reflow (#171). Notification banner auto-hide. Settings dialog already shipped in issue 5. - Spec: 21, 22.
- Deps: 1, 5.
- Test CI: tooltips do not change layout height. Hardware: N/A.
Issue 32 — Packaging, signing & CI
Labels: Feature/DevOps, Priority/High
Milestone: M6
- Self-hosted Mac runner (Gitea has no
macos-latestunless you attach one). Optional GitHub Actions mirror. - Pipeline:
fetch-argyll→ ad-hoccodesign -s -+codesign -dvvon every sidecar Mach-O (hard fail) →xcodebuild build test -scheme ICCery ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO→ unit + mock fixtures (#215) → dmgbuild with background art (not Finder AppleScript, #189) → upload artefact. - App signing: Developer ID + notarize/staple for the
.app/.dmg. Sidecars remain ad-hoc inside the bundle (#165). These are two different gates — do not conflate. - Confirm entitlements: sandbox false.
- Spec: 04 §0.6, 05 §8–9, 23, 24.
- Deps: all v2.0 issues 1–15, 17–31.
- Test CI: unsigned sidecar fails; tests run; dmg produced. Hardware: Gatekeeper-open the notarized dmg on a clean Mac.
Later (v2.1) — do not put on M3
Issue 16 — Quartz print module (folded TargetPrint)
Labels: Feature/Backend, Feature/UI, Priority/High
Milestone: Later
- Separate Swift package
ICCeryPrintKit. Zero deps on wizard types. - Public API:
TargetJobv1 JSON +--jobCLI (fire-and-forget) and in-processNSPrintOperation. Preserve extractability to a standalone app. - ColorSync vocabulary is not the
lppath:PMColorMatchingMode=APCustomColorMatching,PMCustomColorMatchingProfile="", legacycom.apple.print.PrintSettings.PMColorMatchingMode. Never mix withAP_ApplicationColorMatching. - Vendor keys (separate table from issue 14): Epson
ColorModel=RGB+EPSONColorControls=Off; CanonCNColorMatching=None; HPColorModel=RGB+HPColorControl=Off. - Geometry: 72pt=1in, no
backingScaleFactor, interpolation.none, antialias off, pixel-integrity seam test. Resolve SPEC contradiction: job JSON"centered": truevs draw “no centering” — lock “no centering, scale 1.0” for profiling targets. - AirPrint detection → persistent warning.
- Spec: 14.
- Deps: 13, 17 (UI hook). Not required for M3 or M6 exit.
- Test CI: seam-integrity; module compiles standalone. Hardware: 1:1 on paper vs TIFF.
Dependency graph (issue numbers)
1
├─ 2 ─ 3
│ └─ 4 ─ 6 ─ 5
│ ├─ 7 ─ 8
│ │ └─ 9 ─ 10 ─ 11
│ │ └─ 12 ─ 13 ─ 14 ─ 15 ─ 17
│ └─ 18 ─ 19 ─ 20
│ ├─ 21, 22
│ └─ 23 ─ 24 ─ 25 ─ 26
│ └─ 27
├─ 28 ← 24
├─ 29 ← 7, 9, 15, 17, 19, 23, 24
├─ 30 ← 4, 6
├─ 31 ← 5
└─ 32 ← 1–15, 17–31
16 (Later) ← 13, 17
Out of scope
- Windows GDI / DEVMODE /
CREATE_NO_WINDOW; Linuxlp -o raw/ udev; NSIS/WiX. - Display calibration (#90), i18n (#96).
- Linking Argyll. WKWebView/WebGL.
- Shipping TargetPrint as the v2.0 spooler.
- Migrating v0.8.5
settings.json/ history (new bundle id on purpose). Add a ticket later if needed.
Verification after filing
- 7 milestones (M1–M6 + Later), all open.
- 32 issues, all with
Project/ICCery-v2+ Feature + Priority + a milestone. - Issue 16 is on Later, not M3.
- Issue 2 body contains
runCaptured. - Issue 5 body contains the settings dialog.
- Issue 1 min OS is 14.0 and bundle id is
com.gronod.iccery2. - Spot-check issues 14, 15, 19, 24 for ColorSync SPI arity, no
-o raw, nos/u,iccgamut -d 10.