Skip to content

fix: context-aware buttons during cooperative cancellation#576

Merged
grunch merged 2 commits into
mainfrom
fix-buttons
Apr 23, 2026
Merged

fix: context-aware buttons during cooperative cancellation#576
grunch merged 2 commits into
mainfrom
fix-buttons

Conversation

@Catrya

@Catrya Catrya commented Apr 15, 2026

Copy link
Copy Markdown
Member

fix #575

  • Cooperative cancel showed incorrect buttons because the FSM had no memory of the previous status
  • Sellers saw a release button when no fiat was sent (no-op), buyers lost the fiat-sent button from active status
  • Add 4 semantic actions: cooperativeCancelNoFiatByYou/ByPeer and cooperativeCancelFiatSentByYou/ByPeer
  • Add fiatWasSent flag to OrderState, set on fiatSent/fiatSentOk, persists through copyWith chains
  • updateWith() remaps generic protocol actions to semantic variants based on the flag
  • Restore manager reconstructs fiatWasSent from stored message history for cooperativelyCanceled and dispute orders
  • Covers full transition chain: active/fiatSent/dispute to cooperativelyCanceled
  • Update ORDER_STATUS_HANDLING.md with new action matrix, restore flow, and design rationale
  • Add 8 unit tests for remapping logic and flag persistence

Summary by CodeRabbit

  • Bug Fixes

    • Improved cooperative cancellation flow to track whether fiat was sent so UI shows correct action buttons and follow-ups; restore now reconstructs fiat history for accurate state.
  • Notifications

    • Notifications updated to recognize and classify additional cooperative-cancellation scenarios so messages and navigation behave consistently.
  • Documentation

    • Expanded architecture docs with detailed cooperative-cancel variants, remapping logic, and rationale.
  • Tests

    • Added tests validating cancel remapping and fiat-history preservation across transitions.

  - Cooperative cancel showed incorrect buttons because the FSM had no memory of the previous status
  - Sellers saw a release button when no fiat was sent (no-op), buyers lost the fiat-sent button from active status
  - Add 4 semantic actions: cooperativeCancelNoFiatByYou/ByPeer and cooperativeCancelFiatSentByYou/ByPeer
  - Add fiatWasSent flag to OrderState, set on fiatSent/fiatSentOk, persists through copyWith chains
  - updateWith() remaps generic protocol actions to semantic variants based on the flag
  - Restore manager reconstructs fiatWasSent from stored message history for cooperativelyCanceled and dispute orders
  - Covers full transition chain: active/fiatSent/dispute to cooperativelyCanceled
  - Update ORDER_STATUS_HANDLING.md with new action matrix, restore flow, and design rationale
  - Add 8 unit tests for remapping logic and flag persistence
@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c56d380-2154-4838-8d04-5eeb9d9260ec

📥 Commits

Reviewing files that changed from the base of the PR and between b3eaddb and b8717fb.

📒 Files selected for processing (1)
  • lib/features/order/notifiers/order_notifier.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/features/order/notifiers/order_notifier.dart

Walkthrough

This PR introduces four semantic cooperative-cancel action variants that distinguish whether fiat was sent, adds a persisted fiatWasSent flag to OrderState (with remapping of generic cancel actions to the new variants), reconstructs fiatWasSent during restore from message history, and updates notifications, notifiers, UI mappings, docs, and tests to use the new variants.

Changes

Cohort / File(s) Summary
Action Enum
lib/data/models/enums/action.dart
Added four cooperative-cancel semantic variants (cooperativeCancelNoFiatByYou, cooperativeCancelNoFiatByPeer, cooperativeCancelFiatSentByYou, cooperativeCancelFiatSentByPeer).
Notifications
lib/data/models/notification.dart, lib/features/notifications/utils/notification_data_extractor.dart, lib/features/notifications/utils/notification_message_mapper.dart, lib/features/notifications/widgets/notification_details.dart, lib/features/notifications/widgets/notification_item.dart
Extended classification, extraction, mapping, detail rendering, and tap-routing to handle the new cooperative-cancel action variants (treated like existing cooperative-cancel cases).
OrderState Core Logic
lib/features/order/models/order_state.dart
Added persisted fiatWasSent field; updated updateWith() to OR incoming fiatSent/fiatSentOk into fiatWasSent; remapped generic cooperative-cancel actions into the new semantic variants based on fiatWasSent; updated status mapping and actions matrix to reflect variant-specific button availability.
Notifiers & Restore
lib/features/order/notifiers/abstract_mostro_notifier.dart, lib/features/order/notifiers/order_notifier.dart, lib/features/restore/restore_manager.dart
Extended notifier cases for new variants; added OrderNotifier.setFiatWasSent(); changed restore mapping for cooperativelyCanceled and added historical message inspection to reconstruct fiatWasSent for dispute and cooperative-canceled orders.
UI: Trade Detail / Messages
lib/features/trades/screens/trade_detail_screen.dart, lib/features/trades/widgets/mostro_message_detail_widget.dart
Routed new cooperative-cancel variants into existing cancel/accept UI flows and localized text mappings so button text and message text remain consistent with intent.
Docs & Tests
docs/architecture/ORDER_STATUS_HANDLING.md, test/features/order/models/order_state_dispute_auto_close_test.dart
Expanded documentation on cooperative-cancel semantics and fiatWasSent preservation; added tests validating action remapping and fiatWasSent persistence across transitions (including dispute).

Sequence Diagram(s)

sequenceDiagram
  participant Restore as RestoreManager
  participant Store as MessageStore
  participant Notifier as OrderNotifier
  participant State as OrderState
  Restore->>Store: load order messages (fiatSent/fiatSentOk?)
  Store-->>Restore: messages list
  Restore->>Notifier: notifier.setFiatWasSent() if messages contain fiatSent/fiatSentOk
  Notifier->>State: state = state.copyWith(fiatWasSent: true)
  Restore->>State: restore status = cooperativelyCanceled -> use cooperativeCancelInitiatedByPeer
  State->>State: updateWith(reconstructed action) -> remap to cooperativeCancelFiatSentByPeer or cooperativeCancelNoFiatByPeer based on fiatWasSent
  State-->>Notifier: updated state (actions matrix now variant-specific)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • AndreaDiazCorreia
  • BraCR10

Poem

🐰
I hopped through messages, tall and small,
Marked whether fiat did or did not fall.
Four gentle hops to label the chase,
Buttons bloom true in their proper place. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main objective of fixing context-aware buttons during cooperative cancellation, which is the primary focus of all changes in the changeset.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #575 by adding semantic action variants, implementing fiatWasSent flag, remapping logic, restore flow reconstruction, and action matrix updates to provide context-aware buttons based on previous status.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the fix for issue #575: enum extensions, state model updates, notifier logic, restore manager changes, UI handling updates, and supporting tests. No unrelated refactoring or scope creep detected.
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-buttons

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.

🧹 Nitpick comments (3)
lib/features/order/notifiers/order_notifier.dart (1)

150-156: Make setFiatWasSent() idempotent to avoid redundant state emissions.

Line 154 always emits a new state even if fiatWasSent is already true. A quick guard keeps restore replays cheaper.

♻️ Proposed tweak
   void setFiatWasSent() {
-    if (mounted) {
-      state = state.copyWith(fiatWasSent: true);
-    }
+    if (!mounted || state.fiatWasSent) return;
+    state = state.copyWith(fiatWasSent: true);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/notifiers/order_notifier.dart` around lines 150 - 156, The
setFiatWasSent method currently emits a new state unconditionally; change it to
be idempotent by checking the current state's fiatWasSent flag and only calling
state = state.copyWith(fiatWasSent: true) when mounted is true AND
state.fiatWasSent is false so redundant state emissions are avoided (refer to
setFiatWasSent and state.copyWith(fiatWasSent: ...)).
lib/features/notifications/utils/notification_message_mapper.dart (1)

67-73: Consider centralizing cooperative-cancel action grouping to reduce duplication.

Both getTitleKey() and getMessageKey() now repeat the same variant lists. A tiny helper keeps future additions safer.

♻️ Refactor sketch
 class NotificationMessageMapper {
+  static bool _isCooperativeCancelByYou(mostro.Action action) {
+    return action == mostro.Action.cooperativeCancelInitiatedByYou ||
+        action == mostro.Action.cooperativeCancelNoFiatByYou ||
+        action == mostro.Action.cooperativeCancelFiatSentByYou;
+  }
+
+  static bool _isCooperativeCancelByPeer(mostro.Action action) {
+    return action == mostro.Action.cooperativeCancelInitiatedByPeer ||
+        action == mostro.Action.cooperativeCancelNoFiatByPeer ||
+        action == mostro.Action.cooperativeCancelFiatSentByPeer;
+  }
+
   static String getTitleKey(mostro.Action action) {
+    if (_isCooperativeCancelByYou(action)) {
+      return 'notification_cooperative_cancel_initiated_by_you_title';
+    }
+    if (_isCooperativeCancelByPeer(action)) {
+      return 'notification_cooperative_cancel_initiated_by_peer_title';
+    }
     switch (action) {
-      case mostro.Action.cooperativeCancelInitiatedByYou:
-      case mostro.Action.cooperativeCancelNoFiatByYou:
-      case mostro.Action.cooperativeCancelFiatSentByYou:
-        return 'notification_cooperative_cancel_initiated_by_you_title';
-      case mostro.Action.cooperativeCancelInitiatedByPeer:
-      case mostro.Action.cooperativeCancelNoFiatByPeer:
-      case mostro.Action.cooperativeCancelFiatSentByPeer:
-        return 'notification_cooperative_cancel_initiated_by_peer_title';
       ...
     }
   }

   static String getMessageKey(mostro.Action action) {
+    if (_isCooperativeCancelByYou(action)) {
+      return 'notification_cooperative_cancel_initiated_by_you_message';
+    }
+    if (_isCooperativeCancelByPeer(action)) {
+      return 'notification_cooperative_cancel_initiated_by_peer_message';
+    }
     switch (action) {
-      case mostro.Action.cooperativeCancelInitiatedByYou:
-      case mostro.Action.cooperativeCancelNoFiatByYou:
-      case mostro.Action.cooperativeCancelFiatSentByYou:
-        return 'notification_cooperative_cancel_initiated_by_you_message';
-      case mostro.Action.cooperativeCancelInitiatedByPeer:
-      case mostro.Action.cooperativeCancelNoFiatByPeer:
-      case mostro.Action.cooperativeCancelFiatSentByPeer:
-        return 'notification_cooperative_cancel_initiated_by_peer_message';
       ...
     }
   }
 }

Also applies to: 169-175

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/notifications/utils/notification_message_mapper.dart` around
lines 67 - 73, Both getTitleKey() and getMessageKey() duplicate the same
cooperative-cancel action lists; extract a single helper (e.g.,
isCooperativeCancel(Action) or a constant Set<Action> cooperativeCancelActions)
that contains the variants cooperativeCancelNoFiatByYou,
cooperativeCancelFiatSentByYou, cooperativeCancelInitiatedByPeer,
cooperativeCancelNoFiatByPeer, cooperativeCancelFiatSentByPeer and use that
helper from both getTitleKey and getMessageKey to centralize the grouping and
avoid duplication.
lib/features/order/models/order_state.dart (1)

426-434: Consider removing dead code entries for generic variants in active/fiatSent matrices.

Since updateWith() now remaps cooperativeCancelInitiatedByYou/Peer to the semantic variants before getActions() is called, these entries under Status.active and Status.fiatSent will never be matched. They're harmless but could be removed for clarity.

Also applies to: 443-453, 568-576, 584-592

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/models/order_state.dart` around lines 426 - 434, Remove
unreachable action-list entries for Action.cooperativeCancelInitiatedByPeer and
Action.cooperativeCancelInitiatedByYou from the active/fiatSent matrices since
updateWith() now remaps those generic variants to their semantic counterparts
before getActions() runs; locate the matrices under Status.active and
Status.fiatSent in order_state.dart (also the similar blocks around the ranges
noted) and delete the lines that list
cooperativeCancelInitiatedByPeer/cooperativeCancelInitiatedByYou while leaving
the semantic variants intact so behavior is unchanged and the dead entries are
cleaned up.
🤖 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/notifications/utils/notification_message_mapper.dart`:
- Around line 67-73: Both getTitleKey() and getMessageKey() duplicate the same
cooperative-cancel action lists; extract a single helper (e.g.,
isCooperativeCancel(Action) or a constant Set<Action> cooperativeCancelActions)
that contains the variants cooperativeCancelNoFiatByYou,
cooperativeCancelFiatSentByYou, cooperativeCancelInitiatedByPeer,
cooperativeCancelNoFiatByPeer, cooperativeCancelFiatSentByPeer and use that
helper from both getTitleKey and getMessageKey to centralize the grouping and
avoid duplication.

In `@lib/features/order/models/order_state.dart`:
- Around line 426-434: Remove unreachable action-list entries for
Action.cooperativeCancelInitiatedByPeer and
Action.cooperativeCancelInitiatedByYou from the active/fiatSent matrices since
updateWith() now remaps those generic variants to their semantic counterparts
before getActions() runs; locate the matrices under Status.active and
Status.fiatSent in order_state.dart (also the similar blocks around the ranges
noted) and delete the lines that list
cooperativeCancelInitiatedByPeer/cooperativeCancelInitiatedByYou while leaving
the semantic variants intact so behavior is unchanged and the dead entries are
cleaned up.

In `@lib/features/order/notifiers/order_notifier.dart`:
- Around line 150-156: The setFiatWasSent method currently emits a new state
unconditionally; change it to be idempotent by checking the current state's
fiatWasSent flag and only calling state = state.copyWith(fiatWasSent: true) when
mounted is true AND state.fiatWasSent is false so redundant state emissions are
avoided (refer to setFiatWasSent and state.copyWith(fiatWasSent: ...)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65f71174-9173-48b6-b493-7c31b8380ff5

📥 Commits

Reviewing files that changed from the base of the PR and between 4dc0a31 and b3eaddb.

📒 Files selected for processing (14)
  • docs/architecture/ORDER_STATUS_HANDLING.md
  • lib/data/models/enums/action.dart
  • lib/data/models/notification.dart
  • lib/features/notifications/utils/notification_data_extractor.dart
  • lib/features/notifications/utils/notification_message_mapper.dart
  • lib/features/notifications/widgets/notification_details.dart
  • lib/features/notifications/widgets/notification_item.dart
  • lib/features/order/models/order_state.dart
  • lib/features/order/notifiers/abstract_mostro_notifier.dart
  • lib/features/order/notifiers/order_notifier.dart
  • lib/features/restore/restore_manager.dart
  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/features/trades/widgets/mostro_message_detail_widget.dart
  • test/features/order/models/order_state_dispute_auto_close_test.dart

@mostronatorcoder mostronatorcoder 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.

The fix is solid. The approach of using a fiatWasSent boolean flag tracked on OrderState and remapping generic protocol actions to semantic variants in updateWith() is clean and avoids conditional logic at the button-rendering layer.

Highlights:

  • The flag persists correctly through all transitions via copyWith(), including the active → fiatSent → dispute → cooperativeCancel chain.
  • Restore path is correctly handled: cooperativelyCanceled now uses cooperativeCancelInitiatedByPeer as the generic restore action (instead of the terminal cooperativeCancelAccepted), and the restore manager reconstructs fiatWasSent from stored message history for both cooperativelyCanceled and dispute orders.
  • The FSM button matrix is now accurate for all 4 semantic variants on both seller and buyer sides.
  • 8 unit tests cover the critical remapping cases, flag persistence, and the dispute chain.
  • Docs in ORDER_STATUS_HANDLING.md are thorough and explain the design rationale well.

The previous behavior (seller seeing a release button with no hold invoice to release, buyer losing the fiatSent button) was a real UX bug and this fix handles all the edge cases correctly. Approved.

@grunch

grunch commented Apr 20, 2026

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AndreaDiazCorreia AndreaDiazCorreia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

tACK

@grunch grunch merged commit c885098 into main Apr 23, 2026
2 checks passed
@grunch grunch deleted the fix-buttons branch April 23, 2026 12:52
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.

fix: incorrect action buttons during cooperative cancellation

3 participants