Skip to content

app: don't auto-sync offline recordings for custom STT users; manual sync with confirmation#7691

Merged
mdmohsin7 merged 11 commits into
mainfrom
caleb/offline-sync-3p
Jun 8, 2026
Merged

app: don't auto-sync offline recordings for custom STT users; manual sync with confirmation#7691
mdmohsin7 merged 11 commits into
mainfrom
caleb/offline-sync-3p

Conversation

@mdmohsin7

Copy link
Copy Markdown
Member

Problem

Conversations recorded offline by users on a third‑party (custom) STT provider were getting auto‑synced to Omi and transcribed on Omi's servers, which counted against the free transcription limit and locked those conversations — even though the user uses their own STT and never touches Omi's STT live. This was the source of "my conversations are locked even though I use my own provider" complaints.

Root cause: the live path exempts custom‑STT users (use_custom_stt skips Omi STT + credit checks), but the offline auto‑sync path has no such awareness — it uploads raw audio to /v2/sync-local-files, which transcribes on Omi and applies the free‑tier lock.

What this PR does (app‑only, no backend changes)

For users with a custom STT provider enabled (SharedPreferencesUtil().useCustomStt):

  1. Disable auto‑sync of offline recordings at every auto entry point:
    • capture_provider._autoSyncSessionWals (post‑session auto‑sync)
    • capture_provider.recoverOrphanedWals (startup/reconnect recovery)
    • sync_provider._autoUploadPendingPhoneFiles (in‑provider auto‑upload)
    • home/page offline‑data‑detected auto‑sync trigger
  2. Keep manual sync available, behind an explicit confirmation dialog. When a custom‑STT user taps Sync, they get a dialog explaining the trade‑off: these recordings will be transcribed on Omi's servers and will count toward your plan's transcription limit. Wired at all manual syncWals() triggers (sync page, Plan & Usage status card, Limitless card) via a shared helper confirmSyncForCustomStt(). Non‑custom‑STT users are unaffected (helper is a no‑op that returns true).

New strings localized across all 49 locales.

Scope / decisions

  • No backend change. This relies only on existing backend behavior (custom STT = no Omi STT, no lock).
  • Confirmation is shown on the primary "Sync" actions. Per‑item retry pills (for already‑attempted items) are intentionally not gated — the first manual sync already surfaced the dialog.
  • This does not add client‑side transcription of offline files through the user's own provider — that's the larger follow‑up (transcribe offline WALs via the existing custom‑STT client and upload text). Out of scope here per direction.

Related

Testing

  • flutter analyze on all changed source files — no new issues (pre‑existing lints only, unrelated).
  • flutter gen-l10n — clean, zero untranslated warnings; getters generated for all locales.
  • ⚠️ On‑device runtime verification not performed in this environment (headless Linux, no iOS sim). Manual test plan below.

Manual test plan

  • Enable a custom STT provider; record offline; reconnect → confirm conversation is not auto‑synced and not locked.
  • Open the sync page, tap Sync → confirmation dialog appears; Cancel → nothing syncs; Confirm → recordings sync (and count toward limit as warned).
  • With the default Omi provider (no custom STT) → auto‑sync still works and no dialog appears.

@mdmohsin7

Copy link
Copy Markdown
Member Author

@greptile-apps review

@greptile-apps

greptile-apps Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR disables automatic offline-recording sync for users with a custom STT provider and adds an explicit confirmation dialog for manual syncs, preventing those recordings from being silently transcribed on Omi's servers and counting against the plan limit.

  • Auto-sync blocked at four entry points (_autoSyncSessionWals, recoverOrphanedWals, _autoUploadPendingPhoneFiles, and the onOfflineDataDetected home-page callback) via a direct SharedPreferencesUtil().useCustomStt guard.
  • Manual sync gated through the new confirmSyncForCustomStt() helper, which shows an OmiConfirmDialog explaining the trade-off; wired into the sync page, auto-sync status card, and the Limitless sync widget.
  • Localization added for two new strings across all 49 locales.

Confidence Score: 4/5

Safe to merge for the auto-sync paths; the error-card retry pills in sync_page.dart and auto_sync_page.dart remain ungated and can silently consume transcription quota for custom-STT users who experience a sync failure.

All four auto-sync entry points are correctly blocked, and the three primary manual-sync actions are properly gated behind the confirmation dialog. The remaining gap is the unguarded retrySync() call that surfaces after a failed sync — a custom-STT user who sees the error card can tap Retry and upload to Omi's servers without ever seeing the warning dialog, which is the exact behaviour this PR is trying to prevent.

app/lib/pages/conversations/sync_page.dart and app/lib/pages/conversations/auto_sync_page.dart — the error-card retry pills call retrySync() directly without going through confirmSyncForCustomStt.

Important Files Changed

Filename Overview
app/lib/utils/sync_confirmation.dart New utility: reads useCustomStt flag and shows an OmiConfirmDialog before allowing sync; no-ops for non-custom-STT users. Logic and null-safety (confirmed ?? false) are correct.
app/lib/providers/capture_provider.dart Adds early-return guards in _autoSyncSessionWals and recoverOrphanedWals for custom STT users; cleanly skips both auto-sync paths without touching the manual sync flow.
app/lib/providers/sync_provider.dart Adds useCustomStt guard to _autoUploadPendingPhoneFiles; the import and placement are correct, and the manual syncWals() path is unaffected.
app/lib/pages/home/page.dart onOfflineDataDetected callback now returns early when useCustomStt is true, correctly preventing the auto-sync trigger from firing on device reconnect.
app/lib/pages/conversations/sync_page.dart _handleSyncWals now gates on confirmSyncForCustomStt; however the retrySync() pill (line 641) remains ungated, letting custom-STT users retry a failed sync without the warning dialog.
app/lib/pages/conversations/auto_sync_page.dart Status-card Sync pills now gate on confirmSyncForCustomStt with a context.mounted check; the retrySync() error-card pill (line 306) remains ungated.
app/lib/pages/capture/widgets/limitless_sync_widget.dart Sync button now awaits confirmSyncForCustomStt and checks context.mounted before calling syncWals(); guard is correctly placed.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Offline recording created] --> B{Sync trigger}
    B -->|Auto: session end / startup / device reconnect / phone file timer| C{useCustomStt?}
    B -->|Manual: tap Sync button| D{useCustomStt?}

    C -->|Yes| E[Skip — no auto-sync\ncapture_provider / sync_provider / home/page]
    C -->|No| F[Auto-sync via syncWals]

    D -->|Yes| G[confirmSyncForCustomStt\nshow OmiConfirmDialog]
    D -->|No| H[Proceed directly to syncWals]

    G -->|Cancel / dismiss| I[Abort — nothing synced]
    G -->|Confirm| J{context.mounted?}
    J -->|Yes| K[syncWals — Omi STT\ncounts toward limit]
    J -->|No| L[Abort — widget disposed]

    H --> K

    M[Error pill: retrySync] -->|Ungated — no dialog| K

    style E fill:#c8e6c9
    style I fill:#c8e6c9
    style L fill:#c8e6c9
    style K fill:#ffe0b2
    style M fill:#ffcdd2
Loading

Reviews (2): Last reviewed commit: "app: guard context.mounted after sync co..." | Re-trigger Greptile

Comment on lines +173 to +180
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async {
if (await confirmSyncForCustomStt(context)) p.syncWals();
});
} else if (readyToBackUp > 0) {
title = l.syncCardReadyCount(readyToBackUp);
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () => p.syncWals());
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async {
if (await confirmSyncForCustomStt(context)) p.syncWals();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing context.mounted guard after the await in the status pill callbacks. Consistent with the pattern used in sync_page.dart (_handleSyncWals), a mounted check should follow every await before any subsequent work, even when that work doesn't directly use context. This is also needed to satisfy the use_build_context_synchronously lint if it is enabled.

Suggested change
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async {
if (await confirmSyncForCustomStt(context)) p.syncWals();
});
} else if (readyToBackUp > 0) {
title = l.syncCardReadyCount(readyToBackUp);
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () => p.syncWals());
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async {
if (await confirmSyncForCustomStt(context)) p.syncWals();
});
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async {
if (await confirmSyncForCustomStt(context) && context.mounted) p.syncWals();
});
} else if (readyToBackUp > 0) {
title = l.syncCardReadyCount(readyToBackUp);
action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async {
if (await confirmSyncForCustomStt(context) && context.mounted) p.syncWals();
});

Comment on lines +58 to +60
onPressed: () async {
if (await confirmSyncForCustomStt(context)) syncProvider.syncWals();
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing context.mounted guard after the await. While syncProvider.syncWals() itself does not consume context, adding the guard is consistent with the pattern in sync_page.dart and avoids a potential use_build_context_synchronously lint warning if the analysis options are tightened.

Suggested change
onPressed: () async {
if (await confirmSyncForCustomStt(context)) syncProvider.syncWals();
},
onPressed: () async {
if (await confirmSyncForCustomStt(context) && context.mounted) syncProvider.syncWals();
},

@mdmohsin7

Copy link
Copy Markdown
Member Author

@greptile-apps re-review

@mdmohsin7 mdmohsin7 merged commit bc7af83 into main Jun 8, 2026
3 checks passed
@mdmohsin7 mdmohsin7 deleted the caleb/offline-sync-3p branch June 8, 2026 09:23
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