diff --git a/README.md b/README.md
index 5fddfaf9..7ae97dd0 100644
--- a/README.md
+++ b/README.md
@@ -140,8 +140,7 @@ The system supports live, recent, and upcoming game information for multiple spo
| This project can be finnicky! RGB LED Matrix displays are not built the same or to a high-quality standard. We have seen many displays arrive dead or partially working in our discord. Please purchase from a reputable vendor. |
### Raspberry Pi
-- Raspberry Pi Zero's don't have enough processing power for this project.
-- **Raspberry Pi 3B, 4, or 5**
+- **Raspberry Pi 3B, 4, or 5** (a Pi Zero 2 W also works, with the limits described under the 1GB/low-memory bullet below; the original Pi Zero / Zero W doesn't have enough processing power for this project)
[Amazon Affiliate Link – Raspberry Pi 4 4GB RAM](https://amzn.to/4dJixuX)
[Amazon Affiliate Link – Raspberry Pi 4 8GB RAM](https://amzn.to/4qbqY7F)
- **Pi 5 users**: the installer automatically detects Pi 5 and builds the `rpi-rgb-led-matrix` library with RP1 support. If you previously installed on a Pi 4 and migrated the SD card, or if you see `mmap` errors in the logs, force a fresh library build:
@@ -149,12 +148,12 @@ The system supports live, recent, and upcoming game information for multiple spo
sudo RPI_RGB_FORCE_REBUILD=1 ./first_time_install.sh
```
- Pi 5 config: leave `rp1_rio` at `0` (PIO mode, default) and start `gpio_slowdown` at `1`, raising it a step at a time if the image flickers or shows garbage (see `gpio_slowdown` under Display Settings).
- - **1GB models (Pi 3B / 3B+) and other low-memory boards**: supported, but the `rpi-rgb-led-matrix` C++ build needs more memory than the Pi has. The installer detects this automatically, compiles with fewer parallel jobs, and adds a temporary swapfile for the build which it removes afterwards. Expect that step to take 15-25 minutes instead of 2-5, and leave at least **3GB free** on the SD card. If you manage swap yourself, opt out with `--skip-swap`. To pin the compiler down further, use `--build-jobs 1`.
+ - **1GB models (Pi 3B / 3B+), the 512MB Pi Zero 2 W and other low-memory boards**: supported, but the `rpi-rgb-led-matrix` C++ build needs more memory than the Pi has. The installer detects this automatically, compiles with fewer parallel jobs, and adds a temporary swapfile for the build which it removes afterwards. Expect that step to take 15-25 minutes instead of 2-5, and leave at least **3GB free** on the SD card. If you manage swap yourself, opt out with `--skip-swap`. To pin the compiler down further, use `--build-jobs 1`. Once running, keep an eye on memory: see [docs/LOW_MEMORY_BOARDS.md](docs/LOW_MEMORY_BOARDS.md).
### RGB Matrix Bonnet / HAT
- [Adafruit RGB Matrix Bonnet/HAT](https://www.adafruit.com/product/3211) – supports one “chain” of horizontally connected displays
-- [Adafruit Triple LED Matrix Bonnet](https://www.adafruit.com/product/6358) – supports up to 3 vertical “chains” of horizontally connected displays *(use `regular-pi1` as hardware mapping)*
+- [Adafruit Triple LED Matrix Bonnet](https://www.adafruit.com/product/6358) – supports up to 3 vertical “chains” of horizontally connected displays *(use `regular` as hardware mapping)*
- [Electrodragon RGB HAT](https://www.electrodragon.com/product/rgb-matrix-panel-drive-board-raspberry-pi/) – supports up to 3 vertical “chains”
- [Seengreat Matrix Adapter Board](https://amzn.to/3KsnT3j) – single-chain LED Matrix *(use `regular` as hardware mapping)*
@@ -173,7 +172,7 @@ The system supports live, recent, and upcoming game information for multiple spo
## Optional but recommended mod for Adafruit RGB Matrix Bonnet
- By soldering a jumper between pins 4 and 18, you can run a specialized command for polling the matrix display. This provides better brightness, less flicker, and better color.
-- If you do the mod, we will use the default config with led-gpio-mapping=adafruit-hat-pwm, otherwise just adjust your mapping in config.json to adafruit-hat
+- The default config uses `hardware_mapping` `adafruit-hat`. If you do the mod, change it to `adafruit-hat-pwm` (Display settings in the web interface, or `config.json`)
- More information available: https://github.com/hzeller/rpi-rgb-led-matrix/tree/master?tab=readme-ov-file

@@ -347,10 +346,10 @@ If you prefer to install manually or the one-shot installer doesn't work for you
ssh ledpi@ledpi
```
-2. Update repositories, upgrade Raspberry Pi OS, and install prerequisites:
+2. Update repositories, upgrade Raspberry Pi OS, and install git (`first_time_install.sh` installs the build dependencies itself: `python3-pip`, `python-dev-is-python3`, `build-essential`, `cmake`, `ninja-build` and the rest):
```bash
sudo apt update && sudo apt upgrade -y
-sudo apt install -y git python3-pip cython3 build-essential python3-dev python3-pillow scons
+sudo apt install -y git
```
3. Clone this repository:
@@ -400,7 +399,7 @@ If you need to manually edit your config file, you can follow the steps below:
Manual Config.json editing
1. **First-time setup**:
- The previous "First_time_install.sh" script should've already copied the template to create your config.json:
+ The previous `first_time_install.sh` script should've already copied the template to create your config.json:
2. **Edit your configuration**:
```bash
@@ -459,9 +458,9 @@ You can also install plugins directly from GitHub repositories:
See the [Plugin Store documentation](https://github.com/ChuckBuilds/ledmatrix-plugins) for detailed installation instructions.
-For plugin development, check out the [Hello World Plugin](https://github.com/ChuckBuilds/ledmatrix-hello-world) repository as a starter template.
+For plugin development, the `plugins/hello-world/` plugin in the [ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins) repository is a starter template.
-2. **Built-in Managers Deprecated**: The built-in managers (hockey, football, stocks, etc.) are now deprecated and have been moved to the plugin system. **You must install replacement plugins from the Plugin Store** in the web interface instead. The plugin system provides the same functionality with better maintainability and extensibility.
+**Built-in Managers Deprecated**: The built-in managers (hockey, football, stocks, etc.) are now deprecated and have been moved to the plugin system. **You must install replacement plugins from the Plugin Store** in the web interface instead. The plugin system provides the same functionality with better maintainability and extensibility.
## Detailed Information
diff --git a/config/config.template.json b/config/config.template.json
index f48ad5f5..96a0c589 100644
--- a/config/config.template.json
+++ b/config/config.template.json
@@ -133,9 +133,9 @@
"plugin_rotation_order": [],
"use_short_date_format": true,
"vegas_scroll": {
- "live_in_ticker": false,
- "live_weight": 3,
- "favorite_live_weight": 5,
+ "live_in_ticker": false,
+ "live_weight": 3,
+ "favorite_live_weight": 5,
"enabled": false,
"scroll_speed": 50,
"separator_width": 32,
@@ -171,10 +171,7 @@
"follower_position": "left"
},
"plugin_system": {
- "plugins_directory": "plugin-repos",
- "auto_discover": true,
- "auto_load_enabled": true,
- "development_mode": false
+ "plugins_directory": "plugin-repos"
},
"web-ui-info": {
"enabled": true,
diff --git a/docs/ADVANCED_FEATURES.md b/docs/ADVANCED_FEATURES.md
index 7506bc8b..5a9deb07 100644
--- a/docs/ADVANCED_FEATURES.md
+++ b/docs/ADVANCED_FEATURES.md
@@ -886,7 +886,13 @@ Cache Check → Background Fetch → Partial Data → Completion → Cache
### Configuration
-Enable background service per plugin in `config/config.json`:
+Core does not read a `background_service` config block: the service itself
+(`src/background_data_service.py`) is a process-wide singleton, and its
+worker count is whatever the first caller of `get_background_service()`
+passes. The sports scoreboard plugins read their own
+`background_service` settings and pass them to it, so the exact keys and
+where they sit (top level or per league) are defined by each plugin's
+`config_schema.json`. A typical block looks like:
```json
{
@@ -907,11 +913,11 @@ Enable background service per plugin in `config/config.json`:
| Setting | Default | Description |
|---------|---------|-------------|
-| `enabled` | `false` | Enable background service for this plugin |
+| `enabled` | plugin-defined | Use the background service for this plugin's fetches |
| `max_workers` | `3` | Max concurrent background tasks |
| `request_timeout` | `30` | Timeout per API request (seconds) |
| `max_retries` | `3` | Retry attempts on failure |
-| `priority` | `1` | Task priority (1=highest, 10=lowest) |
+| `priority` | `1` | Stored on each request (higher number = higher priority, per `FetchRequest`), but the service runs requests in submission order; it does not reorder by priority |
### Performance Impact
@@ -928,9 +934,9 @@ Enable background service per plugin in `config/config.json`:
The background data service is used by all of the sports scoreboard
plugins (football, hockey, baseball/MLB, basketball, soccer, lacrosse,
-F1, UFC), the odds ticker, and the leaderboard plugin. Each plugin's
-`background_service` block (under its own config namespace) follows the
-same shape as the example above.
+F1, UFC), the odds ticker, and the leaderboard plugin. Each plugin reads
+its own `background_service` block (under its own config namespace); check
+that plugin's `config_schema.json` for the keys it accepts.
### Error Handling & Fallback
diff --git a/docs/ADVANCED_PLUGIN_DEVELOPMENT.md b/docs/ADVANCED_PLUGIN_DEVELOPMENT.md
index eddeb388..b92e1649 100644
--- a/docs/ADVANCED_PLUGIN_DEVELOPMENT.md
+++ b/docs/ADVANCED_PLUGIN_DEVELOPMENT.md
@@ -97,31 +97,53 @@ For plugins that scroll content (tickers, news feeds, etc.), use scrolling state
### Basic Scrolling Implementation
+Scroll with `ScrollHelper`, configured by `src.common.scroll_config`, and
+render one frame per `display()` call. Don't pace the scroll with
+`time.sleep()`: `update_display()` blocks on the panel's
+vsync, which is what paces a scroll. Pass the `frame_hold` that
+`scroll_config.configure()` returned to `set_scrolling_state()`, or the
+scroll runs faster than the configured speed (see
+`set_scrolling_state()` in [PLUGIN_API_REFERENCE.md](PLUGIN_API_REFERENCE.md)).
+
```python
+from PIL import Image, ImageDraw
+
+from src.common import scroll_config
+from src.common.scroll_helper import ScrollHelper
+
+def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.scroll_helper = ScrollHelper(
+ self.display_manager.width, self.display_manager.height, self.logger)
+ self.scroll_settings = scroll_config.configure(
+ self.scroll_helper,
+ plugin_config=self.config,
+ global_config=self.global_config,
+ display_manager=self.display_manager,
+ plugin_logger=self.logger,
+ )
+
+def _build_scroll_image(self, text):
+ font = self.display_manager.regular_font
+ width = self.display_manager.get_text_width(text, font)
+ img = Image.new("RGB", (width, self.display_manager.height))
+ ImageDraw.Draw(img).text((0, 0), text, font=font, fill=(255, 255, 255))
+ self.scroll_helper.set_scrolling_image(img)
+
def display(self, force_clear=False):
- if force_clear:
- self.display_manager.clear()
-
- # Mark as scrolling
- self.display_manager.set_scrolling_state(True)
-
- try:
- # Scroll content
- text = "This is a long scrolling message that needs to scroll across the display..."
- text_width = self.display_manager.get_text_width(text, self.display_manager.regular_font)
- display_width = self.display_manager.width
-
- # Scroll from right to left
- for x in range(display_width, -text_width, -2):
- self.display_manager.clear()
- self.display_manager.draw_text(text, x=x, y=16, color=(255, 255, 255))
- self.display_manager.update_display()
- time.sleep(0.05)
-
- # Update scroll activity timestamp
- self.display_manager.set_scrolling_state(True)
- finally:
- # Always mark as not scrolling when done
+ if force_clear or self.scroll_helper.cached_image is None:
+ self._build_scroll_image(
+ "This is a long scrolling message that needs to scroll across the display...")
+
+ # Mark as scrolling (calling it every frame is fine)
+ self.display_manager.set_scrolling_state(
+ True, frame_hold=self.scroll_settings.frame_hold)
+ self.scroll_helper.update_scroll_position()
+ self.display_manager.image = self.scroll_helper.get_visible_portion()
+ self.display_manager.update_display()
+
+ if self.scroll_helper.is_scroll_complete():
+ # Mark as not scrolling when done
self.display_manager.set_scrolling_state(False)
```
diff --git a/docs/CONFIG_DEBUGGING.md b/docs/CONFIG_DEBUGGING.md
index 4adae1fb..01b41224 100644
--- a/docs/CONFIG_DEBUGGING.md
+++ b/docs/CONFIG_DEBUGGING.md
@@ -292,9 +292,12 @@ cp config/config.json config/config.backup.json
### Automatic Backups
-LEDMatrix creates backups before saves:
+LEDMatrix creates backups before saves (`src/config_manager_atomic.py`):
- Location: `config/backups/`
-- Format: `config_YYYYMMDD_HHMMSS.json`
+- Format: `config.json.backup.YYYYMMDD_HHMMSS_ffffff` (microseconds last),
+ plus a matching `config_secrets.json.backup.` when a secrets
+ file exists
+- The five most recent are kept
### Recovery
@@ -303,7 +306,7 @@ LEDMatrix creates backups before saves:
ls -la config/backups/
# Restore from backup
-cp config/backups/config_20240115_120000.json config/config.json
+cp config/backups/config.json.backup.20240115_120000_000000 config/config.json
```
## Troubleshooting Checklist
diff --git a/docs/CONFIG_REFERENCE.md b/docs/CONFIG_REFERENCE.md
index a650227b..58eb7df7 100644
--- a/docs/CONFIG_REFERENCE.md
+++ b/docs/CONFIG_REFERENCE.md
@@ -16,6 +16,7 @@ tooling against it.
| Key | Type / default | Meaning | Read by |
|---|---|---|---|
| `web_display_autostart` | bool, `true` | Whether the web interface service starts with the system | `scripts/utils/start_web_conditionally.py` |
+| `auto_update.enabled` | bool, `false` | Weekly automatic updates: LEDMatrix code first (health-checked, rolled back on failure), then installed plugins. Toggle in the General tab or install with `first_time_install.sh --enable-auto-update` | `web_interface/auto_update.py`, `src/auto_update_setup.py` (`is_enabled()`) |
| `timezone` | string, `"America/New_York"` | IANA timezone for schedules and displays | `ConfigManager.get_timezone()` |
| `target_fps` | int, `100` | Legacy "Scroll Frame Rate". Core scrolling no longer reads it: scroll frames are presented at `display.hardware.limit_refresh_rate_hz` divided by each scroll's frame hold, and speed comes from each plugin's scroll settings. Still exposed to plugins via `BasePlugin.global_config` | `src/plugin_system/base_plugin.py` |
| `location` | object | `city` / `state` / `country`. Supplies the **default** for a plugin's own `location_city` / `location_state` / `location_country` setting, so weather, radar and friends follow this device without being configured twice. A value saved on the plugin itself still overrides it. | `SchemaManager.apply_device_location()`, then plugins via merged config |
@@ -29,18 +30,18 @@ tooling against it.
| `start_time` / `end_time` | `"HH:MM"`, `07:00`–`23:00` | Global-mode on/off times |
| `days..{enabled,start_time,end_time}` | per-day objects | Per-day-mode overrides |
-Read by `DisplayController` (`src/display_controller.py`, `_check_schedule`
-around line 603). Managed in the web UI under Schedule.
+Read by `DisplayController._check_schedule()` (`src/display_controller.py`).
+Managed in the web UI under Schedule.
## `dim_schedule` — scheduled brightness dimming
-Same shape as `schedule`, plus:
+Same shape as `schedule` (the template sets its `mode` to `"global"`), plus:
| Key | Type / default | Meaning |
|---|---|---|
| `dim_brightness` | int, `30` | Brightness percentage applied while the dim window is active |
-Read by `DisplayController` (`src/display_controller.py` around line 770;
+Read by `DisplayController._check_dim_schedule()` (`src/display_controller.py`;
saved via `POST /api/v3/config/dim-schedule`). The display returns to
`display.hardware.brightness` outside the window.
@@ -101,10 +102,10 @@ logical image to multiple chained physical panels.
| Key | Type / default | Meaning | Read by |
|---|---|---|---|
-| `display_durations` | object, `{}` | Per-plugin display duration in seconds, keyed by plugin id (e.g. `"clock": 15`) | `src/display_controller.py:1030` |
-| `plugin_rotation_order` | array, `[]` | Explicit rotation order of plugin ids; empty = all enabled plugins in discovery order | `src/display_controller.py:2894` |
+| `display_durations` | object, `{}` | Per-plugin display duration in seconds, keyed by plugin id (e.g. `"clock": 15`) | `DisplayController._get_display_duration()` (`src/display_controller.py`) |
+| `plugin_rotation_order` | array, `[]` | Explicit rotation order of plugin ids; empty = all enabled plugins in discovery order | `DisplayController._apply_plugin_rotation_order()` (`src/display_controller.py`) |
| `use_short_date_format` | bool, `true` | Compact date rendering in sports scoreboards | Nothing since `src/base_classes` was removed; scoreboards read `display.use_short_date_format` from their own plugin config |
-| `dynamic_duration.max_duration_seconds` | int, optional | Cap for plugins that request dynamic display time | `src/display_controller.py:405` |
+| `dynamic_duration.max_duration_seconds` | int, optional | Cap for plugins that request dynamic display time | `DisplayController._get_global_dynamic_cap()` (`src/display_controller.py`) |
## `display.vegas_scroll` — continuous scroll mode
@@ -153,16 +154,14 @@ Read by `src/common/sync_manager.py` and `src/display_controller.py`.
|---|---|---|
| `role` | `"standalone"` (default), `"leader"`, or `"follower"` | This device's role in a synced pair |
| `port` | int, `5765` | TCP port used for sync traffic |
-| `follower_position` | `"left"` (default) or `"right"` | Which half of the combined image this follower renders (`src/display_controller.py:522`) |
+| `follower_position` | `"left"` (default) or `"right"` | Which half of the combined image this follower renders (`src/display_controller.py`) |
## `plugin_system`
| Key | Type / default | Meaning |
|---|---|---|
| `plugins_directory` | string, `"plugin-repos"` | Where the Plugin Store installs plugins and the only directory the plugin loader scans. Read by `PluginManager` and `PluginStoreManager` (`src/plugin_system/`); editable under General settings |
-| `auto_discover` | bool, `true` | **Unused.** Legacy key, read by nothing. Plugins are always discovered, and every plugin with `enabled: true` is loaded. Not shown in the web UI; may be left in or removed from config.json |
-| `auto_load_enabled` | bool, `true` | **Unused.** Legacy key, read by nothing (see `auto_discover`). To keep a plugin installed but dormant, set its own `enabled` to `false` |
-| `development_mode` | bool, `false` | **Unused.** Legacy key, read by nothing |
+| `auto_discover`, `auto_load_enabled`, `development_mode` | bool | **Unused.** Legacy keys, read by nothing and no longer in the template; older configs may still carry them. Plugins are always discovered, and every plugin with `enabled: true` is loaded — to keep a plugin installed but dormant, set its own `enabled` to `false`. Not shown in the web UI; may be left in or removed from config.json |
## Plugin config blocks
@@ -176,5 +175,5 @@ See [PLUGIN_CONFIG_CORE_PROPERTIES.md](PLUGIN_CONFIG_CORE_PROPERTIES.md).
| Key | Meaning |
|---|---|
-| `github.api_token` | Optional GitHub token the Plugin Store uses to avoid API rate limits (`src/plugin_system/store_manager.py:348`) |
+| `github.api_token` | Optional GitHub token the Plugin Store uses to avoid API rate limits (`src/plugin_system/store_manager.py`) |
| `.*` | Secrets a plugin declares with `"x-secret": true` in its config schema; merged into that plugin's config at load time |
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
index 0382980b..ee09f0b4 100644
--- a/docs/DEVELOPMENT.md
+++ b/docs/DEVELOPMENT.md
@@ -43,16 +43,21 @@ git submodule update --init --recursive rpi-rgb-led-matrix-master
#### Building the Submodule
-After initializing the submodule, you need to build the Python bindings:
+After initializing the submodule, build and install the `rgbmatrix` Python
+package from the submodule root. Upstream's `pyproject.toml` builds it with
+scikit-build-core, CMake and Ninja; there is no separate `make` step:
```bash
cd rpi-rgb-led-matrix-master
-make build-python
-cd bindings/python
python3 -m pip install --break-system-packages .
```
-**Note:** The `first_time_install.sh` script automates this process during installation.
+On a board with 1 GB of RAM or less, cap the compile so it doesn't run out of
+memory: `CMAKE_BUILD_PARALLEL_LEVEL=1 python3 -m pip install --break-system-packages .`
+
+**Note:** The `first_time_install.sh` script automates this process during
+installation, including the parallelism cap and a temporary swapfile on
+low-memory boards.
#### Troubleshooting
@@ -69,7 +74,7 @@ git submodule update --init --recursive rpi-rgb-led-matrix-master
**Build fails:**
Ensure you have the required build dependencies installed:
```bash
-sudo apt install -y build-essential python3-dev cython3 scons
+sudo apt install -y build-essential python-dev-is-python3 cmake ninja-build
```
**Import error for `rgbmatrix` module:**
@@ -97,8 +102,6 @@ When setting up CI/CD pipelines, ensure submodules are initialized before buildi
- name: Build rpi-rgb-led-matrix
run: |
cd rpi-rgb-led-matrix-master
- make build-python
- cd bindings/python
pip install .
```
@@ -110,8 +113,6 @@ variables:
build:
script:
- cd rpi-rgb-led-matrix-master
- - make build-python
- - cd bindings/python
- pip install .
```
diff --git a/docs/HOW_TO_RUN_TESTS.md b/docs/HOW_TO_RUN_TESTS.md
index db1cef12..636a53b0 100644
--- a/docs/HOW_TO_RUN_TESTS.md
+++ b/docs/HOW_TO_RUN_TESTS.md
@@ -60,20 +60,18 @@ pytest test/test_display_controller.py::TestDisplayControllerModeRotation::test_
### Run Tests by Marker
-The tests use markers to categorize them:
+`pytest.ini` declares the markers `unit`, `integration`, `hardware`, `slow`
+and `plugin` (with `--strict-markers`, so a typo in a marker name is an
+error). Few tests are marked: only a handful carry `unit`, and none currently
+carry `integration`, `slow` or `hardware`, so `-m integration` and `-m slow`
+select nothing. Select tests by file, directory or `-k` instead.
```bash
-# Run only unit tests (fast, isolated)
-pytest -m unit
+# What CI runs for the core suites (excludes anything marked hardware)
+pytest -m "not hardware" test/ --ignore=test/plugins
-# Run only integration tests
-pytest -m integration
-
-# Run tests that don't require hardware
-pytest -m "not hardware"
-
-# Run slow tests
-pytest -m slow
+# Tests whose name matches an expression
+pytest -k "config and not secrets"
```
### Run Tests in a Directory
@@ -138,58 +136,35 @@ pytest -sv
## Coverage Reports
-The test suite is configured to generate coverage reports.
-
-### View Coverage in Terminal
+Coverage is not collected by a plain `pytest` run: `pytest.ini` deliberately
+has no coverage flags, so local runs stay fast. Ask for it explicitly
+(needs `pytest-cov`, which is in `requirements-test.txt`):
```bash
-# Coverage is automatically shown when running pytest
-pytest
+# Terminal summary
+pytest --cov=src --cov=web_interface --cov-report=term test/ --ignore=test/plugins
-# The output will show something like:
-# ----------- coverage: platform linux, python 3.11.5 -----------
-# Name Stmts Miss Cover Missing
-# ---------------------------------------------------------------------
-# src/display_controller.py 450 120 73% 45-67, 89-102
+# HTML report in htmlcov/
+pytest --cov=src --cov=web_interface --cov-report=html test/ --ignore=test/plugins
```
-### Generate HTML Coverage Report
-
-```bash
-# HTML report is automatically generated in htmlcov/
-pytest
-
-# Then open the report in your browser
-# On Linux:
-xdg-open htmlcov/index.html
-
-# On macOS:
-open htmlcov/index.html
-
-# On Windows:
-start htmlcov/index.html
-```
-
-The HTML report shows:
-- Line-by-line coverage
-- Files with low coverage highlighted
-- Interactive navigation
+Then open `htmlcov/index.html` in your browser (`xdg-open` on Linux, `open`
+on macOS, `start` on Windows).
### Coverage Threshold
-The tests are configured to fail if coverage drops below 30%. To change this, edit `pytest.ini`:
-
-```ini
---cov-fail-under=30 # Change this value
-```
+The only threshold is in CI: the core unit-test job in
+[`.github/workflows/test.yml`](../.github/workflows/test.yml) runs with
+`--cov-fail-under=52`. To check it locally, add that flag to the command
+above.
## Common Test Scenarios
### Run Tests After Making Changes
```bash
-# Quick test run (just unit tests)
-pytest -m unit
+# Quick run: just the tests for the area you changed
+pytest test/test_config_manager.py
# Full test suite
pytest
@@ -250,15 +225,10 @@ test/
├── test_error_aggregator.py # Error aggregation tests
├── test_schema_manager.py # Schema manager tests
├── test_web_api.py # Web API tests
-├── plugins/ # Per-plugin test suites
-│ ├── test_clock_simple.py
-│ ├── test_calendar.py
-│ ├── test_basketball_scoreboard.py
-│ ├── test_soccer_scoreboard.py
-│ ├── test_odds_ticker.py
-│ ├── test_text_display.py
-│ ├── test_visual_rendering.py
-│ └── test_plugin_base.py
+├── plugins/ # Plugin rendering suites
+│ ├── test_plugin_matrix.py # Every discovered plugin, across panel sizes
+│ ├── test_harness.py
+│ └── test_visual_rendering.py
└── web_interface/
├── test_config_manager_atomic.py
├── test_state_reconciliation.py
@@ -283,8 +253,8 @@ test/
If you see import errors:
```bash
-# Make sure you're in the project root
-cd /home/chuck/Github/LEDMatrix
+# Make sure you're in the project root (wherever you cloned it)
+cd ~/LEDMatrix
# Check Python path
python -c "import sys; print(sys.path)"
@@ -325,18 +295,18 @@ If coverage reports aren't generating:
# Make sure pytest-cov is installed
pip install pytest-cov
-# Run with explicit coverage
-pytest --cov=src --cov-report=html
+# Coverage is opt-in; ask for it explicitly
+pytest --cov=src --cov=web_interface --cov-report=html
```
## Continuous Integration
The repo runs the pytest suite via
[`.github/workflows/test.yml`](../.github/workflows/test.yml) on every
-push and pull request: a plugin-safety job (harness, visual rendering
-and plugin-matrix tests) plus a unit-test job that runs an explicit
-allowlist of suites — new test files must be added to that list to run
-in CI. Release version consistency is checked by
+push and pull request: a plugin-safety job that runs `test/plugins/`, and a
+core unit-test job that runs the whole `test/` tree except `test/plugins/`
+with `-m "not hardware"` and enforces coverage (`--cov-fail-under=52`). New
+test files are picked up automatically. Release version consistency is checked by
[`.github/workflows/release-version-check.yml`](../.github/workflows/release-version-check.yml).
Bandit, flake8, mypy and gitleaks run as pre-commit hooks (see
`.pre-commit-config.yaml`), not in CI.
@@ -345,17 +315,17 @@ Bandit, flake8, mypy and gitleaks run as pre-commit hooks (see
1. **Run tests before committing**:
```bash
- pytest -m unit # Quick check
+ pytest test/test_.py # Quick check of what you touched
```
2. **Run full suite before pushing**:
```bash
- pytest # Full test suite with coverage
+ pytest # Full test suite (add --cov flags for coverage)
```
3. **Fix failing tests immediately** - Don't let them accumulate
-4. **Keep coverage above threshold** - Aim for 70%+ coverage
+4. **Keep coverage above threshold** - CI fails below 52%
5. **Write tests for new features** - Add tests when adding new functionality
@@ -363,9 +333,9 @@ Bandit, flake8, mypy and gitleaks run as pre-commit hooks (see
```bash
# Most common commands
-pytest # Run all tests with coverage
+pytest # Run all tests (no coverage)
pytest -v # Verbose output
-pytest -m unit # Run only unit tests
+pytest test/test_x.py # Run one file
pytest -k "test_name" # Run tests matching pattern
pytest --cov=src # Generate coverage report
pytest -x # Stop on first failure
diff --git a/docs/MIGRATION_GUIDE.md b/docs/MIGRATION_GUIDE.md
index c6b17c94..b768c82b 100644
--- a/docs/MIGRATION_GUIDE.md
+++ b/docs/MIGRATION_GUIDE.md
@@ -19,7 +19,6 @@ All installation scripts have been moved from the project root to `scripts/insta
| `install_wifi_monitor.sh` | `scripts/install/install_wifi_monitor.sh` |
| `setup_cache.sh` | `scripts/install/setup_cache.sh` |
| `configure_web_sudo.sh` | `scripts/install/configure_web_sudo.sh` |
-| `migrate_config.sh` | `scripts/install/migrate_config.sh` |
#### Permission Fix Scripts
diff --git a/docs/MULTI_ROOT_WORKSPACE_SETUP.md b/docs/MULTI_ROOT_WORKSPACE_SETUP.md
index 56bbab2f..17e0fe09 100644
--- a/docs/MULTI_ROOT_WORKSPACE_SETUP.md
+++ b/docs/MULTI_ROOT_WORKSPACE_SETUP.md
@@ -10,11 +10,12 @@ Official plugins live in a single repository,
[ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins), with one
directory per plugin under `plugins/`. There are no separate per-plugin
repositories. For development you clone that monorepo **next to** LEDMatrix
-and symlink its plugin directories into LEDMatrix's `plugin-repos/`, which is
-where the plugin loader looks by default.
+and symlink the plugin directories you are working on into LEDMatrix's
+`plugins/` directory with `scripts/dev/dev_plugin_setup.sh`.
- ✅ Plugin code stays in the monorepo checkout, with its own git history
-- ✅ LEDMatrix discovers the plugins through symlinks in `plugin-repos/`
+- ✅ LEDMatrix discovers the plugins through symlinks in `plugins/`
+ (git-ignored), so the production `plugin-repos/` directory is untouched
- ✅ `LEDMatrix.code-workspace` opens both repositories in VS Code/Cursor
## Directory Structure
@@ -22,12 +23,11 @@ where the plugin loader looks by default.
```text
~/Github/
├── LEDMatrix/ # Main project
-│ ├── plugin-repos/ # Plugin directory the loader scans
-│ │ ├── starlark-apps/ # Bundled with LEDMatrix (tracked in git)
-│ │ ├── web-ui-info/ # Bundled with LEDMatrix (tracked in git)
-│ │ ├── clock-simple -> ../../ledmatrix-plugins/plugins/clock-simple
-│ │ ├── ledmatrix-weather -> ../../ledmatrix-plugins/plugins/ledmatrix-weather
+│ ├── plugins/ # Dev plugin directory (git-ignored)
+│ │ ├── clock-simple -> ~/Github/ledmatrix-plugins/plugins/clock-simple
+│ │ ├── ledmatrix-weather -> ~/Github/ledmatrix-plugins/plugins/ledmatrix-weather
│ │ └── ...
+│ ├── plugin-repos/ # Default (Plugin Store) plugin directory
│ ├── LEDMatrix.code-workspace # Opens LEDMatrix and ../ledmatrix-plugins
│ └── ...
└── ledmatrix-plugins/ # Plugin monorepo (git repo)
@@ -44,61 +44,65 @@ where the plugin loader looks by default.
### 1. The plugin monorepo
Clone ledmatrix-plugins into the same parent directory as LEDMatrix (the
-scripts below look for `../ledmatrix-plugins` relative to the LEDMatrix
-root):
+workspace file and `scripts/update_plugin_repos.py` look for
+`../ledmatrix-plugins` relative to the LEDMatrix root):
```bash
cd ~/Github
git clone https://github.com/ChuckBuilds/ledmatrix-plugins.git
```
-### 2. Symlinks in plugin-repos/
+### 2. Symlinks in plugins/
-`scripts/setup_plugin_repos.py` creates one symlink per plugin in
-`LEDMatrix/plugin-repos/`, named after the plugin's manifest `id` and pointing
-at `../ledmatrix-plugins/plugins/`.
+`scripts/dev/dev_plugin_setup.sh link ` creates
+`LEDMatrix/plugins/` as a symlink to a plugin directory. Use the
+plugin's manifest `id` as the name: that is the name the loader and
+`config.json` use, and the script warns when the two differ.
### 3. Multi-root workspace
`LEDMatrix.code-workspace` has two roots: LEDMatrix itself and
`../ledmatrix-plugins`.
-## Setup Scripts
+## Setup
-### Initial Setup
+### Link plugins
```bash
cd ~/Github/LEDMatrix
-python3 scripts/setup_plugin_repos.py
+./scripts/dev/dev_plugin_setup.sh link clock-simple ../ledmatrix-plugins/plugins/clock-simple
+./scripts/dev/dev_plugin_setup.sh list # show what is linked
```
-This script:
-- Reads each `manifest.json` under `../ledmatrix-plugins/plugins/`
-- Creates `plugin-repos/` symlinks (relative) to those directories
-- Leaves correct links alone, replaces links that point elsewhere, and skips
- (does not overwrite) a real directory of the same name — for example a
- plugin you installed from the Plugin Store. Remove that directory first if
- you want the linked copy.
+If a real (non-symlink) directory of the same name already exists in
+`plugins/`, the script offers to back it up and replace it.
+
+Without a sibling checkout, `./scripts/dev/dev_plugin_setup.sh link-github
+` clones the monorepo into `~/.ledmatrix-dev-plugins/` instead and links
+the plugin from there. See the
+[Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md).
### Updating Plugins
```bash
cd ~/Github/LEDMatrix
-python3 scripts/update_plugin_repos.py
+python3 scripts/update_plugin_repos.py # git pull in ../ledmatrix-plugins
+# or
+./scripts/dev/dev_plugin_setup.sh update # git pull in every linked checkout
```
-This runs `git pull` in `../ledmatrix-plugins` and prints the result. The
-symlinks pick up the new code; restart the display to load it.
+The symlinks pick up the new code; restart the display to load it.
## Configuration
-The loader reads plugins from `plugin_system.plugins_directory` in
-`config/config.json`. The default is already right for this setup:
+The loader scans only `plugin_system.plugins_directory` in
+`config/config.json` (default `plugin-repos`). Point it at `plugins` so it
+finds the links:
```json
{
"plugin_system": {
- "plugins_directory": "plugin-repos"
+ "plugins_directory": "plugins"
}
}
```
@@ -117,7 +121,7 @@ The loader reads plugins from `plugin_system.plugins_directory` in
### Adding New Plugins
1. Create `plugins//` in the monorepo checkout
-2. Run `python3 scripts/setup_plugin_repos.py` in LEDMatrix to link it
+2. Link it: `./scripts/dev/dev_plugin_setup.sh link ../ledmatrix-plugins/plugins/`
## Troubleshooting
@@ -125,30 +129,21 @@ The loader reads plugins from `plugin_system.plugins_directory` in
```bash
cd ~/Github/LEDMatrix
-ls -la plugin-repos/ # links present and not broken?
-python3 scripts/setup_plugin_repos.py # recreate them
+ls -la plugins/ # links present and not broken?
+./scripts/dev/dev_plugin_setup.sh status # link targets and git state
```
-Also check that `plugin_system.plugins_directory` is `plugin-repos`.
-
-### "Monorepo plugins directory not found"
-
-`setup_plugin_repos.py` expects the monorepo at `../ledmatrix-plugins`. Clone
-it there (or symlink it there).
+Also check that `plugin_system.plugins_directory` is `plugins`.
### Plugin updates not showing
-1. Verify the link target: `ls -la plugin-repos/`
+1. Verify the link target: `ls -la plugins/`
2. Check that you're editing the monorepo checkout, not a store-installed copy
3. Restart the LEDMatrix service (or `run.py`)
## Notes
-- `plugin-repos/` is tracked in git only for the bundled plugins
- (`starlark-apps`, `web-ui-info`). The symlinks you create are untracked
- files; don't commit them.
-- For linking a single plugin into `plugins/` instead (without a sibling
- checkout), see `scripts/dev/dev_plugin_setup.sh` in the
- [Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md).
+- `plugins/` is git-ignored (except `plugins/.gitkeep`); the symlinks are
+ never committed.
- When changing a plugin in the monorepo, bump its manifest `version` and run
`python update_registry.py`, or users won't receive the update.
diff --git a/docs/PLUGIN_API_REFERENCE.md b/docs/PLUGIN_API_REFERENCE.md
index 0eab3552..8b98f710 100644
--- a/docs/PLUGIN_API_REFERENCE.md
+++ b/docs/PLUGIN_API_REFERENCE.md
@@ -618,7 +618,7 @@ The Display Manager provides several pre-loaded fonts:
display_manager.regular_font # Press Start 2P, size 8
display_manager.small_font # Press Start 2P, size 8
display_manager.calendar_font # 5x7 BDF font
-display_manager.extra_small_font # 4x6 TTF font, size 6
+display_manager.extra_small_font # 4x6 TTF font, size 7 (6 snapped to its pixel grid)
display_manager.bdf_5x7_font # Alias for calendar_font
```
@@ -852,12 +852,12 @@ for file_info in files:
Get cache performance metrics.
-**Returns**: Dictionary with cache statistics (hits, misses, hit rate, etc.)
+**Returns**: Dictionary with cache statistics (`total_requests`, `cache_hit_rate`, `background_hit_rate`, `api_calls_saved`, `average_fetch_time`, etc.)
**Example**:
```python
metrics = self.cache_manager.get_cache_metrics()
-self.logger.info(f"Cache hit rate: {metrics['hit_rate']:.2%}")
+self.logger.info(f"Cache hit rate: {metrics['cache_hit_rate']:.2%}")
```
#### `get_memory_cache_stats() -> Dict[str, Any]`
diff --git a/docs/PLUGIN_CONFIGURATION_GUIDE.md b/docs/PLUGIN_CONFIGURATION_GUIDE.md
index 5c4956fa..1a28c04f 100644
--- a/docs/PLUGIN_CONFIGURATION_GUIDE.md
+++ b/docs/PLUGIN_CONFIGURATION_GUIDE.md
@@ -9,7 +9,7 @@ The LEDMatrix system uses a plugin-based architecture where each plugin manages
1. **Install a plugin** from the Plugin Store in the web interface
2. **Navigate to the plugin's configuration tab** (automatically created when installed)
3. **Configure settings** using the auto-generated form
-4. **Save configuration** and restart the display service
+4. **Save configuration**; the running display applies it without a restart
For detailed information, see the sections below.
@@ -189,19 +189,20 @@ plugin-repos/
"author": "Your Name",
"entry_point": "manager.py",
"class_name": "MyPlugin",
- "display_modes": ["my_plugin"],
- "config_schema": "config_schema.json"
+ "display_modes": ["my_plugin"]
}
```
-The required fields the plugin loader will check for are `id`,
-`name`, `version`, `class_name`, and `display_modes`. `entry_point`
-defaults to `manager.py` if omitted. `config_schema` must be a
-**file path** (relative to the plugin directory) — the schema itself
-lives in a separate JSON file, not inline in the manifest. The
-`class_name` value must match the actual class defined in the entry
-point file **exactly** (case-sensitive, no spaces); otherwise the
-loader fails with `AttributeError` at load time.
+The Plugin Store refuses a manifest that lacks any of `id`, `name`,
+`class_name` or `display_modes` (`store_manager.py`); the loader itself
+needs `class_name`. `version` is not required, but the store compares it
+with the registry's `latest_version` to offer updates, so set it.
+`entry_point` defaults to `manager.py` if omitted. The config schema is not
+named in the manifest: it is always the file `config_schema.json` in the
+plugin directory. The `class_name` value must match the actual class
+defined in the entry point file **exactly** (case-sensitive, no spaces);
+otherwise the loader fails with a `PluginError` ("Class ... not found in
+module") at load time.
### Plugin Manager Class
@@ -223,9 +224,11 @@ class MyPlugin(BasePlugin):
"""Render plugin content to the LED matrix."""
pass
- def get_duration(self):
- """Get display duration for this plugin"""
- return self.config.get('duration', 30)
+ # BasePlugin.get_display_duration() already returns
+ # self.config['display_duration'] (default 15s); override it only to
+ # vary the duration with the content.
+ def get_display_duration(self):
+ return self.config.get('display_duration', 30)
```
### Dynamic Duration Configuration
@@ -259,7 +262,7 @@ Each installed plugin automatically gets its own dedicated configuration tab in
### Accessing Plugin Configuration
-1. Navigate to the **Plugins** tab to see all installed plugins
+1. Navigate to the **Plugin Manager** tab to see all installed plugins
2. Click the **Configure** button on any plugin card, or
3. Click directly on the plugin's tab button in the navigation bar
@@ -278,7 +281,6 @@ Configuration forms are automatically generated from each plugin's `config_schem
- **Type-safe inputs**: Form inputs match JSON Schema types
- **Default values**: Fields show current values or schema defaults
- **Real-time validation**: Input constraints enforced (min, max, maxLength, etc.)
-- **Reset to defaults**: One-click reset to restore original settings
- **Help text**: Each field shows description from schema
For more details, see [Plugin Configuration Tabs](PLUGIN_CONFIGURATION_TABS.md).
@@ -337,20 +339,16 @@ The configuration system uses JSON Schema Draft-07 for validation:
2. **Configuration errors**: Validate plugin configuration against schema
3. **Display issues**: Check display durations and plugin display methods
4. **Performance**: Monitor plugin update intervals and resource usage
-5. **Tab not showing**: Verify `config_schema.json` exists and is referenced in manifest
+5. **Form missing or wrong**: Verify `config_schema.json` exists in the plugin directory and is valid JSON Schema
6. **Settings not saving**: Check validation errors and ensure all required fields are filled
### Debug Mode
-Enable debug logging to troubleshoot plugin issues:
+There is no config key for debug logging. Run the display with debug
+logging instead:
-```json
-{
- "plugin_system": {
- "debug": true,
- "log_level": "debug"
- }
-}
+```bash
+python3 run.py -d # or: LEDMATRIX_DEBUG=true python3 run.py
```
## See Also
diff --git a/docs/PLUGIN_CONFIGURATION_TABS.md b/docs/PLUGIN_CONFIGURATION_TABS.md
index 9fcecef4..332c114a 100644
--- a/docs/PLUGIN_CONFIGURATION_TABS.md
+++ b/docs/PLUGIN_CONFIGURATION_TABS.md
@@ -1,19 +1,8 @@
# Plugin Configuration Tabs
-> **Status note:** this doc was written during the rollout of the
-> per-plugin configuration tab feature. The feature itself is shipped
-> and working in the current v3 web interface, but a few file paths
-> in the "Implementation Details" section below still reference the
-> pre-v3 file layout (`web_interface_v2.py`, `templates/index_v2.html`).
-> The current implementation lives in `web_interface/app.py`,
-> `web_interface/blueprints/api_v3/` (plugin config handlers in
-> `plugins.py`), and `web_interface/templates/v3/`.
-> The user-facing description (Overview, Features, Form Generation
-> Process) is still accurate.
-
## 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 main Plugins management tab.
+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
@@ -21,24 +10,27 @@ Each installed plugin now gets its own dedicated configuration tab in the web in
- **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
-- **Reset Functionality**: Users can reset all settings to defaults with one click
- **Real-Time Validation**: Input constraints from JSON Schema are enforced (min, max, maxLength, etc.)
## User Experience
### Accessing Plugin Configuration
-1. Navigate to the **Plugins** tab to see all installed plugins
+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 (marked with a puzzle piece icon)
+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**
-4. Restart the display service to apply changes
+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
@@ -53,22 +45,13 @@ Each installed plugin now gets its own dedicated configuration tab in the web in
### Requirements
-To enable automatic configuration tab generation, your plugin must:
+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.
-1. Include a `config_schema.json` file
-2. Reference it in your `manifest.json`:
-
-```json
-{
- "id": "your-plugin",
- "name": "Your Plugin",
- "icon": "fas fa-star", // Optional: Custom tab icon
- ...
- "config_schema": "config_schema.json"
-}
-```
-
-**Note:** You can optionally specify a custom `icon` for your plugin tab. See [Plugin Custom Icons Guide](PLUGIN_CUSTOM_ICONS.md) for details.
+**Note:** You can optionally specify a Font Awesome `icon` class for your
+plugin tab in `manifest.json`. See [Plugin Custom Icons Guide](PLUGIN_CUSTOM_ICONS.md) for details.
### Supported JSON Schema Types
@@ -209,69 +192,32 @@ Renders as: Dropdown select
### Form Generation Process
-1. Web UI loads installed plugins via `/api/v3/plugins/installed`
-2. For each plugin, the backend loads its `config_schema.json`
-3. Frontend generates a tab button with plugin name
-4. Frontend generates a form based on the JSON Schema
-5. Current config values from `config.json` are populated
-6. When saved, each field is sent to `/api/v3/plugins/config` endpoint
+Forms are rendered on the server, not generated in the browser:
-## Implementation Details
-
-### Backend Changes
-
-**File**: `web_interface_v2.py`
-
-- Modified `/api/v3/plugins/installed` endpoint to include `config_schema_data`
-- Loads each plugin's `config_schema.json` if it exists
-- Returns schema data along with plugin info
-
-### Frontend Changes
-
-**File**: `templates/index_v2.html`
-
-New Functions:
-- `generatePluginTabs(plugins)` - Creates tab buttons and content for each plugin
-- `generatePluginConfigForm(plugin)` - Generates HTML form from JSON Schema
-- `savePluginConfiguration(pluginId)` - Saves form data to backend
-- `resetPluginConfig(pluginId)` - Resets all settings to defaults
-- `configurePlugin(pluginId)` - Navigates to plugin's tab
-
-### Data Flow
-
-```
-Page Load
- → refreshPlugins()
- → /api/v3/plugins/installed
- → Returns plugins with config_schema_data
- → generatePluginTabs()
- → Creates tab buttons
- → Creates tab content
- → generatePluginConfigForm()
- → Reads JSON Schema
- → Creates form inputs
- → Populates current values
-
-User Saves
- → savePluginConfiguration()
- → Reads form data
- → Converts types per schema
- → Sends to /api/v3/plugins/config
- → Updates config.json
- → Shows success notification
-```
+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/`
+ (`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
-- Ensure `config_schema.json` exists in plugin directory
-- Verify `config_schema` field in `manifest.json`
+- Check that the plugin is installed and appears in the **Plugin Manager** tab
- Check browser console for errors
-- Try refreshing plugins (Plugins tab → Refresh button)
+- 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
@@ -283,7 +229,6 @@ User Saves
- Check that config keys match schema properties
- Verify backend API is accessible
- Check browser network tab for API errors
-- Ensure display service is restarted after config changes
## Migration Guide
@@ -301,26 +246,21 @@ If your plugin doesn't have a config schema:
2. Add descriptions for each property
3. Set appropriate defaults
4. Add validation constraints (min, max, etc.)
-5. Reference the schema in your `manifest.json`
### Backward Compatibility
- Plugins without `config_schema.json` still work normally
-- They simply won't have a configuration tab
+- 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
-- The Configure button will navigate to a tab with a friendly message
-## Future Enhancements
+## Beyond the Basic Types
-Potential improvements for future versions:
-
-- **Advanced Schema Features**: Support for nested objects, conditional fields
-- **Visual Validation**: Real-time validation feedback as user types
-- **Color Pickers**: Special input for RGB/color array types
-- **File Uploads**: Support for image/asset uploads
-- **Import/Export**: Save and share plugin configurations
-- **Presets**: Quick-switch between saved configurations
-- **Documentation Links**: Link schema fields to plugin documentation
+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](PLUGIN_CONFIGURATION_GUIDE.md) and
+`web_interface/static/v3/js/widgets/README.md`.
## Example Plugins
diff --git a/docs/PLUGIN_CONFIG_CORE_PROPERTIES.md b/docs/PLUGIN_CONFIG_CORE_PROPERTIES.md
index 2e004c05..2939dd99 100644
--- a/docs/PLUGIN_CONFIG_CORE_PROPERTIES.md
+++ b/docs/PLUGIN_CONFIG_CORE_PROPERTIES.md
@@ -6,7 +6,8 @@ The LEDMatrix plugin system automatically manages certain core properties that a
## Core Properties
-The following properties are automatically managed by the system:
+The following properties are automatically managed by the system (the list
+is `CORE_PLUGIN_PROPERTIES` in `src/plugin_system/schema_manager.py`):
1. **`enabled`** (boolean)
- Default: `true`
@@ -24,6 +25,18 @@ The following properties are automatically managed by the system:
- Description: Enable live priority takeover when plugin has live content
- Used by DisplayController for priority scheduling
+4. **`vegas_width_pct`**, **`vegas_overflow`**, **`vegas_max_width_screens`**
+ (untyped; no default)
+ - Description: Vegas mode tuning for this plugin — card width as a
+ percentage of the panel, `"rotate"` or `"truncate"` on overflow, and the
+ widest the card may be in screens
+ - Read by `src/vegas_mode/plugin_adapter.py` and `BasePlugin`, which
+ validate the values themselves and ignore a bad one with a log line
+
+`skin` and `skin_options` were core properties until the skin system was
+removed. A plugin config saved with them still loads and saves; the keys are
+dropped on the next save (see `RETIRED_PLUGIN_KEYS` in `schema_manager.py`).
+
## How Core Properties Work
### Schema Validation
diff --git a/docs/PLUGIN_CONFIG_QUICK_START.md b/docs/PLUGIN_CONFIG_QUICK_START.md
index 16113ccc..568ffc94 100644
--- a/docs/PLUGIN_CONFIG_QUICK_START.md
+++ b/docs/PLUGIN_CONFIG_QUICK_START.md
@@ -10,8 +10,8 @@
and click **Install**
4. Notice a new tab appears in the second nav row with the plugin's name
5. Click that tab to configure the plugin
-6. Modify settings and click **Save**
-7. From **Overview**, click **Restart Display Service** to see changes
+6. Modify settings and click **Save Configuration**. The running display
+ picks the change up by itself; no restart is needed
That's it! Each installed plugin automatically gets its own configuration tab.
@@ -29,7 +29,6 @@ That's it! Each installed plugin automatically gets its own configuration tab.
- ✅ Proper input types (toggles, numbers, dropdowns)
- ✅ Help text explaining each setting
- ✅ Input validation (min/max, length, etc.)
-- ✅ One-click reset to defaults
## 📋 Example Walkthrough
@@ -40,7 +39,7 @@ Let's configure the "Hello World" plugin:
After installing the plugin, you'll see a new tab:
```
-[Overview] [General] [...] [Plugins] [Hello World] ← New tab!
+[Plugin Manager] [Hello World] ← New tab! (second nav row)
```
### Step 2: Configure Settings
@@ -70,15 +69,16 @@ Display Duration
How long to display in seconds
[10 ]
-[Save Configuration] [Back] [Reset to Defaults]
+[Refresh] [Update] [Uninstall] [Save Configuration]
```
### Step 3: Save and Apply
1. Modify any settings
2. Click **Save Configuration**
-3. See confirmation: "Configuration saved for hello-world. Restart display to apply changes."
-4. Restart the display service
+3. See the confirmation notification. Plugin settings apply live: the
+ display service reloads `config.json` when it changes and passes the new
+ settings to the plugin's `on_config_change()`
## 🛠️ For Plugin Developers
@@ -105,19 +105,14 @@ Create `config_schema.json` in your plugin directory:
}
```
-Reference it in `manifest.json`:
+**Done!** The file name is fixed: the web interface looks for
+`config_schema.json` in the plugin's directory; there is no manifest field
+for it. Every installed plugin gets a tab; the schema is what turns it into a
+form.
-```json
-{
- "id": "my-plugin",
- "icon": "fas fa-star", // Optional: add a custom icon!
- "config_schema": "config_schema.json"
-}
-```
-
-**Done!** Your plugin now has a configuration tab.
-
-**Bonus:** Add an `icon` field for a custom tab icon! Use Font Awesome icons (`fas fa-star`), emoji (⭐), or custom images. See [PLUGIN_CUSTOM_ICONS.md](PLUGIN_CUSTOM_ICONS.md) for the full guide.
+**Bonus:** an `icon` field in `manifest.json` names a Font Awesome class for
+the tab (`"icon": "fas fa-star"`). See
+[PLUGIN_CUSTOM_ICONS.md](PLUGIN_CUSTOM_ICONS.md).
## 🎨 Supported Input Types
@@ -171,12 +166,10 @@ User enters: `255, 0, 0`
### For Users
-1. **Reset Anytime**: Use "Reset to Defaults" to restore original settings
-2. **Navigate Back**: Switch to the **Plugin Manager** tab to see the
+1. **Navigate Back**: Switch to the **Plugin Manager** tab to see the
full list of installed plugins
-3. **Check Help Text**: Each field has a description explaining what it does
-4. **Restart Required**: Remember to restart the display service from
- **Overview** after saving
+2. **Check Help Text**: Each field has a description explaining what it does
+3. **No Restart Needed**: Saved plugin settings apply to the running display
### For Developers
@@ -189,18 +182,17 @@ User enters: `255, 0, 0`
## 🔧 Troubleshooting
### Tab Not Showing
-- Check that `config_schema.json` exists
-- Verify `config_schema` is in `manifest.json`
+- Check that the plugin is installed and listed under **Plugin Manager**
- Refresh the page
- Check browser console for errors
### Settings Not Saving
- Ensure plugin is properly installed
-- Restart the display service after saving
- Check that all required fields are filled
- Look for validation errors in browser console
### Form Looks Wrong
+- Check that `config_schema.json` is in the plugin's directory
- Validate your JSON Schema
- Check that types match your defaults
- Ensure descriptions are strings
diff --git a/docs/PLUGIN_CUSTOM_ICONS.md b/docs/PLUGIN_CUSTOM_ICONS.md
index 79cabc5b..0d7c761a 100644
--- a/docs/PLUGIN_CUSTOM_ICONS.md
+++ b/docs/PLUGIN_CUSTOM_ICONS.md
@@ -2,17 +2,28 @@
## Overview
-Plugins can specify custom icons that appear next to their name in the web interface tabs. This makes your plugin instantly recognizable and adds visual polish to the UI.
+A plugin can name an icon for its tab in the web interface's second nav row
+(next to **Plugin Manager**) with the `icon` field in `manifest.json`.
-## Icon Types Supported
+> **Status:** the tab code honors `icon`, but `GET /api/v3/plugins/installed`
+> (`web_interface/blueprints/api_v3/plugins.py`) does not currently include
+> the manifest's `icon` in its response, so every tab shows the default
+> puzzle piece. Setting `icon` is harmless and will take effect once the API
+> passes it through again.
-The system supports three types of icons:
+## Font Awesome classes only
-### 1. Font Awesome Icons (Recommended)
+`icon` is used verbatim as the CSS class of an `` element
+(`iconEl.className = plugin.icon || 'fas fa-puzzle-piece'` in
+`web_interface/static/v3/js/app-shell.js` and the same fallback in
+`app-early.js`). So it must be a Font Awesome class string. Emoji, image
+paths and URLs are not supported: they would end up as a meaningless class
+name and render nothing.
-The web interface uses Font Awesome 6, giving you access to thousands of icons.
+The web interface bundles Font Awesome Free 6
+(`web_interface/static/v3/vendor/fontawesome/`), so any free `fas`, `far` or
+`fab` icon works.
-**Example:**
```json
{
"id": "my-plugin",
@@ -21,292 +32,33 @@ The web interface uses Font Awesome 6, giving you access to thousands of icons.
}
```
-**Common Font Awesome Icons:**
-- Clock: `fas fa-clock`
+Some common choices:
+
+- Clock / calendar: `fas fa-clock`, `fas fa-calendar-alt`
- Weather: `fas fa-cloud-sun`, `fas fa-cloud-rain`
-- Calendar: `fas fa-calendar`, `fas fa-calendar-alt`
-- Sports: `fas fa-football-ball`, `fas fa-basketball-ball`
+- Sports: `fas fa-football-ball`, `fas fa-basketball-ball`, `fas fa-trophy`
- Music: `fas fa-music`, `fas fa-headphones`
- Finance: `fas fa-chart-line`, `fas fa-dollar-sign`
- News: `fas fa-newspaper`, `fas fa-rss`
-- Settings: `fas fa-cog`, `fas fa-sliders-h`
-- Timer: `fas fa-stopwatch`, `fas fa-hourglass`
-- Alert: `fas fa-bell`, `fas fa-exclamation-triangle`
-- Heart: `fas fa-heart`, `far fa-heart` (outline)
-- Star: `fas fa-star`, `far fa-star` (outline)
-- Image: `fas fa-image`, `fas fa-camera`
-- Video: `fas fa-video`, `fas fa-film`
-- Game: `fas fa-gamepad`, `fas fa-dice`
+- Games: `fas fa-gamepad`, `fas fa-dice`
-**Browse all icons:** [Font Awesome Icon Gallery](https://fontawesome.com/icons)
+Browse the rest in the [Font Awesome gallery](https://fontawesome.com/icons)
+(filter to Free, version 6).
-### 2. Emoji Icons (Fun & Simple)
+## Default
-Use any emoji character for a colorful, fun icon.
-
-**Example:**
-```json
-{
- "id": "hello-world",
- "name": "Hello World",
- "icon": "👋"
-}
-```
-
-**Popular Emojis:**
-- Time: ⏰ 🕐 ⏱️ ⏲️
-- Weather: ☀️ ⛅ 🌤️ 🌧️ ⛈️ 🌩️ ❄️
-- Sports: ⚽ 🏀 🏈 ⚾ 🎾 🏐
-- Music: 🎵 🎶 🎸 🎹 🎤
-- Money: 💰 💵 💴 💶 💷
-- Calendar: 📅 📆
-- News: 📰 📻 📡
-- Fun: 🎮 🎲 🎯 🎨 🎭
-- Nature: 🌍 🌎 🌏 🌳 🌺 🌸
-- Food: 🍕 🍔 🍟 🍦 ☕ 🍰
-
-### 3. Custom Image URLs (Advanced)
-
-Use a custom image file for ultimate branding.
-
-**Example:**
-```json
-{
- "id": "my-plugin",
- "name": "My Plugin",
- "icon": "/plugins/my-plugin/icon.png"
-}
-```
-
-**Requirements:**
-- Image should be 16x16 to 32x32 pixels
-- Supported formats: PNG, SVG, JPG, GIF
-- Can be a relative path, absolute path, or external URL
-- SVG recommended for best quality at any size
-
-## How to Add an Icon
-
-### Step 1: Choose Your Icon
-
-Decide which type suits your plugin:
-- **Font Awesome**: Professional, consistent with UI
-- **Emoji**: Fun, colorful, no setup needed
-- **Custom Image**: Unique branding, requires image file
-
-### Step 2: Add to manifest.json
-
-Add the `icon` field to your plugin's `manifest.json`:
-
-```json
-{
- "id": "my-weather-plugin",
- "name": "Weather Display",
- "version": "1.0.0",
- "author": "Your Name",
- "description": "Shows weather information",
- "icon": "fas fa-cloud-sun", // ← Add this line
- "entry_point": "manager.py",
- ...
-}
-```
-
-### Step 3: Test Your Plugin
-
-1. Install or update your plugin
-2. Open the web interface
-3. Look for your plugin's tab
-4. The icon should appear next to the plugin name
-
-## Examples
-
-### Weather Plugin
-```json
-{
- "id": "weather-advanced",
- "name": "Weather Advanced",
- "icon": "fas fa-cloud-sun",
- "description": "Advanced weather display with forecasts"
-}
-```
-**Result:** Tab shows: `☁️ Weather Advanced`
-
-### Clock Plugin
-```json
-{
- "id": "digital-clock",
- "name": "Digital Clock",
- "icon": "⏰",
- "description": "A beautiful digital clock"
-}
-```
-**Result:** Tab shows: `⏰ Digital Clock`
-
-### Sports Scores Plugin
-```json
-{
- "id": "sports-scores",
- "name": "Sports Scores",
- "icon": "fas fa-trophy",
- "description": "Live sports scores"
-}
-```
-**Result:** Tab shows: `🏆 Sports Scores`
-
-### Custom Branding
-```json
-{
- "id": "company-dashboard",
- "name": "Company Dashboard",
- "icon": "/plugins/company-dashboard/logo.svg",
- "description": "Company metrics display"
-}
-```
-**Result:** Tab shows: `[logo] Company Dashboard`
-
-## Best Practices
-
-### 1. Choose Meaningful Icons
-- Icon should relate to plugin functionality
-- Users should understand what the plugin does at a glance
-- Avoid generic icons for specific functionality
-
-### 2. Keep It Simple
-- Simpler icons work better at small sizes
-- Avoid icons with too much detail
-- Test how your icon looks at 16x16 pixels
-
-### 3. Match the UI Style
-- Font Awesome icons match the interface best
-- If using emoji, consider contrast with background
-- Custom images should use similar color schemes
-
-### 4. Consider Accessibility
-- Icons should be recognizable without color
-- Don't rely solely on color to convey meaning
-- The plugin name should be descriptive
-
-### 5. Test on Different Displays
-- Check icon clarity on various screen sizes
-- Ensure emoji render correctly on target devices
-- Custom images should have good contrast
-
-## Icon Categories
-
-Here are recommended icons by plugin category:
-
-### Time & Calendar
-- `fas fa-clock`, `fas fa-calendar`, `fas fa-hourglass`
-- Emoji: ⏰ 📅 ⏱️
-
-### Weather
-- `fas fa-cloud-sun`, `fas fa-temperature-high`, `fas fa-wind`
-- Emoji: ☀️ 🌧️ ⛈️
-
-### Finance & Stocks
-- `fas fa-chart-line`, `fas fa-dollar-sign`, `fas fa-coins`
-- Emoji: 💰 📈 💵
-
-### Sports & Games
-- `fas fa-football-ball`, `fas fa-trophy`, `fas fa-gamepad`
-- Emoji: ⚽ 🏀 🎮
-
-### Entertainment
-- `fas fa-music`, `fas fa-film`, `fas fa-tv`
-- Emoji: 🎵 🎬 📺
-
-### News & Information
-- `fas fa-newspaper`, `fas fa-rss`, `fas fa-info-circle`
-- Emoji: 📰 📡 ℹ️
-
-### Utilities
-- `fas fa-tools`, `fas fa-cog`, `fas fa-wrench`
-- Emoji: 🔧 ⚙️ 🛠️
-
-### Social Media
-- `fab fa-twitter`, `fab fa-facebook`, `fab fa-instagram`
-- Emoji: 📱 💬 📧
+With no `icon` (or an empty one) the tab shows `fas fa-puzzle-piece`.
## Troubleshooting
-### Icon Not Showing
-1. Check that the `icon` field is correctly spelled in `manifest.json`
-2. For Font Awesome icons, verify the class name is correct
-3. For custom images, check that the file path is accessible
-4. Refresh the plugins in the web interface
-5. Check browser console for errors
-
-### Emoji Looks Wrong
-- Some emojis render differently on different platforms
-- Try a different emoji if one doesn't work well
-- Consider using Font Awesome instead for consistency
-
-### Custom Image Not Loading
-- Verify the image file exists in the specified path
-- Check file permissions (should be readable)
-- Try using an absolute path or URL
-- Ensure image format is supported (PNG, SVG, JPG, GIF)
-- Check image dimensions (16x16 to 32x32 recommended)
-
-### Icon Too Large/Small
-- Font Awesome and emoji icons automatically size correctly
-- For custom images, adjust the image file dimensions
-- SVG images scale best
-
-## Default Behavior
-
-If you don't specify an `icon` field in your manifest:
-- The plugin tab will show a default puzzle piece icon: 🧩
-- This is the fallback for all plugins without custom icons
-
-## Technical Details
-
-The icon system works as follows:
-
-1. **Frontend reads manifest**: When plugins load, the web interface reads each plugin's `manifest.json`
-2. **Icon detection**: The `getPluginIcon()` function determines icon type:
- - Contains `fa-` → Font Awesome icon
- - 1-4 characters → Emoji
- - Starts with `http://`, `https://`, or `/` → Custom image
- - Otherwise → Default puzzle piece
-3. **Rendering**: Icon HTML is generated and inserted into:
- - Tab button in navigation bar
- - Configuration page header
-
-## Advanced: Dynamic Icons
-
-Want to change icons programmatically? While not officially supported, you could:
-
-1. Store multiple icon options in your manifest
-2. Use JavaScript to swap icons based on plugin state
-3. Update the manifest dynamically and refresh plugins
-
-**Example (advanced):**
-```json
-{
- "id": "status-display",
- "icon": "fas fa-circle",
- "icon_states": {
- "active": "fas fa-check-circle",
- "error": "fas fa-exclamation-circle",
- "warning": "fas fa-exclamation-triangle"
- }
-}
-```
+1. Check the class name against the Font Awesome 6 Free gallery; a Pro-only
+ or misspelled class renders as a blank space.
+2. Include the style prefix (`fas`, `far` or `fab`) as well as the icon
+ class.
+3. See the status note above: the icon is currently not passed through by
+ the API.
## Related Documentation
-- [Plugin Configuration Tabs](PLUGIN_CONFIGURATION_TABS.md) - Main plugin tabs documentation
-- [Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md) - How to create plugins
-- [Font Awesome Icons](https://fontawesome.com/icons) - Browse all available icons
-- [Emoji Reference](https://unicode.org/emoji/charts/full-emoji-list.html) - All emoji options
-
-## Summary
-
-Adding a custom icon to your plugin:
-
-1. **Choose** your icon (Font Awesome, emoji, or custom image)
-2. **Add** the `icon` field to `manifest.json`
-3. **Test** in the web interface
-
-That's it! Your plugin now has a professional, recognizable icon in the UI. 🎨
-
+- [Plugin Configuration Tabs](PLUGIN_CONFIGURATION_TABS.md)
+- [Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md)
diff --git a/docs/PLUGIN_DEVELOPMENT_GUIDE.md b/docs/PLUGIN_DEVELOPMENT_GUIDE.md
index e63d832e..95857464 100644
--- a/docs/PLUGIN_DEVELOPMENT_GUIDE.md
+++ b/docs/PLUGIN_DEVELOPMENT_GUIDE.md
@@ -654,7 +654,8 @@ To have your plugin added to the official plugin store:
For your plugin to work well in the plugin store:
- **GitHub repository**: Must be publicly accessible on GitHub
-- **Releases or tags**: Recommended for version tracking
+- **`version` in manifest.json**: The store offers updates by comparing it
+ with the registry's `latest_version`; releases and tags are not read
- **README.md**: Clear installation and configuration instructions
- **config_schema.json**: Recommended for web UI configuration
- **manifest.json**: Required with all required fields
@@ -664,7 +665,8 @@ For your plugin to work well in the plugin store:
1. **Official Registry** (Recommended):
- Listed in default plugin store
- - Automatic updates
+ - Update offers in the Plugin Manager (and weekly automatic updates, if
+ the user turns them on)
- Verified badge
- Requires approval
diff --git a/docs/PLUGIN_IMPLEMENTATION_SUMMARY.md b/docs/PLUGIN_IMPLEMENTATION_SUMMARY.md
deleted file mode 100644
index 3e5d866a..00000000
--- a/docs/PLUGIN_IMPLEMENTATION_SUMMARY.md
+++ /dev/null
@@ -1,358 +0,0 @@
-# LEDMatrix Plugin System - Implementation Summary
-
-> **Status note:** this is a high-level summary written during the
-> initial plugin system rollout. Most of it is accurate, but a few
-> sections describe features that are aspirational or only partially
-> implemented (per-plugin virtual envs, resource limits, registry
-> manager). Drift from current reality is called out inline.
-
-This document provides a comprehensive overview of the plugin architecture implementation, consolidating details from multiple plugin-related implementation summaries.
-
-## Executive Summary
-
-The LEDMatrix plugin system transforms the project into a modular, extensible platform where users can create, share, and install custom displays through a GitHub-based store (similar to Home Assistant Community Store).
-
-## Architecture Overview
-
-### Core Components
-
-```
-LEDMatrix/
-├── src/plugin_system/
-│ ├── base_plugin.py # Plugin interface contract
-│ ├── plugin_loader.py # Discovery + dynamic import
-│ ├── plugin_manager.py # Lifecycle management
-│ ├── store_manager.py # GitHub install / store integration
-│ ├── schema_manager.py # Config schema validation
-│ ├── health_monitor.py # Plugin health metrics
-│ ├── operation_queue.py # Async install/update operations
-│ └── state_manager.py # Persistent plugin state
-├── plugin-repos/ # Default plugin install location
-│ ├── football-scoreboard/
-│ ├── ledmatrix-music/
-│ └── ledmatrix-stocks/
-└── config/config.json # Plugin configurations
-```
-
-> Earlier drafts of this doc referenced `registry_manager.py`. It was
-> never created — discovery happens in `plugin_loader.py`. The earlier
-> default plugin location of `plugins/` has been replaced with
-> `plugin-repos/` (see `config/config.template.json:130`).
-
-### Key Design Decisions
-
-✅ **Gradual Migration**: Plugin system added alongside existing managers
-✅ **GitHub-Based Store**: Simple discovery from GitHub repositories
-✅ **Plugin Isolation**: Each plugin in dedicated directory
-✅ **Configuration Integration**: Plugins use main config.json
-✅ **Backward Compatibility**: Existing functionality preserved
-
-## Implementation Phases
-
-### Phase 1: Core Infrastructure (Completed)
-
-#### Plugin Base Classes
-- **BasePlugin**: Abstract interface for all plugins
-- **Standard Methods**: `update()`, `display()`, `get_config()`
-- **Lifecycle Hooks**: `on_enable()`, `on_disable()`, `on_config_change()`
-
-#### Plugin Manager
-- **Discovery**: Automatic plugin detection in `./plugins/` directory
-- **Loading**: Dynamic import and instantiation
-- **Management**: Enable/disable, configuration updates
-- **Error Handling**: Graceful failure isolation
-
-#### Store Manager
-- **GitHub Integration**: Repository cloning and management
-- **Version Handling**: Tag-based version control
-- **Dependency Resolution**: Automatic dependency installation
-
-### Phase 2: Configuration System (Completed)
-
-#### Nested Schema Validation
-- **JSON Schema**: Comprehensive configuration validation
-- **Type Safety**: Ensures configuration integrity
-- **Dynamic UI**: Schema-driven configuration forms
-
-#### Tabbed Configuration Interface
-- **Organized UI**: Plugin settings in dedicated tabs
-- **Real-time Validation**: Instant feedback on configuration changes
-- **Backup System**: Automatic configuration versioning
-
-#### Live Priority Management
-- **Dynamic Switching**: Real-time display priority changes
-- **API Integration**: RESTful priority management
-- **Conflict Resolution**: Automatic priority conflict handling
-
-### Phase 3: Advanced Features (Completed)
-
-#### Custom Icons
-- **Plugin Branding**: Custom icons for plugin identification
-- **Format Support**: PNG, SVG, and font-based icons
-- **Fallback System**: Default icons when custom ones unavailable
-
-#### Dependency Management
-- **Requirements.txt**: Per-plugin dependencies, installed system-wide
- via pip on first plugin load
-- **Version Pinning**: Standard pip version constraints in
- `requirements.txt`
-
-> Earlier plans called for per-plugin virtual environments. That isn't
-> implemented — plugin Python deps install into the system Python
-> environment (or whatever environment the LEDMatrix service is using).
-> Conflicting versions across plugins are not auto-resolved.
-
-#### Health monitoring
-- **Resource Monitor** (`src/plugin_system/resource_monitor.py`): tracks
- CPU and memory metrics per plugin and warns about slow plugins
-- **Health Monitor** (`src/plugin_system/health_monitor.py`): tracks
- plugin failures and last-success timestamps
-
-> Earlier plans called for hard CPU/memory limits and a sandboxed
-> permission system. Neither is implemented. Plugins run in the same
-> process as the display loop with full file-system and network access
-> — review third-party plugin code before installing.
-
-## Plugin Development
-
-### Plugin Structure
-```
-my-plugin/
-├── manifest.json # Metadata and configuration
-├── manager.py # Main plugin class
-├── requirements.txt # Python dependencies
-├── config_schema.json # Configuration validation
-├── icon.png # Custom icon (optional)
-└── README.md # Documentation
-```
-
-### Manifest Format
-```json
-{
- "id": "my-plugin",
- "name": "My Custom Display",
- "version": "1.0.0",
- "author": "Developer Name",
- "description": "Brief plugin description",
- "entry_point": "manager.py",
- "class_name": "MyPlugin",
- "category": "custom",
- "requires": ["requests>=2.25.0"],
- "config_schema": "config_schema.json"
-}
-```
-
-### Plugin Class Template
-```python
-from src.plugin_system.base_plugin import BasePlugin
-
-class MyPlugin(BasePlugin):
- def __init__(self, config, display_manager, cache_manager):
- super().__init__(config, display_manager, cache_manager)
- self.my_setting = config.get('my_setting', 'default')
-
- def update(self):
- # Fetch data from API, database, etc.
- self.data = self.fetch_my_data()
-
- def display(self, force_clear=False):
- # Render to LED matrix
- self.display_manager.draw_text(
- self.data,
- x=5, y=15
- )
- self.display_manager.update_display()
-```
-
-## Plugin Store & Distribution
-
-### Registry System
-- **GitHub Repository**: chuckbuilds/ledmatrix-plugin-registry
-- **JSON Registry**: plugins.json with metadata
-- **Version Management**: Semantic versioning support
-- **Verification**: Trusted plugin marking
-
-### Installation Process
-1. **Discovery**: Browse available plugins in web UI
-2. **Selection**: Choose plugin and version
-3. **Download**: Clone from GitHub repository
-4. **Installation**: Install dependencies and register plugin
-5. **Configuration**: Set up plugin settings
-6. **Activation**: Enable and start plugin
-
-### Publishing Process
-```bash
-# Create plugin repository
-git init
-git add .
-git commit -m "Initial plugin release"
-git tag v1.0.0
-git push origin main --tags
-
-# Submit to registry (PR to chuckbuilds/ledmatrix-plugin-registry)
-```
-
-## Web Interface Integration
-
-### Plugin Store UI
-- **Browse**: Filter and search available plugins
-- **Details**: Version info, dependencies, screenshots
-- **Installation**: One-click install process
-- **Management**: Enable/disable installed plugins
-
-### Configuration Interface
-- **Tabbed Layout**: Separate tabs for each plugin
-- **Schema-Driven Forms**: Automatic form generation
-- **Validation**: Real-time configuration validation
-- **Live Updates**: Immediate configuration application
-
-### Status Monitoring
-- **Plugin Health**: Individual plugin status indicators
-- **Resource Usage**: Memory and CPU monitoring
-- **Error Reporting**: Plugin-specific error logs
-- **Update Notifications**: Available update alerts
-
-## Testing & Quality Assurance
-
-### Test Coverage
-- **Unit Tests**: Individual component testing
-- **Integration Tests**: Plugin lifecycle testing
-- **Hardware Tests**: Real Pi validation
-- **Performance Tests**: Resource usage monitoring
-
-### Example Plugins Created
-1. **Football Scoreboard**: Live NFL score display
-2. **Music Visualizer**: Audio spectrum display
-3. **Stock Ticker**: Financial data visualization
-
-### Compatibility Testing
-- **Python Versions**: 3.10, 3.11, 3.12 support
-- **Hardware**: Pi 4, Pi 5 validation
-- **Dependencies**: Comprehensive dependency testing
-
-## Performance & Resource Management
-
-### Optimization Features
-- **Lazy Loading**: Plugins loaded only when needed
-- **Background Updates**: Non-blocking data fetching
-- **Memory Management**: Automatic cleanup and garbage collection
-- **Caching**: Intelligent data caching to reduce API calls
-
-### Resource Limits
-- **Memory**: Per-plugin memory monitoring
-- **CPU**: CPU usage tracking and limits
-- **Network**: API call rate limiting
-- **Storage**: Plugin storage quota management
-
-## Security Considerations
-
-### Plugin Sandboxing
-- **File System Isolation**: Restricted file access
-- **Network Controls**: Limited network permissions
-- **Dependency Scanning**: Security vulnerability checking
-- **Code Review**: Manual review for published plugins
-
-### Permission Levels
-- **Trusted Plugins**: Full system access
-- **Community Plugins**: Restricted permissions
-- **Untrusted Plugins**: Minimal permissions (future)
-
-## Migration & Compatibility
-
-### Backward Compatibility
-- **Existing Managers**: Continue working unchanged
-- **Configuration**: Existing configs remain valid
-- **API**: Core APIs unchanged
-- **Performance**: No degradation in existing functionality
-
-### Migration Tools
-- **Config Converter**: Automatic plugin configuration migration
-- **Dependency Checker**: Validate system compatibility
-- **Backup System**: Configuration backup before changes
-
-### Future Migration Path
-```
-v2.0.0: Plugin infrastructure (current)
-v2.1.0: Migration tools and examples
-v2.2.0: Enhanced plugin features
-v3.0.0: Plugin-only architecture (legacy removal)
-```
-
-## Success Metrics
-
-### ✅ Completed Achievements
-- **Architecture**: Modular plugin system implemented
-- **Store**: GitHub-based plugin distribution working
-- **UI**: Web interface plugin management complete
-- **Examples**: 3 functional example plugins created
-- **Testing**: Comprehensive test coverage achieved
-- **Documentation**: Complete developer and user guides
-
-### 📊 Usage Statistics
-- **Plugin Count**: 3+ plugins available
-- **Installation Success**: 100% successful installations
-- **Performance Impact**: <5% overhead on existing functionality
-- **User Adoption**: Plugin system actively used
-
-### 🔮 Future Enhancements
-- **Sandboxing**: Complete plugin isolation
-- **Auto-Updates**: Automatic plugin updates
-- **Marketplace**: Plugin ratings and reviews
-- **Advanced Dependencies**: Complex plugin relationships
-
-## Technical Highlights
-
-### Plugin Discovery
-```python
-def discover_plugins(self):
- """Automatically discover plugins in ./plugins/ directory"""
- for plugin_dir in os.listdir(self.plugins_dir):
- manifest_path = os.path.join(plugin_dir, 'manifest.json')
- if os.path.exists(manifest_path):
- # Load and validate manifest
- # Register plugin with system
-```
-
-### Dynamic Loading
-```python
-def load_plugin(self, plugin_id):
- """Dynamically load and instantiate plugin"""
- plugin_dir = os.path.join(self.plugins_dir, plugin_id)
- sys.path.insert(0, plugin_dir)
-
- try:
- manifest = self.load_manifest(plugin_id)
- module = importlib.import_module(manifest['entry_point'])
- plugin_class = getattr(module, manifest['class_name'])
- return plugin_class(self.config, self.display_manager, self.cache_manager)
- finally:
- sys.path.pop(0)
-```
-
-### Configuration Validation
-```python
-def validate_config(self, plugin_id, config):
- """Validate plugin configuration against schema"""
- schema_path = os.path.join(self.plugins_dir, plugin_id, 'config_schema.json')
- with open(schema_path) as f:
- schema = json.load(f)
-
- try:
- validate(config, schema)
- return True, None
- except ValidationError as e:
- return False, str(e)
-```
-
-## Conclusion
-
-The LEDMatrix plugin system successfully transforms the project into a modular, extensible platform. The implementation provides:
-
-- **For Users**: Easy plugin discovery, installation, and management
-- **For Developers**: Clear plugin API and development tools
-- **For Maintainers**: Smaller core codebase with community contributions
-
-The system maintains full backward compatibility while enabling future growth through community-developed plugins. All major components are implemented, tested, and ready for production use.
-
----
-*This document consolidates plugin implementation details from multiple phase summaries into a comprehensive technical overview.*
diff --git a/docs/PLUGIN_QUICK_REFERENCE.md b/docs/PLUGIN_QUICK_REFERENCE.md
index 09c54b76..8eb7f793 100644
--- a/docs/PLUGIN_QUICK_REFERENCE.md
+++ b/docs/PLUGIN_QUICK_REFERENCE.md
@@ -99,20 +99,13 @@ class MyPlugin(BasePlugin):
### 3. Publishing
-```bash
-# Create repo
-git init
-git add .
-git commit -m "Initial commit"
-git remote add origin https://github.com/YourName/ledmatrix-my-plugin
-git push -u origin main
-
-# Tag release
-git tag v1.0.0
-git push origin v1.0.0
-
-# Submit to registry (PR to ChuckBuilds/ledmatrix-plugins)
-```
+Official plugins live in the
+[ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins)
+monorepo: add `plugins//`, bump `version` in its
+`manifest.json` on every change, run `python update_registry.py` there and
+open a pull request. A third-party plugin can stay in its own repository and
+be installed by URL. Git tags and releases are not read by the store; see
+[PLUGIN_REGISTRY_SETUP_GUIDE.md](PLUGIN_REGISTRY_SETUP_GUIDE.md).
## Using Plugins
@@ -166,20 +159,20 @@ follows this shape:
"name": "Simple Clock",
"author": "ChuckBuilds",
"category": "time",
- "repo": "https://github.com/ChuckBuilds/ledmatrix-clock-simple",
- "versions": [
- {
- "version": "1.0.0",
- "ledmatrix_min_version": "2.0.0",
- "download_url": "https://github.com/.../v1.0.0.zip"
- }
- ],
+ "repo": "https://github.com/ChuckBuilds/ledmatrix-plugins",
+ "branch": "main",
+ "plugin_path": "plugins/clock-simple",
+ "latest_version": "1.0.0",
"verified": true
}
]
}
```
+`plugin_path` is empty for a third-party plugin in its own repository. The
+store offers an update when the installed manifest's `version` is older
+than `latest_version`.
+
## Benefits
### For Users
@@ -212,8 +205,11 @@ intentionally simple:
slow plugins, but no hard CPU/memory caps.
3. **Plugin ratings**: not yet — the Plugin Store shows version,
author, and category but no community rating system.
-4. **Auto-updates**: manual via the Plugin Manager tab; no automatic
- background updates.
+4. **Auto-updates**: off by default. Update from the Plugin Manager tab
+ (per plugin, or **Check & Update All**), or turn on weekly automatic
+ updates in the General tab (`auto_update.enabled`,
+ `web_interface/auto_update.py`), which update LEDMatrix and then the
+ installed plugins.
5. **Dependency conflicts**: each plugin's `requirements.txt` is
installed via pip; conflicting versions across plugins are not
resolved automatically.
diff --git a/docs/PLUGIN_REGISTRY_SETUP_GUIDE.md b/docs/PLUGIN_REGISTRY_SETUP_GUIDE.md
index 87457c14..0fcf7db8 100644
--- a/docs/PLUGIN_REGISTRY_SETUP_GUIDE.md
+++ b/docs/PLUGIN_REGISTRY_SETUP_GUIDE.md
@@ -1,415 +1,109 @@
# Plugin Registry Setup Guide
-This guide explains how to set up and maintain your official plugin registry at [https://github.com/ChuckBuilds/ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins).
+This page explains how the official plugin registry works and how a plugin
+gets into it. The registry and the official plugins both live in one
+repository, [ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins);
+its `SUBMISSION.md`, `VERIFICATION.md` and `docs/` are the authoritative
+contributor guides.
-## Overview
+## How it fits together
-Your plugin registry serves as a **central directory** that lists all official, verified plugins. The registry is just a JSON file; the actual plugins live in their own repositories.
-
-## Repository Structure
-
-```
+```text
ledmatrix-plugins/
-├── README.md # Main documentation
-├── LICENSE # GPL-3.0
-├── plugins.json # The registry file (main file!)
-├── SUBMISSION.md # Guidelines for submitting plugins
-├── VERIFICATION.md # Verification checklist
-└── assets/ # Optional: screenshots, badges
- └── screenshots/
+├── plugins/
+│ ├── clock-simple/ # one directory per official plugin
+│ │ ├── manifest.json # source of truth for the plugin's version
+│ │ ├── manager.py
+│ │ ├── config_schema.json
+│ │ └── requirements.txt
+│ └── ...
+├── plugins.json # the registry the Plugin Store reads
+└── update_registry.py # regenerates plugins.json from the manifests
```
-## Step 1: Create plugins.json
+- **Registry.** The Plugin Store fetches
+ `https://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/plugins.json`
+ (`PluginStoreManager.REGISTRY_URL` in `src/plugin_system/store_manager.py`)
+ and caches it for 15 minutes.
+- **Monorepo plugins** have `repo` set to the ledmatrix-plugins URL and
+ `plugin_path` set to their directory (`plugins/`). The store downloads
+ just that directory (GitHub API, falling back to the repository ZIP), so
+ installed copies have no `.git` directory.
+- **Third-party plugins** keep their own repository: `repo` points at it and
+ `plugin_path` is empty. The store installs them with `git clone`, falling
+ back to an archive download.
+- **Updates.** For registry plugins the store compares the installed
+ manifest's `version` with the entry's `latest_version`. Git tags and GitHub
+ releases are not read.
-This is the **core file** that the Plugin Store reads from.
-
-**Important**: The registry stores **metadata only** (name, description, repo URL, etc.).
-The plugin store always pulls the latest commit information directly from GitHub, so you never manage semantic versions here.
-
-**File**: `plugins.json`
+## A registry entry
```json
{
- "last_updated": "2025-01-09T12:00:00Z",
- "plugins": [
- {
- "id": "clock-simple",
- "name": "Simple Clock",
- "description": "A clean, simple clock display with date and time",
- "author": "ChuckBuilds",
- "category": "time",
- "tags": ["clock", "time", "date"],
- "repo": "https://github.com/ChuckBuilds/ledmatrix-clock-simple",
- "branch": "main",
- "stars": 12,
- "downloads": 156,
- "last_updated": "2025-01-09",
- "last_commit": "abc1234",
- "verified": true,
- "screenshot": "https://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/assets/screenshots/clock-simple.png"
- }
- ]
+ "id": "clock-simple",
+ "name": "Simple Clock",
+ "description": "A clean, simple clock display with date and time",
+ "author": "ChuckBuilds",
+ "category": "time",
+ "tags": ["clock", "time", "date"],
+ "repo": "https://github.com/ChuckBuilds/ledmatrix-plugins",
+ "branch": "main",
+ "plugin_path": "plugins/clock-simple",
+ "stars": 0,
+ "downloads": 0,
+ "last_updated": "2026-09-03",
+ "verified": true,
+ "screenshot": "",
+ "latest_version": "1.0.0"
}
```
-**Note**: There's no need for version arrays or release tracking. The store queries GitHub for the latest commit details (date, branch, and short SHA) whenever metadata is requested.
+[plugin_registry_template.json](plugin_registry_template.json) shows a
+monorepo entry and a third-party entry.
-## Step 2: Create Plugin Repositories
+Don't edit `latest_version` or `last_updated` by hand for monorepo plugins:
+`update_registry.py` in ledmatrix-plugins writes them from each plugin's
+`manifest.json`.
-Each plugin should have its own repository:
+## Adding or changing an official plugin
-### Example: Creating clock-simple Plugin
+1. Add or edit `plugins//` in the monorepo. The store refuses
+ a manifest without `id`, `name`, `class_name` and `display_modes`; also
+ set `version`.
+2. Bump `version` in the plugin's `manifest.json` for every change, or users
+ won't be offered the update.
+3. Run `python update_registry.py` in ledmatrix-plugins and commit the
+ updated `plugins.json` with the plugin change.
+4. Open a pull request. The monorepo's CI and review steps are described in
+ its `SUBMISSION.md`.
-1. **Create new repo**: `ledmatrix-clock-simple`
-2. **Add plugin files**:
- ```
- ledmatrix-clock-simple/
- ├── manifest.json
- ├── manager.py
- ├── requirements.txt
- ├── config_schema.json
- ├── README.md
- └── assets/
- ```
-3. **Add to registry**: Update `plugins.json` in ledmatrix-plugins repo
+## Adding a third-party plugin
-## Step 3: Update README.md
+Test it with **Plugin Manager → Install from GitHub → Install Single Plugin**
+(or `POST /api/v3/plugins/install-from-url`), then follow the "own
+repository" option in the monorepo's `SUBMISSION.md` to request a registry
+entry.
-Create a comprehensive README for your plugin registry:
-
-```markdown
-# LEDMatrix Official Plugins
-
-Official plugin registry for [LEDMatrix](https://github.com/ChuckBuilds/LEDMatrix).
-
-## Available Plugins
-
-
-
-| Plugin | Description | Category | Last Updated |
-|--------|-------------|----------|--------------|
-| [Simple Clock](https://github.com/ChuckBuilds/ledmatrix-clock-simple) | Clean clock display | Time | 2025-01-09 |
-| [NHL Scores](https://github.com/ChuckBuilds/ledmatrix-nhl-scores) | Live NHL scores | Sports | 2025-01-07 |
-
-## Installation
-
-All plugins can be installed through the LEDMatrix web interface:
-
-1. Open web interface (http://your-pi-ip:5000)
-2. Open the **Plugin Manager** tab
-3. Browse or search the **Plugin Store** section
-4. Click **Install**
-
-Or via API:
-```bash
-curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \
- -H "Content-Type: application/json" \
- -d '{"plugin_id": "clock-simple"}'
-```
-
-## Submitting Plugins
-
-See [SUBMISSION.md](SUBMISSION.md) for guidelines on submitting your plugin.
-
-## Creating Plugins
-
-See the main [LEDMatrix Plugin Developer Guide](https://github.com/ChuckBuilds/LEDMatrix/wiki/Plugin-Development).
-
-## Plugin Categories
-
-- **Time**: Clocks, timers, countdowns
-- **Sports**: Scoreboards, schedules, stats
-- **Weather**: Forecasts, current conditions
-- **Finance**: Stocks, crypto, market data
-- **Entertainment**: Games, animations, media
-- **Custom**: Unique displays
-```
-
-## Step 4: Create SUBMISSION.md
-
-Guidelines for community plugin submissions:
-
-```markdown
-# Plugin Submission Guidelines
-
-Want to add your plugin to the official registry? Follow these steps!
-
-## Requirements
-
-Before submitting, ensure your plugin:
-
-- ✅ Has a complete `manifest.json` with all required fields
-- ✅ Follows the plugin architecture specification
-- ✅ Has comprehensive README documentation
-- ✅ Includes example configuration
-- ✅ Has been tested on Raspberry Pi hardware
-- ✅ Follows coding standards (PEP 8)
-- ✅ Has proper error handling
-- ✅ Uses logging appropriately
-- ✅ Has no hardcoded API keys or secrets
-
-## Submission Process
-
-1. **Test Your Plugin**
- ```bash
- # Install via URL on your Pi
- curl -X POST http://your-pi:5000/api/v3/plugins/install-from-url \
- -H "Content-Type: application/json" \
- -d '{"repo_url": "https://github.com/you/ledmatrix-your-plugin"}'
- ```
-
-2. **Fork This Repo**
- Fork [ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins)
-
-4. **Update plugins.json**
- Add your plugin entry (metadata only - no versions needed):
- ```json
- {
- "id": "your-plugin",
- "name": "Your Plugin Name",
- "description": "What it does",
- "author": "YourName",
- "category": "custom",
- "tags": ["tag1", "tag2"],
- "repo": "https://github.com/you/ledmatrix-your-plugin",
- "branch": "main",
- "verified": false
- }
- ```
-
-5. **Submit Pull Request**
- Create PR with title: "Add plugin: your-plugin-name"
-
-## Review Process
-
-1. **Automated Checks**: Manifest validation, structure check
-2. **Code Review**: Manual review of plugin code
-3. **Testing**: Test installation and basic functionality
-4. **Approval**: If accepted, merged and marked as verified
-
-## After Approval
-
-- Plugin appears in official store
-- `verified: true` badge shown
-- Included in plugin count
-- Featured in README
-
-## Updating Your Plugin
-
-Whenever you push new commits to your plugin repository's default branch, the store will automatically surface the latest commit timestamp and short SHA. No release tagging or manifest version bumps are required.
-
-You only need to update the registry if:
-- Plugin metadata changes (name, description, category, etc.)
-- Repository URL changes
-- You want to update the verified status
-
-To update metadata:
-1. Fork the registry repo
-2. Update plugins.json with new metadata
-3. Submit PR with changes
-4. We'll review and merge
-
-## Questions?
-
-Open an issue in this repo or the main LEDMatrix repo.
-```
-
-## Step 5: Create VERIFICATION.md
-
-Checklist for verifying plugins:
-
-```markdown
-# Plugin Verification Checklist
-
-Use this checklist when reviewing plugin submissions.
-
-## Code Review
-
-- [ ] Follows BasePlugin interface
-- [ ] Has proper error handling
-- [ ] Uses logging appropriately
-- [ ] No hardcoded secrets/API keys
-- [ ] Follows Python coding standards
-- [ ] Has type hints where appropriate
-- [ ] Has docstrings for classes/methods
-
-## Manifest Validation
-
-- [ ] All required fields present
-- [ ] Valid JSON syntax
-- [ ] Last updated metadata present when available
-- [ ] Category is valid
-- [ ] Tags are descriptive
-
-## Functionality
-
-- [ ] Installs successfully via URL
-- [ ] Dependencies install correctly
-- [ ] Plugin loads without errors
-- [ ] Display output works correctly
-- [ ] Configuration schema validates
-- [ ] Example config provided
-
-## Documentation
-
-- [ ] README.md exists and is comprehensive
-- [ ] Installation instructions clear
-- [ ] Configuration options documented
-- [ ] Examples provided
-- [ ] License specified
-
-## Security
-
-- [ ] No malicious code
-- [ ] Safe dependency versions
-- [ ] Appropriate permissions
-- [ ] No network access without disclosure
-- [ ] No file system access outside plugin dir
-
-## Testing
-
-- [ ] Tested on Raspberry Pi
-- [ ] Works with 64x32 matrix (minimum)
-- [ ] No excessive CPU/memory usage
-- [ ] No crashes or freezes
-
-## Approval
-
-Once all checks pass:
-- [ ] Set `verified: true` in plugins.json
-- [ ] Merge PR
-- [ ] Welcome plugin author
-- [ ] Update stats (downloads, stars)
-```
-
-## Step 6: Workflow for Adding Plugins
-
-### For Your Own Plugins
+## Testing locally
```bash
-# 1. Create plugin in separate repo
-mkdir ledmatrix-clock-simple
-cd ledmatrix-clock-simple
-# ... create plugin files ...
+# Validate a plugin headlessly (from LEDMatrix)
+python3 scripts/check_plugin.py --plugin
-# 2. Push to GitHub
-git init
-git add .
-git commit -m "Initial commit"
-git remote add origin https://github.com/ChuckBuilds/ledmatrix-clock-simple
-git push -u origin main
-
-# 3. Update registry
-cd ../ledmatrix-plugins
-# Edit plugins.json to add new entry
-git add plugins.json
-git commit -m "Add clock-simple plugin"
-git push
-```
-
-### For Community Submissions
-
-```bash
-# 1. Receive PR on ledmatrix-plugins repo
-# 2. Review using VERIFICATION.md checklist
-# 3. Test installation:
-curl -X POST http://pi:5000/api/v3/plugins/install-from-url \
- -H "Content-Type: application/json" \
- -d '{"repo_url": "https://github.com/contributor/plugin"}'
-
-# 4. If approved, merge PR
-# 5. Set verified: true in plugins.json
-```
-
-## Step 7: Maintaining the Registry
-
-### Regular Updates
-
-```bash
-# Refresh local clones of all plugin repos
-python3 scripts/update_plugin_repos.py
-
-# (Re-)create local plugin repo checkouts from the registry
-python3 scripts/setup_plugin_repos.py
-
-# Audit installed plugins for manifest/schema problems
-python3 scripts/audit_plugins.py
-
-# Validate a single plugin
-python3 scripts/check_plugin.py --plugin
-```
-
-Registry regeneration (`update_registry.py`) lives in the
-`ledmatrix-plugins` monorepo, not in this repo.
-
-## Converting Existing Plugins
-
-To convert your existing plugins (hello-world, clock-simple) to this system:
-
-### 1. Move to Separate Repos
-
-```bash
-# For each plugin in plugins/
-cd plugins/clock-simple
-
-# Create new repo
-git init
-git add .
-git commit -m "Extract clock-simple plugin"
-git remote add origin https://github.com/ChuckBuilds/ledmatrix-clock-simple
-git push -u origin main
-git tag v1.0.0
-git push origin v1.0.0
-```
-
-### 2. Add to Registry
-
-Update `plugins.json` in ledmatrix-plugins repo.
-
-### 3. Keep or Remove from Main Repo
-
-Decision:
-- **Keep**: Leave in main repo for backward compatibility
-- **Remove**: Delete from main repo, users install via store
-
-## Testing the Registry
-
-After setting up:
-
-```bash
-# Test registry fetch
-curl https://raw.githubusercontent.com/ChuckBuilds/ledmatrix-plugins/main/plugins.json
-
-# Test plugin installation
+# Fetch the registry the way the store does
python3 -c "
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
-registry = store.fetch_registry()
-print(f'Found {len(registry[\"plugins\"])} plugins')
+store = PluginStoreManager(plugins_dir='plugin-repos')
+print(len(store.fetch_registry(force_refresh=True).get('plugins', [])), 'plugins')
"
```
-## Benefits of This Setup
-
-✅ **Centralized Discovery**: One place to find all official plugins
-✅ **Decentralized Storage**: Each plugin in its own repo
-✅ **Easy Maintenance**: Update registry without touching plugin code
-✅ **Community Friendly**: Anyone can submit via PR
-✅ **Version Control**: Track plugin versions and updates
-✅ **Verified Badge**: Show trust with verified plugins
-
-## Next Steps
-
-1. Create `plugins.json` in your repo
-2. Update the registry URL in LEDMatrix code (already done)
-3. Create SUBMISSION.md and README.md
-4. Move existing plugins to separate repos
-5. Add them to the registry
-6. Announce the plugin store!
+To work on monorepo plugins against a LEDMatrix checkout, see
+[MULTI_ROOT_WORKSPACE_SETUP.md](MULTI_ROOT_WORKSPACE_SETUP.md) and the
+[Plugin Development Guide](PLUGIN_DEVELOPMENT_GUIDE.md).
## References
-- Plugin Store Implementation: See `PLUGIN_IMPLEMENTATION_SUMMARY.md`
-- User Guide: See `PLUGIN_STORE_GUIDE.md`
-- Architecture: See `PLUGIN_ARCHITECTURE_SPEC.md`
-
+- Plugin Store user guide: [PLUGIN_STORE_GUIDE.md](PLUGIN_STORE_GUIDE.md)
+- Plugin architecture (historical): [PLUGIN_ARCHITECTURE_SPEC.md](PLUGIN_ARCHITECTURE_SPEC.md)
+- [ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins)
diff --git a/docs/PLUGIN_STORE_GUIDE.md b/docs/PLUGIN_STORE_GUIDE.md
index fbec2a36..3f2867c5 100644
--- a/docs/PLUGIN_STORE_GUIDE.md
+++ b/docs/PLUGIN_STORE_GUIDE.md
@@ -4,13 +4,22 @@
The LEDMatrix Plugin Store allows you to discover, install, and manage display plugins for your LED matrix. Install curated plugins from the official registry or add custom plugins directly from any GitHub repository.
+In the web interface, the **Plugin Store** is a section of the **Plugin
+Manager** tab (below the installed plugins), followed by an **Install from
+GitHub** section.
+
+The Python examples below pass `plugins_dir="plugin-repos"`:
+`PluginStoreManager()` defaults to `plugins`, but the web interface and the
+plugin loader use `plugin_system.plugins_directory` from `config.json`
+(`plugin-repos` by default).
+
---
## Quick Reference
### Install from Store
```bash
-# Web UI: Plugin Store → Search → Click Install
+# Web UI: Plugin Manager → Plugin Store section → Search → Click Install
# API:
curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \
-H "Content-Type: application/json" \
@@ -19,7 +28,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \
### Install from GitHub URL
```bash
-# Web UI: Plugin Store → "Install from URL" → Paste URL
+# Web UI: Plugin Manager → Install from GitHub → "Install Single Plugin" → Paste URL
# API:
curl -X POST http://your-pi-ip:5000/api/v3/plugins/install-from-url \
-H "Content-Type: application/json" \
@@ -57,7 +66,7 @@ The official plugin store contains curated, verified plugins that have been revi
**Via Web Interface:**
1. Open the web interface at http://your-pi-ip:5000
-2. Navigate to the "Plugin Store" tab
+2. Navigate to the "Plugin Manager" tab and scroll to the "Plugin Store" section
3. Browse or search for plugins
4. Click "Install" on the desired plugin
5. Wait for installation to complete
@@ -74,7 +83,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \
```python
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir="plugin-repos")
success = store.install_plugin('clock-simple')
if success:
print("Plugin installed!")
@@ -90,10 +99,11 @@ Install any plugin directly from a GitHub repository, even if it's not in the of
**Via Web Interface:**
1. Open the web interface
-2. Navigate to the "Plugin Store" tab
-3. Find the "Install from URL" section
+2. Navigate to the "Plugin Manager" tab
+3. Find "Install Single Plugin" in the "Install from GitHub" section
4. Paste the GitHub repository URL (e.g., `https://github.com/user/ledmatrix-my-plugin`)
-5. Click "Install from URL"
+ and optionally a branch
+5. Click "Install"
6. Review the warning about unverified plugins
7. Confirm installation
8. Wait for installation to complete
@@ -110,7 +120,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install-from-url \
```python
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir="plugin-repos")
result = store.install_from_url('https://github.com/user/ledmatrix-my-plugin')
if result['success']:
@@ -144,7 +154,7 @@ curl "http://your-pi-ip:5000/api/v3/plugins/store/list?tags=nhl&tags=hockey"
```python
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir="plugin-repos")
# Search by query
results = store.search_plugins(query="hockey")
@@ -175,12 +185,9 @@ curl "http://your-pi-ip:5000/api/v3/plugins/installed"
```python
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
-installed = store.list_installed_plugins()
-
-for plugin_id in installed:
- info = store.get_installed_plugin_info(plugin_id)
- print(f"{info['name']} (Last updated: {info.get('last_updated', 'unknown')})")
+store = PluginStoreManager(plugins_dir="plugin-repos")
+for plugin_id in store.list_installed_plugins():
+ print(plugin_id)
```
### Enable/Disable Plugins
@@ -216,7 +223,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/update \
```python
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir="plugin-repos")
success = store.update_plugin('clock-simple')
```
@@ -239,7 +246,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/uninstall \
```python
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir="plugin-repos")
success = store.uninstall_plugin('clock-simple')
```
@@ -296,13 +303,17 @@ When installing from a custom GitHub URL, you'll see a warning about installing
### Plugin Won't Install
-**Problem:** Installation fails with "Failed to clone or download repository"
+**Problem:** Installation fails
**Solutions:**
-- Check that git is installed: `which git`
+- Plugins from the official registry live in the `ledmatrix-plugins`
+ monorepo and are downloaded, not cloned: the store fetches the plugin's
+ directory through the GitHub API and falls back to extracting it from the
+ repository ZIP, so git is not involved (the installed copy has no `.git`)
+- A plugin installed by URL from its own repository is cloned with git,
+ falling back to an archive download; check `which git` if that fails
- Verify the GitHub URL is correct
- Check your internet connection
-- The system will automatically try ZIP download as fallback
### Plugin Won't Load
@@ -410,10 +421,10 @@ As a plugin developer, you can share your plugin with others even before it's in
2. Share the URL with users
3. Users install via:
- Open the LEDMatrix web interface
- - Click "Plugin Store" tab
- - Scroll to "Install from URL"
+ - Open the "Plugin Manager" tab
+ - Scroll to "Install from GitHub" → "Install Single Plugin"
- Paste the URL
- - Click "Install from URL"
+ - Click "Install"
---
@@ -425,14 +436,14 @@ For advanced users, manage plugins via command line:
# Install from registry
python3 -c "
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir='plugin-repos')
store.install_plugin('clock-simple')
"
# Install from URL
python3 -c "
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir='plugin-repos')
result = store.install_from_url('https://github.com/user/plugin')
print(result)
"
@@ -440,16 +451,15 @@ print(result)
# List installed
python3 -c "
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir='plugin-repos')
for plugin_id in store.list_installed_plugins():
- info = store.get_installed_plugin_info(plugin_id)
- print(f'{plugin_id}: {info[\"name\"]} (Last updated: {info.get(\"last_updated\", \"unknown\")})')
+ print(plugin_id)
"
# Uninstall
python3 -c "
from src.plugin_system.store_manager import PluginStoreManager
-store = PluginStoreManager()
+store = PluginStoreManager(plugins_dir='plugin-repos')
store.uninstall_plugin('clock-simple')
"
```
@@ -468,10 +478,17 @@ A: Yes, you can install anytime, but you must restart the display to load them.
A: The existing copy will be replaced with the latest code from the repository.
**Q: Can I install multiple versions of the same plugin?**
-A: No, each plugin ID maps to a single checkout of the repository's default branch.
+A: No, each plugin ID maps to a single installed copy.
**Q: How do I update all plugins at once?**
-A: Currently, you need to update each plugin individually. Bulk update is planned for a future release.
+A: Click **Check & Update All** at the top of the Plugin Manager tab. You can
+also turn on weekly automatic updates (off by default) in the General tab;
+they update LEDMatrix itself and then the installed plugins
+(`web_interface/auto_update.py`).
+
+**Q: How does the store know an update is available?**
+A: For registry plugins it compares the installed manifest's `version` with
+the registry's `latest_version`; git tags and releases are not consulted.
**Q: Can plugins access my API keys from config_secrets.json?**
A: Yes, if a plugin needs API keys, it can access them like core managers do.
diff --git a/docs/PLUGIN_WEB_UI_ACTIONS.md b/docs/PLUGIN_WEB_UI_ACTIONS.md
index 3d0d79d4..1b472147 100644
--- a/docs/PLUGIN_WEB_UI_ACTIONS.md
+++ b/docs/PLUGIN_WEB_UI_ACTIONS.md
@@ -25,9 +25,6 @@ Add a `web_ui_actions` array to your plugin's `manifest.json`:
"script": "path/to/script.py",
"oauth_flow": false,
"section_description": "Optional section description",
- "success_message": "Action completed successfully",
- "error_message": "Action failed",
- "step1_message": "Authorization URL generated",
"step2_prompt": "Please paste the full redirect URL:",
"step2_button_text": "Complete Authentication"
}
@@ -52,12 +49,13 @@ Add a `web_ui_actions` array to your plugin's `manifest.json`:
- **`color`**: Color theme - `"blue"`, `"green"`, `"red"`, `"yellow"`, `"purple"`, etc. (defaults to `"blue"`)
- **`oauth_flow`**: Set to `true` for OAuth-style two-step authentication flows
- **`section_description`**: Description shown at the top of the actions section
-- **`success_message`**: Message shown on successful completion
-- **`error_message`**: Message shown on failure
-- **`step1_message`**: Message shown after step 1 (for OAuth flows)
- **`step2_prompt`**: Prompt text for step 2 redirect URL input
- **`step2_button_text`**: Button text for step 2 (defaults to "Complete Authentication")
+The status messages shown after an action runs come from the action's
+response (`message`), with built-in fallbacks such as "Action completed
+successfully"; there are no manifest fields for them.
+
## Action Types
### Script Actions (`type: "script"`)
@@ -98,7 +96,6 @@ For two-step OAuth flows (e.g., Spotify):
"color": "green",
"script": "authenticate_spotify.py",
"oauth_flow": true,
- "step1_message": "Authorization URL generated",
"step2_prompt": "Please paste the full redirect URL from Spotify after authorization:",
"step2_button_text": "Complete Authentication"
}
@@ -131,9 +128,6 @@ Here's a complete example for the `ledmatrix-music` plugin:
"script": "authenticate_spotify.py",
"oauth_flow": true,
"section_description": "Authenticate with Spotify or YouTube Music to enable music playback display.",
- "success_message": "Spotify authentication completed successfully",
- "error_message": "Spotify authentication failed",
- "step1_message": "Authorization URL generated",
"step2_prompt": "Please paste the full redirect URL from Spotify after authorization:",
"step2_button_text": "Complete Authentication"
},
@@ -145,9 +139,7 @@ Here's a complete example for the `ledmatrix-music` plugin:
"button_text": "Authenticate YTM",
"icon": "fab fa-youtube",
"color": "red",
- "script": "authenticate_ytm.py",
- "success_message": "YouTube Music authentication completed successfully",
- "error_message": "YouTube Music authentication failed"
+ "script": "authenticate_ytm.py"
}
]
}
diff --git a/docs/PLUGIN_WEB_UI_ACTIONS_EXAMPLE.json b/docs/PLUGIN_WEB_UI_ACTIONS_EXAMPLE.json
index 25e171b6..e05e1de1 100644
--- a/docs/PLUGIN_WEB_UI_ACTIONS_EXAMPLE.json
+++ b/docs/PLUGIN_WEB_UI_ACTIONS_EXAMPLE.json
@@ -37,9 +37,6 @@
"script": "authenticate_spotify.py",
"oauth_flow": true,
"section_description": "Authenticate with Spotify or YouTube Music to enable music playback display.",
- "success_message": "Spotify authentication completed successfully",
- "error_message": "Spotify authentication failed",
- "step1_message": "Authorization URL generated",
"step2_prompt": "Please paste the full redirect URL from Spotify after authorization:",
"step2_button_text": "Complete Authentication"
},
@@ -51,9 +48,7 @@
"button_text": "Authenticate YTM",
"icon": "fab fa-youtube",
"color": "red",
- "script": "authenticate_ytm.py",
- "success_message": "YouTube Music authentication completed successfully",
- "error_message": "YouTube Music authentication failed"
+ "script": "authenticate_ytm.py"
}
],
"versions": [
diff --git a/docs/README.md b/docs/README.md
index c0bfeed4..475cb92e 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -63,7 +63,6 @@ Going deeper:
- [REST_API_REFERENCE.md](REST_API_REFERENCE.md) — all web-interface HTTP endpoints
- [PLUGIN_API_REFERENCE.md](PLUGIN_API_REFERENCE.md) — Python APIs available to plugins
- [DEVELOPER_QUICK_REFERENCE.md](DEVELOPER_QUICK_REFERENCE.md) — common dev tasks
-- [PLUGIN_IMPLEMENTATION_SUMMARY.md](PLUGIN_IMPLEMENTATION_SUMMARY.md) — what the plugin system actually does
## Contributing to LEDMatrix itself
@@ -73,18 +72,17 @@ Going deeper:
- [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) — breaking changes between releases
- [SPORTS_UNIFICATION.md](SPORTS_UNIFICATION.md) — how the sports scoreboard base classes are organized
-## Archive
+## Audits
-`docs/archive/` holds older guides that have been superseded or describe
-features that have been removed. They are kept for historical context and
-git history but should not be relied on.
+- [audits/WEB_UI_AUDIT_2026-09.md](audits/WEB_UI_AUDIT_2026-09.md) — web UI audit (September 2026)
## Contributing to the docs
- Markdown only, professional tone, minimal emoji.
- Prefer adding to an existing page over creating a new one. If you add a
new page, link it from this index in the section it belongs to.
-- If a page becomes obsolete, move it to `docs/archive/` rather than
- deleting it, so links don't rot.
+- If a page becomes obsolete, delete it (it stays in the repository
+ history) and fix the links to it; `test/test_doc_links.py` fails on
+ broken relative links.
- Keep examples runnable — paths, commands, and config keys here should
match what's actually in the repo.
diff --git a/docs/REST_API_REFERENCE.md b/docs/REST_API_REFERENCE.md
index 202e2324..e96e0132 100644
--- a/docs/REST_API_REFERENCE.md
+++ b/docs/REST_API_REFERENCE.md
@@ -1116,39 +1116,6 @@ List uploaded images for a plugin.
}
```
-### Authenticate Spotify
-
-**POST** `/api/v3/plugins/authenticate/spotify`
-
-Spotify OAuth for the music plugin (`ledmatrix-music`; the plugin is fixed,
-not taken from the body). Two steps: call with an empty body to get the
-authorization URL, then call again with the URL Spotify redirected to.
-
-**Request Body** (step 2):
-```json
-{
- "redirect_url": "http://127.0.0.1:8888/callback?code=..."
-}
-```
-
-**Response** (step 1, fields at the top level):
-```json
-{
- "status": "success",
- "message": "Authorization URL generated",
- "auth_url": "https://accounts.spotify.com/authorize?..."
-}
-```
-
-Step 2 returns `status`, `message` and the script's `output`.
-
-### Authenticate YouTube Music
-
-**POST** `/api/v3/plugins/authenticate/ytm`
-
-Run the music plugin's YouTube Music authentication script. No body. Returns
-`status`, `message` and the script's `output`.
-
### Upload Calendar Credentials
**POST** `/api/v3/plugins/calendar/upload-credentials`
@@ -1966,7 +1933,10 @@ restarted to pick up changes). See
## Plugin-specific endpoints
-A handful of endpoints belong to individual plugins.
+A handful of endpoints belong to individual plugins. The music plugin's
+Spotify and YouTube Music sign-in and the Of-The-Day data files go through the
+plugin's own web UI actions ([Execute Plugin Action](#execute-plugin-action))
+rather than dedicated routes.
### Calendar
@@ -1976,23 +1946,6 @@ List the calendars on the authenticated Google account. Used by the calendar
plugin's config UI. Returns `calendars` at the top level. The upload and
authenticate endpoints are under [Plugins](#upload-calendar-credentials).
-### Of The Day
-
-**POST** `/api/v3/plugins/of-the-day/json/upload`
-
-Upload JSON data files (multipart field `files`) as Of-The-Day categories.
-Returns `uploaded_files` and `total_files` at the top level.
-
-**POST** `/api/v3/plugins/of-the-day/json/delete`
-
-Delete an uploaded data file.
-
-```json
-{
- "file_id": "category_name"
-}
-```
-
### Plugin Static Assets
**GET** `/api/v3/plugins//static/`
diff --git a/docs/SSH_UNAVAILABLE_AFTER_INSTALL.md b/docs/SSH_UNAVAILABLE_AFTER_INSTALL.md
index 4978b4c6..51811fc5 100644
--- a/docs/SSH_UNAVAILABLE_AFTER_INSTALL.md
+++ b/docs/SSH_UNAVAILABLE_AFTER_INSTALL.md
@@ -8,7 +8,7 @@ After running `first_time_install.sh`, SSH may become unavailable for the follow
**Primary Cause**: The WiFi monitor service (`ledmatrix-wifi-monitor`) automatically enables Access Point (AP) mode when it detects that the Raspberry Pi is not connected to WiFi. When AP mode is active:
-- The Pi creates its own WiFi network: **LEDMatrix-Setup** (password: `ledmatrix123`)
+- The Pi creates its own WiFi network: **LEDMatrix-Setup** (open, no password)
- The Pi's WiFi interface (`wlan0`) switches from client mode to AP mode
- **This disconnects the Pi from your original WiFi network**
- SSH becomes unavailable because the Pi is no longer on your network
@@ -45,7 +45,7 @@ If the script reboots the Pi (which it recommends), network services may restart
1. **Find the AP Network**:
- Look for a WiFi network named **LEDMatrix-Setup** on your phone/computer
- - Default password: `ledmatrix123`
+ - It is an open network: no password
2. **Connect to the AP**:
- Connect your device to the **LEDMatrix-Setup** network
@@ -53,7 +53,7 @@ If the script reboots the Pi (which it recommends), network services may restart
3. **SSH via AP Mode**:
```bash
- ssh devpi@192.168.4.1
+ ssh ledpi@192.168.4.1
```
4. **Disable AP Mode and Reconnect to WiFi**:
@@ -96,7 +96,7 @@ sudo nmcli device wifi connect "YourWiFiSSID" password "YourPassword"
If your Pi is connected via Ethernet:
- SSH should remain available via Ethernet even if WiFi is in AP mode
-- Connect via: `ssh devpi@`
+- Connect via: `ssh ledpi@`
### Option 4: Physical Access
@@ -134,14 +134,22 @@ sudo systemctl disable ledmatrix-wifi-monitor
### Method 3: Configure WiFi Monitor to Not Auto-Enable AP
-Edit the WiFi monitor configuration to prevent automatic AP mode:
+Turn off `auto_enable_ap_mode` so the monitor never starts AP mode on its
+own (you can still enable AP mode by hand). Either switch off
+**Auto-Enable AP Mode** in the web interface's **WiFi** tab, or use the API:
```bash
-# Edit the WiFi config (if it exists)
-nano /home/devpi/LEDMatrix/config/wifi_config.json
+curl -X POST http://:5000/api/v3/wifi/ap/auto-enable \
+ -H "Content-Type: application/json" \
+ -d '{"auto_enable_ap_mode": false}'
+```
-# Or modify the WiFi monitor daemon behavior
-# (requires code changes to wifi_monitor_daemon.py)
+Or set `"auto_enable_ap_mode": false` in `config/wifi_config.json` by hand.
+The monitor daemon reads `wifi_config.json` when it starts, so whichever way
+you change the setting, restart it afterwards:
+
+```bash
+sudo systemctl restart ledmatrix-wifi-monitor
```
## Verification Steps
@@ -149,7 +157,7 @@ nano /home/devpi/LEDMatrix/config/wifi_config.json
After regaining SSH access, verify your installation:
```bash
-cd /home/devpi/LEDMatrix
+cd ~/LEDMatrix # wherever you installed LEDMatrix
./scripts/verify_installation.sh
```
@@ -225,7 +233,7 @@ different responses:
- Prevention and tuning: [LOW_MEMORY_BOARDS.md](LOW_MEMORY_BOARDS.md)
**To regain SSH**:
-1. Connect to **LEDMatrix-Setup** AP network (password: `ledmatrix123`)
+1. Connect to **LEDMatrix-Setup** AP network (open, no password)
2. SSH to `192.168.4.1`
3. Disable AP mode and reconnect to your WiFi network
4. Or disable the WiFi monitor service if not needed
diff --git a/docs/WEB_INTERFACE_GUIDE.md b/docs/WEB_INTERFACE_GUIDE.md
index ebe6a1c7..7b442c6d 100644
--- a/docs/WEB_INTERFACE_GUIDE.md
+++ b/docs/WEB_INTERFACE_GUIDE.md
@@ -95,7 +95,8 @@ Configure basic system settings:
plugins
- **Plugin System Settings** — including the `plugins_directory` (default
`plugin-repos/`) used by the plugin loader
-- **Autostart** options for the display service
+- **Web Display Autostart** — whether the web interface service starts
+ with the system (`web_display_autostart`)
- **Automatic updates** — once a week, update LEDMatrix and every installed
plugin with a newer version. Off by default. Runs 2–5 AM local time when
possible, otherwise within a day of being due. The last result and next
@@ -246,7 +247,7 @@ View real-time system logs:
### Changing Display Brightness
1. Open the **Display** tab
-2. Adjust the **Brightness** slider (0–100)
+2. Adjust the **Brightness** slider (1–100)
3. Click **Save**
4. Click **Restart Display Service** on the **Overview** tab
@@ -428,10 +429,10 @@ The web interface uses modern web technologies:
### File Locations
-**Configuration:**
-- Main config: `/config/config.json`
-- Secrets: `/config/config_secrets.json`
-- WiFi config: `/config/wifi_config.json`
+**Configuration** (relative to the LEDMatrix folder, e.g. `~/LEDMatrix`):
+- Main config: `config/config.json`
+- Secrets: `config/config_secrets.json`
+- WiFi config: `config/wifi_config.json`
**Logs:**
- Display service: `sudo journalctl -u ledmatrix -f`
@@ -444,7 +445,7 @@ The web interface uses modern web technologies:
the Plugin Store install flow and the schema loader additionally
probe `plugins/` so dev symlinks created by
`scripts/dev/dev_plugin_setup.sh` keep working.
-- Plugin config: `/config/config.json` (per-plugin sections)
+- Plugin config: `config/config.json` (per-plugin sections)
---
diff --git a/docs/WIFI_NETWORK_SETUP.md b/docs/WIFI_NETWORK_SETUP.md
index f341e4ff..cf8183e5 100644
--- a/docs/WIFI_NETWORK_SETUP.md
+++ b/docs/WIFI_NETWORK_SETUP.md
@@ -21,9 +21,8 @@ The LEDMatrix WiFi system provides automatic network configuration with intellig
**If not connected to WiFi:**
1. Wait 90 seconds after boot (AP mode activation grace period)
-2. Connect to WiFi network **LEDMatrix-Setup** (default password
- `ledmatrix123` — change it in `config/wifi_config.json` if you want
- an open network or a different password)
+2. Connect to WiFi network **LEDMatrix-Setup** (an open network: no
+ password)
3. Open browser to: `http://192.168.4.1:5000`
4. Open the **WiFi** tab
5. Scan, select your network, and connect
@@ -78,16 +77,8 @@ WiFi settings are stored in `config/wifi_config.json`:
```json
{
"ap_ssid": "LEDMatrix-Setup",
- "ap_password": "ledmatrix123",
"ap_channel": 7,
- "auto_enable_ap_mode": true,
- "saved_networks": [
- {
- "ssid": "YourNetwork",
- "password": "your-password",
- "saved_at": 1234567890.0
- }
- ]
+ "auto_enable_ap_mode": true
}
```
@@ -96,10 +87,8 @@ WiFi settings are stored in `config/wifi_config.json`:
| Setting | Default | Description |
|---------|---------|-------------|
| `ap_ssid` | `LEDMatrix-Setup` | Network name broadcast in AP mode |
-| `ap_password` | `ledmatrix123` | AP password. Set to `""` to make the network open (no password). |
| `ap_channel` | `7` | WiFi channel (1, 6, or 11 are non-overlapping) |
| `auto_enable_ap_mode` | `true` | Automatically enable AP mode when both WiFi and Ethernet are disconnected |
-| `saved_networks` | `[]` | Array of saved WiFi credentials |
### Auto-Enable AP Mode Behavior
@@ -214,8 +203,10 @@ The system checks connections in this order:
### AP Mode Settings
- **SSID**: `LEDMatrix-Setup` (configurable via `ap_ssid`)
-- **Network**: WPA2, default password `ledmatrix123` (configurable via
- `ap_password` — set to `""` for an open network)
+- **Network**: open (no password). Both AP paths create an open network
+ (`_create_hostapd_config()` and `_enable_ap_mode_nmcli_hotspot()` in
+ `src/wifi_manager.py`); an `ap_password` key in `wifi_config.json` is not
+ read
- **IP Address**: 192.168.4.1
- **DHCP Range**: 192.168.4.2 – 192.168.4.20
- **Channel**: 7 (configurable via `ap_channel`)
@@ -233,16 +224,12 @@ When AP mode is active:
### Security Recommendations
-**1. Change AP Password (Optional):**
-```json
-{
- "ap_password": "your-strong-password"
-}
-```
-
-**Note:** The default password is `ledmatrix123` for easy initial
-setup. Change it for any deployment in a public area, or set
-`ap_password` to `""` if you specifically want an open network.
+**1. Keep AP mode short-lived:**
+The setup network is open, so anyone nearby can join it and reach the web
+interface while it is up. AP mode only comes up when WiFi and Ethernet are
+both disconnected (after the 90 second grace period) and goes down again once
+the Pi is connected; in a public area, consider setting
+`auto_enable_ap_mode` to `false` and enabling AP mode by hand when needed.
**2. Use Non-Overlapping WiFi Channels:**
- Channels 1, 6, 11 are non-overlapping (2.4GHz)
@@ -256,21 +243,11 @@ sudo chmod 600 config/wifi_config.json
### Network Configuration Tips
-**Save Multiple Networks:**
-```json
-{
- "saved_networks": [
- {
- "ssid": "Home-Network",
- "password": "home-password"
- },
- {
- "ssid": "Office-Network",
- "password": "office-password"
- }
- ]
-}
-```
+**Multiple Networks:**
+
+NetworkManager remembers every network you connect to and rejoins whichever is
+in range; list them with `nmcli connection show`. LEDMatrix itself does not
+store WiFi passwords.
**Adjust Check Interval:**
diff --git a/docs/archive/AP_MODE_MANUAL_ENABLE.md b/docs/archive/AP_MODE_MANUAL_ENABLE.md
deleted file mode 100644
index cd02ed10..00000000
--- a/docs/archive/AP_MODE_MANUAL_ENABLE.md
+++ /dev/null
@@ -1,159 +0,0 @@
-# AP Mode Manual Enable Configuration
-
-## Overview
-
-By default, Access Point (AP) mode is **not automatically enabled** after installation. AP mode must be manually enabled through the web interface when needed.
-
-## Default Behavior
-
-- **Auto-enable AP mode**: `false` (disabled by default)
-- AP mode will **not** automatically activate when WiFi or Ethernet disconnects
-- AP mode can only be enabled manually through the web interface
-
-## Why Manual Enable?
-
-This prevents:
-- AP mode from activating unexpectedly after installation
-- Network conflicts when Ethernet is connected
-- SSH becoming unavailable due to automatic AP mode activation
-- Unnecessary AP mode activation on systems with stable network connections
-
-## Enabling AP Mode
-
-### Via Web Interface
-
-1. Navigate to the **WiFi** tab in the web interface
-2. Click the **"Enable AP Mode"** button
-3. AP mode will activate if:
- - WiFi is not connected AND
- - Ethernet is not connected
-
-### Via API
-
-```bash
-# Enable AP mode
-curl -X POST http://localhost:5001/api/v3/wifi/ap/enable
-
-# Disable AP mode
-curl -X POST http://localhost:5001/api/v3/wifi/ap/disable
-```
-
-## Enabling Auto-Enable (Optional)
-
-If you want AP mode to automatically enable when WiFi/Ethernet disconnect:
-
-### Via Web Interface
-
-1. Navigate to the **WiFi** tab
-2. Look for the **"Auto-enable AP Mode"** toggle or setting
-3. Enable the toggle
-
-### Via Configuration File
-
-Edit `config/wifi_config.json`:
-
-```json
-{
- "auto_enable_ap_mode": true,
- ...
-}
-```
-
-Then restart the WiFi monitor service:
-
-```bash
-sudo systemctl restart ledmatrix-wifi-monitor
-```
-
-### Via API
-
-```bash
-# Get current setting
-curl http://localhost:5001/api/v3/wifi/ap/auto-enable
-
-# Set auto-enable to true
-curl -X POST http://localhost:5001/api/v3/wifi/ap/auto-enable \
- -H "Content-Type: application/json" \
- -d '{"auto_enable_ap_mode": true}'
-```
-
-## Behavior Summary
-
-| Auto-Enable Setting | WiFi Status | Ethernet Status | AP Mode Behavior |
-|---------------------|-------------|-----------------|------------------|
-| `false` (default) | Any | Any | Manual enable only |
-| `true` | Connected | Any | Disabled |
-| `true` | Disconnected | Connected | Disabled |
-| `true` | Disconnected | Disconnected | **Auto-enabled** |
-
-## When Auto-Enable is Disabled (Default)
-
-- AP mode **never** activates automatically
-- Must be manually enabled via web UI or API
-- Once enabled, it will automatically disable when WiFi or Ethernet connects
-- Useful for systems with stable network connections (e.g., Ethernet)
-
-## When Auto-Enable is Enabled
-
-- AP mode automatically enables when both WiFi and Ethernet disconnect
-- AP mode automatically disables when WiFi or Ethernet connects
-- Useful for portable devices that may lose network connectivity
-
-## Troubleshooting
-
-### AP Mode Not Enabling
-
-1. **Check if WiFi or Ethernet is connected**:
- ```bash
- nmcli device status
- ```
-
-2. **Check auto-enable setting**:
- ```bash
- python3 -c "
- from src.wifi_manager import WiFiManager
- wm = WiFiManager()
- print('Auto-enable:', wm.config.get('auto_enable_ap_mode', False))
- "
- ```
-
-3. **Manually enable AP mode**:
- - Use web interface: WiFi tab → Enable AP Mode button
- - Or via API: `POST /api/v3/wifi/ap/enable`
-
-### AP Mode Enabling Unexpectedly
-
-1. **Check auto-enable setting**:
- ```bash
- cat config/wifi_config.json | grep auto_enable_ap_mode
- ```
-
-2. **Disable auto-enable**:
- ```bash
- # Edit config file
- nano config/wifi_config.json
- # Set "auto_enable_ap_mode": false
-
- # Restart service
- sudo systemctl restart ledmatrix-wifi-monitor
- ```
-
-3. **Check service logs**:
- ```bash
- sudo journalctl -u ledmatrix-wifi-monitor -f
- ```
-
-## Migration from Old Behavior
-
-If you have an existing installation that was auto-enabling AP mode:
-
-1. The default is now `false` (manual enable)
-2. Existing configs will be updated to include `auto_enable_ap_mode: false`
-3. If you want the old behavior, set `auto_enable_ap_mode: true` in `config/wifi_config.json`
-
-## Related Documentation
-
-- [WiFi Setup Guide](WIFI_SETUP.md)
-- [SSH Unavailable After Install](SSH_UNAVAILABLE_AFTER_INSTALL.md)
-- [WiFi Ethernet AP Mode Fix](WIFI_ETHERNET_AP_MODE_FIX.md)
-
diff --git a/docs/archive/AP_MODE_MANUAL_ENABLE_CHANGES.md b/docs/archive/AP_MODE_MANUAL_ENABLE_CHANGES.md
deleted file mode 100644
index 8092f501..00000000
--- a/docs/archive/AP_MODE_MANUAL_ENABLE_CHANGES.md
+++ /dev/null
@@ -1,186 +0,0 @@
-# AP Mode Manual Enable - Implementation Summary
-
-## Changes Made
-
-### 1. Configuration Option Added
-
-Added `auto_enable_ap_mode` configuration option to `config/wifi_config.json`:
-- **Default value**: `false` (manual enable only)
-- **Purpose**: Controls whether AP mode automatically enables when WiFi/Ethernet disconnect
-- **Migration**: Existing configs automatically get this field set to `false` if missing
-
-### 2. WiFi Manager Updates (`src/wifi_manager.py`)
-
-#### Added Configuration Field
-- Default config now includes `"auto_enable_ap_mode": False`
-- Existing configs are automatically migrated to include this field
-
-#### Updated `check_and_manage_ap_mode()` Method
-- Now checks `auto_enable_ap_mode` setting before auto-enabling AP mode
-- AP mode only auto-enables if:
- - `auto_enable_ap_mode` is `true` AND
- - WiFi is NOT connected AND
- - Ethernet is NOT connected
-- AP mode still auto-disables when WiFi or Ethernet connects (regardless of setting)
-- Manual AP mode (via web UI) works regardless of this setting
-
-### 3. Web Interface API Updates (`web_interface/blueprints/api_v3.py`)
-
-#### Updated `/wifi/status` Endpoint
-- Now returns `auto_enable_ap_mode` setting in response
-
-#### Added `/wifi/ap/auto-enable` GET Endpoint
-- Returns current `auto_enable_ap_mode` setting
-
-#### Added `/wifi/ap/auto-enable` POST Endpoint
-- Allows setting `auto_enable_ap_mode` via API
-- Accepts JSON: `{"auto_enable_ap_mode": true/false}`
-
-### 4. Documentation Updates
-
-- Updated `docs/WIFI_SETUP.md` with new configuration option
-- Created `docs/AP_MODE_MANUAL_ENABLE.md` with comprehensive guide
-- Created `docs/AP_MODE_MANUAL_ENABLE_CHANGES.md` (this file)
-
-## Behavior Changes
-
-### Before
-- AP mode automatically enabled when WiFi disconnected (if Ethernet also disconnected)
-- Could cause SSH to become unavailable after installation
-- No way to disable auto-enable behavior
-
-### After
-- AP mode **does not** automatically enable by default
-- Must be manually enabled through web UI or API
-- Can optionally enable auto-enable via configuration
-- Prevents unexpected AP mode activation
-
-## Migration
-
-### Existing Installations
-
-1. **Automatic Migration**:
- - When WiFi manager loads config, it automatically adds `auto_enable_ap_mode: false` if missing
- - No manual intervention required
-
-2. **To Enable Auto-Enable** (if desired):
- ```bash
- # Edit config file
- nano config/wifi_config.json
- # Set "auto_enable_ap_mode": true
-
- # Restart WiFi monitor service
- sudo systemctl restart ledmatrix-wifi-monitor
- ```
-
-### New Installations
-
-- Default behavior is manual enable only
-- No changes needed
-
-## Testing
-
-### Verify Default Behavior
-
-```bash
-# Check config
-python3 -c "
-from src.wifi_manager import WiFiManager
-wm = WiFiManager()
-print('Auto-enable:', wm.config.get('auto_enable_ap_mode', False))
-"
-# Should output: Auto-enable: False
-```
-
-### Test Manual Enable
-
-1. Disconnect WiFi and Ethernet
-2. AP mode should **not** automatically enable
-3. Enable via web UI: WiFi tab → Enable AP Mode
-4. AP mode should activate
-5. Connect WiFi or Ethernet
-6. AP mode should automatically disable
-
-### Test Auto-Enable (if enabled)
-
-1. Set `auto_enable_ap_mode: true` in config
-2. Restart WiFi monitor service
-3. Disconnect WiFi and Ethernet
-4. AP mode should automatically enable within 30 seconds
-5. Connect WiFi or Ethernet
-6. AP mode should automatically disable
-
-## API Usage Examples
-
-### Get Auto-Enable Setting
-```bash
-curl http://localhost:5001/api/v3/wifi/ap/auto-enable
-```
-
-### Set Auto-Enable to True
-```bash
-curl -X POST http://localhost:5001/api/v3/wifi/ap/auto-enable \
- -H "Content-Type: application/json" \
- -d '{"auto_enable_ap_mode": true}'
-```
-
-### Set Auto-Enable to False
-```bash
-curl -X POST http://localhost:5001/api/v3/wifi/ap/auto-enable \
- -H "Content-Type: application/json" \
- -d '{"auto_enable_ap_mode": false}'
-```
-
-### Get WiFi Status (includes auto-enable)
-```bash
-curl http://localhost:5001/api/v3/wifi/status
-```
-
-## Files Modified
-
-1. `src/wifi_manager.py`
- - Added `auto_enable_ap_mode` to default config
- - Added migration logic for existing configs
- - Updated `check_and_manage_ap_mode()` to respect setting
-
-2. `web_interface/blueprints/api_v3.py`
- - Updated `/wifi/status` to include auto-enable setting
- - Added `/wifi/ap/auto-enable` GET endpoint
- - Added `/wifi/ap/auto-enable` POST endpoint
-
-3. `docs/WIFI_SETUP.md`
- - Updated documentation with new configuration option
- - Updated WiFi monitor daemon description
-
-4. `docs/AP_MODE_MANUAL_ENABLE.md` (new)
- - Comprehensive guide for manual enable feature
-
-## Benefits
-
-1. **Prevents SSH Loss**: AP mode won't activate automatically after installation
-2. **User Control**: Users can choose whether to enable auto-enable
-3. **Ethernet-Friendly**: Works well with hardwired connections
-4. **Backward Compatible**: Existing installations automatically migrate
-5. **Flexible**: Can still enable auto-enable if desired
-
-## Deployment
-
-### On Existing Installations
-
-1. **No action required** - automatic migration on next WiFi manager initialization
-2. **Restart WiFi monitor** (optional, to apply immediately):
- ```bash
- sudo systemctl restart ledmatrix-wifi-monitor
- ```
-
-### On New Installations
-
-- Default behavior is already manual enable
-- No additional configuration needed
-
-## Related Issues Fixed
-
-- SSH becoming unavailable after installation
-- AP mode activating when Ethernet is connected
-- Unexpected AP mode activation on stable network connections
-
diff --git a/docs/archive/BACKGROUND_SERVICE_README.md b/docs/archive/BACKGROUND_SERVICE_README.md
deleted file mode 100644
index 4761ae04..00000000
--- a/docs/archive/BACKGROUND_SERVICE_README.md
+++ /dev/null
@@ -1,208 +0,0 @@
-# Background Data Service for LEDMatrix
-
-## Overview
-
-The Background Data Service is a new feature that implements background threading for season data fetching to prevent blocking the main display loop. This significantly improves responsiveness and user experience during data fetching operations.
-
-## Key Benefits
-
-- **Non-blocking**: Season data fetching no longer blocks the main display loop
-- **Immediate Response**: Returns cached or partial data immediately while fetching complete data in background
-- **Configurable**: Can be enabled/disabled per sport with customizable settings
-- **Thread-safe**: Uses proper synchronization for concurrent access
-- **Retry Logic**: Automatic retry with exponential backoff for failed requests
-- **Progress Tracking**: Comprehensive logging and statistics
-
-## Architecture
-
-### Core Components
-
-1. **BackgroundDataService**: Main service class managing background threads
-2. **FetchRequest**: Represents individual fetch operations
-3. **FetchResult**: Contains results of fetch operations
-4. **Sport Managers**: Updated to use background service
-
-### How It Works
-
-1. **Cache Check**: First checks for cached data and returns immediately if available
-2. **Background Fetch**: If no cache, starts background thread to fetch complete season data
-3. **Partial Data**: Returns immediate partial data (current/recent games) for quick display
-4. **Completion**: Background fetch completes and caches full dataset
-5. **Future Requests**: Subsequent requests use cached data for instant response
-
-## Configuration
-
-### NFL Configuration Example
-
-```json
-{
- "nfl_scoreboard": {
- "enabled": true,
- "background_service": {
- "enabled": true,
- "max_workers": 3,
- "request_timeout": 30,
- "max_retries": 3,
- "priority": 2
- }
- }
-}
-```
-
-### Configuration Options
-
-- **enabled**: Enable/disable background service (default: true)
-- **max_workers**: Maximum number of background threads (default: 3)
-- **request_timeout**: HTTP request timeout in seconds (default: 30)
-- **max_retries**: Maximum retry attempts for failed requests (default: 3)
-- **priority**: Request priority (higher = more important, default: 2)
-
-## Implementation Status
-
-### Phase 1: Background Season Data Fetching ✅ COMPLETED
-
-- [x] Created BackgroundDataService class
-- [x] Implemented thread-safe data caching
-- [x] Added retry logic with exponential backoff
-- [x] Modified NFL manager to use background service
-- [x] Added configuration support
-- [x] Created test script
-
-### Phase 2: Rollout to Other Sports (Next Steps)
-
-- [ ] Apply to NCAAFB manager
-- [ ] Apply to NBA manager
-- [ ] Apply to NHL manager
-- [ ] Apply to MLB manager
-- [ ] Apply to other sport managers
-
-## Testing
-
-### Test Script
-
-Run the test script to verify background service functionality:
-
-```bash
-python test_background_service.py
-```
-
-### Test Scenarios
-
-1. **Cache Hit**: Verify immediate return of cached data
-2. **Background Fetch**: Verify non-blocking background data fetching
-3. **Partial Data**: Verify immediate return of partial data during background fetch
-4. **Completion**: Verify background fetch completion and caching
-5. **Subsequent Requests**: Verify cache usage for subsequent requests
-6. **Service Disabled**: Verify fallback to synchronous fetching
-
-### Expected Results
-
-- Initial fetch should return partial data immediately (< 1 second)
-- Background fetch should complete within 10-30 seconds
-- Subsequent fetches should use cache (< 0.1 seconds)
-- No blocking of main display loop
-
-## Performance Impact
-
-### Before Background Service
-- Season data fetch: 10-30 seconds (blocking)
-- Display loop: Frozen during fetch
-- User experience: Poor responsiveness
-
-### After Background Service
-- Initial response: < 1 second (partial data)
-- Background fetch: 10-30 seconds (non-blocking)
-- Display loop: Continues normally
-- User experience: Excellent responsiveness
-
-## Monitoring
-
-### Logs
-
-The service provides comprehensive logging:
-
-```
-[NFL] Background service enabled with 3 workers
-[NFL] Starting background fetch for 2024 season schedule...
-[NFL] Using 15 immediate events while background fetch completes
-[NFL] Background fetch completed for 2024: 256 events
-```
-
-### Statistics
-
-Access service statistics:
-
-```python
-stats = background_service.get_statistics()
-print(f"Total requests: {stats['total_requests']}")
-print(f"Cache hits: {stats['cached_hits']}")
-print(f"Average fetch time: {stats['average_fetch_time']:.2f}s")
-```
-
-## Error Handling
-
-### Automatic Retry
-- Failed requests are automatically retried with exponential backoff
-- Maximum retry attempts are configurable
-- Failed requests are logged with error details
-
-### Fallback Behavior
-- If background service is disabled, falls back to synchronous fetching
-- If background fetch fails, returns partial data if available
-- Graceful degradation ensures system continues to function
-
-## Future Enhancements
-
-### Phase 2 Features
-- Apply to all sport managers
-- Priority-based request queuing
-- Dynamic worker scaling
-- Request batching for efficiency
-
-### Phase 3 Features
-- Real-time data streaming
-- WebSocket support for live updates
-- Advanced caching strategies
-- Performance analytics dashboard
-
-## Troubleshooting
-
-### Common Issues
-
-1. **Background service not starting**
- - Check configuration: `background_service.enabled = true`
- - Verify cache manager is properly initialized
- - Check logs for initialization errors
-
-2. **Slow background fetches**
- - Increase `request_timeout` in configuration
- - Check network connectivity
- - Monitor API rate limits
-
-3. **Memory usage**
- - Background service automatically cleans up old requests
- - Adjust `max_workers` if needed
- - Monitor cache size
-
-### Debug Mode
-
-Enable debug logging for detailed information:
-
-```python
-logging.getLogger('src.background_data_service').setLevel(logging.DEBUG)
-```
-
-## Contributing
-
-When adding background service support to new sport managers:
-
-1. Import the background service
-2. Initialize in `__init__` method
-3. Update data fetching method to use background service
-4. Add configuration options
-5. Test thoroughly
-6. Update documentation
-
-## License
-
-This feature is part of the LEDMatrix project and follows the same license terms.
diff --git a/docs/archive/BROWSER_ERRORS_EXPLANATION.md b/docs/archive/BROWSER_ERRORS_EXPLANATION.md
deleted file mode 100644
index b56ea83c..00000000
--- a/docs/archive/BROWSER_ERRORS_EXPLANATION.md
+++ /dev/null
@@ -1,136 +0,0 @@
-# Browser Console Errors - Explanation
-
-## Summary
-
-**You don't need to worry about these errors.** They are harmless and don't affect functionality. We've improved error suppression to hide them from the console.
-
-## Error Types
-
-### 1. Permissions-Policy Header Warnings
-
-**Examples:**
-```text
-Error with Permissions-Policy header: Unrecognized feature: 'browsing-topics'.
-Error with Permissions-Policy header: Unrecognized feature: 'run-ad-auction'.
-Error with Permissions-Policy header: Origin trial controlled feature not enabled: 'join-ad-interest-group'.
-```
-
-**What they are:**
-- Browser warnings about experimental/advertising features in HTTP headers
-- These features are not used by our application
-- The browser is just informing you that it doesn't recognize these policy features
-
-**Why they appear:**
-- Some browsers or extensions set these headers
-- They're informational warnings, not actual errors
-- They don't affect functionality at all
-
-**Status:** ✅ **Harmless** - Now suppressed in console
-
-### 2. HTMX insertBefore Errors
-
-**Example:**
-```javascript
-TypeError: Cannot read properties of null (reading 'insertBefore')
- at At (htmx.org@1.9.10:1:22924)
-```
-
-**What they are:**
-- HTMX library timing/race condition issues
-- Occurs when HTMX tries to swap content but the target element is temporarily null
-- Usually happens during rapid content updates or when elements are being removed/added
-
-**Why they appear:**
-- HTMX dynamically swaps HTML content
-- Sometimes the target element is removed or not yet in the DOM when HTMX tries to insert
-- This is a known issue with HTMX in certain scenarios
-
-**Impact:**
-- ✅ **No functional impact** - HTMX handles these gracefully
-- ✅ **Content still loads correctly** - The swap just fails silently and retries
-- ✅ **User experience unaffected** - Users don't see any issues
-
-**Status:** ✅ **Harmless** - Now suppressed in console
-
-## What We've Done
-
-### Error Suppression Improvements
-
-1. **Enhanced HTMX Error Suppression:**
- - More comprehensive detection of HTMX-related errors
- - Catches `insertBefore` errors from HTMX regardless of format
- - Suppresses timing/race condition errors
-
-2. **Permissions-Policy Warning Suppression:**
- - Suppresses all Permissions-Policy header warnings
- - Includes specific feature warnings (browsing-topics, run-ad-auction, etc.)
- - Prevents console noise from harmless browser warnings
-
-3. **HTMX Validation:**
- - Added `htmx:beforeSwap` validation to prevent some errors
- - Checks if target element exists before swapping
- - Reduces but doesn't eliminate all timing issues
-
-## When to Worry
-
-You should only be concerned about errors if:
-
-1. **Functionality is broken** - If buttons don't work, forms don't submit, or content doesn't load
-2. **Errors are from your code** - Errors in `plugins.html`, `base.html`, or other application files
-3. **Network errors** - Failed API calls or connection issues
-4. **User-visible issues** - Users report problems
-
-## Current Status
-
-✅ **All harmless errors are now suppressed**
-✅ **HTMX errors are caught and handled gracefully**
-✅ **Permissions-Policy warnings are hidden**
-✅ **Application functionality is unaffected**
-
-## Technical Details
-
-### HTMX insertBefore Errors
-
-**Root Cause:**
-- HTMX uses `insertBefore` to swap content into the DOM
-- Sometimes the parent node is null when HTMX tries to insert
-- This happens due to:
- - Race conditions during rapid updates
- - Elements being removed before swap completes
- - Dynamic content loading timing issues
-
-**Why It's Safe:**
-- HTMX has built-in error handling
-- Failed swaps don't break the application
-- Content still loads via other mechanisms
-- No data loss or corruption
-
-### Permissions-Policy Warnings
-
-**Root Cause:**
-- Modern browsers support Permissions-Policy HTTP headers
-- Some features are experimental or not widely supported
-- Browsers warn when they encounter unrecognized features
-
-**Why It's Safe:**
-- We don't use these features
-- The warnings are informational only
-- No security or functionality impact
-
-## Monitoring
-
-If you want to see actual errors (not suppressed ones), you can:
-
-1. **Temporarily disable suppression:**
- - Comment out the error suppression code in `base.html`
- - Only do this for debugging
-
-2. **Check browser DevTools:**
- - Look for errors in the Network tab (actual failures)
- - Check Console for non-HTMX errors
- - Monitor user reports for functionality issues
-
-## Conclusion
-
-**These errors are completely harmless and can be safely ignored.** They're just noise in the console that doesn't affect the application's functionality. We've improved the error suppression to hide them so you can focus on actual issues if they arise.
-
diff --git a/docs/archive/CAPTIVE_PORTAL_TESTING.md b/docs/archive/CAPTIVE_PORTAL_TESTING.md
deleted file mode 100644
index 4174f4c0..00000000
--- a/docs/archive/CAPTIVE_PORTAL_TESTING.md
+++ /dev/null
@@ -1,445 +0,0 @@
-# Captive Portal Testing Guide
-
-This guide explains how to test the captive portal WiFi setup functionality.
-
-## Prerequisites
-
-1. **Raspberry Pi with LEDMatrix installed**
-2. **WiFi adapter** (built-in or USB)
-3. **Test devices** (smartphone, tablet, or laptop)
-4. **Access to Pi** (SSH or direct access)
-
-## Important: Before Testing
-
-**⚠️ Make sure you have a way to reconnect!**
-
-Before starting testing, ensure you have:
-- **Ethernet cable** (if available) as backup connection
-- **SSH access** via another method (Ethernet, direct connection)
-- **Physical access** to Pi (keyboard/monitor) as last resort
-- **Your WiFi credentials** saved/noted down
-
-**If testing fails, see:** [Reconnecting After Testing](RECONNECT_AFTER_CAPTIVE_PORTAL_TESTING.md)
-
-**Quick recovery script:** `sudo ./scripts/emergency_reconnect.sh`
-
-## Pre-Testing Setup
-
-### 0. Verify WiFi is Ready (IMPORTANT!)
-
-**⚠️ CRITICAL: Run this BEFORE disconnecting Ethernet!**
-
-```bash
-sudo ./scripts/verify_wifi_before_testing.sh
-```
-
-This script will verify:
-- WiFi interface exists and is enabled
-- WiFi can scan for networks
-- You have saved WiFi connections (for reconnecting)
-- Required services are ready
-- Current network status
-
-**Do NOT disconnect Ethernet until this script passes all checks!**
-
-### 1. Ensure WiFi Monitor Service is Running
-
-```bash
-sudo systemctl status ledmatrix-wifi-monitor
-```
-
-If not running:
-```bash
-sudo systemctl start ledmatrix-wifi-monitor
-sudo systemctl enable ledmatrix-wifi-monitor
-```
-
-### 2. Disconnect Pi from WiFi/Ethernet
-
-**⚠️ Only do this AFTER running the verification script!**
-
-To test captive portal, the Pi should NOT be connected to any network:
-
-```bash
-# First, verify WiFi is ready (see step 0 above)
-sudo ./scripts/verify_wifi_before_testing.sh
-
-# Check current network status
-nmcli device status
-
-# Disconnect WiFi (if connected)
-sudo nmcli device disconnect wlan0
-
-# Disconnect Ethernet (if connected)
-# Option 1: Unplug Ethernet cable (safest)
-# Option 2: Via command (if you're sure WiFi works):
-sudo nmcli device disconnect eth0
-
-# Verify disconnection
-nmcli device status
-# Both should show "disconnected" or "unavailable"
-```
-
-### 3. Enable AP Mode
-
-You can enable AP mode manually or wait for it to auto-enable (if `auto_enable_ap_mode` is true):
-
-**Manual enable via web interface:**
-- Access web interface at `http://:5000` (if still accessible)
-- Go to WiFi tab
-- Click "Enable AP Mode"
-
-**Manual enable via command line:**
-```bash
-python3 -c "from src.wifi_manager import WiFiManager; wm = WiFiManager(); print(wm.enable_ap_mode())"
-```
-
-**Or via API:**
-```bash
-curl -X POST http://localhost:5000/api/v3/wifi/ap/enable
-```
-
-### 4. Verify AP Mode is Active
-
-```bash
-# Check hostapd service
-sudo systemctl status hostapd
-
-# Check dnsmasq service
-sudo systemctl status dnsmasq
-
-# Check if wlan0 is in AP mode
-iwconfig wlan0
-# Should show "Mode:Master"
-
-# Check IP address
-ip addr show wlan0
-# Should show 192.168.4.1
-```
-
-### 5. Verify DNSMASQ Configuration
-
-```bash
-# Check dnsmasq config
-sudo cat /etc/dnsmasq.conf
-
-# Should contain:
-# - address=/#/192.168.4.1
-# - address=/captive.apple.com/192.168.4.1
-# - address=/connectivitycheck.gstatic.com/192.168.4.1
-# - address=/www.msftconnecttest.com/192.168.4.1
-# - address=/detectportal.firefox.com/192.168.4.1
-```
-
-### 6. Verify Web Interface is Running
-
-```bash
-# Check if web service is running
-sudo systemctl status ledmatrix-web
-
-# Or check if Flask app is running
-ps aux | grep "web_interface"
-```
-
-## Testing Procedures
-
-### Test 1: DNS Redirection
-
-**Purpose:** Verify that DNS queries are redirected to the Pi.
-
-**Steps:**
-1. Connect a device to "LEDMatrix-Setup" network (password: `ledmatrix123`)
-2. Try to resolve any domain name:
- ```bash
- # On Linux/Mac
- nslookup google.com
- # Should return 192.168.4.1
-
- # On Windows
- nslookup google.com
- # Should return 192.168.4.1
- ```
-
-**Expected Result:** All DNS queries should resolve to 192.168.4.1
-
-### Test 2: HTTP Redirect (Manual Browser Test)
-
-**Purpose:** Verify that HTTP requests redirect to WiFi setup page.
-
-**Steps:**
-1. Connect device to "LEDMatrix-Setup" network
-2. Open a web browser
-3. Try to access any website:
- - `http://google.com`
- - `http://example.com`
- - `http://192.168.4.1` (direct IP)
-
-**Expected Result:** All requests should redirect to `http://192.168.4.1:5000/v3` (WiFi setup interface)
-
-### Test 3: Captive Portal Detection Endpoints
-
-**Purpose:** Verify that device detection endpoints respond correctly.
-
-**Test each endpoint:**
-
-```bash
-# iOS/macOS detection
-curl http://192.168.4.1:5000/hotspot-detect.html
-# Expected: HTML response with "Success"
-
-# Android detection
-curl -I http://192.168.4.1:5000/generate_204
-# Expected: HTTP 204 No Content
-
-# Windows detection
-curl http://192.168.4.1:5000/connecttest.txt
-# Expected: "Microsoft Connect Test"
-
-# Firefox detection
-curl http://192.168.4.1:5000/success.txt
-# Expected: "success"
-```
-
-**Expected Result:** Each endpoint should return the appropriate response
-
-### Test 4: iOS Device (iPhone/iPad)
-
-**Purpose:** Test automatic captive portal detection on iOS.
-
-**Steps:**
-1. On iPhone/iPad, go to Settings > Wi-Fi
-2. Connect to "LEDMatrix-Setup" network
-3. Enter password: `ledmatrix123`
-4. Wait a few seconds
-
-**Expected Result:**
-- iOS should automatically detect the captive portal
-- A popup should appear saying "Sign in to Network" or similar
-- Tapping it should open Safari with the WiFi setup page
-- The setup page should show the captive portal banner
-
-**If it doesn't auto-open:**
-- Open Safari manually
-- Try to visit any website (e.g., apple.com)
-- Should redirect to WiFi setup page
-
-### Test 5: Android Device
-
-**Purpose:** Test automatic captive portal detection on Android.
-
-**Steps:**
-1. On Android device, go to Settings > Wi-Fi
-2. Connect to "LEDMatrix-Setup" network
-3. Enter password: `ledmatrix123`
-4. Wait a few seconds
-
-**Expected Result:**
-- Android should show a notification: "Sign in to network" or "Network sign-in required"
-- Tapping the notification should open a browser with the WiFi setup page
-- The setup page should show the captive portal banner
-
-**If notification doesn't appear:**
-- Open Chrome browser
-- Try to visit any website
-- Should redirect to WiFi setup page
-
-### Test 6: Windows Laptop
-
-**Purpose:** Test captive portal on Windows.
-
-**Steps:**
-1. Connect Windows laptop to "LEDMatrix-Setup" network
-2. Enter password: `ledmatrix123`
-3. Wait a few seconds
-
-**Expected Result:**
-- Windows may show a notification about network sign-in
-- Opening any browser and visiting any website should redirect to WiFi setup page
-- Edge/Chrome may automatically open a sign-in window
-
-**Manual test:**
-- Open any browser
-- Visit `http://www.msftconnecttest.com` or any website
-- Should redirect to WiFi setup page
-
-### Test 7: API Endpoints Still Work
-
-**Purpose:** Verify that WiFi API endpoints function normally during AP mode.
-
-**Steps:**
-1. While connected to "LEDMatrix-Setup" network
-2. Test API endpoints:
-
-```bash
-# Status endpoint
-curl http://192.168.4.1:5000/api/v3/wifi/status
-
-# Scan networks
-curl http://192.168.4.1:5000/api/v3/wifi/scan
-```
-
-**Expected Result:** API endpoints should return JSON responses normally (not redirect)
-
-### Test 8: WiFi Connection Flow
-
-**Purpose:** Test the complete flow of connecting to WiFi via captive portal.
-
-**Steps:**
-1. Connect device to "LEDMatrix-Setup" network
-2. Wait for captive portal to redirect to setup page
-3. Click "Scan" to find available networks
-4. Select a network from the list
-5. Enter WiFi password
-6. Click "Connect"
-7. Wait for connection to establish
-
-**Expected Result:**
-- Device should connect to selected WiFi network
-- AP mode should automatically disable
-- Device should now be on the new network
-- Can access Pi via new network IP address
-
-## Troubleshooting
-
-### Issue: DNS Not Redirecting
-
-**Symptoms:** DNS queries resolve to actual IPs, not 192.168.4.1
-
-**Solutions:**
-1. Check dnsmasq config:
- ```bash
- sudo cat /etc/dnsmasq.conf | grep address
- ```
-2. Restart dnsmasq:
- ```bash
- sudo systemctl restart dnsmasq
- ```
-3. Check dnsmasq logs:
- ```bash
- sudo journalctl -u dnsmasq -n 50
- ```
-
-### Issue: HTTP Not Redirecting
-
-**Symptoms:** Browser shows actual websites instead of redirecting
-
-**Solutions:**
-1. Check if AP mode is active:
- ```bash
- python3 -c "from src.wifi_manager import WiFiManager; wm = WiFiManager(); print(wm._is_ap_mode_active())"
- ```
-2. Check Flask app logs for errors
-3. Verify web interface is running on port 5000
-4. Test redirect middleware manually:
- ```bash
- curl -I http://192.168.4.1:5000/google.com
- # Should return 302 redirect
- ```
-
-### Issue: Captive Portal Not Detected by Device
-
-**Symptoms:** Device doesn't show sign-in notification/popup
-
-**Solutions:**
-1. Verify detection endpoints are accessible:
- ```bash
- curl http://192.168.4.1:5000/hotspot-detect.html
- curl http://192.168.4.1:5000/generate_204
- ```
-2. Try manually opening browser and visiting any website
-3. Some devices require specific responses - check endpoint implementations
-4. Clear device's network settings and reconnect
-
-### Issue: Infinite Redirect Loop
-
-**Symptoms:** Browser keeps redirecting in a loop
-
-**Solutions:**
-1. Check that `/v3` path is in allowed_paths list
-2. Verify redirect middleware logic in `app.py`
-3. Check Flask logs for errors
-4. Ensure WiFi API endpoints are not being redirected
-
-### Issue: AP Mode Not Enabling
-
-**Symptoms:** Can't connect to "LEDMatrix-Setup" network
-
-**Solutions:**
-1. Check WiFi monitor service:
- ```bash
- sudo systemctl status ledmatrix-wifi-monitor
- ```
-2. Check WiFi config:
- ```bash
- cat config/wifi_config.json
- ```
-3. Manually enable AP mode:
- ```bash
- python3 -c "from src.wifi_manager import WiFiManager; wm = WiFiManager(); print(wm.enable_ap_mode())"
- ```
-4. Check hostapd logs:
- ```bash
- sudo journalctl -u hostapd -n 50
- ```
-
-## Verification Checklist
-
-- [ ] DNS redirection works (all domains resolve to 192.168.4.1)
-- [ ] HTTP redirect works (all websites redirect to setup page)
-- [ ] Captive portal detection endpoints respond correctly
-- [ ] iOS device auto-opens setup page
-- [ ] Android device shows sign-in notification
-- [ ] Windows device redirects to setup page
-- [ ] WiFi API endpoints still work during AP mode
-- [ ] Can successfully connect to WiFi via setup page
-- [ ] AP mode disables after WiFi connection
-- [ ] No infinite redirect loops
-- [ ] Captive portal banner appears on setup page when AP mode is active
-
-## Quick Test Script
-
-Save this as `test_captive_portal.sh`:
-
-```bash
-#!/bin/bash
-
-echo "Testing Captive Portal Functionality"
-echo "===================================="
-
-# Test DNS redirection
-echo -e "\n1. Testing DNS redirection..."
-nslookup google.com | grep -q "192.168.4.1" && echo "✓ DNS redirection works" || echo "✗ DNS redirection failed"
-
-# Test HTTP redirect
-echo -e "\n2. Testing HTTP redirect..."
-HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -L http://192.168.4.1:5000/google.com)
-[ "$HTTP_CODE" = "200" ] && echo "✓ HTTP redirect works" || echo "✗ HTTP redirect failed (got $HTTP_CODE)"
-
-# Test detection endpoints
-echo -e "\n3. Testing captive portal detection endpoints..."
-curl -s http://192.168.4.1:5000/hotspot-detect.html | grep -q "Success" && echo "✓ iOS endpoint works" || echo "✗ iOS endpoint failed"
-curl -s -o /dev/null -w "%{http_code}" http://192.168.4.1:5000/generate_204 | grep -q "204" && echo "✓ Android endpoint works" || echo "✗ Android endpoint failed"
-curl -s http://192.168.4.1:5000/connecttest.txt | grep -q "Microsoft" && echo "✓ Windows endpoint works" || echo "✗ Windows endpoint failed"
-curl -s http://192.168.4.1:5000/success.txt | grep -q "success" && echo "✓ Firefox endpoint works" || echo "✗ Firefox endpoint failed"
-
-# Test API endpoints
-echo -e "\n4. Testing API endpoints..."
-API_RESPONSE=$(curl -s http://192.168.4.1:5000/api/v3/wifi/status)
-echo "$API_RESPONSE" | grep -q "status" && echo "✓ API endpoints work" || echo "✗ API endpoints failed"
-
-echo -e "\nTesting complete!"
-```
-
-Make it executable and run:
-```bash
-chmod +x test_captive_portal.sh
-./test_captive_portal.sh
-```
-
-## Notes
-
-- **Port Number:** The web interface runs on port 5000 by default. If you've changed this, update all URLs accordingly.
-- **Network Range:** The AP uses 192.168.4.0/24 network. If you need a different range, update both hostapd and dnsmasq configs.
-- **Password:** Default AP password is `ledmatrix123`. Change it in `config/wifi_config.json` if needed.
-- **Testing on Same Device:** If testing from the Pi itself, you'll need a second device to connect to the AP network.
-
diff --git a/docs/archive/CAPTIVE_PORTAL_TROUBLESHOOTING.md b/docs/archive/CAPTIVE_PORTAL_TROUBLESHOOTING.md
deleted file mode 100644
index 149128a5..00000000
--- a/docs/archive/CAPTIVE_PORTAL_TROUBLESHOOTING.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# Captive Portal Troubleshooting Guide
-
-## Problem: Can't Access Web Interface When Connected to AP
-
-If you've connected to the "LEDMatrix-Setup" WiFi network but can't access the web interface, follow these steps:
-
-## Quick Checks
-
-### 1. Verify Web Server is Running
-
-```bash
-sudo systemctl status ledmatrix-web
-```
-
-If not running:
-```bash
-sudo systemctl start ledmatrix-web
-sudo systemctl enable ledmatrix-web
-```
-
-### 2. Try Direct IP Access
-
-On your phone/device, try accessing the web interface directly:
-- **http://192.168.4.1:5000/v3**
-- **http://192.168.4.1:5000**
-
-The port `:5000` is required - the web server runs on port 5000, not the standard port 80.
-
-### 3. Check DNS Resolution
-
-The captive portal uses DNS redirection. Try accessing:
-- **http://captive.apple.com** (should redirect to setup page)
-- **http://www.google.com** (should redirect to setup page)
-- **http://192.168.4.1:5000** (direct access - should always work)
-
-### 4. Verify AP Mode is Active
-
-```bash
-sudo systemctl status hostapd
-sudo systemctl status dnsmasq
-ip addr show wlan0 | grep 192.168.4.1
-```
-
-All should be active/running.
-
-### 5. Check Firewall
-
-If you have a firewall enabled, ensure port 5000 is open:
-
-```bash
-# For UFW
-sudo ufw allow 5000/tcp
-
-# For iptables
-sudo iptables -A INPUT -p tcp --dport 5000 -j ACCEPT
-```
-
-## Common Issues
-
-### Issue: "Can't connect to server" or "Connection refused"
-
-**Cause**: Web server not running or not listening on the correct interface.
-
-**Solution**:
-```bash
-sudo systemctl start ledmatrix-web
-sudo systemctl status ledmatrix-web
-```
-
-### Issue: DNS not resolving / "Server not found"
-
-**Cause**: dnsmasq not running or DNS redirection not configured.
-
-**Solution**:
-```bash
-# Check dnsmasq
-sudo systemctl status dnsmasq
-
-# Restart AP mode
-cd ~/LEDMatrix
-python3 -c "from src.wifi_manager import WiFiManager; wm = WiFiManager(); wm.disable_ap_mode(); wm.enable_ap_mode()"
-```
-
-### Issue: Page loads but shows "Connection Error" or blank page
-
-**Cause**: Web server is running but Flask app has errors.
-
-**Solution**:
-```bash
-# Check web server logs
-sudo journalctl -u ledmatrix-web -n 50 --no-pager
-
-# Restart web server
-sudo systemctl restart ledmatrix-web
-```
-
-### Issue: Phone connects but browser doesn't open automatically
-
-**Cause**: Some devices don't automatically detect captive portals.
-
-**Solution**: Manually open browser and go to:
-- **http://192.168.4.1:5000/v3**
-- Or try: **http://captive.apple.com** (iOS) or **http://www.google.com** (Android)
-
-## Testing Steps
-
-1. **Disconnect Ethernet** from Pi
-2. **Wait 30 seconds** for AP mode to start
-3. **Connect phone** to "LEDMatrix-Setup" network (password: `ledmatrix123`)
-4. **Open browser** on phone
-5. **Try these URLs**:
- - `http://192.168.4.1:5000/v3` (direct access)
- - `http://captive.apple.com` (iOS captive portal detection)
- - `http://www.google.com` (should redirect)
-
-## Automated Troubleshooting
-
-Run the troubleshooting script:
-
-```bash
-cd ~/LEDMatrix
-./scripts/troubleshoot_captive_portal.sh
-```
-
-This will check all components and provide specific fixes.
-
-## Manual AP Mode Test
-
-To manually test AP mode (bypassing Ethernet check):
-
-```bash
-cd ~/LEDMatrix
-python3 -c "
-from src.wifi_manager import WiFiManager
-wm = WiFiManager()
-
-# Temporarily disconnect Ethernet check
-# (This is for testing only - normally AP won't start with Ethernet)
-print('Enabling AP mode...')
-result = wm.enable_ap_mode()
-print('Result:', result)
-"
-```
-
-**Note**: This will fail if Ethernet is connected (by design). You must disconnect Ethernet first.
-
-## Still Not Working?
-
-1. **Check all services**:
- ```bash
- sudo systemctl status ledmatrix-web hostapd dnsmasq ledmatrix-wifi-monitor
- ```
-
-2. **Check logs**:
- ```bash
- sudo journalctl -u ledmatrix-web -f
- sudo journalctl -u ledmatrix-wifi-monitor -f
- ```
-
-3. **Verify network configuration**:
- ```bash
- ip addr show wlan0
- ip route show
- ```
-
-4. **Test from Pi itself**:
- ```bash
- curl http://192.168.4.1:5000/v3
- ```
-
-If it works from the Pi but not from your phone, it's likely a DNS or firewall issue.
-
diff --git a/docs/archive/CURSOR_PLUGIN_SCHEMA_AUDIT_PLAN.md b/docs/archive/CURSOR_PLUGIN_SCHEMA_AUDIT_PLAN.md
deleted file mode 100644
index 6dbd9291..00000000
--- a/docs/archive/CURSOR_PLUGIN_SCHEMA_AUDIT_PLAN.md
+++ /dev/null
@@ -1,202 +0,0 @@
-# Implementation Plan: Fix Config Schema Validation Issues
-
-Based on audit results showing 186 issues across 20 plugins.
-
-## Overview
-
-Three priority fixes identified from audit:
-1. **Priority 1 (HIGH)**: Remove core properties from required array - will fix ~150 issues
-2. **Priority 2 (MEDIUM)**: Verify default merging logic - will fix remaining required field issues
-3. **Priority 3 (LOW)**: Calendar plugin schema cleanup - will fix 3 extra field warnings
-
-## Priority 1: Remove Core Properties from Required Array
-
-### Problem
-Core properties (`enabled`, `display_duration`, `live_priority`) are system-managed but listed in schema `required` arrays. SchemaManager injects them into properties but doesn't remove them from `required`, causing validation failures.
-
-### Solution
-**File**: `src/plugin_system/schema_manager.py`
-**Location**: `validate_config_against_schema()` method, after line 295
-
-### Implementation Steps
-
-1. **Add code to remove core properties from required array**:
- ```python
- # After injecting core properties (around line 295), add:
- # Remove core properties from required array (they're system-managed)
- if "required" in enhanced_schema:
- core_prop_names = list(core_properties.keys())
- enhanced_schema["required"] = [
- field for field in enhanced_schema["required"]
- if field not in core_prop_names
- ]
- ```
-
-2. **Add logging for debugging** (optional but helpful):
- ```python
- if "required" in enhanced_schema and core_prop_names:
- removed_from_required = [
- field for field in enhanced_schema.get("required", [])
- if field in core_prop_names
- ]
- if removed_from_required and plugin_id:
- self.logger.debug(
- f"Removed core properties from required array for {plugin_id}: {removed_from_required}"
- )
- ```
-
-3. **Test the fix**:
- - Run audit script: `python scripts/audit_plugin_configs.py`
- - Expected: Issue count drops from 186 to ~30-40
- - All "enabled" related errors should be eliminated
-
-### Expected Outcome
-- All 20 plugins should no longer fail validation due to missing `enabled` field
-- ~150 issues resolved (all enabled-related validation errors)
-
-## Priority 2: Verify Default Merging Logic
-
-### Problem
-Some plugins have required fields with defaults that should be applied before validation. Need to verify the default merging happens correctly and handles nested objects.
-
-### Solution
-**File**: `web_interface/blueprints/api_v3.py`
-**Location**: `save_plugin_config()` method, around lines 3218-3221
-
-### Implementation Steps
-
-1. **Review current default merging logic**:
- - Check that `merge_with_defaults()` is called before validation (line 3220)
- - Verify it's called after preserving enabled state but before validation
-
-2. **Verify merge_with_defaults handles nested objects**:
- - Check `src/plugin_system/schema_manager.py` → `merge_with_defaults()` method
- - Ensure it recursively merges nested objects (it does use deep_merge)
- - Test with plugins that have nested required fields
-
-3. **Check if defaults are applied for nested required fields**:
- - Review how `generate_default_config()` extracts defaults from nested schemas
- - Verify nested required fields with defaults are included
-
-4. **Test with problematic plugins**:
- - `ledmatrix-weather`: required fields `api_key`, `location_city` (check if defaults exist)
- - `mqtt-notifications`: required field `mqtt` object (check if default exists)
- - `text-display`: required field `text` (check if default exists)
- - `ledmatrix-music`: required field `preferred_source` (check if default exists)
-
-5. **If defaults don't exist in schemas**:
- - Either add defaults to schemas, OR
- - Make fields optional in schemas if they're truly optional
-
-### Expected Outcome
-- Plugins with required fields that have schema defaults should pass validation
-- Issue count further reduced from ~30-40 to ~5-10
-
-## Priority 3: Calendar Plugin Schema Cleanup
-
-### Problem
-Calendar plugin config has fields not in schema:
-- `show_all_day` (config) but schema has `show_all_day_events` (field name mismatch)
-- `date_format` (not in schema, not used in manager.py)
-- `time_format` (not in schema, not used in manager.py)
-
-### Investigation Results
-- Schema defines: `show_all_day_events` (boolean, default: true)
-- Manager.py uses: `show_all_day_events` (line 82: `config.get('show_all_day_events', True)`)
-- Config has: `show_all_day` (wrong field name - should be `show_all_day_events`)
-- `date_format` and `time_format` appear to be deprecated (not used in manager.py)
-
-### Solution
-
-**File**: `config/config.json` → `calendar` section
-
-### Implementation Steps
-
-1. **Fix field name mismatch**:
- - Rename `show_all_day` → `show_all_day_events` in config.json
- - This matches the schema and manager.py code
-
-2. **Remove deprecated fields**:
- - Remove `date_format` from config (not used in code)
- - Remove `time_format` from config (not used in code)
-
-3. **Alternative (if fields are needed)**: Add `date_format` and `time_format` to schema
- - Only if these fields should be supported
- - Check if they're used anywhere else in the codebase
-
-4. **Test calendar plugin**:
- - Run audit for calendar plugin specifically
- - Verify no extra field warnings remain
- - Test calendar plugin functionality to ensure it still works
-
-### Expected Outcome
-- Calendar plugin shows 0 extra field warnings
-- Final issue count: ~3-5 (only edge cases remain)
-
-## Testing Strategy
-
-### After Each Priority Fix
-
-1. **Run local audit**:
- ```bash
- python scripts/audit_plugin_configs.py
- ```
-
-2. **Check issue count reduction**:
- - Priority 1: Should drop from 186 to ~30-40
- - Priority 2: Should drop from ~30-40 to ~5-10
- - Priority 3: Should drop from ~5-10 to ~3-5
-
-3. **Review specific plugin results**:
- ```bash
- python scripts/audit_plugin_configs.py --plugin
- ```
-
-### After All Fixes
-
-1. **Full audit run**:
- ```bash
- python scripts/audit_plugin_configs.py
- ```
-
-2. **Deploy to Pi**:
- ```bash
- ./scripts/deploy_to_pi.sh src/plugin_system/schema_manager.py web_interface/blueprints/api_v3.py
- ```
-
-3. **Run audit on Pi**:
- ```bash
- ./scripts/run_audit_on_pi.sh
- ```
-
-4. **Manual web interface testing**:
- - Access each problematic plugin's config page
- - Try saving configuration
- - Verify no validation errors appear
- - Check that configs save successfully
-
-## Success Criteria
-
-- [ ] Priority 1: All "enabled" related validation errors eliminated
-- [ ] Priority 1: Issue count reduced from 186 to ~30-40
-- [ ] Priority 2: Plugins with required fields + defaults pass validation
-- [ ] Priority 2: Issue count reduced to ~5-10
-- [ ] Priority 3: Calendar plugin extra field warnings resolved
-- [ ] Priority 3: Final issue count at ~3-5 (only edge cases)
-- [ ] All fixes work on Pi (not just local)
-- [ ] Web interface saves configs without validation errors
-
-## Files to Modify
-
-1. `src/plugin_system/schema_manager.py` - Remove core properties from required array
-2. `plugins/calendar/config_schema.json` OR `config/config.json` - Calendar cleanup (if needed)
-3. `web_interface/blueprints/api_v3.py` - May need minor adjustments for default merging (if needed)
-
-## Risk Assessment
-
-**Priority 1**: Low risk - Only affects validation logic, doesn't change behavior
-**Priority 2**: Low risk - Only ensures defaults are applied (already intended behavior)
-**Priority 3**: Very low risk - Only affects calendar plugin, cosmetic issue
-
-All changes are backward compatible and improve the system rather than changing core functionality.
-
diff --git a/docs/archive/DEBUG_WEB_ISSUE.md b/docs/archive/DEBUG_WEB_ISSUE.md
deleted file mode 100644
index 94c8a1df..00000000
--- a/docs/archive/DEBUG_WEB_ISSUE.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# Debug: Service Deactivated After Installing Dependencies
-
-## What Happened
-
-The service:
-1. ✅ Started successfully
-2. ✅ Installed dependencies
-3. ❌ Deactivated successfully (exited cleanly)
-
-This means it finished running but didn't actually launch the Flask app.
-
-## Most Likely Cause
-
-**`web_display_autostart` is probably set to `false` in your config.json**
-
-The service is designed to exit gracefully if this is false - it won't even try to start Flask.
-
-## Commands to Run RIGHT NOW
-
-### 1. Check the full logs to see what it said before exiting:
-```bash
-sudo journalctl -u ledmatrix-web -n 200 --no-pager | grep -A 5 -B 5 "web_display_autostart\|Configuration\|Launching\|will not"
-```
-
-This will show you if it said something like:
-- "Configuration 'web_display_autostart' is false or not set. Web interface will not be started."
-
-### 2. Check your config.json:
-```bash
-cat ~/LEDMatrix/config/config.json | grep web_display_autostart
-```
-
-### 3. If it's false or missing, set it to true:
-```bash
-nano ~/LEDMatrix/config/config.json
-```
-
-Find the line with `web_display_autostart` and change it to:
-```json
-"web_display_autostart": true,
-```
-
-If the line doesn't exist, add it near the top of the file (after the opening `{`):
-```json
-{
- "web_display_autostart": true,
- ... rest of config ...
-}
-```
-
-### 4. After fixing the config, restart the service:
-```bash
-sudo systemctl restart ledmatrix-web
-```
-
-### 5. Watch it start up:
-```bash
-sudo journalctl -u ledmatrix-web -f
-```
-
-You should see:
-- "Configuration 'web_display_autostart' is true. Starting web interface..."
-- "Dependencies installed successfully"
-- "Launching web interface v3: ..."
-- Flask starting up
-
-## Alternative: View ALL Recent Logs
-
-To see everything that happened:
-```bash
-sudo journalctl -u ledmatrix-web --since "5 minutes ago" --no-pager
-```
-
-This will show you the complete log including what happened after dependency installation.
-
diff --git a/docs/archive/FORM_VALIDATION_FIXES.md b/docs/archive/FORM_VALIDATION_FIXES.md
deleted file mode 100644
index bc840b1c..00000000
--- a/docs/archive/FORM_VALIDATION_FIXES.md
+++ /dev/null
@@ -1,181 +0,0 @@
-# Form Validation Fixes - Preventing "Invalid Form Control" Errors
-
-## Problem
-
-Browser was throwing errors: "An invalid form control with name='...' is not focusable" when:
-- Number inputs had values outside their min/max constraints
-- These fields were in collapsed/hidden nested sections
-- Browser couldn't focus hidden invalid fields to show validation errors
-
-## Root Cause
-
-1. **Value Clamping Missing**: Number inputs were generated with values that didn't respect min/max constraints
-2. **HTML5 Validation on Hidden Fields**: Browser validation tried to validate hidden fields but couldn't focus them
-3. **No Pre-Submit Validation**: Forms didn't fix invalid values before submission
-
-## Fixes Applied
-
-### 1. Plugin Configuration Form (`plugins.html`)
-
-**File**: `web_interface/templates/v3/partials/plugins.html`
-
-**Changes**:
-- ✅ Added value clamping in `generateFieldHtml()` (lines 1825-1844)
- - Clamps values to min/max when generating number inputs
- - Uses default value if provided
- - Ensures all generated fields have valid values
-- ✅ Added `novalidate` attribute to form (line 1998)
-- ✅ Added pre-submit validation fix in `handlePluginConfigSubmit()` (lines 1518-1533)
- - Fixes any invalid values before processing form data
- - Prevents "invalid form control is not focusable" errors
-
-### 2. Plugin Config in Base Template (`base.html`)
-
-**File**: `web_interface/templates/v3/base.html`
-
-**Changes**:
-- ✅ Added value clamping in number input generation (lines 1386-1407)
- - Same logic as plugins.html
- - Clamps values to min/max constraints
-- ✅ Fixed display_duration input (line 1654)
- - Uses `Math.max(5, Math.min(300, value))` to clamp value
-- ✅ Added global `fixInvalidNumberInputs()` function (lines 2409-2425)
- - Can be called from any form's onsubmit handler
- - Fixes invalid number inputs before submission
-
-### 3. Display Settings Form (`display.html`)
-
-**File**: `web_interface/templates/v3/partials/display.html`
-
-**Changes**:
-- ✅ Added `novalidate` attribute to form (line 13)
-- ✅ Added `onsubmit="fixInvalidNumberInputs(this); return true;"` (line 14)
-- ✅ Added local `fixInvalidNumberInputs()` function as fallback (lines 260-278)
-
-### 4. Durations Form (`durations.html`)
-
-**File**: `web_interface/templates/v3/partials/durations.html`
-
-**Changes**:
-- ✅ Added `novalidate` attribute to form (line 13)
-- ✅ Added `onsubmit="fixInvalidNumberInputs(this); return true;"` (line 14)
-
-## Implementation Details
-
-### Value Clamping Logic
-
-```javascript
-// Ensure value respects min/max constraints
-let fieldValue = value !== undefined ? value : (prop.default !== undefined ? prop.default : '');
-if (fieldValue !== '' && fieldValue !== undefined && fieldValue !== null) {
- const numValue = typeof fieldValue === 'string' ? parseFloat(fieldValue) : fieldValue;
- if (!isNaN(numValue)) {
- // Clamp value to min/max if constraints exist
- if (prop.minimum !== undefined && numValue < prop.minimum) {
- fieldValue = prop.minimum;
- } else if (prop.maximum !== undefined && numValue > prop.maximum) {
- fieldValue = prop.maximum;
- } else {
- fieldValue = numValue;
- }
- }
-}
-```
-
-### Pre-Submit Validation Fix
-
-```javascript
-// Fix invalid hidden fields before submission
-const allInputs = form.querySelectorAll('input[type="number"]');
-allInputs.forEach(input => {
- const min = parseFloat(input.getAttribute('min'));
- const max = parseFloat(input.getAttribute('max'));
- const value = parseFloat(input.value);
-
- if (!isNaN(value)) {
- if (!isNaN(min) && value < min) {
- input.value = min;
- } else if (!isNaN(max) && value > max) {
- input.value = max;
- }
- }
-});
-```
-
-## Files Modified
-
-1. ✅ `web_interface/templates/v3/partials/plugins.html`
- - Value clamping in field generation
- - `novalidate` on forms
- - Pre-submit validation fix
-
-2. ✅ `web_interface/templates/v3/base.html`
- - Value clamping in field generation
- - Fixed display_duration input
- - Global `fixInvalidNumberInputs()` function
-
-3. ✅ `web_interface/templates/v3/partials/display.html`
- - `novalidate` on form
- - `onsubmit` handler
- - Local fallback function
-
-4. ✅ `web_interface/templates/v3/partials/durations.html`
- - `novalidate` on form
- - `onsubmit` handler
-
-## Prevention Strategy
-
-### For Future Forms
-
-1. **Always clamp number input values** when generating forms:
- ```javascript
- // Clamp value to min/max
- if (min !== undefined && value < min) value = min;
- if (max !== undefined && value > max) value = max;
- ```
-
-2. **Add `novalidate` to forms** that use custom validation:
- ```html
-