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
6.7 KiB
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:
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 composeAmbientBackground+ glass directly. - Display titles are
TextType.Light. Bold is for labels and metrics only. - Never raw
Text/TextStyle— usetext()fromdesigns/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
- Named event handlers only —
onPressed: _onSave, never() => _save(). selectFieldloads on tap; never pre-load picker options.- List parsing via
utils/ObjectConvertors.dart— never inline.map(...fromJson). - Pagination:
HistoryRequest { PageAndSort }; responses are*Page;last == truestops loading. - Reload-on-return: child
Navigator.pop(context, true), parent refreshes on the result. - New endpoint →
mason make api_endpoint(injects into all four HTTP files). - New secure field →
mason make internal_memory. - 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
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