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:
VasanthandClaude Opus 5.5 committed 2026-10-16 13:59:26 +05:30
1 parent 49f8f49b65
commit 7f2ddecffb
13 files changed
+865 -57

No files matched your search

+45
View File
@@ -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