Skip to content

feat: add community discovery and node selector on first launch#564

Merged
grunch merged 20 commits intomainfrom
feat/community-selection-node
Apr 8, 2026
Merged

feat: add community discovery and node selector on first launch#564
grunch merged 20 commits intomainfrom
feat/community-selection-node

Conversation

@grunch
Copy link
Copy Markdown
Member

@grunch grunch commented Apr 6, 2026

Add a community selector screen shown after the walkthrough for new
users, allowing them to choose a trusted Mostro community before
reaching the home screen. Fetches kind 0 and kind 38385 metadata
from relay.mostro.network via standalone WebSocket connection.

Includes trusted communities for Cuba, Spain, Colombia and Bolivia,
search filtering, custom node support, loading skeleton, error
handling, and full i18n (en/es/it). Existing users are not
interrupted thanks to backward-compatible auto-migration.

Summary by CodeRabbit

  • New Features

    • First-run community selector: browse, search, confirm/skip, use custom node, and add-new-node flows.
    • Community cards with avatar, name/region, about, fees, currencies, amount ranges, and social links.
    • Community list enriched by remote metadata.
  • Behavior

    • New onboarding route directs new users to community selection (walkthrough now proceeds to selector); choice is persisted.
  • Documentation

    • Added Community Discovery guide and multi-language UI strings.
  • Chores

    • Simplified provider wiring and startup routing refresh.

grunch added 2 commits April 6, 2026 17:43
Add a community selector screen shown after the walkthrough for new
users, allowing them to choose a trusted Mostro community before
reaching the home screen. Fetches kind 0 and kind 38385 metadata
from relay.mostro.network via standalone WebSocket connection.

Includes trusted communities for Cuba, Spain, Colombia and Bolivia,
search filtering, custom node support, loading skeleton, error
handling, and full i18n (en/es/it). Existing users are not
interrupted thanks to backward-compatible auto-migration.
- Remove duplicate skipForNow keys from all ARB files
- Fix race condition: treat loading state as unselected in redirect
- Add mounted checks before state mutations in CommunitySelectedNotifier
- Remove manual string truncation, rely on TextOverflow.ellipsis
- Add try-catch to launchUrl for social links
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a first-run community/node selector: compile-time trusted community configs, a Community model, a WebSocket-based CommunityRepository to fetch Nostr metadata, providers and persistence for selection, UI widgets/screens, GoRouter redirect integration, and removal of Riverpod codegen in several providers.

Changes

Cohort / File(s) Summary
Routing & app init
lib/core/app_routes.dart, lib/core/app.dart, lib/features/walkthrough/screens/walkthrough_screen.dart
Router redirect updated to require community selection after first-run; added /community_selector route; app listens to communitySelectedProvider; walkthrough now routes to /community_selector.
Community configuration
lib/core/config/communities.dart, lib/core/config.dart
Adds trustedCommunities and defaultMostroPubkey; Config.trustedMostroNodes computed from trustedCommunities; mostroPubKey override default adjusted.
Domain & repository
lib/features/community/community.dart, lib/data/repositories/community_repository.dart
New Community model and CommunityRepository using WebSocket REQ subscriptions (kinds 0 and 38385), event verification, tag extraction, and metadata aggregation per pubkey.
Providers & storage
lib/features/community/providers/community_selector_provider.dart, lib/data/models/enums/storage_keys.dart, lib/shared/providers/mostro_service_provider.dart, lib/services/event_bus.dart, lib/features/order/providers/order_notifier_provider.dart
Adds communitySelectedProvider, communityListProvider, communityRepositoryProvider; adds SharedPreferencesKeys.communitySelected; replaces several generated Riverpod providers with manual providers/state notifiers.
Selector UI & widgets
lib/features/community/screens/community_selector_screen.dart, lib/features/community/widgets/community_card.dart
New CommunitySelectorScreen with search/filter, loading/error/empty states, confirm/skip/custom-node flows; new CommunityCard widget with selection visuals and social links handling.
Localization
lib/l10n/intl_en.arb, lib/l10n/intl_es.arb, lib/l10n/intl_it.arb, lib/l10n/intl_de.arb, lib/l10n/intl_fr.arb
Adds community selector i18n keys (title, hints, fee/range labels, custom node, errors, empty results, formatted sats, currency label) across locales.
Service changes & tests
lib/services/mostro_service.dart, test/data/repositories/community_signature_test.dart, test/features/mostro/mostro_nodes_notifier_test.dart
MostroService.init() now cancels existing orders subscription before creating a new one; adds unit tests for event signature verification; tests updated to use Config.trustedMostroNodes.
Docs & deps
docs/COMMUNITY_DISCOVERY.md, pubspec.yaml
Adds community discovery documentation; removes riverpod_annotation and riverpod_generator from pubspec.
UI theming minor
lib/core/app_theme.dart
Sets AppBar systemOverlayStyle: SystemUiOverlayStyle.light.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as CommunitySelectorScreen
    participant Provider as communityListProvider
    participant Repo as CommunityRepository
    participant Relay as Nostr Relay (wss://...)
    participant Prefs as SharedPreferences
    participant Router

    User->>UI: open /community_selector
    UI->>Provider: request community list
    Provider->>Provider: build baseline from trustedCommunities
    Provider->>Repo: fetchCommunityMetadata(pubkeys)
    Repo->>Relay: open WebSocket, send REQ (kinds 0 & 38385)
    Relay-->>Repo: EVENT / EOSE messages
    Repo-->>Provider: Map<pubkey, CommunityMetadata>
    Provider-->>UI: enriched List<Community>
    User->>UI: select pubkey / skip / add custom node
    UI->>Prefs: persist community_selected = true
    UI->>Router: navigate to home (`context.go('/')`)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • AndreaDiazCorreia
  • BraCR10
  • Catrya

Poem

🐰
I hop through relays and config lines,
I fetch the names where community shines,
A picker waits once the tour is done,
Choose a node, skip, or add your own—then run! ✨

🚥 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 accurately summarizes the primary feature: adding a community discovery and node selector presented to users on their first launch.
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 feat/community-selection-node

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
Copy Markdown
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 (4)
lib/core/config.dart (1)

21-40: Avoid keeping the trusted community registry in two places.

These pubkeys are now maintained here and again in lib/core/config/communities.dart:30-71. If the two lists drift, the community selector can stop resolving the expected trusted node. Prefer a single shared source of truth and derive the trusted-node list from it.

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

In `@lib/core/config.dart` around lines 21 - 40, The file currently duplicates the
trusted community registry as inline maps containing 'pubkey' and 'name' (the
list shown) which also exists elsewhere causing drift; consolidate by extracting
that canonical list into a single exported constant (the authoritative array of
{'pubkey','name'} maps), remove the duplicate entries from this file, and update
the consumer that builds the trusted-node list (the community selector) to
import and derive its trusted-node list from that single exported constant so
all code references the same source of truth.
lib/features/community/providers/community_selector_provider.dart (1)

15-20: Avoid dynamic type for _prefs – use SharedPreferences.

The dynamic type loses compile-time safety. If sharedPreferencesProvider returns a different type or the API changes, errors would only surface at runtime.

♻️ Suggested fix
+import 'package:shared_preferences/shared_preferences.dart';
+
 class CommunitySelectedNotifier extends StateNotifier<AsyncValue<bool>> {
-  final dynamic _prefs;
+  final SharedPreferences _prefs;
 
   CommunitySelectedNotifier(this._prefs) : super(const AsyncValue.loading()) {
     _init();
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/community/providers/community_selector_provider.dart` around
lines 15 - 20, The field `_prefs` in CommunitySelectedNotifier is declared as
dynamic which loses type safety; change its type to SharedPreferences (from
package:shared_preferences/shared_preferences.dart), update the constructor and
any usages inside CommunitySelectedNotifier (including the _init method) to
expect a SharedPreferences instance provided by sharedPreferencesProvider, and
add the appropriate import for SharedPreferences so the compiler enforces
correct API usage.
lib/features/community/widgets/community_card.dart (1)

195-209: Consider adding a loading placeholder for network images.

The avatar implementation correctly falls back to NymAvatar on error, but there's no visual feedback during image loading. This could cause a brief visual jump when the image loads.

💡 Optional: Add loadingBuilder for smoother UX
   Widget _buildAvatar() {
     if (community.picture != null) {
       return ClipOval(
         child: Image.network(
           community.picture!,
           width: 44,
           height: 44,
           fit: BoxFit.cover,
+          loadingBuilder: (_, child, loadingProgress) {
+            if (loadingProgress == null) return child;
+            return Container(
+              width: 44,
+              height: 44,
+              decoration: BoxDecoration(
+                color: Colors.white.withValues(alpha: 0.1),
+                shape: BoxShape.circle,
+              ),
+            );
+          },
           errorBuilder: (_, __, ___) =>
               NymAvatar(pubkeyHex: community.pubkey, size: 44),
         ),
       );
     }
     return NymAvatar(pubkeyHex: community.pubkey, size: 44);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/community/widgets/community_card.dart` around lines 195 - 209,
The avatar currently uses Image.network inside _buildAvatar with only an
errorBuilder fallback, so add a loadingBuilder to Image.network to show a
placeholder while the network image is loading (for example reuse
NymAvatar(pubkeyHex: community.pubkey, size: 44) or a SizedBox with spinner) to
avoid visual jumps; locate the Image.network in _buildAvatar and add
loadingBuilder that returns the placeholder while loadingProgress != null and
the actual image once loadingProgress is null, preserving the existing
errorBuilder fallback.
lib/features/community/community.dart (1)

48-71: copyWith cannot reset nullable fields to null.

The current implementation uses ?? for all nullable fields, which means once a field has a non-null value, it cannot be reset to null via copyWith. This is likely intentional for this use case (metadata only gets enriched, never cleared), but worth noting for future maintainability.

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

In `@lib/features/community/community.dart` around lines 48 - 71,
Community.copyWith currently uses the "??" fallback so callers cannot explicitly
reset nullable metadata fields to null; change the API to distinguish "not
provided" from "explicitly null" (e.g. introduce a private sentinel like const
_undefined = Object() and change parameters to Object? name = _undefined,
Object? about = _undefined, Object? picture = _undefined, Object? currencies =
_undefined, etc.), then inside copyWith check for the sentinel to decide whether
to keep the existing value, set to the provided non-null value, or set to null
(e.g. name == _undefined ? this.name : name as String?); apply this pattern for
name, about, picture, currencies and any other nullable fields so callers can
explicitly pass null to clear a field.
🤖 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/community/community_fetcher.dart`:
- Around line 7-15: CommunityFetcher (class) and its static fetch method, which
opens the WebSocket to relay.mostro.network and returns Map<String,
CommunityMetadata>, must be moved out of the feature layer and wrapped behind a
repository in lib/data; create a new CommunityRepository with a method like
fetchCommunityMetadata(pubkeys) that encapsulates the WebSocket logic
(previously in CommunityFetcher) and returns the same Map<String,
CommunityMetadata>, update the feature code that calls CommunityFetcher (the
provider that invokes fetch) to depend on and call
CommunityRepository.fetchCommunityMetadata instead, and ensure dependency
injection or provider wiring supplies the repository to the feature rather than
referencing the transport directly.
- Around line 84-100: The code swallows fetch failures (timeout/socket errors)
by only logging and returning results, preventing communityListProvider from
seeing errors; change the timeout and catch handling so failures propagate:
remove or change the onTimeout callback on completer.future.timeout(_timeout,
...) so it throws a TimeoutException (or simply let timeout throw) instead of
just logging, and in the catch block (where logger.e('Failed to fetch community
data: $e')) rethrow the caught error (or throw a new exception) after logging;
keep the finally block to still await ws?.close() and cancel subscription as
currently done so resources are cleaned up while errors propagate to
communityListProvider.

In `@lib/features/community/screens/community_selector_screen.dart`:
- Around line 351-358: The helper _ensureNodeExists currently calls
notifier.addCustomNode(pubkey) without awaiting, which can race with subsequent
selectNode; change _ensureNodeExists to be async and await
notifier.addCustomNode(pubkey) (MostroNodesNotifier.addCustomNode) so the node
is persisted and state updated before any following selectNode call, and update
any callers of _ensureNodeExists to await its Future so they don't proceed
before the node exists (see selectNode usage).
- Around line 315-331: The fragile length-delta detection in _onUseCustomNode
should be replaced with a deterministic set-diff based on node identity: before
calling AddCustomNodeDialog.show(context, ref) snapshot the current pubkeys from
ref.read(mostroNodesProvider), then after the dialog read the provider again and
compute the difference (e.g., newPubkeys =
afterPubkeys.difference(beforePubkeys)); if exactly one new pubkey exists use
that to call _selectAndProceed(lastNewPubkey) (or if multiple were added choose
the intended one or handle accordingly); keep the existing mounted checks and
setState(_isSelecting) wrapping the await _selectAndProceed call.

---

Nitpick comments:
In `@lib/core/config.dart`:
- Around line 21-40: The file currently duplicates the trusted community
registry as inline maps containing 'pubkey' and 'name' (the list shown) which
also exists elsewhere causing drift; consolidate by extracting that canonical
list into a single exported constant (the authoritative array of
{'pubkey','name'} maps), remove the duplicate entries from this file, and update
the consumer that builds the trusted-node list (the community selector) to
import and derive its trusted-node list from that single exported constant so
all code references the same source of truth.

In `@lib/features/community/community.dart`:
- Around line 48-71: Community.copyWith currently uses the "??" fallback so
callers cannot explicitly reset nullable metadata fields to null; change the API
to distinguish "not provided" from "explicitly null" (e.g. introduce a private
sentinel like const _undefined = Object() and change parameters to Object? name
= _undefined, Object? about = _undefined, Object? picture = _undefined, Object?
currencies = _undefined, etc.), then inside copyWith check for the sentinel to
decide whether to keep the existing value, set to the provided non-null value,
or set to null (e.g. name == _undefined ? this.name : name as String?); apply
this pattern for name, about, picture, currencies and any other nullable fields
so callers can explicitly pass null to clear a field.

In `@lib/features/community/providers/community_selector_provider.dart`:
- Around line 15-20: The field `_prefs` in CommunitySelectedNotifier is declared
as dynamic which loses type safety; change its type to SharedPreferences (from
package:shared_preferences/shared_preferences.dart), update the constructor and
any usages inside CommunitySelectedNotifier (including the _init method) to
expect a SharedPreferences instance provided by sharedPreferencesProvider, and
add the appropriate import for SharedPreferences so the compiler enforces
correct API usage.

In `@lib/features/community/widgets/community_card.dart`:
- Around line 195-209: The avatar currently uses Image.network inside
_buildAvatar with only an errorBuilder fallback, so add a loadingBuilder to
Image.network to show a placeholder while the network image is loading (for
example reuse NymAvatar(pubkeyHex: community.pubkey, size: 44) or a SizedBox
with spinner) to avoid visual jumps; locate the Image.network in _buildAvatar
and add loadingBuilder that returns the placeholder while loadingProgress !=
null and the actual image once loadingProgress is null, preserving the existing
errorBuilder fallback.
🪄 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: 03aa088d-667c-4f4d-b67b-0716ea0529ae

📥 Commits

Reviewing files that changed from the base of the PR and between fca09d4 and 535e9c7.

📒 Files selected for processing (13)
  • lib/core/app_routes.dart
  • lib/core/config.dart
  • lib/core/config/communities.dart
  • lib/data/models/enums/storage_keys.dart
  • lib/features/community/community.dart
  • lib/features/community/community_fetcher.dart
  • lib/features/community/providers/community_selector_provider.dart
  • lib/features/community/screens/community_selector_screen.dart
  • lib/features/community/widgets/community_card.dart
  • lib/features/walkthrough/screens/walkthrough_screen.dart
  • lib/l10n/intl_en.arb
  • lib/l10n/intl_es.arb
  • lib/l10n/intl_it.arb

- Move WebSocket fetcher to CommunityRepository in data/repositories
- Propagate fetch errors so communityListProvider shows error state
- Fix dynamic _prefs type to SharedPreferencesAsync for type safety
- Await _ensureNodeExists to prevent race with selectNode
- Use set-diff for robust custom node detection after dialog
- Derive Config.trustedMostroNodes from trustedCommunities (single source)
- Add loadingBuilder to avatar for smooth image loading
Copy link
Copy Markdown
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.

🧹 Nitpick comments (3)
lib/core/config.dart (1)

14-15: Consider using defaultMostroPubkey from communities.dart instead of maintaining a duplicate constant.

_defaultMostroPubKey duplicates defaultMostroPubkey already defined in communities.dart. Since the PR aims to establish trustedCommunities as the single source of truth, this constant could be removed in favor of importing the existing one.

♻️ Proposed refactor
 import 'package:flutter/foundation.dart';
 import 'package:mostro_mobile/core/config/communities.dart';

 class Config {
   // Nostr configuration
   static const List<String> nostrRelays = [
     'wss://relay.mostro.network',
     //'ws://127.0.0.1:7000',
     //'ws://192.168.1.103:7000',
     //'ws://10.0.2.2:7000', // mobile emulator
   ];

-  // Trusted Mostro nodes registry
-  static const String _defaultMostroPubKey =
-      '82fa8cb978b43c79b2156585bac2c011176a21d2aead6d9f7c575c005be88390';
-
   // Derived from trustedCommunities to maintain single source of truth
   static final List<Map<String, String>> trustedMostroNodes =
       trustedCommunities
           .map((c) => {
                 'pubkey': c.pubkey,
                 'name': c.region,
               })
           .toList();

   // Mostro hexkey (backward compatible, overridable via env variable)
   static const String mostroPubKey = String.fromEnvironment(
     'MOSTRO_PUB_KEY',
-    defaultValue: _defaultMostroPubKey,
+    defaultValue: defaultMostroPubkey,
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/core/config.dart` around lines 14 - 15, Remove the duplicated constant
_defaultMostroPubKey from the config class and import and use the existing
defaultMostroPubkey from communities.dart instead; update any references in this
file (e.g., usage within the Config class and any places that reference
_defaultMostroPubKey) to reference defaultMostroPubkey and ensure
trustedCommunities remains the single source of truth for community keys; add
the import for the symbol and delete the local constant to avoid duplication.
lib/data/repositories/community_repository.dart (2)

53-88: Consider handling WebSocket closure before EOSE arrival.

The onDone callback at line 84 completes the completer without error when the WebSocket closes. If the relay closes the connection before sending both EOSE messages, results may contain incomplete data (only partial metadata fetched). This is acceptable for a "best effort" fetch, but the caller should be aware that the returned map may have empty CommunityMetadata entries.

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

In `@lib/data/repositories/community_repository.dart` around lines 53 - 88, The
WebSocket may close before both EOSE messages arrive; update the ws.listen
onDone handler to detect if eoseCount < 2 and, when that happens, complete the
completer with an error (e.g., completeError(StateError('WebSocket closed before
EOSE'))) so callers receive an explicit failure instead of silently returning
potentially incomplete results; reference eoseCount, completer, onDone,
ws.listen, _processEvent and _timeout when making this change so the logic
around waiting for EOSEs and timeout remains consistent.

106-136: Consider adding signature verification for kind 0 events to align with established patterns.

The _processEvent method correctly deduplicates events by keeping the latest created_at and sanitizes image URLs to HTTPS. However, it does not verify event signatures. The MostroNodesNotifier uses event.isVerified() for similar kind 0 metadata processing and logs a warning when verification fails (with fallback logic due to a dart_nostr limitation).

For a community selector that fetches from the trusted relay wss://relay.mostro.network on first launch only, this omission may be acceptable, but applying the same verification pattern used in MostroNodesNotifier would provide consistency across the codebase.

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

In `@lib/data/repositories/community_repository.dart` around lines 106 - 136, In
_processEvent, add signature verification for kind 0 events similar to
MostroNodesNotifier: when kind == 0 call event.isVerified() (or the equivalent
verification method on the event object) and if it returns false log a warning
via logger.w('Failed to verify kind 0 event for $pubkey') then continue with the
existing fallback logic (i.e., still attempt to parse and store the content but
ensure the warning is emitted), updating references to CommunityMetadata (kind0,
kind0CreatedAt) only after handling verification; this keeps behavior consistent
while preserving the current fallback semantics used elsewhere.
🤖 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/core/config.dart`:
- Around line 14-15: Remove the duplicated constant _defaultMostroPubKey from
the config class and import and use the existing defaultMostroPubkey from
communities.dart instead; update any references in this file (e.g., usage within
the Config class and any places that reference _defaultMostroPubKey) to
reference defaultMostroPubkey and ensure trustedCommunities remains the single
source of truth for community keys; add the import for the symbol and delete the
local constant to avoid duplication.

In `@lib/data/repositories/community_repository.dart`:
- Around line 53-88: The WebSocket may close before both EOSE messages arrive;
update the ws.listen onDone handler to detect if eoseCount < 2 and, when that
happens, complete the completer with an error (e.g.,
completeError(StateError('WebSocket closed before EOSE'))) so callers receive an
explicit failure instead of silently returning potentially incomplete results;
reference eoseCount, completer, onDone, ws.listen, _processEvent and _timeout
when making this change so the logic around waiting for EOSEs and timeout
remains consistent.
- Around line 106-136: In _processEvent, add signature verification for kind 0
events similar to MostroNodesNotifier: when kind == 0 call event.isVerified()
(or the equivalent verification method on the event object) and if it returns
false log a warning via logger.w('Failed to verify kind 0 event for $pubkey')
then continue with the existing fallback logic (i.e., still attempt to parse and
store the content but ensure the warning is emitted), updating references to
CommunityMetadata (kind0, kind0CreatedAt) only after handling verification; this
keeps behavior consistent while preserving the current fallback semantics used
elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 638597fd-2bd1-45f2-83ae-78b618b4f15d

📥 Commits

Reviewing files that changed from the base of the PR and between 535e9c7 and 822292e.

📒 Files selected for processing (5)
  • lib/core/config.dart
  • lib/data/repositories/community_repository.dart
  • lib/features/community/providers/community_selector_provider.dart
  • lib/features/community/screens/community_selector_screen.dart
  • lib/features/community/widgets/community_card.dart

grunch added 3 commits April 6, 2026 18:42
Replace @riverpod annotations with manual providers in the 3 files
that used code generation (event_bus, mostro_service_provider,
order_notifier_provider). This eliminates the riverpod_generator
and riverpod_annotation dependencies whose analyzer requirement
conflicted with the test package on Flutter 3.41.6.
Copy link
Copy Markdown
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: 2

🤖 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/services/event_bus.dart`:
- Around line 15-19: The AutoDisposeProvider `eventBusProvider` currently
creates an `EventBus` but leaves its `StreamController` open because the
`ref.onDispose(bus.dispose)` call is commented out; restore proper disposal by
registering the provider's cleanup callback (e.g., call `ref.onDispose` with
`bus.dispose` or a closure that calls `bus.dispose()`) so `EventBus.dispose()`
runs when the provider is disposed, thus preventing the StreamController leak.

In `@lib/shared/providers/mostro_service_provider.dart`:
- Around line 12-25: The provider calls MostroService.init() and
LifecycleManager.onResumed() may call it again, which leaks subscriptions
because MostroService.init() reassigns _ordersSubscription without cancelling;
make init() idempotent by checking and cancelling any existing
_ordersSubscription before creating a new subscription (cancel and null-check
_ordersSubscription inside MostroService.init()), or alternatively remove the
extra call to MostroService.init() from LifecycleManager.onResumed(); ensure you
reference the _ordersSubscription field and the MostroService.init() method (and
keep MostroService.dispose() unchanged to still cancel on provider disposal).
🪄 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: 500e55a3-eeb6-4322-9f46-c8a5dd806021

📥 Commits

Reviewing files that changed from the base of the PR and between 822292e and c0b3f83.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • lib/core/config.dart
  • lib/features/order/providers/order_notifier_provider.dart
  • lib/services/event_bus.dart
  • lib/shared/providers/mostro_service_provider.dart
  • pubspec.yaml
💤 Files with no reviewable changes (1)
  • pubspec.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/core/config.dart

grunch added 11 commits April 6, 2026 19:29
…nodes without trade info

Add NIP-01 signature verification (event ID + Schnorr) before processing
kind 0 and kind 38385 events to prevent relay-injected spoofed metadata.
Filter out community nodes that lack valid kind 38385 trade info from
the selector.
Replace single hardcoded relay with Config.nostrRelays list, trying each
relay in order until one succeeds. Prevents community selector from
breaking when the primary relay is down.
…etch

Return partial results when timeout fires but events were already
received, instead of discarding them. Only rethrow to try the next
relay when zero events arrived.
Add SystemUiOverlayStyle.light to AppBarTheme for screens with AppBar,
and wrap CommunitySelectorScreen with AnnotatedRegion since it lacks an
AppBar.
Fix race condition where the router redirect evaluated
communitySelectedProvider while still loading from SharedPreferences,
causing the community selector to show on every launch. Skip redirect
during loading and refresh the router when the provider resolves.
so the StreamController is properly closed when the provider is disposed.
Copy link
Copy Markdown
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)
test/data/repositories/community_signature_test.dart (1)

8-33: Avoid re-implementing the verifier inside the test helper.

verifyRawEvent() duplicates the same NIP-01 logic as CommunityRepository, so both copies can drift together and still leave the suite green. Extracting the verifier into a shared utility, or driving CommunityRepository through a fake relay payload, would give this test real regression value.

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

In `@test/data/repositories/community_signature_test.dart` around lines 8 - 33,
The test helper verifyRawEvent duplicates CommunityRepository's NIP-01 logic;
remove this re-implementation and instead call the single source-of-truth
verifier or exercise CommunityRepository directly: replace verifyRawEvent usages
with a call to the shared verifier (e.g., NostrKeyPairs.verifyEvent or
CommunityRepository.verifyEvent if it exists) or update the test to construct a
fake relay payload and pass it through CommunityRepository processing so the
repository's actual verification code is exercised; ensure references to
NostrKeyPairs.verify, CommunityRepository, and the test helper verifyRawEvent
are updated/removed accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/COMMUNITY_DISCOVERY.md`:
- Around line 16-18: The fenced ASCII flow blocks like the one containing "App
install -> Walkthrough (complete or skip) -> Community Selector -> Home" in
COMMUNITY_DISCOVERY.md are missing language tags and trigger markdownlint MD040;
update each of those fenced code blocks (including the blocks around lines with
the shown flow and the other ranges noted: 22-24, 28-30, 50-67, 82-105, 162-173,
236-255) by adding a language specifier (use "text") after the opening triple
backticks so they read ```text and stop failing the linter.

In `@lib/data/repositories/community_repository.dart`:
- Around line 41-44: The relay fetch logic is marking success too early: update
_fetchFromRelay() so it does not set hasEvents (or mark a pubkey as served) on
receipt of any EVENT — only set/apply results for a pubkey after _verifyEvent()
returns true and the verified event is applied; likewise change the
onDone/stream completion logic so the fetch is not considered complete when the
socket closes or onDone fires unless both subscriptions reached EOSE or at least
one verified event was applied; if no verified events were applied, have
_fetchFromRelay() signal failure (e.g., return null/throw) so
fetchCommunityMetadata() will continue to the next relay instead of returning
the pre-seeded empty map.

In `@lib/features/community/screens/community_selector_screen.dart`:
- Around line 297-360: The onboarding selection flow currently allows exceptions
from _selectAndProceed and _ensureNodeExists to bubble up with no user feedback;
update _selectAndProceed to wrap the sequence (ensure node exists,
nodesNotifier.selectNode, and communitySelectedProvider.markCommunitySelected)
in a try/catch that logs the error (using your app logger) and surfaces a
localized error to the user (e.g., ScaffoldMessenger.showSnackBar or a dialog
via context) before returning, ensuring buttons are re-enabled; also ensure
_ensureNodeExists persists the custom node before any in-memory selection (use
notifier.addCustomNode to persist and await it, then proceed) and surface/log
any errors there instead of throwing.

In `@lib/features/community/widgets/community_card.dart`:
- Around line 234-245: The current _formatSats builds strings with hard-coded
"K/M" and passes the unit through S.of(context)!.communityFormatSats; change it
to use locale-aware NumberFormat for the numeric part and append the literal
"sats" (not localized). Specifically, in _formatSats use
Localizations.localeOf(context) to get the locale, format amounts <1000 with
NumberFormat.decimalPattern(locale).format(amount) and format >=1000 with
NumberFormat.compact(locale).format(amount), then return a plain string like
"<formatted> sats" (remove calls to S.of(context)!.communityFormatSats and
ensure the unit is the hardcoded "sats").

---

Nitpick comments:
In `@test/data/repositories/community_signature_test.dart`:
- Around line 8-33: The test helper verifyRawEvent duplicates
CommunityRepository's NIP-01 logic; remove this re-implementation and instead
call the single source-of-truth verifier or exercise CommunityRepository
directly: replace verifyRawEvent usages with a call to the shared verifier
(e.g., NostrKeyPairs.verifyEvent or CommunityRepository.verifyEvent if it
exists) or update the test to construct a fake relay payload and pass it through
CommunityRepository processing so the repository's actual verification code is
exercised; ensure references to NostrKeyPairs.verify, CommunityRepository, and
the test helper verifyRawEvent are updated/removed accordingly.
🪄 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: 9d103528-fb2b-45e3-84c6-b0714be087c6

📥 Commits

Reviewing files that changed from the base of the PR and between 9d5fcb5 and 080ce4b.

📒 Files selected for processing (16)
  • docs/COMMUNITY_DISCOVERY.md
  • lib/core/app.dart
  • lib/core/app_routes.dart
  • lib/core/app_theme.dart
  • lib/data/repositories/community_repository.dart
  • lib/features/community/community.dart
  • lib/features/community/providers/community_selector_provider.dart
  • lib/features/community/screens/community_selector_screen.dart
  • lib/features/community/widgets/community_card.dart
  • lib/l10n/intl_de.arb
  • lib/l10n/intl_en.arb
  • lib/l10n/intl_es.arb
  • lib/l10n/intl_fr.arb
  • lib/l10n/intl_it.arb
  • test/data/repositories/community_signature_test.dart
  • test/features/mostro/mostro_nodes_notifier_test.dart
✅ Files skipped from review due to trivial changes (2)
  • test/features/mostro/mostro_nodes_notifier_test.dart
  • lib/core/app_theme.dart
🚧 Files skipped from review as they are similar to previous changes (6)
  • lib/l10n/intl_es.arb
  • lib/l10n/intl_en.arb
  • lib/l10n/intl_it.arb
  • lib/features/community/community.dart
  • lib/features/community/providers/community_selector_provider.dart
  • lib/core/app_routes.dart

…, and verification tracking

- Add error handling in _selectAndProceed with user-facing SnackBar
- Track hasEvents only after verified events are applied, so unverified
  relays fall through to the next relay instead of returning empty results
- Use locale-aware NumberFormat.compact for sats formatting in cards
- Add language tags to fenced code blocks in COMMUNITY_DISCOVERY.md
- Update doc to match current redirect and relay fallback behavior
- Remove duplicated test helper in favor of CommunityRepository.verifyEvent
Catrya
Catrya previously approved these changes Apr 7, 2026
Copy link
Copy Markdown
Member

@Catrya Catrya 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
Copy link
Copy Markdown
Member Author

grunch commented Apr 7, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

✅ 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.

Copy link
Copy Markdown
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

🤖 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/repositories/community_repository.dart`:
- Around line 38-139: The _fetchFromRelay function currently returns the
pre-seeded empty results when the WebSocket closes early (onDone) without any
verified events, preventing failover; update the logic so that if the connection
finishes (onDone or completer completion) and hasEvents is false (and eoseCount
< 2), the function throws an error instead of returning results: specifically,
in the ws.listen onDone handler (or immediately after awaiting
completer.future.timeout) check if (!hasEvents) and complete the completer with
an error (or throw) so the outer catch path logs and rethrows; reference the
_fetchFromRelay function, the completer, onDone, hasEvents, and
eoseCount/subIdKind* variables when applying this change.
🪄 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: 64ccb5d3-d262-46fb-a2d7-a654e8abc7ed

📥 Commits

Reviewing files that changed from the base of the PR and between 9d5fcb5 and 47cf624.

📒 Files selected for processing (17)
  • docs/COMMUNITY_DISCOVERY.md
  • lib/core/app.dart
  • lib/core/app_routes.dart
  • lib/core/app_theme.dart
  • lib/data/repositories/community_repository.dart
  • lib/features/community/community.dart
  • lib/features/community/providers/community_selector_provider.dart
  • lib/features/community/screens/community_selector_screen.dart
  • lib/features/community/widgets/community_card.dart
  • lib/l10n/intl_de.arb
  • lib/l10n/intl_en.arb
  • lib/l10n/intl_es.arb
  • lib/l10n/intl_fr.arb
  • lib/l10n/intl_it.arb
  • lib/services/event_bus.dart
  • test/data/repositories/community_signature_test.dart
  • test/features/mostro/mostro_nodes_notifier_test.dart
✅ Files skipped from review due to trivial changes (6)
  • test/features/mostro/mostro_nodes_notifier_test.dart
  • lib/core/app_theme.dart
  • lib/l10n/intl_es.arb
  • lib/l10n/intl_it.arb
  • test/data/repositories/community_signature_test.dart
  • docs/COMMUNITY_DISCOVERY.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • lib/core/app.dart
  • lib/services/event_bus.dart
  • lib/l10n/intl_de.arb
  • lib/l10n/intl_en.arb
  • lib/features/community/community.dart

Copy link
Copy Markdown
Member

@AndreaDiazCorreia AndreaDiazCorreia left a comment

Choose a reason for hiding this comment

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

tACK, but there are still some comments pending from CodeRabbit. Also, I’m not sure if it’s confusing to call them “communities” in the onboarding and “nodes” in settings. We should decide which term to use, or clarify it as “Community nodes” so it’s clear what we mean when we say “nodes.”

MostronatorCoder and others added 2 commits April 8, 2026 12:45
- Add disclaimer modal to community selector screen shown on first load
- Add disclaimer warning below Mostro node selector in settings
- Add i18n strings for disclaimer (EN, ES, FR, DE, IT)
- Disclaimer: Mostro team not responsible for node operator actions
…vents

The onDone handler now completes with an error when the connection closes
early without any verified events and before both EOSEs arrived, allowing
fetchCommunityMetadata to try the next relay instead of returning empty
results.
@grunch grunch merged commit 728abfe into main Apr 8, 2026
2 checks passed
@grunch grunch deleted the feat/community-selection-node branch April 8, 2026 13:32
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