---
description: "Riverpod conventions for TaskFlow"
alwaysApply: true
---

# Riverpod Standards — TaskFlow

## Provider types
| Type | Use case |
|------|----------|
| `AsyncNotifier` | Async state from Supabase |
| `Notifier` | Synchronous derived/local UI state |
| `StreamNotifier` | Real-time subscriptions |
| `@riverpod` function | Simple computed/derived values |

## Code generation (mandatory)
```dart
// ✅ Always use @riverpod annotation
@riverpod
class AuthNotifier extends _$AuthNotifier {
  @override
  Future<User?> build() => ref.watch(authRepositoryProvider).currentUser();

  Future<void> login(String email, String password) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(
      () => ref.read(authRepositoryProvider).login(email, password),
    );
  }
}

// ❌ Never write manual provider declarations
final authProvider = StateNotifierProvider<AuthNotifier, AsyncValue<User?>>(
  (ref) => AuthNotifier(),
); // DON'T DO THIS
```

## Rules
- `ref.watch()` inside `build()` ONLY — **never** `ref.read()` inside `build()`
- `ref.read(provider.notifier).method()` for mutations in gesture handlers
- `ref.invalidate(provider)` to refresh — never manually reset state to `AsyncLoading()`
- Family providers for parameterized data: `productDetailsProvider(productId)`
- Providers scoped at feature level; core providers in `lib/core/di/`

## AsyncValue in widgets
Every `AsyncValue` MUST handle all three states:
```dart
ref.watch(productsProvider).when(
  data:    (products) => ProductList(products: products),
  loading: ()         => const ProductListShimmer(),   // required
  error:   (e, _)     => ErrorWidget(error: e),         // required
)
```

## File locations in TaskFlow
- `lib/features/[feature]/[feature]_provider.dart` (generated: `[feature]_provider.g.dart`)
- `lib/features/[feature]/[feature]_repository.dart`
- Run `dart run build_runner watch` during development
