Files
GroundedHelper/frontend/CLAUDE.md
alvocool fe268cb8aa Redesign the UI after the fitness reference and replace the logo
Light sage-to-cream ambient backdrop, frosted glass controls and painted
SceneArt landscapes in place of photography. Home becomes a dashboard with a
two-tone greeting, a frosted summary ring, category portraits, an up-next rail
of image-led cards and a floating glass tab bar. The routine library gets an
image header and painted cards; the player gets a scene stage, steps on an
arc, a dotted clock and a night pill with a lime spark. Goals become painted
cards in their own colour.

New logo: a lime sun resting on a ground line under two arcs. The splash
raises the sun and lights the arcs; launcher icons and native splash are
regenerated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
2026-11-02 12:33:13 +03:00

182 lines
6.6 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
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: sage-to-cream ambient backdrop, frosted glass
controls, painted `SceneArt` instead of photos, image-led cards, two-tone
greetings, night pills with a lime spark.
- 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: **General Sans** (Fontshare, FFL — see `fonts/LICENSE-GeneralSans.txt`),
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
```