Files
iccery-v2-mac/docs/02-architecture.md
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 4c2863e52c Add bootstrap docs: AGENTS.md, BUILD-PLAN.md, LICENCE.md, vendored spec (docs/), brand assets
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-08 17:36:50 +01:00

6.9 KiB

02 — Architecture

Host / child split

┌─ ICCery host (any stack) ─────────────────────────────────────┐
│  Wizard UI  ─►  WizardState (basename, cwd, printerName)      │
│       │                                                       │
│       ├─ ProcessManager (spawn / stdin / kill / kill_all)     │
│       ├─ Print subsystem (Win GDI / macOS NSPrint+lp / Linux) │
│       ├─ Settings + presets (settings.json)                   │
│       ├─ Quality store (verification_history.json)            │
│       ├─ Calibration library (.cal files)                     │
│       └─ Profile installer (OS colour stores)                 │
└──────────────┬────────────────────────────────────────────────┘
               │ stdin / stdout / stderr pipes
               │ env ARGYLL_NOT_INTERACTIVE=1
               ▼
┌─ ArgyllCMS sidecars (AGPLv3) ─────────────────────────────────┐
│  instlist  targen  printtarg  chartread  average              │
│  printcal  applycal  colprof  profcheck  iccgamut             │
└───────────────────────────────────────────────────────────────┘

Original implementation: Tauri v2 webview (withGlobalTauri: true) so the vanilla JS frontend calls window.__TAURI__.core.invoke and window.__TAURI__.event.listen. A rewrite may replace this with any IPC (HTTP local server, native bindings, gRPC, etc.) but must keep the same command semantics.

Frontend modules (legacy)

Module Role
app.js Boot, safeInit per stage, show_main_window double-rAF + 1500 ms fallback
state.js wizardState, artefact gating, stage DOM, gamut pause/ensure
targen.js Stage 1
printtarg.js Stage 2 + native print UI
chartread.js Stage 3 state machine
swatch_grid.js Live ΔE₀₀ patches from process:json_row
colprof.js Stage 4
profcheck.js Stage 5 metrics, drift SVG, CSV
gamut_viewer.js Three.js CIELAB viewer (lazy)
calibration.js Stage 0
profile_install.js Stage 5 install
settings.js / presets.js Persistence
cgats_interop.js Import/export datasets
delta_e.js CIEDE2000
color_convert.js Lab/device → CSS
logger.js Forwards console to log_frontend_message

Do not create WebGL during boot. ensureGamutViewer() runs only when Stage 5 becomes visible (#225).

Backend modules (legacy Rust)

Module Role
lib.rs Tauri builder, plugins, generate_handler!, RunEvent kill-all
commands.rs Binary resolve, arg builders, dialogs, stage artefact verify
process_manager.rs tokio spawn, JSON-row split, CREATE_NO_WINDOW
events.rs `process:stdout
print/* OS printing
calibration.rs printcal/applycal + .cal parser
profile_install.rs OS colour-store copy
quality_store.rs verification history, atomic write
settings.rs settings + presets
cgats.rs CGATS/ti3 parser + canonical serializer
macos_webview.rs Dark WKWebView backing
window_lifecycle.rs Close vs Web Content death

Sidecar layout

scripts/fetch-argyll.mjs downloads Gronod/argyllcms GitHub (or Gitea) releases into:

src-tauri/argyll/
  linux-x86_64/instlist
  windows-x86_64/instlist.exe
  macos-x86_64/instlist
  macos-aarch64/instlist
  macos-universal/instlist
  mocks/          # chartread.mock, colprof.mock, profcheck.mock
  reference_gamuts/sRGB.gam

resolve_binary(name):

  1. If settings.argyll_binary_dir is set and the file exists, use it.
  2. Else resource argyll/<platform>/<name>[.exe].
  3. On macOS, prefer macos-universal if that folder contains instlist.
  4. Windows always tries name.exe first (#85).

Env override: ARGYLL_RELEASE_TAG=vX.Y.Z npm run fetch-argyll.

Working directory

Every Argyll run is given an explicit cwd. Empty cwd falls back to Documents → Home → app data (resolve_safe_cwd, #59). Basename must not contain /, \, or ...

Default artefacts live next to each other:

<cwd>/<basename>.ti1
<cwd>/<basename>.ti2
<cwd>/<basename>.tif  (and .1.tif, .2.tif … for multi-page)
<cwd>/<basename>.ti3
<cwd>/<basename>_passN.ti3     # averaging snapshots (#109)
<cwd>/<basename>.icc | .icm
<cwd>/<basename>.gam
<cwd>/CAL_<basename>.ti1|.ti2|.ti3|.cal   # calibration, never collides

Persistence locations

File Where Notes
settings.json app data dir thresholds, argyll dir, presets, LED flag
verification_history.json app data dir max 1000 records, atomic .tmp + rename (#213)
iccery.log app log dir 5 MiB rotate, keep 5 historical segments
Calibration library app data / user-chosen .cal files

macOS log path: ~/Library/Logs/com.gronod.iccery/iccery.log.

Event bus (must be replicated)

Event Payload When
process:stdout { id, line } Non-JSON stdout line
process:stderr { id, line } stderr line
process:exit { id, code } child exited (0 = success; killed may be 1)
process:error { id, error } spawn failure
process:json_row { id, json } stdout line starting ROW_COLORS_JSON: — prefix stripped

Process ids are deterministic strings, e.g. targen_${basename}, chartread_${basename}, instlist, iccgamut_${stem}. Duplicate spawn of a still-running id is rejected (#116).

Frontend listeners must filter on payload.id. A historical bug (#56) was process-id mismatch so UI never saw exit.

Logging

  • Host: tauri-plugin-log to log dir + stdout + webview. wry / tauri_runtime_wry at Info so Monterey "web content process terminated" is captured (#225).
  • Subprocess stdout → log::info!(target: "subprocess"); stderr → warn.
  • Paths in spawn logs are home-sanitized to ~ (sanitize_arg_for_logging).
  • JS logger.js invokes log_frontend_message.
  • Settings log_level is applied at startup and when saved (#158).

Window / WebView contract (macOS especially)

See #225 and macos_webview.rs:

  • Window visible: false, backgroundColor: #1A1A22.
  • After CSS first paint: invoke show_main_window (double requestAnimationFrame + 1500 ms fallback).
  • paint_dark_webview: setBackgroundColor, KVC drawsBackground = NO, setUnderPageBackgroundColor: on macOS 12+.
  • Do not set transparent: true (hit-testing / titlebar).
  • On Exit / CloseRequested: kill_all Argyll children (#147, #149) before teardown so chartread can park an XY head if the UI already sent q\n.