-
Notifications
You must be signed in to change notification settings - Fork 13
Change bridge to match new methods #530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughMigrates from account-centered to pubkey-centered identity, replaces Contacts with Follows, introduces ApiError and Flutter-friendly Rust bridge types (Account/Group/Message/Relay/User/Welcome/FlutterMetadata), rewrites many providers/UI to new types, adds two-step Whitenoise config/init, and regenerates FRB bindings. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Flutter as Flutter App
participant FRB as FRB Bridge
participant Rust as Rust API
User->>Flutter: Launch app
Flutter->>FRB: createWhitenoiseConfig(dataDir, logsDir)
FRB->>Rust: create_whitenoise_config
Rust-->>FRB: WhitenoiseConfig
Flutter->>FRB: initializeWhitenoise(config)
FRB->>Rust: initialize_whitenoise(config)
Rust-->>FRB: Ok
FRB-->>Flutter: Init complete
note over Flutter: Providers start: activePubkey -> activeAccount (server fetch)
sequenceDiagram
autonumber
actor User
participant UI as App UI
participant ActivePubkey as activePubkeyProvider
participant ActiveAccount as activeAccountProvider
participant AccountsAPI as WnAccountsApi (bridge)
User->>UI: Log in / switch account
UI->>ActivePubkey: setActivePubkey(pubkey)
ActivePubkey-->>UI: pubkey state
UI->>ActiveAccount: build()/reload
ActiveAccount->>AccountsAPI: getAccount(pubkey) & getAccountMetadata(pubkey)
AccountsAPI-->>ActiveAccount: Account + FlutterMetadata
ActiveAccount-->>UI: ActiveAccountState
UI->>ActiveAccount: uploadProfilePicture(file) / updateMetadata(...)
ActiveAccount->>AccountsAPI: uploadAccountProfilePicture / updateAccountMetadata
AccountsAPI-->>ActiveAccount: pictureUrl / Ok
ActiveAccount-->>UI: invalidate -> reload state
Estimated code review effort🎯 5 (Critical) | ⏱️ ~150 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
8f69e12 to
d0aa9cc
Compare
… group_type access
d037f37 to
4bd13b2
Compare
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/ui/settings/app_settings/app_settings_screen.dart (1)
35-36: Fix user-facing copy (typo and grammar) and localize.
- "Delete app app data" → "Delete app data"
- "every profile, key, and local files" → "all profiles, keys, and local files"
Also externalize strings via AppLocalizations per guidelines.- title: 'Delete app app data', - content: 'This will erase every profile, key, and local files. This can\'t be undone.', + title: 'Delete app data', + content: 'This will erase all profiles, keys, and local files. This can\'t be undone.',
🧹 Nitpick comments (6)
lib/ui/settings/app_settings/app_settings_screen.dart (6)
17-17: Use camelCase alias for the API import and update call sites.Follow the code style guide for identifiers.
-import 'package:whitenoise/src/rust/api.dart' as wn_api; +import 'package:whitenoise/src/rust/api.dart' as wnApi;Also adjust usage below (see line 90):
- await wn_api.deleteAllData().timeout( + await wnApi.deleteAllData().timeout(
90-95: Avoid magic number: extract delete timeout to a constant.Improves readability and consistency with guidelines.
- await wn_api.deleteAllData().timeout( - const Duration(seconds: 30), + await wnApi.deleteAllData().timeout( + _deleteTimeout, onTimeout: () { throw Exception('Delete operation timed out after 30 seconds'); }, );Add near the top of the file (outside any class):
const Duration _deleteTimeout = Duration(seconds: 30);
124-128: Validate need for legacy activeAccountProvider invalidation.If the pubkey model supersedes accounts, drop this block (and the legacy import) to prevent redundant work and confusion.
- try { - ref.invalidate(activeAccountProvider); - _logger.info('✅ Active account provider invalidated'); - } catch (e) { - _logger.warning('⚠️ Error invalidating active account provider: $e'); - }
137-141: Consider invalidation order with identity first.Invalidate activePubkeyProvider before dependents (e.g., followsProvider) to ensure recompute order is identity → dependents.
27-27: Add explicit types per guidelines.Tighten types for logger, dialog result, and theme mode.
- static final _logger = Logger('AppSettingsScreen'); + static final Logger _logger = Logger('AppSettingsScreen');- final confirmed = await showDialog<bool>( + final bool? confirmed = await showDialog<bool>(- final themeMode = ref.watch(themeProvider).themeMode; + final ThemeMode themeMode = ref.watch(themeProvider).themeMode;Also applies to: 30-30, 175-175
8-9: Keep legacy provider imports until full migration is complete
ActiveAccountProvider is still referenced throughout the codebase (UI screens, services, tests). Add a// TODO:above these imports to clarify the transitional duality and only remove the legacy import and its usages once all references have been migrated to the new provider.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
lib/ui/contact_list/widgets/chat_list_item_tile.dart(2 hunks)lib/ui/settings/app_settings/app_settings_screen.dart(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/ui/contact_list/widgets/chat_list_item_tile.dart
🧰 Additional context used
📓 Path-based instructions (1)
**/*.dart
📄 CodeRabbit inference engine (.cursor/rules/flutter.mdc)
**/*.dart: Always declare the type of each variable and function (parameters and return value). Avoid using 'any'. Create necessary types.
Don't leave blank lines within a function.
One export per file.
Use PascalCase for classes.
Use camelCase for variables, functions, and methods.
Use underscores_case for file and directory names.
Use UPPERCASE for environment variables. Avoid magic numbers and define constants.
Start each function with a verb.
Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
Use complete words instead of abbreviations and correct spelling, except for standard and well-known abbreviations (API, URL, i, j, err, ctx, req, res, next).
Write short functions with a single purpose. Less than 20 instructions.
Name functions with a verb and something else. If it returns a boolean, use isX or hasX, canX, etc. If it doesn't return anything, use executeX or saveX, etc.
Avoid nesting blocks by early checks and returns, or extraction to utility functions.
Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting. Use arrow functions for simple functions (less than 3 instructions). Use named functions for non-simple functions.
Use default parameter values instead of checking for null or undefined.
Reduce function parameters using RO-RO: use an object to pass multiple parameters and to return results. Declare necessary types for input arguments and output.
Use a single level of abstraction in functions.
Don't abuse primitive types and encapsulate data in composite types.
Avoid data validations in functions and use classes with internal validation.
Prefer immutability for data. Use readonly for data that doesn't change. Use 'as const' for literals that don't change.
Declare interfaces to define contracts.
Write small classes with a single purpose. Less than 200 instructions, less than 10 public methods, less than 10 properties.
Use exceptions to handle errors you don't expect. If you catch an exception, it sh...
Files:
lib/ui/settings/app_settings/app_settings_screen.dart
🧠 Learnings (1)
📚 Learning: 2025-08-08T13:39:00.500Z
Learnt from: CR
PR: parres-hq/whitenoise_flutter#0
File: .cursor/rules/flutter.mdc:0-0
Timestamp: 2025-08-08T13:39:00.500Z
Learning: Applies to **/*.dart : Use the whitenoise rust crate (via flutter_rust_bridge) as the source of all data and only way to trigger changes in our data model.
Applied to files:
lib/ui/settings/app_settings/app_settings_screen.dart
🔇 Additional comments (2)
lib/ui/settings/app_settings/app_settings_screen.dart (2)
12-12: LGTM: followsProvider import aligns with the rename.
131-135: LGTM: followsProvider invalidation.Optional: if follows depend on the active pubkey, consider invalidating activePubkeyProvider first to avoid any transient reads on a stale identity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/ui/settings/network/widgets/network_section.dart (1)
26-31: Fix equality override signature; use Object and a type check.Overriding
==withcovariant RelayInfobreaks the standard contract and can cause asymmetric comparisons. UseObjectand anischeck. Also removes the blank line inside the function per guidelines.Apply this diff:
- @override - bool operator ==(covariant RelayInfo other) { - if (identical(this, other)) return true; - - return other.url == url && other.connected == connected && other.status == status; - } + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RelayInfo && + other.url == url && + other.connected == connected && + other.status == status);
♻️ Duplicate comments (5)
justfile (3)
208-209: Align wording: “for all platforms” likely inaccuratePast analysis shows the script builds Android and iOS only. Either update the echo or extend the script to truly build all platforms.
Apply one:
- @echo "🎁 Building versioned release for all platforms..." + @echo "🎁 Building versioned release for Android and iOS..."or extend
scripts/build.shto include macOS/Windows/Linux/Web, then keep the original message. To confirm actual targets, run the verification snippet in the first comment.
204-210: Release must not run auto-fixing precommit; use a check-only gateRunning
fixduring release makes artifacts non-reproducible. Swap to a non-mutating precommit.Apply:
release: @echo "🔨 Building versioned release..." - @echo "✔︎ Running a precommit check..." - just precommit + @echo "✔︎ Running pre-commit checks..." + just precommit-checkAdd this recipe elsewhere in the justfile:
# Check-only precommit (no auto-fixes), safe for CI/release precommit-check: just deps-flutter just deps-rust just check-rust-format just check-dart-format just lint just test-flutter just test-rust @echo "✅ All pre-commit checks passed (no auto-fixes)."
204-210: Guard the release build: clean tree + script existence checksFail fast if the repo is dirty or the build script is missing/not executable.
Apply:
@echo "🎁 Building versioned release for all platforms..." - ./scripts/build.sh --full --versioned + @test -x ./scripts/build.sh || { echo "❌ ./scripts/build.sh missing or not executable"; exit 1; } + @test -z "$$(git status --porcelain)" || { echo "❌ Uncommitted/untracked changes detected. Commit or stash before releasing."; exit 1; } + ./scripts/build.sh --full --versionedrust/src/api/messages.rs (1)
31-33: Replace Debug tag formatting with a stable representation (Vec<Vec>).Debug strings are unstable across versions and languages. Emit tags as vectors of fields instead and convert deterministically.
- use crate::api::{error::ApiError, utils::group_id_from_string}; + use crate::api::{error::ApiError, utils::{group_id_from_string, tag_to_vec}}; @@ - pub tags: Vec<String>, // Simplified tags representation for Flutter + pub tags: Vec<Vec<String>>, // Stable tag fields for Flutter @@ - let tags = chat_message + let tags = chat_message .tags .iter() - .map(|tag| format!("{tag:?}")) + .map(|tag| tag_to_vec(tag)) .collect();Add helper (in utils.rs or here) to produce a deterministic Vec per Tag:
pub fn tag_to_vec(tag: &nostr_sdk::Tag) -> Vec<String> { // Implement using nostr-sdk’s stable conversion (or match variants). // Example fallback (adjust as needed): tag.as_slice().iter().map(|s| s.to_string()).collect() }Also applies to: 189-193, 1-1
lib/domain/models/contact_model.dart (1)
51-58: Null-safety: user.metadata can be null.
This will throw today. Guard and sanitize.- final metadata = user.metadata; - final displayName = _sanitizeString(metadata.displayName); - final about = _sanitizeString(metadata.about); - final website = _sanitizeUrl(metadata.website); - final nip05 = _sanitizeString(metadata.nip05); - final lud16 = _sanitizeString(metadata.lud16); - final picture = _sanitizeUrl(metadata.picture); + final metadata = user.metadata; // may be null + final String displayName = _sanitizeString(metadata?.displayName); + final String about = _sanitizeString(metadata?.about); + final String? website = _sanitizeUrl(metadata?.website); + final String nip05 = _sanitizeString(metadata?.nip05); + final String lud16 = _sanitizeString(metadata?.lud16); + final String? picture = _sanitizeUrl(metadata?.picture);
🧹 Nitpick comments (15)
rust/Cargo.toml (3)
19-22: Remove redundantversionkeys on git deps; Cargo ignores them for git sources.Having both
git/revandversionis confusing;versionis not used for resolution here.-nostr-mls = { version = "0.43.0", git="https://github.com/rust-nostr/nostr", rev = "84b1a016cffc30625567a03e2d3bcae86463f075" } -nostr-sdk = { version = "0.43.0", git="https://github.com/rust-nostr/nostr", rev = "84b1a016cffc30625567a03e2d3bcae86463f075" } +nostr-mls = { git = "https://github.com/rust-nostr/nostr", rev = "84b1a016cffc30625567a03e2d3bcae86463f075" } +nostr-sdk = { git = "https://github.com/rust-nostr/nostr", rev = "84b1a016cffc30625567a03e2d3bcae86463f075" }
26-27: Same forwhitenoise: drop unusedversionwhen pinning to a gitrev.Keeps dependency intent unambiguous. If a release tag exists matching the needed APIs, consider pinning to
taginstead of a raw commit for easier audits.-whitenoise = { version = "0.1.0", git = "https://github.com/parres-hq/whitenoise", rev = "b1ac58bfa3da5c907acac4ce0788b5c2ffbd0d70" } +whitenoise = { git = "https://github.com/parres-hq/whitenoise", rev = "b1ac58bfa3da5c907acac4ce0788b5c2ffbd0d70" }Optional supply‑chain hardening:
- Add
cargo-deny/cargo-auditto CI for license and vuln checks.- Commit
Cargo.lock(for app/binary outputs) to lock transitive deps.
12-12: Rust and Dart FRB versions match; consider caret range (optional)
The Dart pubspec’sflutter_rust_bridge: 2.11.1aligns with the crate’s=2.11.1. If strict pinning isn’t required for codegen, you can relax to a caret constraint (e.g.^2.11.1) to automatically receive patch fixes.rust/src/api/messages.rs (2)
90-96: DRY timestamp conversion into a small helper.Same fallible u64→DateTime logic appears three times.
- created_at: { - let ts = - i64::try_from(message_with_tokens.message.created_at.as_u64()).unwrap_or(0); - Utc.timestamp_opt(ts, 0) - .single() - .unwrap_or_else(|| Utc.timestamp_opt(0, 0).single().unwrap()) - }, + created_at: utc_from_u64(message_with_tokens.message.created_at.as_u64()), @@ - created_at: { - let ts = i64::try_from(user_reaction.created_at.as_u64()).unwrap_or(0); - Utc.timestamp_opt(ts, 0) - .single() - .unwrap_or_else(|| Utc.timestamp_opt(0, 0).single().unwrap()) - }, + created_at: utc_from_u64(user_reaction.created_at.as_u64()), @@ - created_at: { - let ts = i64::try_from(chat_message.created_at.as_u64()).unwrap_or(0); - Utc.timestamp_opt(ts, 0) - .single() - .unwrap_or_else(|| Utc.timestamp_opt(0, 0).single().unwrap()) - }, + created_at: utc_from_u64(chat_message.created_at.as_u64()),Add once:
fn utc_from_u64(ts_u64: u64) -> chrono::DateTime<Utc> { let ts = i64::try_from(ts_u64).unwrap_or(0); Utc.timestamp_opt(ts, 0).single().unwrap_or_else(|| Utc.timestamp_opt(0, 0).single().unwrap()) }Also applies to: 164-169, 210-214
20-20: Consider making MessageWithTokens.content a plain String.It’s always Some(...). Drop Option to simplify the model.
- pub content: Option<String>, + pub content: String, @@ - content: Some(message_with_tokens.message.content.clone()), + content: message_with_tokens.message.content.clone(),Also applies to: 97-99
rust/src/api/relays.rs (2)
46-51: Make output order deterministic (sort by URL).Stable ordering helps UI diffs and caching.
Apply:
- let converted_statuses = statuses - .into_iter() - .map(|(url, status)| (url.to_string(), status.to_string())) - .collect(); - Ok(converted_statuses) + let mut converted_statuses: Vec<(String, String)> = statuses + .into_iter() + .map(|(url, status)| (url.to_string(), status.to_string())) + .collect(); + converted_statuses.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + Ok(converted_statuses)
1-6: Nit: avoid glob import from nostr_sdk prelude here.For a small file only using PublicKey, prefer explicit import to minimize surface (optional).
rust/src/api/accounts.rs (4)
28-31: Return accounts in a stable order (newest first).Deterministic ordering reduces UI churn.
- let accounts = whitenoise.all_accounts().await?; - Ok(accounts.into_iter().map(|a| a.into()).collect()) + let accounts = whitenoise.all_accounts().await?; + let mut out: Vec<Account> = accounts.into_iter().map(|a| a.into()).collect(); + out.sort_unstable_by(|a, b| b.updated_at.cmp(&a.updated_at)); + Ok(out)
84-95: Prefer owned FlutterMetadata over borrowed in FRB surface.Borrowed params can complicate FRB codegen; owned is simpler and consistent with other endpoints.
-pub async fn update_account_metadata( - pubkey: String, - metadata: &FlutterMetadata, -) -> Result<(), ApiError> { +pub async fn update_account_metadata( + pubkey: String, + metadata: FlutterMetadata, +) -> Result<(), ApiError> { let whitenoise = Whitenoise::get_instance()?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; account - .update_metadata(&metadata.into(), whitenoise) + .update_metadata(&metadata.into(), whitenoise) .await .map_err(ApiError::from) }Please confirm FRB generates cleanly with owned FlutterMetadata.
118-124: Sort relays by URL before returning.Stable ordering helps UI and tests.
- let relays = account.relays(relay_type, whitenoise).await?; - Ok(relays.into_iter().map(|r| r.into()).collect()) + let relays = account.relays(relay_type, whitenoise).await?; + let mut out: Vec<Relay> = relays.into_iter().map(|r| r.into()).collect(); + out.sort_unstable_by(|a, b| a.url.cmp(&b.url)); + Ok(out)
35-40: Extract pubkey parsing into a helper
Repeated calls toPublicKey::parse(&pubkey)across this module—extract intofn parse_pubkey(input: &str) -> Result<PublicKey, ApiError>and reuse instead of duplicating the parse logic.
lib/config/providers/chat_provider.dart (4)
90-93: Add explicit types per project guidelines.- final messages = await MessageConverter.fromChatMessageList( + final List<MessageModel> messages = await MessageConverter.fromChatMessageList( @@ - final sentChatMessage = ChatMessage( + final ChatMessage sentChatMessage = ChatMessage( @@ - final messageCache = <String, ChatMessage>{}; + final Map<String, ChatMessage> messageCache = <String, ChatMessage>{}; @@ - final sentMessageModel = await MessageConverter.fromChatMessage( + final MessageModel sentMessageModel = await MessageConverter.fromChatMessage( @@ - final newMessages = await MessageConverter.fromChatMessageList( + final List<MessageModel> newMessages = await MessageConverter.fromChatMessageList( @@ - final newMessagesOnly = newMessages.skip(currentMessages.length).toList(); + final List<MessageModel> newMessagesOnly = newMessages.skip(currentMessages.length).toList(); @@ - final sentChatMessage = ChatMessage( + final ChatMessage sentChatMessage = ChatMessage( @@ - final messageCache = <String, ChatMessage>{}; + final Map<String, ChatMessage> messageCache = <String, ChatMessage>{}; @@ - final sentMessageModel = await MessageConverter.fromChatMessage( + final MessageModel sentMessageModel = await MessageConverter.fromChatMessage(Also applies to: 168-181, 183-201, 202-209, 303-308, 337-338, 605-617, 620-637, 639-645
121-129: Replace magic kind numbers with named constants.
Improves readability and prevents drift.Add near the top of this file:
// Nostr kinds const int nostrKindText = 9; const int nostrKindReaction = 7; const int nostrKindDeletion = 5;Then:
- int kind = 9, // Default to text message + int kind = nostrKindText, @@ - kind: 7, // Nostr kind 7 = reaction + kind: nostrKindReaction, @@ - kind: 9, // Kind 9 for replies + kind: nostrKindText, @@ - kind: 5, // Nostr kind 5 = deletion + kind: nostrKindDeletion,Also applies to: 543-545, 597-599, 704-706
510-514: Use the validated groupId variable to avoid null-coalescing to ''.
Removes redundant null handling and avoids accidental empty key usage.- final groupId = message.groupId; - if (groupId == null || groupId.isEmpty) { + final String? groupId = message.groupId; + if (groupId == null || groupId.isEmpty) { _logger.warning('Cannot update reaction: message has no groupId'); return false; } @@ - groupId: message.groupId ?? '', + groupId: groupId, @@ - await refreshMessagesForGroup(message.groupId ?? ''); + await refreshMessagesForGroup(groupId);Also applies to: 540-545, 548-548
231-233: Error strings: localize via AppLocalizations.Consider wiring AppLocalizations and moving these user-facing strings to l10n.
Also applies to: 557-558, 662-663, 717-718
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (6)
android/app/src/main/jniLibs/arm64-v8a/librust_lib_whitenoise.sois excluded by!**/*.soandroid/app/src/main/jniLibs/armeabi-v7a/librust_lib_whitenoise.sois excluded by!**/*.soandroid/app/src/main/jniLibs/x86_64/librust_lib_whitenoise.sois excluded by!**/*.soios/Podfile.lockis excluded by!**/*.lockpubspec.lockis excluded by!**/*.lockrust/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (16)
justfile(1 hunks)lib/config/providers/chat_provider.dart(24 hunks)lib/domain/models/contact_model.dart(4 hunks)lib/domain/models/dm_chat_data.dart(1 hunks)lib/domain/models/message_model.dart(1 hunks)lib/domain/models/user_model.dart(3 hunks)lib/ui/settings/general_settings_screen.dart(9 hunks)lib/ui/settings/network/widgets/network_section.dart(1 hunks)macos/Flutter/GeneratedPluginRegistrant.swift(2 hunks)pubspec.yaml(1 hunks)rust/Cargo.toml(1 hunks)rust/src/api/accounts.rs(1 hunks)rust/src/api/groups.rs(1 hunks)rust/src/api/messages.rs(1 hunks)rust/src/api/relays.rs(1 hunks)rust/src/api/users.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- pubspec.yaml
- rust/src/api/users.rs
- rust/src/api/groups.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.dart
📄 CodeRabbit inference engine (.cursor/rules/flutter.mdc)
**/*.dart: Always declare the type of each variable and function (parameters and return value). Avoid using 'any'. Create necessary types.
Don't leave blank lines within a function.
One export per file.
Use PascalCase for classes.
Use camelCase for variables, functions, and methods.
Use underscores_case for file and directory names.
Use UPPERCASE for environment variables. Avoid magic numbers and define constants.
Start each function with a verb.
Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
Use complete words instead of abbreviations and correct spelling, except for standard and well-known abbreviations (API, URL, i, j, err, ctx, req, res, next).
Write short functions with a single purpose. Less than 20 instructions.
Name functions with a verb and something else. If it returns a boolean, use isX or hasX, canX, etc. If it doesn't return anything, use executeX or saveX, etc.
Avoid nesting blocks by early checks and returns, or extraction to utility functions.
Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting. Use arrow functions for simple functions (less than 3 instructions). Use named functions for non-simple functions.
Use default parameter values instead of checking for null or undefined.
Reduce function parameters using RO-RO: use an object to pass multiple parameters and to return results. Declare necessary types for input arguments and output.
Use a single level of abstraction in functions.
Don't abuse primitive types and encapsulate data in composite types.
Avoid data validations in functions and use classes with internal validation.
Prefer immutability for data. Use readonly for data that doesn't change. Use 'as const' for literals that don't change.
Declare interfaces to define contracts.
Write small classes with a single purpose. Less than 200 instructions, less than 10 public methods, less than 10 properties.
Use exceptions to handle errors you don't expect. If you catch an exception, it sh...
Files:
lib/domain/models/message_model.dartlib/domain/models/user_model.dartlib/ui/settings/network/widgets/network_section.dartlib/domain/models/dm_chat_data.dartlib/config/providers/chat_provider.dartlib/domain/models/contact_model.dartlib/ui/settings/general_settings_screen.dart
🧠 Learnings (6)
📚 Learning: 2025-08-08T13:39:00.500Z
Learnt from: CR
PR: parres-hq/whitenoise_flutter#0
File: .cursor/rules/flutter.mdc:0-0
Timestamp: 2025-08-08T13:39:00.500Z
Learning: Applies to **/*.dart : Use the whitenoise rust crate (via flutter_rust_bridge) as the source of all data and only way to trigger changes in our data model.
Applied to files:
rust/Cargo.tomllib/config/providers/chat_provider.dartrust/src/api/messages.rs
📚 Learning: 2025-08-08T13:39:00.500Z
Learnt from: CR
PR: parres-hq/whitenoise_flutter#0
File: .cursor/rules/flutter.mdc:0-0
Timestamp: 2025-08-08T13:39:00.500Z
Learning: Applies to **/*.dart : Use flutter_rust_bridge to access core functionality of the app.
Applied to files:
rust/Cargo.toml
📚 Learning: 2025-08-08T13:39:00.500Z
Learnt from: CR
PR: parres-hq/whitenoise_flutter#0
File: .cursor/rules/flutter.mdc:0-0
Timestamp: 2025-08-08T13:39:00.500Z
Learning: Applies to **/*.dart : Use Riverpod to manage state. Use StreamProviders to watch for state changes that come from the rust api. See keepAlive if you need to keep the state alive.
Applied to files:
lib/config/providers/chat_provider.dart
📚 Learning: 2025-08-29T09:11:07.251Z
Learnt from: erskingardner
PR: parres-hq/whitenoise_flutter#530
File: rust/src/api/utils.rs:23-26
Timestamp: 2025-08-29T09:11:07.251Z
Learning: In the nostr-sdk Rust library, PublicKey::parse method accepts both hex and bech32 (npub) encoded strings, so there's no need to use format-specific methods like PublicKey::from_bech32 when parsing public keys from string input.
Applied to files:
rust/src/api/messages.rsrust/src/api/accounts.rs
📚 Learning: 2025-08-12T11:21:53.640Z
Learnt from: josefinalliende
PR: parres-hq/whitenoise_flutter#455
File: lib/ui/settings/profile/switch_profile_bottom_sheet.dart:0-0
Timestamp: 2025-08-12T11:21:53.640Z
Learning: In the whitenoise_flutter codebase, ContactModel.publicKey can be stored in either npub format (from metadata cache) or hex format (from account storage). The activeAccountProvider may return either format depending on how the account was originally stored, so normalization to hex format is required when comparing with other hex-normalized keys in sorting logic.
Applied to files:
rust/src/api/messages.rslib/domain/models/contact_model.dartrust/src/api/accounts.rslib/ui/settings/general_settings_screen.dart
📚 Learning: 2025-08-23T11:02:28.308Z
Learnt from: Quwaysim
PR: parres-hq/whitenoise_flutter#527
File: lib/ui/core/ui/wn_avatar.dart:1-6
Timestamp: 2025-08-23T11:02:28.308Z
Learning: In the whitenoise_flutter codebase, flutter_screenutil is re-exported through lib/ui/core/themes/src/app_theme.dart, so files that import app_theme.dart do not need to directly import flutter_screenutil to use .w, .h, .sp extensions.
Applied to files:
lib/ui/settings/general_settings_screen.dart
🧬 Code graph analysis (3)
rust/src/api/messages.rs (2)
rust/src/api/utils.rs (1)
group_id_from_string(66-69)rust/src/api/error.rs (1)
from(37-41)
rust/src/api/relays.rs (3)
rust/src/api/accounts.rs (1)
from(17-24)rust/src/api/groups.rs (4)
from(30-47)from(90-96)from(109-114)from(126-130)rust/src/api/users.rs (1)
from(18-25)
rust/src/api/accounts.rs (3)
rust/src/api/groups.rs (4)
from(30-47)from(90-96)from(109-114)from(126-130)rust/src/api/relays.rs (1)
from(16-22)rust/src/api/users.rs (1)
from(18-25)
🔇 Additional comments (41)
lib/ui/settings/network/widgets/network_section.dart (1)
34-34: Good move to Object.hash for better distribution and readability.This aligns with modern Dart and keeps hash fields consistent with equality.
macos/Flutter/GeneratedPluginRegistrant.swift (2)
14-14: LGTM: Correct macOS import for package_info_plus.Importing the module as
package_info_plusis correct for the macOS target and aligns with the plugin’s current packaging.
27-27: package_info_plus class prefix is correct
pubspec.lock pins package_info_plus at 2.2.0 and your macOS GeneratedPluginRegistrant.swift registers FPPPackageInfoPlusPlugin, matching the plugin’s recent prefix.rust/Cargo.toml (1)
15-15: Current Tokio features are sufficient
No occurrences of#[tokio::main],#[tokio::test], ortokio::timewere found in the codebase, so the existing["rt", "rt-multi-thread"]feature set is adequate.rust/src/api/messages.rs (4)
241-243: Pubkey parsing: good upgrade to parse() (hex or npub).This aligns with the recent utils learning and hardens inputs.
Also applies to: 256-257
68-73: Token enums mapped clearly.Straightforward, Flutter-friendly representation looks good.
146-176: ReactionSummary conversion looks correct.Counts widened safely; keys normalized to hex.
232-239: FRB chrono support confirmed: Cargo.toml enablesflutter_rust_bridgewith thechronofeature and the generated Dart bindings includeDateTimeencode/decode methods forChrono_Utc. No changes needed.rust/src/api/relays.rs (3)
7-13: Relay DTO for FRB is clean and minimal — LGTM.Fields and #[frb(non_opaque)] are appropriate for Flutter interop.
15-23: Correct conversion from WhitenoiseRelay — LGTM.Direct field mapping is accurate.
43-45: Good: accept both hex and npub via PublicKey::parse.Aligns with the repo’s preference to support multiple encodings.
rust/src/api/accounts.rs (4)
7-14: Account DTO matches FRB needs — LGTM.Non-opaque + chrono fields look consistent with other API types.
16-24: From mapping is correct — LGTM.Hex-encoding pubkey matches the rest of the API.
180-192: Ensure follow/unfollow are idempotent and resilient.Double-follow/unfollow should be safe and return OK; network/db races should not error.
If not already guaranteed by whitenoise, consider guarding in the service layer.
Also applies to: 195-207
98-107: Verify FRB support for ImageType params and enforce HTTPS on server_url
- Confirm flutter_rust_bridge can marshal
ImageTypedirectly inupload_account_profile_picture; if not, retainString + ImageType::try_from(...)to avoid breaking the FFI contract.- After
let server = Url::parse(&server_url)?;, addif server.scheme() != "https"and return an appropriateApiErrorvariant to reject non-HTTPS URLs.lib/domain/models/message_model.dart (1)
261-261: Switch to Object.hash is correct and consistent with ==.lib/domain/models/dm_chat_data.dart (1)
39-39: Good move to Object.hash; null-safe and faster than manual XOR.lib/domain/models/user_model.dart (2)
42-43: hashCode updated to Object.hash: LGTM.
19-27: VerifyuserMetadatareturn type and import alignment
Please confirm thatrust_users.userMetadatareturns a non-nullableFuture<FlutterMetadata>(notFuture<FlutterMetadata?>) and that theimport ... as rust_usersalias ingroup_provider.dartmatches the binding file.lib/config/providers/chat_provider.dart (1)
7-7: Active pubkey listener: good integration and safe post-frame clearing.Also applies to: 23-34
lib/domain/models/contact_model.dart (1)
107-108: hashCode via Object.hash: good.lib/ui/settings/general_settings_screen.dart (20)
6-6: Import added for package information retrieval.The addition of
package_info_plusimport supports the new version display functionality at the bottom of the settings screen.
9-9: Import added for new pubkey-centric provider.The addition of
activePubkeyProviderimport aligns with the migration from account-centered to pubkey-centered identity management.
11-11: Import added for follows management.The addition of
followsProviderimport supports the new follow-based data management replacing the previous contacts system.
37-38: Type migration from AccountData to Account.The state variables have been correctly updated to use the new
Accounttype instead ofAccountData, aligning with the bridge API changes.
40-40: Provider subscription type updated for new state.The subscription type correctly changes from
ProfileStatetoActiveAccountStateto match the new pubkey-centric architecture.
41-41: State variable added for package information.The
PackageInfovariable supports the version display feature at the bottom of the settings screen.
47-47: Package info loading added to initialization.The call to
_loadPackageInfo()properly initializes the version information display.
48-50: Provider subscription updated for new architecture.The subscription correctly changes to
activeAccountProviderto align with the new pubkey-centric state management.
61-61: Subscription disposal properly updated.The disposal of the updated subscription variable maintains proper resource management.
68-68: Provider usage updated for pubkey-centric approach.The code correctly reads from
activePubkeyProviderinstead of account-based providers to get the active pubkey.
87-87: Type update for currentAccount variable.The local variable type correctly changes from
AccountDatatoAccountto match the new API.
97-97: Active pubkey management updated.The code correctly uses
activePubkeyProvider.notifier.setActivePubkey()to manage the active pubkey state, replacing the previous account-based approach.Also applies to: 103-103
118-128: Package info loading implementation.The implementation properly loads package information using
PackageInfo.fromPlatform()with appropriate error handling. The silent error handling is appropriate since version information is non-critical.
130-130: Method signatures updated for new Account type.Both
_switchAccountand_accountToContactModelmethods have been correctly updated to acceptAccountparameters instead ofAccountData.Also applies to: 147-147
132-134: Account switching logic updated for new architecture.The account switching logic correctly:
- Updates the active pubkey using
activePubkeyProvider.notifier.setActivePubkey()- Loads follows using
followsProvider.notifier.loadFollows()- Maintains the existing groups loading logic
This properly integrates with the new pubkey-centric and follows-based architecture.
171-190: Account resolution logic with proper key format handling.The account resolution correctly handles the key format differences:
selectedProfile.publicKeyfrom metadata cache (npub format)account.pubkeyfrom getAccounts (hex format)The implementation properly converts npub to hex using
hexPubkeyFromNpub()for matching, with fallback to direct matching. This aligns with the retrieved learning about key format normalization requirements.
176-176: Variable type updated for account selection.The
selectedAccountvariable type correctly changes fromAccountDatatoAccountto match the new API.
421-434: Version information display implementation.The version display implementation is well-structured:
- Conditionally renders only when package info is available
- Uses appropriate styling with muted foreground color
- Displays both version and build number
- Positioned appropriately at the bottom with proper spacing
133-133: No issues found withloadFollows()
ThefollowsProviderinlib/config/providers/follows_provider.dartdeclares and implements aFuture<void> loadFollows()method (starting at line 72), so the call in your widget is valid.
68-68: activePubkeyProvider and its notifier methods are implemented in lib/config/providers/active_pubkey_provider.dart.
| # Build unversioned android release | ||
| android-build: | ||
| @echo "🔨 Building unversioned android release..." | ||
| @echo "✔︎ Running a precommit check..." | ||
| just precommit | ||
| @echo "🎁 Building unversioned android release..." | ||
| ./scripts/build.sh --full --versioned | ||
| @echo "🎉 Unversioned android release built successfully!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
android-build is mislabeled: passes --versioned and likely builds more than Android
The recipe says "unversioned android release" but calls --versioned and (via the script) may target multiple platforms. Restrict to Android and drop versioning.
Apply:
android-build:
@echo "🔨 Building unversioned android release..."
@echo "✔︎ Running a precommit check..."
- just precommit
+ just precommit-check
@echo "🎁 Building unversioned android release..."
- ./scripts/build.sh --full --versioned
+ @test -x ./scripts/build.sh || { echo "❌ ./scripts/build.sh missing or not executable"; exit 1; }
+ @test -z "$$(git status --porcelain)" || { echo "❌ Uncommitted/untracked changes detected. Commit or stash before building."; exit 1; }
+ ./scripts/build.sh --full --os androidIf --os android isn’t supported, switch to the equivalent flag (e.g., --platform android) or drop the selector and ensure the script only builds Android when no selector is given.
To verify the script’s flags and targets, run:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
test -f scripts/build.sh || { echo "scripts/build.sh not found"; exit 1; }
echo "== supported flags =="
rg -nP -- '--versioned|--full|--os|--platform' scripts/build.sh || true
echo -e "\n== detected flutter build targets =="
rg -nP 'flutter\s+build\s+(apk|appbundle|ios|ipa|macos|windows|linux|web)' scripts/build.sh -C2 || trueLength of output: 2566
Restrict android-build to unversioned Android only
Drop the --versioned flag and use the supported --android selector (script does not support --os/--platform):
- ./scripts/build.sh --full --versioned
+ ./scripts/build.sh --full --androidAlso verify or add a precommit-check task before renaming just precommit to just precommit-check.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Build unversioned android release | |
| android-build: | |
| @echo "🔨 Building unversioned android release..." | |
| @echo "✔︎ Running a precommit check..." | |
| just precommit | |
| @echo "🎁 Building unversioned android release..." | |
| ./scripts/build.sh --full --versioned | |
| @echo "🎉 Unversioned android release built successfully!" | |
| # Build unversioned android release | |
| android-build: | |
| @echo "🔨 Building unversioned android release..." | |
| @echo "✔︎ Running a precommit check..." | |
| just precommit | |
| @echo "🎁 Building unversioned android release..." | |
| ./scripts/build.sh --full --android | |
| @echo "🎉 Unversioned android release built successfully!" |
🤖 Prompt for AI Agents
In justfile around lines 194 to 201, the android-build recipe incorrectly passes
--versioned and should target unversioned Android only and must call the correct
precommit task: remove the --versioned flag and replace it with the supported
selector --android when invoking ./scripts/build.sh (i.e., ./scripts/build.sh
--full --android), rename the invoked precommit target to precommit-check (or
verify/add a precommit-check task in the justfile and update the call from just
precommit to just precommit-check), and keep the surrounding echo messages
consistent with “unversioned android release.”
| final activeAccountState = await ref.read(activeAccountProvider.future); | ||
| final activeAccount = activeAccountState.account; | ||
| if (activeAccount == null) { | ||
| _setGroupError(groupId, 'No active account found'); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Prefer activePubkeyProvider over activeAccountProvider.future, add explicit types.
Removes unnecessary async account fetches and follows the new pubkey-centric flow.
Apply (example for loadMessagesForGroup block at Lines 68-74); mirror in other blocks listed:
- final activeAccountState = await ref.read(activeAccountProvider.future);
- final activeAccount = activeAccountState.account;
- if (activeAccount == null) {
+ final String? pubkey = ref.read(activePubkeyProvider);
+ if (pubkey == null || pubkey.isEmpty) {
_setGroupError(groupId, 'No active account found');
return;
}
@@
- final aggregatedMessages = await fetchAggregatedMessagesForGroup(
- pubkey: activeAccount.pubkey,
+ final aggregatedMessages = await fetchAggregatedMessagesForGroup(
+ pubkey: pubkey,
groupId: groupId,
);
@@
- final messages = await MessageConverter.fromChatMessageList(
+ final List<MessageModel> messages = await MessageConverter.fromChatMessageList(
aggregatedMessages,
- currentUserPublicKey: activeAccount.pubkey,
+ currentUserPublicKey: pubkey,
groupId: groupId,
ref: ref,
);Do likewise in sendMessage, checkForNewMessages, updateMessageReaction, sendReplyMessage, deleteMessage.
Also applies to: 147-152, 290-294, 504-507, 578-584, 683-689
🤖 Prompt for AI Agents
In lib/config/providers/chat_provider.dart around lines 68 to 74, replace the
awaited activeAccountProvider.future usage with a direct read of
activePubkeyProvider and add explicit local types: read final String?
activePubkey = ref.read(activePubkeyProvider); check for null and call
_setGroupError(groupId, 'No active account found') and return if null (instead
of fetching account.account), so we avoid unnecessary async account fetches;
apply the same change (use final String? activePubkey =
ref.read(activePubkeyProvider); explicit types; null check and early return) to
the other blocks listed at lines 147-152, 290-294, 504-507, 578-584, and
683-689, and update any downstream code in those blocks to use activePubkey
instead of activeAccount or activeAccount.account.
| Future<void> _updateGroupOrderForNewMessage(String groupId) async { | ||
| final now = DateTime.now(); | ||
|
|
||
| ref.read(groupsProvider.notifier).updateGroupActivityTime(groupId, now); | ||
| await ref.read(groupsProvider.notifier).updateGroupActivityTime(groupId, now); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
_updateGroupOrderForNewMessage is async: await all call sites.
Prevents unawaited future warnings and keeps ordering deterministic.
Run to find unawaited calls:
If found in async contexts (e.g., Lines 364, 655), add await.
🏁 Script executed:
#!/bin/bash
rg -n "_updateGroupOrderForNewMessage\(" -n -C2 --type dartLength of output: 1518
Await _updateGroupOrderForNewMessage at all call sites
- In lib/config/providers/chat_provider.dart, change line 364 and line 655 from
_updateGroupOrderForNewMessage(groupId);
to
await _updateGroupOrderForNewMessage(groupId);
🤖 Prompt for AI Agents
In lib/config/providers/chat_provider.dart around lines 784-788 the method
_updateGroupOrderForNewMessage is async (returns Future<void>); update its two
call sites at lines ~364 and ~655 to await the call (change
`_updateGroupOrderForNewMessage(groupId);` to `await
_updateGroupOrderForNewMessage(groupId);`) so the caller waits for the update to
complete; ensure the enclosing functions are async (add async if missing) and
propagate or handle any errors as needed.
| // ignore_for_file: public_member_api_docs, sort_constructors_first | ||
| import 'package:whitenoise/src/rust/api/utils.dart'; | ||
| import 'package:whitenoise/src/rust/api/metadata.dart' show FlutterMetadata; | ||
| import 'package:whitenoise/src/rust/api/users.dart' show User; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid User type collision: alias FRB User import.
Prevents confusion with domain User.
-import 'package:whitenoise/src/rust/api/users.dart' show User;
+import 'package:whitenoise/src/rust/api/users.dart' as frb show User;
@@
- factory ContactModel.fromUser({
- required User user,
+ factory ContactModel.fromUser({
+ required frb.User user,Also applies to: 48-50
🤖 Prompt for AI Agents
In lib/domain/models/contact_model.dart around lines 3 and 48-50, the imported
User from package:whitenoise/src/rust/api/users.dart conflicts with the domain
User type; rename/alias that import (e.g., import ... show User as FrbUser) and
update all usages in this file to use the alias (FrbUser) to avoid type
collision and confusion with the domain User.
| displayName: displayName.isNotEmpty ? displayName : 'Unknown User', | ||
| publicKey: user.pubkey, | ||
| imagePath: picture, | ||
| about: about, | ||
| website: website, | ||
| nip05: nip05, | ||
| lud16: lud16, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Key normalization: ensure consistent hex vs npub.
Use a single canonical format when storing/comparing publicKey.
If a helper exists (e.g., normalizePubkey), apply:
- publicKey: user.pubkey,
+ publicKey: normalizePubkey(user.pubkey),🏁 Script executed:
#!/bin/bash
# Find available helpers to normalize keys
rg -nP "(npub|bech32|hex).*pubkey|pubkey.*(npub|hex)|toHex|fromNpub" --type dartLength of output: 2128
🏁 Script executed:
#!/bin/bash
# Inspect helper definitions for pubkey conversion
sed -n '1,200p' lib/config/providers/nostr_keys_provider.dart
# Inspect normalization logic in auth_provider
sed -n '280,340p' lib/config/providers/auth_provider.dartLength of output: 5844
🏁 Script executed:
#!/bin/bash
# Inspect string extensions for pubkey conversion helpers
sed -n '1,200p' lib/utils/string_extensions.dartLength of output: 1237
Normalize publicKey to hex before storing in ContactModel
ContactModel currently uses publicKey: user.pubkey, which may be in hex or npub form. There’s no built-in normalizePubkey helper—add one and use an async factory:
/// utils/nostr_keys.dart
Future<String> normalizeHexPubkey(String key) =>
key.startsWith('npub')
? hexPubkeyFromNpub(npub: key)
: Future.value(key.toLowerCase());
/// lib/domain/models/contact_model.dart
extension ContactModelFactory on ContactModel {
static Future<ContactModel> fromUser({
required User user,
required String displayName,
/* …other fields… */
}) async {
final hex = await normalizeHexPubkey(user.pubkey);
return ContactModel(
displayName: displayName.isNotEmpty ? displayName : 'Unknown User',
publicKey: hex,
/* … */
);
}
}Replace all direct publicKey: user.pubkey with calls to this factory (and update callers to await).
🤖 Prompt for AI Agents
In lib/domain/models/contact_model.dart around lines 60-67, the code assigns
publicKey: user.pubkey which may be npub or hex; add a helper normalizeHexPubkey
(e.g., in utils/nostr_keys.dart) that converts npub→hex and lowercases hex
values, then add an async factory (extension ContactModelFactory.fromUser) that
calls this normalizer and returns a ContactModel with publicKey set to the
normalized hex; replace all direct uses of ContactModel(...) with calls to await
ContactModelFactory.fromUser(...) and update callers to await the factory.
| @@ -1,5 +1,5 @@ | |||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | |||
| import 'package:whitenoise/src/rust/api/utils.dart'; | |||
| import 'package:whitenoise/src/rust/api/metadata.dart' show FlutterMetadata; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Importing FlutterMetadata: verify downstream call sites.
This file now exposes FlutterMetadata on the public surface; ensure all factories/callers updated.
Run to find callers still passing MetadataData:
🏁 Script executed:
#!/bin/bash
rg -nP "User\.fromMetadata\s*\(" -C2 --type dartLength of output: 1274
Update downstream call sites to use FlutterMetadata
Callers in lib/config/providers/group_provider.dart at lines 324 and 405 still invoke User.fromMetadata(metadata, pubkeyString). Ensure the metadata argument is the new FlutterMetadata (not the old MetadataData) and update the import/factory signatures accordingly.
🤖 Prompt for AI Agents
In lib/domain/models/user_model.dart around line 2, the file still imports/uses
the old metadata type; update the import to bring in FlutterMetadata (import
'package:whitenoise/src/rust/api/metadata.dart' show FlutterMetadata) and change
the User.fromMetadata factory signature to accept FlutterMetadata instead of the
old MetadataData; then update downstream call sites in
lib/config/providers/group_provider.dart (around lines 324 and 405) to pass the
FlutterMetadata instance (adjust their imports if they currently import the old
type) so function signatures and call sites match the new FlutterMetadata type.
| # nostr-mls = {version = "0.43.0", path = "../../rust-nostr/mls/nostr-mls"} | ||
| # nostr-sdk = {version = "0.43.0", path = "../../rust-nostr/crates/nostr-sdk"} | ||
|
|
||
| thiserror = "2.0.12" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Check MSRV compatibility for thiserror 2.x across CI/toolchains.
thiserror 2.x bumped MSRV; ensure your CI/toolchain and consumers build with a compatible Rust version.
🏁 Script executed:
#!/bin/bash
# Surface configured toolchains in-repo
fd -a 'rust-toolchain*' | xargs -I{} sh -c 'echo "--- {} ---"; cat "{}"'
fd -a '.github/workflows/*.yml' | xargs rg -nP 'toolchain|rust-version|actions-rust' -C2 || trueLength of output: 19894
Add rust-toolchain.toml and pin Rust MSRV in CI
No Rust toolchain is pinned in the repo or CI; require Rust ≥1.61 (MSRV for thiserror 2.x) by adding a rust-toolchain.toml and updating your workflows to test against that version (lib.rs)
🤖 Prompt for AI Agents
rust/Cargo.toml around line 24: the crate thiserror requires Rust ≥1.61 but the
repo and CI do not pin a toolchain; add a rust-toolchain.toml at the repo root
that pins the MSRV (e.g. [toolchain] channel = "1.61.0" or "1.61"), and update
CI workflows (e.g. .github/workflows/*) to test using that pinned version (or
add it to the actions-rs/setup-rust or matrix) so builds run with the required
Rust toolchain.
| pub async fn remove_account_relay( | ||
| pubkey: String, | ||
| url: String, | ||
| relay_type: RelayType, | ||
| ) -> Result<(), ApiError> { | ||
| let whitenoise = Whitenoise::get_instance()?; | ||
| let pubkey = PublicKey::parse(&pubkey)?; | ||
| let account = whitenoise.find_account_by_pubkey(&pubkey).await?; | ||
| let relay_url = RelayUrl::parse(&url)?; | ||
| let relay = whitenoise.find_or_create_relay_by_url(&relay_url).await?; | ||
| account | ||
| .remove_relay(&relay, relay_type, whitenoise) | ||
| .await | ||
| .map_err(ApiError::from) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Avoid creating Relay records when removing.
Using find_or_create_relay_by_url during removal can create orphan relay rows if the URL didn’t exist, polluting storage.
- Prefer a non-creating lookup (e.g., find_relay_by_url) and no-op if absent.
- If the library lacks such method, add a removal API that accepts URL without creation.
Run to check for a non-creating lookup you could use:
🏁 Script executed:
#!/bin/bash
# Look for candidate APIs to avoid creation on removal.
rg -nP 'find_.*relay.*by_url|get_.*relay.*by_url|remove_.*relay.*by_url' -C2 --type=rustLength of output: 888
Avoid creating Relay records when removing
In remove_account_relay (rust/src/api/accounts.rs:144–158), calling find_or_create_relay_by_url will insert a Relay when it didn’t exist, leading to orphan rows. Add and use a non-creating lookup (e.g. find_relay_by_url(&RelayUrl) -> Option<Relay>) on Whitenoise and no-op if it returns None instead of creating then removing.
🤖 Prompt for AI Agents
In rust/src/api/accounts.rs around lines 144–158, the current implementation
uses find_or_create_relay_by_url which will insert a Relay record when removing
and can create orphan rows; change this to call a non-creating lookup (e.g.
Whitenoise::find_relay_by_url(&relay_url) -> Option<Relay>), and if it returns
None simply return Ok(()) (no-op) instead of creating then removing; adjust
error handling to map existing parse/lookup failures to ApiError as before, and
only proceed to account.remove_relay when find_relay_by_url returns Some(relay).
| #[frb] | ||
| pub async fn send_message_to_group( | ||
| pubkey: &PublicKey, | ||
| group_id: whitenoise::GroupId, | ||
| pubkey: String, | ||
| group_id: String, | ||
| message: String, | ||
| kind: u16, | ||
| tags: Option<Vec<Tag>>, | ||
| ) -> Result<MessageWithTokensData, WhitenoiseError> { | ||
| ) -> Result<MessageWithTokens, ApiError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Avoid exposing nostr_sdk::Tag at the FRB boundary; accept Vec<Vec> and convert internally.
Passing external types through FRB is brittle. Take tag fields and build Tags inside Rust.
- use crate::api::{error::ApiError, utils::group_id_from_string};
+ use crate::api::{error::ApiError, utils::{group_id_from_string, tag_from_vec}};
@@
pub async fn send_message_to_group(
pubkey: String,
group_id: String,
message: String,
kind: u16,
- tags: Option<Vec<Tag>>,
+ tags: Option<Vec<Vec<String>>>,
) -> Result<MessageWithTokens, ApiError> {
@@
- let message_with_tokens = whitenoise
- .send_message_to_group(&account, &group_id, message, kind, tags)
+ let tags = tags
+ .map(|vv| vv.into_iter().map(tag_from_vec).collect::<Result<Vec<_>, ApiError>>())
+ .transpose()?;
+ let message_with_tokens = whitenoise
+ .send_message_to_group(&account, &group_id, message, kind, tags)
.await?;Implement in utils.rs:
pub fn tag_from_vec(fields: Vec<String>) -> Result<nostr_sdk::Tag, ApiError> {
// Reconstruct Tag from fields (match kind, etc.). Return ApiError on invalid input.
nostr_sdk::Tag::parse(fields).map_err(|e| ApiError::Validation { message: e.to_string() })
}To confirm no other FRB-exposed APIs still use Tag:
Also applies to: 245-247, 1-1
🏁 Script executed:
#!/bin/bash
rg -nP '\bOption<\s*Vec\s*<\s*Tag\s*>\s*>' -C2 --type=rustLength of output: 1534
Avoid exposing nostr_sdk::Tag at the FRB boundary
- Change
send_message_to_groupto acceptOption<Vec<Vec<String>>>and convert toVec<Tag>internally via atag_from_vechelper. - Implement in
utils.rs:pub fn tag_from_vec(fields: Vec<String>) -> Result<nostr_sdk::Tag, ApiError> { nostr_sdk::Tag::parse(fields) .map_err(|e| ApiError::Validation { message: e.to_string() }) }
- Update your flutter_rust_bridge configuration and regenerate
rust/src/frb_generated.rsso that noOption<Vec<Tag>>remains in the generated code.
Relevant diff in rust/src/api/messages.rs:
- use crate::api::{error::ApiError, utils::group_id_from_string};
+ use crate::api::{error::ApiError, utils::{group_id_from_string, tag_from_vec}};
@@
pub async fn send_message_to_group(
pubkey: String,
group_id: String,
message: String,
kind: u16,
- tags: Option<Vec<Tag>>,
+ tags: Option<Vec<Vec<String>>>,
) -> Result<MessageWithTokens, ApiError> {
@@
- let message_with_tokens = whitenoise
- .send_message_to_group(&account, &group_id, message, kind, tags)
+ let tags = tags
+ .map(|vv| vv.into_iter().map(tag_from_vec).collect::<Result<Vec<_>, ApiError>>())
+ .transpose()?;
+ let message_with_tokens = whitenoise
+ .send_message_to_group(&account, &group_id, message, kind, tags)
.await?;🤖 Prompt for AI Agents
In rust/src/api/messages.rs around lines 232 to 239, the FRB function currently
exposes nostr_sdk::Tag in its signature; change the signature to accept
Option<Vec<Vec<String>>> for tags and inside the function convert each
Vec<String> into a nostr_sdk::Tag using a helper (tag_from_vec) that maps parse
errors into ApiError::Validation, returning early on error; implement the helper
in utils.rs as described (taking Vec<String> and returning
Result<nostr_sdk::Tag, ApiError>) and use it to build a Vec<Tag> before
proceeding; finally update the flutter_rust_bridge config and regenerate
rust/src/frb_generated.rs so the generated boundary no longer contains
Option<Vec<Tag>>.
This is part one of moving things to the new bridge API methods. I'm sure I'm missing some things still
Summary by CodeRabbit
New Features
Refactor
Chores