You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Rider-facing half: one setting, one choice, one status line.
The setting. An App Setting, "Back up over Wi-Fi only", defaulting to off. When it is on, nothing syncs until the phone is on an unmetered connection — mid-ride included. There are no row classes, no backlog thresholds and no partial exceptions. A switch that partly ignores the Rider is worse than one that does not exist. The setting lives in a synced table, so it restores with everything else.
The choice. The Rider is offered it once, in the flow where backup is turned on, with the pending volume shown, so the only expensive moment in the feature's life — the first upload on a phone with months of history — is a decision they made rather than one that happened to them. Afterwards it is an ordinary settings row.
The status line. The account widget on the social sheet already renders sign-in state and is the most-seen surface in the app. It gains a line:
signed out -> the existing sign-in affordance, with a hint that names backup
up to date -> with a relative time
syncing -> with a pending count
waiting for Wi-Fi
offline
paused -> sign in again / update required / backup error
Native owns the state and actionable pause reason and emits both; JS renders them. Every paused reason also raises a notification: it does not resolve through ordinary retry, and a backup that has silently stopped for weeks is the failure this feature can least afford.
Failures become Diagnostic Events, so the existing event log is the debugging surface and no new error UI is invented. Permanent failures are coalesced by error class/table/cursor and contain metadata only, so one poison row cannot create one new pending Diagnostic Event per retry.
Per AGENTS.md, any reusable component or new visual state added here needs its preview in the component showcase under src/app/settings/components/.
Likely files
src/modules/profile/components/AccountWidget.tsx - the widget that gains the status line, in both its signed-in and signed-out branches
src/screens/main/overlays/TopBar.tsx - where the widget is mounted into the social sheet
modules/vescape-core/src/index.ts - the state native emits and the event JS subscribes to
src/modules/settings/store/ - the app setting and its default
src/app/settings/other.tsx - where a backup settings row plausibly belongs
modules/vescape-core/android/src/main/java/expo/modules/vescapecore/diagnostics/DiagnosticReporter.kt - how failures become Diagnostic Events
modules/vescape-core/android/src/main/java/expo/modules/vescapecore/notification/ - the existing notification surface for the paused state
src/app/settings/components/ - the component showcase entry
Implementation hints
AccountWidget has three branches — loading, signed out, signed in — and the signed-in branch already has an identity block with a secondary text line. The status belongs there rather than as a fourth branch.
The signed-out hint currently reads as generic online-features wording. Backup is now the concrete reason to sign in; say so.
Diagnostic Events are uploaded, so a failure payload must not carry the Device Token, a server URL with credentials in it, or row contents.
docs/agents/react.md covers the icon and UI conventions; docs/design.md covers the visual language. Follow the existing widget surface rather than introducing a new card style.
The pending volume shown at the one-time choice comes from the same per-table pending counts the uploader already computes — do not add a second counting path.
Acceptance criteria
"Back up over Wi-Fi only" exists as an app setting, defaults to off, and syncs like any other setting
With it on, nothing uploads on a metered connection, including during a ride
With it off, uploads happen on any connection
The Rider is offered the choice once when backup is turned on, with the pending volume shown
The account widget shows every backup state and distinguishes paused sign-in, update-required and backup-error reasons
The signed-out hint names backup as a reason to sign in
Every paused reason raises a matching actionable notification as well as showing in the widget
Permanent sync failures appear once per failure fingerprint in the existing Diagnostic Event log, not once per retry
No Diagnostic Event payload contains the Device Token, row contents, coordinates, opaque server body or SQL
Parent
What to build
The Rider-facing half: one setting, one choice, one status line.
The setting. An App Setting, "Back up over Wi-Fi only", defaulting to off. When it is on, nothing syncs until the phone is on an unmetered connection — mid-ride included. There are no row classes, no backlog thresholds and no partial exceptions. A switch that partly ignores the Rider is worse than one that does not exist. The setting lives in a synced table, so it restores with everything else.
The choice. The Rider is offered it once, in the flow where backup is turned on, with the pending volume shown, so the only expensive moment in the feature's life — the first upload on a phone with months of history — is a decision they made rather than one that happened to them. Afterwards it is an ordinary settings row.
The status line. The account widget on the social sheet already renders sign-in state and is the most-seen surface in the app. It gains a line:
Native owns the state and actionable pause reason and emits both; JS renders them. Every
pausedreason also raises a notification: it does not resolve through ordinary retry, and a backup that has silently stopped for weeks is the failure this feature can least afford.Failures become Diagnostic Events, so the existing event log is the debugging surface and no new error UI is invented. Permanent failures are coalesced by error class/table/cursor and contain metadata only, so one poison row cannot create one new pending Diagnostic Event per retry.
Per
AGENTS.md, any reusable component or new visual state added here needs its preview in the component showcase undersrc/app/settings/components/.Likely files
src/modules/profile/components/AccountWidget.tsx- the widget that gains the status line, in both its signed-in and signed-out branchessrc/screens/main/overlays/TopBar.tsx- where the widget is mounted into the social sheetmodules/vescape-core/src/index.ts- the state native emits and the event JS subscribes tosrc/modules/settings/store/- the app setting and its defaultsrc/app/settings/other.tsx- where a backup settings row plausibly belongsmodules/vescape-core/android/src/main/java/expo/modules/vescapecore/diagnostics/DiagnosticReporter.kt- how failures become Diagnostic Eventsmodules/vescape-core/android/src/main/java/expo/modules/vescapecore/notification/- the existing notification surface for the paused statesrc/app/settings/components/- the component showcase entryImplementation hints
AccountWidgethas three branches — loading, signed out, signed in — and the signed-in branch already has an identity block with a secondary text line. The status belongs there rather than as a fourth branch.The signed-out hint currently reads as generic online-features wording. Backup is now the concrete reason to sign in; say so.
Diagnostic Events are uploaded, so a failure payload must not carry the Device Token, a server URL with credentials in it, or row contents.
docs/agents/react.mdcovers the icon and UI conventions;docs/design.mdcovers the visual language. Follow the existing widget surface rather than introducing a new card style.The pending volume shown at the one-time choice comes from the same per-table pending counts the uploader already computes — do not add a second counting path.
Acceptance criteria
Blocked by
Related