utils/unified-design-system/DOCS-phase2-gatelock-widgets.md
Krzysztof kuhy Rudnicki a10d190b65
Some checks failed
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
design_system tests / test (push) Has been cancelled
gatelock pre-commit / pre-commit (push) Has been cancelled
gatelock tests / test (3.10) (push) Has been cancelled
gatelock tests / test (3.11) (push) Has been cancelled
gatelock tests / test (3.12) (push) Has been cancelled
web_ui tests / test (push) Has been cancelled
Add the shared markdown-naming gate and migrate utils' own docs
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
2026-08-24 15:53:12 +02:00

4.1 KiB
Raw Blame History

Implement Phase 2 of the shared component work: extend ~/utils/gatelock (Python/Tk) with the composite widgets that four guard apps each reimplemented. Treat this as override on the spec gate — the spec is below, it is complete, and I do not want a round of questions before you start.

Goal. gatelock already owns the design tokens (LockConfig) and the Tk plumbing, and has four consumers. Its scope stops short of composite widgets, so each consumer grew its own. Move those into gatelock and retire the copies in two proof consumers.

This is NOT a new package. Add to ~/utils/gatelock, which already exists and is consumed as gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.4.1#subdirectory=gatelock.

Build these four, in order:

  1. gatelock/widgets.py — make_button(parent, *, text, variant, command). Canonical implementation is diet-guard's _gatelock_buttons.py::make_button: its variant API picks the text colour from the fill, which structurally prevents the fg-vs-on_fill contrast bug the README documents. leetcode-guard's _button (no <Return> binding) and screen-locker's inline tk.Button are the regressions being retired. Two non-obvious traps both copies had to solve independently and which the shared version must keep: <Return> is unbound on tk.Button under X11, and the default focus ring is 1px black, invisible on #211D1B.
  2. gatelock/widgets.py — heading() and row(), from leetcode_guard/_status_sections.py.
  3. Export the existing ScrollableSurface and delete leetcode_guard/status_view.py::_scrollable, a hand-rolled reimplementation that lost takefocus and the focus ring the shared version already fixes. This one is a straight accessibility regression repair, not a refactor.
  4. gatelock/widget_group.py — WidgetGroup, the per-output fan-out reimplemented four times (~916 lines total: screen-locker 173, leetcode-guard 105, diet-guard 309, wake-alarm 329). Highest line-count win on this stack.

Proof consumers: diet-guard and leetcode-guard — the donor and the worst offender, so the API is validated against both ends. Bump only these two. Leave screen-locker and wake-alarm on their current pin: Phase 2 is additive, untouched consumers keep working, and restarting more live services than necessary is blast radius for no gain. Ask me before restarting anything live.

Extraction criterion. Build a component only where two or more repos already contain a structurally similar implementation. Shipping a widget nobody imports is the failure mode to avoid. Corollary I learned the hard way in Phase 1: when you extract something, retire the donor copies in the same pass, or you have shipped a duplicate rather than removed one.

Verification gate — non-negotiable. gatelock backs live systemd services; a bad import takes 3–4 of them down at once.

  • /usr/bin/python3 -c "import gatelock" against the real interpreter, not a dev venv. The library is tag-pinned in site-packages while the apps are editable, so a cwd-relative check will lie to you.
  • Then diet-guard's and leetcode-guard's test suites.
  • Then launch each gate under xvfb-run -s "-screen 0 1366x768x24" and confirm buttons render and <Return> activates them — that is the specific regression this phase repairs, so it is the specific thing to observe.
  • Do not touch the gate window's X-grab path. Those fullscreen surfaces carry input-hijack invariants; a deadlock there has frozen the PC before.
  • pre-commit run --files <changed> in every touched repo, respect the 250-line file cap, and keep each repo's coverage bar.

Done means: the four items exist in gatelock with tests, the two proof consumers import them with their local copies deleted, <Return> verifiably activates a button in each gate under xvfb, and everything is committed and pushed.

Context, if you want it: ~/utils/unified-design-system/nielsen-audit.md has the full grid and the Phase 1 record. Phase 1 (~/utils/design_system, Dart) is done — 7 exports, 100% coverage, six consumers — and is the pattern to imitate: publish_to: none, own analysis config, own tests, own coverage gate, tag-pinned git dep.