Coach is the centre tab: questions about routines, slips, streak, today, active time and debt, answered on the device from the user's own data, with a target bar chart and buttons that start routines or open the right screen. The palette moves to neutral grey, near-black and one orange accent, and Home's header becomes a light panel. The nav is now overlapping frosted circles whose active disc pops forward, and tabs fade and rise in on switch. Goals leaves the nav for Coach and stays one tap away in Home's category row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
183 lines
6.7 KiB
Markdown
183 lines
6.7 KiB
Markdown
# Grounded — Flutter Frontend
|
||
|
||
*A to-do app that doesn't believe you.*
|
||
|
||
Architecture ported from `Documents/Autoreceptives/Frontend/Receptive`. Same
|
||
structure, conventions and infrastructure; the business logic is the debt and
|
||
enforcement engine.
|
||
|
||
---
|
||
|
||
## 1. Stack
|
||
|
||
Flutter (Dart `^3.6.2`), **stacked** MVVM, **dio**, **flutter_secure_storage**,
|
||
**flutter_dotenv**, **fl_chart**, **flutter_local_notifications**.
|
||
Package name and `lib/` root are both `Grounded`, so imports read
|
||
`package:Grounded/Grounded/...`. Org is `nya` (`nya.grounded`).
|
||
|
||
Codegen is via **Mason** — screens, endpoints and storage fields are generated,
|
||
never hand-scaffolded.
|
||
|
||
---
|
||
|
||
## 2. Layout (`lib/Grounded/`)
|
||
|
||
```
|
||
about/external/data/ responses & domain models (+ pages/ for pagination)
|
||
about/external/initial/ request bodies
|
||
about/internal/application/ enums and app-internal models
|
||
about/internal/file/ FileStorage
|
||
comms/ CommsDirections · ConnectComms · Comms
|
||
informatics/ DataManager · AppDataManager
|
||
memory/ ConnectInternalMemory · InternalMemory
|
||
configs/ Navigator · Env · NotificationServiceConfig
|
||
designs/ Component · Shell · Responsive · buttons/ input/ text/
|
||
see/<feature>/ screens, 4 files each (group/ = groups, shared goals, chat)
|
||
utils/ Colors · CommonUtils · engines · validators
|
||
```
|
||
|
||
---
|
||
|
||
## 3. The 4-file screen pattern (mandatory)
|
||
|
||
| File | Role |
|
||
|---|---|
|
||
| `Foo.dart` | `StatefulWidget` shell — creates `FooState` only. |
|
||
| `FooState.dart` | All UI + local state; implements `ConnectFoo`. |
|
||
| `ViewFoo.dart` | `extends ParentViewModel` — business logic, calls `DataManager`, pushes results back through `ConnectFoo`. |
|
||
| `ConnectFoo.dart` | Abstract callbacks so the ViewModel never touches widgets. |
|
||
|
||
`ViewModelBuilder<ViewFoo>.reactive` wires them. `onViewModelReady` assigns
|
||
`_model` and calls one named `_initiate()`. **No logic in `builder:` before the
|
||
`return`.**
|
||
|
||
Generate with: `mason make mvvc_template --project Grounded --screen Foo`
|
||
|
||
---
|
||
|
||
## 4. ParentViewModel
|
||
|
||
Constructs the single `AppDataManager` and owns everything cross-cutting:
|
||
`showLoading` / `closeLoading`, `hasNetwork(retry)`, `showError`,
|
||
`showApplicationNotification`, and `handleError` — whose decision tree routes
|
||
`401 → sessionExpired()` and error code `5000.901 → updateMe()`.
|
||
|
||
Guard every network call:
|
||
|
||
```dart
|
||
void loadThing(ThingRequest request) async {
|
||
if (!await hasNetwork(() => loadThing(request))) return;
|
||
showLoading('Loading…');
|
||
try {
|
||
final response = await getDataManager().getThing(request);
|
||
closeLoading();
|
||
connection.onThingLoaded(Thing.fromJson(response.data));
|
||
} catch (e) {
|
||
handleError(e, () => loadThing(request), () => dismissError(), 'Retry');
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Domain — the enforcement engine
|
||
|
||
Three primitives: **Commitment** (what you said you'd do), **Debt** (weighted,
|
||
decaying score), **Standing** (`Good → Warned → Grounded → Lockdown`).
|
||
|
||
**Goals** contain commitments via `Commitment.parentId` — "Workout" holds
|
||
"Monday shoulders". Goals never carry debt; the tasks inside them do.
|
||
|
||
Engines in `utils/`, each mapping 1:1 to the spec's formulas:
|
||
|
||
| Engine | Owns |
|
||
|---|---|
|
||
| `DebtEngine` | `w(class) × severity(d) × decay(t)`; abandonment 2×, no decay 30d; late complete retains 30% |
|
||
| `StandingEngine` | tier derivation; what each tier permits |
|
||
| `CapacityEngine` | blocks over-scheduling against p50 of historical completed minutes × 0.85 |
|
||
| `IntegrityEngine` | session integrity, weekly volume, plyo contact ceiling, recovery gate |
|
||
| `ExcuseAnalyser` | on-device excuse clustering + the confrontation copy |
|
||
| `GuardrailEngine` | distress detection, amnesty tokens |
|
||
| `ToneEngine` | **all** enforcement copy — the one place the tone cap is enforced |
|
||
|
||
Thresholds live in `Thresholds.dart`. Never inline a limit.
|
||
|
||
`CommitmentEvent` is append-only and is the source of truth — **not** the
|
||
status field. That's what makes honest history and excuse analysis possible.
|
||
|
||
---
|
||
|
||
## 6. Design language
|
||
|
||
See `design.md`. In short: neutral light-grey backdrop with one orange accent, frosted glass
|
||
controls, painted `SceneArt` instead of photos, image-led cards, two-tone
|
||
greetings, night buttons with a lime spark, softly rounded corners (never
|
||
pill-soft) and a floating round-button nav that switches tabs in place.
|
||
|
||
- Screens are built from `Sheet` (`designs/Shell.dart`); Home, Routines and
|
||
the routine player compose `AmbientBackground` + glass directly.
|
||
- Display titles are `TextType.Light`. Bold is for labels and metrics only.
|
||
- Never raw `Text`/`TextStyle` — use `text()` from `designs/text/Text.dart`.
|
||
- Never inline hex — use `utils/Colors.dart`.
|
||
- Standing owns a colour used consistently everywhere (chrome, chip, meter).
|
||
- One primary button per screen; everything else outlined or text.
|
||
|
||
Font: **Inter** (SIL OFL — see `fonts/LICENSE-Inter.txt`; Light is Inter Display),
|
||
wrapped as `TextType.{Bold,Medium,Regular,Light}`.
|
||
|
||
---
|
||
|
||
## 7. Live task runner
|
||
|
||
`see/live/` is the full-screen runner: a task being run takes over the screen.
|
||
Elapsed time is derived from **wall-clock**, not counted by the ticker, so a
|
||
screen-off period cannot lose time. Backgrounding pauses the clock and
|
||
increments `backgroundedCount` — that's what makes Timer proof mean anything.
|
||
|
||
`LocalNotificationEngine` mirrors it into an ongoing notification and fires
|
||
alarm-class **full-screen intents** for non-negotiables. Android manifest
|
||
carries `USE_FULL_SCREEN_INTENT`, `SCHEDULE_EXACT_ALARM`, and the activity is
|
||
`showWhenLocked` + `turnScreenOn`.
|
||
|
||
---
|
||
|
||
## 8. Hard conventions
|
||
|
||
1. Named event handlers only — `onPressed: _onSave`, never `() => _save()`.
|
||
2. `selectField` loads on tap; never pre-load picker options.
|
||
3. List parsing via `utils/ObjectConvertors.dart` — never inline `.map(...fromJson)`.
|
||
4. Pagination: `HistoryRequest { PageAndSort }`; responses are `*Page`; `last == true` stops loading.
|
||
5. Reload-on-return: child `Navigator.pop(context, true)`, parent refreshes on the result.
|
||
6. New endpoint → `mason make api_endpoint` (injects into all four HTTP files).
|
||
7. New secure field → `mason make internal_memory`.
|
||
8. One class per file, named after the class.
|
||
|
||
---
|
||
|
||
## 9. Environment
|
||
|
||
`.env` is a bundled asset, loaded in `main()` before `runApp`:
|
||
|
||
```
|
||
GROUNDED_PRODUCTION_PATH · GROUNDED_LOCAL_PATH · LOCALISED_APP_VERSION
|
||
```
|
||
|
||
`isProd` in `CommsDirections.dart` flips local↔production. Requests carry the
|
||
three identity headers: `what`, `whom`, `version`.
|
||
|
||
---
|
||
|
||
## 10. Commands
|
||
|
||
```bash
|
||
flutter pub get
|
||
flutter analyze lib/ # bricks/ are excluded — templates aren't valid Dart
|
||
flutter run
|
||
|
||
mason get
|
||
mason make mvvc_template --project Grounded --screen MyScreen
|
||
|
||
dart run flutter_launcher_icons
|
||
dart run flutter_native_splash:create
|
||
```
|