Also bumps Flutter 3.47.3, flutter_secure_storage 11.1.0, idb_shim 2.9.9, sqflite_common_ffi 2.4.3, very_good_analysis 11 and mcp 2.2.0 so the freshness gate lets this through; v11 adds async_return_with_no_await, so the 18 flagged return sites now await explicitly. 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-todo". It is self-contained -- it needs no context from any other session.
Canonical copy:
~/src/utils/dopamine-ux/06-todo.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-todo.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: # | 06-todo.md | DONE <YYYY-MM-DD> | <impl commit sha> | <one-line note> | git add dopamine-ux/00-INDEX.md git commit -m "dopamine-ux: mark 06-todo.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.
Adopt the motion tokens in todo, and make capture feel immediate
findings (3, ranked by value over effort)
- Capture gives no confirmation — a successful capture is indistinguishable from a no-op unless you go looking for the note.
- Zero motion, zero haptics — despite being the only app already wired to the shared package, so adoption here is cheapest.
- The pinned
design_systemtag predates the motion tokens — nothing compiles until the ref is bumped. Easy to forget, fatal if skipped.
what
todo is the only app in kuhy's fleet already wired to the shared
design_system package — it has deliberately no local theme.dart (the
pubspec comment says so: nothing to drift from the frozen table). That makes it
the cheapest place to adopt the motion tokens added by prompt 01, and the
reference implementation the other apps get compared against.
Today it has zero motion and zero haptics. Verified 2026-08-16: no
Animated*, no AnimationController, no HapticFeedback, no audio. Its only
progress UI is the note-editor wizard bar. Capturing a note — the app's whole
purpose, and the thing that has to feel frictionless enough to actually do —
gives nothing back beyond a toast on error.
Per kuhy's rule for this project: reward the capture, never the completion count. A todo app that celebrates finishing things teaches you to log easy things; one that celebrates capturing keeps the backlog honest.
where
Repo: ~/src/todo.
First step — bump the dependency. pubspec.yaml pins design_system at git
ref design_system-v0.1.0, which predates the motion tokens. Bump it to
design_system-v0.2.0 (cut by prompt 01) or the tag that prompt actually
printed. Without this, AppDuration does not exist and nothing else here compiles.
Primary:
lib/ui/capture_screen.dart,lib/ui/capture_screen_widget.dart,lib/ui/capture_screen_sync.dart— the capture path.showError~:76 andshowToast~:78 incapture_screen_sync.dartare the fleet's only uses of the shared feedback helpers.lib/ui/capture_app_bar.dart— progress indicators ~:66, :84.lib/ui/note_editor.dart—showError~:115.lib/ui/note_editor_step_page.dart~:80 —LinearProgressIndicator(value: (index+1)/total), the wizard step bar. The one honest progress surface.lib/ui/notes_list_screen.dart—_searchDebounce = 250ms~:45 (not animation; do not tokenise it).lib/ui/notes_list_navigation.dart,lib/ui/settings_screen.dart(2×),lib/ui/capture_screen.dart— theMaterialPageRoutesites, all using the default platform transition.
Settings — this app has the only working toggle in the fleet; it is the pattern the other prompts copy:
lib/ui/settings_screen.dart~:213-225 —ValueListenableBuilder<AppSettings>wrapping aSwitchListTile; setter ~:111-140 (logs anAnalyticsEvent, opens a Firebase client, callswithAdvancedMode).lib/data/app_settings.dart—AppSettingsimmutable class,SharedPreferenceskeys ~:38-39,load()~:42,withAdvancedMode~:60,reconcileWithRemote~:98,adopt~:132 (last-writer-wins cross-device sync viasettings/advancedMode~:20). Held app-wide as aValueNotifier.
Theme: no local file. lib/main.dart ~:4 imports design_system; ~:60-61 wires
theme: buildLightTheme(), darkTheme: buildDarkTheme(). Source of truth is
~/src/utils/design_system/lib/src/theme.dart.
must
Step 1 — motion + haptics (no new dependency).
- Bump
design_systemto the new tag first, and confirmAppDuration/AppCurveresolve before writing any other code. - Add
HapticFeedback.selectionClick()(or the token vocabulary's "confirm" cue) on successful note capture.HapticFeedbackcomes frompackage:flutter/services.dart— no pubspec change. Add<uses-permission android:name="android.permission.VIBRATE"/>toandroid/app/src/main/AndroidManifest.xml; it is absent today. - Fire the haptic on the tap, not after the Firebase sync returns. The whole point of the micro-animation idea is that immediate feedback removes the perceived latency; a cue gated on a network round-trip reintroduces it.
- Give the capture a visible confirmation — currently a successful capture is indistinguishable from a no-op unless you go looking.
- Animate the wizard
LinearProgressIndicatorbetween steps rather than jumping. - Apply the shared page-transition/motion tokens to the
MaterialPageRoutesites so navigation matches the rest of the system. - Honour
MediaQuery.of(context).disableAnimations: durations collapse to zero, haptics still fire.
Step 2 — sound (separate, independently revertable).
- Add an audio dependency and an
assets:block topubspec.yaml(neither exists) plus a short capture-confirm cue. - Sound ships on, with an opt-out
SwitchListTile— copy theadvancedModepattern verbatim (widget ~:213-225, setter ~:111-140, field inapp_settings.dart). It is the fleet's proven toggle; do not invent a new one. - Consider whether the sound preference should sync across devices like
advancedModedoes, or stay local. Local is the safer default — a per-device audio choice is usually what people mean.
Both steps:
-
must not: reward completing a todo more than capturing one. No streak on completions, no counter that makes finishing feel better than recording. The backlog is only useful if capture stays frictionless and honest.
-
must not: add a variable/random reward to capture. There is no real pool to draw from here, so any variance would be manufactured — which fails the project's second test.
-
must not: fabricate progress. The wizard bar's
(index+1)/totalis true; keep it that way. -
must not: hand-copy token values into a local
theme.dart. This app deliberately has none — that is a feature, and the pubspec comment says so. -
must not: add
# noqa-equivalents or suppress analyzer warnings. -
optional: animate the notes-list reordering/filtering, if it falls out cheaply from the shared tokens.
done
pubspec.yamlreferences the newdesign_systemtag andflutter pub getresolves it.- Capturing a note produces a haptic within ~50ms of the tap and a visible confirmation.
- Wizard step transitions and page navigation use shared motion tokens; no
inline
Durationremains for animation purposes (the 250ms search debounce and 5s auto-sync debounce stay as they are — they are not motion). cd ~/src/todo && flutter analyzeis clean.cd ~/src/todo && flutter testpasses.- With OS "remove animations" enabled, the app works and durations are zero.
- Step 2: the sound toggle flips, persists across restart, and silences the cue.
verify
On the phone — todo is mobile-primary.
adb devices # confirm 23181JEGR08034
cd ~/src/todo
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 notes. adb install -r preserves them.
Then capture a real note on the device and report what the phone did. A haptic is a physical outcome: say what you felt, do not infer it from code. Also step through the note-editor wizard to confirm the progress bar animates.
If the app also has a desktop/web surface deployed under /opt/, note that it
can go stale independently — but this prompt's done-condition is the phone.
read first
lib/ui/settings_screen.dart~:213-225 andlib/data/app_settings.dart— the toggle pattern, in full, before writing the sound switch.lib/ui/capture_screen_sync.dart~:76-78 — howshowToast/showErrorare used today; the shared helpers live in~/src/utils/design_system/lib/src/feedback.dart.~/src/utils/design_system/lib/src/feedback.dart—showToast/showError,_show~:50.confirm.dartbeside it already importsflutter/services.dart, so if prompt 01 added a shared haptic helper it lives near here.~/src/utils/unified-design-system/motion.md— the vocabulary from prompt 01. Prompt 01 must have run first, and its tag must be cut.pubspec.yaml— read the comment explaining why there is no local theme.
context you would otherwise rediscover
showToastandshowErroralready carry fixed dwell times in the shared package (_toastDuration = 3s,_errorDuration = 5s,feedback.dart~:14, :18). Those are not motion tokens; leave them.- This is the only app on
design_system. diet-guard, workout_app and wake_alarm hand-transcribe tokens into local theme files, so a widget copied from this app into one of those will not compile unmodified. - The app has no streaks, no rings, no history charts, and no completion counts today. That is a deliberate starting point for this prompt, not an oversight to fix wholesale.
- Settings sync cross-device last-writer-wins through Firebase
(
settings/advancedMode). If you add a synced preference, followreconcileWithRemote/adopt; if local-only, skip that path entirely.
REMOVE ME AFTER FINISH