feat: automatic backups + harden startup to fail-open - #8
Merged
Conversation
Two related reliability changes. Bootstrap hardening: bootstrap() ran a serial chain of awaited plugin init before runApp with no timeout or try/catch, so any hang or throw stranded the app on the splash screen — the class of bug behind the 2.1.0 getToken() hang. Now only the first-frame essentials run before runApp (SharedPreferences and a guarded, time-boxed Firebase core init); analytics, notifications, FCM and the home widget are deferred after runApp, each isolated by a _guard (try/catch + 10s timeout). The analytics observer is nullable and fail-safe so a missing Firebase app degrades analytics instead of crashing App.build. Automatic backup (opt-in): BackupService writes a complete snapshot on app open and close to two places — a private copy (support dir, feeds "restore latest") and a durable copy that survives uninstall: Android MediaStore Download/Gapjyk/ via a Kotlin MethodChannel in MainActivity.kt (no new dependency, no runtime permission on Android 10+), iOS Documents exposed in the Files app via Info.plist. Files are dated (gapjyk-backup-YYYY-MM-DD.xlsx) so same-day writes overwrite and each location prunes to the newest 14. It reuses the existing encodeBackup and no-ops on an empty store so a wiped session can't overwrite a good same-day backup. Introduced in a new onboarding step and a Settings toggle (+ restore-latest); manual and auto restore now share _applyBackup. Triggers are open/close only (no debounce): every data change already persists synchronously, so the next open covers a crashed session. Verified: flutter analyze/format clean, cspell clean, 47 tests pass including an isolate-safe backup_service_test, shared-isolate run green across seeds, and a minified production release APK builds. Docs: CLAUDE.md gains the startup/backup gotchas and a Decisions entry; full design in .claude/docs/backup.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Minor bump over 2.1.0+14. versionCode 15 for the next production upload. Ships automatic backups (on open/close, private + durable) and the fail-open bootstrap hardening. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related reliability changes.
1. Bootstrap hardening
bootstrap()ran a serial chain of awaited plugin init beforerunAppwith no timeout ortry/catch, so any hang or throw stranded the app on the splash screen — the class of bug behind the 2.1.0getToken()hang.Now only the first-frame essentials run before
runApp:SharedPreferencesand a guarded, time-boxedFirebase.initializeApp. Analytics network calls, notifications, FCM, and the home widget are deferred to afterrunApp, each isolated by a_guard(try/catch + 10s timeout). The analytics observer is nullable and fail-safe, so a missing Firebase app degrades analytics instead of crashingApp.build.2. Automatic backup (opt-in)
BackupServicewrites a complete snapshot on app open and close to two places:getApplicationSupportDirectory()/backups/) — feeds "restore latest automatic backup". Wiped on clear-data/uninstall.Download/Gapjyk/via a KotlinMethodChannelinMainActivity.kt(no new dependency, no runtime permission on Android 10+); iOS → Documents dir exposed in the Files app viaInfo.plistkeys.Details:
gapjyk-backup-YYYY-MM-DD.xlsx), so same-day writes overwrite; each location prunes to the newest 14.encodeBackup; no-ops on an empty store so a wiped session can't overwrite a good same-day backup._applyBackup.Design decisions (agreed up front)
Verification
flutter analyzeclean ·dart formatclean · cspell 0 issuesbackup_service_test.dart(round-trip, empty-store skip, disabled skip, prune-to-14)Not yet done (reviewer note)
Download/Gapjyk/…xlsxappears and survives clear-data. Worth a manual check before release.Full design notes in
.claude/docs/backup.md.🤖 Generated with Claude Code