Markdown across every repo had accumulated a dozen ad-hoc names (refactor_claude_todo.md x23, DESIGN_AUDIT_TODO.md, NEXT_SESSION, HANDOFF, PLAN, SPEC) with nothing distinguishing a live task from a finished record. An audit found docs asserting work was pending that shipped months earlier, and one asserting completion while the work was open. Four namespaces now: README.md, CLAUDE*.md, DOCS*.md, TODO*.md. Task files carry the literal "REMOVE ME AFTER FINISH" and are deleted when the work lands. Enforced bidirectionally -- a TODO without the marker fails, AND any file carrying the marker that is not named TODO*.md fails. Without the reverse rule a task can be renamed into the permanent namespace and quietly live forever, which is the failure being fixed. md_naming/ mirrors the file_length topology and imports its tables (EXCLUDED_DIRS, THIRD_PARTY_REPOS, VENDORED_ANYWHERE, GENERATED_PATTERN) rather than restating them, so the two gates cannot disagree about the same file. 71 tests, 100% branch coverage. stale.py warns on TODOs past 90 days but never fails: no machine can prove a task is finished, so the honest mechanism is a nudge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A47aX5DJwmA3QFn3pkEWi7
8.3 KiB
Add motion and haptics to the wake-alarm phone app
findings (2, ranked by value over effort)
- Setting an alarm gives no confirmation or haptic — and since the real alarm lives in another app entirely, confirming the handoff is the only reassurance this app can offer.
- Zero motion, and the thinnest local theme in the fleet — it lacks
AppSpacing/AppRadius/AppTextSize, so widgets copied from sibling apps will not compile here.
what
~/wake-alarm/phone_app is the smallest surface in this project: 12 Dart files,
a sync companion that sets a time, syncs it, and hands off to the system clock
app. It has zero motion, zero haptics, zero audio, zero progress feedback —
verified 2026-08-16. Its settings screen is a 56-line StatelessWidget with two
ListTiles and no state at all.
Bring it up to the fleet's baseline: a confirmation the alarm was actually set, motion tokens on its transitions, and a haptic on the one action that matters.
This app is deliberately last and deliberately small. It also carries the
project's sharpest hazard — see must not about alarm audio.
where
Repo: ~/wake-alarm. Flutter app: ~/wake-alarm/phone_app (underscore, not
phone-app). The repo root is a Python package; the Flutter app is only the
phone_app/ subdirectory.
Primary:
phone_app/lib/screens/home_screen.dart—_setPhoneAlarm()~:86-96 builds anAndroidIntent(action: 'android.intent.action.SET_ALARM', arguments: {HOUR, MINUTES, SKIP_UI: false, MESSAGE: 'Wake Alarm'})viaandroid_intent_plus. TheCircularProgressIndicatoris at ~:189. The onlyDurationin the whole app is.timeout(const Duration(seconds: 3))~:95 — an intent timeout, not animation. Do not tokenise it.phone_app/lib/screens/settings_screen.dart— 56 lines,StatelessWidget,ListView~:21-53,MaterialPageRoute~:30, :49.phone_app/lib/services/sync_settings.dart— holds only the GitHub token in the keystore (FlutterSecureStorage,_secureTokenKey = 'github_token'~:31,load()~:36,save()~:61). There is no general app-preferences class.phone_app/lib/ui/theme.dart— local hand-copy, nodesign_systemdependency.buildAppTheme()~:12,AppStatusColors~:54. ⚠️ Unlike the other apps it does not declareAppSpacing/AppRadius/AppTextSize, so a widget copied from diet-guard or workout_app will not compile here. Wired atphone_app/lib/main.dart~:18.
must
Step 1 — motion + haptics (no new dependency).
- Add
HapticFeedback.mediumImpact()when the alarm is successfully set — the single most important action in the app. Fire it on the tap, not after the intent round-trips.HapticFeedbackis inpackage:flutter/services.dart; add<uses-permission android:name="android.permission.VIBRATE"/>tophone_app/android/app/src/main/AndroidManifest.xmlif absent. - Give setting the alarm a clear confirmation: which time was set, and that
the handoff to the system clock succeeded. Today the user gets a
_statusstring and a spinner. Since the actual alarm lives in another app, confirming the handoff is the only reassurance this app can offer — make it unambiguous. - Transcribe the motion tokens from prompt 01 into
phone_app/lib/ui/theme.dart, matching the existing hand-copy convention. AddAppSpacing/AppRadius/AppTextSizeonly if you need them; otherwise leave the file's shape alone. - Apply motion tokens to the two
MaterialPageRoutetransitions. - Honour
MediaQuery.of(context).disableAnimations.
Step 2 — sound (optional here; consider skipping).
- A UI confirmation cue would need an audio dependency, an
assets:block, a settings toggle, and a preferences class that does not exist yet (see below). For an app whose entire job is to hand off to another app, that is a poor value ratio. - Recommendation: skip sound in this app and say so explicitly in the session summary. If you do add it, it needs the full opt-out toggle like everywhere else — and the toggle work below.
- To add any toggle here you must do three things the other apps do not require:
(a) convert
SettingsScreenfromStatelessWidgettoStatefulWidget, (b) create a preferences class — there is noAppSettingsequivalent; model it on~/todo/lib/data/app_settings.dart, and noteshared_preferences: ^2.3.0is already a dependency, (c) add theSwitchListTileto theListView.
Both steps:
- must not: touch anything under
~/wake-alarm/wake_alarm/. That is the desktop Python daemon that produces the real alarm noise — sine-tone WAVs viapaplay→aplay→speaker-test(_audio.py,_play_on_all_sinks~:218, pcspkr evdev fallback_beep_pcspkr~:81), escalating 440Hz → 1000Hz → loud in the loop at_alarm.py~:393-407, with sink volume save/restore in_sinks.py. Never route a UI-sound opt-out through any of it. A toggle that silences a wake-up alarm is a bug that makes someone late for work. - The two systems never share a process, package, or file —
phone_apphas no audio engine at all — so a UI sound layer here structurally cannot touch alarm playback. Keep it that way; do not build a bridge between them. - must not: change the alarm-setting intent, its arguments, or the 3s timeout. Feedback wraps that call; it does not modify it.
- must not: add a streak, a counter, or any reward for setting an alarm. Waking up is not a metric this app owns, and rewarding "alarms set" rewards the wrong act entirely.
- must not: copy a widget from another app without checking it only uses symbols this app's thinner theme actually declares.
done
- Setting an alarm produces a haptic and an unambiguous confirmation naming the time that was set.
- Page transitions use the shared motion tokens; no inline animation
Durationremains (the 3s intent timeout stays). cd ~/wake-alarm/phone_app && flutter analyzeis clean.cd ~/wake-alarm/phone_app && flutter testpasses.- With OS "remove animations" enabled, the app works and durations are zero.
git diffshows no changes under~/wake-alarm/wake_alarm/.- The summary states whether sound was added or deliberately skipped.
verify
On the phone.
adb devices # confirm 23181JEGR08034
cd ~/wake-alarm/phone_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 — the app stores
a GitHub token in the keystore and wiping it means re-authenticating.
Then set a real alarm on the device: confirm the haptic fired (state it as a physical observation), the confirmation named the right time, and the system Clock app actually received the alarm. The last one is the real test — this app's job is the handoff, so verify the alarm exists in the Clock app, not just that this screen said so.
Do not test by letting an alarm actually fire unless kuhy asks — that involves the desktop daemon and real noise.
read first
phone_app/lib/screens/home_screen.dart—_setPhoneAlarm()in full, including the timeout and the_statusstring handling.phone_app/lib/ui/theme.dart— note what it does not declare before writing any widget.~/todo/lib/data/app_settings.dart— the preferences-class pattern, only if you decide to do step 2.~/utils/unified-design-system/motion.md— vocabulary from prompt 01. Prompt 01 must have run first.
context you would otherwise rediscover
- The Flutter app produces no audio whatsoever. No audio package, no
assets:block;assets/contains only launcher icons. It fires an AndroidSET_ALARMintent and the system Clock app rings. The desktop Python daemon is an entirely separate program on a different machine. - The repo root (
~/wake-alarm/wake_alarm/,wake_alarm.egg-info) is the Python package. Onlyphone_app/is Flutter. - This app has no
design_systemdependency and the thinnest local theme in the fleet. Adding the dependency is out of scope here (see prompt 07 for what that migration looks like when done properly). - No streaks, no charts, no progress, no completion counts exist — and none should be added. This is a two-button utility.
REMOVE ME AFTER FINISH