utils/prompts/TODO-dopamine-ux-02-structural-check.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

8.4 KiB

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: ~/src/utils/dopamine-ux/02-structural-check.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:

  1. Delete this file and commit that deletion together with the implementation:

    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 ~/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:
    #   | 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:

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 ~/src/utils. That comment describes a check that was never written. This prompt writes it.

where

Repo: ~/src/utils.

Create ~/src/utils/unified-design-system/scripts/structural_check.py.

Edit ~/src/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 ~/src/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 ~/src/utils/unified-design-system/tokens.md (spacing/radius/type)
motion freeze ~/src/utils/unified-design-system/motion.md (created by prompt 01 — run it first)
web/React ~/src/utils/web_ui/src/tokens.css
Flutter/Dart ~/src/utils/design_system/lib/src/tokens.dart
Python/Tkinter ~/src/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 (~/src/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 ~/src/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 ~/src/utils/.github/workflows/palette-drift.yml and the workflow passes.
  3. palette_map.py's "scale check" comment names the real script.
  4. ~/src/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

  • ~/src/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.
  • ~/src/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.
  • ~/src/utils/.github/workflows/palette-drift.yml — how palette_check.py and ramp_check.py are invoked; add your step alongside.
  • ~/src/utils/unified-design-system/motion.md — what prompt 01 created.
  • ~/src/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.
  • ~/src/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.
  • ~/src/utils/gatelock/ is a package inside this monorepo, not a separate repo.

REMOVE ME AFTER FINISH