testsAndMisc/python_pkg/app_icons/labels.py
Krzysztof kuhy Rudnicki 35b497b409
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Has been cancelled
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Has been cancelled
Python tests / test (push) Has been cancelled
repo-contract / repo contract (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / lib-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled
app_icons: fit every glyph to one ink circle, launcher-name rules
- fit.py: centre each glyph and scale it so its furthest ink sits at
  INK_RADIUS = 340px, compensating the unscaled stroke width
- style.py: INK_RADIUS plus INK_RADIUS_DP for vector-drawable consumers
- labels.py/_label_widths.py: LABELS registry; names start with a
  capital, ASCII letters only, <= 10 chars, <= 522 wide in the Pixel
  launcher font
- drop centre_offset, safe_box_overflow and the CLI safe-box warning
- add prompt-bubble (sufler) and camera (macro_cam) glyphs and apps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kQ5PCKqBHAkAVkPSvp6dk
2026-10-23 14:35:00 +02:00

85 lines
2.8 KiB
Python

"""Launcher names for every registered app, and the rules they must meet.
kuhy's rules (2026-10-23): a name starts with a capital, holds only ASCII
letters -- no spaces, underscores or punctuation -- and is short enough that
the Pixel launcher never truncates it with an ellipsis. ``signalbot_remote``
showed as ``signalbot_r...`` while the 12-character ``Daily limits`` fitted.
A character cap alone was not enough: the 10-letter ``FocusOwner`` was still
cut in the app drawer, so the rendered width in the launcher font is capped
too (:mod:`_label_widths`).
``repo_contract`` (kuhyx/utils) applies the same rules to every Flutter repo's
``android:label``, so an app that is not registered here is still held to them.
"""
from __future__ import annotations
import re
from typing import Final
from python_pkg.app_icons._label_widths import MAX_WIDTH, label_width
MAX_LENGTH: Final[int] = 10
_ASCII_LETTERS: Final[re.Pattern[str]] = re.compile(r"[A-Za-z]+")
LABELS: Final[dict[str, str]] = {
"dufs_client": "Cloud",
"focus_owner": "Focuser",
"workout_app": "Workout",
"wake_alarm_sync": "WakeAlarm",
"diet_guard_app": "DietGuard",
"todo": "Todo",
"untools": "Untools",
"punchme": "PunchMe",
"restaurant_rater": "Rater",
"habit_stack": "HabitStack",
"home_inventory": "Inventory",
"epopeja_karta": "Epopeja",
"octoforge": "OctoForge",
"kuhylog": "KuhyLog",
"lyricanki": "LyricAnki",
"betting_sim": "BettingSim",
"home_guard_app": "Homer",
"signalbot_remote": "SignalBot",
"umbrella": "Umbrella",
"book_guard_app": "BookGuard",
"daily_limits": "DailyLimit",
"tutor": "Tutor",
"tunedeck": "TuneDeck",
"sufler": "Sufler",
"macro_cam": "MacroCam",
}
def label_problems(name: str) -> list[str]:
"""List every launcher-name rule a name breaks.
Parameters:
name (str): Candidate launcher label.
Returns:
list[str]: One message per broken rule; empty when the name is valid.
"""
if not name:
return ["label is empty"]
problems: list[str] = []
letters_only = _ASCII_LETTERS.fullmatch(name) is not None
if not letters_only:
problems.append(
f"{name!r} must contain only ASCII letters (no spaces, _ or symbols)"
)
if not name[0].isupper():
problems.append(f"{name!r} must start with a capital letter")
# The width table only knows ASCII letters; other names already failed.
if letters_only and label_width(name) > MAX_WIDTH:
problems.append(
f"{name!r} is {label_width(name)} wide in the launcher font; "
f"at most {MAX_WIDTH} fit"
)
if len(name) > MAX_LENGTH:
problems.append(
f"{name!r} has {len(name)} characters; at most {MAX_LENGTH} fit"
)
return problems