Files
iccery-v2-mac/Sources/ICCery/ICCeryApp.swift
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> ee16fb3fae Settings store, logging & settings dialog (#5)
- AppSettings: snake_case Codable model — argyll_binary_dir,
  default_instrument (stored, never applied to argv), log_level (nil ->
  Debug debug / Info release), delta_e thresholds (2.0/5.0),
  custom_presets, enable_i1pro2_leds, calibration_stale_days 30,
  default_install_location user, ask_before_overwrite_profile,
  open_color_panel_after_install
- Validation with the exact contract strings; save() refuses invalid
  settings; corrupt/missing JSON -> defaults; settingsDidChange
  notification posted on save (for #20)
- LogSink: rolling file at ~/Library/Logs/com.gronod.iccery2/
  iccery.log, 5 MiB x 5 segments, runtime setLevel applied at startup
  and on save (#158); AppLogger gates os_log+file through it
- SettingsView sheet: Argyll dir picker, instrument (display-only
  caveat), i1Pro2 LEDs, ΔE fields + inline errors, stale days, install
  location, overwrite + ColorSync toggles, log level, open-log-folder /
  copy-path / copy-excerpt
- v1 settings path never read; writes atomic via AtomicFileWriter
- 13 new tests; 60/60 green

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

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

51 lines
1.7 KiB
Swift
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.
import AppKit
import ICCeryCore
import SwiftUI
@main
struct ICCeryApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
@State private var model = WizardViewModel()
init() {
try? AppPaths.ensureDirectories()
// Log level is runtime state — apply persisted settings at
// startup (#158); the Settings sheet re-applies on save.
LogSink.shared.applySettings(SettingsStore().load())
}
var body: some Scene {
// Single fixed window (docs/21 §Shell: 1280×800, min 1100×700).
Window("ICCery", id: "main") {
RootView(model: model)
.frame(minWidth: 1100, minHeight: 700)
.preferredColorScheme(.dark)
}
.defaultSize(width: 1280, height: 800)
.windowResizability(.contentMinSize)
.defaultPosition(.center)
}
}
/// AppDelegate: quit when the single window closes, and `killAll` Argyll
/// children before teardown (#147/#149). Termination is deferred until
/// `killAll` has signaled every child so `chartread` can park an XY head
/// when the UI already sent `q\n`.
final class AppDelegate: NSObject, NSApplicationDelegate {
private var terminationRequested = false
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
true
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
guard !terminationRequested else { return .terminateNow }
terminationRequested = true
Task {
await ProcessManager.shared.killAll()
NSApplication.shared.reply(toApplicationShouldTerminate: true)
}
return .terminateLater
}
}