A to-do app that doesn't believe you — an enforcement layer rather than a
neutral ledger.
Architecture ported from Autoreceptives/Frontend/Receptive: stacked MVVM with
the mandatory 4-file screen pattern, one ParentViewModel owning the loading /
network / error overlays and the handleError decision tree, one AppDataManager
gateway, dio comms carrying the three identity headers, secure storage with
random-suffixed keys, and a single-chokepoint Navigator. Package root and Dart
package name are both Grounded; org is nya.
The enforcement engine, one unit per formula in utils/:
- DebtEngine w(class) x severity(d) x decay(t), sublinear severity so old
misses cannot swamp the score; abandonment 2x with 30-day
decay immunity; late complete retains 30%
- StandingEngine Good -> Warned -> Grounded -> Lockdown, derived not set;
Grounded replaces home with the overdue queue
- CapacityEngine blocks over-scheduling against p50 of historically completed
minutes, with a learned per-category estimation multiplier
- IntegrityEngine session integrity, weekly volume, plyometric contact ceiling
and enforced recovery gaps
- ExcuseAnalyser on-device excuse clustering plus the confrontation copy
- GuardrailEngine distress detection and rationed amnesty
- ToneEngine all enforcement copy, so the tone cap lives in one place
CommitmentEvent is append-only and is the source of truth rather than the
status field, which is what makes honest history and excuse analysis possible.
Goals contain commitments via parentId, and a task can be run from a
full-screen runner that derives elapsed time from wall-clock so screen-off
cannot lose time. Backgrounding pauses the clock and is counted. The runner is
mirrored into an ongoing notification, with alarm-class full-screen intents
reserved for non-negotiables.
Design language, fonts, icon and native splash are in place; Mason bricks are
retargeted to this project and verified end-to-end.
flutter analyze lib/ reports no errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mfu2gQLSFN21YRBcU2NrTt
45 lines
1.7 KiB
Dart
45 lines
1.7 KiB
Dart
/// The counterweights. An app built on guilt has an obvious failure mode: the
|
|
/// people who need it most delete it during their worst week. These are not
|
|
/// nice-to-haves — they are the retention strategy.
|
|
class GuardrailEngine {
|
|
/// Debt increase over the window that counts as a spike.
|
|
static const double debtSpikeDelta = 15;
|
|
|
|
/// App opens per week below which engagement counts as dropped.
|
|
static const int engagementFloor = 3;
|
|
|
|
/// Readiness score below which inputs count as degraded.
|
|
static const int readinessFloor = 4;
|
|
|
|
/// Distress is the conjunction, not any single signal: debt spiking *and*
|
|
/// engagement dropping *and* readiness degrading. Strictness must never be
|
|
/// the response to someone who is actually struggling.
|
|
static bool detectDistress({
|
|
required double debtDelta,
|
|
required int appOpensThisWeek,
|
|
required int meanReadiness,
|
|
}) {
|
|
final bool debtSpiking = debtDelta >= debtSpikeDelta;
|
|
final bool disengaging = appOpensThisWeek <= engagementFloor;
|
|
final bool degrading = meanReadiness > 0 && meanReadiness <= readinessFloor;
|
|
|
|
return debtSpiking && disengaging && degrading;
|
|
}
|
|
|
|
/// How many amnesty tokens remain this month. Rationed so they feel
|
|
/// valuable, but they exist so a bad flu does not destroy three months of
|
|
/// progress.
|
|
static int tokensRemaining(int granted, int spent) {
|
|
final int remaining = granted - spent;
|
|
return remaining > 0 ? remaining : 0;
|
|
}
|
|
|
|
static bool canSpendAmnesty(int granted, int spent) {
|
|
return tokensRemaining(granted, spent) > 0;
|
|
}
|
|
|
|
/// When distressed, the plan is cut to this many non-negotiables and nothing
|
|
/// else is asked for.
|
|
static const int distressPlanSize = 3;
|
|
}
|