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 |
||
|---|---|---|
| .. | ||
| tests | ||
| __init__.py | ||
| __main__.py | ||
| _glyph_art_more.py | ||
| _glyph_art.py | ||
| apps.py | ||
| cli.py | ||
| generate.py | ||
| glyphs.py | ||
| preview.py | ||
| README.md | ||
| render.py | ||
| style.py | ||
app_icons — shared launcher icons for the Flutter apps
One icon family across every Flutter/Dart app: a charcoal #211D1B field with
a single glyph in the shared unified-design-system accent (#B8862E),
drawn at one stroke weight and optically centred so a row of them lines up.
Commands
# what is registered
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons list
# contact sheet for visual review: full size / circle-masked / 48dp
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons preview -o /tmp/sheet.png
# write assets into every app repo and rebuild the Android mipmaps
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons generate --android
# one app, plus the hicolor PNGs for a Linux desktop target
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons \
generate --app todo --android --linux-out ~/todo/linux/icons
What gets written
Per app, into <repo>/assets/icon/:
| Layer | Purpose |
|---|---|
icon |
Charcoal field + glyph. Legacy square launcher icon and Linux icon. |
icon_foreground |
Glyph only, transparent. Adaptive-icon foreground layer. |
icon_monochrome |
Glyph only, white. Android 13+ themed icons. |
Both an .svg source and a 1024px .png are written for each, so a build
never needs rsvg-convert. flutter_launcher_icons then turns the PNGs into
the density buckets and mipmap-anydpi-v26/ic_launcher.xml.
Design constraints that bite
- Negative space ≥ half the stroke weight. Strokes closer than that merge into a solid blob at launcher size. A fork whose tines sat exactly one stroke width apart rendered as a tulip.
- Ink must stay inside the 560px safe box. Android masks adaptive icons to
roughly the inner 66% of the canvas.
generatewarns when a glyph overflows. - Stroked outlines can read wrong. A stroked three-tine fork reads as a trident; a filled fork-and-knife pair reads as cutlery. Prefer a filled silhouette when the outline is ambiguous.
- Glyph positioning inside the fragment is irrelevant —
render.centre_offsetmeasures the rendered ink and recentres it. Only the extents matter.
Adding an app
See the app-icon skill (~/.claude/skills/app-icon/SKILL.md) for the full
procedure. In short: add a glyph to glyphs.py, register the app in
apps.py, wire flutter_launcher_icons into its pubspec.yaml, generate,
review the contact sheet, then verify on the phone.
Tests
cd ~/testsAndMisc
python3 -m pytest python_pkg/app_icons --cov=python_pkg.app_icons \
--cov-report=term-missing
External tools (rsvg-convert, magick, dart) are mocked; the suite needs
none of them installed.