"""Registry of the Flutter apps that use the shared icon family. Adding a new app here (plus a glyph in :mod:`python_pkg.app_icons.glyphs`) is all that is needed for it to get an on-style icon. The accent is the one shared family accent (`#B8862E`, from the `unified-design-system` skill) — apps are distinguished by glyph shape, not color; do not give a new app its own accent. """ from __future__ import annotations from pathlib import Path from typing import Final from python_pkg.app_icons._app_icon import AppIcon __all__ = ["APPS", "AppIcon", "get_app"] _HOME = Path.home() APPS: Final[dict[str, AppIcon]] = { app.key: app for app in ( AppIcon( key="dufs_client", # The live app: ~/dufs_client is a stale, remote-less checkout that # was superseded when dufs-cloud absorbed it (see testsAndMisc # CLAUDE.md). Same package name, so it is easy to icon the wrong one. repo=_HOME / "src/dufs-cloud" / "app", accent="#B8862E", glyph="cloud-down", icon_name="dufs-client", linux=False, ), AppIcon( key="focus_owner", # Absorbed into the phone-focus-mode monorepo on 2026-08-24; the # standalone ~/focus-owner clone is gone, as is its GitHub repo. repo=_HOME / "src/phone-focus-mode" / "focus-owner", accent="#B8862E", glyph="padlock-closed", icon_name="focus-owner", linux=False, ), AppIcon( key="workout_app", repo=_HOME / "src/screen-locker" / "stronglift_replacement" / "workout_app", accent="#B8862E", glyph="barbell", icon_name="workout-app", linux=False, ), AppIcon( key="wake_alarm_sync", repo=_HOME / "src/wake-alarm" / "phone_app", accent="#B8862E", glyph="clock", icon_name="wake-alarm-sync", linux=False, ), AppIcon( key="diet_guard_app", repo=_HOME / "src/diet-guard" / "app", accent="#B8862E", glyph="shield-cutlery", icon_name="diet-guard-app", linux=True, ), AppIcon( key="todo", repo=_HOME / "src/todo", accent="#B8862E", glyph="checklist", icon_name="todo", linux=True, ), AppIcon( key="untools", repo=_HOME / "src/untools", accent="#B8862E", glyph="decision-tree", icon_name="untools", # Desktop is the web build in a Chrome --app window, so there is # no GTK app to hand a hicolor PNG to. linux=False, ), AppIcon( key="punchme", repo=_HOME / "src/punchme", accent="#B8862E", glyph="punch-card", icon_name="punchme", # Android-only app: no GTK desktop build to hand a hicolor PNG to. linux=False, ), AppIcon( key="restaurant_rater", repo=_HOME / "src/restaurant-rater", accent="#B8862E", glyph="bowl-star", icon_name="restaurant-rater", # Android-only app: no GTK desktop build to hand a hicolor PNG to. linux=False, ), AppIcon( key="habit_stack", repo=_HOME / "src/habit_stack", accent="#B8862E", glyph="chain-link", icon_name="habit-stack", linux=False, ), AppIcon( key="home_inventory", repo=_HOME / "src/home_inventory", accent="#B8862E", glyph="storage-box", icon_name="home-inventory", linux=True, ), AppIcon( key="epopeja_karta", repo=_HOME / "src/epopeja_karta", accent="#B8862E", glyph="quill-nib", icon_name="epopeja-karta", # A linux/ target exists, but only so the app can be driven on the # desktop during development. It is a character sheet you bring to # the table, so it ships no desktop entry and gets no hicolor set. linux=False, ), AppIcon( key="octoforge", # The Flutter app is a subdirectory of the repo: the repo root # holds the pure-Dart core and the tooling alongside it. repo=_HOME / "src/octoforge" / "app", accent="#B8862E", glyph="anvil", icon_name="octoforge", # Unlike the other phone-first apps, this one is genuinely used on # the desktop too, so it gets the hicolor set and a desktop entry. linux=True, ), AppIcon( key="kuhylog", repo=_HOME / "src/kuhylog", accent="#B8862E", glyph="track-bars", icon_name="kuhylog", # Android is the real target (home-screen widget, quick-settings # tile); the Linux build exists for trying the UI without a phone, # so it still wants the hicolor set. linux=True, ), AppIcon( key="lyricanki", repo=_HOME / "src/lyricanki", accent="#B8862E", glyph="note-ascender", icon_name="lyricanki", # Mobile-primary by decision Q6: the pack builder is a Python tool # run on the desktop, but the Flutter app itself has no Linux target. linux=False, ), AppIcon( key="betting_sim", repo=_HOME / "src/betting-sim" / "app", accent="#B8862E", glyph="track-bars", icon_name="betting_sim", # Ships to Android natively and to Linux via a Chrome-wrapped web # build, so the GTK embedder is never used and the hicolor set # would go unread. linux=False, ), AppIcon( key="home_guard_app", repo=_HOME / "src/home-guard" / "app", accent="#B8862E", glyph="house-tick", icon_name="home-guard", linux=False, ), AppIcon( key="signalbot_remote", # Inside the bot's own repository, not beside it: the app is a # client of that repo's management API, and kuhy's call was that a # route change and the client change land in one commit. repo=_HOME / "src/signal-bot" / "app", accent="#B8862E", glyph="remote-wave", icon_name="signalbot_remote", # Android only. The bot's desktop control is the TypeScript page # it already has, served on loopback. linux=False, ), AppIcon( key="umbrella", repo=_HOME / "src/umbrella", accent="#B8862E", glyph="umbrella", icon_name="umbrella", linux=False, ), AppIcon( key="book_guard_app", repo=_HOME / "src/book-guard/app", accent="#B8862E", glyph="open-book", icon_name="book-guard", linux=False, ), ) } def get_app(key: str) -> AppIcon: """Look up an app by key. Parameters: key (str): App identifier, e.g. ``"todo"``. Returns: AppIcon: The matching configuration. Raises: KeyError: If no app with that key is registered. """ try: return APPS[key] except KeyError: available = ", ".join(sorted(APPS)) msg = f"unknown app {key!r}; available: {available}" raise KeyError(msg) from None