9 Commits
Author SHA1 Message Date
gronod 193e492c10 Update .gitea/workflows/build-macos.yml
Build Windows Packages / Build Windows (push) Successful in 7m35s
Build macOS Packages / Build macOS (Apple Silicon) (push) Successful in 9m25s
Build Linux Packages / Build Linux (push) Successful in 10m52s
Build macOS Packages / Build macOS (Intel) (push) Successful in 7m14s
Re-enabled arm64
2026-09-06 20:16:52 +01:00
gronod 899718d896 Merge pull request 'Merge development into main' (#220) from development into main
Build Windows Packages / Build Windows (push) Successful in 7m44s
Build macOS Packages / Build macOS (Intel) (push) Successful in 8m18s
Build Linux Packages / Build Linux (push) Successful in 10m18s
2026-09-06 19:26:32 +01:00
gronod 7a9637b5d0 Merge pull request 'ci(macos): allow headless DMG bundling on Gitea runners' (#219) from ci/fix-macos-dmg-bundling into development 2026-09-06 19:26:24 +01:00
gronod 1af844c7e2 ci(macos): allow headless DMG bundling on Gitea runners 2026-09-06 19:24:30 +01:00
gronod 56f275a023 Merge branch 'ci/215-frontend-tests' into development (#215) 2026-09-06 16:40:43 +01:00
gronod 54792a8690 Merge branch 'fix/213-atomic-history-writes' into development (#213) 2026-09-06 16:40:41 +01:00
gronod 906b4f9104 ci: add npm test script and integrate frontend tests into CI workflows (#215) 2026-09-06 16:40:37 +01:00
gronod 5f5bf76594 fix(quality_store): atomic file writes for verification_history.json (#213) 2026-09-06 16:39:53 +01:00
gronod 9c7d563dbf chore: bump version to v0.8.4 2026-09-06 16:37:59 +01:00
10 changed files with 97 additions and 31 deletions
+3
View File
@@ -64,6 +64,9 @@ jobs:
- name: Install Node dependencies
run: npm ci
- name: Run frontend tests
run: npm test
- name: Set Release Environment
id: set_env
shell: bash
+9 -20
View File
@@ -19,11 +19,11 @@ jobs:
target: x86_64-apple-darwin
binary_dir: macos-x86_64
arch: x86_64
# - name: Apple Silicon
# os: macos
# target: aarch64-apple-darwin
# binary_dir: macos-aarch64
# arch: arm64
- name: Apple Silicon
os: macos
target: aarch64-apple-darwin
binary_dir: macos-aarch64
arch: arm64
# - name: Universal (Intel + Apple Silicon)
# os: macos
# target: universal-apple-darwin
@@ -59,6 +59,9 @@ jobs:
- name: Install Node dependencies
run: npm ci
- name: Run frontend tests
run: npm test
- name: Fetch ArgyllCMS binaries
env:
ARGYLL_SERVER_URL: ${{ github.server_url }}
@@ -81,23 +84,9 @@ jobs:
echo "SHORT_SHA=${SHORT_SHA}" >> $GITHUB_ENV
echo "PREFIX=ICCery_${TAG}-${SHORT_SHA}-macos-${{ matrix.platform.arch }}" >> $GITHUB_ENV
- name: Warm up macOS GUI for DMG layout
shell: bash
run: |
# Tauri's DMG bundler runs Finder AppleScript to set the background
# picture and icon positions. On CI Macs, Finder/Aqua may be idle,
# which causes AppleEvent timeouts (-1712). Wake them before build.
echo "WindowServer: $(pgrep -l WindowServer || echo 'NOT RUNNING')"
echo "console user: $(stat -f '%Su' /dev/console 2>/dev/null || echo unknown)"
echo "whoami: $(whoami)"
open -g -a Finder || true
open -g -a "System Events" || true
osascript -e 'tell application "Finder" to get name' || true
sleep 10
- name: Build Tauri App
env:
TAURI_BUNDLER_DMG_IGNORE_CI: "true"
CI: "true"
run: npm run tauri build -- --target ${{ matrix.platform.target }}
- name: Prepare Release Assets
+3
View File
@@ -41,6 +41,9 @@ jobs:
- name: Install Node dependencies
run: npm ci
- name: Run frontend tests
run: npm test
- name: Fetch ArgyllCMS binaries
shell: powershell
env:
+1 -1
View File
@@ -2,7 +2,7 @@
> Modern, cross-platform native desktop application for printer profiling, powered by ArgyllCMS.
[![Release](https://img.shields.io/badge/version-v0.8.3-blue.svg)](https://git.i3omb.com/gronod/ICCery)
[![Release](https://img.shields.io/badge/version-v0.8.4-blue.svg)](https://git.i3omb.com/gronod/ICCery)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://git.i3omb.com/gronod/ICCery)
[![Framework](https://img.shields.io/badge/framework-Tauri%20v2%20%2B%20Rust-orange.svg)](https://tauri.app)
[![License](https://img.shields.io/badge/license-Proprietary%20%2F%20EULA-blue.svg)](LICENCE.md)
+5 -1
View File
@@ -14,7 +14,7 @@ ICCery is a native, cross-platform desktop application built with:
- **Frontend**: Vanilla JS (ES Modules) + HTML5/CSS3 with a modern dark theme and responsive layout.
- **Visualization**: Three.js WebGL engine for 3D CIELAB color gamut volumes and sRGB reference comparisons.
- **Engine**: ArgyllCMS command-line utilities orchestrated over isolated standard stream IPC (`stdin`, `stdout`, `stderr`).
- **Current Version**: `v0.8.3` (Production release).
- **Current Version**: `v0.8.4` (Production release).
---
@@ -113,6 +113,10 @@ ICCery is a native, cross-platform desktop application built with:
- [x] **Custom Spectrum File Picker Dialog (#210)**: Implemented native `select_spectrum_file` command wrapping Tauri file dialog with `.sp` filter for custom FWA/OBA spectrum selection in Stage 4 profile generation.
- [x] **CGATS Dataset Import File Picker & State Synchronization (#211)**: Implemented native `select_dataset_file` command with `.ti3`, `.txt`, `.cgats`, and `.csv` filter, synchronized wizard target directory and basename upon import, and guarded against empty target states.
### Maintenance & Reliability Release (`v0.8.4`)
- [x] **Atomic Verification History Persistence (#213)**: Hardened `quality_store.rs` with atomic temporary file writes (`.tmp`), explicit flush/sync, and atomic rename to prevent historical drift data loss or corruption upon unexpected system crashes.
- [x] **Frontend Unit Testing & CI Integration (#215)**: Added standard `npm test` script executing the 3 frontend test suites (`profcheck`, `chartread`, and `gamut_viewer`) and integrated automated frontend test validation into macOS, Linux, and Windows CI workflows.
---
## 3. Future Roadmap
+3 -2
View File
@@ -1,11 +1,12 @@
{
"name": "iccery",
"private": true,
"version": "0.8.3",
"version": "0.8.4",
"type": "module",
"scripts": {
"fetch-argyll": "node scripts/fetch-argyll.mjs",
"tauri": "tauri"
"tauri": "tauri",
"test": "node src/js/profcheck.test.js && node src/js/chartread.test.js && node src/js/gamut_viewer.test.js"
},
"devDependencies": {
"@tauri-apps/cli": "^2"
+1 -1
View File
@@ -1423,7 +1423,7 @@ dependencies = [
[[package]]
name = "iccery"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64 0.22.1",
"image",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "iccery"
version = "0.8.3"
version = "0.8.4"
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
authors = ["Gordon"]
edition = "2021"
+70 -4
View File
@@ -100,15 +100,21 @@ pub fn load_history_file(path: &Path) -> Vec<VerificationRecord> {
}
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return Vec::new(),
Err(e) => {
log::warn!("Failed to read verification history at {}: {}", path.display(), e);
return Vec::new();
}
};
match serde_json::from_str::<VerificationHistoryStore>(&content) {
Ok(store) => store.records,
Err(_) => Vec::new(),
Err(e) => {
log::error!("Failed to parse verification history at {}: {}", path.display(), e);
Vec::new()
}
}
}
/// Writes verification records to the specified JSON path.
/// Writes verification records to the specified JSON path atomically.
/// Automatically creates parent directories if needed.
/// Evicts oldest records by timestamp if count exceeds HISTORY_CAP (1,000).
pub fn write_history_file(path: &Path, records: &[VerificationRecord]) -> Result<(), String> {
@@ -132,7 +138,33 @@ pub fn write_history_file(path: &Path, records: &[VerificationRecord]) -> Result
let json = serde_json::to_string_pretty(&store)
.map_err(|e| format!("Failed to serialize verification history: {}", e))?;
fs::write(path, json).map_err(|e| format!("Failed to write verification history: {}", e))?;
let mut tmp_path = path.as_os_str().to_os_string();
tmp_path.push(".tmp");
let tmp_path = PathBuf::from(tmp_path);
// Write to temporary file with explicit flush and sync
{
use std::io::Write;
let mut file = fs::File::create(&tmp_path)
.map_err(|e| format!("Failed to create temp history file: {}", e))?;
file.write_all(json.as_bytes())
.map_err(|e| {
let _ = fs::remove_file(&tmp_path);
format!("Failed to write temp history file: {}", e)
})?;
file.sync_all()
.map_err(|e| {
let _ = fs::remove_file(&tmp_path);
format!("Failed to sync temp history file: {}", e)
})?;
}
// Atomically replace destination file
if let Err(e) = fs::rename(&tmp_path, path) {
let _ = fs::remove_file(&tmp_path);
return Err(format!("Failed to atomically replace verification history file: {}", e));
}
Ok(())
}
@@ -418,4 +450,38 @@ mod tests {
assert_eq!(p2.extension().unwrap(), "csv");
assert_eq!(p2.to_string_lossy(), "/tmp/history.csv");
}
#[test]
fn test_atomic_write_preserves_data_and_cleans_tmp() {
let temp_dir = std::env::temp_dir().join("iccery_test_atomic_write");
let _ = fs::remove_dir_all(&temp_dir);
fs::create_dir_all(&temp_dir).unwrap();
let file_path = temp_dir.join("verification_history.json");
let mut tmp_file_path = file_path.as_os_str().to_os_string();
tmp_file_path.push(".tmp");
let tmp_file_path = std::path::PathBuf::from(tmp_file_path);
let rec = sample_record("vr-atom-1", "2026-09-06T12:00:00Z", 0.5);
write_history_file(&file_path, &[rec.clone()]).unwrap();
assert!(file_path.exists(), "Target file must exist");
assert!(!tmp_file_path.exists(), "Temporary file must not remain after successful atomic write");
let loaded = load_history_file(&file_path);
assert_eq!(loaded.len(), 1);
assert_eq!(loaded[0], rec);
// Overwrite with updated records to verify atomic replacement
let rec2 = sample_record("vr-atom-2", "2026-09-06T12:05:00Z", 1.2);
write_history_file(&file_path, &[rec.clone(), rec2.clone()]).unwrap();
assert!(!tmp_file_path.exists(), "Temporary file must not remain after overwrite");
let loaded2 = load_history_file(&file_path);
assert_eq!(loaded2.len(), 2);
assert_eq!(loaded2[0], rec);
assert_eq!(loaded2[1], rec2);
let _ = fs::remove_dir_all(&temp_dir);
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ICCery",
"version": "0.8.3",
"version": "0.8.4",
"identifier": "com.gronod.iccery",
"build": {
"frontendDist": "../src"