Files
LEDMatrix/docs/DEV_PREVIEW.md
Chuck 2bff30038e feat: add dev preview server, CLI render script, and visual test display manager
Adds local development tools for rapid plugin iteration without deploying to RPi:

- VisualTestDisplayManager: renders real pixels via PIL (same fonts/interface as production)
- Dev preview server (Flask): interactive web UI with plugin picker, auto-generated config
  forms, zoom/grid controls, and mock data support for API-dependent plugins
- CLI render script: render any plugin to PNG for AI-assisted visual feedback loops
- Updated test runner and conftest to auto-detect plugin-repos/ directory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 14:09:35 -05:00

5.2 KiB

Dev Preview & Visual Testing

Tools for rapid plugin development without deploying to the RPi.

Dev Preview Server

Interactive web UI for tweaking plugin configs and seeing the rendered display in real time.

Quick Start

python scripts/dev_server.py
# Opens at http://localhost:5001

Options

python scripts/dev_server.py --port 8080                          # Custom port
python scripts/dev_server.py --extra-dir /path/to/custom-plugin   # 3rd party plugins
python scripts/dev_server.py --debug                              # Flask debug mode

Workflow

  1. Select a plugin from the dropdown (auto-discovers from plugins/ and plugin-repos/)
  2. The config form auto-generates from the plugin's config_schema.json
  3. Tweak any config value — the display preview updates automatically
  4. Toggle "Auto" off for plugins with slow update() calls, then click "Render" manually
  5. Use the zoom slider to scale the tiny display (128x32) up for detailed inspection
  6. Toggle the grid overlay to see individual pixel boundaries

Mock Data for API-dependent Plugins

Many plugins fetch data from APIs (sports scores, weather, stocks). To render these locally, expand "Mock Data" and paste a JSON object with cache keys the plugin expects.

To find the cache keys a plugin uses, search its manager.py for self.cache_manager.set( calls.

Example for a sports plugin:

{
  "football_scores": {
    "games": [
      {"home": "Eagles", "away": "Chiefs", "home_score": 24, "away_score": 21, "status": "Final"}
    ]
  }
}

CLI Render Script

Render any plugin to a PNG image from the command line. Useful for AI-assisted development and scripted workflows.

Usage

# Basic — renders with default config
python scripts/render_plugin.py --plugin hello-world --output /tmp/hello.png

# Custom config
python scripts/render_plugin.py --plugin clock-simple \
  --config '{"timezone":"America/New_York","time_format":"12h"}' \
  --output /tmp/clock.png

# Different display dimensions
python scripts/render_plugin.py --plugin hello-world --width 64 --height 32 --output /tmp/small.png

# 3rd party plugin from a custom directory
python scripts/render_plugin.py --plugin my-plugin --plugin-dir /path/to/repo --output /tmp/my.png

# With mock API data
python scripts/render_plugin.py --plugin football-scoreboard \
  --mock-data /tmp/mock_scores.json \
  --output /tmp/football.png

Using with Claude Code / AI

Claude can run the render script, then read the output PNG (Claude is multimodal and can see images). This enables a visual feedback loop:

Claude → bash: python scripts/render_plugin.py --plugin hello-world --output /tmp/render.png
Claude → Read /tmp/render.png   ← Claude sees the actual rendered display
Claude → (makes code changes based on what it sees)
Claude → bash: python scripts/render_plugin.py --plugin hello-world --output /tmp/render2.png
Claude → Read /tmp/render2.png  ← verifies the visual change

VisualTestDisplayManager (for test suites)

A display manager that renders real pixels for use in pytest, without requiring hardware.

Basic Usage

from src.plugin_system.testing import VisualTestDisplayManager, MockCacheManager, MockPluginManager

def test_my_plugin_renders_title():
    display = VisualTestDisplayManager(width=128, height=32)
    cache = MockCacheManager()
    pm = MockPluginManager()

    plugin = MyPlugin(
        plugin_id='my-plugin',
        config={'enabled': True, 'title': 'Hello'},
        display_manager=display,
        cache_manager=cache,
        plugin_manager=pm
    )

    plugin.update()
    plugin.display(force_clear=True)

    # Verify pixels were drawn (not just that methods were called)
    pixels = list(display.image.getdata())
    assert any(p != (0, 0, 0) for p in pixels), "Display should not be blank"

    # Save snapshot for manual inspection
    display.save_snapshot('/tmp/test_my_plugin.png')

Pytest Fixture

A visual_display_manager fixture is available in plugin tests:

def test_rendering(visual_display_manager):
    visual_display_manager.draw_text("Test", x=10, y=10, color=(255, 255, 255))
    assert visual_display_manager.width == 128
    pixels = list(visual_display_manager.image.getdata())
    assert any(p != (0, 0, 0) for p in pixels)

Key Differences from MockDisplayManager

Feature MockDisplayManager VisualTestDisplayManager
Renders pixels No (logs calls only) Yes (real PIL rendering)
Loads fonts No Yes (same fonts as production)
Save to PNG No Yes (save_snapshot())
Call tracking Yes Yes (backwards compatible)
Use case Unit tests (method call assertions) Visual tests, dev preview

Plugin Test Runner

The test runner auto-detects plugin-repos/ for monorepo development:

# Auto-detect (tries plugins/ then plugin-repos/)
python scripts/run_plugin_tests.py

# Test specific plugin
python scripts/run_plugin_tests.py --plugin clock-simple

# Explicit directory
python scripts/run_plugin_tests.py --plugins-dir plugin-repos/

# With coverage
python scripts/run_plugin_tests.py --coverage --verbose