- Add InstrumentDevice, InstrumentParser, and InstrumentSelection models. - Add ChartreadArgs, ChartreadConfig, ChartreadClassifier, and ChartreadRow. - Add LabColor, ColorDifference (CIEDE2000), and MeasurementArtefacts. - Extend ArgyllRunner with instlist, chartread streaming, and average. - Implement MeasurementWorkflowViewModel and Stage3View. - Add SwatchPatchView for live intended/measured ΔE₀₀ display. - Route RootView to Stage 3 and wire workflow resume from .ti2. - Fix ChartreadClassifier case sensitivity for prompts and remove-sheet notices. - Fix print notification accessibility in Stage2View and NoticeBanner. - Update M2/M3 UI test expectations and add M4 UI fixtures + Milestone4UITests. - Full universal test suite passes (one M4 interactive test skipped pending fixture timing). Refs #18 #19 #20 #21 #22 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
65 lines
1.8 KiB
Swift
65 lines
1.8 KiB
Swift
import SwiftUI
|
|
|
|
/// Banner notice model — the v2 equivalent of `#wizardNotification`
|
|
/// (docs/21 §Banner).
|
|
struct Notice: Identifiable, Equatable {
|
|
enum Kind: Equatable {
|
|
case info, warning, error
|
|
|
|
var symbolName: String {
|
|
switch self {
|
|
case .info: return "info.circle"
|
|
case .warning: return "exclamationmark.triangle"
|
|
case .error: return "xmark.octagon"
|
|
}
|
|
}
|
|
|
|
var tint: Color {
|
|
switch self {
|
|
case .info: return Theme.accent
|
|
case .warning: return .orange
|
|
case .error: return .red
|
|
}
|
|
}
|
|
}
|
|
|
|
let id = UUID()
|
|
let kind: Kind
|
|
let text: String
|
|
/// Auto-dismiss interval; `nil` keeps the banner until closed.
|
|
var autoHideAfter: TimeInterval? = 6
|
|
}
|
|
|
|
struct NoticeBanner: View {
|
|
let notice: Notice
|
|
let onClose: () -> Void
|
|
|
|
var body: some View {
|
|
HStack(spacing: 10) {
|
|
Image(systemName: notice.kind.symbolName)
|
|
.foregroundStyle(notice.kind.tint)
|
|
Text(notice.text)
|
|
.font(.callout)
|
|
.foregroundStyle(Theme.text)
|
|
.lineLimit(3)
|
|
.accessibilityIdentifier("noticeText")
|
|
.accessibilityValue(notice.text)
|
|
Spacer()
|
|
Button(action: onClose) {
|
|
Image(systemName: "xmark")
|
|
}
|
|
.buttonStyle(.plain)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 8)
|
|
.background(Theme.panel)
|
|
.overlay(
|
|
Rectangle()
|
|
.frame(height: 1)
|
|
.foregroundStyle(Theme.border),
|
|
alignment: .bottom
|
|
)
|
|
}
|
|
}
|