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

@@ -10,6 +10,10 @@ import PhotosUI
struct NoteEditorView: View {
private enum AnimationConstants {
static let keyboardAppearDelay: TimeInterval = 0.5
}
@Environment(\.dismiss) private var dismiss
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@@ -57,18 +61,18 @@ struct NoteEditorView: View {
}
.padding()
}
.navigationTitle("Journal Note")
.navigationTitle(String(localized: "Journal Note"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
Button(String(localized: "Cancel")) {
dismiss()
}
.accessibilityIdentifier(AccessibilityID.NoteEditor.cancelButton)
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
Button(String(localized: "Save")) {
saveNote()
}
.disabled(isSaving || noteText.count > maxCharacters)
@@ -78,14 +82,14 @@ struct NoteEditorView: View {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
Button(String(localized: "Done")) {
isTextFieldFocused = false
}
.accessibilityIdentifier(AccessibilityID.NoteEditor.keyboardDoneButton)
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
DispatchQueue.main.asyncAfter(deadline: .now() + AnimationConstants.keyboardAppearDelay) {
isTextFieldFocused = true
}
}
@@ -205,12 +209,12 @@ struct EntryDetailView: View {
.padding()
}
.background(Color(.systemGroupedBackground))
.navigationTitle("Entry Details")
.navigationTitle(String(localized: "Entry Details"))
.navigationBarTitleDisplayMode(.inline)
.accessibilityIdentifier(AccessibilityID.EntryDetail.sheet)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
Button(String(localized: "Done")) {
dismiss()
}
.accessibilityIdentifier(AccessibilityID.EntryDetail.doneButton)
@@ -222,16 +226,16 @@ struct EntryDetailView: View {
.sheet(isPresented: $showReflectionFlow) {
GuidedReflectionView(entry: entry)
}
.alert("Delete Entry", isPresented: $showDeleteConfirmation) {
Button("Delete", role: .destructive) {
.alert(String(localized: "Delete Entry"), isPresented: $showDeleteConfirmation) {
Button(String(localized: "Delete"), role: .destructive) {
onDelete()
dismiss()
}
.accessibilityIdentifier(AccessibilityID.EntryDetail.deleteConfirmButton)
Button("Cancel", role: .cancel) { }
Button(String(localized: "Cancel"), role: .cancel) { }
.accessibilityIdentifier(AccessibilityID.EntryDetail.deleteCancelButton)
} message: {
Text("Are you sure you want to delete this mood entry? This cannot be undone.")
Text(String(localized: "Are you sure you want to delete this mood entry? This cannot be undone."))
}
.photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhotoItem, matching: .images)
.onChange(of: selectedPhotoItem) { _, newItem in