`Pre-commit checks` has been red on main since 2026-08-24: the md-naming
and file-length hooks are thin shims that exec the real gate out of the
utils repo, and a runner has no ~/utils, so both died with "shared gate
not found at /home/runner/utils/...". The dedicated md-naming.yml
already checks utils out into .utils/; this does the same and points
UTILS_ROOT at it, which is the override the shims already support.
CI_GREEN_SKIP is set because this commit IS that fix.
Also registers punchme (github.com/kuhyx/punchme) in the shared icon
family on the family accent, Android-only so linux=False. Its glyph is a
time card with a clipped corner, a column of four punched holes and
three ruled lines; the first draft put two holes above two wide slots
and read unmistakably as a face at every size.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
focus_owner (com.kuhy.focus_owner, the Device Owner enforcer) was the one
Flutter app never registered with the shared generator. It shipped a
hand-written vector adaptive icon added purely to unblock its build — the app
had no icon resource at all and every release AND debug build failed on AAPT
"resource mipmap/ic_launcher not found". That placeholder used #E8EAED, so it
was off-family as well as unregistered.
None of the existing 13 glyphs fits a device-policy enforcer, so this adds
padlock-closed: shackle stroked (inheriting the group's round caps), body
filled in the accent, keyhole punched back out in the field colour. A fully
stroked padlock loses its keyhole at 48dp and a fully filled one loses the
shackle's opening; splitting them keeps both.
Geometry was computed against style.py's constraints rather than eyeballed,
per the lesson recorded on _NOTE_ASCENDER: body 292..732 x 520..792, shackle
centreline r=132 about (512,520) so its outer top is 352 (inside the 232 safe
edge) and its legs land at 380/644, well within the body. The keyhole leaves
60px of body above it and 76px below the stem, both clear of
MIN_NEGATIVE_SPACE=36 — checked by rendering at 48px, where the hole survives.
The repo path points into ~/phone-focus-mode/focus-owner: focus-owner was
absorbed as a subtree on 2026-08-24 and both the standalone clone and its
GitHub repo are gone.
app_icons stays at 100% branch coverage; 44 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KU9czAaz7wAkjyJ5uEB3t
Two pre-commit hooks and one pre-push extension, plus the _glyph_art split
that had to land first — the cap gate cannot be wired while any file is over
it, and the lyricanki note-ascender glyph had taken _glyph_art.py to 253.
file-length-cap runs check_file_length.sh --all repo-wide rather than on
staged files, so a file that grows past the cap in some other commit is still
caught. Proven to block, not assumed: with a deliberate 261-line file staged
the hook exits 1 and names it; with the file removed it passes.
ci-baseline-green is the user's option (a): refuse to build on a main that is
already red. It fails closed on a definitively failing baseline and warns-and-
passes on anything it cannot determine — offline, no gh, unauthenticated, no
settled runs — each call capped at 5s. Blocking every commit on network
reachability buys nothing when (b) is the real per-change guarantee.
CI_GREEN_SKIP=1 is the escape for a fix-forward commit. python-tests.yml is
excluded on purpose: it is path-filtered, so a shell-only commit never
triggers it and its "latest run" would produce false reds.
This hook already earned its place. Run against a red main earlier in this
session it refused with "Shell tests (failure)", which is how the broken
SETUP_SOURCES was found instead of being pushed over.
ci_mirror gains the shell-tests workflow's side-effect-free half — every
lib/tests/run_all.sh plus the workflow's own test list, the user's option (b).
That list is parsed out of shell-tests.yml rather than copied: a hardcoded
duplicate drifts the moment someone adds a test, and a mirror that quietly
checks less than CI is worse than no mirror because it reports safe. The
Arch-container half stays unmirrored — it wants root and an Arch userland.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
android_ui, app_icons, token_audit, focus_policy and random_jpg are now
entirely under 250 lines. Only wsg_grabber's six test modules remain in
python_pkg.
android_ui/driver.py 411 -> 205 took two cuts and a class split: the
element and error types plus the XML parser to _elements.py, and the
text-entry methods to a TextEntryMixin, using the ABC-with-abstract-
methods shape because the tests call them as bound attributes.
app_icons/glyphs.py 291 -> 93 by moving the twelve path fragments out.
Three intermediate failures, all module-level globals that travelled
with their code (_logger, re, _SAME_FIELD_X_TOLERANCE_PX) - the same
class of miss as _make_verifier, which ruff cannot flag.
Counts unchanged: 81, 137, 14, 44, 57.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
The new ~/untools app (guided thinking tools) needs a family launcher icon
rather than the stock Flutter beach one.
The glyph is one node branching into two -- "decompose a problem into parts",
which is the move every one of the app's 25 frameworks makes in some form.
Nodes are hollow rings rather than filled dots so the 72px stroke leaves
visible interior at icon size.
A two-level, seven-node tree was drawn first and rejected on the generator's
own evidence: it reported the glyph overflowing the 560px safe box by
~170x162px, and rendering confirmed the doubled connectors closed up into a
solid mass at launcher size. One clean split survives the mask and still reads
as a hierarchy. Branch spread was then narrowed to clear a residual 44px.
linux=False for this app: its desktop build is the web build in a Chrome
--app window, so there is no GTK app to hand a hicolor PNG to.
Also whitelists com.kuhy.untools in phone_focus_mode/config.sh. That entry is
source-of-truth only -- the current Pixel 6a is not rooted, has no
/data/local/tmp/focus_mode and no running daemon, so nothing was pushed to the
device and no app is being killed there. Kept for the rooted BL9000 phone.
Verified: 44 app_icons tests pass with the package at 100% coverage; the
generator emits 6 assets with no safe-box warning; the rendered icon was
inspected and matches the family; and the resulting APK installed and stayed
alive on 23181JEGR08034.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hVZ5HfcrcrVzinmjQuQup
Three rising bars with a plotted point above the tallest: a day-by-day log
that adds up to a trend, which is what the app tracks. Bars are strokes
rather than filled rects so the glyph keeps one stroke weight, and the
168px pitch leaves 96px gaps -- comfortably above MIN_NEGATIVE_SPACE, which
is where these glyphs usually fail.
kuhylog has a Linux desktop target as well as Android, so it is registered
with linux=True and gets the hicolor set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQtqicQHSrvBa4XaNNh9Wy
OctoForge (~/octoforge) is app #10 in the shared launcher-icon family. It was
still shipping the stock Flutter icon, which is not a finished state.
The glyph is a filled silhouette rather than a stroked outline: a stroked
anvil is a long thin horn plus a narrow waist, and at the family's 72px stroke
weight the waist closes up solid at 48dp. An octopus (GitHub trade dress, and
a tangle of thin curves that blurs at launcher size) and a bare hammer (reads
as a generic tool, not a forge) were both rejected.
The horn is a separate wedge projecting off the face, not a rounded corner on
it. Two earlier drafts folded the taper into the slab's left end and both
rendered as a bump, leaving the shape reading as an I-beam. It now tapers to a
40px blunt tip, above MIN_NEGATIVE_SPACE so it survives at 48dp.
Registered with linux=True: unlike the other phone-first apps this one has a
real desktop target with a .desktop entry, so it needs the hicolor set.
Verified: generated for Android and Linux, checked against the family preview
sheet at all three sizes, and confirmed in the device app drawer next to todo.
Committed from a temporary worktree: the working tree held another session's
in-flight kuhylog entry in the same hunk, and git-rules.md forbids stashing
around other agents' uncommitted work. That work is untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzEA5srk8VPn6mKKjCaGpb
billsplit was a standalone Flutter app in ~/Downloads with one commit and no
git remote — that directory was the only copy of its history.
Three things the move needed beyond copying the tree:
- The root .gitignore silently swallowed 32 of its files (*.png, *.ico,
*.manifest). Ignored files do not error on `git add`, they just vanish, so
this would have committed an app that cannot build. Added a trailing block
of ! overrides plus .binary-allowlist (which did not exist yet) — the
documented in-repo exception for binaries a build genuinely needs. The
../testsAndMisc_binaries/ symlink scheme does not apply: those directories
are gone, and a symlinked PNG breaks a fresh clone.
- billsplit/.github/workflows/ci.yaml was inert once nested; GitHub only reads
workflows at the repository root. Relocated with a working-directory and a
billsplit/** path filter.
- The app's coverage gate moved to python_pkg/billsplit_coverage/ so every
Python file stays under python_pkg/, and gained a test suite at 100% branch
coverage as that location requires. billsplit/ now holds only Dart.
Also replaces the stock Flutter icon with a family icon from the shared
generator. The glyph is a filled silhouette because the family's 72px stroke
cannot carry a fine torn edge, and the split is an evenodd-cut perforation so
the holes stay transparent on the foreground and monochrome layers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new ~/home_inventory Flutter app needs two things from this repo: a
family launcher icon, and a focus-mode whitelist entry so the daemon does
not pm disable-user it ~1s after launch.
The glyph is an isometric carton — a hexagon plus the three edges meeting
at the near-top corner. A front-on rectangle with a lid seam was tried
first and rejected on the contact sheet: a seam plus a divider reads as a
browser window, not a box.
Body is 488x440, not a rounder 500x440, because STROKE_WIDTH straddles the
path and adds 72px to each rendered dimension; 488 is the widest body whose
ink still fits SAFE_BOX. The generator warned about exactly this at 500.
Accent stays the one shared family accent — apps differ by glyph, not
colour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New Flutter app (~/habit_stack) gets an on-style launcher icon from the
shared family: two interlocking stadium-ring outlines, sized so the
72px stroke leaves a genuinely hollow interior at launcher size. Accent
matches the app's own ThemeData seedColor (#66BB6A), not already
claimed by a sibling app.
Also whitelists com.kuhy.habit_stack in phone_focus_mode/config.sh (both
the always-on productive-apps list and the night/curfew health-tool
list) and redeploys, since an unwhitelisted com.kuhy.* app is killed
within ~1s and would otherwise look like an install failure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every Flutter app still shipped the stock Flutter "beach" launcher icon, and
none had an adaptive-icon descriptor at all. Rather than hand-drawing five
one-off icons, define the family once and let each app contribute only an
accent colour and a glyph.
The family: a charcoal #1B1D21 field carrying one glyph in that app's own
theme seed colour, at a single stroke weight. Glyphs are optically centred
automatically - render.centre_offset rasterises a glyph alone, measures its
ink box, and translates (never scales) the fragment, so a row of icons lines
up while every icon keeps the exact family stroke weight.
safe_box_overflow warns when a glyph exceeds the 560px adaptive-icon safe
box; it caught three real overflows that a launcher mask would have clipped.
The registry points dufs_client at ~/dufs-cloud/app: ~/dufs_client was a
stale, remote-less checkout with an identical package name, since deleted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>