docs: make plugin guidance capability-first

Signed-off-by: barlind <tobias@barlind.se>
This commit is contained in:
barlind
2026-06-18 00:40:35 -07:00
committed by Bret Mogilefsky
parent 26dba55d7e
commit 7d0021d04a
12 changed files with 97 additions and 377 deletions
+11 -30
View File
@@ -1,6 +1,6 @@
# Plugin Authoring Guide
Slopsmith's plugin system is the primary extension point. Each plugin lives in `plugins/<name>/` with a `plugin.json` manifest and can provide any combination of frontend (HTML/JS), backend (Python routes), settings UI, diagnostics, and visualization renderers.
Slopsmith's plugin system is the primary extension point. Each plugin lives in `plugins/<name>/` with a `plugin.json` manifest that declares the capability domains, UI contributions, settings metadata, diagnostics, and runtime files the plugin participates in.
This guide is the entry point. Each topic below has a dedicated doc — read what's relevant to what you're building.
@@ -9,14 +9,14 @@ This guide is the entry point. Each topic below has a dedicated doc — read wha
```text
plugins/my_plugin/
├── plugin.json Manifest (required) — see docs/plugin-manifest.md
├── screen.html Optional — markup mounted at #plugin-my_plugin
├── screen.js Optional — runs in global scope on page load
├── routes.py Optional — exports setup(app, context)
├── settings.html Optional — settings-panel HTML
├── screen.html Optional — UI declared through `ui` contributions
├── screen.js Optional — hydrates declared frontend capabilities
├── routes.py Optional — backend provider/requester implementation
├── settings.html Optional — settings UI declared through `ui.settings`
└── requirements.txt Optional — pip deps auto-installed on load
```
The minimum viable plugin is a `plugin.json` with just `id` and `name`. Everything else is opt-in.
Start every new plugin by describing its Slopsmith-facing behavior in the manifest. A plugin with no behavior beyond metadata can be this small:
```json
{
@@ -26,7 +26,7 @@ The minimum viable plugin is a `plugin.json` with just `id` and `name`. Everythi
}
```
Capability-aware plugins should also declare the `capability-pipelines.v1` standard and the domains they participate in. Legacy fields such as `nav`, `screen`, `settings`, `type: "visualization"`, shortcuts, overlays, and mixer faders still work, but native metadata lets diagnostics, the Capability Inspector, and migration tooling explain plugin behavior without scraping private globals.
Any plugin that participates in app behavior should also declare `standards: ["capability-pipelines.v1"]`, native `capabilities`, and redaction-safe `ui` metadata. Capability declarations are the source of truth for diagnostics, the Capability Inspector, and migration tooling.
## Topics
@@ -34,12 +34,12 @@ Capability-aware plugins should also declare the `capability-pipelines.v1` stand
|---|---|---|
| **Manifest reference** | [plugin-manifest.md](plugin-manifest.md) | Field-by-field reference for `plugin.json`. Read first. |
| **Capability declarations** | [plugin-manifest.md#capabilities](plugin-manifest.md#capabilities) | Declaring provider/requester/observer intent with `capability-pipelines.v1`. |
| **Capability domains** | [capability-domains.md](capability-domains.md) | Active domains, planned domains, and promotion rules. |
| **Capability recipes** | [capability-recipes.md](capability-recipes.md) | Copyable manifest patterns for provider/requester/observer plugins. |
| **Visualization contracts** | [plugin-visualization-contracts.md](plugin-visualization-contracts.md) | Building a highway renderer (setRenderer), an overlay layer, or a note-state provider. |
| **Plugin styles** | [plugin-styles.md](plugin-styles.md) | Shipping a plugin-owned prebuilt stylesheet via `styles: "assets/plugin.css"`. |
| **Audio mixer faders** | [plugin-audio-mixer.md](plugin-audio-mixer.md) | Plugin produces audio outside the song `<audio>` element. |
| **Backend logging** | [plugin-logging.md](plugin-logging.md) | Plugin has a `routes.py`. Use `context["log"]`, never `print()`. |
| **Diagnostics contribution** | [plugin-diagnostics.md](plugin-diagnostics.md) | Adding plugin state to the Export Diagnostics bundle. |
| **Keyboard shortcuts** | [plugin-keyboard-shortcuts.md](plugin-keyboard-shortcuts.md) | Registering keys via `window.registerShortcut()`. |
| **Sibling Python imports** | [plugin-sibling-imports.md](plugin-sibling-imports.md) | Multi-file backend plugins. Use `context["load_sibling"]`. |
| **WebSocket protocol** | [websocket-protocol.md](websocket-protocol.md) | Plugins that read the highway stream directly. |
| **Testing plugins** | [testing-plugins.md](testing-plugins.md) | Conftest fixtures and Playwright patterns for plugin tests. |
@@ -50,28 +50,9 @@ Capability-aware plugins should also declare the `capability-pipelines.v1` stand
- Wrap your plugin code in an IIFE: `(function () { 'use strict'; ... })();`
- Declare `standards: ["capability-pipelines.v1"]` and native `capabilities` when your plugin participates in a Slopsmith capability domain.
- Use `ui` / `ui_contributions` for plugin-owned UI surfaces so the host can attribute them in diagnostics and support bundles.
- Use `localStorage` for user-facing settings, prefixed with your plugin id.
- If hooking `window.playSong`, always call the original and `await` it.
- If hooking `window.showScreen`, clean up your state when leaving the player screen.
- Use `window.slopsmith.emit()` / `window.slopsmith.on()` for inter-plugin communication.
- Use `window.registerShortcut()` to add keyboard shortcuts. Clean up with `window.unregisterShortcut(key, scope)` — pass the same scope you registered with, since the default is `'global'` and won't match `player`/`library`/`settings`/`plugin-*` bindings. For panel-scoped shortcuts, prefer `panel.clearShortcuts()`.
## Plugin frontend globals available at runtime
- `window.playSong(filename, arrangementIdx)` — load and play a song
- `window.showScreen(name)` — navigate between screens
- `window.createHighway()` — factory for the highway renderer (used by main player and splitscreen panels)
- `window.slopsmith` — event emitter (`emit`, `on`, `off`)
- `window.slopsmith.audio` — audio mixer fader registry
- `window.slopsmith.diagnostics` — diagnostics namespace (`contribute`, `snapshotConsole`, etc.)
- `window.registerShortcut` / `window.unregisterShortcut` / `window.createShortcutPanel` — keyboard shortcuts API
- `highway` global — set when the player is active. Getters: `getTime`, `getNotes`, `getChords`, `getChordTemplates`, `getSongInfo`, `getStringCount`, `getLefty`, `getInverted`, `getBeats`, `isDefaultRenderer`, …
## Plugin load order
Plugins load alphabetically by directory name. This determines the `playSong` wrapper chain order (last-loaded wrapper runs first; alphabetically earliest plugin runs closest to the original) and which plugin's UI elements appear first.
If your plugin depends on another's globals, **check at runtime** with `typeof window.X === 'function'`, not at load time. Plugins are independent — assume any other plugin may be missing or disabled.
- Prefer native capability commands, events, and provider registration over private globals. If a domain you need is not active yet, document the gap in the PR instead of baking in a new private integration.
## Licensing for curated plugins