Skip to content

Instantly share code, notes, and snippets.

@adamstirtan
Created February 3, 2026 14:27
Show Gist options
  • Select an option

  • Save adamstirtan/95ef925f46dcbcdef92cf46fb655dc70 to your computer and use it in GitHub Desktop.

Select an option

Save adamstirtan/95ef925f46dcbcdef92cf46fb655dc70 to your computer and use it in GitHub Desktop.
ClassZoo copilot-instructions.md

ClassZoo Copilot Instructions

ClassZoo is a Flutter app (iOS/Android/Web) backed by Supabase for auth, data, and storage. The Flutter project root is src/.

Project structure

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

App architecture

Entry point (main.dart):

  • Loads .env (dev) or .env.production (release) via flutter_dotenv
  • Initializes Supabase
  • Sets home: AuthGate() and onGenerateRoute: AppRouter.generateRoute

Auth flow (auth/auth_gate.dart):

  • Listens to Supabase.instance.client.auth.onAuthStateChange
  • Gates: Login → EULA → Policy acceptance → BasePage or SchoolSelectionPage
  • Persists flags via SharedPreferences using keys from core/constants.dart

Navigation (core/app_router.dart):

  • Explicit string routes: /students, /policies/:id, /news/create
  • _buildRouteWithId expects settings.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)

Code patterns

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 NULL or .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 state
  • SecondaryDestructiveButton — Danger actions
  • LoadingScaffold, ErrorScaffold — Standard loading/error states
  • PageContent, SectionHeader, FormTextLabel — Layout helpers

Constants — Never hardcode preference keys or error messages:

import 'package:classzoo/core/constants.dart';
prefs.getInt(Constants.preferenceIdActiveSchool);

Supabase conventions

  • Access via Supabase.instance.client or 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_id and respect RLS policies

Key tables: user_profiles, user_schools, students, school_policies, user_policy_acceptance

Testing

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), ...);

Build commands (run from src/)

flutter pub get          # Install dependencies
flutter run              # Run app
flutter test             # Run tests
dart format .            # Format code
flutter analyze          # Static analysis

CI requirements (enforced on PR)

  1. Format: dart format --output=none --set-exit-if-changed .
  2. Analyze: dart analyze --fatal-infos --fatal-warnings
  3. Test: flutter test --coverage

Contribution checklist

  • Use constants from core/constants.dart — no hardcoded keys
  • Reuse components from components/ — check before creating new ones
  • Add routes to AppRouter with proper argument typing
  • Check mounted before any setState after async operations
  • Follow model pattern: fromSupabase(), toJson(), computed getters
  • Run dart format . and flutter analyze before committing
  • Add tests for new models, services, and complex widgets

Workflow Orchestration

1. Plan mode default

  • 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.

2. Subagent strategy

  • 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

3. Self-Improvement loop

  • After ANY correction from the user: update .github/copilot-instructions.md with the new rules, updating existing rules as necessary.
  • Write rules for yourself that prevent making the same mistake again.
  • Review lessons at session start.

4. Verification steps

  • 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.

5. Demand elegance

  • 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment