Skip to content

Conversation

@Quwaysim
Copy link
Contributor

@Quwaysim Quwaysim commented Aug 29, 2025

Description

Improved precommit checks for releases
Added all types of envs to gitignore
Improved logic in some providers.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore
  • 🧪 Tests

Checklist

  • Run just precommit to ensure that formatting and linting are correct
  • Run just check-flutter-coverage to ensure that flutter coverage rules are passing
  • Updated the CHANGELOG.md file with your changes (if they affect the user experience)

Summary by CodeRabbit

  • Bug Fixes

    • Create Profile: loading indicator now clears when no active account is found.
    • Edit Profile: safer state handling prevents null issues; clearer error messages and reliable saving-state resets.
    • Follows: prevent overlapping loads for smoother updates.
  • Refactor

    • Follows scheduling switched to microtask for consistent timing.
    • Metadata: standardized exception type when no active account is present.
  • Chores

    • Broader env ignore rules while allowing .env example templates.
    • Added precommit-check and added pre-release validations and messaging updates.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 29, 2025

Walkthrough

Widened env-related ignores, added a precommit-check recipe and stricter pre-release validations in the Justfile, and applied safer state/error handling across profile providers, changed follows scheduling to microtasks with a loading guard, and switched a thrown env-related error to StateError.

Changes

Cohort / File(s) Summary of Changes
Env ignore rules
\.gitignore
Broadened env ignores to .env.*, .envrc, and *.env; preserved .env; added unignore rules for .env.example and .env.*.example.
Build & release tooling
justfile
Added precommit-check (non-fixing subset). Release workflow now ensures a clean working tree, verifies scripts/build.sh exists and is executable, uses just precommit-check, and updated release messaging.
Create profile provider
lib/config/providers/create_profile_screen_provider.dart
On early exit when no active account, explicitly reset isLoading to false before returning and show error toast.
Edit profile provider
lib/config/providers/edit_profile_screen_provider.dart
Switched to null-safe state.asData?.value usage for previous state; set isSaving via a data-state copy; on error, capture message (including ApiError text) into data-state error and clear flags; finally block now only mutates if a non-null data state exists.
Follows provider
lib/config/providers/follows_provider.dart
Replaced WidgetsBinding.instance.addPostFrameCallback with Future.microtask; removed flutter/widgets.dart import; added early-return guard if state.isLoading to avoid concurrent loads.
Metadata cache
lib/config/providers/metadata_cache_provider.dart
Replaced throw 'No active account found' with throw StateError('No active account found') to use a typed exception.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant EPSN as EditProfileScreenNotifier
  participant API as Profile API

  U->>EPSN: updateProfileData(newData)
  EPSN->>EPSN: prev = state.asData?.value ?? default
  EPSN->>EPSN: state = Data(prev with isSaving=true, clear error/stack)
  EPSN->>API: saveProfile(newData)
  alt success
    API-->>EPSN: saved profile
    EPSN->>EPSN: state = Data(updated, isSaving=false)
  else error
    API-->>EPSN: throw e, st
    EPSN->>EPSN: message = e is ApiError ? await e.messageText() : e.toString()
    EPSN->>EPSN: state = Data(prev with isSaving=false, error=message, stack=st)
  end
Loading
sequenceDiagram
  autonumber
  participant A as ActiveAccountWatcher
  participant FP as FollowsProvider

  A-->>FP: activePubkey changed
  note right of FP #eef2ff: Schedule with microtask (was post-frame)
  FP->>FP: clearFollows()
  FP->>FP: loadFollows()
  alt state.isLoading == true
    FP-->>FP: return early
  else
    FP->>FP: state.isLoading = true
    FP->>API: fetch follows
    API-->>FP: result / error
    FP->>FP: update state, isLoading=false
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • untreu2
  • codeswot

Poem

I twitch my whiskers, tap the keys—
New just hops in tidy breeze.
Env dots hide, examples stay,
Profiles save the safer way.
Microtask hops, StateError neat—🐇✨


📜 Recent 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 49a90a2 and 5000e5c.

⛔ Files ignored due to path filters (1)
  • ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • lib/config/providers/create_profile_screen_provider.dart (1 hunks)
  • lib/config/providers/follows_provider.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/config/providers/create_profile_screen_provider.dart
  • lib/config/providers/follows_provider.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Flutter CI
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/coderabbit-suggestions

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
lib/config/providers/create_profile_screen_provider.dart (1)

41-43: Localize user-facing strings.

Replace hardcoded toasts with AppLocalizations-backed messages (or inject a translation service via getIt), per project guidelines.

I can wire a small I18n helper for providers that lack BuildContext.

Also applies to: 55-56, 95-96, 109-110

lib/config/providers/metadata_cache_provider.dart (2)

288-292: Make isContactCached async and update all call sites

  • Apply this diff in lib/config/providers/metadata_cache_provider.dart to use _getStandardizedNpub and return Future:
    - bool isContactCached(String publicKey) {
    -   final normalizedKey = _normalizePublicKey(publicKey);
    -   final cached = state.cache[normalizedKey];
    -   return cached != null && !cached.isExpired;
    - }
    + Future<bool> isContactCached(String publicKey) async {
    +   final standardNpub = await _getStandardizedNpub(publicKey);
    +   final cached = state.cache[standardNpub];
    +   return cached != null && !cached.isExpired;
    + }
  • In lib/examples/metadata_cache_examples.dart:
    • Change the example wrapper to Future<bool> isContactCached(...) async { … }
    • Add await when calling cache.isContactCached(publicKey) (e.g. before await expensiveNetworkCall())
  • In docs/METADATA_CACHING_SOLUTION.md: update all snippets to use await metadataCache.isContactCached(publicKey) within async functions

317-328: Use standardized npub in updateCachedMetadata
Change updateCachedMetadata to Future<void>, call await _getStandardizedNpub(publicKey) instead of _normalizePublicKey, and update all call sites in lib/utils/metadata_cache_utils.dart and lib/examples/metadata_cache_examples.dart to await cache.updateCachedMetadata(...).

🧹 Nitpick comments (8)
lib/config/providers/create_profile_screen_provider.dart (2)

60-93: Handle first-time profile creation (initialMetadata == null).

Current flow silently does nothing if there's no existing metadata. Either construct new FlutterMetadata from inputs or route to a dedicated “create” path.

Would you like me to draft a minimal create-path that sets displayName/about/picture when initialMetadata is null?


46-53: Declare explicit local variable types.

Guidelines require explicit types for locals (e.g., FlutterMetadata?, ActiveAccountState, String?). Replace inferred types in these spots.

Also applies to: 103-107

.gitignore (1)

15-19: Broader env ignore rules look good; add direnv cache too.

Include .direnv/ so developer-local env caches aren’t committed.

Apply this diff:

 .env
 .env.*
 .envrc
+/.direnv/
 *.env
 !.env.example
 !.env.*.example
lib/config/providers/metadata_cache_provider.dart (1)

103-150: Consider surfacing “no active account” distinctly.

The throw is caught and converted to an “Unknown User” fallback, masking auth state issues. Either (a) check active account before try/catch and return a typed failure to caller, or (b) attach a sentinel to the fallback (e.g., error field in state) so callers can react.

justfile (1)

217-238: Release preflight checks are a good safety net.

Clean working tree and build script checks reduce failed releases. Optionally call just doctor upfront to fail fast on missing tools.

Apply this small addition:

 release:
     @echo "🔨 Building versioned release..."
+    @echo "🏥 Running environment doctor..."
+    just doctor
     @echo "🔍 Verifying working tree is clean..."
lib/config/providers/edit_profile_screen_provider.dart (3)

56-61: Prefer asData and explicit typing; avoid mutating non-data states in finally.

Use state.asData?.value (not state.value) and add an explicit type to satisfy the code style and make intent clear.

-      final stateValue = state.value;
-      if (stateValue != null) {
-        state = AsyncValue.data(
-          stateValue.copyWith(isSaving: false, stackTrace: null, error: null),
-        );
-      }
+      final ProfileState? current = state.asData?.value;
+      if (current != null) {
+        state = AsyncValue.data(
+          current.copyWith(isSaving: false, stackTrace: null, error: null),
+        );
+      }

117-119: Good state preservation before save; add explicit type per guidelines.

Declare the variable type explicitly.

-    final previousState = state.asData?.value ?? const ProfileState();
+    final ProfileState previousState = state.asData?.value ?? const ProfileState();

53-53: Fix log context label.

Method name is fetchProfileData, not loadProfileData.

-      _logger.severe('loadProfileData', e, st);
+      _logger.severe('fetchProfileData', e, st);
📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 248bcbb and 49a90a2.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • .gitignore (1 hunks)
  • justfile (2 hunks)
  • lib/config/providers/create_profile_screen_provider.dart (1 hunks)
  • lib/config/providers/edit_profile_screen_provider.dart (3 hunks)
  • lib/config/providers/follows_provider.dart (2 hunks)
  • lib/config/providers/metadata_cache_provider.dart (1 hunks)
🧰 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/config/providers/follows_provider.dart
  • lib/config/providers/metadata_cache_provider.dart
  • lib/config/providers/create_profile_screen_provider.dart
  • lib/config/providers/edit_profile_screen_provider.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 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/follows_provider.dart
🪛 GitHub Actions: CI
lib/config/providers/follows_provider.dart

[error] 1-1: Dart formatter updated this file. Run 'dart format -w' to apply formatting. Command: 'dart format --set-exit-if-changed lib/ integration_test/'.

lib/config/providers/create_profile_screen_provider.dart

[error] 1-1: Dart formatter updated this file. Run 'dart format -w' to apply formatting. Command: 'dart format --set-exit-if-changed lib/ integration_test/'.

🔇 Additional comments (6)
lib/config/providers/create_profile_screen_provider.dart (2)

56-57: Good catch: clear loading state on early return.

Prevents the spinner from sticking when no active account exists.


1-1: Format lib/config/providers/create_profile_screen_provider.dart
Run dart format -w lib/config/providers/create_profile_screen_provider.dart locally to satisfy CI formatting checks.

lib/config/providers/metadata_cache_provider.dart (1)

116-117: Prefer typed exceptions—nice improvement.

Switching to StateError improves signal in logs/handlers.

lib/config/providers/follows_provider.dart (2)

44-56: Switch to microtask: verify no “set state during build” hazards.

Microtasks can run before the next frame; ensure no rebuild exceptions on listener setup. If issues arise, use Future(() { ... }) instead to defer to next event loop.

I can provide a quick test harness to simulate pubkey flips and assert no exceptions.


1-1: Run Dart formatter on follows_provider.dart
Please run dart format -w lib/config/providers/follows_provider.dart locally to apply the required formatting for CI.

justfile (1)

18-28: precommit-check: solid CI-parity set.

Nice separation from auto-fixing precommit.

Comment on lines +175 to 178
final prev = state.asData?.value ?? const ProfileState();
final message = e is ApiError ? (await e.messageText()) : e.toString();
state = AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Standardize error handling: keep errors inside ProfileState everywhere and add explicit types.

This block correctly retains form data by storing the error in ProfileState instead of flipping to AsyncValue.error. Apply this pattern consistently (e.g., authentication/active-account failures and image-pick failure currently use AsyncValue.error) to avoid UI state loss and inconsistency. Also add explicit types.

-      final prev = state.asData?.value ?? const ProfileState();
-      final message = e is ApiError ? (await e.messageText()) : e.toString();
+      final ProfileState prev = state.asData?.value ?? const ProfileState();
+      final String message = e is ApiError ? (await e.messageText()) : e.toString();
       state = AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));

Example helper you can add to this notifier to replace all current AsyncValue.error(...) sites:

void _setLocalError(String message, StackTrace st) {
  final ProfileState prev = state.asData?.value ?? const ProfileState();
  state = AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));
}

Then replace, for example:

  • Lines 25–29 (“Not authenticated”)
  • Lines 37–38 (“No active account found”)
  • Line 54 (catch in fetchProfileData)
  • Lines 112–113 (image pick failure)
  • Lines 124–125, 133–135 (auth/active account in updateProfileData)

with _setLocalError(...) to keep the UI data intact.

🤖 Prompt for AI Agents
In lib/config/providers/edit_profile_screen_provider.dart around lines 175–178,
error handling flips to AsyncValue.error in other places which loses UI form
data and lacks explicit types; add a typed helper like _setLocalError(String
message, StackTrace st) that reads the previous ProfileState (final ProfileState
prev = state.asData?.value ?? const ProfileState()) and sets state =
AsyncValue.data(prev.copyWith(isSaving: false, error: message, stackTrace: st));
then replace all current AsyncValue.error(...) and untyped error assignments at
the listed locations (lines 25–29 “Not authenticated”, 37–38 “No active account
found”, the catch at line 54 in fetchProfileData, lines 112–113 image pick
failure, and lines 124–125 and 133–135 in updateProfileData) with calls to
_setLocalError(message, st) using explicit String and StackTrace types so errors
remain inside ProfileState and form data is preserved.

Comment on lines 72 to 75
if (state.isLoading) return;

state = state.copyWith(isLoading: true, error: null);

Copy link
Contributor

@coderabbitai coderabbitai bot Aug 29, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Re-entrancy guard can drop needed refreshes.

If loadFollows is in-flight, subsequent calls (e.g., after add/remove) return early, leaving stale state. Track and share a single in-flight Future instead of a boolean guard.

Apply this diff within loadFollows:

-  Future<void> loadFollows() async {
-    if (state.isLoading) return;
-    
-    state = state.copyWith(isLoading: true, error: null);
+  Future<void> loadFollows() async {
+    // De-duplicate concurrent calls by sharing a single in-flight Future.
+    if (_inflightLoad != null) return _inflightLoad!;
+    state = state.copyWith(isLoading: true, error: null);

And add the following to the class:

// At class level:
Future<void>? _inflightLoad;

Refactor the body to set/clear the shared future:

_inflightLoad = _doLoadFollows();
return _inflightLoad!;

Move existing logic into _doLoadFollows() and in finally set:

finally {
  state = state.copyWith(isLoading: false);
  _inflightLoad = null;
}
🤖 Prompt for AI Agents
In lib/config/providers/follows_provider.dart around lines 72 to 75, the current
boolean re-entrancy guard (isLoading) causes subsequent callers to return early
and miss needed refreshes; instead add a class-level Future<void>?
_inflightLoad; move the existing load logic into a new private method
_doLoadFollows(), refactor loadFollows to check for and return the shared
_inflightLoad if present, otherwise set _inflightLoad = _doLoadFollows() and
return it; ensure _doLoadFollows() sets state = state.copyWith(isLoading: true,
error: null) at start and in a finally block clears the loading flag and sets
_inflightLoad = null.

Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer the guard you added, this coderabbit suggestion seems less readable to me ..

Copy link
Contributor

Choose a reason for hiding this comment

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

@josefinalliende Thank you for the feedback! You're absolutely right that the simple boolean guard is much more readable and maintainable. The straightforward if (state.isLoading) return; approach is clear, easy to understand, and effectively prevents concurrent calls without adding unnecessary complexity.

Your preference for readability over the more complex Future-based approach is a solid engineering decision. The simple guard accomplishes the primary goal of preventing re-entrant calls while keeping the code clean and maintainable.


✏️ Learnings added
Learnt from: josefinalliende
PR: parres-hq/whitenoise_flutter#547
File: lib/config/providers/follows_provider.dart:0-0
Timestamp: 2025-08-29T19:58:49.679Z
Learning: In the whitenoise_flutter project, the user josefinalliende prefers simple boolean guards over complex Future-based re-entrancy protection for readability and maintainability reasons.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

@josefinalliende josefinalliende left a comment

Choose a reason for hiding this comment

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

✅ LGTM. Tried it locally and it seems to me that master and this branch have the same behaviour

Comment on lines 72 to 75
if (state.isLoading) return;

state = state.copyWith(isLoading: true, error: null);

Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer the guard you added, this coderabbit suggestion seems less readable to me ..

@Quwaysim Quwaysim merged commit 3b46956 into master Aug 29, 2025
2 checks passed
@josefinalliende josefinalliende deleted the refactor/coderabbit-suggestions branch August 30, 2025 00:01
@coderabbitai coderabbitai bot mentioned this pull request Sep 2, 2025
11 tasks
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.

4 participants