fix: style screen title casing and google_fonts crash reporting - #295
Conversation
Style URLs use a lowercase canonical form (buildStylePath), so the router hands StyleScreen a lowercased style name. The screen rendered that raw value in its header and breadcrumb, so tapping a "Golden Ale" chip landed on a page titled "golden ale". Resolve the display name from a matched drink's original style string instead, keeping the lowercase value only for URL/lookup purposes. https://claude.ai/code/session_0135nVBYGpwkaQvG13XyS66H
google_fonts downloads fonts over HTTP on first use. When the device is offline or the font CDN fails, the load throws an uncaught async error that PlatformDispatcher.onError recorded to Crashlytics with fatal: true. The app keeps running with a fallback font, so this is a transient, non-fatal condition — reporting it as fatal distorts the crash-free metric. Classify google_fonts font-fetch failures (by exception message and by google_fonts stack frames) and record them as non-fatal in both the Flutter and async error handlers. https://claude.ai/code/session_0135nVBYGpwkaQvG13XyS66H
There was a problem hiding this comment.
Pull request overview
Fixes a UI regression where StyleScreen displayed the lowercased style value coming from canonicalized style URLs, by resolving and rendering the original mixed-case style name from the matched drinks list. Additionally, this PR changes global Crashlytics error classification to treat certain google_fonts font-load failures as non-fatal, with accompanying unit tests.
Changes:
- Resolve a mixed-case
displayStylefrom matching drinks and use it for the StyleScreen header + breadcrumb title. - Add a widget test ensuring a lowercase URL param (e.g.
ipa) renders asIPA. - Downgrade detected
google_fontsruntime font-fetch failures to non-fatal Crashlytics reports, and add unit tests for the detection helper.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| lib/screens/style_screen.dart | Uses a resolved displayStyle (from matched drink data) for visible UI labels instead of the lowercased route param. |
| test/style_screen_test.dart | Adds coverage to ensure lowercased route params still display the original mixed-case style name. |
| lib/main.dart | Introduces isTransientFontLoadError and uses it to mark some font-load errors as non-fatal in Crashlytics. |
| test/main_test.dart | Adds unit tests for isTransientFontLoadError classification behavior. |
| /// Whether [error] originates from `google_fonts` runtime font fetching. | ||
| /// | ||
| /// google_fonts downloads fonts over HTTP on first use. When the device is | ||
| /// offline or the font CDN fails, the load throws an uncaught async error. | ||
| /// The app keeps running with a fallback font, so such failures are transient | ||
| /// and non-fatal — they must not be recorded to Crashlytics as fatal crashes, | ||
| /// which would otherwise distort the crash-free metric. | ||
| bool isTransientFontLoadError(Object error, StackTrace? stack) { | ||
| if (error.toString().contains('Failed to load font')) return true; | ||
| return stack != null && stack.toString().contains('google_fonts'); |
| // Pass all uncaught Flutter errors to Crashlytics. Transient google_fonts | ||
| // font-fetch failures are downgraded to non-fatal (see | ||
| // isTransientFontLoadError). | ||
| FlutterError.onError = (details) { | ||
| if (isTransientFontLoadError(details.exception, details.stack)) { | ||
| FirebaseCrashlytics.instance.recordFlutterError(details); | ||
| } else { | ||
| FirebaseCrashlytics.instance.recordFlutterFatalError(details); | ||
| } | ||
| }; |
LCOV of commit
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
toggleFavorite re-applies filters when the favourites filter is active, but toggleTasted did not. Marking a drink tasted while the "not tasted" visibility filter was on left the drink stuck in the visible list until the next filter, sort or reload. Re-run filter+sort in toggleTasted when the notTasted filter is active, mirroring toggleFavorite. https://claude.ai/code/session_0135nVBYGpwkaQvG13XyS66H
🚀 Cloudflare Pages PreviewYour preview deployment is ready! Preview URL: https://claude-bug-hunt-fy6so.staging-cambeerfestival.pages.dev This preview will be automatically updated when you push new commits to this PR. |
* chore: add dart format to pre-commit check and CI Adds a `format` mise task (`dart format .`) and includes it in the `check` pre-commit gate. CI gains a `dart format --set-exit-if-changed .` step before `flutter analyze` so unformatted code is a hard failure on PRs rather than a silent auto-fix. The local task reformats in place; CI uses `--set-exit-if-changed` to fail the build without modifying files. * style: apply dart format to entire codebase Bulk-format all Dart files with `dart format .`. No logic changes — line wrapping, trailing whitespace, and indentation only. * docs: document dart format --no-deps usage in AGENTS.md * chore: add dart:format, prettier:format and mise:format tasks Splits the single `format` task into three named sub-tasks: - `dart:format` — dart format . (use --no-deps for speed after Dart changes) - `prettier:format` — Prettier for JS/TS/MJS files - `mise:format` — mise fmt for mise.toml The top-level `format` task now depends on all three. CI check formatting step expanded to cover all three. Prettier added to root package.json; .prettierignore excludes build/, android/, ios/. * style: apply prettier to JS/TS/MJS files * fix: correct CI format check and dart:format task ordering - Add --output=none to dart format CI check so it doesn't modify files (--set-exit-if-changed alone still writes) - Remove mise fmt --check from CI: mise is not installed in the CI environment (setup-flutter-app uses subosito/flutter-action directly) - Make dart:format depend on generate so mock files are formatted after codegen, not before * style: format files introduced by PR #295 bug fixes --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Bug fixes found during a bug hunt against the current
main.1. Style screen shows a lowercase slug instead of the style name (
e9de883)buildStylePathlowercases the style name for canonical URLs, so the routerhands
StyleScreena lowercased value. The screen rendered that raw value inits header and breadcrumb — tapping the "Golden Ale" chip on a drink landed on
a page titled "golden ale". The display name is now resolved from a matched
drink's original style string; the lowercase value is kept only for URL/lookup.
2. Tasted toggle didn't refresh the list under the "not tasted" filter (
1677402)toggleFavoritere-applies filters when the favourites filter is active, buttoggleTasteddid not. Marking a drink tasted while the "Not tasted"visibility filter was on left it stuck in the visible list until the next
filter/sort/reload.
toggleTastednow re-runs filter+sort when thenotTastedfilter is active, mirroring
toggleFavorite.3. Transient google_fonts failures reported as fatal crashes (
a1b9bd0)google_fontsdownloads fonts over HTTP on first use. A network/CDN failurethrows an uncaught async error that
main.dartrecorded to Crashlytics withfatal: true, even though the app keeps running with a fallback font — so atransient, handled condition was inflating the crash-free metric. Added
isTransientFontLoadError(matches the exception message and google_fontsstack frames) and both error handlers now record these as non-fatal.
Test plan
./bin/mise run test— full suite passes./bin/mise run analyze— clean (one pre-existing unrelated info)StyleScreentest: a lowercase URL param still renders the proper-cased nameBeerProvidertest:toggleTasteddrops the drink from the list while the not-tasted filter is activeisTransientFontLoadErrortests: message detection, stack-frame detection, and that unrelated errors stay fatalhttps://claude.ai/code/session_0135nVBYGpwkaQvG13XyS66H