TargenConfig(preset:): explicit ofps_high_quality: false is now preserved in Core mapping (previously collapsed to nil), remaining distinguishable from a missing key; -G is still only emitted for true. UI snapshots continue to normalise disabled controls to nil.
FWA conversion is centralised in PresetMapping.swift: ColprofFwaSelection moved there with init(presetValue:) (nil/"none" → .none, "" → .empty, case-insensitive D50/D65, anything else → .custom) and presetValue(customPath:) for the reverse direction. The duplicate switches in ProfileWorkflowViewModel (fwaValue, applyPreset) are gone.
ProfileWorkflowViewModel.applyPreset now resets all Stage 4 state deterministically: a nil/non-custom preset FWA clears any previously selected custom .sp path, and preset apply_calibration/calibration_file are applied to the calibration controls.
TargetWorkflowViewModel.applyPreset applies profile + calibration state before constructing the Stage 2 PrinttargConfig, so the layout receives the preset's effective calibration path rather than stale live state.
Stage 1/2 form-field assignment stays encapsulated in applyTargenForm/applyPrinttargForm; no raw preset field lists leak back to the view model.
TestAppEnvironment
Tests/ICCeryCoreTests/TestAppEnvironment.swift — shared factory that builds an AppEnvironment backed by a unique NSTemporaryDirectory() root (settings.json, wizard_state.json, verification_history.json) plus a fresh ProcessManager, ArgyllRunner, and CupsService. It never touches the real Application Support directory and never mutates the global process environment.
Tests added
PresetTests.swift (PresetMappingTests):
Full ProfilingPreset ↔ TargenConfig/PrinttargConfig/ColprofConfig round-trip with every field asserted (back == preset).
Round-trip for all full-spread algorithms: ofps, t, r, R, q, Q, i, I.
FWA conversion matrix in both directions (nil, none, ``, D50, D65, custom .sp path).
PresetViewModelMappingTests.swift (app-target, @MainActor, isolated via TestAppEnvironment):
Apply custom-FWA preset then nil-FWA preset → stale selection/path cleared.
Preset calibration_file/apply_calibration reaches Stage 2 instead of stale live state; disabled calibration clears Stage 2.
Stage 1/2 form fields apply end-to-end.
Test gate
xcodebuild test -scheme ICCery -only-testing:ICCeryCoreTests/{ProfilingPresetTests,PresetMappingTests,PresetStoreTests,PresetMigrationTests,PresetViewModelMappingTests} -only-testing:ICCeryUITests/Milestone2UITests/testPresetApplyAndBuiltInProtection — 24 swift-testing tests + 1 UI test passed, 0 failures (DerivedData-feat-82-preset-mapping-contracts, universal arm64+x86_64).
v1 snake_case schema and built-in preset IDs unchanged.
Refs #82
## Mapping contract fixes
- `TargenConfig(preset:)`: explicit `ofps_high_quality: false` is now preserved in Core mapping (previously collapsed to `nil`), remaining distinguishable from a missing key; `-G` is still only emitted for `true`. UI snapshots continue to normalise disabled controls to `nil`.
- FWA conversion is centralised in `PresetMapping.swift`: `ColprofFwaSelection` moved there with `init(presetValue:)` (`nil`/`"none"` → `.none`, `""` → `.empty`, case-insensitive `D50`/`D65`, anything else → `.custom`) and `presetValue(customPath:)` for the reverse direction. The duplicate switches in `ProfileWorkflowViewModel` (`fwaValue`, `applyPreset`) are gone.
- `ProfileWorkflowViewModel.applyPreset` now resets all Stage 4 state deterministically: a `nil`/non-custom preset FWA clears any previously selected custom `.sp` path, and preset `apply_calibration`/`calibration_file` are applied to the calibration controls.
- `TargetWorkflowViewModel.applyPreset` applies profile + calibration state **before** constructing the Stage 2 `PrinttargConfig`, so the layout receives the preset's effective calibration path rather than stale live state.
- Stage 1/2 form-field assignment stays encapsulated in `applyTargenForm`/`applyPrinttargForm`; no raw preset field lists leak back to the view model.
## TestAppEnvironment
`Tests/ICCeryCoreTests/TestAppEnvironment.swift` — shared factory that builds an `AppEnvironment` backed by a unique `NSTemporaryDirectory()` root (settings.json, wizard_state.json, verification_history.json) plus a fresh `ProcessManager`, `ArgyllRunner`, and `CupsService`. It never touches the real Application Support directory and never mutates the global process environment.
## Tests added
`PresetTests.swift` (`PresetMappingTests`):
- Full `ProfilingPreset` ↔ `TargenConfig`/`PrinttargConfig`/`ColprofConfig` round-trip with every field asserted (`back == preset`).
- Round-trip for all full-spread algorithms: `ofps`, `t`, `r`, `R`, `q`, `Q`, `i`, `I`.
- Optional advanced targen fields stay `nil` (not numeric sentinels); explicit `ofpsHighQuality=false` vs `nil` distinction.
- `no_randomize`/`random_seed` layout matrix (raster / deterministic / custom seed).
- Custom page fallback matrix: valid `WxH` ≥ 50 mm, `foo`, undersized `30x40`, truncated `210x` → A4 fallback.
- FWA conversion matrix in both directions (`nil`, `none`, ``, `D50`, `D65`, custom `.sp` path).
`PresetViewModelMappingTests.swift` (app-target, `@MainActor`, isolated via `TestAppEnvironment`):
- Apply custom-FWA preset then nil-FWA preset → stale selection/path cleared.
- Preset `calibration_file`/`apply_calibration` reaches Stage 2 instead of stale live state; disabled calibration clears Stage 2.
- Stage 1/2 form fields apply end-to-end.
## Test gate
`xcodebuild test -scheme ICCery -only-testing:ICCeryCoreTests/{ProfilingPresetTests,PresetMappingTests,PresetStoreTests,PresetMigrationTests,PresetViewModelMappingTests} -only-testing:ICCeryUITests/Milestone2UITests/testPresetApplyAndBuiltInProtection` — **24 swift-testing tests + 1 UI test passed, 0 failures** (DerivedData-feat-82-preset-mapping-contracts, universal arm64+x86_64).
v1 snake_case schema and built-in preset IDs unchanged.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Refs #82
Mapping contract fixes
TargenConfig(preset:): explicitofps_high_quality: falseis now preserved in Core mapping (previously collapsed tonil), remaining distinguishable from a missing key;-Gis still only emitted fortrue. UI snapshots continue to normalise disabled controls tonil.PresetMapping.swift:ColprofFwaSelectionmoved there withinit(presetValue:)(nil/"none"→.none,""→.empty, case-insensitiveD50/D65, anything else →.custom) andpresetValue(customPath:)for the reverse direction. The duplicate switches inProfileWorkflowViewModel(fwaValue,applyPreset) are gone.ProfileWorkflowViewModel.applyPresetnow resets all Stage 4 state deterministically: anil/non-custom preset FWA clears any previously selected custom.sppath, and presetapply_calibration/calibration_fileare applied to the calibration controls.TargetWorkflowViewModel.applyPresetapplies profile + calibration state before constructing the Stage 2PrinttargConfig, so the layout receives the preset's effective calibration path rather than stale live state.applyTargenForm/applyPrinttargForm; no raw preset field lists leak back to the view model.TestAppEnvironment
Tests/ICCeryCoreTests/TestAppEnvironment.swift— shared factory that builds anAppEnvironmentbacked by a uniqueNSTemporaryDirectory()root (settings.json, wizard_state.json, verification_history.json) plus a freshProcessManager,ArgyllRunner, andCupsService. It never touches the real Application Support directory and never mutates the global process environment.Tests added
PresetTests.swift(PresetMappingTests):ProfilingPreset↔TargenConfig/PrinttargConfig/ColprofConfiground-trip with every field asserted (back == preset).ofps,t,r,R,q,Q,i,I.nil(not numeric sentinels); explicitofpsHighQuality=falsevsnildistinction.no_randomize/random_seedlayout matrix (raster / deterministic / custom seed).WxH≥ 50 mm,foo, undersized30x40, truncated210x→ A4 fallback.nil,none, ``,D50,D65, custom.sppath).PresetViewModelMappingTests.swift(app-target,@MainActor, isolated viaTestAppEnvironment):calibration_file/apply_calibrationreaches Stage 2 instead of stale live state; disabled calibration clears Stage 2.Test gate
xcodebuild test -scheme ICCery -only-testing:ICCeryCoreTests/{ProfilingPresetTests,PresetMappingTests,PresetStoreTests,PresetMigrationTests,PresetViewModelMappingTests} -only-testing:ICCeryUITests/Milestone2UITests/testPresetApplyAndBuiltInProtection— 24 swift-testing tests + 1 UI test passed, 0 failures (DerivedData-feat-82-preset-mapping-contracts, universal arm64+x86_64).v1 snake_case schema and built-in preset IDs unchanged.