Files
LEDMatrix/docs/PLUGIN_CONFIGURATION_TABS.md
T
ChuckandClaude Opus 5.5 a231d4dbc7 chore: delete unreferenced scripts and archived docs; fix stale doc claims (#607)
* chore(scripts): delete unreferenced helper scripts

None of these is referenced by an installer, systemd unit, CI workflow,
test, the web UI or src/:

- utils/cleanup_venv.sh removes venv_web_v2, which nothing creates
- utils/clear_python_cache.sh hardcodes ~/LEDMatrix and a .webassets-cache
  nothing uses
- install/migrate_config.sh only copies the template, which the installer
  and ConfigManager already do
- install/debug_install.sh, debug/debug_web_manual.py
- diagnose_web_ui.sh and verify_web_ui.sh overlap diagnose_web_interface.sh,
  which the docs point to
- fix_internet_connectivity.sh is iptables-only (stale on nftables)
- diagnose_plugin_permissions.sh, dev/validate_python.py
- download_nba_logos.py + README_NBA_LOGOS.md: logo_downloader fetches
  logos on demand
- setup_plugin_repos.py linked into the production plugin-repos/ dir; the
  dev workflow is scripts/dev/dev_plugin_setup.sh, and
  MULTI_ROOT_WORKSPACE_SETUP.md now uses it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(config): drop unused plugin_system flags and a dead unit comment

- config.template.json: remove plugin_system.auto_discover,
  auto_load_enabled and development_mode. Nothing reads them; the web UI
  only stores them when a client sends them. ConfigManager's migration
  only adds template keys, so existing configs keep theirs unchanged.
- config.template.json: re-indent vegas_scroll's live_* keys.
- systemd/ledmatrix.service: remove the comment documenting
  LEDMATRIX_ON_DEMAND_PLUGIN / on_demand_env.conf; nothing reads either.
- CONFIG_REFERENCE.md: say the legacy keys are no longer in the template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: delete docs/archive and PLUGIN_IMPLEMENTATION_SUMMARY.md

- docs/archive/: superseded guides; the repository history keeps them
  and no live doc links into the directory. The one open document in it,
  WEB_UI_AUDIT_2026-09.md, moves to docs/audits/ and is linked from the
  docs index.
- PLUGIN_IMPLEMENTATION_SUMMARY.md invented usage statistics, called
  v2.0.0 current, listed shipped auto-updates as future work and
  documented a BasePlugin.get_config() that does not exist.
- docs/README.md: drop both, and stop telling contributors to archive
  obsolete pages instead of deleting them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plugin-api): fix extra_small_font size, cache metric key and scroll pacing example

- PLUGIN_API_REFERENCE: extra_small_font loads at 7, not 6 (crisp_size
  snaps it, src/display_manager.py); get_cache_metrics() returns
  cache_hit_rate, not hit_rate (src/cache/cache_metrics.py).
- ADVANCED_PLUGIN_DEVELOPMENT: the basic scrolling example slept in a loop
  and never passed frame_hold; use ScrollHelper + scroll_config.configure()
  and set_scrolling_state(True, frame_hold=...) as PLUGIN_API_REFERENCE does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(plugin-config): match the config tab, icon and web-action docs to the code

- PLUGIN_CONFIG_QUICK_START / PLUGIN_CONFIGURATION_TABS /
  PLUGIN_CONFIGURATION_GUIDE: there is no "Reset to Defaults" button (the
  tab has Refresh, Update, Uninstall, Save Configuration); plugin config
  hot-reloads (ConfigService + on_config_change), so no restart; the
  schema is found by the fixed name config_schema.json, not a manifest
  config_schema field; the tab row is "Plugin Manager", not "Plugins";
  forms are server-rendered from /v3/partials/plugin-config/<id>; the
  duration hook is get_display_duration()/display_duration; a class_name
  mismatch raises PluginError; the store requires id, name, class_name and
  display_modes (not version); plugin_system.debug/log_level do not exist
  (use run.py -d / LEDMATRIX_DEBUG). Drop "future" features that shipped.
- PLUGIN_CONFIG_CORE_PROPERTIES: list all of CORE_PLUGIN_PROPERTIES,
  including skin, skin_options and the vegas_* tuning keys.
- PLUGIN_CUSTOM_ICONS: icon is only a Font Awesome class (fallback
  fa-puzzle-piece); emoji/URL icons and getPluginIcon() never existed in
  v3. Note that /api/v3/plugins/installed currently omits icon.
- PLUGIN_WEB_UI_ACTIONS (+ example JSON): success_message, error_message
  and step1_message are never read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(store): describe the monorepo registry and the store UI as they are

- PLUGIN_STORE_GUIDE: the Plugin Store is a section of the Plugin
  Manager tab; URL installs are "Install from GitHub" -> "Install Single
  Plugin"; bulk update exists (Check & Update All) plus opt-in weekly
  auto-update; PluginStoreManager() defaults to plugins/, so the Python
  examples pass plugin-repos; registry plugins are downloaded (GitHub API,
  ZIP fallback), not cloned; updates compare version with latest_version.
- PLUGIN_REGISTRY_SETUP_GUIDE: replace the per-plugin-repo + tag
  walkthrough with a short page on the monorepo registry (plugin_path,
  latest_version, update_registry.py) that points at the monorepo's own
  SUBMISSION.md. Drops the reference to the deleted
  PLUGIN_IMPLEMENTATION_SUMMARY.md and setup_plugin_repos.py.
- plugin_registry_template.json: use the real entry shape.
- PLUGIN_QUICK_REFERENCE: automatic background updates exist (opt-in);
  registry example and publishing steps use the monorepo, not tags.
- PLUGIN_DEVELOPMENT_GUIDE: tags/releases are not read by the store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(readme): fix the Triple Bonnet mapping, install prerequisites and backup names

- README: the Adafruit Triple Bonnet uses `regular` (3 outputs), not
  `regular-pi1` (1 output) -- src/matrix_support.py MAPPING_OUTPUTS, and
  the README's own hardware_mapping section; the template default mapping
  is adafruit-hat, the PWM mod switches it to adafruit-hat-pwm; manual
  install only needs git up front (first_time_install.sh installs
  python-dev-is-python3, cmake, ninja-build etc.; cython3/scons are not
  used); the Pi Zero 2 W is a supported low-memory board, consistent with
  PRODUCT.md, LOW_MEMORY_BOARDS.md and the installer's low-memory build;
  fix the "First_time_install.sh" spelling, an orphan "2." list item and
  the hello-world starter link (it lives in the plugins monorepo).
- CONFIG_DEBUGGING: automatic backups are
  config/backups/config.json.backup.<YYYYMMDD_HHMMSS_ffffff> (five kept),
  not config_YYYYMMDD_HHMMSS.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(dev): correct the test-running and rgbmatrix build instructions

- HOW_TO_RUN_TESTS: coverage is not collected by a plain pytest run and
  pytest.ini has no threshold; the only one is --cov-fail-under=52 in the
  core unit-test job of .github/workflows/test.yml, which runs the whole
  test/ tree (not an allowlist). Almost no tests carry markers, so
  -m integration / -m slow select nothing; drop them and -m unit as the
  quick check. Replace the hardcoded /home/chuck path.
- DEVELOPMENT: the rgbmatrix package is built with pip install . from
  the submodule root (scikit-build-core + CMake + Ninja), as
  first_time_install.sh does; there is no make build-python /
  bindings/python step, and the build deps are python-dev-is-python3,
  cmake and ninja-build, not cython3/scons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(wifi): the setup AP is open; auto-enable can be turned off without code changes

- WIFI_NETWORK_SETUP / SSH_UNAVAILABLE_AFTER_INSTALL: both AP paths in
  src/wifi_manager.py create an open network and nothing reads
  ap_password, so drop the "ledmatrix123" password and the ap_password
  key/advice.
- SSH_UNAVAILABLE_AFTER_INSTALL: disabling automatic AP mode does not
  need code changes -- auto_enable_ap_mode is a WiFi-tab toggle and
  POST /api/v3/wifi/ap/auto-enable; note the monitor daemon reads
  wifi_config.json at start, so restart it after changing the setting.
  Use the ledpi username and a relative install path like the other docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(reference): add auto_update, drop drifted line numbers, fix UI and service details

- CONFIG_REFERENCE: document the top-level auto_update.enabled key (read
  by web_interface/auto_update.py and src/auto_update_setup.py); replace
  drifted file:line references with function names; the template's
  dim_schedule mode is "global".
- ADVANCED_FEATURES: core does not read a per-plugin background_service
  block (the sports plugins read their own), and priority is "higher
  number = higher priority" on FetchRequest but not used for ordering.
- WEB_INTERFACE_GUIDE: the General tab toggle is "Web Display Autostart"
  (web interface service), brightness is 1-100, and config paths are
  relative to the LEDMatrix folder, not /config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop references to code removed in #608

get_installed_plugin_info, WiFiManager's saved_networks and the six
always-skipping plugin test files are deleted there. NetworkManager already
remembers joined networks; LEDMatrix no longer stores WiFi passwords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: don't link SKIN_SYSTEM.md from the core-properties page

#615 deletes SKIN_SYSTEM.md; with this link, whichever of the two merged
second would break test_doc_links. The skin/skin_options entries go when
#615 removes the keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:36:38 -04:00

8.0 KiB

Plugin Configuration Tabs

Overview

Each installed plugin now gets its own dedicated configuration tab in the web interface. This provides a clean, organized way to configure plugins without cluttering the Plugin Manager tab.

Features

  • Automatic Tab Generation: When a plugin is installed, a new tab is automatically created in the web UI
  • JSON Schema-Based Forms: Configuration forms are automatically generated based on each plugin's config_schema.json
  • Type-Safe Inputs: Form inputs are created based on the JSON Schema type (boolean, number, string, array, enum)
  • Default Values: All fields show current values or fallback to schema defaults
  • Real-Time Validation: Input constraints from JSON Schema are enforced (min, max, maxLength, etc.)

User Experience

Accessing Plugin Configuration

  1. Navigate to the Plugin Manager tab to see all installed plugins
  2. Click the Configure button on any plugin card
  3. You'll be automatically taken to that plugin's configuration tab
  4. Alternatively, click directly on the plugin's tab button in the second nav row

Configuring a Plugin

  1. Open the plugin's configuration tab
  2. Modify settings using the generated form
  3. Click Save Configuration. The settings apply to the running display without a restart: the display service reloads config.json when it changes and calls the plugin's on_config_change()

The tab also has Refresh (reload the form), Update (update the plugin) and Uninstall buttons.

Plugin Manager vs Per-Plugin Configuration

  • Plugin Manager tab (second nav row): used for browsing the Plugin Store, installing plugins, toggling installed plugins on/off, and updating/uninstalling them
  • Per-plugin tabs (one per installed plugin, also in the second nav row): used for configuring that specific plugin's behavior and settings via a form auto-generated from its config_schema.json

For Plugin Developers

Requirements

Every installed plugin gets a tab. To get a generated form in it, include a config_schema.json file in the plugin's directory. The name is fixed: the web interface finds the schema by that file name (SchemaManager in src/plugin_system/schema_manager.py), and no manifest field points to it.

Note: You can optionally specify a Font Awesome icon class for your plugin tab in manifest.json. See Plugin Custom Icons Guide for details.

Supported JSON Schema Types

The form generator supports the following JSON Schema types:

Boolean

{
  "type": "boolean",
  "default": true,
  "description": "Enable or disable this feature"
}

Renders as: Toggle switch

Number / Integer

{
  "type": "integer",
  "default": 60,
  "minimum": 1,
  "maximum": 300,
  "description": "Update interval in seconds"
}

Renders as: Number input with min/max constraints

String

{
  "type": "string",
  "default": "Hello, World!",
  "minLength": 1,
  "maxLength": 50,
  "description": "The message to display"
}

Renders as: Text input with length constraints

Array

{
  "type": "array",
  "items": {
    "type": "integer",
    "minimum": 0,
    "maximum": 255
  },
  "minItems": 3,
  "maxItems": 3,
  "default": [255, 255, 255],
  "description": "RGB color [R, G, B]"
}

Renders as: Text input (comma-separated values)
Example input: 255, 128, 0

Enum (Select)

{
  "type": "string",
  "enum": ["small", "medium", "large"],
  "default": "medium",
  "description": "Display size"
}

Renders as: Dropdown select

Example config_schema.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "title": "My Plugin Configuration",
  "description": "Configure my awesome plugin",
  "properties": {
    "enabled": {
      "type": "boolean",
      "default": true,
      "description": "Enable or disable this plugin"
    },
    "message": {
      "type": "string",
      "default": "Hello!",
      "minLength": 1,
      "maxLength": 50,
      "description": "The message to display"
    },
    "update_interval": {
      "type": "integer",
      "default": 60,
      "minimum": 1,
      "maximum": 3600,
      "description": "Update interval in seconds"
    },
    "color": {
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 0,
        "maximum": 255
      },
      "minItems": 3,
      "maxItems": 3,
      "default": [255, 255, 255],
      "description": "RGB color [R, G, B]"
    },
    "mode": {
      "type": "string",
      "enum": ["scroll", "static", "fade"],
      "default": "scroll",
      "description": "Display mode"
    }
  },
  "required": ["enabled"],
  "additionalProperties": false
}

Best Practices

  1. Use Descriptive Labels: The description field is shown as help text under each input
  2. Set Sensible Defaults: Always provide default values that work out of the box
  3. Use Constraints: Leverage min/max, minLength/maxLength to guide users
  4. Mark Required Fields: Use the required array in your schema
  5. Organize Properties: List properties in order of importance

Form Generation Process

Forms are rendered on the server, not generated in the browser:

  1. The web UI loads installed plugins via /api/v3/plugins/installed and adds a tab button for each one
  2. Opening a tab loads /v3/partials/plugin-config/<plugin_id> (web_interface/blueprints/pages_v3.py), which loads the plugin's schema through SchemaManager and its current values from config.json
  3. web_interface/templates/v3/partials/plugin_config.html renders the form from the schema (widgets named by x-widget are rendered by the scripts in web_interface/static/v3/js/widgets/)
  4. Save Configuration posts the form to /api/v3/plugins/config (web_interface/blueprints/api_v3/plugins.py), which validates it against the schema, writes config.json (secret fields go to config_secrets.json) and shows a notification

Troubleshooting

Plugin Tab Not Appearing

  • Check that the plugin is installed and appears in the Plugin Manager tab
  • Check browser console for errors
  • Reload the page

Form Not Generating Correctly

  • Ensure config_schema.json exists in the plugin directory
  • Validate your config_schema.json against JSON Schema Draft 07
  • Check that all properties have a type field
  • Ensure default values match the specified type
  • Look for JavaScript errors in browser console

Configuration Not Saving

  • Ensure the plugin is properly installed
  • Check that config keys match schema properties
  • Verify backend API is accessible
  • Check browser network tab for API errors

Migration Guide

For Existing Plugins

If your plugin already has a config_schema.json:

  1. No changes needed! The tab will be automatically generated.
  2. Test the generated form to ensure all fields render correctly.
  3. Consider adding more descriptive description fields.

If your plugin doesn't have a config schema:

  1. Create config_schema.json based on your current config structure
  2. Add descriptions for each property
  3. Set appropriate defaults
  4. Add validation constraints (min, max, etc.)

Backward Compatibility

  • Plugins without config_schema.json still work normally
  • Their tab shows plain text, number and checkbox inputs for the keys already in their config.json section, or "No configuration options available for this plugin." when there are none
  • Users can still edit config via the Raw JSON editor

Beyond the Basic Types

Nested objects (rendered as collapsible sections), x-widget widgets such as color-picker and file-upload, and more are supported; see PLUGIN_CONFIGURATION_GUIDE.md and web_interface/static/v3/js/widgets/README.md.

Example Plugins

See these plugins for examples of config schemas:

  • hello-world: Simple plugin with basic types
  • clock-simple: Plugin with enum and number types

Support

For questions or issues:

  • Check the main LEDMatrix wiki
  • Review plugin documentation
  • Open an issue on GitHub
  • Join the community Discord