Also bumps Flutter 3.47.3, flutter_secure_storage 11.1.0, idb_shim 2.9.9, mcp 2.2.0 and ruff 0.16.7 so the freshness gate lets this through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
10 KiB
This file is a ready-to-use prompt. Open this repo and say "do dopamine-ux-diet-guard". It is self-contained -- it needs no context from any other session.
Canonical copy:
~/src/utils/dopamine-ux/03-diet-guard.md(source of truth; this is a distributed copy). Running order and cross-prompt rules:~/src/utils/dopamine-ux/00-INDEX.md. Generated 2026-08-16 from a survey of this repo -- line numbers are accurate as of that date; anchor on the symbol names, not the line numbers.
⛔ WHEN YOU FINISH THIS PROMPT
Do both, in the same session that completes the work:
-
Delete this file and commit that deletion together with the implementation:
git rm prompts/dopamine-ux-diet-guard.mdDo not leave a finished prompt lying in the repo -- a stale prompt is indistinguishable from a pending one, and the next session will re-run it.
-
Log completion in the canonical index, in
~/src/utils(a different repo, so it needs its own commit):cd ~/src/utils # append to the "Completion log" table in dopamine-ux/00-INDEX.md: # | 03-diet-guard.md | DONE <YYYY-MM-DD> | <impl commit sha> | <one-line note> | git add dopamine-ux/00-INDEX.md git commit -m "dopamine-ux: mark 03-diet-guard.md done" git push
If you finish only part of the prompt, do neither -- instead edit this file to strike out what is done and state what remains, so the next session starts from the true state. Partial completion must never be recorded as DONE.
Make logging a meal feel satisfying in diet_guard_app
findings (4, ranked by value over effort)
_onLogMeal()gives zero feedback — no haptic, no sound, no animation, no confirmation. The flagship gap. Fix needs no new dependency.- Streak text is inert — "Adherence streak: N days" is a plain
Textthat blinks to a new value. Real data, no salience. - No VIBRATE permission — blocks haptics until added to the manifest.
- No audio capability at all — no package, no
assets:block. Step 2 only.
what
Logging a meal currently produces no feedback whatsoever. _onLogMeal() in
lib/screens/log_meal_screen.dart writes the meal, syncs it, clears the fields,
and calls setState(() { _progress = buildTodayProgress(log); }). The progress
card silently appears. No SnackBar, no haptic, no sound, no animation.
That is the whole problem. In kuhy's words: "noting what you ate should be SATISFYING and ADDICTING so you WANT TO ACTUALLY note down what you ate." The data quality of the entire app depends on the logging habit surviving the days you don't feel like it, and right now the act of logging gives nothing back.
Make the moment of logging land: an immediate haptic, a real confirmation, and the streak/progress reacting visibly rather than blinking into existence.
where
Repo: ~/src/diet-guard. Flutter app: ~/src/diet-guard/app (package diet_guard_app).
Primary:
app/lib/screens/log_meal_screen.dart—_onLogMeal()(line 122-168 as of 2026-08-16; grep the symbol, the line will drift). ThesetStatethat silently swaps in the progress card is at ~:164-166. Card rendered at ~:240.app/lib/widgets/today_progress_card.dart—TodayProgressmodel (~:15),TodayProgressCard(~:64). Consumed/budget kcal ~:100, P/C/F macro row ~:122, "Adherence streak: N days" ~:129-130 — all plainText, no ring, no bar.app/lib/widgets/streak_summary_row.dart—StreakSummaryRow: logging streak, adherence streak, YTD tally. Also text-only.app/lib/screens/log_meal_progress.dart—buildTodayProgress(DayLog)(~:18),adherenceStreak(...)(~:31).
Theme (local hand-copy, no design_system dependency):
app/lib/ui/theme.dart—buildAppTheme()~:12,AppStatusColorsThemeExtension ~:89, redeclaredAppSpacing~:124,AppRadius~:146,AppTextSize, plus anAppWidthclass the shared package does not have.
Settings (for the sound opt-out in step 2):
app/lib/screens/settings_screen.dart— body is aListViewinsideCenter > ConstrainedBox(maxWidth: AppWidth.prose)at ~:161-165. NoSwitchListTileexists anywhere in this app — you are adding the first.app/lib/services/app_settings_service.dart— the store. Not SharedPreferences: a singleton over aDocumentStorewriting JSON toapp_settings.json(~:26).init()~:49,_load()~:95-112, setters funnel through_persist/_writeToDisk~:147-165 (read the comment at ~:153-155 —_writeToDiskis the single write path every setter must use).resetForTesting/initForTestingat ~:66/:74.
must
Step 1 — motion + haptics (no new dependency; land this first).
- On successful meal log, fire
HapticFeedback.mediumImpact()(or the token vocabulary's "complete" cue if prompt 01 defined one).HapticFeedbackcomes frompackage:flutter/services.dart— no pubspec change needed. Add<uses-permission android:name="android.permission.VIBRATE"/>toapp/android/app/src/main/AndroidManifest.xml; it is currently absent. - Fire it immediately on the tap, before the write/sync completes. The article's point about micro-animations is that they replace latency with feedback; a haptic that waits for a network round-trip defeats it.
- Animate the progress card's arrival and the numbers' change rather than snapping: the kcal figure and the streak count should visibly move to their new value. Use the motion tokens from prompt 01.
- Show an explicit confirmation of what was recorded — the user should not have to infer success from a card appearing.
- Honour
MediaQuery.of(context).disableAnimations: collapse durations to zero. Haptics still fire (they are not motion).
Step 2 — sound (separate, independently revertable).
- Add an audio dependency and an
assets:block toapp/pubspec.yaml(neither exists today), plus a short confirmation cue asset. - Sound ships on, with an opt-out
SwitchListTileinsettings_screen.dart, backed by a new bool inapp_settings_service.dartfollowing the existing field pattern: private field + static getter, parsed in_load(), written via_writeToDisk. - The toggle controls UI sound only. Never route notification-channel behaviour through it.
Both steps:
- must not: reward the calorie number. The satisfying moment fires on logging, not on being under budget. Rewarding a low number teaches the user to eat less rather than record honestly, and corrupts the app's own data. A meal that blows the budget gets the same confirmation as one that doesn't.
- must not: fabricate or round up any displayed value. The streak counts real consecutive days; the progress bar reflects the true ratio. Framing, timing and salience are fair game — the numbers are not.
- must not: add a celebratory cue to deleting or editing a meal. Only the record-creating act gets it.
- must not: touch
~/src/diet-guard/diet_guard/(the Python desktop side) — it has its own streak logic and is out of scope here. - optional: a progress ring instead of the plain kcal text. The article's near-miss point applies honestly here — a ring at 80% of budget is truthful and more motivating than a number. Only if it stays accurate.
done
- Logging a meal on the phone produces a haptic within ~50ms of the tap, an explicit confirmation, and a visible (not instant) update of the streak/progress.
cd ~/src/diet-guard/app && flutter analyzeis clean.cd ~/src/diet-guard/app && flutter testpasses.- With OS "remove animations" enabled, the screen still works and durations are zero — verified by toggling it, not by reading the code.
- Step 2 only: the settings toggle flips, persists across an app restart, and silences the cue.
verify
On the phone — this app is mobile-primary; desktop is not sufficient.
adb devices # confirm 23181JEGR08034 is attached
cd ~/src/diet-guard/app
flutter build apk --release
adb install -r build/app/outputs/flutter-apk/app-release.apk
Never flutter install, never adb uninstall, never pm clear — those wipe
real logged food data. adb install -r preserves it.
Then log a real meal on the device and report what you felt and saw. A haptic is
a physical outcome: state what the phone did, do not claim it worked from code.
If com.kuhy.* focus-mode kills the app on launch, the app needs a focus
whitelist entry before it can be tested.
read first
app/lib/screens/log_meal_screen.dart—_onLogMeal(). Read the whole method before editing; the sync call and the field-clearing both matter for where the haptic goes.app/lib/services/app_settings_service.dart— thedailyKcalGoalfield (~:34/:40) is the pattern to copy for a new bool. Note the_writeToDiskcomment.~/src/diet-guard/diet_guard/_calendar_view.py—streaks_text()~:136 andytd_text()~:154. The Dartstreak_summary_row.dartdocstring says it mirrors this formatting; keep them consistent if you change wording.~/src/utils/unified-design-system/motion.md— the motion/haptic vocabulary from prompt 01. Prompt 01 must have run first.
context you would otherwise rediscover
- This app has no
design_systemdependency —app/lib/ui/theme.dartis a hand-transcription of the shared tokens (values are byte-identical, names in comments). Motion tokens from prompt 01 must be transcribed into that local theme file, matching the existing convention. Do not add the dependency here; that is a separate, larger refactor. - Capability baseline, verified 2026-08-16: no audio package, no
assets:block, novibrationpackage, no VIBRATE permission. The only existing audio-adjacent thing is thediet_guard_due_slotnotification channel (app/lib/services/notification_backend_io.dart~:17-18, :52-55) atImportance.highwith no explicit sound/vibration override — leave it alone. - The app already computes both a logging streak and an adherence streak
(
_daystatus.py:144/:153on the Python side, mirrored in Dart). You are making existing true data feel like something, not inventing a new metric. - diet-guard state lives under XDG (
~/.local/share/diet_guard), unlike screen-locker's in-repo JSON. Tests isolate it via~/src/diet-guard/diet_guard/tests/conftest.py:97_isolate_state.
REMOVE ME AFTER FINISH