mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-22 04:41:23 +00:00
* Update GitHub repo references from feedback* to feedBack* * rename: slopsmith -> feedBack, byron -> got-feedBack Renames across the entire codebase: - slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack - byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack - /home/byron/ -> /opt/got-feedBack/ - byron@ougsoft.com -> hi@got-feedBack.org - github.com/byrongamatos/ -> github.com/got-feedback/ - com.byron. -> com.got-feedback. - SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback - Protocol/storage strings migrated with read-old/write-new pattern - window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias Refs: #rename-slopsmith * rename: complete regen against current main + fix backward-compat alias Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of current main (3 commits had landed since the branch: #572/#554/#574), resolving the four content conflicts in favour of main's newer content (autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge). Completion fixes on top of the mechanical rename: - Re-apply rename to post-branch content the original rename never saw: window.slopsmith(.Tour) consumers in lessons.js / notifications.js / onboarding-tour.js, and the matching JS + python tests (autoplay_exit, progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env vars now match server.py (which reads FEEDBACK_SYNC_STARTUP / FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real path again. - Restore the window.slopsmith backward-compat alias dropped during conflict resolution, and move the bus aliases to AFTER the _feedBackExisting merge block so they reference the fully-assembled object (also fixes the loop_api.test.js API-surface regex, which the original PR latently broke). - Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR) and gitignore it. - Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped source labels. Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669 passed / 1683 collected with 0 import errors; zero residual slopsmith/byron except the two intentional window.slopsmith aliases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rename: implement advertised backward-compat + prune dead community plugins Address gaps where PR #537's "Backward compatibility" section was advertised but not implemented, and clean up the community plugin list. Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured): - New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI / SLOPSMITH_PLUGINS_DIR / etc. deployments keep working. - Fix the rename collapsing plugins/__init__.py and minigames/routes.py from `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost). Storage (app.js update-channel): - Read feedBack-update-channel, fall back to legacy slopsmith-update-channel, and clear the legacy key on write — so a user's update-channel preference survives the rename instead of resetting to "stable". Community plugin list (README): the rename rewrote third-party repo URLs we don't own. Probed every one; their owners never renamed, so: - Restore the 13 live community plugins to their real slopsmith-* names. - Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle). - Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory). Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors, rename-sensitive + startup suites green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: byrongamatos <xasiklas@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
192 lines
9.2 KiB
Markdown
192 lines
9.2 KiB
Markdown
# Building plugins for the v3 UI (fee[dB]ack v0.3.0)
|
||
|
||
v0.3.0 ("fee[dB]ack") ships a redesigned UI **behind a flag** — `FEEDBACK_UI=v3`
|
||
or the `/v3` route. The classic UI (v2) remains the default until 0.3.0 ships, so
|
||
plugins must work in **both**.
|
||
|
||
The good news: v3 **reuses the same engine** as v2 — same `server.py`, `app.js`,
|
||
`highway.js`, `playSong`, `showScreen`, capability registry, library providers,
|
||
and the `window.feedBackViz_<id>` / `setRenderer` visualization contract. So your
|
||
plugin's **backend, capabilities, library providers, `nav`/`screen`, visualization
|
||
renderers, diagnostics, and settings export all work unchanged in v3.** v3 surfaces
|
||
your `nav` entry in the new sidebar (via `shell.js` `renderPluginNav`) and your
|
||
screen mounts exactly as before.
|
||
|
||
**The one thing that changed is the player chrome** — and only if your plugin
|
||
injects controls into it.
|
||
|
||
## What changed in the player
|
||
|
||
In v2, `#player-controls` was a wide, **always-visible** bottom bar. In v3 it
|
||
became a **minimal, auto-hiding centered transport** (it fades ~2.5 s after the
|
||
pointer goes still during playback), flanked by a **hover-reveal left icon rail**
|
||
with popovers.
|
||
|
||
So the legacy way of injecting a control breaks in v3 two ways:
|
||
|
||
1. **Auto-hide** — a button you append to `#player-controls` vanishes with the
|
||
transport after a couple seconds.
|
||
2. **Dead anchors** — legacy code commonly inserts before a `<span class="text-gray-700">`
|
||
separator or the `button:last-child` (the ✕ Close button). **Neither exists in
|
||
the v3 transport**, so your control lands at the wrong end or is unreachable.
|
||
|
||
## The contract: detect v3, mount into the plugin-control slot
|
||
|
||
The host exposes:
|
||
|
||
- `window.feedBack.uiVersion === 'v3'` — detect v3 (absent / not `'v3'` in v2).
|
||
- `window.feedBack.ui.playerControlSlot()` — returns a **stable, always-reachable
|
||
container** (the "Plugins" rail popover). In v3, append your control(s) here
|
||
instead of `#player-controls`.
|
||
|
||
Canonical pattern for any control you inject into the player:
|
||
|
||
```js
|
||
function playerSlot() {
|
||
return (window.feedBack && window.feedBack.uiVersion === 'v3'
|
||
&& window.feedBack.ui && typeof window.feedBack.ui.playerControlSlot === 'function')
|
||
? window.feedBack.ui.playerControlSlot() : null;
|
||
}
|
||
|
||
function injectMyButton() {
|
||
const slot = playerSlot();
|
||
const controls = slot || document.getElementById('player-controls'); // v3 slot, else v2 bar
|
||
if (!controls) return;
|
||
if (myBtn && controls.contains(myBtn)) return; // guard the ACTUAL container
|
||
// Legacy inserts before a separator / the ✕ Close button; the v3 slot has no
|
||
// such anchor, so just append there.
|
||
const anchor = slot ? null : controls.querySelector('span.text-gray-700, button:last-child');
|
||
myBtn = document.createElement('button');
|
||
/* ... */
|
||
if (anchor) controls.insertBefore(myBtn, anchor); else controls.appendChild(myBtn);
|
||
}
|
||
```
|
||
|
||
Rules:
|
||
|
||
- **Gate v3 behavior on `uiVersion`** so v2 is byte-for-byte unchanged.
|
||
- **Never** `insertBefore` the legacy `span.text-gray-700` separator or
|
||
`button:last-child` — they don't exist in the v3 transport. Append instead.
|
||
- **Guard idempotency against the actual container** (`controls.contains(myBtn)`),
|
||
not a hard-coded `#player-controls` — otherwise re-injection logic breaks in v3.
|
||
- **Dropdowns/panels** your control opens: position them via the trigger's
|
||
`getBoundingClientRect()` (portal to `document.body` or `#player`), **not**
|
||
relative to `#player-controls` — the trigger now lives in the rail popover.
|
||
- **Overlays/HUDs/canvases** you attach to `#player` keep working; just keep their
|
||
`z-index` **under the chrome layers**: transport/HUD `z-20`, rail `z-30`,
|
||
popovers `z-40`.
|
||
|
||
## Pedalboard metadata (icon, description, category)
|
||
|
||
The v3 **Plugins page** renders each plugin as a guitar **pedal** grouped onto
|
||
category **pedalboards**. To make your pedal look good, declare three optional,
|
||
additive manifest fields (all surfaced in `/api/plugins`):
|
||
|
||
```json
|
||
{
|
||
"id": "my_plugin",
|
||
"name": "My Plugin",
|
||
"description": "One short sentence shown under the pedal name.",
|
||
"category": "audio",
|
||
"icon": "assets/thumb.png"
|
||
}
|
||
```
|
||
|
||
- **`description`** — one short sentence (clamped to ~2 lines on the pedal).
|
||
- **`category`** — which board the pedal sits on. Suggested:
|
||
`audio | creation | practice | game | tools`. Unknown/absent → a curated default
|
||
then `"other"`.
|
||
- **`icon`** — assets-relative thumbnail (~square, ~256×256 PNG/SVG), served via
|
||
the existing sandboxed `/api/plugins/<id>/assets/...` route (same containment
|
||
rule as `styles`). **Shortcut:** if you omit `icon` but ship
|
||
`assets/thumb.png`, the loader auto-detects it — no manifest edit needed.
|
||
Plugins with no thumbnail get a default pedal graphic.
|
||
|
||
All three are backward-compatible: omit them and the plugin still loads and shows
|
||
a default pedal.
|
||
|
||
## The compatibility shim (don't rely on it)
|
||
|
||
So un-updated plugins still function, the host runs a `MutationObserver` that
|
||
re-homes any non-native `#player-controls` child into the slot. It's a safety net
|
||
— but it **breaks plugins that guard re-injection with
|
||
`#player-controls.contains(myBtn)`** (once the host moves the node out, the check
|
||
goes false and the plugin re-injects every song). **Mount into the slot yourself**
|
||
(the pattern above) to be correct; treat the shim as a fallback only.
|
||
|
||
## Styling
|
||
|
||
v3 uses `fb-*` design tokens (`fb-card`, `fb-text`, `fb-textDim`, `fb-primary`,
|
||
`fb-border`); v2 uses `dark-*` / `accent`. Legacy classes still **render
|
||
acceptably** in v3's dark theme, so a plugin that only uses core-guaranteed
|
||
utilities is functional in both. For polish, ship your own stylesheet via the
|
||
`styles` capability ([plugin-styles.md](plugin-styles.md)) declaring the tokens you
|
||
use — but the host slot already provides a styled container, so simple controls
|
||
need nothing special.
|
||
|
||
## Enabling / disabling plugins (Pedalboard footswitch)
|
||
|
||
The v3 **Pedalboard** Plugins page renders each plugin as a guitar pedal whose
|
||
"footswitch" turns the plugin on or off. The backend contract:
|
||
|
||
- **`enabled` field on every `/api/plugins` entry** — a boolean, default `true`.
|
||
Absent (older entries, stubbed test rows) is treated as enabled. The frontend
|
||
hides the nav and shows the footswitch unlit when `enabled` is `false`.
|
||
- **`POST /api/plugins/{plugin_id}/enabled`** — body `{"enabled": <bool>}`,
|
||
returns `{"id": "<id>", "enabled": <bool>}`.
|
||
- `400` if the body is missing/invalid or `enabled` isn't a real boolean
|
||
(`0`/`1`/strings are rejected).
|
||
- `400` if you try to disable an always-on plugin — `capability_inspector`
|
||
and any `app_tour_*` may never be disabled (disabling would brick the app or
|
||
the capability-graph review surface). Bundled plugins are otherwise
|
||
disable-able.
|
||
- `404` for an unknown plugin id (not loaded and not pending).
|
||
|
||
### Persistence
|
||
|
||
The choice is persisted under `CONFIG_DIR/plugin_state.json` as
|
||
`{"<plugin_id>": {"enabled": false}, ...}`. **Only non-default (`enabled:false`)
|
||
entries are stored** — re-enabling drops the key entirely, so the file stays
|
||
small and "absent ⇒ enabled" is the invariant. A missing or corrupt state file
|
||
is tolerated (logged, falls back to `{}`) and never crashes startup.
|
||
|
||
### Restart semantics
|
||
|
||
- Toggling **persists immediately** and flips the **in-memory** `enabled` flag,
|
||
so the very next `/api/plugins` (and thus the nav, the Pedalboard, and the
|
||
capability pipeline) reflects the change at once — no restart needed for the
|
||
UI to update.
|
||
- A plugin **disabled at runtime keeps its already-mounted routes/screen** until
|
||
the next restart; full hot-unload is out of scope. The frontend treats
|
||
`enabled:false` as "off" regardless.
|
||
- At **startup**, the loader **skips disabled plugins entirely** — it does not
|
||
install requirements, run `routes.setup()`, or register their screen, nav, or
|
||
capabilities. They still appear in `/api/plugins` as a disabled entry
|
||
(`status: "disabled"`, `enabled: false`) so the UI can show an "off" pedal you
|
||
can switch back on. **Re-enabling** a plugin that was skipped at startup
|
||
updates the flag immediately but the plugin only actually mounts on the next
|
||
restart.
|
||
|
||
### Capability pipeline
|
||
|
||
A disabled plugin is **excluded from the capability pipeline**: its
|
||
`capabilities`, `standards`, `capability_validation_warnings`,
|
||
`capability_unsupported_versions`, and `compatibility_shims` are emptied in the
|
||
`/api/plugins` response whenever `enabled` is `false` (covering both
|
||
startup-skipped and runtime-toggled-off plugins). Because the browser capability
|
||
registry registers any entry that carries a capability declaration regardless of
|
||
status, suppressing the metadata here is what actually keeps a disabled plugin
|
||
out of the capability graph.
|
||
|
||
## Checklist
|
||
|
||
- [ ] Backend / capabilities / library provider / `nav` + `screen` /
|
||
visualization renderer — **no change needed** (they work in v3 as-is).
|
||
- [ ] If you inject a control into the player: detect v3 and mount into
|
||
`window.feedBack.ui.playerControlSlot()`; drop the dead separator /
|
||
`button:last-child` anchor; guard `contains()` against the actual container.
|
||
- [ ] Dropdowns positioned via `getBoundingClientRect()`, not `#player-controls`.
|
||
- [ ] `#player` overlays keep `z-index` ≤ the chrome layers (transport/HUD 20,
|
||
rail 30, popovers 40).
|
||
- [ ] Verify in **both** `/` (v2) and `/v3`.
|