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
tokens.md 260 -> 219 + operability.md 69
nielsen-audit.md 258 -> 154 + shipped-record.md 122
README.md 257 -> 172 + operability-patterns.md 95
Worked the requested order rather than cutting straight to a split.
(a) Automate: the categorical ramp's four constraints were restated in prose
with the measured margins hand-copied (ΔE 34.6/26.0/25.6/27.4, etc).
ramp_check.py prints every one of those numbers and fails if any
regresses, so the prose now points at the command instead of duplicating
its output. The two consequences the numbers do not state — why the ramp
is mid-toned, and why lightness falls monotonically — stay in prose.
(b) Dedup: measured, not assumed. Zero paragraphs are shared between the
three files; README already delegates the rules to tokens.md and shows
only per-stack code. Nothing to remove.
(c/d) Split on meaning: tokens.md is "frozen values", so the behavioural
requirements that *use* those tokens moved to operability.md, with the
per-stack code for them in operability-patterns.md. nielsen-audit.md is a
live grid, so completed phases moved to shipped-record.md — following the
existing phase3-record.md convention, not a new one.
The focus-ring token row had to stay in tokens.md: palette_check.py parses
that file, and moving the row made the check fail with "MISSING md:focus-ring"
— a token it cannot see is a token nothing compares across the four stacks.
The rationale lives in operability.md and the row links to it.
palette_check and ramp_check both at 0 failing checks; every relative link
between the docs resolves.
`bash scripts/check_file_length.sh --all` now exits 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
gatelock v0.3.0. Every change here works around a Tk *default*, not an
application mistake -- which is why they belong in the shared backend rather
than being fixed four times.
On a lock surface these are not accessibility niceties. The lockers hold a
global input grab with VT switching disabled, so a control the keyboard cannot
reach, or a button clipped off the bottom of the screen, is a bricked machine.
LockConfig gains a type scale, a spacing scale and a focus-ring token:
- font() applies Tk's sign convention. Tk encodes the unit in the SIGN of the
font size -- positive is points, negative is pixels -- so passing the
design system's px values straight through rendered every string ~30%
oversized (measured: body at 16pt has a 26px linespace where 16px gives 20).
That alone pushed diet_guard's meal gate to 866px of content inside a 739px
pane on a 1366x768 screen.
- focus_kwargs() sets highlightcolor, the *focused* ring. Tk's default is a 1px
BLACK ring, invisible against the palette's #211D1B background. Note
highlightbackground is the UNfocused ring: setting only that one inverts the
affordance, which is what diet_guard was doing.
_keyboard.py collects three Tk defaults that each make a form unusable:
- tk.Text traps Tab (inserts a literal tab and refocuses itself) and no-ops
Shift-Tab; the only exits are the undocumented Ctrl+Tab / Ctrl+Shift+Tab.
- tk.Button binds <space> on X11 but never <Return>, so the key a user reaches
for to submit does nothing.
- Escape needs binding at the toplevel to work wherever focus happens to sit.
_scrollable.py adds ScrollableSurface, a viewport that content can actually be
reached in:
- A place()-centred frame takes its *requested* size and is clipped
SYMMETRICALLY, so overflow removes the heading and the submit button at the
same time, with no scrollbar and no hint anything is missing.
- A bare tk.Canvas has no class key bindings, so ::tk::FocusOK rejects it as a
focus stop and the only way to scroll is dragging the thumb with a pointer.
- Canvas clipping does not unmap children, so scrolled-out fields stay in the
tab ring while Tk never scrolls to follow focus -- Tab lands on fields the
user can neither see nor reveal.
- A Canvas holds content in a canvas *item*, so it never propagates the
content's requested size; a toplevel told to size itself has nothing to grow
to. _request_content_size() fixes that, clamped to the display.
center_when_fits keeps short screens looking exactly as they did, so the
viewport can replace a centred frame without a visual change.
A scrollable dialog Toplevel is deliberately NOT the mechanism: on an
override-redirect lock surface a second toplevel steals the Tk grab, which the
recovery tick then kills -- the 2026-07-26 failure where a frozen sport
selector logged a walk as table tennis.
unified-design-system gains the two rule families this implements (pointer-free
operability, 1366x768 usable / 1024x600 graceful), a focus-ring token, and the
px/pt sign convention written down so it cannot recur.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Moves the design-system rules, frozen token table, and annotated
component references out of ~/.claude/skills/ and the two Claude
Artifacts into a proper subdir here, matching the existing
gatelock/crdt-sync/guard-lib pattern of one-subdir-per-shared-library.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>