docs: extract plugin contracts into modular docs and slim CLAUDE.md

CLAUDE.md had grown to 545 lines / 50 KB — most of it plugin-author
content that other AI tools (Cursor, Copilot, Codex, Aider) and humans
without AI never reach. Extract the plugin surface into 10 focused
docs and a JSON Schema for plugin.json, then slim CLAUDE.md to a
156-line navigable index.

New docs (~999 lines total, all self-contained):

  docs/PLUGIN_AUTHORING.md            — entry point and quickstart
  docs/plugin-manifest.md             — plugin.json field reference
  docs/plugin-visualization-contracts.md — setRenderer / overlay / note-state
  docs/plugin-audio-mixer.md          — fader registration
  docs/plugin-logging.md              — context["log"] + env vars
  docs/plugin-diagnostics.md          — server_files / callable
  docs/plugin-keyboard-shortcuts.md   — registerShortcut + scopes
  docs/plugin-sibling-imports.md      — load_sibling pattern
  docs/websocket-protocol.md          — /ws/highway message reference
  docs/testing-plugins.md             — pytest fixtures + Playwright

  schema/plugin.schema.json           — Draft 2020-12 schema for
                                        plugin.json; license enum
                                        mirrors CONTRIBUTING's curated
                                        allowlist. Backs CI validation
                                        and the plugin-validate skill.

CLAUDE.md slim (581 lines changed, -485):

  - Removed ~300 lines of plugin-author prose (now in docs/).
  - Kept architecture quick reference, running the app, testing,
    git workflow, versioning, song formats, frontend/backend
    conventions, plugin authoring INDEX (table → docs/), first-hour
    pitfalls, "For AI agents" footer.
  - Anchor stubs preserved next to the new index entries so deep
    links from specs/001-slopsmith-platform/analyze.md still resolve.

Verification:
  python -c "import json,glob,jsonschema; s=json.load(open('schema/plugin.schema.json')); [jsonschema.validate(json.load(open(p)), s) for p in sorted(glob.glob('plugins/*/plugin.json'))]"
  # ok — validates highway_3d, app_tour_library, app_tour_settings
Signed-off-by: Miguel_LZPF <mgcdreamer@gmail.com>
This commit is contained in:
Miguel_LZPF
2026-06-18 00:38:50 -07:00
committed by Bret Mogilefsky
parent cad78857fb
commit 1214a6c0a0
12 changed files with 1013 additions and 625 deletions
+64
View File
@@ -0,0 +1,64 @@
# Plugin keyboard shortcuts
Plugins can register keyboard shortcuts via the global `window.registerShortcut()` function. Shortcuts appear in the `?` help panel.
## Registration
```js
window.registerShortcut({
key: 'k', // key value (e.key) or key code (e.code)
description: 'Toggle my view', // shown in the help panel
scope: 'player', // 'global' | 'player' | 'library' | 'settings' | 'plugin-{id}'
condition: () => _isMyViewActive, // optional guard
handler: (e) => _myAction() // called when shortcut triggers
});
```
## Scope
Scope controls when the shortcut is active:
- **`global`** — works on any screen
- **`player`** — only on the player screen
- **`library`** — only on the home/favorites screens
- **`settings`** — only on the settings screen
- **`plugin-{id}`** — only when your plugin's screen is active
## Panel-scoped shortcuts
For plugins that create multiple panels (e.g., splitscreen), shortcuts are automatically scoped to the active panel. Use `const panel = window.createShortcutPanel(id)` to create a panel (keep the returned reference so you can call `panel.clearShortcuts()` during cleanup) and `window.setActiveShortcutPanel(id)` to switch between them. Each panel has its own shortcut registry, so multiple panels can have the same key without collisions.
## Condition
`condition` is an optional guard function. If it returns false, the shortcut is skipped even if in scope.
## Key matching
The handler matches against both `e.key` (character produced) and `e.code` (physical key). Use `e.key` for letters/symbols that depend on keyboard layout, and `e.code` for special keys (e.g. `Space`, `ArrowLeft`).
## Cleanup
Clean up with `window.unregisterShortcut(key, scope)`. **You must pass the same scope you registered with** — the default is `'global'` and won't match `player`/`library`/`settings`/`plugin-*` bindings.
For panel-scoped shortcuts, prefer `panel.clearShortcuts()` over per-key unregister calls.
## Built-in shortcuts
| Key | Description |
|-----|-------------|
| `?` | Show keyboard shortcuts panel (global) |
| `Space` | Play/Pause (player only) |
| `←` / `→` | Seek ±5 seconds (player only) |
| `Escape` | Back to library (player only) |
| `[` / `]` | Audio offset ±10ms (Shift: ±50ms) (player only) |
Don't override these. The `?` panel is the canonical reference for users.
## Debugging
Open the browser console and type `_listShortcuts()` to inspect every registered shortcut, its scope, and its source plugin.
## Related
- [PLUGIN_AUTHORING.md](PLUGIN_AUTHORING.md) — guide index
- [testing-plugins.md](testing-plugins.md) — Playwright tests for shortcut behaviour