Files
iccery-v2-mac/docs/22-settings-presets.md
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> b494b4fffd docs(m10): defaultInstrument seeds Spot Read; README floor is macOS 12
Refs #148

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-13 12:23:43 +01:00

92 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 22 — Settings and presets
Persisted at `{app_data}/settings.json` via `load_settings` / `save_settings`. Invalid JSON loads `AppSettings::default()`. `save_settings` calls `validate()` then writes pretty JSON and **immediately** applies `log::set_max_level`.
## `AppSettings` (`settings.rs`)
| Field | Default | Notes |
|-------|---------|-------|
| `argyll_binary_dir` | `null` | Overrides bundled sidecars. `resolve_binary` checks this first. |
| `default_instrument` | `null` | Stored; seeds the Spot Read instrument picker when that instrument is present (#148). **Never** written into `printtarg -i` or `targen` argv. Stage 2 `#instrumentSelect` remains the printtarg instrument. |
| `log_level` | `null` | `error` / `warn` / `info` / `debug` / `trace`. `null` → Debug in debug builds, Info in release. Applied at startup **and** on save (#158). |
| `delta_e_good_max` | `2.0` | Stage 3 swatch traffic-light "Good". Must be ≥ 0. |
| `delta_e_warning_max` | `5.0` | Stage 3 "Warning" band. Must be **strictly greater** than good. |
| `custom_presets` | `[]` | User presets; built-ins are **not** stored here. |
| `enable_i1pro2_leds` | `false` | Adds `chartread -Y l`. Default off so stock Argyll still runs. |
| `calibration_stale_days` | `30` | Stage 0 / Stage 1 reminder. |
| `default_install_location` | `"user"` | `"user"` \| `"system"` for Stage 5. |
| `ask_before_overwrite_profile` | `true` | |
| `open_color_panel_after_install` | `false` | |
Validation error strings (surface in `#deltaEThresholdError`):
- `"ΔE thresholds cannot be negative."`
- `"Good ΔE threshold must be strictly less than the warning threshold."`
Saving dispatches a DOM `settings-saved` event so the live swatch grid reclassifies without a re-read.
Settings dialog ids: `argyll_binary_dir`, `default_instrument`, `enable_i1pro2_leds`, `deltaEGoodMax`, `deltaEWarningMax`, `deltaEThresholdError`, `calibrationStaleDays`, `defaultInstallLocation`, `askBeforeOverwriteProfile`, `openColorPanelAfterInstall`, `logLevelSelect`, `btnOpenLogFolder`, `btnCopyLogPath`, `btnCopyLogExcerpt`, `logPathDisplay`, `saveSettingsBtn`, `closeSettingsBtn`.
Log helpers: `get_log_path`, `get_recent_log_excerpt`, `open_log_dir`.
## `ProfilingPreset`
Every field is optional-defaulted with `#[serde(default)]` except the required identity / Stage 12 core. `applyPreset` **must** set TIFF DPI (#113) — a historical bug left `#tiffDpi` at 300 when loading the 150 dpi draft preset.
| Field | Type | Stage |
|-------|------|-------|
| `id`, `name`, `description` | string | identity. Names/descriptions go through `textContent`, never `innerHTML` (#114 XSS) |
| `colour_space` | `"rgb"` \| `"cmyk"` | 1 |
| `patch_count`, `white_patches`, `black_patches` | u32 | 1 |
| `grey_steps`, `single_channel_steps`, `neutral_steps` | Option\<u32\> | 1 advanced |
| `preconditioning_profile` | Option\<path\> | 1 `-c` |
| `neutral_concentration` | Option\<f64\> | 1 `-N` |
| `ofps_high_quality` | Option\<bool\> | 1 `-G` |
| `ofps_adaptation` | Option\<f64\> | 1 `-A` |
| `full_spread_algorithm` | Option\<string\> | 1 `-t/-r/-R/-q/-Q/-i/-I` |
| `total_ink_limit` | Option\<u32\> | 1 `-l` CMYK |
| `dark_emphasis`, `device_power` | Option\<f64\> | 1 `-V` / `-p` |
| `instrument`, `page_size`, `bit_depth`, `dpi` | | 2 |
| `random_seed`, `no_randomize` | | 2 `#printtargLayoutOrder` |
| `calibration_file`, `apply_calibration` | | 0 / 2 `-K` |
| `colprof_algorithm`, `colprof_quality`, `colprof_intent` | | 4 |
| `colprof_fwa`, `colprof_illuminant`, `colprof_observer` | | 4 |
| `colprof_input_viewing_cond`, `colprof_output_viewing_cond` | | 4 |
## Built-in presets (`get_default_presets`)
Built-ins cannot be deleted. Custom presets overlay by `id`. Import/export is JSON via `export_preset_json` / `import_preset_json` with schema validation (unknown keys ignored via serde default; missing required fields fail).
| id | Name | Space | Patches | Page | Bit | DPI | Quality | Extra |
|----|------|-------|---------|------|-----|-----|---------|-------|
| `preset-std-rgb` | Standard RGB Photo (800 patches) | rgb | 800, white 4 | A4 | 8 | 300 | `m` | FWA D50, seed 1, instrument i1, alg `l` |
| `preset-hq-cmyk` | High-Gamut CMYK Proofing (1500 patches) | cmyk | 1500, black 8 | A3 | 16 | 300 | `h` | ink limit 320, FWA D50, seed 1 |
| `preset-draft-rgb` | Fast RGB Draft (400 patches) | rgb | 400 | A4 | 8 | **150** | `l` | FWA D50, seed 1 |
| `preset-ultra-rgb` | Ultra Precision RGB (2500 patches) | rgb | 2500, white 6, black 6 | A3 | 16 | 300 | `u` | `ofps_high_quality=true` (`-G`), FWA D50, seed 1 |
All four: `instrument: "i1"`, `colprof_algorithm: "l"`, `random_seed: 1`, `no_randomize: false`, `colprof_fwa: "D50"`.
UI: `#presetSelect`, `#btnSavePresetModal``#savePresetDialog` (`savePresetName`, `savePresetDesc`, `btnConfirmSavePreset`), `#btnOpenPresetsDialog``#managePresetsDialog` (`managePresetsList`, `btnExportActivePreset`, `btnImportPreset`).
## Media library (`media_library.json`)
Persisted at `{app_data}/media_library.json` — a sibling of `settings.json`, never a field inside it (issue #146). A `MediaRecipe` binds a CUPS queue + paper + ink set + optional `.cal` to a `ProfilingPreset`. Cap: 200 entries; the 201st is refused with an error, never silently evicted. Corrupt JSON → keep the file, load `[]`, persistent warning banner.
| Field | Type | Notes |
|-------|------|-------|
| `id` | string | `recipe-<uuid>`, never user-typed |
| `name`, `notes` | string | Rendered through `Text` only (#114) |
| `printer_id` | string | CUPS queue id (`lpstat -e` name) |
| `printer_display_name` | string | Human label; applied to `wizard.printerName` |
| `paper_name`, `ink_set` | string | Library metadata only — never written to targen flags |
| `driver_media_type` | string? | Last captured CUPS `media_type` (read-only) |
| `colour_space` | `"rgb"` \| `"cmyk"` | Must match the bound preset |
| `preset_id` | string | `ProfilingPreset.id` (built-in or custom) |
| `calibration_url` | string? | Absolute `.cal` path, stored verbatim |
| `apply_calibration` | bool | Forced off for `CAL_` stems or missing files |
| `created`, `updated` | iso8601 | |
Apply path: recipe → `applyPreset` (#82 mapping, no second Stage 1 form) → queue re-enumerated (`lpstat -e`) → `printer_id` absent from a non-empty list warns "not installed" and leaves the queue untouched; an empty list is indeterminate and never flags. `CAL_` bound cal **or** a live `CAL_` wizard basename forces `applyCalibration` off — `printtarg -K` can never see a `CAL_` file (literal refusal; escape hatch is rename + re-capture). Staleness: `.printer` = absent from enumerated queues; `.calibration` = bound cal `CREATED + calibration_stale_days < now`.
Capture with no preset selected auto-snapshots the live form as a `custom-` preset and binds to it. UI: `#mediaSelect` (immediate apply, `#presetSelect`-style), `#btnMediaLibraryCapture``#saveMediaRecipeDialog`, `#btnMediaLibraryManage``#manageMediaDialog` (`mediaLibraryList`, `mediaRow-{id}`, `btnMediaLibraryApply-{id}`, `btnMediaLibraryDelete-{id}`), stale badge `#mediaRecipeStale`.