Skip to content

Conversation

@Catrya
Copy link
Member

@Catrya Catrya commented Nov 1, 2025

…ng in pending orders

  • Use exact timestamps from order_expires_at tag for precise calculations
  • Add dynamic day/hour scaling: 14d 20h 06m (>24h) or HH:MM:SS (≤24h)
  • Refactor: Create shared DynamicCountdownWidget to remove duplication
  • Apply consistent behavior across TakeOrderScreen and TradeDetailScreen

Summary by CodeRabbit

  • New Features

    • Added a shared DynamicCountdownWidget for pending order/trade countdowns (requires explicit expiration and createdAt).
  • Refactor

    • Consolidated scattered countdown logic into a single adaptive widget that switches between day-scale and hour-scale displays.
    • Countdown components now derive expiration from the order's explicit expires_at value.
  • Bug Fixes

    • Order expiration mapping updated to prefer expires_at timestamps and handle parsing/validation.
  • Documentation

    • Added docs describing the Dynamic Countdown Timer System and integration points.

…ng in pending orders

  - Use exact timestamps from order_expires_at tag for precise calculations
  - Add dynamic day/hour scaling: 14d 20h 06m (>24h) or HH:MM:SS (≤24h)
  - Refactor: Create shared DynamicCountdownWidget to remove duplication
  - Apply consistent behavior across TakeOrderScreen and TradeDetailScreen
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 1, 2025

Walkthrough

Adds a new expiresAt getter on NostrEvent, updates Order.fromEvent to parse that tag into expiresAt, and introduces DynamicCountdownWidget used by TakeOrderScreen and TradeDetailScreen to replace prior manual countdown logic.

Changes

Cohort / File(s) Summary
Data model extension
lib/data/models/nostr_event.dart, lib/data/models/order.dart
Added String? get expiresAt on NostrEvent extension returning the expires_at tag; Order.fromEvent now reads event.expiresAt and parses it with int.tryParse for the order expiresAt field (null-safe).
Screen countdown refactor
lib/features/order/screens/take_order_screen.dart, lib/features/trades/screens/trade_detail_screen.dart
Replaced manual countdown calculations and circular_countdown usage with DynamicCountdownWidget; screens now derive expiration and createdAt from the order (order.expiresAt → parsed DateTime, and order.createdAt) and pass them to the new widget; removed Mostro/time-sync fallback logic.
New widget & docs
lib/shared/widgets/dynamic_countdown_widget.dart, CLAUDE.md, docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
Added DynamicCountdownWidget(expiration, createdAt) implementing day-scale (>24h) and hour-scale (≤24h) displays, hiding itself for expirations >1 hour past or when expiration ≤ createdAt; documentation updated to describe the new system and integrations.

Sequence Diagram(s)

sequenceDiagram
    participant Screen as TakeOrder/TradeDetailScreen
    participant Dynamic as DynamicCountdownWidget
    participant Circular as CircularCountdown

    Note over Screen: Screen reads Order (order.expiresAt, order.createdAt)
    Screen->>Dynamic: render(expiration, createdAt)

    alt remaining > 24h (day-scale)
        Dynamic->>Circular: render(totalDays, daysLeft)
        Dynamic->>Dynamic: format "Xd Yh Zm"
    else remaining ≤ 24h (hour-scale)
        Dynamic->>Circular: render(totalHours, hoursLeft)
        Dynamic->>Dynamic: format "HH:MM:SS"
    end

    Circular-->>Dynamic: progress updates
    Dynamic-->>Screen: widget UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Check null-handling and parsing in lib/data/models/order.dart for event.expiresAt.
  • Validate boundary conditions and formatting logic in lib/shared/widgets/dynamic_countdown_widget.dart (24h transition, >1h past, expiration ≤ createdAt).
  • Confirm screens pass parsed DateTime correctly and removed time-sync fallbacks.

Possibly related PRs

Suggested reviewers

  • chebizarro
  • grunch

Poem

🐰
I found the tag named expires_at bright,
I parse the ticks and stitch the night,
A widget hops with days and hours,
Circles spin as time reveals its powers,
Hooray — the countdown blooms in light! ✨

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: using order_expires_at timestamps and implementing dynamic scaling for the countdown timer across the codebase.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix_clock_pending_orders

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7247917 and 9587cd8.

📒 Files selected for processing (5)
  • lib/data/models/nostr_event.dart (1 hunks)
  • lib/data/models/order.dart (1 hunks)
  • lib/features/order/screens/take_order_screen.dart (4 hunks)
  • lib/features/trades/screens/trade_detail_screen.dart (2 hunks)
  • lib/shared/widgets/dynamic_countdown_widget.dart (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
lib/shared/**

📄 CodeRabbit inference engine (AGENTS.md)

Put shared utilities in lib/shared/

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*.dart: Use Dart formatter defaults (two-space indentation, trailing commas) and run flutter format before committing
Resolve all Flutter analyzer warnings (flutter analyze must be clean)

**/*.dart: Remove unused imports and unused dependencies
Do not add // ignore: must_be_immutable to classes within generated files; rely on regeneration and existing file-level ignores

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/data/models/nostr_event.dart
  • lib/data/models/order.dart
lib/**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Name Riverpod providers as Provider or Notifier

lib/**/*.dart: Always use localized strings via S.of(context)!.keyName; avoid hardcoded user-facing text
Use clear, concise English for variable names, function names, and code comments; all comments must be in English
Target zero flutter analyze issues
Use latest Flutter/Dart APIs (e.g., Color.withValues() instead of withOpacity())
After async operations, check mounted before using BuildContext
Use const constructors where possible

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/data/models/nostr_event.dart
  • lib/data/models/order.dart
lib/features/**

📄 CodeRabbit inference engine (AGENTS.md)

Place application feature code under lib/features//, grouped by domain

Files:

  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
lib/**/screens/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Pass BuildContext to methods that need localization (S.of(context)!.keyName usage)

Files:

  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
lib/data/**

📄 CodeRabbit inference engine (AGENTS.md)

Store persistence and API code under lib/data/

Files:

  • lib/data/models/nostr_event.dart
  • lib/data/models/order.dart
🧠 Learnings (15)
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/main.dart : Configure timeago locales and app initialization in main.dart

Applied to files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/**/*.dart : Use latest Flutter/Dart APIs (e.g., Color.withValues() instead of withOpacity())

Applied to files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/data/models.dart : Export models through lib/data/models.dart

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-07T17:31:12.117Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T17:31:12.117Z
Learning: Applies to lib/**/*.dart : Name Riverpod providers as <Feature>Provider or <Feature>Notifier

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/features/*/providers/**/*.dart : Organize Riverpod providers by feature under features/{feature}/providers/

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-06-04T19:35:20.209Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 110
File: test/notifiers/take_order_notifier_test.dart:72-74
Timestamp: 2025-06-04T19:35:20.209Z
Learning: MostroService methods like takeBuyOrder() and takeSellOrder() return Future<void> and trigger side effects through other mechanisms rather than direct return values. When testing these methods, focus on verifying method calls and testing state changes through the provider system rather than mocking return values.

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/shared/providers/mostro_database_provider.dart : Initialize Sembast database in shared/providers/mostro_database_provider.dart

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/generated/l10n.dart : Generated localization entrypoint file is lib/generated/l10n.dart

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/notifications/notification_service.dart : Notification service configuration and custom icons should be defined in notification_service.dart

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-08-15T01:37:12.243Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 270
File: lib/shared/widgets/order_filter.dart:133-135
Timestamp: 2025-08-15T01:37:12.243Z
Learning: The MostroP2P/mobile project requires Flutter >=3.27.0 as specified in pubspec.yaml, which supports the Color.withValues() method, so usage of withValues() throughout the codebase is valid and should not be flagged as a compatibility issue.

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to test/mocks.mocks.dart : Do not manually edit test/mocks.mocks.dart (Mockito-generated)

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/core/models/relay_list_event.dart : Parse NIP-65 kind 10002 events, validate WS URLs, and handle timestamps/null-safety

Applied to files:

  • lib/data/models/order.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
lib/features/trades/screens/trade_detail_screen.dart (1)

982-999: Clamp countdown inputs before building the widget.

expiresAtTimestamp is already in milliseconds. If createdAt is missing (defaults to 0) totalDuration inside DynamicCountdownWidget becomes extremely large, but the bigger problem is that malformed timestamps (e.g., negative, zero, or in milliseconds instead of seconds) bubble straight into the widget where they can produce invalid totals. Please validate the parsed value (e.g., ensure it’s positive and reasonably near expiration) or skip rendering. This prevents the dynamic countdown from receiving junk data and avoids exploding progress denominators.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
lib/shared/widgets/dynamic_countdown_widget.dart (1)

31-34: Clarify the 1-hour grace period for expired orders.

The code returns an empty widget only if expiration is more than 1 hour in the past. This allows expired orders (0-60 minutes past expiration) to still display a countdown, which might confuse users. Consider either:

  1. Showing a "Expired" state instead of hiding the widget for 0-60 minute window
  2. Hiding immediately when expiration passes (remove the 1-hour buffer)
  3. Documenting why the 1-hour grace period is intentional

If immediate hiding is preferred:

-    // Handle edge case: expiration in the past
-    if (expiration.isBefore(now.subtract(const Duration(hours: 1)))) {
-      // If expiration is more than 1 hour in the past, likely invalid
+    // Hide widget if expiration has passed
+    if (expiration.isBefore(now)) {
       return const SizedBox.shrink();
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9587cd8 and b56449a.

📒 Files selected for processing (5)
  • CLAUDE.md (1 hunks)
  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (1 hunks)
  • lib/data/models/order.dart (1 hunks)
  • lib/features/order/screens/take_order_screen.dart (4 hunks)
  • lib/shared/widgets/dynamic_countdown_widget.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/data/models/order.dart
🧰 Additional context used
📓 Path-based instructions (6)
lib/shared/**

📄 CodeRabbit inference engine (AGENTS.md)

Put shared utilities in lib/shared/

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*.dart: Use Dart formatter defaults (two-space indentation, trailing commas) and run flutter format before committing
Resolve all Flutter analyzer warnings (flutter analyze must be clean)

**/*.dart: Remove unused imports and unused dependencies
Do not add // ignore: must_be_immutable to classes within generated files; rely on regeneration and existing file-level ignores

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/features/order/screens/take_order_screen.dart
lib/**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Name Riverpod providers as Provider or Notifier

lib/**/*.dart: Always use localized strings via S.of(context)!.keyName; avoid hardcoded user-facing text
Use clear, concise English for variable names, function names, and code comments; all comments must be in English
Target zero flutter analyze issues
Use latest Flutter/Dart APIs (e.g., Color.withValues() instead of withOpacity())
After async operations, check mounted before using BuildContext
Use const constructors where possible

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/features/order/screens/take_order_screen.dart
lib/features/**

📄 CodeRabbit inference engine (AGENTS.md)

Place application feature code under lib/features//, grouped by domain

Files:

  • lib/features/order/screens/take_order_screen.dart
lib/**/screens/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Pass BuildContext to methods that need localization (S.of(context)!.keyName usage)

Files:

  • lib/features/order/screens/take_order_screen.dart
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Store additional project guidance and documentation in docs/

Files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
🧠 Learnings (10)
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Document architectural changes in CLAUDE.md and related docs (NOSTR.md, README.md)

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • CLAUDE.md
  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/main.dart : Configure timeago locales and app initialization in main.dart

Applied to files:

  • CLAUDE.md
  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/features/relays/relays_notifier.dart : Implement relay operations: syncWithMostroInstance, removeRelayWithBlacklist, addRelayWithSmartValidation, _handleMostroRelayListUpdate

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/core/models/relay_list_event.dart : Parse NIP-65 kind 10002 events, validate WS URLs, and handle timestamps/null-safety

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/data/models.dart : Export models through lib/data/models.dart

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-10-07T17:31:12.117Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T17:31:12.117Z
Learning: Applies to lib/**/*.dart : Name Riverpod providers as <Feature>Provider or <Feature>Notifier

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/features/*/providers/**/*.dart : Organize Riverpod providers by feature under features/{feature}/providers/

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-06-04T19:35:20.209Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 110
File: test/notifiers/take_order_notifier_test.dart:72-74
Timestamp: 2025-06-04T19:35:20.209Z
Learning: MostroService methods like takeBuyOrder() and takeSellOrder() return Future<void> and trigger side effects through other mechanisms rather than direct return values. When testing these methods, focus on verifying method calls and testing state changes through the provider system rather than mocking return values.

Applied to files:

  • lib/features/order/screens/take_order_screen.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (10)
docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (1)

343-351: Remove unused createdAt parameter or document its purpose.

The DynamicCountdownWidget signature includes createdAt (line 320), but it is never used in the build method. The countdown logic only uses expiration to calculate remaining time. Either remove this parameter or add documentation explaining its intended use (e.g., for audit logging, total elapsed time tracking).

CLAUDE.md (1)

248-259: LGTM! Clear documentation of the Dynamic Countdown Timer System.

The documentation accurately describes the DynamicCountdownWidget implementation, including the dual display modes (day scale for >24h, hour scale for ≤24h), data source (order_expires_at), and integration points. This will help future developers understand the countdown system.

lib/features/order/screens/take_order_screen.dart (4)

21-21: LGTM! Clean import for the new shared countdown widget.

The import follows the project's structure and enables the countdown refactor.


89-91: LGTM! Constructor properly refactored to accept the order object.

The countdown widget now receives the complete NostrEvent order, allowing it to extract both expiration and creation timestamps. This is cleaner than passing a derived DateTime.

Also applies to: 444-449


480-484: LGTM! Clean delegation to the shared countdown widget.

The DynamicCountdownWidget encapsulates all countdown logic, eliminating duplication. The parameters (expiration, createdAt) provide the widget with everything it needs.


465-477: I need to gather more information about the Mostro protocol's timestamp format expectations.<!-- [scratchpad_start]
[task verify_timestamp_format_2/2]
[observations]

  • Found Mostro protocol documentation showing "expiration" tag format
  • In mostro.network documentation (search result 3 & 10): both show expiration as ["expiration", "1719391096"]
  • Unix timestamp 1719391096 is June 24, 2024 when converted (seconds since epoch)
  • This confirms seconds format, not milliseconds
  • Code multiplies by 1000 to convert to DateTime.fromMillisecondsSinceEpoch, which expects milliseconds
  • The assumption of seconds is CORRECT
    [analysis]
  • Mostro protocol sends expiration timestamp in seconds (standard Unix timestamp)
  • Multiplication by 1000 is CORRECT to convert seconds to milliseconds for DateTime
  • No need for dual-format handling - protocol is consistent with seconds
    [pending]
  • None - sufficient evidence found
    [done]
  • Verification complete: timestamp format assumption is correct, code is safe
    [scratchpad_start] -->

Based on the Mostro protocol documentation, the expiration tag contains a Unix timestamp like "1719391096", confirming that order_expires_at is sent in seconds. The codebase consistently treats this value as seconds and multiplies by 1000 to convert to milliseconds for DateTime.fromMillisecondsSinceEpoch(), which is correct.

The timestamp format assumption is verified and the code change is safe. No dual-format detection logic is needed because the Mostro protocol guarantees seconds format.

lib/shared/widgets/dynamic_countdown_widget.dart (4)

1-24: LGTM! Well-structured countdown widget with clear documentation.

The widget properly encapsulates the countdown logic with clean dependencies (circular_countdown, Riverpod, localization). The documentation clearly explains the dual-mode behavior and scope (pending orders only).


36-46: LGTM! Robust edge-case handling.

The early return for invalid time ranges (expiration ≤ createdAt) prevents crashes. The remainingTime calculation correctly clamps to zero when expired.


52-70: Excellent fix! Day scale rendering now handles zero/negative totals safely.

The ceiling division with clamping (line 54: ((totalDuration.inSeconds + 86399) ~/ 86400).clamp(1, double.infinity).toInt()) ensures totalDays is always ≥ 1, and line 55 clamps daysLeft within valid range. This addresses the previous critical issue where zero totals caused NaN/Infinity errors in CircularCountdown.


71-92: Excellent fix! Hour scale rendering now handles zero/negative totals safely.

The ceiling division with clamping (line 73: ((totalDuration.inSeconds + 3599) ~/ 3600).clamp(1, double.infinity).toInt()) ensures totalHours is always ≥ 1, and line 74 clamps hoursLeft within valid range. This prevents the division-by-zero errors that plagued the previous implementation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b56449a and 845393d.

📒 Files selected for processing (3)
  • CLAUDE.md (2 hunks)
  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (4 hunks)
  • lib/features/order/screens/take_order_screen.dart (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/features/order/screens/take_order_screen.dart
🧰 Additional context used
📓 Path-based instructions (1)
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Store additional project guidance and documentation in docs/

Files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
🧠 Learnings (10)
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Document architectural changes in CLAUDE.md and related docs (NOSTR.md, README.md)

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • CLAUDE.md
  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-06-26T15:03:23.529Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 127
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:45-54
Timestamp: 2025-06-26T15:03:23.529Z
Learning: In AbstractMostroNotifier, state updates occur for all messages regardless of timestamp to hydrate the OrderNotifier from MostroStorage during sync, while handleEvent is only called for recent messages (within 60 seconds) to prevent re-triggering side effects like notifications and navigation for previously handled messages. This design prevents displaying stale notifications when the app is reopened or brought to the foreground.

Applied to files:

  • CLAUDE.md
  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/features/relays/relays_notifier.dart : Implement relay operations: syncWithMostroInstance, removeRelayWithBlacklist, addRelayWithSmartValidation, _handleMostroRelayListUpdate

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/main.dart : Configure timeago locales and app initialization in main.dart

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/**/*.dart : Target zero flutter analyze issues

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/core/mostro_fsm.dart : MostroFSM manages order state transitions (core/mostro_fsm.dart)

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-05-06T15:49:55.079Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:97-103
Timestamp: 2025-05-06T15:49:55.079Z
Learning: In the Mostro protocol, an order cannot be canceled unless it has a valid orderId, so null checks on orderId during cancel operations are unnecessary.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-06-04T19:35:20.209Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 110
File: test/notifiers/take_order_notifier_test.dart:72-74
Timestamp: 2025-06-04T19:35:20.209Z
Learning: MostroService methods like takeBuyOrder() and takeSellOrder() return Future<void> and trigger side effects through other mechanisms rather than direct return values. When testing these methods, focus on verifying method calls and testing state changes through the provider system rather than mocking return values.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
CLAUDE.md (1)

248-259: ✅ Well-documented Dynamic Countdown Timer System section.

The new section clearly explains the DynamicCountdownWidget, its location, data source, dual display modes, automatic transition point, localization approach, scope, and integration points. The use of method names (rather than line numbers) and implementation file paths makes this documentation resilient to refactors.

Verify that the localization key S.of(context)!.timeLeftLabel() is defined in all three ARB files (intl_en.arb, intl_es.arb, intl_it.arb) to ensure the documentation aligns with implementation.

docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (2)

354-372: Reject this review comment—it contradicts itself and the code already follows the recommended practice.

The current documentation (lines 354–372) already includes method names alongside the code snippets, exactly as recommended. The proposed diff removes these descriptive comments, which contradicts the stated guidance to "use method/class names alongside line numbers for resilience."

Additionally, the line references cited (477–480, 87–93, 862–1062) don't appear in the current documentation. The actual implementation confirms:

  • TakeOrderScreen: DynamicCountdownWidget usage at lines 483–486
  • TradeDetailScreen: _CountdownWidget usage at lines 87–90

No changes are needed.

Likely an incorrect or invalid review comment.


364-378: Clarify architectural relationship between _CountdownWidget and DynamicCountdownWidget.

These are distinct but related components:

  • _CountdownWidget (line 87 in trade_detail_screen.dart): Private screen-specific orchestrator that determines which countdown logic to use based on order status
  • DynamicCountdownWidget (lib/shared/widgets/dynamic_countdown_widget.dart): Shared public countdown display component for pending orders

The documentation shows code using _CountdownWidget but then describes DynamicCountdownWidget without clarifying that _CountdownWidget orchestrates and internally calls DynamicCountdownWidget. Update the documentation to explain this relationship explicitly so developers understand the architectural separation.

@Catrya Catrya marked this pull request as draft November 1, 2025 17:09
@Catrya Catrya marked this pull request as ready for review November 4, 2025 02:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (2)

313-341: Fix incomplete pseudocode (previously flagged).

The pseudocode still references undefined variables:

  • Line 323: now is used but not defined
  • Line 331: totalDays is used but not computed
  • Line 337: totalHours is used but not computed

This was already flagged in a previous review. Please either:

  1. Add the missing variable definitions to make it runnable code
  2. Clearly mark this as "conceptual pseudocode" with a note that it's simplified

364-372: Clarify component naming confusion (previously flagged).

Line 367 references _CountdownWidget in the TradeDetailScreen example, but this section is documenting DynamicCountdownWidget. This creates confusion:

  1. Are _CountdownWidget and DynamicCountdownWidget the same component with different names?
  2. Are they distinct components? If so, why is _CountdownWidget shown in a section about DynamicCountdownWidget?

Based on the scope section (lines 374-378) which states "DynamicCountdownWidget is specifically designed for orders in Status.pending," it appears there may be multiple countdown components. Please clarify the relationship and ensure examples use the correct component name.

🧹 Nitpick comments (1)
lib/shared/widgets/dynamic_countdown_widget.dart (1)

26-46: Clarify the rebuild/reactivity strategy for countdown updates.

The widget captures DateTime.now() once during build (line 28) but doesn't watch countdownTimeProvider or any periodic time stream. This means the countdown won't actually update every second unless the parent widget triggers rebuilds.

Based on the documentation (TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md lines 387-388), it appears the parent screens are responsible for watching countdownTimeProvider and triggering rebuilds. Consider adding a doc comment clarifying this requirement:

/// Note: This widget captures time at build and does not auto-rebuild.
/// Parent widgets should watch countdownTimeProvider to trigger periodic rebuilds.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 845393d and 5f4ccb6.

📒 Files selected for processing (6)
  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (4 hunks)
  • lib/data/models/nostr_event.dart (1 hunks)
  • lib/data/models/order.dart (1 hunks)
  • lib/features/order/screens/take_order_screen.dart (4 hunks)
  • lib/features/trades/screens/trade_detail_screen.dart (2 hunks)
  • lib/shared/widgets/dynamic_countdown_widget.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/data/models/order.dart
  • lib/features/order/screens/take_order_screen.dart
🧰 Additional context used
📓 Path-based instructions (5)
lib/shared/**

📄 CodeRabbit inference engine (AGENTS.md)

Put shared utilities in lib/shared/

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*.dart: Use Dart formatter defaults (two-space indentation, trailing commas) and run flutter format before committing
Resolve all Flutter analyzer warnings (flutter analyze must be clean)

**/*.dart: Remove unused imports and unused dependencies
Do not add // ignore: must_be_immutable to classes within generated files; rely on regeneration and existing file-level ignores

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/data/models/nostr_event.dart
lib/**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Name Riverpod providers as Provider or Notifier

lib/**/*.dart: Always use localized strings via S.of(context)!.keyName; avoid hardcoded user-facing text
Use clear, concise English for variable names, function names, and code comments; all comments must be in English
Target zero flutter analyze issues
Use latest Flutter/Dart APIs (e.g., Color.withValues() instead of withOpacity())
After async operations, check mounted before using BuildContext
Use const constructors where possible

Files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/data/models/nostr_event.dart
lib/data/**

📄 CodeRabbit inference engine (AGENTS.md)

Store persistence and API code under lib/data/

Files:

  • lib/data/models/nostr_event.dart
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Store additional project guidance and documentation in docs/

Files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
🧠 Learnings (6)
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/main.dart : Configure timeago locales and app initialization in main.dart

Applied to files:

  • lib/shared/widgets/dynamic_countdown_widget.dart
  • lib/data/models/nostr_event.dart
📚 Learning: 2025-10-22T12:29:26.971Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-22T12:29:26.971Z
Learning: Applies to lib/core/mostro_fsm.dart : MostroFSM manages order state transitions (core/mostro_fsm.dart)

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-06-26T15:03:23.529Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 127
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:45-54
Timestamp: 2025-06-26T15:03:23.529Z
Learning: In AbstractMostroNotifier, state updates occur for all messages regardless of timestamp to hydrate the OrderNotifier from MostroStorage during sync, while handleEvent is only called for recent messages (within 60 seconds) to prevent re-triggering side effects like notifications and navigation for previously handled messages. This design prevents displaying stale notifications when the app is reopened or brought to the foreground.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-05-06T15:49:55.079Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:97-103
Timestamp: 2025-05-06T15:49:55.079Z
Learning: In the Mostro protocol, an order cannot be canceled unless it has a valid orderId, so null checks on orderId during cancel operations are unnecessary.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-06-04T19:35:20.209Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 110
File: test/notifiers/take_order_notifier_test.dart:72-74
Timestamp: 2025-06-04T19:35:20.209Z
Learning: MostroService methods like takeBuyOrder() and takeSellOrder() return Future<void> and trigger side effects through other mechanisms rather than direct return values. When testing these methods, focus on verifying method calls and testing state changes through the provider system rather than mocking return values.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
lib/data/models/nostr_event.dart (1)

42-42: LGTM! Clean implementation following established patterns.

The expiresAt getter follows the same pattern as other tag accessors in the extension (like orderId, currency, etc.) and correctly retrieves the expires_at tag value.

lib/shared/widgets/dynamic_countdown_widget.dart (3)

6-24: Well-documented widget with clear scope and responsibilities.

The documentation clearly specifies that this widget is for pending orders only and describes the dual display modes. The constructor properly requires both expiration and createdAt timestamps for precise calculations.


52-70: Day scale logic correctly implements ceiling division and bounds checking.

The implementation properly addresses the previous concern about zero/negative totals:

  • Line 54: Ceiling division with .clamp(1, double.infinity) ensures totalDays >= 1
  • Line 55: .clamp(0, totalDays) prevents daysLeft from exceeding the total

The format string "14d 20h 06m" matches the PR objectives, and the code correctly uses localized strings via S.of(context)!.timeLeftLabel().


71-91: Hour scale logic is well-implemented with proper formatting.

The hour scale implementation mirrors the day scale's robust approach:

  • Line 73: Ceiling division ensures totalHours >= 1
  • Line 74: Clamping prevents overflow
  • Lines 78-79: Correctly formats as "HH:MM:SS" with zero-padding using padLeft(2, '0')

The consistent use of localized strings and the clean separation between day/hour modes makes this code maintainable.

docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (1)

374-378: Clear scope definition for the countdown widget.

The scope and limitations are well-articulated:

  • Clearly states DynamicCountdownWidget is for Status.pending orders only
  • Explains that waiting states use separate countdown logic based on expirationSeconds
  • Specifies the data source as the expires_at Nostr tag

This helps developers understand when to use this widget versus other countdown mechanisms.

@Catrya Catrya requested a review from grunch November 4, 2025 02:49
Copy link
Member

@grunch grunch left a comment

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 1d1596e into main Nov 13, 2025
2 checks passed
@grunch grunch deleted the fix_clock_pending_orders branch November 13, 2025 17:19
AndreaDiazCorreia pushed a commit that referenced this pull request Nov 15, 2025
#354)

* feat: improve countdown timer with order_expires_at and dynamic scaling in pending orders

  - Use exact timestamps from order_expires_at tag for precise calculations
  - Add dynamic day/hour scaling: 14d 20h 06m (>24h) or HH:MM:SS (≤24h)
  - Refactor: Create shared DynamicCountdownWidget to remove duplication
  - Apply consistent behavior across TakeOrderScreen and TradeDetailScreen

* fix: use int.tryParse for order_expires_at parsing and update documentation

* fix: safe parsing for order countdown timestamps

* fix: prevent countdown crashes with zero totals from short orders

* update documentation

* coderabbit suggestion

* use expires_at
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.

3 participants