Files
GroundedHelper/frontend/CLAUDE.md
alvocool 4b71257429 Add groups with shared goals, objectives and chat; drop the water tracker
Groups let people keep each other honest: create one or join with a
six-letter invite code. Each group has shared goals split into measurable
objectives (km, sessions, words) that every member logs toward, a
leaderboard of kept rate and streak with nudges, and a group chat where
check-ins and nudges appear as cards beside the talk. The open chat polls
for new messages.

Reached from a new Groups tab (Training moves into the day rail), a Home
"Your groups" rail that opens the chat, and a Group tile in the add sheet.
Mock mode seeds two groups, a joinable one and members who reply.

The water tracker is removed; it did not fit the app. Bottom sheets are
now square app-wide via the theme.

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

6.6 KiB
Raw Permalink 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: sage-to-cream ambient backdrop, frosted glass controls, painted SceneArt instead of photos, image-led cards, two-tone greetings, square night buttons with a lime spark, no rounded corners.

  • 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

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