Files
scarf/site/template.html.tmpl
Alan Wizemann 68f6b98fcf feat(catalog-config): mirror manifest v2 schema in validator + site
Phase D of v2.3 template configuration — closes the loop between the
Swift app and the catalog pipeline. Authors can now ship schemaful
bundles; the Python validator enforces the same invariants the Swift
installer does; the catalog site displays the schema so visitors see
what they'll need to configure before installing.

Python validator (tools/build-catalog.py):
- SUPPORTED_SCHEMA_VERSIONS accepts both 1 and 2 (v1 bundles are
  unchanged; v2 adds optional manifest.config).
- New _validate_config_schema function mirrors the Swift
  ProjectConfigService.validateSchema rules: unique keys, supported
  types, enum option presence + unique values, list itemType ==
  "string", secret-field cannot declare a default,
  modelRecommendation.preferred non-empty when present.
- _validate_contents_claim cross-checks contents.config (field count)
  against config.schema actual length — mismatch refused.
- TemplateRecord.to_catalog_entry exposes `config` in catalog.json so
  the site can render the schema.
- render_site copies each bundle's template.json to the detail dir as
  manifest.json (only when the manifest has a config block — keeps
  the served tree lean and makes "no manifest.json" a meaningful
  404 signal in the frontend).
- catalog.json's own schemaVersion stays at 1 (independent of per-
  template manifest schemaVersion).

Python tests (tools/test_build_catalog.py): 8 new cases in a new
ConfigSchemaValidationTests suite — accepts schemaful bundle, rejects
duplicate keys, rejects secret-with-default, rejects enum-without-
options, rejects unsupported field type, rejects contents.config
count mismatch, rejects unsupported list itemType, legacy v1
manifests pass unchanged. 24/24 Python tests total.

Site (site/widgets.js):
- New renderConfigSchema(container, config) — mirrors the display
  on the Scarf install preview. Renders each field as a <dt>/<dd>
  pair with type + required badges; enum shows choice labels; list
  fields show min/max bounds; string fields show pattern/length;
  secret fields get a "Stored in Keychain" reassurance. Optional
  modelRecommendation panel at the bottom with preferred + rationale
  + alternatives.
- The renderer is display-only — the site never collects values;
  that's the Scarf app's job.

template.html.tmpl adds a #config-schema <section>. The inline script
fetches manifest.json from the detail dir; on success hands the
config block to ScarfWidgets.renderConfigSchema; on 404 (schema-less
templates) silently leaves the section empty. CSS in styles.css
adds a config-schema panel matching the accent-green aesthetic.

24/24 Python + 50/50 Swift tests pass. site-status-checker still
renders correctly (schema-less; manifest.json isn't copied for it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:14:29 +02:00

105 lines
3.6 KiB
Cheetah

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{NAME}} — Scarf Templates</title>
<meta name="description" content="{{DESC}}">
<link rel="stylesheet" href="../styles.css">
<link rel="icon" type="image/png" href="../assets/icon.png">
</head>
<body>
<header class="site-header">
<a class="brand" href="..">
<img src="../assets/icon.png" alt="" width="40" height="40">
<span class="brand-name">Scarf Templates</span>
</a>
<nav class="site-nav">
<a href="..">Catalog</a>
<a href="https://github.com/awizemann/scarf">GitHub</a>
<a href="https://github.com/awizemann/scarf/blob/main/templates/CONTRIBUTING.md">Contribute</a>
</nav>
</header>
<main class="detail">
<section class="detail-header">
<div>
<h1>{{NAME}} <span class="version">v{{VERSION}}</span></h1>
<p class="desc">{{DESC}}</p>
<p class="meta">
<span class="author">by {{AUTHOR_HTML}}</span>
<span class="id">{{ID}}</span>
<span class="category">{{CATEGORY}}</span>
</p>
<p class="tags">{{TAGS_HTML}}</p>
</div>
<div class="install-actions">
<a class="btn btn-primary" href="{{SCARF_INSTALL_URL}}">Install with Scarf</a>
<a class="btn btn-secondary" href="{{INSTALL_URL_ENCODED}}">Download .scarftemplate</a>
</div>
</section>
<section class="detail-dashboard">
<h2>Live dashboard preview</h2>
<p class="detail-dashboard-note">
Exactly what you'll see inside Scarf after install. Values shown here are
placeholders; the agent updates them each time the cron job runs.
</p>
<div id="dashboard-preview"></div>
</section>
<section class="detail-config">
<div id="config-schema"></div>
</section>
<section class="detail-readme">
<h2>README</h2>
<div id="readme-body"></div>
</section>
</main>
<footer class="site-footer">
<p>
Scarf is open source:
<a href="https://github.com/awizemann/scarf">github.com/awizemann/scarf</a>.
</p>
</footer>
<script src="../widgets.js"></script>
<script>
// Fetch + render dashboard + README + config schema at page load.
// Dashboard + README live next to index.html in this template's
// detail dir; the config schema comes from the sibling manifest.json
// that the build-catalog renderer also copies in.
(async function () {
const dashboardEl = document.getElementById("dashboard-preview");
const readmeEl = document.getElementById("readme-body");
const configEl = document.getElementById("config-schema");
try {
const d = await fetch("dashboard.json").then(r => r.json());
ScarfWidgets.renderDashboard(dashboardEl, d);
} catch (e) {
dashboardEl.textContent = "Could not load dashboard preview.";
}
try {
const md = await fetch("README.md").then(r => r.text());
readmeEl.innerHTML = ScarfWidgets.renderMarkdown(md);
} catch (e) {
readmeEl.textContent = "Could not load README.";
}
try {
// manifest.json may not exist for schema-less templates — that's
// fine, we just leave the config section empty.
const res = await fetch("manifest.json");
if (res.ok) {
const manifest = await res.json();
ScarfWidgets.renderConfigSchema(configEl, manifest.config);
}
} catch (e) {
// Silent — config-schema display is optional.
}
})();
</script>
</body>
</html>