docs(02): create phase 2 household creation plans

Phase 2: Household Creation & Invites
- 6 plans in 4 waves covering SHARE-01 through SHARE-05
- Data models, repository pattern, and Supabase integration
- Database schema with RLS policies for multi-tenant isolation
- State management with Riverpod and business logic use cases
- Complete UI components for household management
- Navigation integration with authentication flow

Ready for execution: /gsd:execute-phase 2
This commit is contained in:
Dani B
2026-01-28 15:50:09 -05:00
parent 80c59f1a5f
commit e20858f608
7 changed files with 3023 additions and 0 deletions

View File

@@ -0,0 +1,676 @@
---
phase: 02-household-creation
plan: 06
type: execute
wave: 4
depends_on: [02-05, 02-03]
files_modified:
- lib/core/router/app_router.dart
- lib/features/home/presentation/pages/home_page.dart
- lib/providers/auth_provider.dart
- lib/main.dart
autonomous: true
must_haves:
truths:
- "Navigation system integrates household selection into app flow"
- "Auth state changes automatically load user households"
- "Home page displays current household context with navigation to household management"
- "Routing handles all household states (no household, single household, multiple households)"
- "Real-time household updates reflect across the app"
artifacts:
- path: "lib/core/router/app_router.dart"
provides: "Household-aware navigation routes"
contains: "HouseholdListRoute", "CreateHouseholdRoute", "JoinHouseholdRoute"
- path: "lib/features/home/presentation/pages/home_page.dart"
provides: "Home page with household context"
contains: "current household display", "switch household action"
- path: "lib/providers/auth_provider.dart"
provides: "Auth state with household integration"
contains: "household loading on auth change"
key_links:
- from: "lib/core/router/app_router.dart"
to: "lib/features/household/presentation/pages/household_list_page.dart"
via: "route definition"
pattern: "HouseholdListRoute.*HouseholdListPage"
- from: "lib/providers/auth_provider.dart"
to: "lib/features/household/providers/household_provider.dart"
via: "state synchronization"
pattern: "ref.read.*householdProvider.*loadUserHouseholds"
---
<objective>
Integrate household functionality into app navigation and authentication flow for seamless user experience.
Purpose: Connect household management with existing app architecture so users naturally transition from authentication to household setup and inventory management.
Output: Fully integrated household system with navigation, auth integration, and state management ready for production.
</objective>
<execution_context>
@~/.opencode/get-shit-done/workflows/execute-plan.md
@~/.opencode/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
# Navigation and Auth References
@lib/core/router/app_router.dart
@lib/features/home/presentation/pages/home_page.dart
@lib/providers/auth_provider.dart
@lib/main.dart
</context>
<tasks>
<task type="auto">
<name>Update Router with Household Routes</name>
<files>lib/core/router/app_router.dart</files>
<action>
Add household routes to existing router structure:
1. Import household pages:
```dart
import '../features/household/presentation/pages/household_list_page.dart';
import '../features/household/presentation/pages/create_household_page.dart';
import '../features/household/presentation/pages/join_household_page.dart';
import '../features/household/providers/household_provider.dart';
import '../providers/auth_provider.dart';
import '../features/household/domain/entities/household_entity.dart';
```
2. Add household route definitions:
```dart
// Household routes
static const householdList = '/households';
static const createHousehold = '/households/create';
static const joinHousehold = '/households/join';
```
3. Update router with household routes:
```dart
GoRouter router({required String initialLocation}) {
return GoRouter(
initialLocation: initialLocation,
redirect: (context, state) {
final auth = ref.read(authProvider);
// Handle unauthenticated users
if (auth.user == null) {
final isAuthRoute = state.location.startsWith('/auth') ||
state.location == '/';
if (!isAuthRoute) {
return '/auth/login';
}
return null;
}
// Authenticated user flow
final user = auth.user!;
// Check if user has households
final householdsAsync = ref.read(householdProvider);
final households = householdsAsync.maybeWhen(
data: (households) => households,
orElse: () => null,
);
// If households not loaded yet, don't redirect
if (households == null) return null;
// If no households, go to household list to create/join
if (households.isEmpty) {
final isHouseholdRoute = state.location.startsWith('/households');
if (!isHouseholdRoute) {
return HouseholdListRoute.householdList;
}
return null;
}
// Has households - check if current household is set
final currentHousehold = ref.read(currentHouseholdProvider);
if (currentHousehold == null) {
// Auto-select first household
ref.read(currentHouseholdProvider.notifier).state = households.first;
}
// Handle specific redirects
if (state.location == '/' || state.location == '/auth/login') {
return HomePage.route;
}
return null;
},
routes: [
// Existing auth routes...
// Household routes
GoRoute(
path: HouseholdListRoute.householdList,
name: 'household-list',
builder: (context, state) => const HouseholdListPage(),
),
GoRoute(
path: HouseholdListRoute.createHousehold,
name: 'create-household',
builder: (context, state) => const CreateHouseholdPage(),
),
GoRoute(
path: HouseholdListRoute.joinHousehold,
name: 'join-household',
builder: (context, state) => const JoinHouseholdPage(),
),
// Home route (existing)
GoRoute(
path: HomePage.route,
name: 'home',
builder: (context, state) => const HomePage(),
),
],
errorBuilder: (context, state) => Scaffold(
body: Center(
child: Text('Page not found: ${state.location}'),
),
),
);
}
```
4. Add household navigation helpers:
```dart
class HouseholdListRoute {
static const householdList = '/households';
static const createHousehold = '/households/create';
static const joinHousehold = '/households/join';
static void goToList(BuildContext context) {
GoRouter.of(context).push(householdList);
}
static void goToCreate(BuildContext context) {
GoRouter.of(context).push(createHousehold);
}
static void goToJoin(BuildContext context) {
GoRouter.of(context).push(joinHousehold);
}
}
```
Update auth redirect logic to handle household selection.
Include proper route guards for authenticated users with no households.
</action>
<verify>flutter analyze lib/core/router/app_router.dart passes</verify>
<done>Router includes household routes with proper authentication and household state guards</done>
</task>
<task type="auto">
<name>Update Auth Provider with Household Integration</name>
<files>lib/providers/auth_provider.dart</files>
<action>
Add household loading to auth state changes:
1. Import household provider:
```dart
import '../features/household/providers/household_provider.dart';
import '../features/household/providers/household_provider.dart' as household;
```
2. Add household loading to auth state changes:
```dart
class AuthProvider extends StateNotifier<AsyncValue<AuthState>> {
final AuthRepository _repository;
AuthProvider(this._repository) : super(const AsyncValue.loading()) {
_initialize();
}
Future<void> _initialize() async {
state = const AsyncValue.loading();
try {
final currentUser = _repository.currentUser;
if (currentUser == null) {
state = const AsyncValue.data(AuthState.unauthenticated());
return;
}
// User is authenticated, get their session
final session = await _repository.getCurrentSession();
if (session == null) {
state = const AsyncValue.data(AuthState.unauthenticated());
return;
}
state = AsyncValue.data(AuthState.authenticated(currentUser));
// Load user households after successful auth
_loadUserHouseholds(currentUser.id);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
Future<void> signIn(String email, String password) async {
state = const AsyncValue.loading();
try {
final user = await _repository.signIn(email, password);
state = AsyncValue.data(AuthState.authenticated(user));
// Load households after successful sign in
_loadUserHouseholds(user.id);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
Future<void> signUp(String email, String password) async {
state = const AsyncValue.loading();
try {
final user = await _repository.signUp(email, password);
state = AsyncValue.data(AuthState.authenticated(user));
// Load households after successful sign up
_loadUserHouseholds(user.id);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
Future<void> signOut() async {
try {
await _repository.signOut();
state = const AsyncValue.data(AuthState.unauthenticated());
// Clear household state
household.clearHouseholds();
ref.read(currentHouseholdProvider.notifier).state = null;
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
Future<void> _loadUserHouseholds(String userId) async {
try {
// This will trigger the household provider to load
final container = ProviderScope.containerOf(context!);
final householdNotifier = container.read(household.householdProvider.notifier);
await householdNotifier.loadUserHouseholds(userId);
} catch (e) {
// Household loading failure shouldn't break auth flow
// The household provider will handle its own error states
}
}
}
```
3. Update auth state to include household info:
```dart
class AuthState {
final User? user;
final bool isAuthenticated;
final bool hasHouseholds;
const AuthState({
this.user,
required this.isAuthenticated,
this.hasHouseholds = false,
});
factory AuthState.unauthenticated() => const AuthState(
isAuthenticated: false,
);
factory AuthState.authenticated(User user, {bool hasHouseholds = false}) => AuthState(
user: user,
isAuthenticated: true,
hasHouseholds: hasHouseholds,
);
AuthState copyWith({
User? user,
bool? isAuthenticated,
bool? hasHouseholds,
}) {
return AuthState(
user: user ?? this.user,
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
hasHouseholds: hasHouseholds ?? this.hasHouseholds,
);
}
}
```
Ensure household state is synchronized with authentication changes.
Include proper cleanup on sign out.
</action>
<verify>flutter analyze lib/providers/auth_provider.dart passes</verify>
<done>Auth provider integrates household loading with authentication state changes</done>
</task>
<task type="auto">
<name>Update Home Page with Household Context</name>
<files>lib/features/home/presentation/pages/home_page.dart</files>
<action>
Add household context and navigation to home page:
1. Add imports:
```dart
import '../../household/providers/household_provider.dart';
import '../../household/presentation/pages/household_list_page.dart';
import '../widgets/household_switcher.dart'; // We'll create this widget
```
2. Add household context to home page:
```dart
class HomePage extends ConsumerWidget {
const HomePage({Key? key}) : super(key: key);
static const String route = '/home';
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(authProvider).user;
final currentHousehold = ref.watch(currentHouseholdProvider);
final householdsAsync = ref.watch(householdProvider);
return Scaffold(
appBar: AppBar(
title: Row(
children: [
if (currentHousehold != null) ...[
CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.primary,
radius: 16,
child: Text(
currentHousehold.name.isNotEmpty
? currentHousehold.name[0].toUpperCase()
: 'H',
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
currentHousehold.name,
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'${currentHousehold.members.length} members',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
),
],
),
),
IconButton(
onPressed: () => _showHouseholdSwitcher(context),
icon: const Icon(Icons.keyboard_arrow_down),
tooltip: 'Switch Household',
),
] else ...[
const Text('Sage'),
],
],
),
actions: [
if (user != null)
PopupMenuButton<String>(
onSelected: (value) {
switch (value) {
case 'households':
HouseholdListRoute.goToList(context);
break;
case 'logout':
_showLogoutConfirmation(context);
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'households',
child: Row(
children: [
Icon(Icons.home),
SizedBox(width: 8),
Text('Manage Households'),
],
),
),
const PopupMenuItem(
value: 'logout',
child: Row(
children: [
Icon(Icons.logout),
SizedBox(width: 8),
Text('Log Out'),
],
),
),
],
),
],
),
body: householdsAsync.when(
loading: () => const Center(
child: CircularProgressIndicator(),
),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error,
size: 64,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 16),
Text(
'Failed to load households',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
error.toString(),
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => Navigator.of(context).pushNamed('/households'),
child: const Text('Manage Households'),
),
],
),
),
data: (households) {
if (households.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.home_outlined,
size: 96,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3),
),
const SizedBox(height: 24),
Text(
'No Household Selected',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
'Create or join a household to start managing your inventory',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: () => HouseholdListRoute.goToList(context),
icon: const Icon(Icons.home),
label: const Text('Manage Households'),
),
],
),
);
}
if (currentHousehold == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Normal home page content with inventory
return _buildInventoryContent(context, ref, currentHousehold);
},
),
);
}
Widget _buildInventoryContent(BuildContext context, WidgetRef ref, HouseholdEntity household) {
// This would be the normal inventory/home content
// For now, show placeholder
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.inventory_2_outlined,
size: 96,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 24),
Text(
'Welcome to ${household.name}!',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
'Your inventory will appear here once you start adding items',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
const Text(
'Phase 3 will bring barcode scanning and item management',
style: TextStyle(fontStyle: FontStyle.italic),
textAlign: TextAlign.center,
),
],
),
);
}
void _showHouseholdSwitcher(BuildContext context) {
// This would show a bottom sheet or dialog to switch households
// For now, navigate to household list
HouseholdListRoute.goToList(context);
}
void _showLogoutConfirmation(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Log Out'),
content: const Text('Are you sure you want to log out?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
ref.read(authProvider.notifier).signOut();
},
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.error,
),
child: const Text('Log Out'),
),
],
),
);
}
}
```
Add household context display and switching functionality.
Include proper loading and error states.
Add navigation to household management.
</action>
<verify>flutter analyze lib/features/home/presentation/pages/home_page.dart passes</verify>
<done>Home page includes household context with navigation to household management</done>
</task>
<task type="auto">
<name>Update Main Provider Registration</name>
<files>lib/main.dart</files>
<action>
Register household providers in main.dart:
1. Add household provider imports:
```dart
import 'features/household/providers/household_provider.dart';
```
2. Add household providers to ProviderScope overrides:
```dart
ProviderScope(
overrides: [
// Auth providers (existing)
authRepositoryProvider.overrideWithValue(AuthRepositoryImpl(client)),
authProvider.overrideWithValue(AuthProvider(authRepository)),
// Household providers
householdRemoteDatasourceProvider.overrideWithValue(HouseholdRemoteDatasource(client)),
householdRepositoryProvider.overrideWithValue(HouseholdRepositoryImpl(householdRemoteDatasource)),
],
child: MyApp(),
)
```
Ensure all household providers are properly initialized with dependencies.
</action>
<verify>flutter analyze lib/main.dart passes</verify>
<done>Main app includes household provider registration with proper dependencies</done>
</task>
</tasks>
<verification>
1. Router includes household routes with proper authentication and household state guards
2. Auth provider integrates household loading with authentication state changes
3. Home page displays current household context with navigation to household management
4. Household state is properly initialized and synchronized across the app
5. Navigation flow handles all household states seamlessly
</verification>
<success_criteria>
Household functionality is fully integrated into app navigation and authentication flow with seamless user experience ready for production.
</success_criteria>
<output>
After completion, create `.planning/phases/02-household-creation/02-06-SUMMARY.md` with integration completion summary
</output>