v1.1 polish: accessibility, error logging, localization, and code quality sweep

- Wrap 30+ production print() statements in #if DEBUG guards across 18 files
- Add VoiceOver labels, hints, and traits to Watch app, Live Activities, widgets
- Add .accessibilityAddTraits(.isButton) to 15+ onTapGesture views
- Add text alternatives for color-only indicators (progress dots, mood circles)
- Localize raw string literals in NoteEditorView, EntryDetailView, widgets
- Replace 25+ silent try? with do/catch + AppLogger error logging
- Replace hardcoded font sizes with semantic Dynamic Type fonts
- Fix FIXME in IconPickerView (log icon change errors)
- Extract magic animation delays to named constants across 8 files
- Add widget empty state "Log your first mood!" messaging
- Hide decorative images from VoiceOver, add labels to ColorPickers
- Remove stale TODO in Color+Codable (alpha change deferred for migration)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-03-26 20:09:14 -05:00
parent 4d9e906c4d
commit 1f040ab676
41 changed files with 427 additions and 107 deletions

View File

@@ -492,6 +492,11 @@ struct VotingLayoutPickerCompact: View {
// MARK: - Celebration Animation Picker
struct CelebrationAnimationPickerCompact: View {
private enum AnimationConstants {
static let previewTriggerDelay: TimeInterval = 0.5
static let dismissTransitionDelay: TimeInterval = 0.35
}
@AppStorage(UserDefaultsStore.Keys.celebrationAnimation.rawValue, store: GroupUserDefaults.groupDefaults) private var celebrationAnimationIndex: Int = 0
@AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@@ -586,7 +591,7 @@ struct CelebrationAnimationPickerCompact: View {
// Auto-trigger the celebration after a brief pause
Task { @MainActor in
try? await Task.sleep(for: .seconds(0.5))
try? await Task.sleep(for: .seconds(AnimationConstants.previewTriggerDelay))
guard previewAnimation == animation else { return }
if hapticFeedbackEnabled {
HapticFeedbackManager.shared.play(for: animation)
@@ -603,7 +608,7 @@ struct CelebrationAnimationPickerCompact: View {
previewOpacity = 0
}
Task { @MainActor in
try? await Task.sleep(for: .seconds(0.35))
try? await Task.sleep(for: .seconds(AnimationConstants.dismissTransitionDelay))
withAnimation(.easeOut(duration: 0.15)) {
previewAnimation = nil
}
@@ -879,7 +884,9 @@ struct SubscriptionBannerView: View {
do {
try await AppStore.showManageSubscriptions(in: windowScene)
} catch {
#if DEBUG
print("Failed to open subscription management: \(error)")
#endif
}
}
}

View File

@@ -69,8 +69,10 @@ struct IconPickerView: View {
ForEach(iconSets, id: \.self.0){ iconSet in
Button(action: {
UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in
// FIXME: Handle error
UIApplication.shared.setAlternateIconName(iconSet.1) { error in
if let error {
AppLogger.settings.error("Failed to set app icon '\(iconSet.1)': \(error.localizedDescription)")
}
}
AnalyticsManager.shared.track(.appIconChanged(iconTitle: iconSet.1))
}, label: {

View File

@@ -48,6 +48,8 @@ struct ImagePackPickerView: View {
imagePack = images
AnalyticsManager.shared.track(.iconPackChanged(packId: images.rawValue))
}
.accessibilityAddTraits(.isButton)
.accessibilityLabel(String(localized: "Select \(String(describing: images)) icon pack"))
if images.rawValue != (MoodImages.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 {
Divider()
}

View File

@@ -47,6 +47,8 @@ struct PersonalityPackPickerView: View {
LocalNotification.rescheduleNotifiations()
// }
}
.accessibilityAddTraits(.isButton)
.accessibilityLabel(String(localized: "Select \(aPack.title()) personality pack"))
// .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0)
.alert(isPresented: $showOver18Alert) {
let primaryButton = Alert.Button.default(Text(String(localized: "customize_view_over18alert_ok"))) {

View File

@@ -35,6 +35,8 @@ struct ShapePickerView: View {
.onTapGesture {
shapeRefreshToggleThing.toggle()
}
.accessibilityAddTraits(.isButton)
.accessibilityLabel(String(localized: "Refresh shapes"))
}
}
@@ -51,6 +53,8 @@ struct ShapePickerView: View {
shape = ashape
AnalyticsManager.shared.track(.moodShapeChanged(shapeId: shape.rawValue))
}
.accessibilityAddTraits(.isButton)
.accessibilityLabel(String(localized: "Select \(String(describing: ashape)) shape"))
.contentShape(Rectangle())
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)