From 6383bd74529d2e679b6a6496516b96434eb7cf0d Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 16 Oct 2026 16:16:09 +0200 Subject: [PATCH] app_icons: tunedeck gets headphones; later entries move to _apps_more apps.py was one entry short of the 250-line cap, so tutor and the new tunedeck entry live in _apps_more.MORE_APPS, merged into APPS unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CyiH5e86uaQzhXzVXTQiVc --- ...-icons-tunedeck-headphones-2026-10-16.json | 14 ++++++++ ...-icons-tunedeck-headphones-2026-10-16.json | 36 +++++++++++++++++++ python_pkg/app_icons/_apps_more.py | 36 +++++++++++++++++++ python_pkg/app_icons/_glyph_art_extra.py | 17 +++++++++ python_pkg/app_icons/apps.py | 12 ++----- python_pkg/app_icons/glyphs.py | 3 +- python_pkg/app_icons/tests/test_registry.py | 13 +++++++ 7 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 docs/superpowers/contracts/app-icons-tunedeck-headphones-2026-10-16.json create mode 100644 docs/superpowers/evidence/app-icons-tunedeck-headphones-2026-10-16.json create mode 100644 python_pkg/app_icons/_apps_more.py diff --git a/docs/superpowers/contracts/app-icons-tunedeck-headphones-2026-10-16.json b/docs/superpowers/contracts/app-icons-tunedeck-headphones-2026-10-16.json new file mode 100644 index 00000000..d31fa261 --- /dev/null +++ b/docs/superpowers/contracts/app-icons-tunedeck-headphones-2026-10-16.json @@ -0,0 +1,14 @@ +{ + "title": "tunedeck launcher icon (headphones)", + "objective": "The shared app_icons generator registers `tunedeck` with a new `headphones` glyph that stays inside SAFE_BOX and reads at 48dp, without a per-repo copy of the generator, while apps.py stays under the 250-line cap.", + "acceptance_criteria": [ + "generate --app tunedeck --android --web writes assets/icon/*, the Android mipmaps and the web icons", + "python_pkg/app_icons stays at 100% line and branch coverage; every file <= 250 lines", + "every other app's registry entry is unchanged (tutor only moves module)" + ], + "out_of_scope": [ + "Changing style.py or any other app's glyph", + "Committing the generated files in ~/src/tunedeck from this repo" + ], + "verifier": "pytest --cov=python_pkg.app_icons --cov-branch; the generate and preview commands; pre-commit hooks on the staged files" +} diff --git a/docs/superpowers/evidence/app-icons-tunedeck-headphones-2026-10-16.json b/docs/superpowers/evidence/app-icons-tunedeck-headphones-2026-10-16.json new file mode 100644 index 00000000..b5b06399 --- /dev/null +++ b/docs/superpowers/evidence/app-icons-tunedeck-headphones-2026-10-16.json @@ -0,0 +1,36 @@ +{ + "intent": "Give the new tunedeck Flutter app (music / podcast player) an on-style launcher icon: headphones in the shared accent on the family charcoal.", + "scope": [ + "python_pkg/app_icons/_glyph_art_extra.py, glyphs.py: the `headphones` glyph", + "python_pkg/app_icons/_apps_more.py, apps.py: tutor moves to MORE_APPS (apps.py was at 249 lines) and tunedeck joins it", + "python_pkg/app_icons/tests/test_registry.py: headphones registry tests" + ], + "changes": [ + "New `headphones` glyph: stroked r=216 headband over two filled 112x216 ear cups, 288px gap between cups, ink 512x428 inside SAFE_BOX", + "tunedeck registered with accent #B8862E, linux=True (desktop is a web build in a Chrome --app window)", + "APPS merges MORE_APPS unchanged, so lookups and ordering of existing apps are the same" + ], + "verification": [ + { + "command": "PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons generate --app tunedeck --android --web", + "result": "pass", + "evidence": "tunedeck: wrote 6 asset files; wrote 5 web icons; regenerated Android mipmaps; icon.png viewed: headphones on #211D1B" + }, + { + "command": "PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons preview -o ~/data/claude-scratch/tunedeck-icon-sheet.png", + "result": "pass", + "evidence": "Sheet viewed: headphones read at full size, circle-masked and 48dp next to the other glyphs; no safe-box warning" + }, + { + "command": "python3 -m pytest -q python_pkg/app_icons/tests --cov=python_pkg.app_icons --cov-branch", + "result": "pass", + "evidence": "55 passed; every python_pkg/app_icons module 100% line and branch" + } + ], + "risks": [ + "The band's round caps show as small shoulders above the cups; intended as the headphone yoke" + ], + "rollback": [ + "git revert this commit; tunedeck and the headphones glyph leave the registry; tutor's entry is byte-identical either way" + ] +} diff --git a/python_pkg/app_icons/_apps_more.py b/python_pkg/app_icons/_apps_more.py new file mode 100644 index 00000000..ebc5746f --- /dev/null +++ b/python_pkg/app_icons/_apps_more.py @@ -0,0 +1,36 @@ +"""Later registry entries, split out of :mod:`apps` for the 250-line cap. + +Which file an entry lives in carries no meaning; :data:`apps.APPS` merges both. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Final + +from python_pkg.app_icons._app_icon import AppIcon + +_HOME = Path.home() + +MORE_APPS: Final[tuple[AppIcon, ...]] = ( + AppIcon( + key="tutor", + repo=_HOME / "src/tutor/app", + accent="#B8862E", + glyph="lightbulb", + icon_name="tutor", + # Desktop is the web build in a Chrome --app window, as for todo + # and home_inventory, whose desktop entries use the hicolor set. + linux=True, + ), + AppIcon( + key="tunedeck", + repo=_HOME / "src/tunedeck", + accent="#B8862E", + glyph="headphones", + icon_name="tunedeck", + # Desktop is a web build behind a local wrapper in a Chrome --app + # window (see the repo's SPEC.md), so it gets the hicolor set too. + linux=True, + ), +) diff --git a/python_pkg/app_icons/_glyph_art_extra.py b/python_pkg/app_icons/_glyph_art_extra.py index f3401888..bf2d51d5 100644 --- a/python_pkg/app_icons/_glyph_art_extra.py +++ b/python_pkg/app_icons/_glyph_art_extra.py @@ -24,3 +24,20 @@ _LIGHTBULB = """\ A 150 150 0 0 1 662 442 C 662 500 576 510 576 556 Z"/> """ + +# Headphones -- tunedeck, a music player: the listening itself. A stroked +# headband over two filled ear cups. The music note (``note-ascender``) is +# lyricanki's, and a cassette's reels need more negative space than a 560px +# box leaves at the family stroke weight, so the ear cups carry the meaning. +# +# Geometry, ink inside SAFE_BOX (x=256..768 = 512 wide, y=308..736 = 428 tall): +# band semicircle r=216 about (512,560), so its ink tops out at y=308 and +# its ends land at x=296 / x=728, buried inside the cups. +# cups filled rounded rects x=256..368 and x=656..768, y=520..736; the +# 288px gap between them is far clear of MIN_NEGATIVE_SPACE=36. +_HEADPHONES = """\ + + + """ diff --git a/python_pkg/app_icons/apps.py b/python_pkg/app_icons/apps.py index 13f1913a..e308e0d2 100644 --- a/python_pkg/app_icons/apps.py +++ b/python_pkg/app_icons/apps.py @@ -13,6 +13,7 @@ from pathlib import Path from typing import Final from python_pkg.app_icons._app_icon import AppIcon +from python_pkg.app_icons._apps_more import MORE_APPS __all__ = ["APPS", "AppIcon", "get_app"] @@ -215,16 +216,7 @@ APPS: Final[dict[str, AppIcon]] = { icon_name="daily-limits", linux=False, ), - AppIcon( - key="tutor", - repo=_HOME / "src/tutor/app", - accent="#B8862E", - glyph="lightbulb", - icon_name="tutor", - # Desktop is the web build in a Chrome --app window, as for todo - # and home_inventory, whose desktop entries use the hicolor set. - linux=True, - ), + *MORE_APPS, ) } diff --git a/python_pkg/app_icons/glyphs.py b/python_pkg/app_icons/glyphs.py index 3ba015c5..4c00634e 100644 --- a/python_pkg/app_icons/glyphs.py +++ b/python_pkg/app_icons/glyphs.py @@ -29,7 +29,7 @@ from python_pkg.app_icons._glyph_art import ( _SHIELD_CUTLERY, _STORAGE_BOX, ) -from python_pkg.app_icons._glyph_art_extra import _LIGHTBULB +from python_pkg.app_icons._glyph_art_extra import _HEADPHONES, _LIGHTBULB from python_pkg.app_icons._glyph_art_more import ( _BOWL_STAR, _HOURGLASS, @@ -100,6 +100,7 @@ GLYPHS: Final[dict[str, Glyph]] = { _REMOTE_WAVE, ), Glyph("lightbulb", "Filled lightbulb over a two-bar screw base", _LIGHTBULB), + Glyph("headphones", "Headband arc over two filled ear cups", _HEADPHONES), ) } diff --git a/python_pkg/app_icons/tests/test_registry.py b/python_pkg/app_icons/tests/test_registry.py index db595d80..41f7c6e1 100644 --- a/python_pkg/app_icons/tests/test_registry.py +++ b/python_pkg/app_icons/tests/test_registry.py @@ -93,3 +93,16 @@ class TestLightbulbGlyph: def test_bulb_is_filled_in_the_accent(self) -> None: """The bulb takes the accent marker, so the monochrome layer recolours it.""" assert f'fill="{style.ACCENT_MARKER}"' in glyphs.get_glyph("lightbulb").body + + +class TestHeadphonesGlyph: + def test_tunedeck_uses_it(self) -> None: + app = apps.get_app("tunedeck") + assert app.glyph == "headphones" + assert app.linux + + def test_cups_are_filled_in_the_accent(self) -> None: + """The cups take the accent marker, so the monochrome layer recolours it.""" + body = glyphs.get_glyph("headphones").body + assert f'fill="{style.ACCENT_MARKER}"' in body + assert 'stroke="none"' in body