app_icons: tunedeck gets headphones; later entries move to _apps_more
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyiH5e86uaQzhXzVXTQiVc
This commit is contained in:
Krzysztof Rudnicki 2026-10-16 16:16:09 +02:00
parent 70df11f306
commit 6383bd7452
7 changed files with 120 additions and 11 deletions

View File

@ -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"
}

View File

@ -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"
]
}

View File

@ -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,
),
)

View File

@ -24,3 +24,20 @@ _LIGHTBULB = """\
A 150 150 0 0 1 662 442 C 662 500 576 510 576 556 Z"/>
<path d="M 440 668 L 584 668"/>
<path d="M 480 780 L 544 780"/>"""
# 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 = """\
<path d="M 296 560 A 216 216 0 0 1 728 560"/>
<rect x="256" y="520" width="112" height="216" rx="40" \
fill="{{ACCENT}}" stroke="none"/>
<rect x="656" y="520" width="112" height="216" rx="40" \
fill="{{ACCENT}}" stroke="none"/>"""

View File

@ -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,
)
}

View File

@ -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),
)
}

View File

@ -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