mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 02:26:37 +00:00
feat(dashboards): v2.7 widget catalog — file-reading widgets, sparkline, typed status, project-wide watch
Major project-dashboard release. Five new widget types (markdown_file, log_tail, cron_status, image, status_grid), inline sparkline on stat, typed status enum shared by list + status_grid, structured WidgetErrorCard, and a project-wide .scarf/ directory watch that picks up files cron jobs write next to dashboard.json. - ProjectDashboard: extend DashboardWidget with path/lines/jobId/cells/gridColumns/sparkline; add StatusGridCell + ListItemStatus (lenient parse with synonyms) - HermesFileWatcher: watch each project's .scarf/ dir alongside dashboard.json (local FSEvents + remote SSH mtime poll); updateProjectWatches signature now takes dashboardPaths + scarfDirs - New widget views: CronStatus, Image, LogTail, MarkdownFile, StatusGrid, plus WidgetErrorCard for structured failure messaging; legacy "Unknown" placeholder replaced everywhere - WidgetPathResolver: project-root-anchored path resolution that rejects absolute paths + ".." escapes pre and post canonicalization - Stat widget gains optional inline sparkline (pure SwiftUI Path, no Charts dep); list widget rows route through typed status with semantic icons + ScarfColor tints - iOS list widget + unsupported card adopt typed status + warning-toned error card (parity with Mac error styling); new widget types remain Mac-only - Site mirror: widgets.js renders all five new types (file-reading widgets show annotated catalog placeholders), sparkline SVG, status-grid grid; styles.css adds typed-status palette + error-card + sparkline + grid styles - Catalog validator: tools/widget-schema.json is the single source of truth; build-catalog.py loads it and enforces per-type required fields. 8 new test cases in test_build_catalog.py covering schema load, v2.7 additions, and missing-required rejection - Template-author skill (SKILL.md) gains v2.7 Widget Catalog section + canonical status guidance; CONTRIBUTING.md points authors at widget-schema.json; template-author bundle rebuilt - Localizable.xcstrings picks up auto-extracted strings for the previously-shipped OAuth keepalive feature - Release notes drafted at releases/v2.7.0/RELEASE_NOTES.md Backwards compatible — existing dashboard.json renders byte-identically, status synonyms (ok/up/down/active/etc.) keep working. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+29
-1
@@ -56,7 +56,24 @@ SUPPORTED_SCHEMA_VERSIONS = {SCHEMA_VERSION_V1, SCHEMA_VERSION_V2, SCHEMA_VERSIO
|
||||
SLASH_COMMAND_NAME_RE = re.compile(r"^[a-z][a-z0-9-]*$")
|
||||
MAX_BUNDLE_BYTES = 5 * 1024 * 1024 # 5 MB cap on submissions; installer is 50 MB
|
||||
REQUIRED_BUNDLE_FILES = ("template.json", "README.md", "AGENTS.md", "dashboard.json")
|
||||
SUPPORTED_WIDGET_TYPES = {"stat", "progress", "text", "table", "chart", "list", "webview"}
|
||||
# Widget vocabulary — loaded from tools/widget-schema.json (single source of
|
||||
# truth, also referenced by the agent-authoring SKILL.md). Each entry has
|
||||
# `required` + `optional` field name lists. Adding a widget type means
|
||||
# editing widget-schema.json + implementing the Swift view + the JS
|
||||
# renderer; this file picks up the additions automatically.
|
||||
def _load_widget_schema() -> dict:
|
||||
schema_path = Path(__file__).resolve().parent / "widget-schema.json"
|
||||
with schema_path.open("r", encoding="utf-8") as f:
|
||||
schema = json.load(f)
|
||||
if schema.get("schemaVersion") != 1:
|
||||
raise SystemExit(f"unsupported widget-schema version: {schema.get('schemaVersion')}")
|
||||
widgets = schema.get("widgets") or {}
|
||||
if not isinstance(widgets, dict) or not widgets:
|
||||
raise SystemExit("widget-schema.json: 'widgets' must be a non-empty object")
|
||||
return widgets
|
||||
|
||||
WIDGET_SCHEMA = _load_widget_schema()
|
||||
SUPPORTED_WIDGET_TYPES = set(WIDGET_SCHEMA.keys())
|
||||
|
||||
# Mirror of Swift's TemplateConfigField.FieldType. Order matters only
|
||||
# for error messages that echo this set.
|
||||
@@ -423,6 +440,17 @@ def _validate_dashboard(zf: zipfile.ZipFile, template_dir: Path, errors: list[Va
|
||||
template_dir,
|
||||
f"dashboard widget {widget.get('title')!r} has unknown type {widget_type!r}"
|
||||
))
|
||||
continue
|
||||
spec = WIDGET_SCHEMA[widget_type]
|
||||
for required_field in spec.get("required", []):
|
||||
if required_field == "title":
|
||||
continue # validated implicitly by the title in the error message
|
||||
if widget.get(required_field) in (None, "", []):
|
||||
errors.append(ValidationError(
|
||||
template_dir,
|
||||
f"dashboard widget {widget.get('title')!r} (type {widget_type!r}) "
|
||||
f"missing required field {required_field!r}"
|
||||
))
|
||||
|
||||
|
||||
def _scan_for_secrets(zf: zipfile.ZipFile, template_dir: Path, errors: list[ValidationError]) -> None:
|
||||
|
||||
Reference in New Issue
Block a user