Skip to content

Data sources attribution + roaming alert-filter defaults#93

Merged
kellylford merged 3 commits into
mainfrom
feature/data-sources-attribution
Jul 8, 2026
Merged

Data sources attribution + roaming alert-filter defaults#93
kellylford merged 3 commits into
mainfrom
feature/data-sources-attribution

Conversation

@kellylford

Copy link
Copy Markdown
Owner

Summary

Two related Settings/data improvements, plus a small cleanup.

1. Data Sources & Attribution screen (Settings → About)

The app credited only Open-Meteo, but it pulls from six providers. Replaced the single Open-Meteo link with a dedicated Data Sources & Attribution screen grouping providers by what they power:

  • Weather Data: Open-Meteo (forecast/historical/marine/air-quality/nowcast, CC BY 4.0), Apple Weather (WeatherKit)
  • Weather Alerts: NWS/NOAA (US), Environment and Climate Change Canada, MeteoAlarm/EUMETNET (Europe)
  • Location & Maps: Apple Maps (MapKit + Core Location)

Each row is an accessible link (children: .ignore, explicit label + hint, .isLink).

2. Roaming alert-filter defaults

A "Save Current Filters as Default" button at the bottom of the alert digest page saves the current severity filter + hazard type. Re-applied each time the alert browser opens. Stored in AppSettings as raw-value strings, so it persists locally and roams across devices via the existing iCloud KVS sync (no iCloudSyncService changes). First setting that lives outside the Settings page.

3. Cleanup

Fixed a stale comment in RegionalWeatherService that referenced Nominatim's rate limit; the code actually uses Apple CLGeocoder.

Version

Bumped to 1.5.8 (3).

Test plan

  • xcodebuild Debug build for iPhone 17 simulator succeeds
  • VoiceOver pass on the new Data Sources screen and the Save-default button
  • Verify saved alert filters roam to a second device

🤖 Generated with Claude Code

kellylford and others added 2 commits July 8, 2026 17:06
Credit all six external providers, not just Open-Meteo: Open-Meteo,
Apple Weather (WeatherKit), NWS/NOAA, Environment and Climate Change
Canada, MeteoAlarm (EUMETNET), and Apple Maps. Replaces the single
Open-Meteo link in Settings > About with a dedicated screen grouping
providers by what they power, each an accessible link.

Also fix a stale comment in RegionalWeatherService that referenced
Nominatim's rate limit; the code uses Apple CLGeocoder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "Save Current Filters as Default" button at the bottom of the
alert digest page. It stores the current severity filter and hazard
type, which are re-applied each time the alert browser opens.

The defaults live in AppSettings (raw-value strings, keeping Models
decoupled from the service-layer SeverityFilter/HazardType enums), so
they persist locally and roam across devices via the existing iCloud
KVS sync — no iCloudSyncService changes needed. This is the first
setting that lives outside the Settings page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@kellylford kellylford left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Data sources attribution + roaming alert-filter defaults

Reviewed the full diff plus surrounding code (Settings.swift, SettingsManager.swift, iCloudSyncService.swift, AlertBrowserService.swift, AlertBrowserView.swift, BrowseCitiesView.swift, SettingsView.swift). This is a clean, well-scoped PR. I could not find any blocking or crash-level defect. Details below by severity.

Verified correct (the things most likely to break, that don't)

  • Codable roundtrip in AppSettings. CodingKeys, init(from:), and encode(to:) are all consistent for both new properties. encodeIfPresent/decodeIfPresent on defaultAlertHazardType preserves nil vs. non-nil correctly. Additive fields decoded with decodeIfPresent + no currentVersion bump = safe both directions for older/newer stored blobs. Not bumping currentVersion is the right call here: applyRemoteSettings() gates on remote.settingsVersion == currentVersion, so a bump would have made pre-existing (v3) blobs stop roaming.
  • Roaming/iCloud. Confirmed the claim. saveSettings()iCloudSyncService.pushSettings()NSUbiquitousKeyValueStore.set(encodedAppSettings). The two new fields ride inside the encoded blob, so they roam with no iCloudSyncService change. Correct.
  • SeverityFilter.extremeOnly = "Extreme" case-name/raw-value mismatch. Storing/reconstructing by rawValue works — RawRepresentable keys off the raw value, not the case name, so SeverityFilter(rawValue: "Extreme") reliably yields .extremeOnly. The PR uses rawValue throughout. No issue.
  • State seeding. The didApplyDefaults once-guard is correct: onAppear seeds synchronously before the async .task load renders the filters (no race), and the guard preserves in-session filter changes when navigating into a group detail and back. Good.
  • DataSourceRow accessibility. Textbook: .accessibilityElement(children: .ignore), explicit label + hint, .isLink, decorative arrow image hidden. Matches CLAUDE.md's non-negotiable a11y pattern.

Should-fix

  • AlertBrowserView.swift ~L277-283 — saved hazard type absent from current region shows a blank Type picker. If a user saves a default hazard type (say .fire) and later opens a region with no fire alerts, presentFamilies omits .fire, so the menu Picker bound to $hazardType has no tag matching the current selection. The picker renders with a blank/empty selected label while the list is silently filtered to empty. The "No alerts match this filter. Try All types." message mitigates it, but the control not reflecting the active filter is confusing and it degrades the very feature being added. Fix: include the selected hazardType in the menu even when its count is 0 (e.g. append it to presentFamilies if missing), or reset hazardType = nil in applySavedDefaultsIfNeeded() when the saved type isn't present in the freshly loaded alerts.

Nits

  • SettingsView.swift ~L543-548 — decorative checkmark.seal.fill not hidden. The NavigationLink's explicit .accessibilityLabel overrides the combined children so VoiceOver still reads correctly, but CLAUDE.md says all decorative images need .accessibilityHidden(true). Add it for consistency.
  • SettingsView.swift ~L1099 — .accessibilityAddTraits(.isStaticText) on the intro Text is redundant (Text already exposes static-text semantics). Harmless; can drop.
  • SettingsView.swift DataSourceRow URL(string:)! force-unwrap. Fine for hardcoded literals and matches the pre-existing pattern that was replaced, but a future typo in a urlString becomes a crash. Low priority; a guard let/EmptyView fallback would be safer.
  • AlertBrowserView.swift L248-251 — the justSavedDefaults reset is an unstructured Task {} that isn't cancelled on disappear. Setting @State after the view is gone is a no-op, so this is harmless, but a .task-scoped timer or Task cancellation would be tidier.

Summary

Solid PR. No blocking issues. The Codable and roaming mechanics — the highest-risk parts — are correct. One should-fix UX edge case around a saved hazard filter not present in the current region, plus a few minor a11y/style nits.

If a saved default hazard type isn't present in the current region's
alerts, keep it in the Type menu (shown as "Name (0)") so the picker
reflects the active filter instead of rendering blank.

Also mark the Data Sources icon accessibilityHidden and drop a
redundant .isStaticText trait, per review nits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kellylford kellylford merged commit e4940f2 into main Jul 8, 2026
2 checks passed
@kellylford kellylford deleted the feature/data-sources-attribution branch July 8, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant