Distribute dopamine-ux prompts into their target repos
Some checks failed
palette drift / palette (push) Has been cancelled

Each prompt is now also copied to <repo>/prompts/dopamine-ux-*.md so a repo can
be opened and the prompt run directly. ~/utils/dopamine-ux/ remains the source
of truth; corrections go here and are re-distributed.

Adds the completion protocol every copy now carries: on finishing, the session
deletes its repo copy in the implementation commit and appends a DONE row to the
completion log in 00-INDEX.md. Partial completion is never recorded as done --
the file is edited to state what remains instead, since a stale prompt is
indistinguishable from a pending one.

Also fixes a codespell violation (re-declared -> redeclared) caught by
diet-guard's pre-commit hooks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdApz7EZ1zDfvtvvi1idDv
This commit is contained in:
Krzysztof Rudnicki 2026-08-16 21:13:19 +02:00
parent 35415de225
commit 1533eec081
4 changed files with 391 additions and 1 deletions

View File

@ -69,6 +69,45 @@ Ranked by value-over-effort from the survey data:
the `assets:` block and the VIBRATE permission are **already present**. Only
the call is missing.
## Distributed copies, and the completion protocol
Each prompt is **also copied into the repo it acts on**, at
`<repo>/prompts/dopamine-ux-*.md`, so you can open a repo and say
"do dopamine-ux-<name>" without going hunting. **This directory stays the source
of truth** — if a prompt needs correcting, fix it here and re-distribute.
| Prompt | Distributed to |
|---|---|
| 01 | `~/utils/prompts/dopamine-ux-01-motion-tokens.md` |
| 02 | `~/utils/prompts/dopamine-ux-02-structural-check.md` |
| 03 | `~/diet-guard/prompts/dopamine-ux-diet-guard.md` |
| 04 | `~/screen-locker/prompts/dopamine-ux-04-screen-locker.md` |
| 05 | `~/screen-locker/prompts/dopamine-ux-05-workout-app.md` |
| 06 | `~/todo/prompts/dopamine-ux-todo.md` |
| 07 | `~/dufs-cloud/prompts/dopamine-ux-07-theme.md` |
| 08 | `~/dufs-cloud/prompts/dopamine-ux-08-motion.md` |
| 09 | `~/wake-alarm/prompts/dopamine-ux-wake-alarm.md` |
When a session finishes a prompt it **deletes its repo copy** (in the same
commit as the implementation) and **appends a row below**. A finished prompt left
in a repo is indistinguishable from a pending one, and the next session re-runs
it. Partial completion is never recorded as DONE — the file is edited to state
what remains instead.
### Completion log
| Prompt | Status | Impl commit | Note |
|---|---|---|---|
| 01-motion-tokens.md | not started | — | — |
| 02-structural-check.md | not started | — | — |
| 03-diet-guard.md | not started | — | — |
| 04-screen-locker.md | not started | — | — |
| 05-workout-app.md | not started | — | — |
| 06-todo.md | not started | — | — |
| 07-dufs-theme.md | not started | — | — |
| 08-dufs-motion.md | not started | — | — |
| 09-wake-alarm.md | not started | — | — |
## Keeping these prompts honest
Every path and symbol cited across these files is machine-checked:

View File

@ -42,7 +42,7 @@ Primary:
Theme (local hand-copy, **no `design_system` dependency**):
- `app/lib/ui/theme.dart` — `buildAppTheme()` ~:12, `AppStatusColors`
ThemeExtension ~:89, re-declared `AppSpacing` ~:124, `AppRadius` ~:146,
ThemeExtension ~:89, redeclared `AppSpacing` ~:124, `AppRadius` ~:146,
`AppTextSize`, plus an `AppWidth` class the shared package does not have.
Settings (for the sound opt-out in step 2):

View File

@ -0,0 +1,176 @@
> **This file is a ready-to-use prompt.** Open this repo and say
> "do dopamine-ux-01-motion-tokens". It is self-contained -- it needs no context from
> any other session.
>
> Canonical copy: `~/utils/dopamine-ux/01-motion-tokens.md` (source of truth; this is a
> distributed copy). Running order and cross-prompt rules: `~/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:
1. **Delete this file** and commit that deletion together with the
implementation:
```bash
git rm prompts/dopamine-ux-01-motion-tokens.md
```
Do 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.
2. **Log completion in the canonical index**, in `~/utils` (a *different* repo,
so it needs its own commit):
```bash
cd ~/utils
# append to the "Completion log" table in dopamine-ux/00-INDEX.md:
# | 01-motion-tokens.md | DONE <YYYY-MM-DD> | <impl commit sha> | <one-line note> |
git add dopamine-ux/00-INDEX.md
git commit -m "dopamine-ux: mark 01-motion-tokens.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.
---
Add motion, haptic and sound tokens to the unified design system
## findings (3, ranked by value over effort)
1. **No motion token exists in any stack** — greenfield. Every app that wants an
animation currently invents its own number, and one already has.
2. **No haptic vocabulary exists** — the highest-value gap, because
`HapticFeedback` needs no dependency, so three of four apps can adopt it
the day this lands.
3. **`tokens.md` is 260 lines against a 250 cap** — it cannot absorb a new
section, so motion needs a sibling file.
## what
The shared design system has colour, spacing, radius, type and shadow tokens —
and **no motion tokens at all**. Verified 2026-08-16: no `Duration`, `Curve`,
`--duration-*`, `--ease-*`, or haptic constant exists in any stack copy. There is
not even a prose position that motion is out of scope.
Consequence across kuhy's Flutter fleet: **exactly one animation exists in four
apps** — a lone `AnimatedContainer(duration: Duration(milliseconds: 200))` with an
inline magic number. Every other transition is a Flutter/browser default.
Create the motion token layer so the per-app prompts (03–09) have something
canonical to consume instead of each inventing durations.
## where
Repo: `~/utils` (git repo, has CI + the file-length gate wired).
**Create** `~/utils/unified-design-system/motion.md` — the prose freeze.
**Do NOT append to `~/utils/unified-design-system/tokens.md`.** It is 260 lines
and the cap is 250, so it already fails the gate; adding to it makes that worse.
`screen-size.md` was split out of it for exactly this reason — follow that
precedent and add a pointer line in `tokens.md` only if you can do so without
growing it (i.e. by shortening another line).
Then mirror the tokens into the three code stacks:
| Stack | File | Form |
|---|---|---|
| Flutter/Dart | `~/utils/design_system/lib/src/tokens.dart` | `class AppDuration` / `class AppCurve` |
| web/React | `~/utils/web_ui/src/tokens.css` | `--duration-*`, `--ease-*` custom props |
| Python/Tkinter | `~/utils/gatelock/gatelock/_window.py` (`class LockConfig`) | int ms fields — **only if meaningful**; see `must not` |
There are **three** code stacks plus the prose freeze. Do not go hunting for a
fourth — it does not exist.
## must
- Define a **small** scale. Recommended, but use judgement:
`instant 0ms · fast 120ms · base 200ms · slow 320ms`, and easing
`standard (0.2, 0, 0, 1)` · `decelerate (0, 0, 0, 1)` · `accelerate (0.3, 0, 1, 1)`.
Justify each value in `motion.md` in one line — the file is a freeze, so the
reasoning has to survive without you.
- Include a **haptic** vocabulary mapped to Flutter's `HapticFeedback` API:
which semantic event gets `selectionClick` vs `lightImpact` vs `mediumImpact`.
This is the highest-value part — `HapticFeedback` needs no dependency, so
three of four apps can adopt it immediately.
- Include a **sound** vocabulary: named semantic cues (`confirm`, `complete`,
`error`) with the rule that sound ships **on** (opt-out) per app, and that
each app owns its own asset files.
- State the reduced-motion contract in `motion.md`: web uses
`@media (prefers-reduced-motion: reduce)`, Flutter uses
`MediaQuery.of(context).disableAnimations`. Motion tokens collapse to `0ms`
under it; **haptics and sound are unaffected** (they are not motion).
- Keep every file at or under **250 lines** (`~/utils/file_length/config.py`,
`MAX_LINES = 250`). `.md` and `.py` are capped; `.css` is not.
- **Cut tags at the end** — this is the step that makes the work reach consumers:
- `design_system-v0.2.0` (consumed by `~/todo`, currently on `v0.1.0`)
- `web_ui-v0.3.2` (consumed by `~/dufs-cloud/web`, currently on `v0.3.1`)
Print both tag names at the end of the session; prompts 06, 07 and 08 need them.
- must not: add motion values to the Tkinter `LockConfig` just for symmetry.
Tkinter has no animation framework. If a duration has no meaning there, mark
the row `n/a (tk)` in `motion.md` and leave the Python alone. An honest `n/a`
beats a dead constant.
- must not: express any motion token as a hex-looking value. The existing
`palette_check.py` parses `#RRGGBB` and would either ignore it or trip on it.
- must not: touch any colour token, or add a colour alongside the motion work.
That *would* break `palette_check.py`'s completeness half.
- must not: add `# noqa` or `type: ignore` anywhere.
- optional: a `prefers-reduced-motion` helper in
`~/utils/design_system/lib/src/` and/or `web_ui/src/` so consumers get the
collapse behaviour without reimplementing it. Nice, not required.
## done
All four hold:
1. `~/utils/unified-design-system/motion.md` exists, is ≤250 lines, and defines
duration, easing, haptic and sound vocabularies with a one-line rationale each.
2. `python3 ~/utils/file_length/check.py` (or
`~/utils/scripts/check_file_length.sh --all`) reports **no new** violations.
`tokens.md` (260), `nielsen-audit.md` (258) and `README.md` (257) are
pre-existing failures — do not fix them here, but do not add a fourth.
3. `python3 ~/utils/unified-design-system/scripts/palette_check.py` still exits 0.
4. Both tags exist: `git -C ~/utils tag | grep -E 'design_system-v0.2.0|web_ui-v0.3.2'`
prints two lines.
## verify
Desktop, any way you like — this prompt ships no UI. Run the two gate commands in
`done` and paste their output. Then confirm the token names you chose are what
prompts 03–09 will reference by printing the Dart class and the CSS custom-prop
block.
## read first
- `~/utils/unified-design-system/tokens.md` — the existing freeze; match its
table format and tone exactly. Note its length problem before editing.
- `~/utils/unified-design-system/screen-size.md` — the precedent for a sibling
file split out of `tokens.md` (32 lines; short and self-contained).
- `~/utils/design_system/lib/src/tokens.dart` — `AppPalette`, `AppSpacing`,
`AppRadius`, `AppTextSize`. Your `AppDuration`/`AppCurve` sit alongside these.
- `~/utils/web_ui/src/tokens.css` — the full custom-prop list.
- `~/utils/design_system/lib/src/feedback.dart` — `showToast`/`showError` and
`_show`. `confirm.dart` next to it **already imports
`package:flutter/services.dart`**, so `HapticFeedback` is one call away with no
new import. This is the natural home for a shared haptic helper.
- `~/utils/unified-design-system/scripts/palette_check.py` — read the four
`parse_*` regexes to see why motion tokens are invisible to it. Prompt 02
builds the checker that covers them; **do not extend this script here.**
## context you would otherwise rediscover
- **Green CI does not mean no drift for these tokens.** `palette_check.py` only
matches `#RRGGBB`, so a `--duration-fast: 120ms` is invisible to every one of
its parsers. `palette_map.py:159` claims structural values are "checked by the
scale check" — **no such script exists**. Prompt 02 writes it.
- `~/utils/gatelock/` is a package inside the `~/utils` monorepo, not a
standalone repo. It is consumed as a pinned git dependency.
- Only `~/todo` depends on `design_system`; diet-guard, workout_app and
wake_alarm hand-transcribe the token values into local `lib/ui/theme.dart`
copies. So tokens added here reach exactly one app automatically — the others
are handled per-prompt.

View File

@ -0,0 +1,175 @@
> **This file is a ready-to-use prompt.** Open this repo and say
> "do dopamine-ux-02-structural-check". It is self-contained -- it needs no context from
> any other session.
>
> Canonical copy: `~/utils/dopamine-ux/02-structural-check.md` (source of truth; this is a
> distributed copy). Running order and cross-prompt rules: `~/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:
1. **Delete this file** and commit that deletion together with the
implementation:
```bash
git rm prompts/dopamine-ux-02-structural-check.md
```
Do 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.
2. **Log completion in the canonical index**, in `~/utils` (a *different* repo,
so it needs its own commit):
```bash
cd ~/utils
# append to the "Completion log" table in dopamine-ux/00-INDEX.md:
# | 02-structural-check.md | DONE <YYYY-MM-DD> | <impl commit sha> | <one-line note> |
git add dopamine-ux/00-INDEX.md
git commit -m "dopamine-ux: mark 02-structural-check.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.
---
Add a structural-token drift checker to CI
## findings (2, ranked by value over effort)
1. **Spacing, radius and type have never been drift-checked** — `palette_check.py`
parses hex only, so three whole token categories can diverge across stacks
while CI stays green. Motion inherits the same hole.
2. **`palette_map.py` points at a "scale check" that does not exist** — a
comment describing a guarantee nobody wrote.
## what
`palette_check.py` guarantees the colour palette agrees across all stack copies.
It does **not** check anything else — verified 2026-08-16 by reading its four
parsers, every one of which requires a `#RRGGBB` value:
```python
parse_css : r"(--[a-z0-9-]+)\s*:\s*(#[0-9A-Fa-f]{6})\s*;"
parse_dart: r"static const Color (\w+) = Color\(0x[fF]{2}([0-9A-Fa-f]{6})\)"
parse_tk : r'^\s{4}(\w+):\s*str\s*=\s*"(#[0-9A-Fa-f]{6})"'
parse_md : r"^\|\s*`([a-z0-9-]+)`\s*\|\s*`(#[0-9A-Fa-f]{6})`"
```
So **spacing, radius and type have been silently unprotected all along**, and the
motion tokens added by prompt 01 inherit the same hole. A `--duration-fast: 120ms`
is invisible to every parser: CI passes while the stacks drift apart.
Worse, `scripts/palette_map.py:159` claims structural values are "checked by the
scale check" — **no such script exists anywhere in `~/utils`**. That comment
describes a check that was never written. This prompt writes it.
## where
Repo: `~/utils`.
**Create** `~/utils/unified-design-system/scripts/structural_check.py`.
**Edit** `~/utils/.github/workflows/palette-drift.yml` — add a step invoking it.
The workflow deliberately has no `paths:` filter; keep it that way.
**Fix** the stale comment at `~/utils/unified-design-system/scripts/palette_map.py`
(~:159) to name the real script.
Sources to compare (three code stacks + the prose freeze):
| Role | Path |
|---|---|
| prose freeze | `~/utils/unified-design-system/tokens.md` (spacing/radius/type) |
| motion freeze | `~/utils/unified-design-system/motion.md` (**created by prompt 01** — run it first) |
| web/React | `~/utils/web_ui/src/tokens.css` |
| Flutter/Dart | `~/utils/design_system/lib/src/tokens.dart` |
| Python/Tkinter | `~/utils/gatelock/gatelock/_window.py` (`class LockConfig`) |
## must
- Cover **motion** (durations, easing) **and** the already-unprotected
**spacing, radius and type** scales, in one checker.
- Mirror `palette_check.py`'s two-halves design, because it is the part that
actually works:
1. **Agreement** — for each mapped token, resolve it in every non-`n/a` stack
and fail if values disagree.
2. **Completeness, bidirectional** — every structural value parsed out of any
source must be mapped, or explicitly exempted. This is what catches
"added it to one stack, forgot the others".
- Support an explicit **`n/a` per stack**, and require a reason string. Tkinter
has no animation framework; a duration legitimately does not exist there. An
honest `n/a` with a reason is correct — a fabricated constant is not.
- **Adjudicate with exit codes.** Exit non-zero on drift with a message naming
the token and the disagreeing files. No warnings-only mode.
- Keep every file ≤ **250 lines** (`~/utils/file_length/config.py`). Note
`palette_check.py` is 231 and `palette_map.py` is 162 — that pair was split
precisely to stay under the cap. **Split yours the same way from the start**
(`structural_check.py` + `structural_map.py`) rather than discovering the
problem at 251 lines.
- Ruff-clean under the repo's config. No `# noqa`, no `type: ignore`.
- **Expect it to fail on first run.** Spacing/radius/type have never been
compared; there may be real pre-existing drift. That is the point of writing
it. Fix whatever it surfaces, in the stack copy that is wrong, and say in the
session summary what drifted.
- must not: weaken the checker, add an exemption, or narrow its scope to make it
green. If a token genuinely differs per stack for a good reason, encode it as
an `n/a`/variant **with the reason recorded**, not as a silent skip.
- must not: modify `palette_check.py`. It works; it is simply colour-only. The
two scripts run side by side.
- must not: change any token *value* to resolve a disagreement without saying so
explicitly — that is a design change, and `tokens.md` is a freeze. Surface it
and let kuhy decide which value is canonical.
- optional: teach it the shadow tier too, if that falls out cheaply.
## done
1. `python3 ~/utils/unified-design-system/scripts/structural_check.py` exits 0,
and exits non-zero when you deliberately break one stack copy (**test both
directions** — a checker that never fails is not a checker).
2. It is wired into `~/utils/.github/workflows/palette-drift.yml` and the
workflow passes.
3. `palette_map.py`'s "scale check" comment names the real script.
4. `~/utils/scripts/check_file_length.sh --all` reports no new violations.
5. The session summary states what drift, if any, the first run found.
## verify
Desktop. Run the script, paste the output. Then prove it fails: change one
spacing or duration value in **one** stack copy, re-run, paste the non-zero exit
and the message, and revert. Both halves of that demonstration are required —
the passing run alone proves nothing.
## read first
- `~/utils/unified-design-system/scripts/palette_check.py` — the design to
mirror. Read `NON_COLOUR_CSS`: it is the regex that currently *excludes*
spacing/radius/type/shadow from the colour check, and therefore the precise
list of what you are now covering.
- `~/utils/unified-design-system/scripts/palette_map.py` — the `PALETTE` tuple of
`Token(canonical, md, css, dart, tk, why)` rows. Your map wants the same shape,
including the `why` field.
- `~/utils/.github/workflows/palette-drift.yml` — how `palette_check.py` and
`ramp_check.py` are invoked; add your step alongside.
- `~/utils/unified-design-system/motion.md` — what prompt 01 created.
- `~/utils/unified-design-system/tokens.md` — the spacing/radius/type tables.
**Do not edit it**; it is 260 lines and already over the cap.
## context you would otherwise rediscover
- The four "stack copies" are really **three code stacks plus a prose freeze**.
There is no fourth code stack; do not hunt for one.
- `~/dufs-cloud/app/lib/ui/theme.dart` is a **fifth, uncompared copy** of the
palette (the Flutter app has no `design_system` dependency and hand-restates
every hex). It is out of scope here — prompt 07 wires it up properly. Do not
add it as a source to this checker; wait until 07 has removed it.
- Three Flutter apps (diet-guard, workout_app, wake_alarm) similarly
hand-transcribe tokens into local `lib/ui/theme.dart` files. Those are
deliberately outside the checker's scope for now — bringing them in is a much
larger job than this prompt.
- `~/utils/gatelock/` is a package inside this monorepo, not a separate repo.