mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 11:19:24 +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>
153 lines
7.4 KiB
JavaScript
153 lines
7.4 KiB
JavaScript
/*
|
|
* ui.library-card-injection — core capability for plugin-contributed library
|
|
* card actions (fee[dB]ack v0.3.0).
|
|
*
|
|
* Replaces the legacy pattern where plugins (Sloppak Converter, Find More,
|
|
* editor) injected buttons by DOM-observing `.song-card`. Plugins now REGISTER
|
|
* card actions with placement, applicability, enabled state, and a run handler;
|
|
* the library renders + dispatches them and emits action-result events. The
|
|
* owner is registered in the capability runtime so the Capability Inspector and
|
|
* diagnostics can see it (design/05; docs/capability-roadmap.md domain #9).
|
|
*
|
|
* Public API: window.feedBack.libraryCardActions
|
|
* register(spec) -> unregister() spec: { id, pluginId, label, icon?,
|
|
* placement?('menu'|'inline'|'overlay'), order?, destructive?,
|
|
* applies?(song)->bool, enabled?(song)->bool, run(song, ctx) }
|
|
* list(song) -> [actionSummary] applicable actions, sorted
|
|
* run(id, song, ctx) -> Promise<{ ok, outcome, result?|error? }>
|
|
* snapshot() -> redaction-safe registry snapshot
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
const w = (typeof window !== 'undefined') ? window : {};
|
|
w.feedBack = w.feedBack || {};
|
|
if (w.feedBack.libraryCardActions && w.feedBack.libraryCardActions.version === 1) return;
|
|
const capabilities = w.feedBack.capabilities;
|
|
const DOMAIN = 'ui.library-card-injection';
|
|
const PLACEMENTS = ['menu', 'inline', 'overlay'];
|
|
|
|
const actions = new Map(); // id -> normalized spec
|
|
|
|
function _emit(event, payload) {
|
|
if (capabilities && typeof capabilities.emitEvent === 'function') {
|
|
try { capabilities.emitEvent(DOMAIN, event, payload); } catch (e) { /* */ }
|
|
}
|
|
}
|
|
|
|
function _normalize(spec) {
|
|
if (!spec || !spec.id || typeof spec.run !== 'function') return null;
|
|
return {
|
|
id: String(spec.id),
|
|
pluginId: String(spec.pluginId || 'unknown'),
|
|
label: String(spec.label || spec.id),
|
|
icon: spec.icon || '',
|
|
placement: PLACEMENTS.includes(spec.placement) ? spec.placement : 'menu',
|
|
order: Number.isFinite(spec.order) ? spec.order : 100,
|
|
destructive: !!spec.destructive,
|
|
applies: typeof spec.applies === 'function' ? spec.applies : () => true,
|
|
enabled: typeof spec.enabled === 'function' ? spec.enabled : () => true,
|
|
run: spec.run,
|
|
};
|
|
}
|
|
|
|
function register(spec) {
|
|
const a = _normalize(spec);
|
|
if (!a) { return () => {}; }
|
|
// Reject ID collisions: silently overwriting would make unregister()/
|
|
// run() nondeterministic once two providers share an action id.
|
|
if (actions.has(a.id)) {
|
|
if (typeof console !== 'undefined' && console.warn) {
|
|
console.warn('[library-card-actions] duplicate action id ignored:', a.id);
|
|
}
|
|
return () => {};
|
|
}
|
|
actions.set(a.id, a);
|
|
_emit('action-registered', { id: a.id, pluginId: a.pluginId, placement: a.placement });
|
|
return function unregister() {
|
|
if (actions.delete(a.id)) _emit('action-unregistered', { id: a.id, pluginId: a.pluginId });
|
|
};
|
|
}
|
|
|
|
function unregister(id) {
|
|
// Normalize the id once (so the lookup, delete, and event payload all
|
|
// agree for non-string ids) and capture the action before deleting, so
|
|
// the action-unregistered payload always carries a defined pluginId —
|
|
// matching the register()-returned unregister path.
|
|
const key = String(id);
|
|
const a = actions.get(key);
|
|
if (!actions.delete(key)) return;
|
|
_emit('action-unregistered', { id: key, pluginId: a ? a.pluginId : undefined });
|
|
}
|
|
|
|
function list(song) {
|
|
const out = [];
|
|
for (const a of actions.values()) {
|
|
let applicable = true;
|
|
try { applicable = a.applies(song) !== false; } catch (e) { applicable = false; }
|
|
if (!applicable) continue;
|
|
let enabled = true;
|
|
try { enabled = a.enabled(song) !== false; } catch (e) { enabled = true; }
|
|
out.push({ id: a.id, pluginId: a.pluginId, label: a.label, icon: a.icon, placement: a.placement, order: a.order, destructive: a.destructive, enabled });
|
|
}
|
|
out.sort((x, y) => (x.order - y.order) || x.label.localeCompare(y.label));
|
|
return out;
|
|
}
|
|
|
|
async function run(id, song, ctx) {
|
|
const a = actions.get(String(id));
|
|
if (!a) return { ok: false, outcome: 'no-action' };
|
|
// Mirror list()'s defensive semantics for the predicate checks: an
|
|
// applies() error means not-applicable, an enabled() error is non-fatal
|
|
// (treat as enabled). Only a.run() throwing is a 'failed' outcome.
|
|
let applicable = true;
|
|
try { applicable = a.applies(song) !== false; } catch (e) { applicable = false; }
|
|
if (!applicable) return { ok: false, outcome: 'not-applicable' };
|
|
let enabled = true;
|
|
try { enabled = a.enabled(song) !== false; } catch (e) { enabled = true; }
|
|
if (!enabled) return { ok: false, outcome: 'disabled' };
|
|
try {
|
|
const result = await a.run(song, ctx || {});
|
|
_emit('action-result', { id: a.id, pluginId: a.pluginId, outcome: 'handled' });
|
|
return { ok: true, outcome: 'handled', result };
|
|
} catch (e) {
|
|
_emit('action-result', { id: a.id, pluginId: a.pluginId, outcome: 'failed', reason: e && e.message ? e.message : String(e) });
|
|
return { ok: false, outcome: 'failed', error: e && e.message ? e.message : String(e) };
|
|
}
|
|
}
|
|
|
|
function snapshot() {
|
|
return {
|
|
schema: 'feedBack.library_card_actions.v1',
|
|
actions: Array.from(actions.values()).map((a) => ({ id: a.id, pluginId: a.pluginId, placement: a.placement, order: a.order, destructive: a.destructive })),
|
|
};
|
|
}
|
|
|
|
// Register the capability owner (Inspector/diagnostics visibility). The
|
|
// ergonomic register/list/run API above is what the library + plugins use;
|
|
// these commands mirror it for inspection.
|
|
if (capabilities && typeof capabilities.registerOwner === 'function') {
|
|
try {
|
|
capabilities.registerOwner(DOMAIN, {
|
|
pluginId: 'core.ui.library-card-injection',
|
|
kind: 'provider-coordinator',
|
|
commands: ['list', 'run', 'inspect'],
|
|
operations: ['action.run'],
|
|
events: ['action-registered', 'action-unregistered', 'action-result'],
|
|
ownership: 'multi-provider',
|
|
safety: 'safe',
|
|
version: 1,
|
|
description: 'Coordinates plugin-contributed library-card actions (placement, applicability, enabled state, action-result events).',
|
|
handlers: {
|
|
list: (ctx) => ({ outcome: 'handled', payload: list(ctx && ctx.payload && ctx.payload.song) }),
|
|
run: (ctx) => run(((ctx && ctx.payload) || {}).id, ((ctx && ctx.payload) || {}).song, ctx).then((r) => ({ outcome: r.ok ? 'handled' : (r.outcome === 'failed' ? 'failed' : 'denied'), payload: r })),
|
|
inspect: () => ({ outcome: 'handled', payload: snapshot() }),
|
|
},
|
|
});
|
|
} catch (e) { /* owner registration is best-effort */ }
|
|
}
|
|
|
|
const api = { version: 1, register, unregister, list, run, snapshot };
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = api; // node tests
|
|
w.feedBack.libraryCardActions = api;
|
|
})();
|