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>