diff --git a/Sources/ICCery/CalibrationView.swift b/Sources/ICCery/CalibrationView.swift index f0178f4..3a0bcc0 100644 --- a/Sources/ICCery/CalibrationView.swift +++ b/Sources/ICCery/CalibrationView.swift @@ -7,102 +7,135 @@ struct CalibrationView: View { @ObservedObject var wizard: WizardViewModel var body: some View { - VStack(alignment: .leading, spacing: 0) { - Text("Calibrate Printer") - .font(.title2.bold()) - .padding(.horizontal, 16) - .padding(.top, 16) - - Form { - Section("Wedge Settings") { - Picker("Colour Space", selection: $model.colourSpace) { - Text("RGB").tag(ColourSpace.rgb) - Text("CMYK").tag(ColourSpace.cmyk) - } - - HStack { - Text("Steps per channel") - Spacer() - TextField("", value: $model.steps, format: .number) - .frame(width: 60) - .accessibilityIdentifier("calSteps") - } - - HStack { - Text("White patches") - Spacer() - TextField("", value: $model.whitePatches, format: .number) - .frame(width: 60) - } - - if model.colourSpace == .cmyk { - HStack { - Text("Ink-limit exploration") - Spacer() - TextField("", text: $model.inkLimit) - .frame(width: 60) - .accessibilityIdentifier("calInkExplore") - } - } - - Toggle("Neutral emphasis", isOn: $model.includeNeutralEmphasis) - } - - Section("Workflow") { - HStack(spacing: 12) { - Button("Generate Target") { model.generateTarget() } - .accessibilityIdentifier("btnCalGenerate") - .disabled(wizard.basename.isEmpty - || wizard.effectiveWorkingDirectory == nil - || model.isGenerating) - - Button("Create Layout & Print") { model.createLayout() } - .accessibilityIdentifier("btnCalLayout") - .disabled(wizard.basename.isEmpty - || wizard.effectiveWorkingDirectory == nil - || model.isGenerating) - - Button("Measure") { model.measureChart() } - .accessibilityIdentifier("btnCalMeasure") - .disabled(model.calibrationTi3URL == nil) - - Button("Compute Curves") { model.computeCurves() } - .accessibilityIdentifier("btnCalCompute") - .disabled(!model.canCompute) - } - - if let url = model.computedCalURL { - Toggle("Apply calibration to next profile", isOn: $model.applyToProfile) - .onChange(of: model.applyToProfile) { _ in model.updateApplyToProfile() } - .accessibilityIdentifier("calApplyToggle") - - Text("Loaded: \(url.lastPathComponent)") - .font(.caption) - .foregroundStyle(.secondary) - } - } - - if !model.calibrationLog.isEmpty { - Section("Log") { - ScrollView { - VStack(alignment: .leading, spacing: 2) { - ForEach(model.calibrationLog, id: \.self) { line in - Text(line) - .font(.system(.caption, design: .monospaced)) - } - } - } - .frame(minHeight: 80, maxHeight: 120) - } + VStack(spacing: 0) { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + Text("Calibrate Printer") + .font(.title2.bold()) + .foregroundStyle(Theme.text) + wedgeSection + workflowSection + if !model.calibrationLog.isEmpty { logSection } } + .padding(20) + .frame(maxWidth: .infinity, alignment: .leading) } + .background(Theme.background) + + Divider().overlay(Theme.border) HStack { Spacer() - Button("Return to Profiling") { model.returnToProfiling() } + Button("Return to Profiling", role: .cancel) { model.returnToProfiling() } + .keyboardShortcut(.cancelAction) .accessibilityIdentifier("btnCalReturn") } .padding(16) } + .accessibilityElement(children: .contain) + .accessibilityIdentifier("stage-cal") + } + + // MARK: - Wedge settings + + private var wedgeSection: some View { + VStack(alignment: .leading, spacing: 8) { + Text("Wedge Settings").font(.headline).foregroundStyle(Theme.text) + Picker("Colour Space", selection: $model.colourSpace) { + Text("RGB").tag(ColourSpace.rgb) + Text("CMYK").tag(ColourSpace.cmyk) + } + .pickerStyle(.segmented) + .frame(maxWidth: 220) + + HStack(spacing: 12) { + Text("Steps per channel") + .foregroundStyle(Theme.text) + .frame(width: 140, alignment: .leading) + TextField("", value: $model.steps, format: .number) + .textFieldStyle(.roundedBorder) + .frame(width: 70) + .accessibilityIdentifier("calSteps") + } + + HStack(spacing: 12) { + Text("White patches") + .foregroundStyle(Theme.text) + .frame(width: 140, alignment: .leading) + TextField("", value: $model.whitePatches, format: .number) + .textFieldStyle(.roundedBorder) + .frame(width: 70) + } + + if model.colourSpace == .cmyk { + HStack(spacing: 12) { + Text("Ink-limit exploration") + .foregroundStyle(Theme.text) + .frame(width: 140, alignment: .leading) + TextField("", text: $model.inkLimit) + .textFieldStyle(.roundedBorder) + .frame(width: 70) + .accessibilityIdentifier("calInkExplore") + } + } + + Toggle("Neutral emphasis", isOn: $model.includeNeutralEmphasis) + .toggleStyle(.checkbox) + .foregroundStyle(Theme.text) + .accessibilityIdentifier("calNeutralEmphasis") + } + } + + // MARK: - Workflow + + private var workflowSection: some View { + VStack(alignment: .leading, spacing: 8) { + Text("Workflow").font(.headline).foregroundStyle(Theme.text) + HStack(spacing: 12) { + Button("Generate Target") { model.generateTarget() } + .accessibilityIdentifier("btnCalGenerate") + .disabled(wizard.basename.isEmpty + || wizard.effectiveWorkingDirectory == nil + || model.isGenerating) + + Button("Create Layout & Print") { model.createLayout() } + .accessibilityIdentifier("btnCalLayout") + .disabled(wizard.basename.isEmpty + || wizard.effectiveWorkingDirectory == nil + || model.isGenerating) + + Button("Measure") { model.measureChart() } + .accessibilityIdentifier("btnCalMeasure") + .disabled(model.calibrationTi3URL == nil) + + Button("Compute Curves") { model.computeCurves() } + .accessibilityIdentifier("btnCalCompute") + .disabled(!model.canCompute) + } + + if let url = model.computedCalURL { + Toggle("Apply calibration to next profile", isOn: $model.applyToProfile) + .toggleStyle(.checkbox) + .foregroundStyle(Theme.text) + .onChange(of: model.applyToProfile) { _ in model.updateApplyToProfile() } + .accessibilityIdentifier("calApplyToggle") + + Text("Loaded: \(url.lastPathComponent)") + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + + // MARK: - Log + + private var logSection: some View { + ProcessLogView( + lines: model.calibrationLog, + minHeight: 80, + maxHeight: 120, + containerId: "calLogContainer", + logId: "calLog" + ) } } diff --git a/Sources/ICCery/RootView.swift b/Sources/ICCery/RootView.swift index 6496880..d30917b 100644 --- a/Sources/ICCery/RootView.swift +++ b/Sources/ICCery/RootView.swift @@ -39,6 +39,7 @@ struct RootView: View { } WizardStageContent(model: model, workflow: workflow) } + .frame(maxWidth: .infinity, maxHeight: .infinity) } .frame(minWidth: 1100, minHeight: 700) .background(Theme.background) diff --git a/Tests/ICCeryUITests/Milestone6CalibrationUITests.swift b/Tests/ICCeryUITests/Milestone6CalibrationUITests.swift index 6d20785..fb4e52a 100644 --- a/Tests/ICCeryUITests/Milestone6CalibrationUITests.swift +++ b/Tests/ICCeryUITests/Milestone6CalibrationUITests.swift @@ -86,6 +86,60 @@ final class Milestone6CalibrationUITests: XCTestCase { } } + /// Stage 0 must not push the sidebar off-screen: the macOS `Form` + /// rows with expanding spacers once gave the stage an unbounded ideal + /// width, and window centering shifted the 270 pt sidebar into + /// negative X (issue #163). AX-tree existence checks cannot see that, + /// so assert real frame geometry. + func testCalibrationViewDoesNotOverflowWindow() throws { + let calButton = app.buttons["btnCalibratePrinter"] + XCTAssertTrue(calButton.waitForExistence(timeout: 10)) + calButton.tap() + + XCTAssertTrue(app.staticTexts["Calibrate Printer"].waitForExistence(timeout: 5)) + + let window = app.windows.firstMatch + XCTAssertTrue(window.exists) + XCTAssertGreaterThanOrEqual(calButton.frame.minX, 0) + XCTAssertLessThanOrEqual(calButton.frame.maxX, window.frame.maxX) + let ret = app.buttons["btnCalReturn"] + XCTAssertTrue(ret.waitForExistence(timeout: 5)) + XCTAssertTrue(ret.isHittable) + } + + /// "Return to Profiling" is the single Stage 0 exit and carries the + /// cancel-action shortcut, so Escape must dismiss the dashboard too + /// (issue #163). `typeKey` delivery is unreliable on the macOS 12 CI + /// runner (m10 phase-08), so the Escape check falls back to the + /// deterministic button tap. + func testCalibrationReturnButtonAndEscapeDismiss() throws { + let calButton = app.buttons["btnCalibratePrinter"] + XCTAssertTrue(calButton.waitForExistence(timeout: 10)) + calButton.tap() + XCTAssertTrue(app.staticTexts["Calibrate Printer"].waitForExistence(timeout: 5)) + + let returnButton = app.buttons["btnCalReturn"] + XCTAssertTrue(returnButton.waitForExistence(timeout: 5)) + XCTAssertTrue(returnButton.isHittable) + returnButton.tap() + + let stage1 = app.descendants(matching: .any)["stage-1"] + XCTAssertTrue(stage1.waitForExistence(timeout: 5)) + + // Re-enter and try Escape; fall back to the button where the + // runtime does not deliver typeKey. + XCTAssertTrue(calButton.waitForExistence(timeout: 5)) + calButton.tap() + XCTAssertTrue(app.staticTexts["Calibrate Printer"].waitForExistence(timeout: 5)) + + app.typeKey(XCUIKeyboardKey.escape, modifierFlags: []) + if !stage1.waitForExistence(timeout: 4) { + XCTAssertTrue(returnButton.waitForExistence(timeout: 5)) + returnButton.tap() + XCTAssertTrue(stage1.waitForExistence(timeout: 5)) + } + } + /// A failing calibration targen surfaces the error through the /// wizard notice and restores the original basename (issue #80). func testCalibrationTargenFailureRestoresBasename() throws { diff --git a/docs/21-ui-reference.md b/docs/21-ui-reference.md index e49abb0..0ea7777 100644 --- a/docs/21-ui-reference.md +++ b/docs/21-ui-reference.md @@ -34,7 +34,7 @@ Process logs: `
` expandable (#54), wrapping text ## Stage 0 ids -`stage-cal`, `calApplyToggleDash`, `calRgbHint`, `calSteps`, `calInkExplore`, `calNeutralEmphasis`, `btnCalGenerate`, `btnCalLayout`, `btnCalMeasure`, `calCurrentFile`, `btnCalLoad`, `btnCalLibrary`, `btnCalClear`, `calSavedSelect`, `btnCalCompute`, `calCurveSvg`, `calCurveLegend`, `calTacValue`, `calTacOverride`, `calInkLimitControls`, `calRecommendedPower`, `btnCalBackToWizard`, `calLogContainer`, `calLog`. +`stage-cal`, `calApplyToggleDash`, `calRgbHint`, `calSteps`, `calInkExplore`, `calNeutralEmphasis`, `btnCalGenerate`, `btnCalLayout`, `btnCalMeasure`, `calCurrentFile`, `btnCalLoad`, `btnCalLibrary`, `btnCalClear`, `calSavedSelect`, `btnCalCompute`, `calCurveSvg`, `calCurveLegend`, `calTacValue`, `calTacOverride`, `calInkLimitControls`, `calRecommendedPower`, `btnCalReturn`, `calLogContainer`, `calLog`. Collision: `calCollisionDialog`, `calCollisionMessage`, `calOverwriteBtn`, `calRenameBtn`, `calCancelCollisionBtn`. @@ -86,4 +86,4 @@ Tauri v2 has **no** `window.__TAURI__.dialog`. Use invoke wrappers (`select_*`). ## Complete `id=` roster (355) -`openSettingsBtn`, `openAboutBtn`, `btnSavePresetModal`, `btnOpenPresetsDialog`, `presetSelect`, `btnCalibratePrinter`, `calStatusChip`, `wizardNotification`, `wizardNotificationIcon`, `wizardNotificationText`, `wizardNotificationClose`, `stage-cal`, `calApplyToggleDash`, `calRgbHint`, `calSteps`, `calInkExplore`, `calNeutralEmphasis`, `btnCalGenerate`, `btnCalLayout`, `btnCalMeasure`, `calCurrentFile`, `btnCalLoad`, `btnCalLibrary`, `btnCalClear`, `calSavedSelect`, `btnCalCompute`, `calCurveSvg`, `calCurveLegend`, `calTacValue`, `calTacOverride`, `calInkLimitControls`, `calRecommendedPower`, `btnCalBackToWizard`, `calLogContainer`, `calLog`, `stage-1`, `btnToggleAllHelp`, `calStage1Recommend`, `btnCalRecalibrate`, `stage1FormContainer`, `patchCountPreset`, `patchCountCustom`, `whitePatches`, `blackPatches`, `btn-import-dataset`, `btnOpenExisting`, `targetBasename`, `btnBrowse`, `selectedPathDisplay`, `targenAdvancedDetails`, `targenPrecondProfile`, `btnBrowsePrecondProfile`, `targenNeutralSteps`, `targenNeutralConcentration`, `targenNeutralConcVal`, `targenGreySteps`, `targenSingleChannelSteps`, `targenAdaptation`, `targenAdaptationVal`, `targenDarkEmphasis`, `targenDarkEmphasisVal`, `targenDevicePower`, `targenInkLimitGroup`, `targenInkLimit`, `targenAlgorithm`, `targenHighQuality`, `btnGenerate`, `targenLogContainer`, `targenLog`, `stage-2`, `cmWarningBanner`, `instrumentSelect`, `pageSizeSelect`, `customPageSizeRow`, `customPageW`, `customPageH`, `tiffDpi`, `printtargLayoutOrder`, `printtargCustomSeedGroup`, `printtargCustomSeed`, `btnToggleLabelEdit`, `targetMetadataPrinter`, `targetMetadataInkSet`, `targetMetadataDriverPaper`, `targetMetadataActualPaper`, `targetLabelPreview`, `btnCreateLayout`, `printtargLogContainer`, `printtargLog`, `tiffGallery`, `galleryInfo`, `galleryGrid`, `rawPrintPanel`, `printNotification`, `printNotificationIcon`, `printNotificationText`, `printerSelect`, `btnRefreshPrinters`, `btnPrinterProperties`, `printerStatusBadge`, `cupsOptionsGroup`, `chkPpdFallback`, `printerTraySelect`, `mediaTypeGroup`, `printerMediaTypeSelect`, `btnOrientPortrait`, `btnOrientLandscape`, `btnPrintAll`, `btnAdvanceToStage3`, `stage-3`, `stage3LoadedTargetBanner`, `stage3TargetBasename`, `stage3TargetMeta`, `stage3TargetBadge`, `chartreadInstrumentSelect`, `btnDetectInstruments`, `xyTableHint`, `xyTablePanel`, `xyTableActiveStepBadge`, `xyStepPlace`, `xyStepAlign`, `xyStepScan`, `xyStepRemove`, `chartreadState`, `chartreadPrompt`, `btnStartRead`, `btnCalibrate`, `btnDoneRead`, `btnAccept`, `btnRetry`, `btnUndo`, `btnSkip`, `btnCancel`, `readProgressContainer`, `readProgress`, `readProgressText`, `readStats`, `swatchGrid`, `chartreadAveragingPanel`, `passCounterBadge`, `passesList`, `btnMeasureAnotherSheet`, `btnFinishAndAverage`, `chartreadLogContainer`, `chartreadLog`, `stage-4`, `colprofQuality`, `colprofDescription`, `colprofCopyright`, `colprofAlgorithm`, `colprofFwa`, `colprofCustomSpRow`, `colprofCustomSpPath`, `btnBrowseCustomSp`, `colprofIlluminant`, `colprofObserver`, `colprofInputViewCond`, `colprofOutputViewCond`, `btnCreateProfile`, `colprofSpinnerContainer`, `colprofStageLabel`, `colprofSuccessCard`, `colprofSuccessInfo`, `btnGoToVerify`, `colprofLogContainer`, `colprofLog`, `stage-5`, `btnVerify`, `btnInstallProfile`, `profcheckReportCard`, `profcheckBadge`, `profcheckAvgDe`, `profcheckMaxDe`, `profcheckRmsDe`, `driftHistorySection`, `driftAlertCard`, `driftAlertIcon`, `driftAlertText`, `btnDriftRecalibrate`, `driftFilterRow`, `driftPrinterFilter`, `driftChartWrap`, `driftTrendChart`, `driftEmptyState`, `verificationHistoryTable`, `verificationHistoryTbody`, `btnExportHistoryCsv`, `btnClearHistory`, `gamutViewerWrap`, `gamutViewerContainer`, `gamutControlsPanel`, `chkProfileGamut`, `rngProfileOpacity`, `chkSrgbReference`, `rngSrgbOpacity`, `chkLabAxes`, `rngAxisOpacity`, `btnGamutResetCamera`, `profcheckLogContainer`, `profcheckLog`, `settingsDialog`, `argyll_binary_dir`, `default_instrument`, `enable_i1pro2_leds`, `deltaEGoodMax`, `deltaEWarningMax`, `deltaEThresholdError`, `calibrationStaleDays`, `defaultInstallLocation`, `askBeforeOverwriteProfile`, `openColorPanelAfterInstall`, `logLevelSelect`, `btnOpenLogFolder`, `btnCopyLogPath`, `btnCopyLogExcerpt`, `logPathDisplay`, `saveSettingsBtn`, `closeSettingsBtn`, `calCollisionDialog`, `calCollisionMessage`, `calOverwriteBtn`, `calRenameBtn`, `calCancelCollisionBtn`, `profileInstallCollisionDialog`, `profileInstallCollisionMessage`, `profileOverwriteBtn`, `profileRenameBtn`, `profileCancelCollisionBtn`, `aboutDialog`, `aboutVersion`, `aboutBuildDate`, `closeAboutBtn`, `savePresetDialog`, `savePresetName`, `savePresetDesc`, `btnConfirmSavePreset`, `btnCloseSavePresetDialog`, `managePresetsDialog`, `managePresetsList`, `btnExportActivePreset`, `btnImportPreset`, `btnCloseManagePresetsDialog`, `mediaSelect`, `mediaRecipeStale`, `btnMediaLibraryCapture`, `btnMediaLibraryManage`, `saveMediaRecipeDialog`, `saveMediaName`, `saveMediaNotes`, `saveMediaPaper`, `saveMediaInk`, `saveMediaPrinter`, `saveMediaPreset`, `saveMediaColourSpace`, `saveMediaCal`, `saveMediaApplyCal`, `btnConfirmSaveMedia`, `btnCloseSaveMediaDialog`, `manageMediaDialog`, `mediaLibraryList`, `mediaLibraryEmpty`, `mediaRow-{id}`, `btnMediaLibraryApply-{id}`, `btnMediaLibraryDelete-{id}`, `btnMediaLibraryApply`, `btnMediaLibraryCaptureFromManage`, `btnCloseManageMediaDialog`, `btnSpotRead`, `spotReadView`, `btnCloseSpotRead`, `spotSidecarMissing`, `btnSpotDetectInstruments`, `spotDetectError`, `spotInstrumentSelect`, `spotDefaultMissing`, `spotSetDefault`, `spotXYHint`, `spotPrompt`, `spotLastError`, `spotLogContainer`, `spotLog`, `btnSpotStart`, `btnSpotCalibrate`, `btnSpotTrigger`, `btnSpotStop`, `spotLastSample`, `spotLastEmpty`, `spotLabL`, `spotLabA`, `spotLabB`, `spotXYZ`, `spotSwatch`, `spotDeltaE`, `spotLastInstrument`, `spotLabImplausible`, `spotHistoryTable`, `spotHistoryEmpty`, `spotHistoryRow-{uuid}`, `btnSpotCopyLab`, `btnSpotExportCsv`, `btnViewGamut`, `gamutView`, `gamutStatusText`, `gamutNoticeText`, `btnResetGamutCamera`, `gamutLayer-sRGB`, `gamutLayer-profile`, `gamutLayer-compare`, `btnGamutAddCompare`, `btnGamutOpenGam`, `btnGamutOpenProfile`, `btnGamutRemoveCompare`, `btnGamutSampleTiff`, `gamutInspectPanel`, `gamutInspectIdle`, `gamutInspectL`, `gamutInspectA`, `gamutInspectB`, `gamutInspect-sRGB`, `gamutInspect-profile`, `gamutInspect-compare`, `gamutInspectSwatch`, `gamutInspectApprox`, `gamutLabEntryL`, `gamutLabEntryA`, `gamutLabEntryB`, `btnGamutInspectLab`, `gamutTiffPreview`, `btnCloseGamutTiffPreview`, `gamutViewerUnavailable`, `menuProjectNew`, `menuProjectOpen`, `menuProjectRecents`, `projectRecent-{id}`, `menuProjectRecentsClear`, `menuProjectSave`, `menuProjectSaveAs`, `menuProjectReport`, `menuProjectClose`, `projectChip`, `projectChipName`, `projectChipPath`, `projectChipStale`, `btnProjectOpen`, `btnProjectSave`, `btnProjectReveal`, `projectNewAlert`, `btnProjectNewCancel`, `btnProjectNewConfirm`, `btnProjectDirtySave`, `btnProjectDirtyDiscard`, `btnProjectDirtyCancel`, `projectRelocateSheet`, `btnProjectRelocate`, `btnProjectRelocateCancel`. +`openSettingsBtn`, `openAboutBtn`, `btnSavePresetModal`, `btnOpenPresetsDialog`, `presetSelect`, `btnCalibratePrinter`, `calStatusChip`, `wizardNotification`, `wizardNotificationIcon`, `wizardNotificationText`, `wizardNotificationClose`, `stage-cal`, `calApplyToggleDash`, `calRgbHint`, `calSteps`, `calInkExplore`, `calNeutralEmphasis`, `btnCalGenerate`, `btnCalLayout`, `btnCalMeasure`, `calCurrentFile`, `btnCalLoad`, `btnCalLibrary`, `btnCalClear`, `calSavedSelect`, `btnCalCompute`, `calCurveSvg`, `calCurveLegend`, `calTacValue`, `calTacOverride`, `calInkLimitControls`, `calRecommendedPower`, `btnCalReturn`, `calLogContainer`, `calLog`, `stage-1`, `btnToggleAllHelp`, `calStage1Recommend`, `btnCalRecalibrate`, `stage1FormContainer`, `patchCountPreset`, `patchCountCustom`, `whitePatches`, `blackPatches`, `btn-import-dataset`, `btnOpenExisting`, `targetBasename`, `btnBrowse`, `selectedPathDisplay`, `targenAdvancedDetails`, `targenPrecondProfile`, `btnBrowsePrecondProfile`, `targenNeutralSteps`, `targenNeutralConcentration`, `targenNeutralConcVal`, `targenGreySteps`, `targenSingleChannelSteps`, `targenAdaptation`, `targenAdaptationVal`, `targenDarkEmphasis`, `targenDarkEmphasisVal`, `targenDevicePower`, `targenInkLimitGroup`, `targenInkLimit`, `targenAlgorithm`, `targenHighQuality`, `btnGenerate`, `targenLogContainer`, `targenLog`, `stage-2`, `cmWarningBanner`, `instrumentSelect`, `pageSizeSelect`, `customPageSizeRow`, `customPageW`, `customPageH`, `tiffDpi`, `printtargLayoutOrder`, `printtargCustomSeedGroup`, `printtargCustomSeed`, `btnToggleLabelEdit`, `targetMetadataPrinter`, `targetMetadataInkSet`, `targetMetadataDriverPaper`, `targetMetadataActualPaper`, `targetLabelPreview`, `btnCreateLayout`, `printtargLogContainer`, `printtargLog`, `tiffGallery`, `galleryInfo`, `galleryGrid`, `rawPrintPanel`, `printNotification`, `printNotificationIcon`, `printNotificationText`, `printerSelect`, `btnRefreshPrinters`, `btnPrinterProperties`, `printerStatusBadge`, `cupsOptionsGroup`, `chkPpdFallback`, `printerTraySelect`, `mediaTypeGroup`, `printerMediaTypeSelect`, `btnOrientPortrait`, `btnOrientLandscape`, `btnPrintAll`, `btnAdvanceToStage3`, `stage-3`, `stage3LoadedTargetBanner`, `stage3TargetBasename`, `stage3TargetMeta`, `stage3TargetBadge`, `chartreadInstrumentSelect`, `btnDetectInstruments`, `xyTableHint`, `xyTablePanel`, `xyTableActiveStepBadge`, `xyStepPlace`, `xyStepAlign`, `xyStepScan`, `xyStepRemove`, `chartreadState`, `chartreadPrompt`, `btnStartRead`, `btnCalibrate`, `btnDoneRead`, `btnAccept`, `btnRetry`, `btnUndo`, `btnSkip`, `btnCancel`, `readProgressContainer`, `readProgress`, `readProgressText`, `readStats`, `swatchGrid`, `chartreadAveragingPanel`, `passCounterBadge`, `passesList`, `btnMeasureAnotherSheet`, `btnFinishAndAverage`, `chartreadLogContainer`, `chartreadLog`, `stage-4`, `colprofQuality`, `colprofDescription`, `colprofCopyright`, `colprofAlgorithm`, `colprofFwa`, `colprofCustomSpRow`, `colprofCustomSpPath`, `btnBrowseCustomSp`, `colprofIlluminant`, `colprofObserver`, `colprofInputViewCond`, `colprofOutputViewCond`, `btnCreateProfile`, `colprofSpinnerContainer`, `colprofStageLabel`, `colprofSuccessCard`, `colprofSuccessInfo`, `btnGoToVerify`, `colprofLogContainer`, `colprofLog`, `stage-5`, `btnVerify`, `btnInstallProfile`, `profcheckReportCard`, `profcheckBadge`, `profcheckAvgDe`, `profcheckMaxDe`, `profcheckRmsDe`, `driftHistorySection`, `driftAlertCard`, `driftAlertIcon`, `driftAlertText`, `btnDriftRecalibrate`, `driftFilterRow`, `driftPrinterFilter`, `driftChartWrap`, `driftTrendChart`, `driftEmptyState`, `verificationHistoryTable`, `verificationHistoryTbody`, `btnExportHistoryCsv`, `btnClearHistory`, `gamutViewerWrap`, `gamutViewerContainer`, `gamutControlsPanel`, `chkProfileGamut`, `rngProfileOpacity`, `chkSrgbReference`, `rngSrgbOpacity`, `chkLabAxes`, `rngAxisOpacity`, `btnGamutResetCamera`, `profcheckLogContainer`, `profcheckLog`, `settingsDialog`, `argyll_binary_dir`, `default_instrument`, `enable_i1pro2_leds`, `deltaEGoodMax`, `deltaEWarningMax`, `deltaEThresholdError`, `calibrationStaleDays`, `defaultInstallLocation`, `askBeforeOverwriteProfile`, `openColorPanelAfterInstall`, `logLevelSelect`, `btnOpenLogFolder`, `btnCopyLogPath`, `btnCopyLogExcerpt`, `logPathDisplay`, `saveSettingsBtn`, `closeSettingsBtn`, `calCollisionDialog`, `calCollisionMessage`, `calOverwriteBtn`, `calRenameBtn`, `calCancelCollisionBtn`, `profileInstallCollisionDialog`, `profileInstallCollisionMessage`, `profileOverwriteBtn`, `profileRenameBtn`, `profileCancelCollisionBtn`, `aboutDialog`, `aboutVersion`, `aboutBuildDate`, `closeAboutBtn`, `savePresetDialog`, `savePresetName`, `savePresetDesc`, `btnConfirmSavePreset`, `btnCloseSavePresetDialog`, `managePresetsDialog`, `managePresetsList`, `btnExportActivePreset`, `btnImportPreset`, `btnCloseManagePresetsDialog`, `mediaSelect`, `mediaRecipeStale`, `btnMediaLibraryCapture`, `btnMediaLibraryManage`, `saveMediaRecipeDialog`, `saveMediaName`, `saveMediaNotes`, `saveMediaPaper`, `saveMediaInk`, `saveMediaPrinter`, `saveMediaPreset`, `saveMediaColourSpace`, `saveMediaCal`, `saveMediaApplyCal`, `btnConfirmSaveMedia`, `btnCloseSaveMediaDialog`, `manageMediaDialog`, `mediaLibraryList`, `mediaLibraryEmpty`, `mediaRow-{id}`, `btnMediaLibraryApply-{id}`, `btnMediaLibraryDelete-{id}`, `btnMediaLibraryApply`, `btnMediaLibraryCaptureFromManage`, `btnCloseManageMediaDialog`, `btnSpotRead`, `spotReadView`, `btnCloseSpotRead`, `spotSidecarMissing`, `btnSpotDetectInstruments`, `spotDetectError`, `spotInstrumentSelect`, `spotDefaultMissing`, `spotSetDefault`, `spotXYHint`, `spotPrompt`, `spotLastError`, `spotLogContainer`, `spotLog`, `btnSpotStart`, `btnSpotCalibrate`, `btnSpotTrigger`, `btnSpotStop`, `spotLastSample`, `spotLastEmpty`, `spotLabL`, `spotLabA`, `spotLabB`, `spotXYZ`, `spotSwatch`, `spotDeltaE`, `spotLastInstrument`, `spotLabImplausible`, `spotHistoryTable`, `spotHistoryEmpty`, `spotHistoryRow-{uuid}`, `btnSpotCopyLab`, `btnSpotExportCsv`, `btnViewGamut`, `gamutView`, `gamutStatusText`, `gamutNoticeText`, `btnResetGamutCamera`, `gamutLayer-sRGB`, `gamutLayer-profile`, `gamutLayer-compare`, `btnGamutAddCompare`, `btnGamutOpenGam`, `btnGamutOpenProfile`, `btnGamutRemoveCompare`, `btnGamutSampleTiff`, `gamutInspectPanel`, `gamutInspectIdle`, `gamutInspectL`, `gamutInspectA`, `gamutInspectB`, `gamutInspect-sRGB`, `gamutInspect-profile`, `gamutInspect-compare`, `gamutInspectSwatch`, `gamutInspectApprox`, `gamutLabEntryL`, `gamutLabEntryA`, `gamutLabEntryB`, `btnGamutInspectLab`, `gamutTiffPreview`, `btnCloseGamutTiffPreview`, `gamutViewerUnavailable`, `menuProjectNew`, `menuProjectOpen`, `menuProjectRecents`, `projectRecent-{id}`, `menuProjectRecentsClear`, `menuProjectSave`, `menuProjectSaveAs`, `menuProjectReport`, `menuProjectClose`, `projectChip`, `projectChipName`, `projectChipPath`, `projectChipStale`, `btnProjectOpen`, `btnProjectSave`, `btnProjectReveal`, `projectNewAlert`, `btnProjectNewCancel`, `btnProjectNewConfirm`, `btnProjectDirtySave`, `btnProjectDirtyDiscard`, `btnProjectDirtyCancel`, `projectRelocateSheet`, `btnProjectRelocate`, `btnProjectRelocateCancel`.