|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Repository Structure |
| 6 | + |
| 7 | +This is a monorepo with four sub-projects: |
| 8 | + |
| 9 | +- `school_data_hub_flutter/` — Flutter client (Android, Windows; WIP iOS/macOS/Linux) |
| 10 | +- `school_data_hub_server/` — Serverpod 2.9.1 Dart backend |
| 11 | +- `school_data_hub_client/` — Serverpod-generated shared client library (used by both Flutter and server) |
| 12 | +- `school_data_hub_website/` — Project website |
| 13 | + |
| 14 | +## Common Commands |
| 15 | + |
| 16 | +### Flutter Client (`school_data_hub_flutter/`) |
| 17 | + |
| 18 | +```bash |
| 19 | +flutter run # Run on connected device/emulator |
| 20 | +flutter test # Run all tests |
| 21 | +flutter test test/path/to/test.dart # Run a single test file |
| 22 | +flutter analyze # Lint |
| 23 | +flutter pub get # Install dependencies |
| 24 | +``` |
| 25 | + |
| 26 | +### Server (`school_data_hub_server/`) |
| 27 | + |
| 28 | +```bash |
| 29 | +# Run locally (apply pending migrations on start) |
| 30 | +dart run bin/main.dart --apply-migrations |
| 31 | + |
| 32 | +# Shortcut via Makefile |
| 33 | +make run |
| 34 | + |
| 35 | +# After modifying .yaml model schemas — regenerate models + UML |
| 36 | +make generate # = serverpod generate + uml |
| 37 | +make migration # = serverpod generate + create-migration + apply + uml |
| 38 | + |
| 39 | +# Start Docker (Postgres + Redis) for local dev |
| 40 | +make docker # = docker compose up --build --detach |
| 41 | +``` |
| 42 | + |
| 43 | +### Release (Shorebird — from `school_data_hub_flutter/`) |
| 44 | + |
| 45 | +```bash |
| 46 | +shorebird release android --artifact=apk # Full release |
| 47 | +shorebird patch android # OTA patch |
| 48 | +``` |
| 49 | + |
| 50 | +## Architecture |
| 51 | + |
| 52 | +### Privacy-First Design |
| 53 | + |
| 54 | +Personal pupil data (`PupilIdentity`) is **never stored on the server**. It is stored encrypted on each device (via `flutter_secure_storage`) and transferred between devices over an encrypted stream. The server only holds anonymous `PupilData` referenced by a numeric internal ID. API calls require both an authenticated session **and** the pupil's internal ID. |
| 55 | + |
| 56 | +### Flutter Client Architecture |
| 57 | + |
| 58 | +**Dependency Injection** uses `get_it` (accessed via the `di` global from `watch_it`/`flutter_it`). Managers are registered in three ordered scopes: |
| 59 | + |
| 60 | +1. **Core scope** (always registered, `InitManager.registerCoreManagers()`): `EnvManager`, `NotificationService`, `ServerpodConnectivityMonitor`, `ShorebirdUpdateManager` |
| 61 | +2. **Active-env scope** (`InitScope.onActiveEnvScope`): registered in `InitOnActiveEnv` after an environment (school key) is set |
| 62 | +3. **Auth scope** (`InitScope.onAuthScope`): registered in `InitOnUserAuth` after login — contains all feature managers (`PupilProxyManager`, `AttendanceManager`, etc.) |
| 63 | +4. **Matrix scope** (`InitScope.onMatrixEnvScope`): lazily pushed when Matrix credentials are present |
| 64 | + |
| 65 | +Scopes are dropped on logout/env-change and re-registered fresh. See `core/init/init_manager.dart` and `core/init/init_on_user_auth.dart` for the full dependency graph. |
| 66 | + |
| 67 | +**State management** uses `watch_it` (`flutter_it` package). All managers extend `ChangeNotifier`. Widgets extend `WatchingWidget` (stateless) or `WatchingStatefulWidget` (stateful) and use: |
| 68 | +- `watchValue((ManagerType x) => x.someValueNotifier)` — watch a `ValueNotifier` field |
| 69 | +- `watch(someChangeNotifier)` — watch any `Listenable` |
| 70 | + |
| 71 | +**The `PupilProxy` model** (`features/_pupil/domain/models/pupil_proxy.dart`) is the central reactive object. It combines: |
| 72 | +- `PupilData` — server model from Serverpod, fetched via `PupilDataApiService` |
| 73 | +- `PupilIdentity` — local personal data (name, class, birthday, etc.) managed by `PupilIdentityManager` |
| 74 | + |
| 75 | +`PupilProxyManager` holds the master list of all proxies, keeps them updated via the `HubStreamService` server-sent event stream, and re-fetches on reconnect. |
| 76 | + |
| 77 | +**Feature structure** follows `data / domain / presentation` layers: |
| 78 | +- `data/` — API service classes (one per feature, calling Serverpod endpoints via `school_data_hub_client`) |
| 79 | +- `domain/` — manager singletons, domain models, filter managers |
| 80 | +- `presentation/` — pages and widgets |
| 81 | + |
| 82 | +**Real-time updates** flow through `HubStreamService`, which wraps a Serverpod streaming endpoint. Managers subscribe to this stream and handle `PupilData` (upsert) and `HubReconnected` (re-fetch) events. |
| 83 | + |
| 84 | +**Filtering** is done through a hierarchy of filter managers (`PupilsFilter`, `PupilFilterManager`, feature-specific filter managers). `PupilsFilter` is the aggregate filter used to produce the filtered list shown in UIs. Migration to the `PupilsFilter` architecture is in progress — some older features still use ad-hoc filtering. |
| 85 | + |
| 86 | +**Navigation** uses a bottom navigation bar with 5 tabs: Pupil Lists, School Lists, Learning Resources, Tools, Settings. Individual features navigate imperatively (no `go_router` routing in use for main flow; `go_router` is listed as a dependency but is a WIP). |
| 87 | + |
| 88 | +### Server Architecture |
| 89 | + |
| 90 | +Built on **Serverpod 2.9.1**. Model schemas live in `lib/src/_features/<feature>/schemas/` as `.yaml` files. Running `serverpod generate` produces Dart classes in `lib/src/generated/` and the client library in `school_data_hub_client/`. |
| 91 | + |
| 92 | +Features mirror the Flutter client: `_attendance`, `_authorizations`, `_pupil`, `_school_lists`, `_schoolday_events`, `books`, `learning`, `learning_support`, `matrix`, `timetable`, `user`, `workbooks`, etc. |
| 93 | + |
| 94 | +Encrypted files (documents, images) are stored in `storage/private/` subdirectories (`avatars`, `documents`, `events`, `auths`). |
| 95 | + |
| 96 | +### Local Development Setup |
| 97 | + |
| 98 | +Server URL in the school key: |
| 99 | +- **Windows**: `http://127.0.0.1:5000/api` |
| 100 | +- **Android Emulator**: `http://10.0.2.2:5000/api` |
| 101 | + |
| 102 | +The server requires Docker running (Postgres + Redis). Start with `make docker` from `school_data_hub_server/`, then `make run` to apply migrations and start the Dart server. |
| 103 | + |
| 104 | +## Key Conventions |
| 105 | + |
| 106 | +- `di<SomeManager>()` — access any registered singleton anywhere in the Flutter app |
| 107 | +- Managers own their API service instances directly (instantiated in the manager, not DI-registered separately, unless shared) |
| 108 | +- Use `_log = Logger('FeatureName')` in every class; logs are collected by `LogService` and visible in the in-app log viewer |
| 109 | +- Server model changes: edit the `.yaml` schema → `make migration` → never hand-edit generated files in `lib/src/generated/` |
| 110 | +- All `.dart` files in `school_data_hub_client/` are generated — do not edit them directly |
0 commit comments