Files
computer-playbook/plugins/action/design_assets.py
T
kevinveenbirkenbachandClaude Opus 5.5 63f3631567 fix(design): give lockup text on pages a contrasting color in both color schemes
A lockup slot on a page or panel drew its title in the light
--design-primary with a --design-on-primary outline in both color schemes.
With an orange base that is about 3:1 on the light surface, and the SVG
never changed in dark mode; the Sphinx, Jenkins and Jellyfin passes
reported it.

sys-front-inj-design now passes --design-link with a --design-surface-1
outline for light and their dark values for dark. render_assets() hands
the dark pair to every slot without frame: true, and render_svg() adds a
style rule under prefers-color-scheme: dark that overrides the fill and
stroke attributes. Slots on brand chrome keep the frame colors, and the PNG
keeps the light pair. The design_assets action requires the two new
arguments; its two callers, assets.yml and Keycloak's theme task, pass
them.

A unit test asserts the dark rule on page slots and its absence on frame
and logo-only slots; it fails when the dark pair is not handed on. In
headless Chromium an <img> of the SVG renders the title #825200 on #fff5ea
in light and #ffa500 on #140e07 in dark.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:48:21 +02:00

81 lines
2.5 KiB
Python

from __future__ import annotations
import shutil
import tempfile
from pathlib import Path
from typing import Any
from plugins.action.stack_host_copy import ActionModule as StackHostCopyAction
from utils.design.branding import is_disabled
from utils.design.logo import render_assets
_REQUIRED = (
"logo",
"title",
"domain",
"fill",
"stroke",
"dark_fill",
"dark_stroke",
"frame_fill",
"frame_stroke",
"slots",
"dest",
)
class ActionModule(StackHostCopyAction):
"""
Render the branding assets of one role on the controller and copy them to
the stack host.
Args (task):
logo absolute logo path
title title text, or false
domain role domain
fill text color of a slot on a page or panel
stroke text outline color of a slot on a page or panel
dark_fill dark-mode text color of a page or panel slot's SVG
dark_stroke dark-mode text outline color of a page or panel slot's SVG
frame_fill text color of a slot on brand-colored chrome
frame_stroke text outline color of a slot on brand-colored chrome
slots {name: {width, height, text_only, frame}}
dest destination directory on the stack host
"""
def run(
self, tmp: Any = None, task_vars: dict[str, Any] | None = None
) -> dict[str, Any]:
args = dict(self._task.args)
missing = [key for key in _REQUIRED if key not in args]
if missing:
return {
"failed": True,
"msg": f"design_assets: missing {', '.join(missing)}",
}
workdir = Path(tempfile.mkdtemp(prefix="design-assets-"))
try:
title = False if is_disabled(args["title"]) else str(args["title"])
assets = render_assets(
args["logo"],
title,
args["domain"],
args["fill"],
args["stroke"],
args["dark_fill"],
args["dark_stroke"],
args["frame_fill"],
args["frame_stroke"],
args["slots"],
)
for name, content in assets.items():
(workdir / name).write_bytes(content)
self._task.args = {
"src": f"{workdir}/",
"dest": f"{str(args['dest']).rstrip('/')}/",
}
return super().run(tmp=tmp, task_vars=task_vars)
finally:
shutil.rmtree(workdir, ignore_errors=True)