Files
LEDMatrix/schema/manifest_schema.json
T
ChuckandClaude Opus 5 a694b23cd1 feat(web): serve plugin-declared widgets, and actually ask for them
LEDMatrixWidgets.loadPluginWidget has always fetched
/static/plugin-widgets/<plugin>/<widget>.js, and docs/widget-guide.md has
always documented that path, but nothing served it. soccer-scoreboard has
shipped a 17KB widgets/custom-leagues.js since August that could never
load. Both halves were missing, not just the route:

- serve_plugin_widget serves the script from the plugin's widgets/
  directory as text/javascript. The manifest is the allowlist -- only a
  widget the plugin declares is reachable -- so installing a plugin does
  not publish everything it ships. Path handling mirrors the sibling
  serve_plugin_web_ui: allowlist regexes, os.path.basename, resolve() +
  relative_to() containment, and the ledmatrix- prefix fallback. The
  declared script name is guarded too, since it comes from the plugin
  rather than the request.

- The config form never requested one. Its x-widget dispatch is a
  hardcoded list of core widget names, so a plugin's own widget fell
  through to a plain text input. An unrecognised x-widget on a string
  field now asks ensureWidget() for it. The text input stays as the
  fallback and is removed only once the widget has actually rendered, so
  a missing or broken widget costs the user an editor rather than their
  configured value on the next save.

- manifest_schema.json gains "widgets", so the declaration is validated
  rather than merely tolerated by additionalProperties.

Verified in a browser against the real partial: a declared widget loads,
registers and renders, and its field posts exactly one value; a field
whose widget 404s keeps its text input and still posts its value.

Not addressed: loadPluginWidgetsFromManifest still has no caller. The
per-field ensureWidget path is lazier and is what the form now uses, so
that bulk helper is dead weight -- worth removing, but left alone here
rather than inventing a call site for it.

Known limitation, documented: only string-typed fields take this path.
object/array/boolean/number fields and enums are dispatched by the
template's own branches, which still only know core widgets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 10:34:51 -04:00

292 lines
8.4 KiB
JSON

{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Plugin Manifest Schema",
"description": "Schema for validating plugin manifest.json files",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique plugin identifier"
},
"name": {
"type": "string",
"description": "Human-readable plugin name"
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
"description": "Plugin version (semver format)"
},
"author": {
"type": "string",
"description": "Plugin author name"
},
"description": {
"type": "string",
"description": "Brief description of what the plugin does"
},
"homepage": {
"type": "string",
"format": "uri",
"description": "Plugin homepage URL"
},
"entry_point": {
"type": "string",
"default": "manager.py",
"description": "Python file containing the plugin class"
},
"class_name": {
"type": "string",
"description": "Name of the plugin class"
},
"category": {
"type": "string",
"description": "Plugin category (e.g., 'sports', 'weather', 'custom')"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Array of tags for categorization"
},
"icon": {
"type": "string",
"description": "Icon identifier (e.g., Font Awesome class)"
},
"compatible_versions": {
"type": "array",
"items": {
"type": "string",
"pattern": "^(>=|<=|>|<|~|\\^)?\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$|^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?\\s+-\\s+\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$"
},
"minItems": 1,
"description": "Array of semver ranges specifying compatible LEDMatrix versions (required)"
},
"min_ledmatrix_version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
"description": "Optional: Minimum LEDMatrix version required (string)"
},
"max_ledmatrix_version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
"description": "Optional: Maximum LEDMatrix version supported (string)"
},
"requires": {
"type": "object",
"properties": {
"python": {
"type": "string",
"description": "Python version requirement (e.g., '>=3.9')"
},
"display_size": {
"type": "object",
"properties": {
"min_width": {
"type": "integer",
"minimum": 1
},
"min_height": {
"type": "integer",
"minimum": 1
},
"max_width": {
"type": "integer",
"minimum": 1
},
"max_height": {
"type": "integer",
"minimum": 1
}
}
}
}
},
"display": {
"type": "object",
"properties": {
"design_size": {
"type": "object",
"properties": {
"width": {
"type": "integer",
"minimum": 8
},
"height": {
"type": "integer",
"minimum": 8
}
},
"required": ["width", "height"],
"description": "Panel size the plugin's layout was authored against; core derives the adaptive-layout scale factor from it. Defaults to 128x32 when omitted."
}
},
"description": "Display/layout hints for the adaptive layout system"
},
"config_schema": {
"type": "string",
"description": "Path to configuration schema file"
},
"assets": {
"type": "object",
"description": "Asset requirements description"
},
"update_interval": {
"type": "integer",
"minimum": 1,
"description": "Update interval in seconds"
},
"default_duration": {
"type": "number",
"minimum": 1,
"description": "Default display duration in seconds"
},
"display_modes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Array of display mode names this plugin provides"
},
"api_requirements": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"required": {
"type": "boolean"
},
"description": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
},
"rate_limit": {
"type": "string"
}
}
}
},
"download_url_template": {
"type": "string",
"format": "uri",
"description": "URL template for downloading plugin releases (use {version} placeholder)"
},
"versions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"released": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Release date (YYYY-MM-DD format)"
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
"description": "Plugin version for this entry"
},
"ledmatrix_min_version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
"description": "Optional: Minimum LEDMatrix version required for this plugin version (string)"
}
},
"required": ["released", "version"]
},
"description": "Array of version history entries"
},
"last_updated": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Last update date (YYYY-MM-DD format)"
},
"stars": {
"type": "integer",
"minimum": 0,
"description": "Number of stars (for registry)"
},
"downloads": {
"type": "integer",
"minimum": 0,
"description": "Number of downloads (for registry)"
},
"verified": {
"type": "boolean",
"description": "Whether plugin is verified (for registry)"
},
"screenshot": {
"type": "string",
"description": "Screenshot URL (for registry)"
},
"api_version": {
"type": "string",
"description": "API version (legacy field)"
},
"dependencies": {
"type": "array",
"items": {
"type": "string"
},
"description": "Python dependencies (legacy - use requirements.txt instead)"
},
"requirements_file": {
"type": "string",
"description": "Path to requirements.txt file"
},
"website": {
"type": "string",
"format": "uri",
"description": "Plugin website URL (alias for homepage)"
},
"license": {
"type": "string",
"description": "License type (e.g., 'MIT', 'Apache-2.0')"
},
"web_ui_actions": {
"type": "array",
"items": {
"type": "object"
},
"description": "Web UI action definitions"
},
"widgets": {
"type": "array",
"description": "Custom web-UI widgets this plugin provides. Each entry is served at /static/plugin-widgets/<plugin-id>/<name>.js from the plugin's widgets/ directory; only declared widgets are served. Reference one from config_schema.json with \"x-widget\": \"<name>\".",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]{1,64}$",
"description": "Widget name, as used in x-widget and in the URL."
},
"script": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]{1,64}\\.js$",
"description": "Filename inside widgets/. Defaults to <name>.js."
},
"description": {
"type": "string",
"description": "Human-readable summary shown to plugin authors."
}
}
}
},
"ledmatrix_version": {
"type": "string",
"description": "Deprecated: Use compatible_versions instead. LEDMatrix version this plugin targets"
}
},
"required": ["id", "name", "version", "author", "entry_point", "class_name", "compatible_versions"]
}