utils/dopamine-ux/TODO-01-motion-tokens.md
Krzysztof kuhy Rudnicki 7096c102cd
Some checks are pending
crdt_sync_dart tests / test (push) Waiting to run
crdt_sync_flutter tests / test (push) Waiting to run
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (push) Waiting to run
dependency freshness / dependencies on newest stable (push) Waiting to run
design_system tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
freedays pre-commit / pre-commit (push) Waiting to run
freedays tests / test (push) Waiting to run
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (push) Waiting to run
github_device_auth tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
music_theory tests / test (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
sync_settings_ui tests / test (push) Waiting to run
web_ui tests / test (push) Waiting to run
Re-point paths for the ~ reorganisation (home-tidy)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
2026-09-11 18:45:55 +02:00

7.3 KiB
Raw Blame History

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: ~/src/utils (git repo, has CI + the file-length gate wired).

Create ~/src/utils/unified-design-system/motion.md — the prose freeze.

Do NOT append to ~/src/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 ~/src/utils/design_system/lib/src/tokens.dart class AppDuration / class AppCurve
web/React ~/src/utils/web_ui/src/tokens.css --duration-*, --ease-* custom props
Python/Tkinter ~/src/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 (~/src/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 ~/src/todo, currently on v0.1.0)
    • web_ui-v0.3.2 (consumed by ~/src/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 ~/src/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. ~/src/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 ~/src/utils/file_length/check.py (or ~/src/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 ~/src/utils/unified-design-system/scripts/palette_check.py still exits 0.
  4. Both tags exist: git -C ~/src/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

  • ~/src/utils/unified-design-system/tokens.md — the existing freeze; match its table format and tone exactly. Note its length problem before editing.
  • ~/src/utils/unified-design-system/screen-size.md — the precedent for a sibling file split out of tokens.md (32 lines; short and self-contained).
  • ~/src/utils/design_system/lib/src/tokens.dart — AppPalette, AppSpacing, AppRadius, AppTextSize. Your AppDuration/AppCurve sit alongside these.
  • ~/src/utils/web_ui/src/tokens.css — the full custom-prop list.
  • ~/src/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.
  • ~/src/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.
  • ~/src/utils/gatelock/ is a package inside the ~/src/utils monorepo, not a standalone repo. It is consumed as a pinned git dependency.
  • Only ~/src/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.

REMOVE ME AFTER FINISH