π΄ Offline Sync & Feature Flags β
Offline Sync (providers/sync-provider.tsx) β
SyncProvider monitors network state and app foreground transitions to automatically flush any notes created while offline.
Sync Triggers β
| Trigger | Delay |
|---|---|
| Network reconnects | 1.5s debounce |
| App comes to foreground | Immediate |
| New note added to offline queue | Immediate |
Retry Strategy β
Failed uploads use exponential backoff: 5 retries with delays of 5s, 25s, 125s, 625s, 3125s (max ~52 min total). After 5 failures, the note is marked failed and an alert is shown to the user.
Reading Sync State β
import { useSyncContext } from '@/providers/sync-provider';
const { syncState, failedCount, retryAll } = useSyncContext();
// syncState: 'idle' | 'syncing' | 'error'
// failedCount: number of permanently failed notes
// retryAll(): manually retry all failed notesOffline Queue (hooks/use-offline-queue.ts) β
Notes created without connectivity are saved to MMKV (synchronous key-value storage, faster than AsyncStorage).
The offlineNotes array is shaped as Draft[] so it can be rendered in the same list component as synced notes.
const { offlineNotes, addToQueue, removeFromQueue } = useOfflineQueue();
// Add a note to the queue (called automatically by useCreateNote on network error)
addToQueue(draft);
// Remove after successful sync
removeFromQueue(draft.id);Feature Flags (lib/feature-flags/) β
Feature flags are backed by Firebase Remote Config. All flags are boolean.
Flag Registry (lib/feature-flags/flags.ts) β
Every new flag must be added here before use. The key must exactly match the key in the Firebase Remote Config console.
export const FEATURE_FLAG_DEFAULTS = {
example_new_map_clustering: false,
// add new flags here
} as const;
export type FeatureFlagName = keyof typeof FEATURE_FLAG_DEFAULTS;Defaults must be "safe off" β the app must work correctly when the flag is false.
Initialization (lib/feature-flags/remote-config.ts) β
initRemoteConfig() is called once on app start. It fetches and activates remote values. If it fails (e.g. no network), it silently falls back to the in-code defaults β it never throws.
Cache interval:
- Production: 1 hour
- Dev / Staging: 0 (always fetch fresh)
Reading Flags β
import { useFeatureFlag } from '@/hooks/use-feature-flag';
// Single flag (preferred)
const isClusteringEnabled = useFeatureFlag('example_new_map_clustering');
// Full flag map
import { useFeatureFlags } from '@/hooks/use-feature-flags';
const flags = useFeatureFlags();TypeScript will error on unknown flag names.
Adding a New Flag β Checklist β
- Add the key + default to
FEATURE_FLAG_DEFAULTSinlib/feature-flags/flags.ts - Add the key in the Firebase Remote Config console with the desired production value
- Use
useFeatureFlag('your_flag_key')in the component - Guard the new behavior:
if (!isEnabled) return <OldBehavior />