feat(m9): state management and view demotions for macOS 12 (Slice 4) #108

Merged
gronod merged 1 commits from feat/m9-slice4-state-and-views into milestone/m9-monterey 2026-09-11 21:57:57 +01:00
Owner

Slice 4 (Phases 3 & 4) of the M9 macOS 12 retarget.

State management (Observation → Combine)

  • All 8 view models (TargetWorkflow, ProfileWorkflow, Calibration, MeasurementWorkflow, PrintSession, Wizard, Gamut, Settings) migrated from @Observable/import Observation to ObservableObject + import Combine.
  • Every observed mutable property is now @Published (incl. @Published private(set)); @MainActor isolation and didSet observers retained.
  • Views: @State on view models → @StateObject (ICCeryApp, SettingsView, GamutView); all @Bindable occurrences → @ObservedObject; plain ViewModel parameters converted to @ObservedObject (RootView.WizardStageContent, Stage2View.GalleryPageView, SidebarView).
  • Child-VM observation: SwiftUI does not propagate nested ObservableObject changes through a parent, so views reading workflow.wizard.*, workflow.print.*, or workflow.profile.* now hold the child as an explicit @ObservedObject (SidebarView observes wizard+profile; Stage2View/GalleryPageView observe print+wizard; Stage3/4/5 observe wizard; RootView observes wizard).

Window lifecycle (macOS 12)

  • Window scene → WindowGroup; secondary-window creation suppressed via CommandGroup(replacing: .newItem) {}.
  • AppDelegate sets initial content size 1280×800, contentMinSize 1100×700, and centres the window; applicationShouldHandleReopen(_:hasVisibleWindows:) returns true so the Dock icon re-shows/recreates the window.
  • #147/#149 termination/killAll behaviour unchanged.

View demotions

  • Stage2View: .task(id:).onAppear + .onChange(of:) with an explicit @State private var printGenerationTask cancelled before each relaunch; two-parameter .onChange → single-parameter macOS 12 form.
  • CalibrationView: zero-parameter .onChange → single-parameter form; .formStyle(.grouped) removed.
  • GamutView: .onKeyPress/focusEffectDisabled removed; R-key camera reset now handled by an NSEvent.addLocalMonitorForEvents(.keyDown) in GamutSceneView.Coordinator, removed in dismantleNSView.
  • SettingsView: .formStyle(.grouped) removed.

Test alignment

  • CupsOptionsFilterTests: the @convention(c) mode-resolver closure now spells ColorSyncSuppressorTests. instead of Self. — dynamic-Self capture is illegal in a C function pointer. Previously unreachable: the file only parses/typechecks now that the app target compiles.

Verification

  • git grep for @Observable, import Observation, @Bindable, formStyle, focusEffectDisabled, onKeyPress over Sources/ → zero occurrences.
  • xcodegen generate --spec project.yml regenerated cleanly.
  • xcodebuild build-for-testing -scheme ICCery -destination 'platform=macOS' ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NOTEST BUILD SUCCEEDED (universal build).
  • git status --porcelain clean; docs/megaplans/ untouched.

Assignee: gronod

Slice 4 (Phases 3 & 4) of the M9 macOS 12 retarget. ## State management (Observation → Combine) - All 8 view models (`TargetWorkflow`, `ProfileWorkflow`, `Calibration`, `MeasurementWorkflow`, `PrintSession`, `Wizard`, `Gamut`, `Settings`) migrated from `@Observable`/`import Observation` to `ObservableObject` + `import Combine`. - Every observed mutable property is now `@Published` (incl. `@Published private(set)`); `@MainActor` isolation and `didSet` observers retained. - Views: `@State` on view models → `@StateObject` (ICCeryApp, SettingsView, GamutView); all `@Bindable` occurrences → `@ObservedObject`; plain ViewModel parameters converted to `@ObservedObject` (RootView.WizardStageContent, Stage2View.GalleryPageView, SidebarView). - Child-VM observation: SwiftUI does not propagate nested `ObservableObject` changes through a parent, so views reading `workflow.wizard.*`, `workflow.print.*`, or `workflow.profile.*` now hold the child as an explicit `@ObservedObject` (SidebarView observes wizard+profile; Stage2View/GalleryPageView observe print+wizard; Stage3/4/5 observe wizard; RootView observes wizard). ## Window lifecycle (macOS 12) - `Window` scene → `WindowGroup`; secondary-window creation suppressed via `CommandGroup(replacing: .newItem) {}`. - `AppDelegate` sets initial content size 1280×800, `contentMinSize` 1100×700, and centres the window; `applicationShouldHandleReopen(_:hasVisibleWindows:)` returns `true` so the Dock icon re-shows/recreates the window. - `#147/#149` termination/`killAll` behaviour unchanged. ## View demotions - Stage2View: `.task(id:)` → `.onAppear` + `.onChange(of:)` with an explicit `@State private var printGenerationTask` cancelled before each relaunch; two-parameter `.onChange` → single-parameter macOS 12 form. - CalibrationView: zero-parameter `.onChange` → single-parameter form; `.formStyle(.grouped)` removed. - GamutView: `.onKeyPress`/`focusEffectDisabled` removed; R-key camera reset now handled by an `NSEvent.addLocalMonitorForEvents(.keyDown)` in `GamutSceneView.Coordinator`, removed in `dismantleNSView`. - SettingsView: `.formStyle(.grouped)` removed. ## Test alignment - `CupsOptionsFilterTests`: the `@convention(c)` mode-resolver closure now spells `ColorSyncSuppressorTests.` instead of `Self.` — dynamic-Self capture is illegal in a C function pointer. Previously unreachable: the file only parses/typechecks now that the app target compiles. ## Verification - `git grep` for `@Observable`, `import Observation`, `@Bindable`, `formStyle`, `focusEffectDisabled`, `onKeyPress` over `Sources/` → zero occurrences. - `xcodegen generate --spec project.yml` regenerated cleanly. - `xcodebuild build-for-testing -scheme ICCery -destination 'platform=macOS' ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO` → **TEST BUILD SUCCEEDED** (universal build). - `git status --porcelain` clean; `docs/megaplans/` untouched. Assignee: gronod
gronod added 1 commit 2026-09-11 21:51:43 +01:00
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
gronod self-assigned this 2026-09-11 21:51:51 +01:00
gronod merged commit 7a6b82816b into milestone/m9-monterey 2026-09-11 21:57:57 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gronod/iccery-v2-mac#108