Skip to content

fix: resolve trade index desync, session lookup crash#566

Merged
grunch merged 6 commits into
mainfrom
fix/trade-index-sync-and-session-handling
Apr 9, 2026
Merged

fix: resolve trade index desync, session lookup crash#566
grunch merged 6 commits into
mainfrom
fix/trade-index-sync-and-session-handling

Conversation

@grunch

@grunch grunch commented Apr 8, 2026

Copy link
Copy Markdown
Member

This commit addresses several interconnected issues discovered when
running the app on Linux desktop after clearing SharedPreferences data.

Session creation crash (session_notifier.dart)

The duplicate-session check in newSession() compared s.orderId == orderId
even when orderId was null. This caused any session with a null orderId
(i.e., any pending order creation) to match, returning a stale session
without registering the new requestId in _requestIdToSession. The
subsequent _getSession() lookup by requestId then threw
"No session found for requestId". Fixed by only performing the duplicate
check when orderId is non-null.

Trade index desync after SharedPreferences loss (app_init_provider.dart, restore_manager.dart)

On Linux, flutter_secure_storage uses libsecret/GNOME Keyring, which
persists outside ~/.local/share/. When that directory is deleted,
the mnemonic survives but the trade key index (stored in
SharedPreferences) resets to the default of 1. Mostro then rejects
all messages with invalid_trade_index because the index is far behind
the server's record.

Added a lightweight syncTradeIndex() method to RestoreService that
fetches only the last-trade-index from Mostro and updates local
storage, without performing a full restore. The app initializer now
detects the inconsistency (master key exists but trade index is at
default) and triggers this sync in the background at startup.

invalid_trade_index not handled in order creation (add_order_notifier.dart)

When Mostro responded with invalid_trade_index during order creation,
the app showed a snackbar but did not clean up the temporary session
or navigate back to the order book, leaving the UI stuck. Added
invalidTradeIndex to the terminal-error branch that triggers
_cleanupSessionAndNavigateBack().

Missing too_many_requests CantDo variant (cant_do_reason.dart)

Mostro can respond with cant_do reason "too_many_requests", which was
not present in the CantDoReason enum. This caused a FormatException
during JSON parsing, silently dropping the message. Added the variant
to the enum, localization strings (en/es/it), and the notification
message map.

Summary by CodeRabbit

  • New Features

    • Added "too many requests" reason and user message with EN/ES/IT translations and UI mapping.
    • Added trade-index synchronization API, concurrency guard for restore operations, and automatic sync during app initialization when needed.
    • Added persistence check for stored trade key index.
  • Bug Fixes

    • Fixed session reuse logic when no order ID is present.
    • Treat invalid trade index as a terminal error alongside invalid fiat currency.

grunch added 2 commits April 8, 2026 15:51
…ntDo variant

This commit addresses several interconnected issues discovered when
running the app on Linux desktop after clearing SharedPreferences data.

**Session creation crash (session_notifier.dart)**

The duplicate-session check in newSession() compared s.orderId == orderId
even when orderId was null. This caused any session with a null orderId
(i.e., any pending order creation) to match, returning a stale session
without registering the new requestId in _requestIdToSession. The
subsequent _getSession() lookup by requestId then threw
"No session found for requestId". Fixed by only performing the duplicate
check when orderId is non-null.

**Trade index desync after SharedPreferences loss (app_init_provider.dart, restore_manager.dart)**

On Linux, flutter_secure_storage uses libsecret/GNOME Keyring, which
persists outside ~/.local/share/. When that directory is deleted,
the mnemonic survives but the trade key index (stored in
SharedPreferences) resets to the default of 1. Mostro then rejects
all messages with invalid_trade_index because the index is far behind
the server's record.

Added a lightweight syncTradeIndex() method to RestoreService that
fetches only the last-trade-index from Mostro and updates local
storage, without performing a full restore. The app initializer now
detects the inconsistency (master key exists but trade index is at
default) and triggers this sync in the background at startup.

**invalid_trade_index not handled in order creation (add_order_notifier.dart)**

When Mostro responded with invalid_trade_index during order creation,
the app showed a snackbar but did not clean up the temporary session
or navigate back to the order book, leaving the UI stuck. Added
invalidTradeIndex to the terminal-error branch that triggers
_cleanupSessionAndNavigateBack().

**Missing too_many_requests CantDo variant (cant_do_reason.dart)**

Mostro can respond with cant_do reason "too_many_requests", which was
not present in the CantDoReason enum. This caused a FormatException
during JSON parsing, silently dropping the message. Added the variant
to the enum, localization strings (en/es/it), and the notification
message map.
StreamSubscription.cancel() returns a Future. Not awaiting it could
cause incomplete cleanup if the method is called in quick succession.
This aligns with the awaited cleanup pattern used elsewhere in the
RestoreService.
@grunch grunch requested review from BraCR10 and Catrya and removed request for BraCR10 April 8, 2026 19:09
@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@grunch has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 53 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 12 minutes and 53 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6f25a6f1-4a22-4ec8-a4a4-69bf01057050

📥 Commits

Reviewing files that changed from the base of the PR and between f728a0b and db6eb50.

📒 Files selected for processing (1)
  • lib/features/restore/restore_manager.dart

Walkthrough

Adds a new CantDo reason tooManyRequests and translations; updates notification/UI mappings and AddOrderNotifier terminal handling; adds operation coordination and syncTradeIndex() to RestoreService with app-init trigger; adds persisted-trade-index checks in KeyManager/KeyStorage; tweaks session creation guard.

Changes

Cohort / File(s) Summary
Cant-Do Enum & Localizations
lib/data/models/enums/cant_do_reason.dart, lib/l10n/intl_en.arb, lib/l10n/intl_es.arb, lib/l10n/intl_it.arb
Added CantDoReason.tooManyRequests ('too_many_requests') and tooManyRequests localization keys (EN/ES/IT).
Notification & UI Mapping
lib/shared/widgets/notification_listener_widget.dart, lib/features/trades/widgets/mostro_message_detail_widget.dart
Mapped 'too_many_requests' to localized messages in the notification mapper and Mostro message detail view.
Order Notifier
lib/features/order/notifiers/add_order_notifier.dart
Treats CantDoReason.invalidTradeIndex as a terminal error (cleanup + navigate back) alongside invalidFiatCurrency; generalized logging/wording.
Restore Flow & Sync
lib/features/restore/restore_manager.dart
Added _operationInProgress coordination and new Future<void> syncTradeIndex() to request last-trade-index, apply timeout, update key index, and ensure temporary subscription cleanup.
App Init Integration
lib/shared/providers/app_init_provider.dart
Checks keyManager.hasMasterKey() before init() and conditionally calls restoreService.syncTradeIndex() (unawaited) when a master key exists but trade index is not persisted.
Key Manager / Storage
lib/features/key_manager/key_manager.dart, lib/features/key_manager/key_storage.dart
Added hasPersistedTradeKeyIndex() to KeyManager and KeyStorage to detect persisted trade key index in storage.
Session Notifier
lib/shared/notifiers/session_notifier.dart
Adjusted newSession early-return to only reuse sessions when orderId is non-null (avoids matching null orderId).

Sequence Diagram

sequenceDiagram
    participant App as App Initializer
    participant KM as KeyManager
    participant RS as RestoreService
    participant Mostro as Mostro API
    participant Sub as Temp Subscription

    App->>KM: hasMasterKey()
    alt master key exists && !hasPersistedTradeKeyIndex
        App->>RS: syncTradeIndex() (unawaited)
        RS->>RS: if _operationInProgress -> return
        RS->>KM: read masterKey / derive tempTradeKey
        RS->>Mostro: send last-trade-index request
        Mostro-->>Sub: emit event with trade index
        Sub-->>RS: deliver event
        RS->>RS: extract tradeIndex
        alt tradeIndex >= 0
            RS->>KM: set current key index = tradeIndex + 1
        end
        RS->>Sub: cancel subscription
        RS->>RS: clear temp keys & _operationInProgress=false
    else
        App->>RS: skip sync
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Catrya

Poem

🐰 Too many hops, the queue did plea,
I synced the keys beneath the tree.
Canceled subs and tidied the nest,
Now trades may hop and all can rest. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fixes: resolving trade index desync and session lookup crash, which are the primary issues addressed across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/trade-index-sync-and-session-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
lib/features/order/notifiers/add_order_notifier.dart (1)

49-54: Consider aligning terminal-error naming/logging with the new branch logic.

Now that invalid_trade_index is also terminal here, it would help to rename/comment/log this path generically (instead of fiat-specific wording) to keep debugging signals accurate.

♻️ Small cleanup idea
-  /// Clean up session and navigate back for invalid_fiat_currency error
+  /// Clean up session and navigate back for terminal cant-do errors
   void _cleanupSessionAndNavigateBack() {
-    logger.i('Cleaning up session and navigating back after invalid_fiat_currency');
+    logger.i('Cleaning up session and navigating back after terminal cant-do error');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/notifiers/add_order_notifier.dart` around lines 49 - 54,
The branch handling terminal errors currently treats invalidTradeIndex alongside
invalidFiatCurrency but uses fiat-specific wording; update the logic around
CantDo handling (referencing CantDo, CantDoReason, and the _resetForRetry()
call) to treat terminal errors generically: rename any variable/comment/log
message that mentions fiat to something like "terminal error" or "invalid
request parameter", and update logging/messages for the else branch (the path
checking CantDoReason.invalidFiatCurrency and CantDoReason.invalidTradeIndex) so
it describes a generic terminal error instead of fiat-specific wording to make
debugging accurate.
lib/shared/providers/app_init_provider.dart (1)

24-29: Desync detection condition is a bit too broad.

tradeKeyIndex == 1 can also be a valid steady state, so this may trigger syncTradeIndex() unnecessarily. Consider gating on “missing persisted index” (or equivalent explicit desync signal) rather than the value alone.

💡 Possible direction
-  if (hadMasterKey && keyManager.tradeKeyIndex == 1) {
+  if (hadMasterKey && await keyManager.isTradeIndexMissingLocally()) {
     unawaited(ref.read(restoreServiceProvider).syncTradeIndex());
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/shared/providers/app_init_provider.dart` around lines 24 - 29, The
current desync check uses keyManager.tradeKeyIndex == 1 which can be a valid
state; instead gate the sync on an explicit “missing persisted index” signal:
replace the condition with hadMasterKey && !keyManager.hasPersistedTradeIndex
(or hadMasterKey && keyManager.persistentTradeIndex == null) and keep the call
to unawaited(ref.read(restoreServiceProvider).syncTradeIndex()); if KeyManager
lacks a hasPersistedTradeIndex/persistentTradeIndex accessor, add one to
KeyManager and use it here so we only call syncTradeIndex() when the persisted
index is actually absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/data/models/enums/cant_do_reason.dart`:
- Around line 26-27: The switch that formats/displayes CantDoReason values in
lib/features/trades/widgets/mostro_message_detail_widget.dart doesn't handle the
new enum value CantDoReason.tooManyRequests, so it falls through to default; add
a case for CantDoReason.tooManyRequests in the same switch (or the helper that
maps CantDoReason to UI text) and return the appropriate localized message using
the same localization pattern used for other cases (match how pendingOrderExists
is handled) so users see the specific "too many requests" reason instead of the
generic default.

In `@lib/features/restore/restore_manager.dart`:
- Around line 980-982: The catch in syncTradeIndex currently logs only the error
object; change the handler to capture the stack trace (use catch (e,
stackTrace)) and pass it into the logger.e call (e.g., logger.e('syncTradeIndex:
failed', error: e, stackTrace: stackTrace)) so failures include stack traces
like the rest of restore_manager.dart.
- Around line 956-987: syncTradeIndex mutates shared instance fields
(_tempSubscription, _tempTradeKey, _masterKey, _currentStage) that
initRestoreProcess also uses, causing race conditions; either serialize the
operations with an operation-level lock or make syncTradeIndex use only local
variables and avoid touching those shared fields. Add a simple mutex/flag (e.g.,
a private Future<void>? _syncLock or bool _syncInProgress) and await/guard
around the critical section in syncTradeIndex (or refactor to use local vars for
tempSubscription/tempTradeKey/masterKey/currentStage and only read/write
persistent state via keyManager/setCurrentKeyIndex), ensuring you still cancel
the local subscription and clear local state in finally without altering the
shared fields used by initRestoreProcess.

---

Nitpick comments:
In `@lib/features/order/notifiers/add_order_notifier.dart`:
- Around line 49-54: The branch handling terminal errors currently treats
invalidTradeIndex alongside invalidFiatCurrency but uses fiat-specific wording;
update the logic around CantDo handling (referencing CantDo, CantDoReason, and
the _resetForRetry() call) to treat terminal errors generically: rename any
variable/comment/log message that mentions fiat to something like "terminal
error" or "invalid request parameter", and update logging/messages for the else
branch (the path checking CantDoReason.invalidFiatCurrency and
CantDoReason.invalidTradeIndex) so it describes a generic terminal error instead
of fiat-specific wording to make debugging accurate.

In `@lib/shared/providers/app_init_provider.dart`:
- Around line 24-29: The current desync check uses keyManager.tradeKeyIndex == 1
which can be a valid state; instead gate the sync on an explicit “missing
persisted index” signal: replace the condition with hadMasterKey &&
!keyManager.hasPersistedTradeIndex (or hadMasterKey &&
keyManager.persistentTradeIndex == null) and keep the call to
unawaited(ref.read(restoreServiceProvider).syncTradeIndex()); if KeyManager
lacks a hasPersistedTradeIndex/persistentTradeIndex accessor, add one to
KeyManager and use it here so we only call syncTradeIndex() when the persisted
index is actually absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b9e8dd7d-d839-4292-8e2b-9efd59ff4f35

📥 Commits

Reviewing files that changed from the base of the PR and between a1fbf60 and fa1b542.

📒 Files selected for processing (9)
  • lib/data/models/enums/cant_do_reason.dart
  • lib/features/order/notifiers/add_order_notifier.dart
  • lib/features/restore/restore_manager.dart
  • lib/l10n/intl_en.arb
  • lib/l10n/intl_es.arb
  • lib/l10n/intl_it.arb
  • lib/shared/notifiers/session_notifier.dart
  • lib/shared/providers/app_init_provider.dart
  • lib/shared/widgets/notification_listener_widget.dart

Comment thread lib/data/models/enums/cant_do_reason.dart
Comment thread lib/features/restore/restore_manager.dart
Comment thread lib/features/restore/restore_manager.dart Outdated
- Add tooManyRequests case to _getCantDoMessage switch in
  mostro_message_detail_widget.dart so users see the specific
  localized message instead of the generic default fallback.

- Add operation guard (_operationInProgress) to RestoreService to
  prevent race conditions between syncTradeIndex and initRestoreProcess
  sharing instance fields (_tempSubscription, _tempTradeKey, _masterKey,
  _currentStage). Both methods now set the flag on entry and clear it
  in finally.

- Capture stack trace in syncTradeIndex catch block (catch (e, stack))
  and pass it to logger.e for consistency with the rest of
  restore_manager.dart.

- Replace tradeKeyIndex == 1 heuristic in app_init_provider with
  explicit hasPersistedTradeKeyIndex() check via KeyStorage and
  KeyManager. This correctly distinguishes between a genuinely new
  user at index 1 and a missing SharedPreferences entry that defaults
  to 1.

- Update _cleanupSessionAndNavigateBack doc comment and log message
  to say "terminal error" instead of "invalid_fiat_currency" since the
  method now also handles invalidTradeIndex.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/restore/restore_manager.dart`:
- Around line 824-826: initRestoreProcess() must guard against concurrent
operations like syncTradeIndex() to avoid corrupting shared state
(_tempSubscription, _currentCompleter, etc.); add a check at the start of
initRestoreProcess() that returns/fails early (and logs) when
_operationInProgress is true instead of setting _operationInProgress = true
unconditionally, so the method only proceeds when no other operation is active
and then sets _operationInProgress before touching shared fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd638260-2dc0-4a89-98c4-0a1a650ab3fd

📥 Commits

Reviewing files that changed from the base of the PR and between fa1b542 and 0730ac7.

📒 Files selected for processing (6)
  • lib/features/key_manager/key_manager.dart
  • lib/features/key_manager/key_storage.dart
  • lib/features/order/notifiers/add_order_notifier.dart
  • lib/features/restore/restore_manager.dart
  • lib/features/trades/widgets/mostro_message_detail_widget.dart
  • lib/shared/providers/app_init_provider.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/features/order/notifiers/add_order_notifier.dart
  • lib/shared/providers/app_init_provider.dart

Comment thread lib/features/restore/restore_manager.dart
initRestoreProcess() now mirrors the same guard pattern already used by syncTradeIndex()
it checks _operationInProgress at the top and returns false early (with a warning log)
if another operation is active, preventing concurrent corruption of shared
state (_tempSubscription, _currentCompleter, etc.). Analyzer passes clean.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/restore/restore_manager.dart`:
- Around line 988-993: The code currently skips persisting when
response.tradeIndex == 0, so wallets with no prior trades never get the default
persisted; update the syncTradeIndex path to always persist the default index
instead of only when response.tradeIndex > 0 — call
keyManager.setCurrentKeyIndex(response.tradeIndex + 1) (and log via logger.i)
for tradeIndex == 0 as well (e.g., change the conditional or remove it) so the
defaulted value is written to storage and prevents repeated resync on cold
start.
- Around line 984-985: A race occurs because _sendLastTradeIndexRequest() can
trigger a reply before _currentCompleter is set by _waitForEvent(), causing
_handleTempSubscriptionsResponse() to drop the event; fix by arming the awaiting
mechanism before sending the request: create or initialize the completer for
RestoreStage.gettingTradeIndex (i.e. invoke the logic that _waitForEvent() would
set up) prior to calling _sendLastTradeIndexRequest(), then call _waitForEvent()
or await the already-created completer; reference the methods
_sendLastTradeIndexRequest(), _waitForEvent(),
_handleTempSubscriptionsResponse(), and the
_currentCompleter/RestoreStage.gettingTradeIndex symbols when making the change.
- Around line 824-828: The guard in initRestoreProcess that returns false when
_operationInProgress is true causes silent no-ops upstream (caller doesn’t check
the bool); change initRestoreProcess (and related call sites) so that instead of
silently returning false it either awaits the active operation to finish or
throws a handled error. Concretely, replace the early return on
_operationInProgress in initRestoreProcess with one of: 1) wait for the existing
operation to complete (e.g., track an in-progress Future/Completer and await it)
and then proceed to run syncTradeIndex()/restore logic, or 2) throw a specific
exception (e.g., RestoreInProgressException) so callers can surface a
user-facing error; update callers that currently ignore the boolean to handle
the thrown exception if you choose option 2. Ensure you reference and update the
_operationInProgress flag, the in-progress Future/Completer you add, and the
initRestoreProcess method so restores are not silently skipped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02906c67-e074-4857-a813-ef9f634d7716

📥 Commits

Reviewing files that changed from the base of the PR and between 0730ac7 and 63039d0.

📒 Files selected for processing (1)
  • lib/features/restore/restore_manager.dart

Comment thread lib/features/restore/restore_manager.dart
Comment thread lib/features/restore/restore_manager.dart Outdated
Comment thread lib/features/restore/restore_manager.dart Outdated
… restore

- Arm completer before sending trade index request in syncTradeIndex()
  to prevent reply from being dropped when it arrives before
  _waitForEvent() sets up _currentCompleter
- Always persist trade index in syncTradeIndex() even when tradeIndex
  is 0, preventing repeated resync on cold start for new wallets
- Replace silent return false in initRestoreProcess() with awaiting
  the in-progress operation via Completer, so callers get meaningful
  results instead of silent no-ops

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
lib/features/restore/restore_manager.dart (2)

1012-1018: Consider clearing _currentCompleter in finally for consistency.

initRestoreProcess() clears _currentCompleter in its finally block (line 951), but syncTradeIndex() does not. While the completed completer won't cause functional issues, clearing it maintains consistent cleanup patterns across both methods.

♻️ Suggested cleanup addition
     } finally {
       await _tempSubscription?.cancel();
       _tempSubscription = null;
+      _currentCompleter = null;
       _tempTradeKey = null;
       _masterKey = null;
       _operationInProgress = false;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/restore/restore_manager.dart` around lines 1012 - 1018, The
finally block in syncTradeIndex() should mirror initRestoreProcess() by clearing
the shared cleanup state; specifically, add resetting _currentCompleter to null
in the finally block alongside _tempSubscription, _tempTradeKey, _masterKey, and
_operationInProgress so the completer is consistently cleared after
syncTradeIndex() completes or fails.

971-1018: Good implementation addressing prior feedback.

The method correctly:

  • Arms the completer before sending the request (lines 992-993)
  • Logs failures with stack trace (line 1011)
  • Always persists the trade index regardless of value (line 1006)

One minor observation: syncTradeIndex() sets _operationInProgress = true but doesn't set _operationCompleter. If initRestoreProcess() is called while syncTradeIndex() is running, it will see _operationInProgress == true and _operationCompleter == null, causing it to return false immediately (line 831) rather than awaiting completion.

Given that syncTradeIndex() is a brief background operation (~10s max) and this race is unlikely in practice, this is acceptable. For full consistency, you could optionally set a completer here too.

♻️ Optional: Add completer for full coordination symmetry
   Future<void> syncTradeIndex() async {
     if (_operationInProgress) {
       logger.w('syncTradeIndex: another operation in progress, skipping');
       return;
     }

     final keyManager = ref.read(keyManagerProvider);
     if (keyManager.masterKeyPair == null) {
       logger.w('syncTradeIndex: no master key, skipping');
       return;
     }

     _operationInProgress = true;
+    _operationCompleter = Completer<bool>();
     try {
       // ... existing code ...
+      _operationCompleter?.complete(true);
     } catch (e, stack) {
       logger.e('syncTradeIndex: failed', error: e, stackTrace: stack);
+      _operationCompleter?.complete(false);
     } finally {
       await _tempSubscription?.cancel();
       _tempSubscription = null;
       _tempTradeKey = null;
       _masterKey = null;
       _operationInProgress = false;
+      _operationCompleter = null;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/restore/restore_manager.dart` around lines 971 - 1018,
syncTradeIndex sets _operationInProgress = true but never assigns
_operationCompleter, so concurrent callers to initRestoreProcess (which checks
_operationInProgress and _operationCompleter) may bail out instead of awaiting
completion; fix by creating and assigning a Completer<bool> to
_operationCompleter at the start of syncTradeIndex (before async work) and
ensure you complete or completeError that completer in both the try and
catch/finally paths (use the existing finally block to complete if not already
completed), so initRestoreProcess can await _operationCompleter while
_operationInProgress is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/features/restore/restore_manager.dart`:
- Around line 1012-1018: The finally block in syncTradeIndex() should mirror
initRestoreProcess() by clearing the shared cleanup state; specifically, add
resetting _currentCompleter to null in the finally block alongside
_tempSubscription, _tempTradeKey, _masterKey, and _operationInProgress so the
completer is consistently cleared after syncTradeIndex() completes or fails.
- Around line 971-1018: syncTradeIndex sets _operationInProgress = true but
never assigns _operationCompleter, so concurrent callers to initRestoreProcess
(which checks _operationInProgress and _operationCompleter) may bail out instead
of awaiting completion; fix by creating and assigning a Completer<bool> to
_operationCompleter at the start of syncTradeIndex (before async work) and
ensure you complete or completeError that completer in both the try and
catch/finally paths (use the existing finally block to complete if not already
completed), so initRestoreProcess can await _operationCompleter while
_operationInProgress is true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 183392d4-bfa7-4a5e-a0a4-c632c6240b20

📥 Commits

Reviewing files that changed from the base of the PR and between 63039d0 and f728a0b.

📒 Files selected for processing (1)
  • lib/features/restore/restore_manager.dart

@grunch grunch merged commit e96671b into main Apr 9, 2026
2 checks passed
@grunch grunch deleted the fix/trade-index-sync-and-session-handling branch April 9, 2026 14:21
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