ClassZoo is a Flutter app (iOS/Android/Web) backed by Supabase for auth, data, and storage. The Flutter project root is src/.
src/
├── lib/
│ ├── main.dart # Entry point
│ ├── auth/auth_gate.dart # Auth flow controller
│ ├── core/
│ │ ├── app_router.dart # Named route definitions
│ │ └── constants.dart # Preference keys, error messages, validation
│ ├── components/ # Reusable widgets
│ ├── extensions/ # BuildContext extensions
│ ├── helpers/ # Utility functions
│ ├── models/ # Data models with fromSupabase() factories
│ ├── pages/ # Screen widgets
│ ├── services/ # Supabase service wrappers
│ └── styles/app_styles.dart # Material 3 theme tokens
├── test/ # Unit tests mirroring lib/ structure
└── supabase/migrations/ # Database migrations
Entry point (main.dart):
- Loads
.env(dev) or.env.production(release) viaflutter_dotenv - Initializes Supabase
- Sets
home: AuthGate()andonGenerateRoute: AppRouter.generateRoute
Auth flow (auth/auth_gate.dart):
- Listens to
Supabase.instance.client.auth.onAuthStateChange - Gates: Login → EULA → Policy acceptance →
BasePageorSchoolSelectionPage - Persists flags via
SharedPreferencesusing keys fromcore/constants.dart
Navigation (core/app_router.dart):
- Explicit string routes:
/students,/policies/:id,/news/create _buildRouteWithIdexpectssettings.arguments as int- Push with:
Navigator.pushNamed(context, '/route', arguments: id)
Main app shell (pages/base_page.dart):
- Bottom nav with role-based tabs (teachers see School tab, parents don't)
Models — Parse Supabase rows via factory constructors:
factory Student.fromSupabase(Map<String, dynamic> json) {
return Student(
id: json['id'] as int,
firstName: json['first_name'] as String,
// ...
);
}- Expose computed getters:
fullName,initials,isTeacher,isParent - Use soft-delete: filter with
deleted_at IS NULLor.isFilter('deleted_at', null)
Async UI — Always check mounted before setState:
setState(() => _isLoading = true);
try {
await doWork();
if (!mounted) return;
context.showSnackBar('Success');
} catch (e) {
if (mounted) context.showSnackBar(e.toString(), isError: true);
} finally {
if (mounted) setState(() => _isLoading = false);
}Snackbars — Use the context extension:
import 'package:classzoo/extensions/build_context_extensions.dart';
context.showSnackBar('Message');
context.showSnackBar('Error', isError: true);Shared components — Prefer existing widgets:
PrimaryActionButton— Full-width action button with loading stateSecondaryDestructiveButton— Danger actionsLoadingScaffold,ErrorScaffold— Standard loading/error statesPageContent,SectionHeader,FormTextLabel— Layout helpers
Constants — Never hardcode preference keys or error messages:
import 'package:classzoo/core/constants.dart';
prefs.getInt(Constants.preferenceIdActiveSchool);- Access via
Supabase.instance.clientor wrap in services for reused logic - Use
.maybeSingle()when a row may not exist - Use
.inFilter()for multi-value and.isFilter()for NULL checks - Always filter by
school_idand respect RLS policies
Key tables: user_profiles, user_schools, students, school_policies, user_policy_acceptance
Tests mirror lib/ structure in src/test/. Use helpers from test/test_helpers.dart:
// Initialize Supabase mock
await initializeSupabaseForTesting();
// Mock SharedPreferences
setUpMockSharedPreferences({'active-school-id': 1});
// Freeze time for date-dependent tests
final timeMock = TimeMock();
timeMock.freeze(DateTime(2025, 1, 15), ...);flutter pub get # Install dependencies
flutter run # Run app
flutter test # Run tests
dart format . # Format code
flutter analyze # Static analysis- Format:
dart format --output=none --set-exit-if-changed . - Analyze:
dart analyze --fatal-infos --fatal-warnings - Test:
flutter test --coverage
- Use constants from
core/constants.dart— no hardcoded keys - Reuse components from
components/— check before creating new ones - Add routes to
AppRouterwith proper argument typing - Check
mountedbefore anysetStateafter async operations - Follow model pattern:
fromSupabase(),toJson(), computed getters - Run
dart format .andflutter analyzebefore committing - Add tests for new models, services, and complex widgets
- Enter plan mode for ANY non-trivial change (more than formatting, comments, or docs).
- If something goes sideways, STOP and re-plan immediately.
- Use plan mode for verification steps, not just building.
- Write detailed specs upfront to reduce amibiguity.
- Use subagents liberally to keep main contex window clean
- Offload research, exploration and parallel analysis to subagents
- For complex tasks, use more compute using subagents
- One tasks per subagent for focused execution
- After ANY correction from the user: update
.github/copilot-instructions.mdwith the new rules, updating existing rules as necessary. - Write rules for yourself that prevent making the same mistake again.
- Review lessons at session start.
- Never mark a task complete without proving it works.
- Ask yourself, would a human reviewer approve this change?
- Run tests, check formatting, validate correctness as needed.
- For non-trivial code, ask "is there a more elegant way to do this?"
- If a fix feels clunky, refactor until it feels right.
- Skip this for simple obvious code changes, don't over-engineer.
- Challenge your own work before finalizing.