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

6.7 KiB
Raw Blame History

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

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