diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index f177ffb..c8092c4 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -60,6 +60,18 @@ Plans:
**Dependencies:** Phase 1 (AUTH required to create households)
+**Plans:** 6 plans in 4 waves
+
+Plans:
+- [ ] 02-01-PLAN.md — Household data models, repository pattern, and Supabase integration
+- [ ] 02-02-PLAN.md — Database schema with RLS policies and household isolation
+- [ ] 02-03-PLAN.md — Household state management and use cases with Riverpod
+- [ ] 02-04-PLAN.md — Household UI components (cards, invite dialogs, creation page)
+- [ ] 02-05-PLAN.md — Household management UI (join flow, list hub, navigation)
+- [ ] 02-06-PLAN.md — Navigation integration and auth state synchronization
+
+**Status:** Pending - Ready for execution
+
---
### Phase 3: Barcode Scanning & Product Lookup
diff --git a/.planning/phases/02-household-creation/02-01-PLAN.md b/.planning/phases/02-household-creation/02-01-PLAN.md
new file mode 100644
index 0000000..392fa9d
--- /dev/null
+++ b/.planning/phases/02-household-creation/02-01-PLAN.md
@@ -0,0 +1,266 @@
+---
+phase: 02-household-creation
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - lib/features/household/domain/models/household_models.dart
+ - lib/features/household/data/datasources/household_remote_datasource.dart
+ - lib/features/household/data/repositories/household_repository_impl.dart
+ - lib/features/household/domain/repositories/household_repository.dart
+ - lib/features/household/domain/entities/household_entity.dart
+autonomous: true
+
+must_haves:
+ truths:
+ - "Household data model supports creation, membership, and invite code functionality"
+ - "Repository interface defines household operations without implementation details"
+ - "Remote datasource handles Supabase household table operations"
+ - "Entity model separates business logic from data layer"
+ artifacts:
+ - path: "lib/features/household/domain/models/household_models.dart"
+ provides: "Household, HouseholdMember, InviteCode data models"
+ contains: "class Household", "class HouseholdMember", "class InviteCode"
+ - path: "lib/features/household/domain/repositories/household_repository.dart"
+ provides: "HouseholdRepository interface"
+ contains: "abstract class HouseholdRepository", "createHousehold", "joinHousehold", "generateInviteCode"
+ - path: "lib/features/household/data/repositories/household_repository_impl.dart"
+ provides: "Supabase implementation of household operations"
+ contains: "class HouseholdRepositoryImpl", "implements HouseholdRepository"
+ - path: "lib/features/household/data/datasources/household_remote_datasource.dart"
+ provides: "Supabase client wrapper for household operations"
+ contains: "class HouseholdRemoteDatasource", "SupabaseClient"
+ - path: "lib/features/household/domain/entities/household_entity.dart"
+ provides: "Business logic entities for household operations"
+ contains: "class HouseholdEntity", "class HouseholdMemberEntity"
+ key_links:
+ - from: "lib/features/household/data/repositories/household_repository_impl.dart"
+ to: "lib/features/household/data/datasources/household_remote_datasource.dart"
+ via: "constructor injection"
+ pattern: "HouseholdRemoteDatasource.*datasource"
+ - from: "lib/features/household/data/repositories/household_repository_impl.dart"
+ to: "lib/features/household/domain/models/household_models.dart"
+ via: "model mapping"
+ pattern: "Household.*from.*HouseholdModel"
+---
+
+
+Create the foundation for household management with proper data models, repository pattern, and Supabase integration.
+
+Purpose: Establish clean architecture patterns for household operations that support multi-tenant isolation and real-time sync requirements.
+Output: Complete data layer with models, repository interface, and Supabase datasource ready for household operations.
+
+
+
+@~/.opencode/get-shit-done/workflows/execute-plan.md
+@~/.opencode/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+# Phase 1 Architecture References
+@lib/features/authentication/data/repositories/auth_repository_impl.dart
+@lib/features/authentication/domain/repositories/auth_repository.dart
+@lib/features/authentication/domain/models/user_models.dart
+@lib/features/authentication/domain/entities/user_entity.dart
+
+
+
+
+
+ Create Household Data Models
+ lib/features/household/domain/models/household_models.dart
+
+ Create household data models following auth model patterns:
+
+ 1. HouseholdModel with:
+ - id (UUID)
+ - name (String, required)
+ - createdAt (DateTime)
+ - updatedAt (DateTime)
+ - createdBy (UUID, references user)
+
+ 2. HouseholdMemberModel with:
+ - id (UUID)
+ - householdId (UUID)
+ - userId (UUID)
+ - role (enum: owner, editor, viewer)
+ - joinedAt (DateTime)
+
+ 3. InviteCodeModel with:
+ - id (UUID)
+ - householdId (UUID)
+ - code (String, 8 chars, unique)
+ - createdAt (DateTime)
+ - expiresAt (DateTime, 30 days from creation)
+ - createdBy (UUID)
+ - usedBy (UUID?, nullable)
+ - usedAt (DateTime?, nullable)
+
+ Include fromJson/toJson methods, copyWith methods, and proper null safety.
+ Reference auth model patterns for consistency.
+
+ flutter analyze lib/features/household/domain/models/household_models.dart passes
+ Household data models compile with proper JSON serialization and validation
+
+
+
+ Create Household Entity Models
+ lib/features/household/domain/entities/household_entity.dart
+
+ Create entity models separating business logic from data persistence:
+
+ 1. HouseholdEntity with:
+ - Core properties (same as model but without JSON methods)
+ - List members
+ - InviteCode? currentInvite (if user is owner)
+ - bool isCurrentUserOwner
+
+ 2. HouseholdMemberEntity with:
+ - id, householdId, userId, role, joinedAt
+ - User? user (optional populated user data)
+ - bool isCurrentUser
+
+ Include business logic methods:
+ - canInviteMembers() (owner/editor only)
+ - canRemoveMember() (owner only, can't remove self if last owner)
+ - getRoleDisplayName()
+
+ Follow auth entity patterns for consistency.
+
+ flutter analyze lib/features/household/domain/entities/household_entity.dart passes
+ Household entities compile with business logic methods and proper separation from data models
+
+
+
+ Create Household Repository Interface
+ lib/features/household/domain/repositories/household_repository.dart
+
+ Create abstract repository defining household operations:
+
+ abstract class HouseholdRepository {
+ Future createHousehold(String name, String userId);
+ Future> getUserHouseholds(String userId);
+ Future getHouseholdById(String householdId);
+ Future generateInviteCode(String householdId, String createdBy);
+ Future joinHousehold(String inviteCode, String userId);
+ Future leaveHousehold(String householdId, String userId);
+ Future removeMember(String householdId, String memberUserId);
+ Future updateMemberRole(String householdId, String memberUserId, HouseholdRole role);
+ Future> getHouseholdMembers(String householdId);
+ Future getActiveInviteCode(String householdId);
+ Future revokeInviteCode(String inviteCodeId);
+ }
+
+ Include Household enum:
+ enum HouseholdRole { owner, editor, viewer }
+
+ Follow auth repository interface patterns.
+
+ flutter analyze lib/features/household/domain/repositories/household_repository.dart passes
+ Household repository interface defines all required operations with proper typing
+
+
+
+ Create Household Remote Datasource
+ lib/features/household/data/datasources/household_remote_datasource.dart
+
+ Create Supabase datasource implementing low-level data operations:
+
+ class HouseholdRemoteDatasource {
+ final SupabaseClient client;
+
+ HouseholdRemoteDatasource(this.client);
+
+ // Core CRUD operations
+ Future
+ flutter analyze lib/features/household/data/datasources/household_remote_datasource.dart passes
+ Household remote datasource provides low-level Supabase operations with proper error handling
+
+
+
+ Create Household Repository Implementation
+ lib/features/household/data/repositories/household_repository_impl.dart
+
+ Create repository implementation using datasource and models:
+
+ class HouseholdRepositoryImpl implements HouseholdRepository {
+ final HouseholdRemoteDatasource datasource;
+
+ HouseholdRepositoryImpl(this.datasource);
+
+ @override
+ Future createHousehold(String name, String userId) async {
+ // Create household record
+ // Add owner as member
+ // Return populated entity
+ }
+
+ @override
+ Future generateInviteCode(String householdId, String createdBy) async {
+ // Generate 8-character unique code
+ // Check for collisions
+ // Set expiry to 30 days
+ // Revoke any existing codes
+ }
+
+ @override
+ Future joinHousehold(String inviteCode, String userId) async {
+ // Validate invite code
+ // Check expiry
+ // Add member with 'editor' role
+ // Mark invite as used
+ // Return household entity
+ }
+
+ // Implement all other interface methods...
+ }
+
+ Include invite code generation logic (8 chars, alphanumeric, collision checking).
+ Handle business rules (only owners can generate invites, can't leave if last owner, etc.).
+ Map between models and entities with proper validation.
+
+ flutter analyze lib/features/household/data/repositories/household_repository_impl.dart passes
+ Household repository implementation provides business logic and data mapping with validation
+
+
+
+
+
+1. All models compile with proper JSON serialization and null safety
+2. Repository interface defines all required operations from SHARE-01 through SHARE-05
+3. Remote datasource uses Supabase client for household table operations
+4. Repository implementation includes business rules for invite codes and member management
+5. Data layer follows clean architecture with proper separation of concerns
+
+
+
+Household data layer is complete with models, repository interface, and Supabase implementation ready for UI integration and invite code generation.
+
+
+
\ No newline at end of file
diff --git a/.planning/phases/02-household-creation/02-02-PLAN.md b/.planning/phases/02-household-creation/02-02-PLAN.md
new file mode 100644
index 0000000..73d15b6
--- /dev/null
+++ b/.planning/phases/02-household-creation/02-02-PLAN.md
@@ -0,0 +1,433 @@
+---
+phase: 02-household-creation
+plan: 02
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - lib/features/household/data/datasources/household_remote_datasource.dart
+ - lib/features/household/domain/repositories/household_repository.dart
+ - lib/features/household/data/repositories/household_repository_impl.dart
+ - lib/features/household/domain/models/household_models.dart
+ - lib/features/household/domain/entities/household_entity.dart
+autonomous: true
+
+must_haves:
+ truths:
+ - "Supabase database schema supports households, members, and invite codes with proper relationships"
+ - "Row-Level Security policies isolate household data at database layer"
+ - "Database constraints prevent duplicate members and invalid invite codes"
+ - "Realtime subscriptions enable household member sync"
+ artifacts:
+ - path: "supabase/migrations/001_create_household_tables.sql"
+ provides: "Database schema for household functionality"
+ contains: "CREATE TABLE households", "CREATE TABLE household_members", "CREATE TABLE invite_codes"
+ - path: "supabase/migrations/002_household_rls_policies.sql"
+ provides: "Row-Level Security policies for household data isolation"
+ contains: "CREATE POLICY", "ROW LEVEL SECURITY"
+ - path: "supabase/migrations/003_household_indexes.sql"
+ provides: "Performance indexes for household queries"
+ contains: "CREATE INDEX", "household_id", "user_id"
+ key_links:
+ - from: "lib/features/household/data/repositories/household_repository_impl.dart"
+ to: "supabase/migrations/001_create_household_tables.sql"
+ via: "table operations"
+ pattern: "from.*households"
+ - from: "supabase/migrations/002_household_rls_policies.sql"
+ to: "supabase/migrations/001_create_household_tables.sql"
+ via: "security policies"
+ pattern: "POLICY.*FOR.*households"
+---
+
+
+Create Supabase database schema with proper household isolation, member relationships, and invite code management.
+
+Purpose: Establish database foundation that enforces multi-tenant security and supports real-time household synchronization requirements.
+Output: Complete database schema with tables, RLS policies, and indexes for household operations.
+
+
+
+@~/.opencode/get-shit-done/workflows/execute-plan.md
+@~/.opencode/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/research/ARCHITECTURE.md
+
+# Database Architecture References
+@lib/features/authentication/data/repositories/auth_repository_impl.dart
+
+
+
+
+
+ Create Household Tables Migration
+ supabase/migrations/001_create_household_tables.sql
+
+ Create database schema for household functionality:
+
+ 1. households table:
+ ```sql
+ CREATE TABLE households (
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
+ name TEXT NOT NULL CHECK (length(name) >= 1 AND length(name) <= 100),
+ created_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
+ created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
+ updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
+ );
+
+ CREATE TRIGGER households_updated_at
+ BEFORE UPDATE ON households
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+ ```
+
+ 2. household_members table:
+ ```sql
+ CREATE TABLE household_members (
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
+ household_id UUID NOT NULL REFERENCES households(id) ON DELETE CASCADE,
+ user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
+ role TEXT NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')),
+ joined_at TIMESTAMPTZ DEFAULT now() NOT NULL,
+ UNIQUE(household_id, user_id)
+ );
+ ```
+
+ 3. invite_codes table:
+ ```sql
+ CREATE TABLE invite_codes (
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
+ household_id UUID NOT NULL REFERENCES households(id) ON DELETE CASCADE,
+ code TEXT NOT NULL UNIQUE CHECK (length(code) = 8),
+ created_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
+ created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
+ expires_at TIMESTAMPTZ NOT NULL,
+ used_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
+ used_at TIMESTAMPTZ,
+ CONSTRAINT valid_invite_code CHECK (
+ (used_by IS NULL AND used_at IS NULL) OR
+ (used_by IS NOT NULL AND used_at IS NOT NULL)
+ )
+ );
+ ```
+
+ Follow Supabase migration patterns and include proper constraints.
+
+ supabase db push --dry-run shows valid SQL syntax
+ Household tables created with proper relationships and constraints
+
+
+
+ Create Household RLS Policies
+ supabase/migrations/002_household_rls_policies.sql
+
+ Create Row-Level Security policies for household data isolation:
+
+ 1. Enable RLS on all household tables:
+ ```sql
+ ALTER TABLE households ENABLE ROW LEVEL SECURITY;
+ ALTER TABLE household_members ENABLE ROW LEVEL SECURITY;
+ ALTER TABLE invite_codes ENABLE ROW LEVEL SECURITY;
+ ```
+
+ 2. households table policies:
+ ```sql
+ -- Users can view households they belong to
+ CREATE POLICY "Users can view their households" ON households
+ FOR SELECT USING (
+ id IN (
+ SELECT household_id FROM household_members
+ WHERE user_id = auth.uid()
+ )
+ );
+
+ -- Users can insert households (will be owner via member insertion)
+ CREATE POLICY "Users can create households" ON households
+ FOR INSERT WITH CHECK (created_by = auth.uid());
+
+ -- Only household owners can update households
+ CREATE POLICY "Owners can update households" ON households
+ FOR UPDATE USING (
+ id IN (
+ SELECT household_id FROM household_members
+ WHERE user_id = auth.uid() AND role = 'owner'
+ )
+ );
+ ```
+
+ 3. household_members table policies:
+ ```sql
+ -- Users can view members of their households
+ CREATE POLICY "Users can view household members" ON household_members
+ FOR SELECT USING (
+ household_id IN (
+ SELECT household_id FROM household_members
+ WHERE user_id = auth.uid()
+ )
+ );
+
+ -- Users can insert themselves as members (via join flow)
+ CREATE POLICY "Users can join households" ON household_members
+ FOR INSERT WITH CHECK (user_id = auth.uid());
+
+ -- Only owners can manage member roles and remove members
+ CREATE POLICY "Owners can manage members" ON household_members
+ FOR UPDATE USING (
+ household_id IN (
+ SELECT household_id FROM household_members
+ WHERE user_id = auth.uid() AND role = 'owner'
+ )
+ );
+
+ -- Only owners can remove members (except themselves if last owner handled in app logic)
+ CREATE POLICY "Owners can remove members" ON household_members
+ FOR DELETE USING (
+ household_id IN (
+ SELECT household_id FROM household_members
+ WHERE user_id = auth.uid() AND role = 'owner'
+ )
+ );
+ ```
+
+ 4. invite_codes table policies:
+ ```sql
+ -- Users can view invite codes for their households
+ CREATE POLICY "Users can view household invite codes" ON invite_codes
+ FOR SELECT USING (
+ household_id IN (
+ SELECT household_id FROM household_members
+ WHERE user_id = auth.uid()
+ )
+ );
+
+ -- Only owners can create invite codes
+ CREATE POLICY "Owners can create invite codes" ON invite_codes
+ FOR INSERT WITH CHECK (
+ created_by = auth.uid() AND
+ household_id IN (
+ SELECT household_id FROM household_members
+ WHERE user_id = auth.uid() AND role = 'owner'
+ )
+ );
+
+ -- Only owners can revoke invite codes
+ CREATE POLICY "Owners can revoke invite codes" ON invite_codes
+ FOR DELETE USING (
+ created_by = auth.uid()
+ );
+ ```
+
+ Ensure policies enforce multi-tenant isolation at database layer.
+
+ supabase db push --dry-run shows valid RLS policies
+ Row-Level Security policies enforce household data isolation with proper role-based access
+
+
+
+ Create Household Performance Indexes
+ supabase/migrations/003_household_indexes.sql
+
+ Create performance indexes for household operations:
+
+ 1. households table indexes:
+ ```sql
+ -- For finding households by creator
+ CREATE INDEX idx_households_created_by ON households(created_by);
+
+ -- For household sorting by creation date
+ CREATE INDEX idx_households_created_at ON households(created_at);
+ ```
+
+ 2. household_members table indexes:
+ ```sql
+ -- For finding user's households (primary query)
+ CREATE INDEX idx_household_members_user_id ON household_members(user_id);
+
+ -- For finding household members (primary query)
+ CREATE INDEX idx_household_members_household_id ON household_members(household_id);
+
+ -- Composite index for member role queries
+ CREATE INDEX idx_household_members_household_role ON household_members(household_id, role);
+
+ -- For checking if user is member of household
+ CREATE UNIQUE INDEX idx_household_members_unique ON household_members(household_id, user_id);
+ ```
+
+ 3. invite_codes table indexes:
+ ```sql
+ -- For invite code lookup (primary query)
+ CREATE UNIQUE INDEX idx_invite_codes_code ON invite_codes(code);
+
+ -- For finding household's active invites
+ CREATE INDEX idx_invite_codes_household_id ON invite_codes(household_id);
+
+ -- For cleaning expired invites
+ CREATE INDEX idx_invite_codes_expires_at ON invite_codes(expires_at);
+
+ -- For finding unused invites
+ CREATE INDEX idx_invite_codes_unused ON invite_codes(used_by) WHERE used_by IS NULL;
+ ```
+
+ 4. Realtime function for household changes:
+ ```sql
+ -- Function to broadcast household member changes
+ CREATE OR REPLACE FUNCTION broadcast_household_change()
+ RETURNS TRIGGER AS $$
+ BEGIN
+ -- This will be used by Supabase Realtime
+ RETURN NEW;
+ END;
+ $$ LANGUAGE plpgsql;
+
+ -- Trigger for household member changes
+ CREATE TRIGGER household_members_broadcast
+ AFTER INSERT OR UPDATE OR DELETE ON household_members
+ FOR EACH ROW EXECUTE FUNCTION broadcast_household_change();
+ ```
+
+ Include indexes that support the most common household queries and real-time sync.
+
+ supabase db push --dry-run shows valid index creation
+ Performance indexes optimize household queries and support real-time synchronization
+
+
+
+ Create Household Database Functions
+ supabase/migrations/004_household_functions.sql
+
+ Create database functions for household operations:
+
+ 1. Generate unique invite code function:
+ ```sql
+ CREATE OR REPLACE FUNCTION generate_invite_code()
+ RETURNS TEXT AS $$
+ DECLARE
+ new_code TEXT;
+ code_exists BOOLEAN;
+ BEGIN
+ LOOP
+ -- Generate 8-character alphanumeric code
+ new_code := upper(substring(encode(gen_random_bytes(4), 'hex'), 1, 8));
+
+ -- Check for collision
+ SELECT EXISTS(SELECT 1 FROM invite_codes WHERE code = new_code AND used_by IS NULL) INTO code_exists;
+
+ EXIT WHEN NOT code_exists;
+ END LOOP;
+
+ RETURN new_code;
+ END;
+ $$ LANGUAGE plpgsql;
+ ```
+
+ 2. Get user's households with member info:
+ ```sql
+ CREATE OR REPLACE FUNCTION get_user_households(user_uuid UUID)
+ RETURNS TABLE (
+ household_id UUID,
+ household_name TEXT,
+ user_role TEXT,
+ member_count BIGINT,
+ created_at TIMESTAMPTZ
+ ) AS $$
+ BEGIN
+ RETURN QUERY
+ SELECT
+ h.id,
+ h.name,
+ hm.role,
+ (SELECT COUNT(*) FROM household_members WHERE household_id = h.id),
+ h.created_at
+ FROM households h
+ JOIN household_members hm ON h.id = hm.household_id
+ WHERE hm.user_id = user_uuid
+ ORDER BY h.created_at DESC;
+ END;
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
+ ```
+
+ 3. Validate and consume invite code:
+ ```sql
+ CREATE OR REPLACE FUNCTION join_household_by_code(
+ invite_code TEXT,
+ user_uuid UUID
+ )
+ RETURNS TABLE (
+ success BOOLEAN,
+ household_id UUID,
+ household_name TEXT,
+ error_message TEXT
+ ) AS $$
+ DECLARE
+ invite_record RECORD;
+ household_record RECORD;
+ is_already_member BOOLEAN;
+ BEGIN
+ -- Find valid invite code
+ SELECT ic.*, h.name as household_name INTO invite_record
+ FROM invite_codes ic
+ JOIN households h ON ic.household_id = h.id
+ WHERE ic.code = upper(invite_code)
+ AND ic.used_by IS NULL
+ AND ic.expires_at > now();
+
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT false, NULL::UUID, NULL::TEXT, 'Invalid or expired invite code'::TEXT;
+ RETURN;
+ END IF;
+
+ -- Check if already member
+ SELECT EXISTS(
+ SELECT 1 FROM household_members
+ WHERE household_id = invite_record.household_id AND user_id = user_uuid
+ ) INTO is_already_member;
+
+ IF is_already_member THEN
+ RETURN QUERY SELECT false, NULL::UUID, NULL::TEXT, 'Already a member of this household'::TEXT;
+ RETURN;
+ END IF;
+
+ -- Add as member with editor role
+ INSERT INTO household_members (household_id, user_id, role)
+ VALUES (invite_record.household_id, user_uuid, 'editor');
+
+ -- Mark invite as used
+ UPDATE invite_codes
+ SET used_by = user_uuid, used_at = now()
+ WHERE id = invite_record.id;
+
+ -- Get household info
+ SELECT id, name INTO household_record
+ FROM households WHERE id = invite_record.household_id;
+
+ RETURN QUERY SELECT true, household_record.id, household_record.name, NULL::TEXT;
+ END;
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
+ ```
+
+ Include proper SECURITY DEFINER settings and error handling.
+
+ supabase db push --dry-run shows valid function creation
+ Database functions provide efficient household operations with proper security
+
+
+
+
+
+1. Database schema supports all household operations from SHARE-01 through SHARE-05
+2. RLS policies enforce multi-tenant isolation at database layer
+3. Performance indexes optimize common household queries
+4. Database functions provide efficient invite code generation and validation
+5. Realtime triggers support household member synchronization
+
+
+
+Supabase database schema is complete with household tables, security policies, indexes, and functions ready for application integration.
+
+
+
\ No newline at end of file
diff --git a/.planning/phases/02-household-creation/02-03-PLAN.md b/.planning/phases/02-household-creation/02-03-PLAN.md
new file mode 100644
index 0000000..f2fa84c
--- /dev/null
+++ b/.planning/phases/02-household-creation/02-03-PLAN.md
@@ -0,0 +1,367 @@
+---
+phase: 02-household-creation
+plan: 03
+type: execute
+wave: 2
+depends_on: [02-01]
+files_modified:
+ - lib/features/household/providers/household_provider.dart
+ - lib/features/household/domain/usecases/create_household_usecase.dart
+ - lib/features/household/domain/usecases/generate_invite_code_usecase.dart
+ - lib/features/household/domain/usecases/join_household_usecase.dart
+ - lib/features/household/domain/usecases/get_user_households_usecase.dart
+autonomous: true
+
+must_haves:
+ truths:
+ - "Household state management provides reactive updates across the app"
+ - "Use cases encapsulate business logic for household operations"
+ - "Provider integrates with existing authentication state"
+ - "Household operations handle loading states and errors properly"
+ artifacts:
+ - path: "lib/features/household/providers/household_provider.dart"
+ provides: "Global household state management"
+ contains: "class HouseholdProvider", "Riverpod", "AsyncValue"
+ - path: "lib/features/household/domain/usecases/create_household_usecase.dart"
+ provides: "Household creation business logic"
+ contains: "class CreateHouseholdUseCase", "Future"
+ - path: "lib/features/household/domain/usecases/join_household_usecase.dart"
+ provides: "Household joining logic with validation"
+ contains: "class JoinHouseholdUseCase", "invite code validation"
+ - path: "lib/features/household/domain/usecases/get_user_households_usecase.dart"
+ provides: "User household retrieval logic"
+ contains: "class GetUserHouseholdsUseCase", "List"
+ key_links:
+ - from: "lib/features/household/providers/household_provider.dart"
+ to: "lib/features/household/domain/usecases/create_household_usecase.dart"
+ via: "dependency injection"
+ pattern: "CreateHouseholdUseCase.*createHouseholdUseCase"
+ - from: "lib/features/household/providers/household_provider.dart"
+ to: "lib/providers/auth_provider.dart"
+ via: "authentication state"
+ pattern: "ref.watch.*authProvider"
+---
+
+
+Implement household state management and business logic use cases following clean architecture patterns.
+
+Purpose: Provide reactive household state that integrates with authentication and handles all household operations with proper error handling.
+Output: Complete state management layer with use cases and Riverpod provider ready for UI integration.
+
+
+
+@~/.opencode/get-shit-done/workflows/execute-plan.md
+@~/.opencode/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+# State Management References
+@lib/providers/auth_provider.dart
+@lib/features/authentication/data/repositories/auth_repository_impl.dart
+
+
+
+
+
+ Create Household Use Cases
+
+ lib/features/household/domain/usecases/create_household_usecase.dart
+ lib/features/household/domain/usecases/generate_invite_code_usecase.dart
+ lib/features/household/domain/usecases/join_household_usecase.dart
+ lib/features/household/domain/usecases/get_user_households_usecase.dart
+
+
+ Create use cases for household operations following clean architecture:
+
+ 1. CreateHouseholdUseCase (create_household_usecase.dart):
+ ```dart
+ class CreateHouseholdUseCase {
+ final HouseholdRepository repository;
+
+ CreateHouseholdUseCase(this.repository);
+
+ Future call(String name, String userId) async {
+ if (name.trim().isEmpty) {
+ throw HouseholdValidationException('Household name cannot be empty');
+ }
+
+ if (name.length > 100) {
+ throw HouseholdValidationException('Household name too long (max 100 characters)');
+ }
+
+ return await repository.createHousehold(name.trim(), userId);
+ }
+ }
+ ```
+
+ 2. GenerateInviteCodeUseCase (generate_invite_code_usecase.dart):
+ ```dart
+ class GenerateInviteCodeUseCase {
+ final HouseholdRepository repository;
+
+ GenerateInviteCodeUseCase(this.repository);
+
+ Future call(String householdId, String userId) async {
+ // Check if user is owner (will be validated in repository)
+ return await repository.generateInviteCode(householdId, userId);
+ }
+ }
+ ```
+
+ 3. JoinHouseholdUseCase (join_household_usecase.dart):
+ ```dart
+ class JoinHouseholdUseCase {
+ final HouseholdRepository repository;
+
+ JoinHouseholdUseCase(this.repository);
+
+ Future call(String inviteCode, String userId) async {
+ if (inviteCode.trim().isEmpty) {
+ throw HouseholdValidationException('Invite code cannot be empty');
+ }
+
+ if (inviteCode.length != 8) {
+ throw HouseholdValidationException('Invite code must be 8 characters');
+ }
+
+ return await repository.joinHousehold(inviteCode.trim().toUpperCase(), userId);
+ }
+ }
+ ```
+
+ 4. GetUserHouseholdsUseCase (get_user_households_usecase.dart):
+ ```dart
+ class GetUserHouseholdsUseCase {
+ final HouseholdRepository repository;
+
+ GetUserHouseholdsUseCase(this.repository);
+
+ Future> call(String userId) async {
+ return await repository.getUserHouseholds(userId);
+ }
+ }
+ ```
+
+ Include proper validation and business logic in each use case.
+ Create custom exception classes for household operations.
+
+ flutter analyze lib/features/household/domain/usecases/*.dart passes
+ Household use cases encapsulate business logic with proper validation
+
+
+
+ Create Household Provider
+ lib/features/household/providers/household_provider.dart
+
+ Create Riverpod provider for household state management:
+ ```dart
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+ // Use case providers
+ final createHouseholdUseCaseProvider = Provider((ref) {
+ final repository = ref.watch(householdRepositoryProvider);
+ return CreateHouseholdUseCase(repository);
+ });
+
+ final generateInviteCodeUseCaseProvider = Provider((ref) {
+ final repository = ref.watch(householdRepositoryProvider);
+ return GenerateInviteCodeUseCase(repository);
+ });
+
+ final joinHouseholdUseCaseProvider = Provider((ref) {
+ final repository = ref.watch(householdRepositoryProvider);
+ return JoinHouseholdUseCase(repository);
+ });
+
+ final getUserHouseholdsUseCaseProvider = Provider((ref) {
+ final repository = ref.watch(householdRepositoryProvider);
+ return GetUserHouseholdsUseCase(repository);
+ });
+
+ // Main household provider
+ class HouseholdProvider extends StateNotifier>> {
+ final GetUserHouseholdsUseCase _getUserHouseholdsUseCase;
+ final CreateHouseholdUseCase _createHouseholdUseCase;
+ final JoinHouseholdUseCase _joinHouseholdUseCase;
+ final GenerateInviteCodeUseCase _generateInviteCodeUseCase;
+
+ HouseholdProvider(
+ this._getUserHouseholdsUseCase,
+ this._createHouseholdUseCase,
+ this._joinHouseholdUseCase,
+ this._generateInviteCodeUseCase,
+ ) : super(const AsyncValue.loading()) {
+ _initialize();
+ }
+
+ Future _initialize() async {
+ state = const AsyncValue.loading();
+ try {
+ // Will be called when auth state is available
+ } catch (e, stack) {
+ state = AsyncValue.error(e, stack);
+ }
+ }
+
+ Future loadUserHouseholds(String userId) async {
+ state = const AsyncValue.loading();
+ try {
+ final households = await _getUserHouseholdsUseCase(userId);
+ state = AsyncValue.data(households);
+ } catch (e, stack) {
+ state = AsyncValue.error(e, stack);
+ }
+ }
+
+ Future createHousehold(String name, String userId) async {
+ try {
+ final household = await _createHouseholdUseCase(name, userId);
+
+ // Refresh the list
+ await loadUserHouseholds(userId);
+
+ return household;
+ } catch (e) {
+ throw HouseholdOperationException('Failed to create household: $e');
+ }
+ }
+
+ Future joinHousehold(String inviteCode, String userId) async {
+ try {
+ final household = await _joinHouseholdUseCase(inviteCode, userId);
+
+ // Refresh the list
+ await loadUserHouseholds(userId);
+
+ return household;
+ } catch (e) {
+ throw HouseholdOperationException('Failed to join household: $e');
+ }
+ }
+
+ Future generateInviteCode(String householdId, String userId) async {
+ try {
+ return await _generateInviteCodeUseCase(householdId, userId);
+ } catch (e) {
+ throw HouseholdOperationException('Failed to generate invite code: $e');
+ }
+ }
+
+ void clearHouseholds() {
+ state = const AsyncValue.data([]);
+ }
+ }
+
+ // Provider instance
+ final householdProvider = StateNotifierProvider>>((ref) {
+ return HouseholdProvider(
+ ref.read(getUserHouseholdsUseCaseProvider),
+ ref.read(createHouseholdUseCaseProvider),
+ ref.read(joinHouseholdUseCaseProvider),
+ ref.read(generateInviteCodeUseCaseProvider),
+ );
+ });
+
+ // Current selected household provider
+ final currentHouseholdProvider = StateProvider((ref) => null);
+
+ // Repository provider (to be added to main providers file)
+ final householdRepositoryProvider = Provider((ref) {
+ final datasource = ref.read(householdRemoteDatasourceProvider);
+ return HouseholdRepositoryImpl(datasource);
+ });
+
+ final householdRemoteDatasourceProvider = Provider((ref) {
+ final client = ref.read(supabaseClientProvider);
+ return HouseholdRemoteDatasource(client);
+ });
+ ```
+
+ Follow auth provider patterns for consistency.
+ Include proper error handling and state transitions.
+
+ flutter analyze lib/features/household/providers/household_provider.dart passes
+ Household provider provides reactive state management with proper integration
+
+
+
+ Create Household Exception Classes
+ lib/features/household/domain/exceptions/household_exceptions.dart
+
+ Create custom exception classes for household operations:
+ ```dart
+ // Base household exception
+ abstract class HouseholdException implements Exception {
+ final String message;
+ const HouseholdException(this.message);
+
+ @override
+ String toString() => 'HouseholdException: $message';
+ }
+
+ // Validation exceptions
+ class HouseholdValidationException extends HouseholdException {
+ const HouseholdValidationException(String message) : super(message);
+
+ @override
+ String toString() => 'HouseholdValidationException: $message';
+ }
+
+ // Operation exceptions
+ class HouseholdOperationException extends HouseholdException {
+ const HouseholdOperationException(String message) : super(message);
+
+ @override
+ String toString() => 'HouseholdOperationException: $message';
+ }
+
+ // Specific household exceptions
+ class HouseholdNotFoundException extends HouseholdException {
+ const HouseholdNotFoundException(String message) : super(message);
+
+ @override
+ String toString() => 'HouseholdNotFoundException: $message';
+ }
+
+ class InviteCodeException extends HouseholdException {
+ const InviteCodeException(String message) : super(message);
+
+ @override
+ String toString() => 'InviteCodeException: $message';
+ }
+
+ class HouseholdMembershipException extends HouseholdException {
+ const HouseholdMembershipException(String message) : super(message);
+
+ @override
+ String toString() => 'HouseholdMembershipException: $message';
+ }
+ ```
+
+ Follow authentication exception patterns for consistency.
+
+ flutter analyze lib/features/household/domain/exceptions/household_exceptions.dart passes
+ Household exception classes provide clear error categorization
+
+
+
+
+
+1. Use cases encapsulate business logic with proper validation
+2. Household provider provides reactive state management following Riverpod patterns
+3. Exception classes provide clear error categorization
+4. Integration with authentication state is properly structured
+5. All household operations handle loading states and errors appropriately
+
+
+
+Household state management is complete with use cases, provider, and exception handling ready for UI integration.
+
+
+
\ No newline at end of file
diff --git a/.planning/phases/02-household-creation/02-04-PLAN.md b/.planning/phases/02-household-creation/02-04-PLAN.md
new file mode 100644
index 0000000..f3268cc
--- /dev/null
+++ b/.planning/phases/02-household-creation/02-04-PLAN.md
@@ -0,0 +1,590 @@
+---
+phase: 02-household-creation
+plan: 04
+type: execute
+wave: 3
+depends_on: [02-03, 02-01]
+files_modified:
+ - lib/features/household/presentation/pages/create_household_page.dart
+ - lib/features/household/presentation/pages/join_household_page.dart
+ - lib/features/household/presentation/pages/household_list_page.dart
+ - lib/features/household/presentation/widgets/invite_code_dialog.dart
+ - lib/features/household/presentation/widgets/household_card.dart
+autonomous: true
+
+must_haves:
+ truths:
+ - "Users can create households with validation and feedback"
+ - "Users can join households using 8-character invite codes"
+ - "Household management UI provides clear navigation and state feedback"
+ - "Invite code generation works with proper security and expiration"
+ - "All household operations handle loading states and errors gracefully"
+ artifacts:
+ - path: "lib/features/household/presentation/pages/create_household_page.dart"
+ provides: "Household creation interface"
+ contains: "CreateHouseholdPage", "form validation", "create button"
+ - path: "lib/features/household/presentation/pages/join_household_page.dart"
+ provides: "Household joining interface with invite code input"
+ contains: "JoinHouseholdPage", "invite code field", "join button"
+ - path: "lib/features/household/presentation/pages/household_list_page.dart"
+ provides: "Household overview and management"
+ contains: "HouseholdListPage", "household cards", "create/join buttons"
+ - path: "lib/features/household/presentation/widgets/invite_code_dialog.dart"
+ provides: "Invite code generation and display"
+ contains: "InviteCodeDialog", "code display", "share functionality"
+ - path: "lib/features/household/presentation/widgets/household_card.dart"
+ provides: "Household display component"
+ contains: "HouseholdCard", "member count", "role display"
+ key_links:
+ - from: "lib/features/household/presentation/pages/create_household_page.dart"
+ to: "lib/features/household/providers/household_provider.dart"
+ via: "Riverpod consumer"
+ pattern: "ref.read.*householdProvider"
+ - from: "lib/features/household/presentation/pages/household_list_page.dart"
+ to: "lib/providers/auth_provider.dart"
+ via: "authentication state"
+ pattern: "ref.watch.*authProvider"
+---
+
+
+Create household management UI components for creating, joining, and managing households with proper validation and user feedback.
+
+Purpose: Provide intuitive interfaces for all household operations from SHARE-01 through SHARE-05 with consistent UX and error handling.
+Output: Complete household management UI ready for integration with navigation system.
+
+
+
+@~/.opencode/get-shit-done/workflows/execute-plan.md
+@~/.opencode/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+# UI Pattern References
+@lib/features/authentication/presentation/pages/signup_page.dart
+@lib/features/authentication/presentation/pages/login_page.dart
+@lib/features/authentication/presentation/widgets/auth_button.dart
+
+
+
+
+
+ Create Household Card Widget
+ lib/features/household/presentation/widgets/household_card.dart
+
+ Create household card component following auth UI patterns:
+ ```dart
+ import 'package:flutter/material.dart';
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
+ import '../domain/entities/household_entity.dart';
+
+ class HouseholdCard extends ConsumerWidget {
+ final HouseholdEntity household;
+ final VoidCallback? onTap;
+ final VoidCallback? onGenerateInvite;
+ final VoidCallback? onLeave;
+ final bool showActions;
+
+ const HouseholdCard({
+ Key? key,
+ required this.household,
+ this.onTap,
+ this.onGenerateInvite,
+ this.onLeave,
+ this.showActions = true,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ return Card(
+ margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ child: ListTile(
+ leading: CircleAvatar(
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ child: Text(
+ household.name.isNotEmpty ? household.name[0].toUpperCase() : 'H',
+ style: const TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ title: Text(
+ household.name,
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ subtitle: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 4),
+ Text('${household.members.length} member${household.members.length != 1 ? 's' : ''}'),
+ if (household.currentInvite != null)
+ Text(
+ 'Invite active',
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.primary,
+ fontSize: 12,
+ ),
+ ),
+ ],
+ ),
+ trailing: showActions ? _buildActions(context) : null,
+ onTap: onTap,
+ ),
+ );
+ }
+
+ Widget _buildActions(BuildContext context) {
+ return PopupMenuButton(
+ onSelected: (value) {
+ switch (value) {
+ case 'invite':
+ onGenerateInvite?.call();
+ break;
+ case 'leave':
+ onLeave?.call();
+ break;
+ }
+ },
+ itemBuilder: (context) => [
+ if (household.isCurrentUserOwner || household.members.any(m => m.isCurrentUser && m.role.name != 'viewer'))
+ const PopupMenuItem(
+ value: 'invite',
+ child: Row(
+ children: [
+ Icon(Icons.person_add),
+ SizedBox(width: 8),
+ Text('Generate Invite'),
+ ],
+ ),
+ ),
+ if (!household.isCurrentUserOwner || household.members.length > 1)
+ const PopupMenuItem(
+ value: 'leave',
+ child: Row(
+ children: [
+ Icon(Icons.exit_to_app),
+ SizedBox(width: 8),
+ Text('Leave Household'),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+ }
+ ```
+
+ Follow Material Design card patterns and authentication UI consistency.
+ Include role-based action visibility (only owners/editors can generate invites).
+
+ flutter analyze lib/features/household/presentation/widgets/household_card.dart passes
+ Household card component displays household information with appropriate actions
+
+
+
+ Create Invite Code Dialog
+ lib/features/household/presentation/widgets/invite_code_dialog.dart
+
+ Create dialog for generating and sharing invite codes:
+ ```dart
+ import 'package:flutter/material.dart';
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
+ import 'package:flutter/services.dart';
+ import '../../domain/models/household_models.dart';
+ import '../../providers/household_provider.dart';
+
+ class InviteCodeDialog extends ConsumerStatefulWidget {
+ final String householdId;
+ final String householdName;
+
+ const InviteCodeDialog({
+ Key? key,
+ required this.householdId,
+ required this.householdName,
+ }) : super(key: key);
+
+ @override
+ ConsumerState createState() => _InviteCodeDialogState();
+ }
+
+ class _InviteCodeDialogState extends ConsumerState {
+ InviteCodeModel? _inviteCode;
+ bool _isLoading = false;
+ String? _error;
+
+ @override
+ void initState() {
+ super.initState();
+ _generateInviteCode();
+ }
+
+ Future _generateInviteCode() async {
+ setState(() {
+ _isLoading = true;
+ _error = null;
+ });
+
+ try {
+ final user = ref.read(authProvider).user;
+ if (user == null) throw Exception('User not authenticated');
+
+ final inviteCode = await ref.read(householdProvider.notifier)
+ .generateInviteCode(widget.householdId, user.id);
+
+ setState(() {
+ _inviteCode = inviteCode;
+ _isLoading = false;
+ });
+ } catch (e) {
+ setState(() {
+ _error = e.toString();
+ _isLoading = false;
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: Text('Invite to ${widget.householdName}'),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (_isLoading)
+ const Column(
+ children: [
+ CircularProgressIndicator(),
+ SizedBox(height: 16),
+ Text('Generating invite code...'),
+ ],
+ )
+ else if (_error != null)
+ Column(
+ children: [
+ Icon(
+ Icons.error,
+ color: Theme.of(context).colorScheme.error,
+ size: 48,
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'Failed to generate invite code',
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ const SizedBox(height: 8),
+ Text(_error!),
+ const SizedBox(height: 16),
+ ElevatedButton(
+ onPressed: _generateInviteCode,
+ child: const Text('Try Again'),
+ ),
+ ],
+ )
+ else if (_inviteCode != null)
+ Column(
+ children: [
+ Text(
+ 'Share this code with household members:',
+ style: Theme.of(context).textTheme.bodyMedium,
+ ),
+ const SizedBox(height: 16),
+ Container(
+ padding: const EdgeInsets.all(16),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceVariant,
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Column(
+ children: [
+ Text(
+ _inviteCode!.code,
+ style: Theme.of(context).textTheme.headlineMedium?.copyWith(
+ fontWeight: FontWeight.bold,
+ letterSpacing: 2,
+ ),
+ ),
+ const SizedBox(height: 8),
+ Text(
+ 'Expires: ${DateFormat('MMM dd, yyyy').format(_inviteCode!.expiresAt)}',
+ style: Theme.of(context).textTheme.bodySmall,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ Expanded(
+ child: ElevatedButton.icon(
+ onPressed: () => _copyToClipboard(_inviteCode!.code),
+ icon: const Icon(Icons.copy),
+ label: const Text('Copy Code'),
+ ),
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: OutlinedButton.icon(
+ onPressed: () => _shareInvite(_inviteCode!.code),
+ icon: const Icon(Icons.share),
+ label: const Text('Share'),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: const Text('Close'),
+ ),
+ ],
+ );
+ }
+
+ Future _copyToClipboard(String code) async {
+ await Clipboard.setData(ClipboardData(text: code));
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Invite code copied to clipboard')),
+ );
+ }
+ }
+
+ Future _shareInvite(String code) async {
+ // Share functionality would be implemented here
+ await _copyToClipboard(code);
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Invite code copied - paste to share')),
+ );
+ }
+ }
+ }
+ ```
+
+ Include proper loading states, error handling, and copy-to-clipboard functionality.
+ Follow Material Design dialog patterns from authentication flows.
+
+ flutter analyze lib/features/household/presentation/widgets/invite_code_dialog.dart passes
+ Invite code dialog provides code generation, display, and sharing functionality
+
+
+
+ Create Create Household Page
+ lib/features/household/presentation/pages/create_household_page.dart
+
+ Create household creation page following authentication UI patterns:
+ ```dart
+ import 'package:flutter/material.dart';
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
+ import 'package:flutter/services.dart';
+ import '../../providers/household_provider.dart';
+ import '../../domain/exceptions/household_exceptions.dart';
+
+ class CreateHouseholdPage extends ConsumerStatefulWidget {
+ const CreateHouseholdPage({Key? key}) : super(key: key);
+
+ @override
+ ConsumerState createState() => _CreateHouseholdPageState();
+ }
+
+ class _CreateHouseholdPageState extends ConsumerState {
+ final _formKey = GlobalKey();
+ final _nameController = TextEditingController();
+ bool _isLoading = false;
+ String? _error;
+
+ @override
+ void dispose() {
+ _nameController.dispose();
+ super.dispose();
+ }
+
+ Future _createHousehold() async {
+ if (!_formKey.currentState!.validate()) return;
+
+ setState(() {
+ _isLoading = true;
+ _error = null;
+ });
+
+ try {
+ final user = ref.read(authProvider).user;
+ if (user == null) throw Exception('User not authenticated');
+
+ final household = await ref.read(householdProvider.notifier)
+ .createHousehold(_nameController.text.trim(), user.id);
+
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Household "${household.name}" created successfully!'),
+ backgroundColor: Colors.green,
+ ),
+ );
+ Navigator.of(context).pop(household);
+ }
+ } catch (e) {
+ setState(() {
+ _error = _getErrorMessage(e);
+ _isLoading = false;
+ });
+ }
+ }
+
+ String _getErrorMessage(dynamic error) {
+ if (error is HouseholdValidationException) {
+ return error.message;
+ } else if (error is HouseholdOperationException) {
+ return error.message;
+ }
+ return 'Failed to create household. Please try again.';
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Create Household'),
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ foregroundColor: Theme.of(context).colorScheme.onSurface,
+ elevation: 0,
+ ),
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.all(24),
+ child: Form(
+ key: _formKey,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const SizedBox(height: 32),
+ Icon(
+ Icons.home,
+ size: 64,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ const SizedBox(height: 24),
+ Text(
+ 'Create a New Household',
+ style: Theme.of(context).textTheme.headlineSmall,
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 8),
+ Text(
+ 'Start tracking your shared inventory with family or roommates',
+ style: Theme.of(context).textTheme.bodyMedium?.copyWith(
+ color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 48),
+ TextFormField(
+ controller: _nameController,
+ decoration: const InputDecoration(
+ labelText: 'Household Name',
+ hintText: 'e.g., The Smith Family',
+ prefixIcon: Icon(Icons.home),
+ ),
+ textInputAction: TextInputAction.done,
+ autofillHints: const [AutofillHints.name],
+ validator: (value) {
+ if (value == null || value.trim().isEmpty) {
+ return 'Please enter a household name';
+ }
+ if (value.trim().length > 100) {
+ return 'Household name too long (max 100 characters)';
+ }
+ return null;
+ },
+ onFieldSubmitted: (_) => _createHousehold(),
+ ),
+ const SizedBox(height: 24),
+ if (_error != null)
+ Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.errorContainer,
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ Icons.error,
+ color: Theme.of(context).colorScheme.error,
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Text(
+ _error!,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.error,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 24),
+ ElevatedButton(
+ onPressed: _isLoading ? null : _createHousehold,
+ style: ElevatedButton.styleFrom(
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ ),
+ child: _isLoading
+ ? const SizedBox(
+ height: 20,
+ width: 20,
+ child: CircularProgressIndicator(strokeWidth: 2),
+ )
+ : const Text('Create Household'),
+ ),
+ const SizedBox(height: 16),
+ OutlinedButton(
+ onPressed: () => Navigator.of(context).pop(),
+ style: OutlinedButton.styleFrom(
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ ),
+ child: const Text('Cancel'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+ }
+ ```
+
+ Follow authentication page patterns for consistency.
+ Include proper validation, loading states, and error handling.
+
+ flutter analyze lib/features/household/presentation/pages/create_household_page.dart passes
+ Create household page provides form validation and user feedback
+
+
+
+
+
+1. Household card component displays household information with role-based actions
+2. Invite code dialog handles generation, display, copying, and sharing
+3. Create household page follows authentication UI patterns with proper validation
+4. All components handle loading states and errors appropriately
+5. UI is consistent with existing authentication flows
+
+
+
+Household management UI components are complete with proper validation, error handling, and user feedback ready for integration.
+
+
+
\ No newline at end of file
diff --git a/.planning/phases/02-household-creation/02-05-PLAN.md b/.planning/phases/02-household-creation/02-05-PLAN.md
new file mode 100644
index 0000000..531c832
--- /dev/null
+++ b/.planning/phases/02-household-creation/02-05-PLAN.md
@@ -0,0 +1,679 @@
+---
+phase: 02-household-creation
+plan: 05
+type: execute
+wave: 3
+depends_on: [02-04]
+files_modified:
+ - lib/features/household/presentation/pages/join_household_page.dart
+ - lib/features/household/presentation/pages/household_list_page.dart
+autonomous: true
+
+must_haves:
+ truths:
+ - "Users can join households using 8-character invite codes with validation"
+ - "Household list page shows all user households with clear actions"
+ - "Join flow provides immediate feedback on invalid/expired codes"
+ - "Household management integrates with navigation and authentication state"
+ - "All operations handle loading states and errors gracefully"
+ artifacts:
+ - path: "lib/features/household/presentation/pages/join_household_page.dart"
+ provides: "Household joining interface with invite code validation"
+ contains: "JoinHouseholdPage", "invite code input", "validation feedback"
+ - path: "lib/features/household/presentation/pages/household_list_page.dart"
+ provides: "Household overview and management hub"
+ contains: "HouseholdListPage", "household cards", "create/join actions"
+ key_links:
+ - from: "lib/features/household/presentation/pages/join_household_page.dart"
+ to: "lib/features/household/providers/household_provider.dart"
+ via: "household joining use case"
+ pattern: "ref.read.*householdProvider.*joinHousehold"
+ - from: "lib/features/household/presentation/pages/household_list_page.dart"
+ to: "lib/features/household/presentation/widgets/household_card.dart"
+ via: "household display"
+ pattern: "HouseholdCard.*onTap"
+---
+
+
+Complete household management UI with join household flow and household list hub with navigation integration.
+
+Purpose: Provide complete household management experience where users can join existing households and manage all their households from one central location.
+Output: Full household management UI ready for navigation integration and user testing.
+
+
+
+@~/.opencode/get-shit-done/workflows/execute-plan.md
+@~/.opencode/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+# UI Pattern References
+@lib/features/authentication/presentation/pages/signup_page.dart
+@lib/features/authentication/presentation/pages/login_page.dart
+@lib/features/authentication/presentation/widgets/auth_button.dart
+@lib/features/household/presentation/widgets/household_card.dart
+
+
+
+
+
+ Create Join Household Page
+ lib/features/household/presentation/pages/join_household_page.dart
+
+ Create join household page with invite code validation:
+ ```dart
+ import 'package:flutter/material.dart';
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
+ import 'package:flutter/services.dart';
+ import '../../providers/household_provider.dart';
+ import '../../domain/exceptions/household_exceptions.dart';
+ import '../../domain/entities/household_entity.dart';
+
+ class JoinHouseholdPage extends ConsumerStatefulWidget {
+ const JoinHouseholdPage({Key? key}) : super(key: key);
+
+ @override
+ ConsumerState createState() => _JoinHouseholdPageState();
+ }
+
+ class _JoinHouseholdPageState extends ConsumerState {
+ final _formKey = GlobalKey();
+ final _codeController = TextEditingController();
+ bool _isLoading = false;
+ String? _error;
+ HouseholdEntity? _joinedHousehold;
+
+ @override
+ void dispose() {
+ _codeController.dispose();
+ super.dispose();
+ }
+
+ Future _joinHousehold() async {
+ if (!_formKey.currentState!.validate()) return;
+
+ setState(() {
+ _isLoading = true;
+ _error = null;
+ });
+
+ try {
+ final user = ref.read(authProvider).user;
+ if (user == null) throw Exception('User not authenticated');
+
+ final household = await ref.read(householdProvider.notifier)
+ .joinHousehold(_codeController.text.trim(), user.id);
+
+ setState(() {
+ _joinedHousehold = household;
+ _isLoading = false;
+ });
+
+ // Auto-close after successful join
+ Future.delayed(const Duration(seconds: 2), () {
+ if (mounted) {
+ Navigator.of(context).pop(household);
+ }
+ });
+ } catch (e) {
+ setState(() {
+ _error = _getErrorMessage(e);
+ _isLoading = false;
+ });
+ }
+ }
+
+ String _getErrorMessage(dynamic error) {
+ if (error is HouseholdValidationException) {
+ return error.message;
+ } else if (error is HouseholdOperationException) {
+ return error.message;
+ } else if (error is InviteCodeException) {
+ return error.message;
+ }
+ return 'Failed to join household. Please check the invite code and try again.';
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Join Household'),
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ foregroundColor: Theme.of(context).colorScheme.onSurface,
+ elevation: 0,
+ ),
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.all(24),
+ child: Form(
+ key: _formKey,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const SizedBox(height: 32),
+ Icon(
+ Icons.group_add,
+ size: 64,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ const SizedBox(height: 24),
+ Text(
+ 'Join a Household',
+ style: Theme.of(context).textTheme.headlineSmall,
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 8),
+ Text(
+ 'Enter the 8-character invite code from your household member',
+ style: Theme.of(context).textTheme.bodyMedium?.copyWith(
+ color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 48),
+ TextFormField(
+ controller: _codeController,
+ decoration: InputDecoration(
+ labelText: 'Invite Code',
+ hintText: 'ABCD1234',
+ prefixIcon: const Icon(Icons.key),
+ helperText: '8 characters, case-insensitive',
+ ),
+ textCapitalization: TextCapitalization.characters,
+ textInputAction: TextInputAction.done,
+ inputFormatters: [
+ UpperCaseTextFormatter(),
+ FilteringTextInputFormatter.allow(RegExp(r'[A-Z0-9]')),
+ LengthLimitingTextInputFormatter(8),
+ ],
+ onChanged: (value) {
+ // Auto-format and move to next field after 8 chars
+ if (value.length == 8 && !_isLoading) {
+ _formKey.currentState?.validate();
+ }
+ },
+ validator: (value) {
+ if (value == null || value.trim().isEmpty) {
+ return 'Please enter an invite code';
+ }
+ if (value.trim().length != 8) {
+ return 'Invite code must be 8 characters';
+ }
+ return null;
+ },
+ onFieldSubmitted: (_) => _joinHousehold(),
+ ),
+ const SizedBox(height: 24),
+ if (_error != null)
+ Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.errorContainer,
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ Icons.error,
+ color: Theme.of(context).colorScheme.error,
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Text(
+ _error!,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.error,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ if (_joinedHousehold != null)
+ Container(
+ padding: const EdgeInsets.all(12),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.primaryContainer,
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ Icons.check_circle,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Successfully joined!',
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.primary,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ Text(
+ 'You are now a member of ${_joinedHousehold!.name}',
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.onPrimaryContainer,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ if (_joinedHousehold == null) ...[
+ const SizedBox(height: 24),
+ ElevatedButton(
+ onPressed: _isLoading ? null : _joinHousehold,
+ style: ElevatedButton.styleFrom(
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ ),
+ child: _isLoading
+ ? const SizedBox(
+ height: 20,
+ width: 20,
+ child: CircularProgressIndicator(strokeWidth: 2),
+ )
+ : const Text('Join Household'),
+ ),
+ const SizedBox(height: 16),
+ OutlinedButton(
+ onPressed: () => Navigator.of(context).pop(),
+ style: OutlinedButton.styleFrom(
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ ),
+ child: const Text('Cancel'),
+ ),
+ ],
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+ }
+
+ // Helper formatter for uppercase input
+ class UpperCaseTextFormatter extends TextInputFormatter {
+ @override
+ TextEditingValue formatEditUpdate(
+ TextEditingValue oldValue,
+ TextEditingValue newValue,
+ ) {
+ return TextEditingValue(
+ text: newValue.text.toUpperCase(),
+ selection: newValue.selection,
+ );
+ }
+ }
+ ```
+
+ Include auto-formatting, validation, and clear success/error feedback.
+ Follow authentication page patterns for consistency.
+
+ flutter analyze lib/features/household/presentation/pages/join_household_page.dart passes
+ Join household page provides invite code validation with immediate feedback
+
+
+
+ Create Household List Page
+ lib/features/household/presentation/pages/household_list_page.dart
+
+ Create household list page as management hub:
+ ```dart
+ import 'package:flutter/material.dart';
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
+ import '../widgets/household_card.dart';
+ import '../widgets/invite_code_dialog.dart';
+ import '../../providers/household_provider.dart';
+ import '../../domain/entities/household_entity.dart';
+ import '../pages/create_household_page.dart';
+ import '../pages/join_household_page.dart';
+
+ class HouseholdListPage extends ConsumerStatefulWidget {
+ const HouseholdListPage({Key? key}) : super(key: key);
+
+ @override
+ ConsumerState createState() => _HouseholdListPageState();
+ }
+
+ class _HouseholdListPageState extends ConsumerState {
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _loadHouseholds();
+ });
+ }
+
+ Future _loadHouseholds() async {
+ final user = ref.read(authProvider).user;
+ if (user != null) {
+ await ref.read(householdProvider.notifier).loadUserHouseholds(user.id);
+ }
+ }
+
+ Future _showCreateHousehold() async {
+ final result = await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) => const CreateHouseholdPage(),
+ ),
+ );
+
+ if (result != null) {
+ // Household was created, show success message
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Welcome to ${result.name}!'),
+ backgroundColor: Colors.green,
+ ),
+ );
+ }
+ }
+ }
+
+ Future _showJoinHousehold() async {
+ final result = await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) => const JoinHouseholdPage(),
+ ),
+ );
+
+ if (result != null) {
+ // Household was joined, show success message
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('You joined ${result.name}!'),
+ backgroundColor: Colors.green,
+ ),
+ );
+ }
+ }
+ }
+
+ Future _showInviteDialog(HouseholdEntity household) async {
+ await showDialog(
+ context: context,
+ builder: (context) => InviteCodeDialog(
+ householdId: household.id,
+ householdName: household.name,
+ ),
+ );
+ }
+
+ Future _handleHouseholdTap(HouseholdEntity household) async {
+ // Set as current household and navigate to home/inventory
+ ref.read(currentHouseholdProvider.notifier).state = household;
+
+ if (mounted) {
+ // Navigate to inventory/home page
+ Navigator.of(context).pushReplacementNamed('/home');
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final user = ref.watch(authProvider).user;
+ final householdsAsync = ref.watch(householdProvider);
+ final currentHousehold = ref.watch(currentHouseholdProvider);
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('My Households'),
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ foregroundColor: Theme.of(context).colorScheme.onSurface,
+ elevation: 0,
+ actions: [
+ IconButton(
+ onPressed: _loadHouseholds,
+ icon: const Icon(Icons.refresh),
+ tooltip: 'Refresh',
+ ),
+ ],
+ ),
+ body: RefreshIndicator(
+ onRefresh: _loadHouseholds,
+ child: 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: _loadHouseholds,
+ child: const Text('Try Again'),
+ ),
+ ],
+ ),
+ ),
+ data: (households) {
+ if (households.isEmpty) {
+ return _buildEmptyState(context);
+ }
+
+ return Column(
+ children: [
+ if (currentHousehold != null)
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(16),
+ margin: const EdgeInsets.all(16),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.primaryContainer,
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ Icons.home,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Current Household',
+ style: Theme.of(context).textTheme.labelMedium?.copyWith(
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ ),
+ Text(
+ currentHousehold.name,
+ style: Theme.of(context).textTheme.titleMedium?.copyWith(
+ color: Theme.of(context).colorScheme.onPrimaryContainer,
+ ),
+ ),
+ ],
+ ),
+ ),
+ IconButton(
+ onPressed: () => _handleHouseholdTap(currentHousehold),
+ icon: const Icon(Icons.arrow_forward),
+ ),
+ ],
+ ),
+ ),
+ Expanded(
+ child: ListView.builder(
+ padding: const EdgeInsets.only(bottom: 100),
+ itemCount: households.length,
+ itemBuilder: (context, index) {
+ final household = households[index];
+ return HouseholdCard(
+ household: household,
+ onTap: () => _handleHouseholdTap(household),
+ onGenerateInvite: household.canInviteMembers()
+ ? () => _showInviteDialog(household)
+ : null,
+ onLeave: household.members.length > 1
+ ? () => _showLeaveConfirmation(household)
+ : null,
+ showActions: true,
+ );
+ },
+ ),
+ ),
+ ],
+ );
+ },
+ ),
+ ),
+ floatingActionButton: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ FloatingActionButton.extended(
+ onPressed: _showCreateHousehold,
+ icon: const Icon(Icons.add),
+ label: const Text('Create'),
+ heroTag: 'create',
+ ),
+ const SizedBox(height: 12),
+ FloatingActionButton.extended(
+ onPressed: _showJoinHousehold,
+ icon: const Icon(Icons.group_add),
+ label: const Text('Join'),
+ heroTag: 'join',
+ backgroundColor: Theme.of(context).colorScheme.secondary,
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildEmptyState(BuildContext context) {
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.all(32),
+ 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 Households Yet',
+ style: Theme.of(context).textTheme.headlineSmall,
+ ),
+ const SizedBox(height: 8),
+ Text(
+ 'Create your first household or join an existing one to start sharing 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: _showCreateHousehold,
+ icon: const Icon(Icons.add),
+ label: const Text('Create Household'),
+ style: ElevatedButton.styleFrom(
+ padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
+ ),
+ ),
+ const SizedBox(height: 12),
+ OutlinedButton.icon(
+ onPressed: _showJoinHousehold,
+ icon: const Icon(Icons.group_add),
+ label: const Text('Join Household'),
+ style: OutlinedButton.styleFrom(
+ padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Future _showLeaveConfirmation(HouseholdEntity household) async {
+ final confirmed = await showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: const Text('Leave Household'),
+ content: Text('Are you sure you want to leave ${household.name}?'),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(false),
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(true),
+ child: const Text('Leave'),
+ style: TextButton.styleFrom(
+ foregroundColor: Theme.of(context).colorScheme.error,
+ ),
+ ),
+ ],
+ ),
+ );
+
+ if (confirmed == true) {
+ // Handle leaving household
+ // This would be implemented in the repository/provider
+ }
+ }
+ }
+ ```
+
+ Include empty states, loading indicators, error handling, and refresh functionality.
+ Provide clear navigation to household creation, joining, and inventory management.
+
+ flutter analyze lib/features/household/presentation/pages/household_list_page.dart passes
+ Household list page provides complete management hub with navigation integration
+
+
+
+
+
+1. Join household page provides invite code validation with auto-formatting and immediate feedback
+2. Household list page shows all user households with clear management actions
+3. Navigation between create, join, and list flows works seamlessly
+4. Empty states and error handling provide clear user guidance
+5. All components follow existing authentication UI patterns for consistency
+
+
+
+Household management UI is complete with join flow, list hub, and navigation integration ready for user testing.
+
+
+
\ No newline at end of file
diff --git a/.planning/phases/02-household-creation/02-06-PLAN.md b/.planning/phases/02-household-creation/02-06-PLAN.md
new file mode 100644
index 0000000..8da3858
--- /dev/null
+++ b/.planning/phases/02-household-creation/02-06-PLAN.md
@@ -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"
+---
+
+
+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.
+
+
+
+@~/.opencode/get-shit-done/workflows/execute-plan.md
+@~/.opencode/get-shit-done/templates/summary.md
+
+
+
+@.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
+
+
+
+
+
+ Update Router with Household Routes
+ lib/core/router/app_router.dart
+
+ 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.
+
+ flutter analyze lib/core/router/app_router.dart passes
+ Router includes household routes with proper authentication and household state guards
+
+
+
+ Update Auth Provider with Household Integration
+ lib/providers/auth_provider.dart
+
+ 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> {
+ final AuthRepository _repository;
+
+ AuthProvider(this._repository) : super(const AsyncValue.loading()) {
+ _initialize();
+ }
+
+ Future _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 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 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 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 _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.
+
+ flutter analyze lib/providers/auth_provider.dart passes
+ Auth provider integrates household loading with authentication state changes
+
+
+
+ Update Home Page with Household Context
+ lib/features/home/presentation/pages/home_page.dart
+
+ 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(
+ 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.
+
+ flutter analyze lib/features/home/presentation/pages/home_page.dart passes
+ Home page includes household context with navigation to household management
+
+
+
+ Update Main Provider Registration
+ lib/main.dart
+
+ 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.
+
+ flutter analyze lib/main.dart passes
+ Main app includes household provider registration with proper dependencies
+
+
+
+
+
+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
+
+
+
+Household functionality is fully integrated into app navigation and authentication flow with seamless user experience ready for production.
+
+
+
\ No newline at end of file