No-AI skill tracer: SKILL.md, theme presets, sample data, Seam 1 page check
- rpa-dashboard-no-ai/SKILL.md: trigger description and the workflow in spec order (scan, report/keep-or-restyle, colours, stack, sections, build, wire, verify). - templates/themes.md: Tebra Sky and Superbill Ember, light and dark blocks. - templates/palette.py: derives a custom palette from one brand hex and checks text contrast in light and dark. - dashboard.html: built-in sample data so a page with no bot works on first open; cleared batches in the donut; component tokens so Ember is a token-only swap; accessible names for KPI cards, nav, steps, log, search and the dark toggle. - bridge.py: placeholders inside string literals, so it passes the lint hook unfilled. - tests/page_check: Seam 1 harness (fake Qt bridge, light/dark/narrow). - tests/evals: Seam 2 runner and fixture a-empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
49f8f49b65
commit
7f2ddecffb
13 files changed
+865
-57
No files matched your search
@@ -0,0 +1,45 @@
|
||||
"""Custom palette: one brand hex in, light + dark accent tokens out, readable in both modes."""
|
||||
import importlib.util
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PALETTE = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "palette.py"
|
||||
spec = importlib.util.spec_from_file_location("palette", PALETTE)
|
||||
palette = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(palette)
|
||||
|
||||
# Fixed Tebra Sky base colours the derived accents sit on (from dashboard.html).
|
||||
LIGHT_BG, DARK_BG = "#eaf6fb", "#182a38"
|
||||
LIGHT_INK, DARK_INK = "#1c2b33", "#e6eef4"
|
||||
LOG_ERROR = "#f68c82" # dimmest log text colour
|
||||
|
||||
|
||||
def contrast(a, b):
|
||||
def lum(h):
|
||||
c = [int(h[i:i + 2], 16) / 255 for i in (1, 3, 5)]
|
||||
c = [x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055) ** 2.4 for x in c]
|
||||
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
|
||||
hi, lo = sorted([lum(a), lum(b)], reverse=True)
|
||||
return (hi + 0.05) / (lo + 0.05)
|
||||
|
||||
|
||||
def test_derived_palette_is_readable_in_light_and_dark():
|
||||
for brand in ["#3d9bd0", "#ffd400", "#0b1f4d", "#7a3cff", "#e4002b", "#00a651", "#ffffff", "#000000"]:
|
||||
t = palette.derive(brand)
|
||||
for mode, bg, ink in [("light", LIGHT_BG, LIGHT_INK), ("dark", DARK_BG, DARK_INK)]:
|
||||
m = t[mode]
|
||||
assert contrast("#ffffff", m["--accent"]) >= 3, (brand, mode, "button text")
|
||||
assert contrast("#ffffff", m["--accent-2"]) >= 3, (brand, mode, "button text, light end")
|
||||
assert contrast(m["--accent"], bg) >= 3, (brand, mode, "accent text")
|
||||
assert contrast(ink, m["--accent-soft"]) >= 4.5, (brand, mode, "secondary button text")
|
||||
assert contrast(LOG_ERROR, m["--accent-dark"]) >= 4.5, (brand, mode, "log text")
|
||||
|
||||
|
||||
def test_brand_kept_when_it_already_passes():
|
||||
assert palette.derive("#1f6fb2")["light"]["--accent"] == "#1f6fb2"
|
||||
|
||||
|
||||
def test_cli_prints_both_token_blocks_and_contrast_table():
|
||||
out = subprocess.run([sys.executable, str(PALETTE), "#7a3cff"], capture_output=True, text=True, check=True).stdout
|
||||
assert "body{" in out and "body.dark{" in out and "PASS" in out and "FAIL" not in out
|
||||
Reference in new issue
Block a user