mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
feat(site): dogfood the Scarf dashboard format as the catalog website
Adds site/ with vanilla HTML + CSS + ~300 lines of JavaScript that
renders ProjectDashboard JSON directly in the browser. Each template's
detail page shows a live preview of the exact dashboard the user will
get post-install — the catalog IS the dogfood.
site/widgets.js mirrors the Swift widget dispatcher:
- stat (big number + colored icon + optional subtitle)
- progress (0..1 bar)
- text with inline markdown subset (headings, bold/italic, inline code,
code fences, bullet + numbered lists, links)
- table (plain HTML)
- list (with up/down/unknown status badges)
- chart (SVG line + bar — no Chart.js dependency)
- webview (sandboxed iframe)
- unknown (placeholder so the page doesn't silently omit widgets)
Plus the renderMarkdown helper used by the template detail page to
display the bundle's README.
site/index.html.tmpl + site/template.html.tmpl are substitution-only —
the Python regenerator swaps {{CARDS}}, {{COUNT}}, {{COUNT_PLURAL}},
{{NAME}}, {{DESC}}, {{VERSION}}, {{AUTHOR_HTML}}, {{TAGS_HTML}},
{{INSTALL_URL_ENCODED}}, {{SCARF_INSTALL_URL}}. The detail page fetches
dashboard.json + README.md at page load and hands them to widgets.js.
No client-side framework, no bundler, no npm.
site/styles.css: minimal CSS with scarf green accent, prefers-color-
scheme dark support, responsive at 680px. One file, ~280 lines.
build-catalog.py extended to copy dashboard.json + README.md out of each
bundle into its detail dir so widgets.js can fetch them without
reaching across directories (and so gh-pages doesn't need to serve zip
contents at request time).
Two new Python tests: end-to-end site rendering (both cards, install
URL wiring, static asset copy, per-template dashboard + README copy)
and the {{COUNT_PLURAL}} singular-vs-plural flip. 16/16 Python tests
green.
Smoke-tested locally with python3 -m http.server: every endpoint
(index, catalog.json, detail HTML, per-template dashboard.json + README,
widgets.js) returns 200. The .gh-pages-worktree/appcast.xml +
.gh-pages-worktree/index.html are untouched — the catalog is purely
additive under /templates/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -364,6 +364,75 @@ class CatalogJsonTests(unittest.TestCase):
|
||||
self.assertEqual(entry["detailSlug"], "tester-shape")
|
||||
|
||||
|
||||
class SiteRenderingTests(unittest.TestCase):
|
||||
"""Verify the regenerator produces usable HTML + copies dashboard.json
|
||||
+ README.md into each detail dir for widgets.js to fetch. No browser
|
||||
automation — just shape checks so we catch silly breakages
|
||||
(missing tokens, stale templates, broken copy)."""
|
||||
|
||||
def test_render_site_end_to_end(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
repo = make_fake_repo(Path(tmp))
|
||||
# Build a couple templates so the grid has more than one card.
|
||||
make_template_dir(repo, "alice", "alpha")
|
||||
make_template_dir(repo, "bob", "beta")
|
||||
|
||||
# Give the fake repo a site/ dir so render_site produces HTML.
|
||||
site_src = repo / "site"
|
||||
site_src.mkdir()
|
||||
(site_src / "index.html.tmpl").write_text(
|
||||
"<h1>Catalog ({{COUNT}} template{{COUNT_PLURAL}})</h1>{{CARDS}}"
|
||||
)
|
||||
(site_src / "template.html.tmpl").write_text(
|
||||
"<h1>{{NAME}}</h1><p>{{DESC}}</p>"
|
||||
"<a href=\"{{SCARF_INSTALL_URL}}\">install</a>"
|
||||
"<a href=\"{{INSTALL_URL_ENCODED}}\">download</a>"
|
||||
)
|
||||
(site_src / "widgets.js").write_text("/* test widgets */")
|
||||
(site_src / "styles.css").write_text("/* test styles */")
|
||||
|
||||
records = []
|
||||
for tdir in build_catalog._iter_templates(repo):
|
||||
r, errors = build_catalog.validate_template(tdir)
|
||||
self.assertEqual(errors, [])
|
||||
records.append(r)
|
||||
|
||||
out = Path(tmp) / "out"
|
||||
build_catalog.render_site(records, out, repo)
|
||||
|
||||
# Index: both cards present, plural form flipped for count=2.
|
||||
idx = (out / "index.html").read_text()
|
||||
self.assertIn("Catalog (2 templates)", idx)
|
||||
self.assertIn("alice-alpha/", idx)
|
||||
self.assertIn("bob-beta/", idx)
|
||||
|
||||
# Static assets copied.
|
||||
self.assertTrue((out / "widgets.js").exists())
|
||||
self.assertTrue((out / "styles.css").exists())
|
||||
self.assertTrue((out / "catalog.json").exists())
|
||||
|
||||
# Each detail dir has index.html + dashboard.json + README.md.
|
||||
alpha = out / "alice-alpha"
|
||||
self.assertTrue((alpha / "index.html").exists())
|
||||
self.assertTrue((alpha / "dashboard.json").exists())
|
||||
self.assertTrue((alpha / "README.md").exists())
|
||||
|
||||
alpha_html = (alpha / "index.html").read_text()
|
||||
# Install URL wires through the scarf:// scheme + raw GH URL.
|
||||
self.assertIn("scarf://install?url=https://raw.githubusercontent.com/", alpha_html)
|
||||
|
||||
def test_render_index_singular_form_for_one_template(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
repo = make_fake_repo(Path(tmp))
|
||||
make_template_dir(repo, "alice", "alpha")
|
||||
records = []
|
||||
for tdir in build_catalog._iter_templates(repo):
|
||||
r, _ = build_catalog.validate_template(tdir)
|
||||
records.append(r)
|
||||
html = build_catalog.render_index("{{COUNT}} template{{COUNT_PLURAL}}", records)
|
||||
self.assertEqual(html, "1 template")
|
||||
|
||||
|
||||
class RealBundleTest(unittest.TestCase):
|
||||
"""Run the validator against the actual shipped Site Status Checker
|
||||
bundle. Catches drift between validator + real-world author
|
||||
|
||||
Reference in New Issue
Block a user