Imported from TheSimpleApp/simpleapp (
AGENTS.md). Install upstream withnpx skills add TheSimpleApp/simpleapp. Copyright stays with the author.
AGENTS.md - BriefBuilder Codebase Guide
Project Overview
BriefBuilder is a Flutter application that guides clients through a structured planning flow to produce clear MVP plans. It features a shared workspace model where admins manage clients and apps, while clients complete guided briefs for their projects.
Tech Stack:
- Flutter 3.6+ with Material 3
- Supabase (PostgreSQL + Auth)
- Provider for state management
- go_router for navigation
- Google Fonts (Inter)
Core Architecture Patterns
1. Clean Separation of Concerns
lib/
├── models/ # Data models (toJson, fromJson, copyWith)
├── services/ # Database operations (CRUD for each model)
├── providers/ # State management (ChangeNotifier)
├── screens/ # UI pages (organized by feature)
├── auth/ # Authentication layer (abstract + concrete)
├── supabase/ # Supabase config & generic service
└── theme.dart # Centralized theming & design tokens
2. Data Model Pattern
Every model follows this structure:
class ModelName {
final String id;
final DateTime createdAt;
final DateTime updatedAt;
// Constructor with required fields
// factory fromJson() - handles String/DateTime conversion
// toJson() - converts to Map for Supabase
// copyWith() - immutable updates
}
Key Convention: All timestamp fields handle both String (from Supabase) and DateTime (local) types:
createdAt: json['created_at'] is String
? DateTime.parse(json['created_at'] as String)
: (json['created_at'] as DateTime)
3. Service Layer Pattern
Each model has a corresponding service class:
class ModelService {
static const String _table = 'table_name';
Future<Model?> getById(String id) async { ... }
Future<List<Model>> getAll() async { ... }
Future<Model?> create(Model model) async { ... }
Future<Model?> update(String id, Map<String, dynamic> updates) async { ... }
Future<bool> delete(String id) async { ... }
}
Error Handling: Always wrap in try/catch, log with debugPrint(), return null/empty list on error.
4. Provider Pattern
State management uses ChangeNotifier:
class FeatureProvider extends ChangeNotifier {
bool _isLoading = false;
Data? _data;
bool get isLoading => _isLoading;
Data? get data => _data;
Future<void> loadData() async {
_isLoading = true;
notifyListeners();
// ... fetch data
_isLoading = false;
notifyListeners();
}
}
Register in main.dart via MultiProvider.
5. Authentication Architecture
Abstract Interface: lib/auth/auth_manager.dart defines:
- Core methods:
authStateChanges,currentUser,signOut - Mixins for different auth methods:
EmailSignInManager,MagicLinkSignInManager, etc.
Concrete Implementation: lib/auth/supabase_auth_manager.dart implements the interface using Supabase Auth.
AuthProvider: Wraps auth manager in ChangeNotifier for reactive UI.
6. Navigation with go_router
- Routes defined in
lib/nav.dart - Use
context.go()orcontext.push()for navigation - NEVER use
Navigator.push()orNavigator.pop() - Auth redirect logic integrated into router
- Route constants in
AppRoutesclass
Database Schema
Current tables (Supabase):
users- User profiles (id FK to auth.users)workspaces- Apps/projects created by adminsworkspace_members- Join table for user-workspace relationshipsbriefs- One brief per workspace (status, currentPhase)brief_responses- Individual question answers (JSON answer field)comments- Comments on briefs (future feature)
Key Relationships:
workspace_members.user_id→users.idworkspace_members.workspace_id→workspaces.idbriefs.workspace_id→workspaces.idbrief_responses.brief_id→briefs.id
Code Style Guidelines
Naming Conventions
- Classes: PascalCase (
BriefProvider,WorkspaceService) - Files: snake_case (
brief_provider.dart,workspace_service.dart) - Private members: Leading underscore (
_isLoading,_authManager) - Constants: camelCase for class members, UPPER_SNAKE for top-level (
static const String _table)
Import Style
Always use absolute imports:
import 'package:thesimpleapp/models/brief.dart'; // ✅ Good
import '../models/brief.dart'; // ❌ Bad
Widget Style
Use classes, not functions, for widgets:
// ✅ Good
class AppHeader extends StatelessWidget {
const AppHeader({super.key});
@override
Widget build(BuildContext context) => ...
}
// ❌ Bad
Widget _buildAppHeader() => ...
Make reusable widgets public (no leading underscore) so they can be imported.
Expression Bodies
Prefer => for simple functions:
bool get isAuthenticated => _user != null; // ✅
Widget build(BuildContext context) => Scaffold(...); // ✅
Trailing Commas
Avoid excessive trailing commas for concise formatting (personal preference in this codebase).
Error Handling
Always log errors:
try {
// operation
} catch (e) {
debugPrint('Error doing X: $e');
return null; // or empty list
}
Theme System
Design Tokens
Centralized in lib/theme.dart:
Spacing:
AppSpacing.xs // 4.0
AppSpacing.sm // 8.0
AppSpacing.md // 16.0
AppSpacing.lg // 24.0
AppSpacing.xl // 32.0
Border Radius:
AppRadius.sm // 8.0
AppRadius.md // 12.0
AppRadius.lg // 16.0
Text Styles:
Access via context.textStyles.headlineMedium, .bodyLarge, etc.
Extensions:
context.textStyles.titleLarge.bold
context.textStyles.bodyMedium.withColor(Colors.red)
Color Palette:
- Light mode: Soft blue-gray (
LightModeColors) - Dark mode: High contrast (
DarkModeColors) - Always reference theme colors via
Theme.of(context).colorScheme.primary, etc. - Never hardcode colors except predefined
Colors.*constants
Supabase Integration
Generic Service Layer
lib/supabase/supabase_config.dart provides:
SupabaseConfig.initialize()- Initialize SupabaseSupabaseConfig.client- Access Supabase clientSupabaseService- Generic CRUD operations
Common Operations
// Select multiple
final data = await SupabaseService.select(
'table_name',
filters: {'column': 'value'},
orderBy: 'created_at',
ascending: false,
);
// Select single
final data = await SupabaseService.selectSingle(
'table_name',
filters: {'id': id},
);
// Insert
final result = await SupabaseService.insert('table_name', model.toJson());
// Update (always include updated_at)
final result = await SupabaseService.update(
'table_name',
{...updates, 'updated_at': DateTime.now().toIso8601String()},
filters: {'id': id},
);
// Delete
await SupabaseService.delete('table_name', filters: {'id': id});
Auth Operations
Via SupabaseAuthManager:
await authManager.signInWithEmail(context, email, password);
await authManager.createAccountWithEmail(context, email, password);
await authManager.signOut();
Common Implementation Patterns
1. Adding a New Feature Screen
- Create
lib/screens/feature/screen_name.dart - Add route in
lib/nav.dart:GoRoute( path: '/feature/:id', name: 'feature-detail', pageBuilder: (context, state) { final id = state.pathParameters['id']!; return NoTransitionPage(child: FeatureScreen(id: id)); }, ) - Add route constant:
static String feature(String id) => '/feature/$id';
2. Creating a New Data Model
- Create
lib/models/model_name.dartwith standard pattern - Create
lib/services/model_name_service.dart - Add corresponding Supabase table in SQL migrations
- Add RLS policies
3. Adding State Management
- Create
lib/providers/feature_provider.dart - Register in
main.dart:ChangeNotifierProvider(create: (_) => FeatureProvider()) - Access in screens:
final provider = Provider.of<FeatureProvider>(context); // or final provider = context.watch<FeatureProvider>();
4. Loading States
Standard pattern:
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.data == null) {
return const Center(child: Text('No data'));
}
return ListView(...);
5. Form Handling
Use TextEditingController + Form + GlobalKey<FormState>:
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
super.dispose();
}
Form(
key: _formKey,
child: TextFormField(
controller: _emailController,
validator: (value) => value?.isEmpty ?? true ? 'Required' : null,
),
)
// On submit
if (_formKey.currentState!.validate()) {
// process form
}
Project-Specific Rules
User Roles
Two roles: client and admin
- Stored in
users.role(TEXT, default 'client') - Admins can:
- Manage workspaces
- Manage users and roles
- View all briefs
- Clients can:
- Access assigned workspaces
- Fill out briefs
Brief Flow Stages
6 phases (1-6):
- Discovery
- Goals
- Features
- Style
- Priorities
- Review
Stored in briefs.current_phase (INT)
Brief Status
Values: draft, in_progress, submitted, completed
Stored in briefs.status (TEXT)
Auto-Bootstrap Admin
Hard-coded in SupabaseAuthManager._handleAuthStateChange():
- If user email is
david@thesimpleapp.io, auto-set role toadmin
Workspace Access Logic
- Admins see all workspaces in admin panel
- Clients see only workspaces they're assigned to via
workspace_members - Auto-redirect logic:
- 1 workspace → go directly to brief flow
- Multiple workspaces → show picker dashboard
Testing & Debugging
Debug Login
Login screen has a "Demo Login" button:
- Email:
demo@test.com - Password:
demo1234 - Auto-creates account if doesn't exist
Logging
Use debugPrint() from package:flutter/foundation.dart:
debugPrint('Feature: Action completed - result: $data');
Never use:
print()(doesn't respect debug mode)- Just toast/snackbar for errors (always log as well)
Dependencies
Key packages:
supabase_flutter- Backend & authprovider- State managementgo_router- Navigationgoogle_fonts- Typography (Inter)uuid- Generate IDs
Keep it minimal - prefer Flutter/Dart built-in libraries over external packages.
Current State (as of last update)
Completed:
- ✅ Authentication (email/password)
- ✅ User management
- ✅ Workspace CRUD
- ✅ Admin panel (clients, apps, assign)
- ✅ Data models & services
- ✅ BriefProvider (auto-save logic)
In Progress:
- 🚧 Phase 2: Client brief flow UI (dashboard + stepper)
Not Started:
- ⏳ Comments system
- ⏳ Brief summary/export
- ⏳ Admin view of client briefs
Agent Workflow Rules
After every task submission, the agent MUST:
1. Check and Update plan.md
- Review the current state of
plan.md - Mark completed items as done (
[x]) - Update "Current Status" section
- Add new tasks if scope has expanded
- Note any blockers or questions that arose
- Refactor if the plan structure needs improvement
2. Check and Update changelog.md
- Review if the changes warrant a changelog entry
- Add entries for user-facing changes only (not internal refactors)
- Use simple, non-technical language (this is customer-facing!)
- Group changes under dated sections
- Categories: Added, Fixed, Improved, Changed
Changelog writing guidelines:
- ✅ "You can now leave comments on brief questions"
- ✅ "Fixed an issue where your progress wasn't saving"
- ❌ "Implemented CommentProvider with ChangeNotifier pattern"
- ❌ "Refactored service layer to use async/await"
Key Constraints & Gotchas
- go_router only - Do not use Navigator directly
- Absolute imports - Always use
package:thesimpleapp/... - Widget classes, not functions - Reusable widgets should be classes
- Theme colors only - No hardcoded hex colors
- Timestamp handling - Always support both String and DateTime in fromJson
- Auto-updated_at - Always include in update operations
- Error logging - Always debugPrint errors, don't silently fail
- Material 3 - Use Material 3 components and design patterns
Quick Reference Commands
# Dependencies
flutter pub get
# Code generation (if needed)
flutter pub run build_runner build
# Run app
flutter run
# Analyze code
flutter analyze
Questions to Ask When Implementing
- Does this need a new model? → Follow model pattern + service + SQL table
- Does this need state management? → Create provider, register in main
- Is this a new screen? → Add route in nav.dart
- Am I using colors? → Reference theme, don't hardcode
- Am I creating a reusable widget? → Make it a public class
- Am I handling errors? → Always try/catch + debugPrint
- Am I navigating? → Use context.go/push, never Navigator
- Am I updating records? → Include updated_at timestamp
Last Updated: 2025-01-13 Project Version: v1.0.0+1