feat: add WCAG AA accessibility app-wide, fix CloudKit container config, remove debug logs

- Add VoiceOver labels, hints, and element grouping across all 60+ views
- Add Reduce Motion support (Theme.Animation.prefersReducedMotion) to all animations
- Replace fixed font sizes with semantic Dynamic Type styles
- Hide decorative elements from VoiceOver with .accessibilityHidden(true)
- Add .minimumHitTarget() modifier ensuring 44pt touch targets
- Add AccessibilityAnnouncer utility for VoiceOver announcements
- Improve color contrast values in Theme.swift for WCAG AA compliance
- Extract CloudKitContainerConfig for explicit container identity
- Remove PostHog debug console log from AnalyticsManager

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-11 09:27:23 -06:00
parent e9c15d70b1
commit d63d311cab
77 changed files with 982 additions and 263 deletions

View File

@@ -25,6 +25,7 @@ struct DateRangePicker: View {
private let calendar = Calendar.current
private let daysOfWeek = ["S", "M", "T", "W", "T", "F", "S"]
private let daysOfWeekFull = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
private var monthYearString: String {
let formatter = DateFormatter()
@@ -96,13 +97,13 @@ struct DateRangePicker: View {
if isDemoMode && !hasAppliedDemoSelection {
hasAppliedDemoSelection = true
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
withAnimation(.easeInOut(duration: 0.3)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
// Navigate to demo month
displayedMonth = DemoConfig.demoStartDate
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay + 0.5) {
withAnimation(.easeInOut(duration: 0.3)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
startDate = DemoConfig.demoStartDate
endDate = DemoConfig.demoEndDate
selectionState = .complete
@@ -119,7 +120,7 @@ struct DateRangePicker: View {
let newYear = calendar.component(.year, from: newValue)
if oldMonth != newMonth || oldYear != newYear {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
displayedMonth = calendar.startOfDay(for: newValue)
}
}
@@ -148,6 +149,7 @@ struct DateRangePicker: View {
Image(systemName: "arrow.right")
.font(.subheadline)
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
// End date
VStack(alignment: .trailing, spacing: 4) {
@@ -168,17 +170,18 @@ struct DateRangePicker: View {
private var monthNavigation: some View {
HStack {
Button {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
displayedMonth = calendar.date(byAdding: .month, value: -1, to: displayedMonth) ?? displayedMonth
}
} label: {
Image(systemName: "chevron.left")
.font(.body)
.foregroundStyle(Theme.warmOrange)
.frame(width: 36, height: 36)
.frame(minWidth: 44, minHeight: 44)
.background(Theme.warmOrange.opacity(0.15))
.clipShape(Circle())
}
.accessibilityLabel("Previous month")
.accessibilityIdentifier("wizard.dates.previousMonth")
Spacer()
@@ -191,28 +194,30 @@ struct DateRangePicker: View {
Spacer()
Button {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
displayedMonth = calendar.date(byAdding: .month, value: 1, to: displayedMonth) ?? displayedMonth
}
} label: {
Image(systemName: "chevron.right")
.font(.body)
.foregroundStyle(Theme.warmOrange)
.frame(width: 36, height: 36)
.frame(minWidth: 44, minHeight: 44)
.background(Theme.warmOrange.opacity(0.15))
.clipShape(Circle())
}
.accessibilityLabel("Next month")
.accessibilityIdentifier("wizard.dates.nextMonth")
}
}
private var daysOfWeekHeader: some View {
HStack(spacing: 0) {
ForEach(Array(daysOfWeek.enumerated()), id: \.offset) { _, day in
ForEach(Array(daysOfWeek.enumerated()), id: \.offset) { index, day in
Text(day)
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
.frame(maxWidth: .infinity)
.accessibilityLabel(daysOfWeekFull[index])
}
}
}
@@ -243,6 +248,7 @@ struct DateRangePicker: View {
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: "calendar.badge.clock")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
Text("\(tripDuration) day\(tripDuration == 1 ? "" : "s")")
.font(.subheadline)
.foregroundStyle(Theme.textSecondary(colorScheme))
@@ -348,7 +354,7 @@ struct DayCell: View {
}
Text(dayNumber)
.font(.system(size: 14, weight: (isStart || isEnd) ? .bold : .medium))
.font(.subheadline)
.foregroundStyle(
isPast ? Theme.textMuted(colorScheme).opacity(0.5) :
(isStart || isEnd) ? .white :