`tokens.md` freezes the palette as prose and three packages re-express it
as code -- `web_ui/src/tokens.css`, `design_system`'s `tokens.dart`, and
gatelock's `LockConfig`. Nothing compared them, so "one palette
everywhere" held only for as long as four hand-maintained copies happened
to agree. A hex edited in one stack drifted silently in the other three.
`palette_check.py` parses all four and runs two halves:
* agreement -- every mapped token carries the same hex in every
non-exempt stack
* completeness -- every colour parsed from any source must be mapped or
exempted with a stated reason
The second half is the load-bearing one. A pure agreement check passes
happily when someone adds a token to one stack and never touches the map,
which is the drift most likely to actually happen; an unmapped token is
therefore a failure, not a skip. Absences are declared per token per stack
with a reason (Tk has no light theme and no borders; the categorical ramp
is web-only) so "this stack doesn't have it" is on the record rather than
a silent pass.
It found one real gap on its first run: `tokens.css` sets `--surface-2` to
`paper-raised` in the light block -- the light theme collapses both
elevation steps onto one surface -- which `tokens.md` documented only in
prose and no map covered. Now an explicitly compared row, so if either
half of that pair moves, it fails.
Proven to fail, not just to pass: drift injected into each of the four
sources in turn, plus an unmapped token, was caught every time with the
offending stack named; re-proven after the 250-line-cap split into
`palette_map.py`. Runs from its own workflow with deliberately no `paths:`
filter -- drift is a disagreement *between* packages, so a path-scoped
trigger would skip exactly the edit that causes it. That was not
hypothetical: `ramp_check.py` was reachable only from the
`web_ui/**`-filtered workflow, so a ramp edit in `tokens.md` or
`design_system` never ran it. It moves here too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U4Rj3s7K2qzrLNgJqqPhno
Matches the Phase 1 design_system pattern the brief points at: own lint config,
own tests, own coverage gate enforced in CI.
The lint script was declared but unrunnable -- there was no eslint flat config,
and the build tsconfig excludes tests (correctly; they must not ship in dist/),
so type-aware linting had no project for them. Adds eslint.config.js and a
lint-only tsconfig that widens the include without emitting. Running it surfaced
one real defect: nth() interpolated a number into a template literal without
String(), which the consumers' configs would have flagged.
The gate also checks that the committed dist/ matches a fresh build. This is not
theoretical -- the check caught the stale dist/ produced by that very String()
fix on its first run. A stale dist/ in a tag ships old code to every consumer
with no error anywhere, which is the one failure mode this packaging choice
introduces. dist/ is excluded from the whitespace hooks: they would "fix"
generated output that the next build immediately reverts.
CI additionally re-runs the categorical ramp's constraint proof, so the palette
cannot regress silently either.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U4Rj3s7K2qzrLNgJqqPhno
The pre-push coverage gate only ran for whoever had the local hook
installed, which is deployment hygiene rather than a gate. Adds the
mirrored workflow sync_settings_ui already has, so the 100% bar is
enforced on push and PR.
EmptyState.action was invented surface: no donor had it. home_inventory's
three call sites are pure placeholders, and kuhylog's action-bearing
_empty was explicitly excluded from the survey as a different element.
Shipping a parameter nobody asked for on a widget that has no consumer
yet is the failure mode this package is supposed to avoid, so it goes,
with a comment recording why rather than leaving the next reader to
rediscover it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XS1m7b1piBtAL22USLW6jL
Extracts the near-identical "Sync settings" UI duplicated across 5 apps
(todo, diet_guard_app, workout_app, wake_alarm/phone_app, home_inventory)
into a single tested package. Firebase sync (primary) + optional local
backup slot; GitHub mirror UI was dropped per kuhy's explicit decision
mid-build in favor of Firebase-only, so existing apps keep their local
GitHub code untouched, just disconnected from this shared screen.
100% line coverage, flutter analyze clean under very_good_analysis.
Generalized check_no_crdt_sync_override.sh to guard any utils git
dependency (crdt_sync, sync_settings_ui), not just crdt_sync by name.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdSp6Nbrs9xvsWkCMDN9a1
The display fix in 161eccd could never prove itself: both workflows trigger
on `paths: ['gatelock/**']`, and the workflow files live under .github/, so
editing one changes nothing GitHub will act on. The tests job stayed red at
the last gatelock commit with the fix already pushed -- which is the worst
kind of green-looking red, because nothing was running to say otherwise.
A workflow is part of what it tests, so each now lists its own path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsXnTHyuuCUZsqetTaajcF
Headless, the viewport, fit-check and keyboard tests skip -- 25 of them --
and take their coverage with them, which is why this job went red the moment
they landed: 100% is the bar and _fitcheck drops to 47%, _keyboard to 18%.
Skipping is the worse half. These tests exist because Tk's own focus
delivery and canvas scrolling cannot be mocked -- mocking them is exactly how
a viewport that scrolled itself passed every check for a week -- so a green
run that skipped them would be reporting nothing about the thing they guard.
xvfb-run gives the runner a display, which is what every local run already
used.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsXnTHyuuCUZsqetTaajcF