Files
tfaour 5f4f8f2ea7
Build and push server image / test (push) Successful in 44s
Build and push server image / build-and-push (push) Successful in 3m33s
Build and push server image / deploy (push) Failing after 1m27s
Add a curated theme system for "modern" style widgets, inspired by Tesserae
Frame.theme (7 presets in app/theme_tokens.py) drives font family,
corner radius, drop shadow, and an accent hue for every modern-style
widget's header/accent region. Rich accent colors (not just the 6 flat
panel inks) are approximated via denser Bayer stippling confined to just
that region (html_render.ordered_dither_regions), so icon/text content
elsewhere stays exactly as crisp as it is today -- verified directly
against real Chromium renders, both in unit tests and via run-server.
"classic" is a byte-identical no-visual-change default: weather's header
keeps its original fixed blue gradient, tasks/calendar keep their flat
THEME_* ink.

Themes are purely stylistic -- battery's charge-level color, calendar/
tasks' per-owner event chips, and text's own per-widget font choice are
never touched.
2026-07-31 10:34:28 +00:00

93 lines
4.2 KiB
Python

"""app.theme_tokens -- resolve_theme()'s fallback logic (does "classic"
stay a byte-identical no-op vs. each widget kind's pre-theme-system
look?) and the font table every preset resolves against. No rendering
here -- see test_html_render.py for ordered_dither_regions and each
widget's own test_widgets_*.py for dispatch-level "does a theme actually
change the output" coverage."""
from __future__ import annotations
from pathlib import Path
from app import panel_style, theme_tokens
def test_classic_weather_matches_historical_fixed_gradient():
"""Weather's modern style never went through panel_style.THEME --
its header was always this fixed blue gradient (see html_render.py's
removed ACCENT_START/ACCENT_END). "classic" must reproduce it
byte-for-byte so themes are additive, not a silent regression."""
resolved = theme_tokens.resolve_theme("classic", "weather", None)
assert resolved["accent_hex"] == "#1c4fd6"
assert resolved["accent_hex_dark"] == "#6fa8ff"
def test_classic_tasks_and_calendar_use_flat_theme_ink():
"""Tasks/calendar's classic accent was always a flat single ink (no
gradient) resolved through panel_style.THEME -- both ends of the
"gradient" must be that same ink, not two different shades."""
tasks = theme_tokens.resolve_theme("classic", "tasks", None)
calendar = theme_tokens.resolve_theme("classic", "calendar", None)
assert tasks["accent_hex"] == tasks["accent_hex_dark"]
assert calendar["accent_hex"] == calendar["accent_hex_dark"]
from app.image_pipeline import DEFAULT_PALETTE_RGB
expected_tasks = "#%02x%02x%02x" % tuple(DEFAULT_PALETTE_RGB[panel_style.THEME_TASKS])
expected_calendar = "#%02x%02x%02x" % tuple(DEFAULT_PALETTE_RGB[panel_style.THEME_CALENDAR])
assert tasks["accent_hex"] == expected_tasks
assert calendar["accent_hex"] == expected_calendar
def test_classic_unmapped_widget_kind_falls_back_to_black():
resolved = theme_tokens.resolve_theme("classic", "battery", None)
from app.image_pipeline import DEFAULT_PALETTE_RGB
assert resolved["accent_hex"] == "#%02x%02x%02x" % tuple(DEFAULT_PALETTE_RGB[panel_style.BLACK])
def test_unknown_theme_name_falls_back_to_classic():
assert theme_tokens.resolve_theme("not-a-real-theme", "weather", None) == \
theme_tokens.resolve_theme("classic", "weather", None)
assert theme_tokens.resolve_theme(None, "weather", None) == \
theme_tokens.resolve_theme("classic", "weather", None)
def test_every_preset_resolves_without_error_for_every_widget_kind():
widget_kinds = ["weather", "tasks", "calendar", "battery", "text", "static", "whiteboard"]
for theme_name in theme_tokens.THEMES:
for kind in widget_kinds:
resolved = theme_tokens.resolve_theme(theme_name, kind, None)
assert resolved["accent_hex"].startswith("#") and len(resolved["accent_hex"]) == 7
assert resolved["accent_hex_dark"].startswith("#") and len(resolved["accent_hex_dark"]) == 7
assert resolved["font_family"] in theme_tokens.FONT_FAMILIES
assert Path(resolved["font_regular"]).exists()
assert Path(resolved["font_bold"]).exists()
def test_non_gradient_theme_has_equal_accent_stops():
""""moss" is configured with gradient=False -- its two CSS gradient
stops must be identical (a flat fill), unlike a gradient theme's."""
resolved = theme_tokens.resolve_theme("moss", "tasks", None)
assert resolved["gradient"] is False
assert resolved["accent_hex"] == resolved["accent_hex_dark"]
def test_gradient_theme_has_a_darker_second_stop():
resolved = theme_tokens.resolve_theme("terracotta", "tasks", None)
assert resolved["gradient"] is True
assert resolved["accent_hex"] == "#a84b2a"
assert resolved["accent_hex_dark"] != resolved["accent_hex"]
def test_font_path_covers_every_family_and_style_combination():
for family in theme_tokens.FONT_FAMILIES:
for bold in (False, True):
for italic in (False, True):
assert theme_tokens.font_path(family, bold, italic).exists()
def test_font_path_falls_back_to_default_family_for_unknown_name():
assert theme_tokens.font_path("not-a-real-family", False, False) == \
theme_tokens.font_path(theme_tokens.DEFAULT_FONT_FAMILY, False, False)