* 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>
9.2 KiB
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:
- Auto-hide — a button you append to
#player-controlsvanishes with the transport after a couple seconds. - Dead anchors — legacy code commonly inserts before a
<span class="text-gray-700">separator or thebutton: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:
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
uiVersionso v2 is byte-for-byte unchanged. - Never
insertBeforethe legacyspan.text-gray-700separator orbutton: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 todocument.bodyor#player), not relative to#player-controls— the trigger now lives in the rail popover. - Overlays/HUDs/canvases you attach to
#playerkeep working; just keep theirz-indexunder the chrome layers: transport/HUDz-20, railz-30, popoversz-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):
{
"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 asstyles). Shortcut: if you omiticonbut shipassets/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) 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:
enabledfield on every/api/pluginsentry — a boolean, defaulttrue. Absent (older entries, stubbed test rows) is treated as enabled. The frontend hides the nav and shows the footswitch unlit whenenabledisfalse.POST /api/plugins/{plugin_id}/enabled— body{"enabled": <bool>}, returns{"id": "<id>", "enabled": <bool>}.400if the body is missing/invalid orenabledisn't a real boolean (0/1/strings are rejected).400if you try to disable an always-on plugin —capability_inspectorand anyapp_tour_*may never be disabled (disabling would brick the app or the capability-graph review surface). Bundled plugins are otherwise disable-able.404for 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
enabledflag, 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:falseas "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/pluginsas 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-childanchor; guardcontains()against the actual container. - Dropdowns positioned via
getBoundingClientRect(), not#player-controls. #playeroverlays keepz-index≤ the chrome layers (transport/HUD 20, rail 30, popovers 40).- Verify in both
/(v2) and/v3.