Files
GroundedHelper/frontend/CLAUDE.md
alvocool 4cd3f6c038 Add Coach, take the reference's colours and copy its nav and motion
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
2026-11-09 07:22:35 +03:00

183 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```