Skip to content

feat: WatermelonDB migration, minimize animation & performance fixes#4

Closed
Yannam-Builds wants to merge 51 commits into
mainfrom
codex/WatermelonDB
Closed

feat: WatermelonDB migration, minimize animation & performance fixes#4
Yannam-Builds wants to merge 51 commits into
mainfrom
codex/WatermelonDB

Conversation

@Yannam-Builds

@Yannam-Builds Yannam-Builds commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • WatermelonDB full migration — all screens and hooks go through the repository layer; no direct database imports in UI code
  • Active workout minimize animation — replaced the shrink-to-pill approach with a clean slide-down (minimize) / slide-up (maximize) using Reanimated withSpring
  • Fix black flash on maximize — the root export of ActiveWorkoutScreen returned a full-screen colored View while WatermelonDB was hydrating; changed to return null so the underlying home screen shows through instead
  • Fix home screen flashuseDeferredScreenReady gained a stayReady option (backed by an everReady ref) so the Training Intelligence card never resets once it has loaded, even when the active workout screen overlays and then releases focus

Key files changed

File What changed
src/screens/ActiveWorkoutScreen.js Slide animation, return null on init, root export fix
src/hooks/useDeferredScreenReady.js Added stayReady + everReady ref
src/screens/HomeScreen.js Uses stayReady: true for intelligence card
src/context/ActiveWorkoutBannerContext.js Cleaned back to simple state (removed leftover shared-value experiments)
src/components/FloatingWorkoutWidget.js Cleaned back (removed measurement refs)
src/navigation/AppNavigator.js transparentModal + animation: none + contentStyle: transparent
src/navigation/navigationRef.js Imperative navigation outside component tree
src/services/workoutNotificationBridge.js Workout reminder notification scheduling

Known remaining issue

A brief black flash still appears before the slide-up animation when tapping the pill to re-open the workout. The JS-layer fix (return null) is in place but the flash appears to originate at the native layer — Android creates the Fragment/surface before JS renders. Further investigation needed (may require keeping the screen mounted and using pointerEvents toggling instead of navigate away/back).

Test plan

  • Start a workout → screen slides up cleanly
  • Minimize → screen slides down, pill appears
  • Tap pill → screen slides back up (check for black flash)
  • Navigate between tabs while workout is minimized — home screen intelligence card should not re-flash
  • End workout → session saved correctly
  • Backup export / import still works

Yannam-Builds and others added 21 commits April 11, 2026 17:50
Full rewrite of the data layer from AsyncStorage + AppContext to
WatermelonDB (offline-first SQLite ORM). Includes all Claude session
changes that Codex missed during its port.

Core WatermelonDB migration:
- New db/ layer: schema (v2), models, repositories for all data types
- New hooks: useWatermelonAppData, useWatermelonHistory, useWatermelonPlans,
  useWatermelonStats, useWatermelonSettings, useWatermelonGymProfiles,
  useWatermelonBodyMeasurements, useWatermelonManualRecovery, useWatermelonHome
- WatermelonDB-backed workout repository, import/export repository,
  settings repository with typed key-value store
- Native Android WorkManager backup scheduler (IronlogBackupSchedulerModule,
  IronlogBackupWorker, IronlogBackupTaskService)
- Headless JS backup task registered in index.js

Feature improvements (Claude sessions, missed by Codex):
- Celebration animation: removed burst/fountain variants, kept only
  fireworks (85 particles) and wave (55 particles) to fix device lag
- Google Drive OAuth removed; replaced with native share sheet via
  react-native-share (no credentials needed)
- Daily scheduled backup via WorkManager PeriodicWorkRequest (24h repeat)
  with selectable hour:minute time picker in BackupCenterScreen
- Battery optimization exemption prompt when scheduling auto-backups
- Rolling local snapshot retention control (2-20 files, default 4)
- DataPortabilityScreen: simple plain-JSON export/import (no encryption),
  accessible from Settings > Backup & Export
- BackupCenterScreen renamed to "Advanced Backup Centre" in Settings
- Legacy backup format support removed; only ironlog_watermelon_export v1
- Active session prefix mismatch fixed (store uses full key as-is)
- Scheduled backup config keys: scheduledBackupEnabled/Hour/Minute
- AndroidManifest: added FOREGROUND_SERVICE and
  REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permissions; removed OAuth deep-link
- CLAUDE.md added for AI-assisted development

New screens: DataPortabilityScreen, SplashScreen, AIPlanScreen,
TrainingIntelligenceScreen, OnboardingScreen
New navigation: PagerView-based tab system replacing bottom tab navigator
New components: FluidTabBar, AppScreenHeader, GoldConfettiBurst, PRHud,
  GlassModeContext, ui/Skeleton, ui/InlineToast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.json and eas.json are Expo-managed files no longer used after the
migration to bare React Native + Gradle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Performance:
- StatsScreen: wrap streak/totalSets/avgDur, chartData/chartMax/pointSpacing,
  pbEntries, and STATS in useMemo — were recomputed on every render
- HomeScreen: wrap weeklyGoalDays, goalStreak, weekSessions/hitDays/avgDur,
  and activePlanDays in useMemo — were recomputed on every render
- GoldConfettiBurst: remove SharedValue from useEffect dep array — it's a
  stable Reanimated ref and caused spurious effect reruns
- HistoryScreen: use stable exercise keys (name+index) in renderHistoryCard

Bug fixes:
- AppNavigator: add AppErrorBoundary class component so crashes show a
  recoverable error screen instead of a blank white screen
- database.js: register SmokeTest model (table was in schema but unregistered)
- IronlogBackupSchedulerPackage: suppress @deprecated override warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- favoriteExercisesService: replaced AsyncStorage with WatermelonDB settingsRepository
- ExerciseLibraryService: removed all AsyncStorage calls; index now sourced from
  WatermelonDB wmGetExercisesSnapshot with in-memory _bundledIndexCache fallback;
  retryLibraryFetch invalidates cache instead of clearing AS keys
- backupService: replaced readJsonStorage/writeJsonStorage with settingsRepository;
  migrated getOrCreateDeviceId and clearQueuedBackup to settingsRepository;
  getManagedStorageMap now builds from exportDatabase() (WM tables) instead of
  AsyncStorage.getAllKeys/multiGet; removed legacy AsyncStorage.multiRemove/multiSet
  from restore path (WM importDatabase sync block already handles restore)

Dead code files (storage.js, nativeMigrationBridge.js, trainingRepository.js,
migrations.js) retain AsyncStorage for their one-time upgrade migration logic
but are no longer imported by any active component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deleted five files with no active importers:
- src/storage/storage.js           (original AsyncStorage layer, superseded by WM)
- src/services/migrations.js       (legacy AS schema migration helpers)
- src/services/nativeMigrationBridge.js (one-time AS→SQLite bridge, never imported)
- src/domain/storage/trainingRepository.js (AS training repo, superseded by WM)
- src/domain/storage/trainingDatabase.js  (companion to trainingRepository)

All five were unreferenced. Removing them before Play Store release eliminates
dead AsyncStorage dependencies and shrinks the bundle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents all bugs Codex introduced/missed, every file changed, and
the key invariants that must be preserved going forward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
replaceTrainingSnapshotCompat was calling importDatabase() which only
accepts the native WM export format. importAnyPayload() handles legacy
format detection too, so restoring older encrypted backups works correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Race condition: `initialized` (WM collection hooks) was becoming true
before the separate useEffect that reads `onboarding_complete` from
settingsRepository completed. The navigator mounted with
`onboardingComplete = false` and `initialRouteName='Onboarding'`
was already locked in.

Added `settingsLoaded` flag — set to true only after the settings
useEffect resolves — and gated it into the `initialized` check so
the navigator never renders until both WM data AND the onboarding
flag are ready.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the slider only animated via withSpring after onPageSelected
fired (page fully settled), so it never moved during a swipe — then
jumped to catch up. Felt completely disconnected from the gesture.

Fix:
- AppNavigator creates a Reanimated scrollX shared value
- PagerView.onPageScroll writes position+offset to scrollX on every
  frame of the swipe (0.0 → 1.0 → 2.0 etc.)
- scrollX is threaded through FluidTabBar → PremiumFrostedTabBar
- tabWidthSV shared value keeps tabWidth reactive inside worklets
- useAnimatedStyle reads scrollX.value * tabWidthSV.value directly,
  so the slider tracks the finger 1:1 during drag
- withSpring fallback kept for initial render / when scrollX absent
- Tap navigation still works: setPage() triggers onPageScroll which
  updates scrollX naturally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ActiveWorkoutScreen crash:
- Outer ActiveWorkoutScreen referenced `s.container` (makeStyles result) in
  the !day guard, but `s` is only defined inside the inner WorkoutContent
  component. In Hermes release builds this becomes ReferenceError 's' doesn't
  exist. Fixed using plain inline styles for the guard fallback view.

Onboarding popup every launch:
- onboarding_complete was previously stored in AsyncStorage; after the WM
  migration the key was never written to WM, so every launch read null -> false
  -> popup triggered.
- HomeScreen now only shows the popup for truly new users (no plans AND no
  history). Existing users with plans are never shown the popup again.
- useWatermelonAppData adds a one-time migration effect: when settingsLoaded
  becomes true and the user has plans but the flag is unset, it writes
  onboarding_complete=true to WM so all future cold starts resolve correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Full WatermelonDB migration: all screens use repository layer, no direct DB access from screens/hooks
- Active workout minimize animation: slide-down/slide-up via Reanimated withSpring (replaced shrink-to-pill approach)
- Fix black flash on maximize: return null (transparent) instead of colored View when WatermelonDB not yet initialized
- Fix home screen flash: useDeferredScreenReady stayReady option prevents intelligence card re-triggering on focus changes
- Notification bridge: workoutNotificationBridge.js for workout reminder scheduling
- Navigation ref: navigationRef.js for imperative navigation outside component tree
- Misc: Button component, CustomAlert, PremiumFrostedTabBar, colorUtils updates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@Yannam-Builds Yannam-Builds deleted the codex/WatermelonDB branch May 26, 2026 21:40
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