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

150 lines
6.9 KiB
Markdown

# 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|stderr|exit|error|json_row` |
| `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`.