Existing frontend: detect, report, then keep or restyle (ticket 03)
- templates/detect_frontend.py: finds plain HTML, React/Vue/Angular/Svelte apps and PyQt web views; prints location, stack, main colours, font and every token block (CSS custom properties, <style> tags, Tailwind config). - templates/frontend.md: shared report / keep / restyle procedure, written so rpa-dashboard-ai can point to it. - SKILL.md steps 1, 2 and 6 point to it. - tests: detection self-check; fixture c-web-existing-frontend; eval entries c-keep and c-restyle (new pages only, per-question grading, checks on what changed in the existing files), both passing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3eb3fdf00e
commit
f11bddd958
15 files changed
+1210
-12
No files matched your search
@@ -0,0 +1,105 @@
|
||||
"""Frontend detection: where an existing frontend is, its stack and its colour tokens."""
|
||||
import importlib.util
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DETECT = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "detect_frontend.py"
|
||||
spec = importlib.util.spec_from_file_location("detect_frontend", DETECT)
|
||||
detect_frontend = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(detect_frontend)
|
||||
|
||||
|
||||
def project(tmp_path, files):
|
||||
for name, text in files.items():
|
||||
(tmp_path / name).parent.mkdir(parents=True, exist_ok=True)
|
||||
(tmp_path / name).write_text(text, encoding="utf-8")
|
||||
return tmp_path
|
||||
|
||||
|
||||
def cli(root):
|
||||
return subprocess.run([sys.executable, str(DETECT), str(root)], capture_output=True, text=True, check=True).stdout
|
||||
|
||||
|
||||
def test_no_frontend_when_only_tooling_and_installed_skills_have_html(tmp_path):
|
||||
root = project(tmp_path, {
|
||||
"main.py": "print('[step 1] Login')\n",
|
||||
"node_modules/pkg/index.html": "<style>:root{--x:#123456}</style>",
|
||||
".venv/lib/site.html": "<p>",
|
||||
".claude/skills/rpa-dashboard-no-ai/templates/dashboard.html": "<style>body{--accent:#3d9bd0}</style>",
|
||||
"dist/index.html": "<p>",
|
||||
})
|
||||
assert detect_frontend.detect(root) == []
|
||||
assert cli(root).strip() == "No existing frontend found."
|
||||
|
||||
|
||||
FIXTURE_C = Path(__file__).resolve().parent / "fixtures" / "c-web-existing-frontend"
|
||||
|
||||
|
||||
def test_plain_html_site_reports_location_stack_light_and_dark_tokens():
|
||||
[f] = detect_frontend.detect(FIXTURE_C)
|
||||
assert f["path"] == "web/"
|
||||
assert f["stack"] == "plain HTML (web)"
|
||||
light, dark = f["blocks"]
|
||||
assert (light["file"], light["selector"], light["dark"]) == ("web/css/theme.css", ":root", False)
|
||||
assert light["tokens"]["--color-brand"] == "#0f766e"
|
||||
assert light["tokens"]["--font-sans"] == '"Trebuchet MS", "Segoe UI", sans-serif'
|
||||
assert (dark["selector"], dark["dark"]) == ('[data-theme="dark"]', True)
|
||||
assert dark["tokens"]["--color-brand"] == "#0d9488"
|
||||
|
||||
out = cli(FIXTURE_C)
|
||||
first = out.splitlines()[0]
|
||||
assert first.startswith("Frontend found at `web/`, stack `plain HTML (web)`, colours `--color-brand #0f766e,")
|
||||
assert 'fonts `"Trebuchet MS", "Segoe UI", sans-serif`' in first
|
||||
assert "web/css/theme.css" in out and '[data-theme="dark"]' in out
|
||||
|
||||
|
||||
def test_react_app_is_one_frontend_with_its_media_query_dark_tokens(tmp_path):
|
||||
root = project(tmp_path, {
|
||||
"server.py": "from fastapi import FastAPI\n",
|
||||
"frontend/package.json": json.dumps({"dependencies": {"react": "^19.0.0", "react-dom": "^19.0.0"},
|
||||
"devDependencies": {"vite": "^6.0.0"}}),
|
||||
"frontend/index.html": '<div id="root"></div><script type="module" src="/src/main.jsx"></script>',
|
||||
"frontend/src/index.css": ":root { --primary: 222.2 47.4% 11.2%; --radius: .5rem }\n"
|
||||
"@media (prefers-color-scheme: dark) { :root { --primary: 210 40% 98% } }",
|
||||
"frontend/node_modules/react/index.html": "<p>",
|
||||
})
|
||||
[f] = detect_frontend.detect(root)
|
||||
assert (f["path"], f["stack"]) == ("frontend/", "React (Vite, web)")
|
||||
light, dark = f["blocks"]
|
||||
assert (light["file"], light["tokens"]["--primary"], light["dark"]) == ("frontend/src/index.css", "222.2 47.4% 11.2%", False)
|
||||
assert (dark["selector"], dark["tokens"], dark["dark"]) == (
|
||||
"@media (prefers-color-scheme: dark) :root", {"--primary": "210 40% 98%"}, True)
|
||||
assert "colours `--primary 222.2 47.4% 11.2%`" in cli(root)
|
||||
|
||||
|
||||
def test_vue_app_reads_component_styles_and_tailwind_theme(tmp_path):
|
||||
root = project(tmp_path, {
|
||||
"package.json": json.dumps({"dependencies": {"vue": "^3.5.0"}, "devDependencies": {"tailwindcss": "^3.4.0"}}),
|
||||
"tailwind.config.js": "module.exports = { theme: { extend: { colors: { brand: '#e11d48', 'brand-dark': \"#9f1239\" } } } }",
|
||||
"src/App.vue": "<template><p>{{ msg }}</p></template>\n<style>:root { --surface: #ffffff; }</style>",
|
||||
})
|
||||
[f] = detect_frontend.detect(root)
|
||||
assert (f["path"], f["stack"]) == ("./", "Vue (Tailwind, web)")
|
||||
blocks = {b["file"]: b for b in f["blocks"]}
|
||||
assert blocks["src/App.vue"]["tokens"] == {"--surface": "#ffffff"}
|
||||
assert blocks["tailwind.config.js"]["tokens"] == {"brand": "#e11d48", "brand-dark": "#9f1239"}
|
||||
assert "brand #e11d48" in cli(root).splitlines()[0]
|
||||
|
||||
|
||||
def test_pyqt_web_view_is_a_desktop_frontend(tmp_path):
|
||||
host = "from PyQt6.QtWebEngineWidgets import QWebEngineView\nview = QWebEngineView()\n"
|
||||
root = project(tmp_path / "file", {
|
||||
"app.py": host + "view.load(QUrl.fromLocalFile('ui/page.html'))\n",
|
||||
"bot.py": "print('[step 1] Login')\n",
|
||||
"ui/page.html": "<style>:root { --accent: #2563eb; }</style>",
|
||||
})
|
||||
[f] = detect_frontend.detect(root)
|
||||
assert (f["path"], f["stack"]) == ("ui/", "PyQt web view (desktop, host `app.py`)")
|
||||
assert f["blocks"][0]["tokens"] == {"--accent": "#2563eb"}
|
||||
|
||||
inline = project(tmp_path / "inline", {"main.py": host + "view.setHtml('<style>body{--accent:#16a34a}</style><p>Hi</p>')\n"})
|
||||
[f] = detect_frontend.detect(inline)
|
||||
assert (f["path"], f["stack"]) == ("main.py", "PyQt web view (desktop, host `main.py`)")
|
||||
assert f["blocks"][0]["tokens"] == {"--accent": "#16a34a"}
|
||||
Reference in new issue
Block a user