Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,20 @@ repos:
args: [--ignore-missing-imports, --no-error-summary]
pass_filenames: false
files: ^src/

- repo: https://github.com/PyCQA/bandit
rev: 1.8.3
hooks:
- id: bandit
args:
- '-r'
- '-ll'
- '-c'
- 'bandit.yaml'
- '-x'
- './tests,./test,./venv,./.venv,./scripts/prove_security.py,./rpi-rgb-led-matrix-master'

- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.3
hooks:
- id: gitleaks
166 changes: 166 additions & 0 deletions docs/DEV_PREVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# 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

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

### Options

```bash
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:
```json
{
"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

```bash
# 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:

```bash
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

```python
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:

```python
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:

```bash
# 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
```
Loading