Compare commits
3
Commits
2c97e98dc3
..
v0.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d50c3c4f3c | ||
|
|
9a365acd2b | ||
|
|
47a5373a8e |
@@ -1,30 +1,5 @@
|
|||||||
# ICCery Agent Notes
|
# ICCery Agent Notes
|
||||||
|
|
||||||
## Stage 3 Swatch Grid
|
|
||||||
|
|
||||||
- Swatch patches render a 135° diagonal split: top-left is the intended/expected colour, bottom-right is the measured colour.
|
|
||||||
- Argyll `chartread` emits `is_pad` for boundary/spacer patches. White reference patches (e.g. `-e` white steps) may also carry `is_pad`, but they have valid `expected.Lab` or non-zero `device` data. The guard skips only pads with no measurement *and* all-zero device values.
|
|
||||||
- Row/patch order from `chartread` (rows A→Z, patches 1→N within each row) is rendered left-to-right / top-to-bottom to match the `printtarg` output.
|
|
||||||
- Tooltip shows intended Lab (or device %), measured Lab, and ΔE₀₀ with a Good/Warning/Bad classification.
|
|
||||||
|
|
||||||
## UI Button Conventions
|
|
||||||
|
|
||||||
The frontend uses a tiered button sizing system defined in `src/styles/main.css`. Prefer these utility classes over inline `style` attributes.
|
|
||||||
|
|
||||||
| Class | Size | Use for |
|
|
||||||
|-------|------|---------|
|
|
||||||
| `.btn-sm` | 28px | Toolbar actions, header icons, minor toggles |
|
|
||||||
| `.btn-md` (default for `button.secondary`/`button.danger`) | 36px | Standard dialog/form actions and browse buttons |
|
|
||||||
| `.btn-lg` | 40px | Primary stage actions (Generate, Create Layout, Create Profile, Verify, etc.) |
|
|
||||||
| `.btn-icon-sq` | 36×36px | Square icon-only buttons (refresh, settings, etc.) |
|
|
||||||
| `.icon-btn` | 28×28px | Small header icon-only buttons (settings, about, save preset, manage presets) |
|
|
||||||
| `.btn-properties` | 36px | Printer driver preferences button |
|
|
||||||
|
|
||||||
- Primary action buttons use `button.primary` plus `.btn-lg` for major stage actions.
|
|
||||||
- Danger actions use `button.danger` (36px).
|
|
||||||
- All action rows use one of: `.stage-actions`, `.modal-actions`, `.chartread-actions`, `.print-actions-row`, `.btn-row`, `.btn-row-sm`, `.btn-row-end`, or `.input-row-sm`.
|
|
||||||
- Avoid inline `style` on `<button>` elements or their immediate parent rows.
|
|
||||||
|
|
||||||
## Build Commands
|
## Build Commands
|
||||||
|
|
||||||
- **Rust backend**: `cd src-tauri && CARGO_INCREMENTAL=0 cargo check` (the project lives on a network filesystem that doesn't support file locking, so `CARGO_INCREMENTAL=0` is required)
|
- **Rust backend**: `cd src-tauri && CARGO_INCREMENTAL=0 cargo check` (the project lives on a network filesystem that doesn't support file locking, so `CARGO_INCREMENTAL=0` is required)
|
||||||
|
|||||||
@@ -16,14 +16,13 @@
|
|||||||
- 🪄 **Linear 5-Stage Wizard Workflow**:
|
- 🪄 **Linear 5-Stage Wizard Workflow**:
|
||||||
1. **Stage 1 — Patch Generation (`targen`)**: Configure RGB (driver-managed) or CMYK (RIP-managed) patch sets with custom counts, profiling presets, and neutral/grey axis boosting.
|
1. **Stage 1 — Patch Generation (`targen`)**: Configure RGB (driver-managed) or CMYK (RIP-managed) patch sets with custom counts, profiling presets, and neutral/grey axis boosting.
|
||||||
2. **Stage 2 — Target Creation & Raw Printing (`printtarg`)**: Format patch targets for spectrophotometers (i1Pro, i1Pro2, ColorMunki, SpyderPrint). View high-resolution downscaled TIFF previews and print directly using native OS raw unmanaged pathways (Windows GDI uncorrected / Linux CUPS `raw`).
|
2. **Stage 2 — Target Creation & Raw Printing (`printtarg`)**: Format patch targets for spectrophotometers (i1Pro, i1Pro2, ColorMunki, SpyderPrint). View high-resolution downscaled TIFF previews and print directly using native OS raw unmanaged pathways (Windows GDI uncorrected / Linux CUPS `raw`).
|
||||||
3. **Stage 3 — Interactive Measurement (`chartread`) & Averaging (`average`)**: Instrument auto-detection (`instlist`), real-time calibration prompts, interactive strip reading state machine, live swatch grid with CIEDE2000 ($\Delta E_{00}$) quality indicators, diagonally split intended-vs-measured colour swatches, white reference patch preservation, and multi-pass sheet averaging for measurement noise reduction.
|
3. **Stage 3 — Interactive Measurement (`chartread`) & Averaging (`average`)**: Instrument auto-detection (`instlist`), real-time calibration prompts, interactive strip reading state machine, live swatch grid with CIEDE2000 ($\Delta E_{00}$) quality indicators, and multi-pass sheet averaging for measurement noise reduction.
|
||||||
4. **Stage 4 — Profile Calculation (`colprof`)**: Generate high-precision cLUT mathematical ICC/ICM profiles with configurable algorithm quality, descriptions, and copyright tagging.
|
4. **Stage 4 — Profile Calculation (`colprof`)**: Generate high-precision cLUT mathematical ICC/ICM profiles with configurable algorithm quality, descriptions, and copyright tagging.
|
||||||
5. **Stage 5 — Verification & 3D Gamut (`profcheck` + `iccgamut`)**: Comprehensive mathematical validation report (Peak, Average, RMS $\Delta E$) paired with an interactive 3D CIELAB convex hull color volume viewer, touch controls, and bundled sRGB reference wireframe comparison.
|
5. **Stage 5 — Verification & 3D Gamut (`profcheck` + `iccgamut`)**: Comprehensive mathematical validation report (Peak, Average, RMS $\Delta E$) paired with an interactive 3D CIELAB convex hull color volume viewer, touch controls, and bundled sRGB reference wireframe comparison.
|
||||||
- 📋 **Profiling Presets**: One-click configuration presets (Standard RGB Photo, High-Gamut CMYK Proofing, Fast RGB Draft) with custom preset export/import and security validation.
|
- 📋 **Profiling Presets**: One-click configuration presets (Standard RGB Photo, High-Gamut CMYK Proofing, Fast RGB Draft) with custom preset export/import and security validation.
|
||||||
- 🐧 **glibc Compatibility**: Pre-built Linux packages compiled with Ubuntu 22.04 LTS compatibility for Debian/Ubuntu environments.
|
- 🐧 **glibc Compatibility**: Pre-built Linux packages compiled with Ubuntu 22.04 LTS compatibility for Debian/Ubuntu environments.
|
||||||
- 🛡️ **Disk Artefact Gating**: Stepper navigation strictly verifies generated artefacts on disk (`.ti1`, `.ti2`, `.ti3`, `.icc`/`.icm`), preventing out-of-order execution while preserving backward navigation.
|
- 🛡️ **Disk Artefact Gating**: Stepper navigation strictly verifies generated artefacts on disk (`.ti1`, `.ti2`, `.ti3`, `.icc`/`.icm`), preventing out-of-order execution while preserving backward navigation.
|
||||||
- 🌐 **Platform-Aware**: Automatic handling of platform profile conventions (`.icm` on Windows, `.icc` on Linux/macOS) and native OS printer subsystems.
|
- 🌐 **Platform-Aware**: Automatic handling of platform profile conventions (`.icm` on Windows, `.icc` on Linux/macOS) and native OS printer subsystems.
|
||||||
- 🎛️ **Consistent UI Controls**: Tiered button sizing (`btn-sm`/`btn-md`/`btn-lg`/`btn-icon-sq`) and standardised action row classes provide a uniform, polished interface across all wizard stages and dialogs.
|
|
||||||
- ⚖️ **Clean AGPL Boundary**: Complete isolation of AGPLv3 binaries via asynchronous tokio IPC process pipelines.
|
- ⚖️ **Clean AGPL Boundary**: Complete isolation of AGPLv3 binaries via asynchronous tokio IPC process pipelines.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -99,15 +99,6 @@ ICCery is a native, cross-platform desktop application built with:
|
|||||||
- [ ] **macOS Raw Spooling**: Native CoreGraphics/CUPS raw print dialog bypass.
|
- [ ] **macOS Raw Spooling**: Native CoreGraphics/CUPS raw print dialog bypass.
|
||||||
- [ ] **SpectroScan & Automated Table Support (#93)**: Support XY automated scanning tables (i1iO / SpectroScan) in `chartread` (deferred).
|
- [ ] **SpectroScan & Automated Table Support (#93)**: Support XY automated scanning tables (i1iO / SpectroScan) in `chartread` (deferred).
|
||||||
|
|
||||||
### Milestone 13 — UI/UX & Workflow Polish
|
|
||||||
|
|
||||||
- [x] **Global Button Standardization (#177)**: Enforce `.btn-sm`/`.btn-md`/`.btn-lg`/`.btn-icon-sq` tiers across all stages, remove inline button styles, and add CSS custom properties for button metrics.
|
|
||||||
- [x] **Swatch Grid White Patch & Orientation Polish (#178)**: Finalise `is_pad` guard documentation, diagonally split swatch tooltips, and validate `printtarg` row/column ordering.
|
|
||||||
- [ ] **Configurable CIEDE2000 Thresholds (#184)**: User-configurable good/warning ΔE₀₀ upper bounds in Settings, persisted across sessions and applied to the Stage 3 swatch grid.
|
|
||||||
- [ ] **Stage 4 OBA/FWA Tooltips & Tests (#176)**: Contextual help for OBA/FWA, illuminant, observer, and viewing-condition controls; additional `colprof` arg builder tests.
|
|
||||||
- [ ] **3D Gamut Viewer Controls (#185)**: Camera reset, opacity sliders, keyboard shortcut, and full public-API JSDoc.
|
|
||||||
- [ ] **Gamut / Profcheck Hardening (#179)**: Validate `.gam` vertex/face parsing, improved `profcheck` regex fallbacks for legacy text output, and user-visible parser warnings.
|
|
||||||
|
|
||||||
### Milestone 12 — Future Workflow & Advanced Analytics (Deferred)
|
### Milestone 12 — Future Workflow & Advanced Analytics (Deferred)
|
||||||
- [ ] **Batch Verification & Drift Tracking (#95)**: Track printer drift over time by comparing periodic verification measurements against a baseline profile.
|
- [ ] **Batch Verification & Drift Tracking (#95)**: Track printer drift over time by comparing periodic verification measurements against a baseline profile.
|
||||||
- [ ] **Multi-Language Localization (#96)**: Full UI internationalization (English, German, French, Japanese).
|
- [ ] **Multi-Language Localization (#96)**: Full UI internationalization (English, German, French, Japanese).
|
||||||
|
|||||||
+34
-20
@@ -253,17 +253,14 @@ pub fn detect_driver_color_bypass(output: &str) -> Option<(&'static str, &'stati
|
|||||||
Some(("CNIJIntent2", "4"))
|
Some(("CNIJIntent2", "4"))
|
||||||
} else if output.contains("CNIJIntent") {
|
} else if output.contains("CNIJIntent") {
|
||||||
Some(("CNIJIntent", "4"))
|
Some(("CNIJIntent", "4"))
|
||||||
} else if output.contains("EPIJ_CCor") {
|
|
||||||
// Epson "Color Settings" option. The value 0 disables driver-side color correction.
|
|
||||||
Some(("EPIJ_CCor", "0"))
|
|
||||||
} else if output.contains("EPIJ_CMat") {
|
} else if output.contains("EPIJ_CMat") {
|
||||||
// Epson "Color Settings" option. The value 3 is "Off (No Color
|
// Epson "Color Settings" option. The value 3 is "Off (No Color
|
||||||
// Adjustment)" which disables driver-side color management.
|
// Adjustment)" which disables driver-side color management.
|
||||||
Some(("EPIJ_CMat", "3"))
|
Some(("EPIJ_CMat", "3"))
|
||||||
} else if output.contains("StpColorCorrection") {
|
|
||||||
Some(("StpColorCorrection", "Uncorrected"))
|
|
||||||
} else if output.contains("ColorCorrection") {
|
} else if output.contains("ColorCorrection") {
|
||||||
Some(("ColorCorrection", "Uncorrected"))
|
Some(("ColorCorrection", "Uncorrected"))
|
||||||
|
} else if output.contains("StpColorCorrection") {
|
||||||
|
Some(("StpColorCorrection", "Uncorrected"))
|
||||||
} else if output.contains("EpsonColorMode") {
|
} else if output.contains("EpsonColorMode") {
|
||||||
Some(("EpsonColorMode", "Off"))
|
Some(("EpsonColorMode", "Off"))
|
||||||
} else {
|
} else {
|
||||||
@@ -518,21 +515,38 @@ printer Zebra_Label disabled since Wed 10 Jun 2026 - reason: out of ribbon\n\
|
|||||||
let merged = merge_printer_info(&destinations, &statuses, default_dest);
|
let merged = merge_printer_info(&destinations, &statuses, default_dest);
|
||||||
assert_eq!(merged.len(), 4);
|
assert_eq!(merged.len(), 4);
|
||||||
|
|
||||||
assert_eq!(merged[0].name, "Epson-Stylus-SX420W".to_string());
|
assert_eq!(
|
||||||
assert_eq!(merged[0].status, "Idle".to_string());
|
merged[0],
|
||||||
assert!(merged[0].is_default);
|
Printer {
|
||||||
|
name: "Epson-Stylus-SX420W".to_string(),
|
||||||
assert_eq!(merged[1].name, "HP_LaserJet".to_string());
|
status: "Idle".to_string(),
|
||||||
assert_eq!(merged[1].status, "Printing".to_string());
|
is_default: true,
|
||||||
assert!(!merged[1].is_default);
|
}
|
||||||
|
);
|
||||||
assert_eq!(merged[2].name, "Virtual_PDF".to_string());
|
assert_eq!(
|
||||||
assert_eq!(merged[2].status, "Idle".to_string());
|
merged[1],
|
||||||
assert!(!merged[2].is_default);
|
Printer {
|
||||||
|
name: "HP_LaserJet".to_string(),
|
||||||
assert_eq!(merged[3].name, "Zebra_Label".to_string());
|
status: "Printing".to_string(),
|
||||||
assert_eq!(merged[3].status, "Stopped".to_string());
|
is_default: false,
|
||||||
assert!(!merged[3].is_default);
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
merged[2],
|
||||||
|
Printer {
|
||||||
|
name: "Virtual_PDF".to_string(),
|
||||||
|
status: "Idle".to_string(),
|
||||||
|
is_default: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
merged[3],
|
||||||
|
Printer {
|
||||||
|
name: "Zebra_Label".to_string(),
|
||||||
|
status: "Stopped".to_string(),
|
||||||
|
is_default: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+22
-22
@@ -35,9 +35,9 @@
|
|||||||
|
|
||||||
<!-- Profiling Preset Selector -->
|
<!-- Profiling Preset Selector -->
|
||||||
<div class="preset-selector-container" style="padding: 0 16px 12px; border-bottom: 1px solid var(--border-color, #2a2a30);">
|
<div class="preset-selector-container" style="padding: 0 16px 12px; border-bottom: 1px solid var(--border-color, #2a2a30);">
|
||||||
<div class="form-group-header">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||||
<label for="presetSelect" class="form-group-label-small">Profiling Preset</label>
|
<label for="presetSelect" style="font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; font-weight: 600;">Profiling Preset</label>
|
||||||
<div class="btn-row-sm">
|
<div style="display: flex; gap: 6px; align-items: center; justify-content: flex-end;">
|
||||||
<button type="button" id="btnSavePresetModal" class="icon-btn btn-sm" title="Save current settings as Preset">
|
<button type="button" id="btnSavePresetModal" class="icon-btn btn-sm" title="Save current settings as Preset">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -131,10 +131,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group has-tooltip">
|
<div class="form-group has-tooltip">
|
||||||
<div class="form-group-header">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||||
<label>Project Directory & Filename</label>
|
<label style="margin: 0;">Project Directory & Filename</label>
|
||||||
<div class="btn-row-sm">
|
<div style="display:flex; justify-content:flex-end;">
|
||||||
<button type="button" id="btn-import-dataset" class="secondary btn-sm">Import Dataset...</button>
|
<button type="button" id="btn-import-dataset" class="secondary btn-sm" style="margin-right: 4px;">Import Dataset...</button>
|
||||||
<button type="button" id="btnOpenExisting" class="secondary btn-sm">Open Target (.ti2/.ti3)</button>
|
<button type="button" id="btnOpenExisting" class="secondary btn-sm">Open Target (.ti2/.ti3)</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -407,8 +407,8 @@
|
|||||||
|
|
||||||
<!-- Target Label & Metadata (printtarg -d) -->
|
<!-- Target Label & Metadata (printtarg -d) -->
|
||||||
<div class="form-group" style="margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-color, #333);">
|
<div class="form-group" style="margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-color, #333);">
|
||||||
<div class="form-group-header">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||||
<label class="form-group-title">Target Label & Metadata (Optional)</label>
|
<label style="margin: 0; font-weight: 600;">Target Label & Metadata (Optional)</label>
|
||||||
<button type="button" class="secondary btn-sm" id="btnToggleLabelEdit">Edit Custom Field</button>
|
<button type="button" class="secondary btn-sm" id="btnToggleLabelEdit">Edit Custom Field</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="sub-label" style="margin-bottom: 10px;">Printed below test patches to identify hardware, ink, and media used for this profiling run.</p>
|
<p class="sub-label" style="margin-bottom: 10px;">Printed below test patches to identify hardware, ink, and media used for this profiling run.</p>
|
||||||
@@ -481,7 +481,7 @@
|
|||||||
<select id="printerSelect">
|
<select id="printerSelect">
|
||||||
<option value="" disabled selected>Detecting printers...</option>
|
<option value="" disabled selected>Detecting printers...</option>
|
||||||
</select>
|
</select>
|
||||||
<button type="button" class="btn-icon-sq" id="btnRefreshPrinters" title="Refresh printer list">↻</button>
|
<button type="button" class="btn-icon" id="btnRefreshPrinters" title="Refresh printer list">↻</button>
|
||||||
<button type="button" class="btn-properties" id="btnPrinterProperties" title="Open native printer driver preferences (disable colour management, set media type, etc.)">⚙️ Preferences</button>
|
<button type="button" class="btn-properties" id="btnPrinterProperties" title="Open native printer driver preferences (disable colour management, set media type, etc.)">⚙️ Preferences</button>
|
||||||
<span id="printerStatusBadge" class="status-badge badge-idle hidden">Idle</span>
|
<span id="printerStatusBadge" class="status-badge badge-idle hidden">Idle</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -521,7 +521,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="print-actions-row">
|
<div class="print-actions-row">
|
||||||
<button class="primary" id="btnPrintAll">
|
<button class="primary btn-print" id="btnPrintAll">
|
||||||
<span class="btn-text">🖨️ Print All Pages (Bypass CM)</span>
|
<span class="btn-text">🖨️ Print All Pages (Bypass CM)</span>
|
||||||
<span class="btn-spinner hidden"></span>
|
<span class="btn-spinner hidden"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -550,8 +550,8 @@
|
|||||||
<div class="form-container" style="margin-bottom: 16px;">
|
<div class="form-container" style="margin-bottom: 16px;">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="chartreadInstrumentSelect">Communication Port</label>
|
<label for="chartreadInstrumentSelect">Communication Port</label>
|
||||||
<div class="input-row-sm">
|
<div class="input-row" style="display: flex; gap: 8px; align-items: center;">
|
||||||
<select id="chartreadInstrumentSelect">
|
<select id="chartreadInstrumentSelect" style="flex: 1;">
|
||||||
<option value="">Auto (First available port)</option>
|
<option value="">Auto (First available port)</option>
|
||||||
</select>
|
</select>
|
||||||
<button type="button" class="secondary" id="btnDetectInstruments" title="Detect connected instruments via instlist">↻ Detect</button>
|
<button type="button" class="secondary" id="btnDetectInstruments" title="Detect connected instruments via instlist">↻ Detect</button>
|
||||||
@@ -593,13 +593,13 @@
|
|||||||
|
|
||||||
<!-- Multi-Pass Session & Averaging Controls -->
|
<!-- Multi-Pass Session & Averaging Controls -->
|
||||||
<div class="averaging-panel hidden" id="chartreadAveragingPanel" style="margin-top: 16px; padding: 14px; border: 1px solid var(--border-color, #333); border-radius: 8px; background: rgba(255, 255, 255, 0.03);">
|
<div class="averaging-panel hidden" id="chartreadAveragingPanel" style="margin-top: 16px; padding: 14px; border: 1px solid var(--border-color, #333); border-radius: 8px; background: rgba(255, 255, 255, 0.03);">
|
||||||
<div class="form-group-header">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
<h3 class="form-group-title">Multi-Pass Target Averaging</h3>
|
<h3 style="margin: 0; font-size: 1rem;">Multi-Pass Target Averaging</h3>
|
||||||
<span id="passCounterBadge" class="status-badge badge-idle">Pass 1 Complete</span>
|
<span id="passCounterBadge" class="status-badge badge-idle">Pass 1 Complete</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="sub-label" style="margin-bottom: 12px;">Each successful sheet is saved as a separate _passN.ti3 file. Stage 4 stays locked until you finish. Measure extra copies to reduce noise, then finish to write the canonical .ti3.</p>
|
<p class="sub-label" style="margin-bottom: 12px;">Each successful sheet is saved as a separate _passN.ti3 file. Stage 4 stays locked until you finish. Measure extra copies to reduce noise, then finish to write the canonical .ti3.</p>
|
||||||
<div id="passesList" class="passes-list" style="display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px;"></div>
|
<div id="passesList" class="passes-list" style="display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px;"></div>
|
||||||
<div class="btn-row">
|
<div style="display: flex; gap: 8px;">
|
||||||
<button type="button" class="secondary" id="btnMeasureAnotherSheet">➕ Measure Another Sheet</button>
|
<button type="button" class="secondary" id="btnMeasureAnotherSheet">➕ Measure Another Sheet</button>
|
||||||
<button type="button" class="primary" id="btnFinishAndAverage" title="Complete measurement (Finish) before profiling">✓ Average Passes & Proceed →</button>
|
<button type="button" class="primary" id="btnFinishAndAverage" title="Complete measurement (Finish) before profiling">✓ Average Passes & Proceed →</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -660,8 +660,8 @@
|
|||||||
|
|
||||||
<div class="form-group hidden" id="colprofCustomSpRow">
|
<div class="form-group hidden" id="colprofCustomSpRow">
|
||||||
<label>Custom Spectrum File (.sp)</label>
|
<label>Custom Spectrum File (.sp)</label>
|
||||||
<div class="input-row-sm">
|
<div style="display: flex; gap: 8px;">
|
||||||
<input type="text" id="colprofCustomSpPath" placeholder="Select .sp file..." readonly>
|
<input type="text" id="colprofCustomSpPath" placeholder="Select .sp file..." readonly style="flex:1;">
|
||||||
<button class="secondary btn-sm" id="btnBrowseCustomSp">Browse</button>
|
<button class="secondary btn-sm" id="btnBrowseCustomSp">Browse</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -723,7 +723,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stage-actions">
|
<div class="stage-actions">
|
||||||
<button class="primary btn-lg" id="btnCreateProfile">Calculate Profile (.icc)</button>
|
<button class="primary" id="btnCreateProfile">Calculate Profile (.icc)</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Spinner and Progress Stage -->
|
<!-- Spinner and Progress Stage -->
|
||||||
@@ -736,7 +736,7 @@
|
|||||||
<div class="success-card hidden" id="colprofSuccessCard">
|
<div class="success-card hidden" id="colprofSuccessCard">
|
||||||
<h3>🎉 Profile Calculation Complete</h3>
|
<h3>🎉 Profile Calculation Complete</h3>
|
||||||
<p id="colprofSuccessInfo"></p>
|
<p id="colprofSuccessInfo"></p>
|
||||||
<button class="primary btn-lg" id="btnGoToVerify">Proceed to Verification →</button>
|
<button class="primary" id="btnGoToVerify">Proceed to Verification →</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Process Log -->
|
<!-- Process Log -->
|
||||||
@@ -752,7 +752,7 @@
|
|||||||
<p>Check the numerical accuracy of your profile against the original measurement data.</p>
|
<p>Check the numerical accuracy of your profile against the original measurement data.</p>
|
||||||
|
|
||||||
<div class="stage-actions">
|
<div class="stage-actions">
|
||||||
<button class="primary btn-lg" id="btnVerify">Verify Profile Accuracy</button>
|
<button class="primary" id="btnVerify">Verify Profile Accuracy</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Report Card -->
|
<!-- Report Card -->
|
||||||
@@ -862,7 +862,7 @@
|
|||||||
</select>
|
</select>
|
||||||
<small class="help-hint" style="display:block; font-size:0.75rem; color:var(--text-muted, #888); margin-top:3px;">Applies immediately on Save</small>
|
<small class="help-hint" style="display:block; font-size:0.75rem; color:var(--text-muted, #888); margin-top:3px;">Applies immediately on Save</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row wrap">
|
<div style="display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap;">
|
||||||
<button type="button" id="btnOpenLogFolder" class="secondary" title="Open log folder in file explorer">📂 Open Log Folder</button>
|
<button type="button" id="btnOpenLogFolder" class="secondary" title="Open log folder in file explorer">📂 Open Log Folder</button>
|
||||||
<button type="button" id="btnCopyLogPath" class="secondary" title="Copy full log file path to clipboard">📋 Copy Path</button>
|
<button type="button" id="btnCopyLogPath" class="secondary" title="Copy full log file path to clipboard">📋 Copy Path</button>
|
||||||
<button type="button" id="btnCopyLogExcerpt" class="secondary" title="Copy recent log entries (last 200 lines) to clipboard">📋 Copy Recent Logs</button>
|
<button type="button" id="btnCopyLogExcerpt" class="secondary" title="Copy recent log entries (last 200 lines) to clipboard">📋 Copy Recent Logs</button>
|
||||||
|
|||||||
+5
-26
@@ -5,19 +5,6 @@ const { listen } = window.__TAURI__.event;
|
|||||||
|
|
||||||
let unlistenJsonRow = null;
|
let unlistenJsonRow = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Traffic-light classification for a CIEDE2000 value using default
|
|
||||||
* tolerance bands. These defaults are overridden by user settings when
|
|
||||||
* configured (see feat/184-configurable-delta-e).
|
|
||||||
* @param {number} deltaE
|
|
||||||
* @returns {string} "Good" | "Warning" | "Bad"
|
|
||||||
*/
|
|
||||||
function classifyDeltaE(deltaE) {
|
|
||||||
if (deltaE < 2) return "Good";
|
|
||||||
if (deltaE < 5) return "Warning";
|
|
||||||
return "Bad";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start listening for row events from a chartread process.
|
* Start listening for row events from a chartread process.
|
||||||
* @param {string} processId - The process ID (e.g. "chartread_my_profile")
|
* @param {string} processId - The process ID (e.g. "chartread_my_profile")
|
||||||
@@ -64,8 +51,6 @@ export async function startSwatchListener(processId, onRowComplete) {
|
|||||||
if (progressText) progressText.textContent = `Strip ${data.row_id} — ${data.row_index + 1} / ${data.total_rows}`;
|
if (progressText) progressText.textContent = `Strip ${data.row_id} — ${data.row_index + 1} / ${data.total_rows}`;
|
||||||
|
|
||||||
// Create row container
|
// Create row container
|
||||||
// Row and patch order from chartread (A->Z, 1->N) matches the physical
|
|
||||||
// left-to-right / top-to-bottom layout produced by printtarg.
|
|
||||||
const rowEl = document.createElement("div");
|
const rowEl = document.createElement("div");
|
||||||
rowEl.className = "swatch-row";
|
rowEl.className = "swatch-row";
|
||||||
|
|
||||||
@@ -78,10 +63,6 @@ export async function startSwatchListener(processId, onRowComplete) {
|
|||||||
rowPatches.className = "swatch-row-patches";
|
rowPatches.className = "swatch-row-patches";
|
||||||
|
|
||||||
for (const patch of data.patches) {
|
for (const patch of data.patches) {
|
||||||
// Argyll marks spacer/boundary patches with is_pad. Some printtarg
|
|
||||||
// layouts also flag white reference patches (-e white steps) as pads,
|
|
||||||
// but those carry valid expected or measured device data. Only skip
|
|
||||||
// pads that have no measurement and no non-zero device coordinates.
|
|
||||||
if (patch.is_pad && !patch.measured && (!patch.device || patch.device.every(v => v === 0))) continue;
|
if (patch.is_pad && !patch.measured && (!patch.device || patch.device.every(v => v === 0))) continue;
|
||||||
|
|
||||||
const patchEl = document.createElement("div");
|
const patchEl = document.createElement("div");
|
||||||
@@ -103,21 +84,20 @@ export async function startSwatchListener(processId, onRowComplete) {
|
|||||||
|
|
||||||
const swatch = document.createElement("div");
|
const swatch = document.createElement("div");
|
||||||
swatch.className = "swatch-color";
|
swatch.className = "swatch-color";
|
||||||
|
|
||||||
if (measuredCss) {
|
if (measuredCss) {
|
||||||
swatch.style.background = `linear-gradient(135deg, ${intendedCss} 50%, ${measuredCss} 50%)`;
|
swatch.style.background = `linear-gradient(135deg, ${intendedCss} 50%, ${measuredCss} 50%)`;
|
||||||
} else {
|
} else {
|
||||||
swatch.style.backgroundColor = intendedCss;
|
swatch.style.backgroundColor = intendedCss;
|
||||||
}
|
}
|
||||||
|
|
||||||
patchEl.appendChild(swatch);
|
patchEl.appendChild(swatch);
|
||||||
|
|
||||||
// Build a structured tooltip for the patch
|
|
||||||
let titleStr = `${patch.loc} (ID: ${patch.id})`;
|
let titleStr = `${patch.loc} (ID: ${patch.id})`;
|
||||||
if (patch.expected && patch.expected.Lab) {
|
if (patch.expected && patch.expected.Lab) {
|
||||||
titleStr += `\nIntended Lab: ${patch.expected.Lab.map(v => v.toFixed(1)).join(', ')}`;
|
titleStr += `\nIntended Lab: ${patch.expected.Lab.map(v => v.toFixed(1)).join(', ')}`;
|
||||||
} else if (patch.device) {
|
} else if (patch.device) {
|
||||||
titleStr += `\nIntended Device: ${patch.device.map(v => v.toFixed(1)).join(', ')}`;
|
titleStr += `\nDevice: ${patch.device.map(v => v.toFixed(1)).join(', ')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (patch.measured && patch.measured.Lab) {
|
if (patch.measured && patch.measured.Lab) {
|
||||||
@@ -127,13 +107,12 @@ export async function startSwatchListener(processId, onRowComplete) {
|
|||||||
// Compute and display ΔE₀₀ if both expected and measured Lab are present
|
// Compute and display ΔE₀₀ if both expected and measured Lab are present
|
||||||
if (patch.expected && patch.expected.Lab && patch.measured && patch.measured.Lab) {
|
if (patch.expected && patch.expected.Lab && patch.measured && patch.measured.Lab) {
|
||||||
const deltaE = computeDeltaE00(patch.expected.Lab, patch.measured.Lab);
|
const deltaE = computeDeltaE00(patch.expected.Lab, patch.measured.Lab);
|
||||||
const classification = classifyDeltaE(deltaE);
|
|
||||||
|
|
||||||
const deLabel = document.createElement("div");
|
const deLabel = document.createElement("div");
|
||||||
deLabel.className = "swatch-de";
|
deLabel.className = "swatch-de";
|
||||||
deLabel.textContent = deltaE.toFixed(1);
|
deLabel.textContent = deltaE.toFixed(1);
|
||||||
|
|
||||||
titleStr += `\nΔE₀₀: ${deltaE.toFixed(2)} (${classification})`;
|
titleStr += `\nΔE₀₀: ${deltaE.toFixed(2)}`;
|
||||||
|
|
||||||
// Traffic light classification
|
// Traffic light classification
|
||||||
if (deltaE < 2) {
|
if (deltaE < 2) {
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// Manual smoke tests for swatch_grid.js
|
|
||||||
// Run in a browser/devtools console after the app has loaded:
|
|
||||||
// import('./swatch_grid.test.js').then(m => m.runAll())
|
|
||||||
|
|
||||||
export function runAll() {
|
|
||||||
console.group('swatch_grid smoke tests');
|
|
||||||
testClassifyDeltaE();
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
function testClassifyDeltaE() {
|
|
||||||
// classifyDeltaE is currently internal to swatch_grid.js, but we can
|
|
||||||
// validate the visual classification by exercising the colour thresholds.
|
|
||||||
// This is a placeholder for a future unit-test harness.
|
|
||||||
console.log('testClassifyDeltaE: no direct export; validated by manual inspection');
|
|
||||||
}
|
|
||||||
+22
-102
@@ -5,11 +5,6 @@
|
|||||||
--text-color: #d4d4d4;
|
--text-color: #d4d4d4;
|
||||||
--accent-color: #007acc;
|
--accent-color: #007acc;
|
||||||
--border-color: #333333;
|
--border-color: #333333;
|
||||||
--btn-height-sm: 28px;
|
|
||||||
--btn-height-md: 36px;
|
|
||||||
--btn-height-lg: 40px;
|
|
||||||
--btn-radius-sm: 4px;
|
|
||||||
--btn-radius-md: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -132,27 +127,25 @@ button.primary {
|
|||||||
background: linear-gradient(135deg, var(--accent-color), #2d73a8);
|
background: linear-gradient(135deg, var(--accent-color), #2d73a8);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--btn-radius-md);
|
border-radius: 6px;
|
||||||
padding: 0 18px;
|
padding: 0 18px;
|
||||||
height: var(--btn-height-md);
|
height: 36px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.2s, transform 0.1s;
|
transition: opacity 0.2s, transform 0.1s;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary:hover {
|
button.primary:hover {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button sizing system — use CSS variables for consistency across the UI */
|
/* Button sizing system */
|
||||||
.btn-sm { height: var(--btn-height-sm); padding: 0 10px; font-size: 0.78rem; border-radius: var(--btn-radius-sm); box-sizing: border-box; }
|
.btn-sm { height: 28px; padding: 0 10px; font-size: 0.78rem; border-radius: 4px; }
|
||||||
.btn-md { height: var(--btn-height-md); padding: 0 16px; font-size: 0.875rem; border-radius: var(--btn-radius-md); box-sizing: border-box; }
|
.btn-md { height: 36px; padding: 0 16px; font-size: 0.875rem; border-radius: 6px; }
|
||||||
.btn-lg { height: var(--btn-height-lg); padding: 0 20px; font-size: 0.95rem; font-weight: 600; border-radius: var(--btn-radius-md); box-sizing: border-box; }
|
.btn-lg { height: 40px; padding: 0 20px; font-size: 0.95rem; font-weight: 600; border-radius: 6px; }
|
||||||
button.primary.btn-lg { height: var(--btn-height-lg); }
|
.btn-icon-sq { width: 36px; height: 36px; min-width: 36px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
|
||||||
.btn-icon-sq { width: var(--btn-height-md); height: var(--btn-height-md); min-width: var(--btn-height-md); padding: 0; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; }
|
|
||||||
|
|
||||||
/* Action containers */
|
/* Action containers */
|
||||||
.stage-actions {
|
.stage-actions {
|
||||||
@@ -170,63 +163,10 @@ button.primary.btn-lg { height: var(--btn-height-lg); }
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Utility for a horizontal row of buttons (replaces inline style="display:flex;..." on parent divs) */
|
|
||||||
.btn-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-row-end {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-row-sm {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-row.wrap,
|
|
||||||
.btn-row-sm.wrap {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
margin: 14px 0;
|
margin: 14px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group-header label {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group-label-small {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
opacity: 0.7;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group-title {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group:first-child {
|
.form-group:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
@@ -314,17 +254,6 @@ select option:checked {
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-row-sm {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-row-sm > input,
|
|
||||||
.input-row-sm > select {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-row input {
|
.input-row input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
@@ -355,10 +284,9 @@ button.secondary {
|
|||||||
color: white;
|
color: white;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
height: var(--btn-height-md);
|
height: 36px;
|
||||||
border-radius: var(--btn-radius-sm);
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.secondary:hover {
|
button.secondary:hover {
|
||||||
@@ -561,7 +489,6 @@ button:disabled {
|
|||||||
.chartread-actions {
|
.chartread-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
@@ -570,10 +497,9 @@ button.danger {
|
|||||||
background-color: #c62828;
|
background-color: #c62828;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0 16px;
|
padding: 10px 20px;
|
||||||
height: var(--btn-height-md);
|
font-size: 1rem;
|
||||||
font-size: 0.875rem;
|
border-radius: 4px;
|
||||||
border-radius: var(--btn-radius-sm);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,12 +779,12 @@ button.danger:hover {
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: var(--btn-radius-sm);
|
border-radius: 4px;
|
||||||
color: #d1d5db;
|
color: #d1d5db;
|
||||||
transition: color 0.15s ease, background-color 0.15s ease;
|
transition: color 0.15s ease, background-color 0.15s ease;
|
||||||
width: var(--btn-height-sm);
|
width: 28px;
|
||||||
height: var(--btn-height-sm);
|
height: 28px;
|
||||||
min-width: var(--btn-height-sm);
|
min-width: 28px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1047,9 +973,8 @@ button.danger:hover {
|
|||||||
background: rgba(255, 255, 255, 0.06);
|
background: rgba(255, 255, 255, 0.06);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
padding: 0 12px;
|
padding: 6px 12px;
|
||||||
height: var(--btn-height-md);
|
border-radius: 4px;
|
||||||
border-radius: var(--btn-radius-sm);
|
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -1059,7 +984,6 @@ button.danger:hover {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-properties:hover {
|
.btn-properties:hover {
|
||||||
@@ -1118,24 +1042,20 @@ button.danger:hover {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Legacy alias: .btn-icon should not be used for new buttons; prefer .btn-icon-sq */
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
width: var(--btn-height-md);
|
|
||||||
height: var(--btn-height-md);
|
|
||||||
min-width: var(--btn-height-md);
|
|
||||||
padding: 0;
|
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
border-radius: var(--btn-radius-sm);
|
width: 34px;
|
||||||
display: inline-flex;
|
height: 34px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon:hover {
|
.btn-icon:hover {
|
||||||
|
|||||||
Reference in New Issue
Block a user