mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 11:19:24 +00:00
rename: slopsmith → feedBack, byron → got-feedBack (#537)
* 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>
This commit is contained in:
co-authored by
Claude Opus 4.8
byrongamatos
parent
a8ad02739a
commit
af2949677a
+284
-279
File diff suppressed because it is too large
Load Diff
+19
-19
@@ -1,6 +1,6 @@
|
||||
// Audio mixer — registry + popover for per-channel volume control (slopsmith#87).
|
||||
// Audio mixer — registry + popover for per-channel volume control (feedBack#87).
|
||||
//
|
||||
// Plugins (or core) register a fader spec via window.slopsmith.audio.registerFader(spec).
|
||||
// Plugins (or core) register a fader spec via window.feedBack.audio.registerFader(spec).
|
||||
// Each spec is the source of truth for its own value: the popover only calls
|
||||
// getValue() to render and setValue() to commit. Persistence is the plugin's
|
||||
// responsibility — the registry doesn't store values.
|
||||
@@ -10,8 +10,8 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (!window.slopsmith) {
|
||||
console.warn('[mixer] window.slopsmith missing — audio-mixer.js loaded too early');
|
||||
if (!window.feedBack) {
|
||||
console.warn('[mixer] window.feedBack missing — audio-mixer.js loaded too early');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ let _openTimer = null;
|
||||
function _audioEl() { return document.getElementById('audio'); }
|
||||
|
||||
function _audioSession() {
|
||||
return window.slopsmith && window.slopsmith.audioSession;
|
||||
return window.feedBack && window.feedBack.audioSession;
|
||||
}
|
||||
|
||||
function _capabilities() {
|
||||
return window.slopsmith && window.slopsmith.capabilities;
|
||||
return window.feedBack && window.feedBack.capabilities;
|
||||
}
|
||||
|
||||
async function _mixCommand(command, payload) {
|
||||
@@ -143,7 +143,7 @@ function _applySongVolume(v) {
|
||||
// routes every stem through its own master GainNode, so a.volume above is
|
||||
// dead. Drive that master instead when the stems plugin has published its
|
||||
// hook (it clears the hook on teardown for stem-less songs).
|
||||
const stemsSetMaster = window.slopsmith?.stems?.setMasterVolume;
|
||||
const stemsSetMaster = window.feedBack?.stems?.setMasterVolume;
|
||||
if (typeof stemsSetMaster === 'function') {
|
||||
// A synchronous throw or a rejected Promise from the stems plugin hook
|
||||
// must not abort _applySongVolume before it returns / persists. The
|
||||
@@ -155,13 +155,13 @@ function _applySongVolume(v) {
|
||||
// consistent with the other ignored async calls in this module.
|
||||
void Promise.resolve(stemsSetMaster(linear))
|
||||
.then(function () {
|
||||
_recordAudioBridge('stems.master-volume', 'window.slopsmith.stems.setMasterVolume', 'core.song', 'handled');
|
||||
_recordAudioBridge('stems.master-volume', 'window.feedBack.stems.setMasterVolume', 'core.song', 'handled');
|
||||
})
|
||||
.catch(function () {
|
||||
_recordAudioBridge('stems.master-volume', 'window.slopsmith.stems.setMasterVolume', 'core.song', 'failed', 'Stems master volume hook rejected');
|
||||
_recordAudioBridge('stems.master-volume', 'window.feedBack.stems.setMasterVolume', 'core.song', 'failed', 'Stems master volume hook rejected');
|
||||
});
|
||||
} catch (_) {
|
||||
_recordAudioBridge('stems.master-volume', 'window.slopsmith.stems.setMasterVolume', 'core.song', 'failed', 'Stems master volume hook threw');
|
||||
_recordAudioBridge('stems.master-volume', 'window.feedBack.stems.setMasterVolume', 'core.song', 'failed', 'Stems master volume hook threw');
|
||||
}
|
||||
}
|
||||
_registerAudioSessionFader({
|
||||
@@ -183,7 +183,7 @@ function _applySongVolume(v) {
|
||||
_recordAudioBridge('audio-mix.song-volume', 'applySongVolume', 'core.song', 'handled');
|
||||
// Desktop + JUCE: song audio is mixed in the native engine; HTML5 volume is ignored.
|
||||
if (window._juceMode) {
|
||||
const setGain = window.slopsmithDesktop?.audio?.setGain;
|
||||
const setGain = window.feedBackDesktop?.audio?.setGain;
|
||||
if (typeof setGain === 'function') {
|
||||
// Same dual guard as the stems hook above: the try/catch covers a
|
||||
// synchronous throw from setGain, the .catch() covers a rejected IPC.
|
||||
@@ -502,17 +502,17 @@ function _init() {
|
||||
_btnEl = document.getElementById('btn-mixer');
|
||||
_popoverEl = document.getElementById('mixer-popover');
|
||||
_registerSongFader();
|
||||
if (window.slopsmith && window.slopsmith.on) {
|
||||
window.slopsmith.on('screen:changed', _onScreenChanged);
|
||||
window.slopsmith.on('audio-mix:fader-value-changed', () => { if (_open) _renderPopover(); });
|
||||
window.slopsmith.on('audio-mix:fader-unavailable', () => { if (_open) _renderPopover(); });
|
||||
window.slopsmith.on('audio-mix:participant-registered', () => { if (_open) _renderPopover(); });
|
||||
window.slopsmith.on('audio-mix:participant-removed', () => { if (_open) _renderPopover(); });
|
||||
if (window.feedBack && window.feedBack.on) {
|
||||
window.feedBack.on('screen:changed', _onScreenChanged);
|
||||
window.feedBack.on('audio-mix:fader-value-changed', () => { if (_open) _renderPopover(); });
|
||||
window.feedBack.on('audio-mix:fader-unavailable', () => { if (_open) _renderPopover(); });
|
||||
window.feedBack.on('audio-mix:participant-registered', () => { if (_open) _renderPopover(); });
|
||||
window.feedBack.on('audio-mix:participant-removed', () => { if (_open) _renderPopover(); });
|
||||
}
|
||||
window.dispatchEvent(new Event('slopsmith:audio:ready'));
|
||||
window.dispatchEvent(new Event('feedBack:audio:ready'));
|
||||
}
|
||||
|
||||
window.slopsmith.audio = Object.assign(window.slopsmith.audio || {}, {
|
||||
window.feedBack.audio = Object.assign(window.feedBack.audio || {}, {
|
||||
registerFader, unregisterFader, getFaders,
|
||||
openMixer, closeMixer, toggleMixer,
|
||||
applySongVolume: _applySongVolume,
|
||||
|
||||
+18
-18
@@ -1,4 +1,4 @@
|
||||
// Slopsmith capability registry and dispatcher.
|
||||
// FeedBack capability registry and dispatcher.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
};
|
||||
}
|
||||
|
||||
function _ensureSlopsmithEventBus() {
|
||||
const existing = window.slopsmith && typeof window.slopsmith === 'object' ? window.slopsmith : null;
|
||||
function _ensureFeedBackEventBus() {
|
||||
const existing = window.feedBack && typeof window.feedBack === 'object' ? window.feedBack : null;
|
||||
const hasEventTarget = existing
|
||||
&& typeof existing.addEventListener === 'function'
|
||||
&& typeof existing.removeEventListener === 'function'
|
||||
@@ -53,12 +53,12 @@
|
||||
bus.off = function (event, fn, options) {
|
||||
this.removeEventListener(event, fn, options);
|
||||
};
|
||||
window.slopsmith = bus;
|
||||
window.feedBack = bus;
|
||||
return bus;
|
||||
}
|
||||
|
||||
_ensureSlopsmithEventBus();
|
||||
if (window.slopsmith.capabilities && window.slopsmith.capabilities.version === 1) return;
|
||||
_ensureFeedBackEventBus();
|
||||
if (window.feedBack.capabilities && window.feedBack.capabilities.version === 1) return;
|
||||
|
||||
const VALID_ROLES = new Set([
|
||||
'owner', 'coordinator', 'provider', 'executor', 'observer', 'requester', 'transformer', 'handler',
|
||||
@@ -209,7 +209,7 @@
|
||||
|
||||
const VALID_SETTING_TYPES = new Set(['toggle', 'range', 'select']);
|
||||
|
||||
// Normalize per-instance control descriptors (slopsmith#849) declared on a
|
||||
// Normalize per-instance control descriptors (feedBack#849) declared on a
|
||||
// capability. Lenient (drops malformed entries rather than failing the whole
|
||||
// declaration): the server-side validator in plugins/__init__.py already
|
||||
// strict-checks the /api/plugins manifest path; this keeps runtime
|
||||
@@ -661,7 +661,7 @@
|
||||
lifecycle: 'plugin-defined',
|
||||
label: 'Plugin-defined',
|
||||
tone: 'info',
|
||||
summary: 'Declared by a plugin or test fixture rather than registered as a core Slopsmith domain.',
|
||||
summary: 'Declared by a plugin or test fixture rather than registered as a core FeedBack domain.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -863,12 +863,12 @@
|
||||
_notifySubscribers(event, detail);
|
||||
_notifySubscribers(`${capabilityName}:${event}`, detail);
|
||||
try {
|
||||
if (window.slopsmith && typeof window.slopsmith.emit === 'function') {
|
||||
window.slopsmith.emit(`${capabilityName}:${event}`, detail);
|
||||
window.slopsmith.emit('capability:event', detail);
|
||||
if (window.feedBack && typeof window.feedBack.emit === 'function') {
|
||||
window.feedBack.emit(`${capabilityName}:${event}`, detail);
|
||||
window.feedBack.emit('capability:event', detail);
|
||||
} else {
|
||||
window.dispatchEvent(new CustomEvent(`${capabilityName}:${event}`, { detail }));
|
||||
window.dispatchEvent(new CustomEvent('slopsmith:capability:event', { detail }));
|
||||
window.dispatchEvent(new CustomEvent('feedBack:capability:event', { detail }));
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[capabilities] event dispatch failed:', err);
|
||||
@@ -1238,7 +1238,7 @@
|
||||
if (!source || typeof source !== 'object') return 'legacy-runtime';
|
||||
const candidate = source.source || source.pluginId || source.owner || source.requester || source.providerId || source.id;
|
||||
if (candidate) return String(candidate);
|
||||
const activePluginId = window.slopsmith && (window.slopsmith._loadingPluginId || window.slopsmith._activePluginId);
|
||||
const activePluginId = window.feedBack && (window.feedBack._loadingPluginId || window.feedBack._activePluginId);
|
||||
return activePluginId ? String(activePluginId) : 'legacy-runtime';
|
||||
}
|
||||
|
||||
@@ -1413,7 +1413,7 @@
|
||||
function snapshotDiagnostics() {
|
||||
const pipelineSummaries = inspect();
|
||||
const snapshot = {
|
||||
schema: 'slopsmith.capabilities.diagnostics.v1',
|
||||
schema: 'feedBack.capabilities.diagnostics.v1',
|
||||
pipelines: pipelineSummaries,
|
||||
participants: pipelineSummaries.flatMap(pipeline => Array.isArray(pipeline.participants) ? pipeline.participants : []),
|
||||
recentDecisions: recentDecisions.slice(),
|
||||
@@ -1447,14 +1447,14 @@
|
||||
capabilitiesChangeScheduled = false;
|
||||
const detail = { timestamp: _now() };
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent('slopsmith:capabilities:changed', { detail }));
|
||||
window.dispatchEvent(new CustomEvent('feedBack:capabilities:changed', { detail }));
|
||||
} catch (_err) { /* capability diagnostics must not break runtime behavior */ }
|
||||
});
|
||||
}
|
||||
|
||||
function _contributeDiagnostics() {
|
||||
if (contributing) return;
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (diagnostics && typeof diagnostics.contribute === 'function') {
|
||||
contributing = true;
|
||||
try { diagnostics.contribute('capabilities', snapshotDiagnostics()); }
|
||||
@@ -1494,7 +1494,7 @@
|
||||
recordUserOverride,
|
||||
};
|
||||
|
||||
window.slopsmith.capabilities = api;
|
||||
window.feedBack.capabilities = api;
|
||||
|
||||
registerParticipant('core', {
|
||||
diagnostics: {
|
||||
@@ -1533,7 +1533,7 @@
|
||||
},
|
||||
});
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent('slopsmith:capabilities:ready', { detail: api }));
|
||||
window.dispatchEvent(new CustomEvent('feedBack:capabilities:ready', { detail: api }));
|
||||
_notifySubscribers('registered', { capability: '*', pluginId: 'core', timestamp: _now() });
|
||||
} catch (_) {}
|
||||
})();
|
||||
@@ -2,13 +2,13 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.audioEffects && window.slopsmith.audioEffects.version === 1) return;
|
||||
if (window.feedBack.audioEffects && window.feedBack.audioEffects.version === 1) return;
|
||||
|
||||
const SCHEMA = 'slopsmith.audio_effects.diagnostics.v1';
|
||||
const PLAN_SCHEMA = 'slopsmith.audio_effects.chain_plan.v1';
|
||||
const SCHEMA = 'feedBack.audio_effects.diagnostics.v1';
|
||||
const PLAN_SCHEMA = 'feedBack.audio_effects.chain_plan.v1';
|
||||
const OWNER_ID = 'core.audio.effects';
|
||||
const DEFAULT_ROUTE_KEY = 'desktop-main';
|
||||
const DEFAULT_TIMEOUT_MS = 2000;
|
||||
@@ -354,7 +354,7 @@
|
||||
|
||||
function _emit(event, detail) {
|
||||
try {
|
||||
if (window.slopsmith && typeof window.slopsmith.emit === 'function') window.slopsmith.emit(event, detail);
|
||||
if (window.feedBack && typeof window.feedBack.emit === 'function') window.feedBack.emit(event, detail);
|
||||
} catch (_) { /* best effort */ }
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@
|
||||
}
|
||||
|
||||
function _desktopExecutor() {
|
||||
const desktop = window.slopsmithDesktop && window.slopsmithDesktop.audioEffects;
|
||||
const desktop = window.feedBackDesktop && window.feedBackDesktop.audioEffects;
|
||||
if (!desktop || typeof desktop.loadChainPlan !== 'function') return null;
|
||||
const handlers = { loadChainPlan: desktop.loadChainPlan.bind(desktop) };
|
||||
if (typeof desktop.releaseRoute === 'function') handlers.releaseRoute = desktop.releaseRoute.bind(desktop);
|
||||
@@ -388,9 +388,9 @@
|
||||
if (typeof desktop.setRouteGain === 'function') handlers.setRouteGain = desktop.setRouteGain.bind(desktop);
|
||||
return {
|
||||
executorId: 'desktop-native',
|
||||
pluginId: 'slopsmith-desktop',
|
||||
pluginId: 'feedBack-desktop',
|
||||
routeKey: DEFAULT_ROUTE_KEY,
|
||||
label: 'Slopsmith Desktop native audio',
|
||||
label: 'FeedBack Desktop native audio',
|
||||
enabled: true,
|
||||
availability: 'available',
|
||||
sourceMode: 'native',
|
||||
@@ -1382,7 +1382,7 @@
|
||||
const shims = [
|
||||
{ shimId: 'audio-effects.legacy-tone-controls', capability: 'audio-effects', source: OWNER_ID, legacySurface: 'legacy tone/effect controls', reason: 'Legacy tone controls are attributed until providers use native audio-effects route operations.' },
|
||||
{ shimId: 'audio-effects.legacy-nam-routing', capability: 'audio-effects', source: OWNER_ID, legacySurface: 'NAM tone native-preset route interception', reason: 'Legacy NAM/Rig Builder route interception remains a migration bridge until providers resolve chain plans natively.' },
|
||||
{ shimId: 'audio-effects.legacy-native-load', capability: 'audio-effects', source: OWNER_ID, legacySurface: 'window.slopsmithDesktop.audio.loadPreset', reason: 'Direct Desktop native preset loading is attributed until providers route physical loads through compatible audio-effects executors.' },
|
||||
{ shimId: 'audio-effects.legacy-native-load', capability: 'audio-effects', source: OWNER_ID, legacySurface: 'window.feedBackDesktop.audio.loadPreset', reason: 'Direct Desktop native preset loading is attributed until providers route physical loads through compatible audio-effects executors.' },
|
||||
{ shimId: 'audio-effects.legacy-tone-db', capability: 'audio-effects', source: OWNER_ID, legacySurface: 'nam_tone.db tone_mappings', reason: 'Legacy NAM tone database mapping access is attributed until providers use the core audio-effects mapping index exclusively.' },
|
||||
{ shimId: 'audio-effects.legacy-midi-amp', capability: 'audio-effects', source: OWNER_ID, legacySurface: 'MIDI amp/external effect handoff', reason: 'Legacy external effect handoffs are attributed until provider route operations land.' },
|
||||
];
|
||||
@@ -1390,7 +1390,7 @@
|
||||
}
|
||||
|
||||
function _contributeDiagnostics() {
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (diagnostics && typeof diagnostics.contribute === 'function') {
|
||||
try { diagnostics.contribute('audio-effects', snapshot()); }
|
||||
catch (_) { /* diagnostics must not break playback */ }
|
||||
@@ -1429,7 +1429,7 @@
|
||||
getDiagnostics: snapshot,
|
||||
};
|
||||
|
||||
window.slopsmith.audioEffects = api;
|
||||
window.feedBack.audioEffects = api;
|
||||
_registerDomain();
|
||||
_registerBridgeMetadata();
|
||||
_contributeDiagnostics();
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.audioSession && window.slopsmith.audioSession.version === 1) return;
|
||||
if (window.feedBack.audioSession && window.feedBack.audioSession.version === 1) return;
|
||||
|
||||
const SCHEMA = 'slopsmith.audio_session.diagnostics.v1';
|
||||
const SCHEMA = 'feedBack.audio_session.diagnostics.v1';
|
||||
const DOMAINS = Object.freeze(['audio-mix', 'audio-input', 'audio-monitoring', 'stems']);
|
||||
const MAX_OUTCOMES = 100;
|
||||
const MAX_DOMAIN_ITEMS = 50;
|
||||
const FADER_OPERATION_TIMEOUT_MS = 2000;
|
||||
const INPUT_OPERATION_TIMEOUT_MS = 2000;
|
||||
const MONITORING_OPERATION_TIMEOUT_MS = 2000;
|
||||
const SELECTED_INPUT_STORAGE_KEY = 'slopsmith.audioInput.selectedLogicalSourceKey';
|
||||
const SELECTED_MONITORING_PROVIDER_STORAGE_KEY = 'slopsmith.audioMonitoring.selectedLogicalMonitoringKey';
|
||||
const DIRECT_MONITOR_STORAGE_KEY = 'slopsmith.audioMonitoring.directMonitorPreference';
|
||||
const SELECTED_INPUT_STORAGE_KEY = 'feedBack.audioInput.selectedLogicalSourceKey';
|
||||
const SELECTED_MONITORING_PROVIDER_STORAGE_KEY = 'feedBack.audioMonitoring.selectedLogicalMonitoringKey';
|
||||
const DIRECT_MONITOR_STORAGE_KEY = 'feedBack.audioMonitoring.directMonitorPreference';
|
||||
const OWNER_ID = 'core.audio.session';
|
||||
const REQUIRED_MIX_KINDS = Object.freeze(['song', 'plugin', 'stem', 'monitoring', 'preview']);
|
||||
const MIX_KINDS = new Set([...REQUIRED_MIX_KINDS, 'analyser', 'other']);
|
||||
@@ -2912,19 +2912,19 @@
|
||||
function _registerBridgeMetadata() {
|
||||
if (typeof capabilities.registerCompatibilityShim !== 'function') return;
|
||||
const shims = [
|
||||
{ shimId: 'audio-mix.fader-registry', capability: 'audio-mix', source: OWNER_ID, legacySurface: 'window.slopsmith.audio.registerFader', reason: 'Legacy mixer faders are attributed as audio-mix participants.' },
|
||||
{ shimId: 'audio-mix.fader-registry', capability: 'audio-mix', source: OWNER_ID, legacySurface: 'window.feedBack.audio.registerFader', reason: 'Legacy mixer faders are attributed as audio-mix participants.' },
|
||||
{ shimId: 'audio-mix.song-volume', capability: 'audio-mix', source: OWNER_ID, legacySurface: 'applySongVolume', reason: 'Legacy song volume writes are attributed as audio-mix route/fader bridge hits.' },
|
||||
{ shimId: 'audio-mix.analyser', capability: 'audio-mix', source: OWNER_ID, legacySurface: '3D Highway analyser fallback', reason: 'Legacy analyser taps are attributed until renderers request analyser providers through audio-mix.' },
|
||||
{ shimId: 'audio-input.legacy-source', capability: 'audio-input', source: OWNER_ID, legacySurface: 'plugin/browser input source handoff', reason: 'Legacy input source handoffs are attributed while note-detection input providers migrate to audio-input.' },
|
||||
{ shimId: 'audio-monitoring.audio-barrier', capability: 'audio-monitoring', source: OWNER_ID, legacySurface: 'window.slopsmithAudioBarrier', reason: 'Legacy audio startup barriers are attributed as monitoring readiness bridges.' },
|
||||
{ shimId: 'stems.master-volume', capability: 'stems', source: OWNER_ID, legacySurface: 'window.slopsmith.stems.setMasterVolume', reason: 'Legacy Stems master volume calls are attributed while native owner operations are staged.' },
|
||||
{ shimId: 'audio-monitoring.audio-barrier', capability: 'audio-monitoring', source: OWNER_ID, legacySurface: 'window.feedBackAudioBarrier', reason: 'Legacy audio startup barriers are attributed as monitoring readiness bridges.' },
|
||||
{ shimId: 'stems.master-volume', capability: 'stems', source: OWNER_ID, legacySurface: 'window.feedBack.stems.setMasterVolume', reason: 'Legacy Stems master volume calls are attributed while native owner operations are staged.' },
|
||||
{ shimId: 'stems.private-state', capability: 'stems', source: OWNER_ID, legacySurface: 'plugin-specific Stems/NAM ducking handshake', reason: 'Existing stem automation requesters are tracked until they migrate to native claims.' },
|
||||
];
|
||||
for (const shim of shims) capabilities.registerCompatibilityShim({ ...shim, status: 'active' });
|
||||
}
|
||||
|
||||
function _contributeDiagnostics() {
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (diagnostics && typeof diagnostics.contribute === 'function') {
|
||||
try { diagnostics.contribute('audio-session', snapshot()); }
|
||||
catch (_) { /* diagnostics must not break playback */ }
|
||||
@@ -2968,7 +2968,7 @@
|
||||
getDiagnostics: snapshot,
|
||||
};
|
||||
|
||||
window.slopsmith.audioSession = api;
|
||||
window.feedBack.audioSession = api;
|
||||
_registerDomains();
|
||||
_registerBridgeMetadata();
|
||||
_contributeDiagnostics();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 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.slopsmith.libraryCardActions
|
||||
* 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) }
|
||||
@@ -20,9 +20,9 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
const w = (typeof window !== 'undefined') ? window : {};
|
||||
w.slopsmith = w.slopsmith || {};
|
||||
if (w.slopsmith.libraryCardActions && w.slopsmith.libraryCardActions.version === 1) return;
|
||||
const capabilities = w.slopsmith.capabilities;
|
||||
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'];
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
|
||||
function snapshot() {
|
||||
return {
|
||||
schema: 'slopsmith.library_card_actions.v1',
|
||||
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 })),
|
||||
};
|
||||
}
|
||||
@@ -148,5 +148,5 @@
|
||||
|
||||
const api = { version: 1, register, unregister, list, run, snapshot };
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = api; // node tests
|
||||
w.slopsmith.libraryCardActions = api;
|
||||
w.feedBack.libraryCardActions = api;
|
||||
})();
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.libraryProviders && window.slopsmith.libraryProviders.version === 1) return;
|
||||
if (window.feedBack.libraryProviders && window.feedBack.libraryProviders.version === 1) return;
|
||||
|
||||
const PROVIDER_KEY = 'slopsmith.libProvider';
|
||||
const PROVIDER_KEY = 'feedBack.libProvider';
|
||||
const LOCAL_PROVIDER = Object.freeze({
|
||||
id: 'local',
|
||||
label: 'My Library',
|
||||
@@ -302,17 +302,17 @@
|
||||
};
|
||||
|
||||
function _contributeDiagnostics() {
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (diagnostics && typeof diagnostics.contribute === 'function') {
|
||||
try {
|
||||
diagnostics.contribute('library-capability', {
|
||||
schema: 'slopsmith.library_capability.v1',
|
||||
schema: 'feedBack.library_capability.v1',
|
||||
..._snapshot(),
|
||||
});
|
||||
} catch (_) { /* diagnostics must not break the library */ }
|
||||
}
|
||||
}
|
||||
|
||||
window.slopsmith.libraryProviders = providerApi;
|
||||
window.feedBack.libraryProviders = providerApi;
|
||||
_contributeDiagnostics();
|
||||
})();
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.midiInput && window.slopsmith.midiInput.version === 1) return;
|
||||
if (window.feedBack.midiInput && window.feedBack.midiInput.version === 1) return;
|
||||
|
||||
const STORAGE_KEY = 'slopsmith.midiInput.selectedLogicalSourceKey';
|
||||
const STORAGE_KEY = 'feedBack.midiInput.selectedLogicalSourceKey';
|
||||
|
||||
// providerId → { id, label, participantId, handlers:{ enumerate, open, close } }
|
||||
// handlers are LIVE functions supplied in-page via the public global; they
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
|
||||
function _contributeDiagnostics() {
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (!diagnostics || typeof diagnostics.contribute !== 'function') return;
|
||||
try {
|
||||
const snap = _snapshot();
|
||||
@@ -113,7 +113,7 @@
|
||||
sources: snap.sources.map(({ label: _l, ...safe }) => safe),
|
||||
};
|
||||
diagnostics.contribute('midi-input-capability', {
|
||||
schema: 'slopsmith.midi_input.diagnostics.v1',
|
||||
schema: 'feedBack.midi_input.diagnostics.v1',
|
||||
...redacted,
|
||||
});
|
||||
} catch (_) { /* diagnostics must not break input */ }
|
||||
@@ -358,7 +358,7 @@
|
||||
// ── public global (live surface for in-page consumers) ──────────────────
|
||||
// Providers register live handlers here; consumers (input_setup, piano,
|
||||
// drums) get a live session handle for the "play a note" check.
|
||||
window.slopsmith.midiInput = {
|
||||
window.feedBack.midiInput = {
|
||||
version: 1,
|
||||
snapshot: _snapshot,
|
||||
listSources: () => _sourceListShape(),
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.noteDetection && window.slopsmith.noteDetection.version === 1) return;
|
||||
if (window.feedBack.noteDetection && window.feedBack.noteDetection.version === 1) return;
|
||||
|
||||
// providerId → { id, label, kind, primitives } — kind is 'midi' (exact
|
||||
// verdicts from a digital instrument), 'engine' (desktop JUCE verifier),
|
||||
@@ -79,7 +79,7 @@
|
||||
}
|
||||
|
||||
function _contributeDiagnostics() {
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (diagnostics && typeof diagnostics.contribute === 'function') {
|
||||
try {
|
||||
const snap = _snapshot();
|
||||
@@ -91,7 +91,7 @@
|
||||
providers: snap.providers.map(({ label: _label, ...safe }) => safe),
|
||||
};
|
||||
diagnostics.contribute('note-detection-capability', {
|
||||
schema: 'slopsmith.note_detection_capability.v1',
|
||||
schema: 'feedBack.note_detection_capability.v1',
|
||||
...redacted,
|
||||
});
|
||||
} catch (_) { /* diagnostics must not break detection */ }
|
||||
@@ -329,7 +329,7 @@
|
||||
return true;
|
||||
}
|
||||
if (!_wrapLegacyNoteStateProvider()) {
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
if (typeof sm.on === 'function') {
|
||||
try { sm.on('song:loaded', () => { _wrapLegacyNoteStateProvider(); }); }
|
||||
catch (_) { /* best-effort */ }
|
||||
@@ -339,7 +339,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
window.slopsmith.noteDetection = {
|
||||
window.feedBack.noteDetection = {
|
||||
version: 1,
|
||||
snapshot: _snapshot,
|
||||
reportHit: (detail) => _reportResult('hit', detail),
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.playback && window.slopsmith.playback.version === 1) return;
|
||||
if (window.feedBack.playback && window.feedBack.playback.version === 1) return;
|
||||
|
||||
const SCHEMA = 'slopsmith.playback.diagnostics.v1';
|
||||
const SCHEMA = 'feedBack.playback.diagnostics.v1';
|
||||
const OWNER_ID = 'core.playback';
|
||||
const MAX_CURRENT_OUTCOMES = 50;
|
||||
const MAX_CURRENT_EVENTS = 50;
|
||||
@@ -659,12 +659,12 @@
|
||||
}
|
||||
|
||||
function _flushDiagnostics() {
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (diagnostics && typeof diagnostics.contribute === 'function') {
|
||||
try { diagnostics.contribute('playback', snapshot({ exportMode: 'exported' })); }
|
||||
catch (_) { /* diagnostics must never break playback */ }
|
||||
}
|
||||
try { window.dispatchEvent(new CustomEvent('slopsmith:playback:changed', { detail: { timestamp: _now() } })); }
|
||||
try { window.dispatchEvent(new CustomEvent('feedBack:playback:changed', { detail: { timestamp: _now() } })); }
|
||||
catch (_) { /* support UI refresh is best effort */ }
|
||||
}
|
||||
|
||||
@@ -1062,7 +1062,7 @@
|
||||
}
|
||||
|
||||
function _installLegacyEventBridge() {
|
||||
if (!window.slopsmith || typeof window.slopsmith.on !== 'function') return;
|
||||
if (!window.feedBack || typeof window.feedBack.on !== 'function') return;
|
||||
const map = {
|
||||
'song:loading': 'loading',
|
||||
'song:loaded': 'ready',
|
||||
@@ -1076,7 +1076,7 @@
|
||||
'loop:restart': 'loop-restarted',
|
||||
};
|
||||
for (const [legacy, playbackEvent] of Object.entries(map)) {
|
||||
window.slopsmith.on(legacy, event => {
|
||||
window.feedBack.on(legacy, event => {
|
||||
// Skip echoes of our own command: while a command handler is
|
||||
// driving the transport adapter, the song:* events it produces
|
||||
// are not genuine legacy-surface usage and would duplicate the
|
||||
@@ -1141,7 +1141,7 @@
|
||||
inspect: () => snapshot({ exportMode: 'exported' }),
|
||||
};
|
||||
|
||||
window.slopsmith.playback = api;
|
||||
window.feedBack.playback = api;
|
||||
_installLegacyEventBridge();
|
||||
_contributeDiagnostics();
|
||||
})();
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.tunings && window.slopsmith.tunings.version === 1) return;
|
||||
if (window.feedBack.tunings && window.feedBack.tunings.version === 1) return;
|
||||
|
||||
capabilities.registerOwner('tuning', {
|
||||
description: 'Provides merged guitar/bass tunings from core defaults and plugin contributors.',
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
window.slopsmith.tunings = Object.freeze({
|
||||
window.feedBack.tunings = Object.freeze({
|
||||
version: 1,
|
||||
get: function () {
|
||||
return fetch('/api/tunings').then(function (r) { return r.json(); });
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
// fallback are attributed in redaction-safe diagnostics.
|
||||
//
|
||||
// Legacy bridge: today's plugins keep working unchanged. Discovery still
|
||||
// happens through `type: "visualization"` manifests and `window.slopsmithViz_*`
|
||||
// happens through `type: "visualization"` manifests and `window.feedBackViz_*`
|
||||
// factory globals — both are registered here as compatibility shims and their
|
||||
// hits accounted, per the promotion checklist in docs/capability-roadmap.md.
|
||||
// app.js calls the small runtime API on `window.slopsmith.vizDomain` at its
|
||||
// app.js calls the small runtime API on `window.feedBack.vizDomain` at its
|
||||
// existing integration points (picker population, renderer install); this
|
||||
// module owns everything else.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
const capabilities = window.slopsmith.capabilities;
|
||||
window.feedBack = window.feedBack || {};
|
||||
const capabilities = window.feedBack.capabilities;
|
||||
if (!capabilities || capabilities.version !== 1) return;
|
||||
if (window.slopsmith.vizDomain && window.slopsmith.vizDomain.version === 1) return;
|
||||
if (window.feedBack.vizDomain && window.feedBack.vizDomain.version === 1) return;
|
||||
|
||||
// providerId → { id, label, contextType, claims } in picker (= directory)
|
||||
// order, which is the auto-match precedence order.
|
||||
@@ -35,7 +35,7 @@
|
||||
function _degraded(reason, payload = {}) { return { outcome: 'degraded', reason, payload }; }
|
||||
|
||||
function _factoryFor(providerId) {
|
||||
const factory = window['slopsmithViz_' + providerId];
|
||||
const factory = window['feedBackViz_' + providerId];
|
||||
return typeof factory === 'function' ? factory : null;
|
||||
}
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
}
|
||||
|
||||
function _contributeDiagnostics() {
|
||||
const diagnostics = window.slopsmith && window.slopsmith.diagnostics;
|
||||
const diagnostics = window.feedBack && window.feedBack.diagnostics;
|
||||
if (diagnostics && typeof diagnostics.contribute === 'function') {
|
||||
try {
|
||||
diagnostics.contribute('visualization-capability', {
|
||||
schema: 'slopsmith.visualization_capability.v1',
|
||||
schema: 'feedBack.visualization_capability.v1',
|
||||
..._snapshot(),
|
||||
});
|
||||
} catch (_) { /* diagnostics must not break rendering */ }
|
||||
@@ -145,7 +145,7 @@
|
||||
return _deepFreeze(settings);
|
||||
}
|
||||
|
||||
// Per-instance control descriptors (slopsmith#849) come from the generic
|
||||
// Per-instance control descriptors (feedBack#849) come from the generic
|
||||
// capability participant model: a provider declares them in its manifest
|
||||
// (capabilities.visualization.settings), core registers + normalizes them,
|
||||
// and we read them back here by pluginId. This keeps inspect('visualization')
|
||||
@@ -187,7 +187,7 @@
|
||||
// registered capability participant (not an app.js side channel). A
|
||||
// consuming host (splitscreen) renders these generically and applies
|
||||
// values via the renderer instance's applySetting(key, value)
|
||||
// (slopsmith#849). Deep-cloned + frozen so a list-providers consumer
|
||||
// (feedBack#849). Deep-cloned + frozen so a list-providers consumer
|
||||
// can't mutate internal provider state through the shallow snapshot
|
||||
// clone. Absent until a provider declares them in its manifest.
|
||||
const declaredSettings = settingsById.get(id);
|
||||
@@ -200,8 +200,8 @@
|
||||
if (typeof capabilities.registerCompatibilityShim === 'function') {
|
||||
capabilities.registerCompatibilityShim({
|
||||
capability: 'visualization',
|
||||
shimId: 'visualization:window.slopsmithViz_*',
|
||||
legacySurface: 'window.slopsmithViz_* factory globals',
|
||||
shimId: 'visualization:window.feedBackViz_*',
|
||||
legacySurface: 'window.feedBackViz_* factory globals',
|
||||
source: 'core.visualization',
|
||||
ownerPluginId: 'core.visualization',
|
||||
reason: 'Renderer factories are discovered via window globals until plugins declare visualization capabilities in their manifests.',
|
||||
@@ -270,7 +270,7 @@
|
||||
// Mirror the legacy event bus into domain events so observers can rely on
|
||||
// `visualization:*` without knowing the window bus names. Guarded: the
|
||||
// bus may not exist in minimal/test environments.
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
if (typeof sm.on === 'function') {
|
||||
try {
|
||||
sm.on('viz:renderer:ready', () => _emit('renderer-ready', { providerId: activeProviderId }));
|
||||
@@ -328,7 +328,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
window.slopsmith.vizDomain = {
|
||||
window.feedBack.vizDomain = {
|
||||
version: 1,
|
||||
snapshot: _snapshot,
|
||||
refreshProviders,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Slopsmith diagnostics — client console capture + hardware probe + contribute API.
|
||||
// FeedBack diagnostics — client console capture + hardware probe + contribute API.
|
||||
//
|
||||
// Loaded ASAP from index.html so the console-wrap is in place before
|
||||
// app.js (and any plugins) start logging. See
|
||||
@@ -6,7 +6,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (window.slopsmith && window.slopsmith.diagnostics) return; // idempotent
|
||||
if (window.feedBack && window.feedBack.diagnostics) return; // idempotent
|
||||
|
||||
const MAX_ENTRIES = 500;
|
||||
const MAX_ENTRY_BYTES = 4096;
|
||||
@@ -162,14 +162,14 @@
|
||||
function _detectRuntime() {
|
||||
const ua = _ua();
|
||||
if (/Electron\//.test(ua)) {
|
||||
const versions = (window.slopsmithElectron && window.slopsmithElectron.versions) || {};
|
||||
const versions = (window.feedBackElectron && window.feedBackElectron.versions) || {};
|
||||
return {
|
||||
kind: 'electron',
|
||||
electron: versions.electron || null,
|
||||
chrome: versions.chrome || null,
|
||||
node: versions.node || null,
|
||||
v8: versions.v8 || null,
|
||||
app_version: (window.slopsmithElectron && window.slopsmithElectron.appVersion) || null,
|
||||
app_version: (window.feedBackElectron && window.feedBackElectron.appVersion) || null,
|
||||
};
|
||||
}
|
||||
return { kind: 'browser' };
|
||||
@@ -283,7 +283,7 @@
|
||||
// Regexp matching localStorage key names that are likely to contain
|
||||
// secrets. Values for matching keys are replaced with "<redacted>"
|
||||
// regardless of the user's redaction toggle, since plugin authors
|
||||
// commonly store tokens in localStorage (cf. slopsmith plugin guide).
|
||||
// commonly store tokens in localStorage (cf. feedBack plugin guide).
|
||||
const _LS_SECRET_KEY_RE = /(?:^|[-_.])(api[_-]?key|token|secret|password|passwd|pwd|auth|bearer|credential|apikey)($|[-_.])/i;
|
||||
|
||||
function snapshotLocalStorage() {
|
||||
@@ -355,8 +355,8 @@
|
||||
}, { plugin_count: 0, ui_contribution_count: 0, runtime_domain_count: 0 });
|
||||
}
|
||||
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
window.slopsmith.diagnostics = {
|
||||
window.feedBack = window.feedBack || {};
|
||||
window.feedBack.diagnostics = {
|
||||
snapshot: snapshotConsole,
|
||||
snapshotConsole,
|
||||
snapshotHardware,
|
||||
|
||||
+60
-60
@@ -23,7 +23,7 @@ function createHighway() {
|
||||
let _juceRoutingPromise = Promise.resolve();
|
||||
|
||||
function _audioSession() {
|
||||
return window.slopsmith && window.slopsmith.audioSession;
|
||||
return window.feedBack && window.feedBack.audioSession;
|
||||
}
|
||||
|
||||
function _reportAudioSessionStart(info) {
|
||||
@@ -102,7 +102,7 @@ function createHighway() {
|
||||
// pause listener early-returns and never emits.
|
||||
let _chartLastAdvanceAt = 0;
|
||||
// Observed playback rate, derived from the actual delta between
|
||||
// successive anchor updates. Slopsmith's speed slider (audio
|
||||
// successive anchor updates. FeedBack's speed slider (audio
|
||||
// playbackRate != 1) means audio time advances slower/faster than
|
||||
// real time; interpolating with a fixed 1x rate would drift. Each
|
||||
// re-anchor refines the estimate from the latest segment. Default
|
||||
@@ -113,7 +113,7 @@ function createHighway() {
|
||||
// threshold for "audio looks paused" — if setTime hasn't advanced t
|
||||
// in this long, treat as paused.
|
||||
const _CHART_MAX_INTERP_MS = 100;
|
||||
// Visibility-aware rAF (slopsmith#246): when the canvas is hidden
|
||||
// Visibility-aware rAF (feedBack#246): when the canvas is hidden
|
||||
// (display:none on itself or any ancestor — e.g. splitscreen's
|
||||
// workaround), pause renderer.draw and emit highway:visibility on
|
||||
// transitions so renderers that mount sibling DOM (3D Highway's
|
||||
@@ -123,7 +123,7 @@ function createHighway() {
|
||||
let _visibleOverride = null;
|
||||
let _lastVisible = null;
|
||||
let animFrame = null;
|
||||
// Paused-render throttle (slopsmith#654). The rAF loop runs
|
||||
// Paused-render throttle (feedBack#654). The rAF loop runs
|
||||
// unconditionally and only gates on visibility + ready, never on
|
||||
// playback — so an expensive renderer (3D Highway's Three.js WebGL
|
||||
// scene) does a full render every frame even while paused. That is
|
||||
@@ -177,7 +177,7 @@ function createHighway() {
|
||||
// "drums loaded but empty".
|
||||
let drumTab = null; // { version, name, kit: [...], hits: [...] }
|
||||
let ready = false;
|
||||
// Master-difficulty (slopsmith#48). _phrases stays null as a
|
||||
// Master-difficulty (feedBack#48). _phrases stays null as a
|
||||
// "slider disabled" sentinel when the source chart has no ladder
|
||||
// data (GP imports, legacy sloppak) — the server omits the
|
||||
// `phrases` message entirely in that case. When populated, the
|
||||
@@ -216,7 +216,7 @@ function createHighway() {
|
||||
// absent key reads as on; only an explicit 'false' hides it.
|
||||
let _showFingerHints = localStorage.getItem('showFingerHints') !== 'false';
|
||||
let _drawHooks = []; // plugin draw callbacks: fn(ctx, W, H)
|
||||
// slopsmith#254 — per-note judgment overlay. A plugin (note_detect)
|
||||
// feedBack#254 — per-note judgment overlay. A plugin (note_detect)
|
||||
// registers fn(note, chartTime) -> 'hit' | 'active' | 'miss' | null
|
||||
// (or { state, alpha?, color? }); renderers consult it per visible
|
||||
// note so the gem itself can light up / a held sustain can glow,
|
||||
@@ -229,7 +229,7 @@ function createHighway() {
|
||||
const v = parseFloat(localStorage.getItem('renderScale') || '1');
|
||||
return Number.isFinite(v) ? Math.max(0.25, Math.min(1, v)) : 1;
|
||||
})();
|
||||
// Load-adaptive render scale (slopsmith#654). _renderScale is the
|
||||
// Load-adaptive render scale (feedBack#654). _renderScale is the
|
||||
// user's manual ceiling; _autoScale is an automatic multiplier the
|
||||
// draw loop lowers when the active renderer's per-frame cost blows
|
||||
// the budget (a heavy 3D Highway WebGL scene on a weak GPU, or on
|
||||
@@ -270,8 +270,8 @@ function createHighway() {
|
||||
let _lefty = localStorage.getItem('lefty') === '1';
|
||||
let _lastChordOnFretLine = null; // chord object currently shown on fret line
|
||||
let _chordFretLineNotes = []; // notes to render on fret line
|
||||
const _frameMismatchWarned = new Set(); // chord ids already warned about (slopsmith#88)
|
||||
// Per-chord render info, computed lazily once per src array (slopsmith#88).
|
||||
const _frameMismatchWarned = new Set(); // chord ids already warned about (feedBack#88)
|
||||
// Per-chord render info, computed lazily once per src array (feedBack#88).
|
||||
const _chordRenderInfo = new WeakMap(); // chord -> { chainIndex, chainLen, isFull, baseFret, sortedNotes, nonZeroNotes, nonZeroFrets, allMuted, hasMultipleNotes }
|
||||
let _chordRenderCacheSrc = null;
|
||||
let _chordRenderCacheInverted = null;
|
||||
@@ -452,7 +452,7 @@ function createHighway() {
|
||||
}
|
||||
|
||||
function updateSmoothAnchor(anchor, dt) {
|
||||
// Smoothing rate balances two regressions seen in slopsmith#88:
|
||||
// Smoothing rate balances two regressions seen in feedBack#88:
|
||||
// rate=1.0 (was) snapped to target every frame — visible jitter
|
||||
// on aerial passages where anchors moved every few frames.
|
||||
// rate=0.15 (Knaifhogg) was too gentle — large jumps (low frets
|
||||
@@ -560,7 +560,7 @@ function createHighway() {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// ── Per-note judgment state (slopsmith#254) ──────────────────────────
|
||||
// ── Per-note judgment state (feedBack#254) ──────────────────────────
|
||||
// Resolves the registered provider for one chart note. Returns null
|
||||
// when no provider is set, the provider throws, it reports nothing,
|
||||
// or the reported alpha is non-positive. Otherwise a normalized
|
||||
@@ -690,7 +690,7 @@ function createHighway() {
|
||||
|
||||
// ── Drawing ──────────────────────────────────────────────────────────
|
||||
//
|
||||
// slopsmith#36 — swappable renderers.
|
||||
// feedBack#36 — swappable renderers.
|
||||
//
|
||||
// The default renderer below is the original 2D canvas highway. Its
|
||||
// methods still reach into the factory closure (ctx, beats, notes,
|
||||
@@ -776,7 +776,7 @@ function createHighway() {
|
||||
// signal to fall back to legacy MIDI-encoded drums.
|
||||
drumTab,
|
||||
|
||||
// Master-difficulty (slopsmith#48)
|
||||
// Master-difficulty (feedBack#48)
|
||||
mastery: _mastery,
|
||||
hasPhraseData: !!(_phrases && _phrases.length > 0),
|
||||
// When phrase data authored ANY handshape, respect the filtered
|
||||
@@ -809,7 +809,7 @@ function createHighway() {
|
||||
project,
|
||||
fretX,
|
||||
|
||||
// Per-note judgment overlay (slopsmith#254). Renderers call
|
||||
// Per-note judgment overlay (feedBack#254). Renderers call
|
||||
// this per visible note / chord-note to find out whether a
|
||||
// scorer (note_detect) has flagged it hit / actively-held /
|
||||
// missed, so the gem itself can light up instead of relying
|
||||
@@ -941,8 +941,8 @@ function createHighway() {
|
||||
// per-panel picker in Wave C) that the factory auto-reverted
|
||||
// to the default renderer — so the UI / persisted selection
|
||||
// don't keep advertising the broken plugin.
|
||||
if (window.slopsmith && typeof window.slopsmith.emit === 'function') {
|
||||
try { window.slopsmith.emit('viz:reverted', { reason }); }
|
||||
if (window.feedBack && typeof window.feedBack.emit === 'function') {
|
||||
try { window.feedBack.emit('viz:reverted', { reason }); }
|
||||
catch (e) { console.error('viz:reverted emit:', e); }
|
||||
}
|
||||
}
|
||||
@@ -952,8 +952,8 @@ function createHighway() {
|
||||
// and is actively drawing (its sync init returned, or its async
|
||||
// readyPromise resolved). App.js uses this to update the Auto
|
||||
// closed-state label only once the renderer is confirmed active.
|
||||
if (window.slopsmith && typeof window.slopsmith.emit === 'function') {
|
||||
try { window.slopsmith.emit('viz:renderer:ready', {}); }
|
||||
if (window.feedBack && typeof window.feedBack.emit === 'function') {
|
||||
try { window.feedBack.emit('viz:renderer:ready', {}); }
|
||||
catch (e) { console.error('viz:renderer:ready emit:', e); }
|
||||
}
|
||||
}
|
||||
@@ -1045,9 +1045,9 @@ function createHighway() {
|
||||
// canvas element across events. Lazy lookups via
|
||||
// getElementById('highway') do not need this — they'll pick
|
||||
// up the new element on their next call.
|
||||
if (window.slopsmith && typeof window.slopsmith.emit === 'function') {
|
||||
if (window.feedBack && typeof window.feedBack.emit === 'function') {
|
||||
try {
|
||||
window.slopsmith.emit('highway:canvas-replaced', {
|
||||
window.feedBack.emit('highway:canvas-replaced', {
|
||||
oldCanvas, newCanvas, contextType: newType,
|
||||
});
|
||||
} catch (e) { console.error('highway:canvas-replaced emit:', e); }
|
||||
@@ -1252,9 +1252,9 @@ function createHighway() {
|
||||
if (v === _lastVisible) return;
|
||||
_lastVisible = v;
|
||||
if (typeof window !== 'undefined'
|
||||
&& window.slopsmith
|
||||
&& typeof window.slopsmith.emit === 'function') {
|
||||
window.slopsmith.emit('highway:visibility', { visible: v, canvas });
|
||||
&& window.feedBack
|
||||
&& typeof window.feedBack.emit === 'function') {
|
||||
window.feedBack.emit('highway:visibility', { visible: v, canvas });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1360,7 +1360,7 @@ function createHighway() {
|
||||
// Decide once whether this frame renders, and reuse it for both the
|
||||
// perf-HUD reset and the draw gate. `_lastVisible` going false has two
|
||||
// distinct causes that differ for a CUSTOM renderer painting its own
|
||||
// surface (slopsmith#819):
|
||||
// surface (feedBack#819):
|
||||
// • override-hide (`_visibleOverride === false`) — a renderer called
|
||||
// `setVisible(false)` because an opaque overlay covers the
|
||||
// *canvas*. The `highway:visibility` event already fired above so
|
||||
@@ -1376,7 +1376,7 @@ function createHighway() {
|
||||
// renderer, even if an override-hide is also in effect.
|
||||
// Previously the gate was a bare `if (!_lastVisible) return`, which
|
||||
// starved an active custom renderer's draw() on an override-hide — the
|
||||
// Tab View cursor froze in single-player (slopsmith#734).
|
||||
// Tab View cursor froze in single-player (feedBack#734).
|
||||
let _rendering = _lastVisible;
|
||||
if (!_rendering
|
||||
&& _visibleOverride === false
|
||||
@@ -1558,7 +1558,7 @@ function createHighway() {
|
||||
}
|
||||
|
||||
function drawNote(W, H, x, y, scale, string, fret, opts, ns) {
|
||||
// ns (slopsmith#254): normalized judgment state from _noteState,
|
||||
// ns (feedBack#254): normalized judgment state from _noteState,
|
||||
// or null/undefined. `lit` means render the gem in the bright
|
||||
// string colour with an additive halo; a miss gets a faint red
|
||||
// wash instead. ns absent → byte-for-byte the original render.
|
||||
@@ -1603,7 +1603,7 @@ function createHighway() {
|
||||
ctx.fillStyle = color;
|
||||
roundRect(ctx, W/2 - hw, y - barH/2, hw * 2, barH, 2);
|
||||
ctx.fill();
|
||||
// Judgment glow (slopsmith#254) — central halo on the bar.
|
||||
// Judgment glow (feedBack#254) — central halo on the bar.
|
||||
// _paintGemGlow takes a half-extent; barH is the full bar height.
|
||||
_paintGemGlow(W/2, y, barH * 0.5, string, ns);
|
||||
// "0" label
|
||||
@@ -1708,7 +1708,7 @@ function createHighway() {
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Judgment glow (slopsmith#254) — additive halo for a correct
|
||||
// Judgment glow (feedBack#254) — additive halo for a correct
|
||||
// hit / held sustain, faint red wash for a miss. Drawn before
|
||||
// the fret number so the number stays legible on top.
|
||||
_paintGemGlow(x, y, isHarmonic ? half * 1.2 : half, string, ns);
|
||||
@@ -1913,7 +1913,7 @@ function createHighway() {
|
||||
const sw0 = Math.max(2, 6 * p0.scale);
|
||||
const sw1 = Math.max(2, 6 * p1.scale);
|
||||
|
||||
// slopsmith#254 — a sustain that's currently being held
|
||||
// feedBack#254 — a sustain that's currently being held
|
||||
// correctly "sizzles" in the bright string colour (glow +
|
||||
// flickering brightness + a crackling current down the
|
||||
// middle); otherwise the usual dim trail. A miss is left dim
|
||||
@@ -1931,7 +1931,7 @@ function createHighway() {
|
||||
// frames yet drifts on song progression; combined with
|
||||
// _frameIdx + n.s it gives a non-correlated walk through
|
||||
// the LUT, matching the original visual intent
|
||||
// (slopsmith#254 comment above).
|
||||
// (feedBack#254 comment above).
|
||||
const seedBase = (_frameIdx + n.s + ((n.t * 60) | 0)) | 0;
|
||||
ctx.save();
|
||||
ctx.fillStyle = col;
|
||||
@@ -1977,7 +1977,7 @@ function createHighway() {
|
||||
}
|
||||
|
||||
function drawNotes(W, H) {
|
||||
// Master-difficulty filter (slopsmith#48): when the source had
|
||||
// Master-difficulty filter (feedBack#48): when the source had
|
||||
// phrase-level ladder data, render from the mastery-filtered
|
||||
// array. _filteredNotes stays null for slider-disabled sources
|
||||
// so rendering falls through to the flat notes array unchanged.
|
||||
@@ -2315,7 +2315,7 @@ function createHighway() {
|
||||
const cn = sorted[j];
|
||||
const x = fretX(cn.f, p.scale, W);
|
||||
const ny = p.y * H - actualTotalH / 2 + j * actualSpread;
|
||||
// slopsmith#254 — per-string judgment, keyed by the
|
||||
// feedBack#254 — per-string judgment, keyed by the
|
||||
// chord's chart time (matches how note_detect stores it).
|
||||
const cnNs = _noteStateProvider ? _noteState(cn, ch.t) : null;
|
||||
|
||||
@@ -2606,7 +2606,7 @@ function createHighway() {
|
||||
return lo;
|
||||
}
|
||||
|
||||
// ── Chord rendering — chains, frames, fretline preview (slopsmith#88) ──
|
||||
// ── Chord rendering — chains, frames, fretline preview (feedBack#88) ──
|
||||
//
|
||||
// Charts often repeat the same chord shape several times in a
|
||||
// row (e.g. a G strummed 4 times). We call a contiguous run of same-id
|
||||
@@ -2962,7 +2962,7 @@ function createHighway() {
|
||||
ready = false;
|
||||
notes = []; chords = []; handShapes = []; beats = []; sections = []; anchors = []; chordTemplates = []; lyrics = []; lyricsSource = ""; toneChanges = []; toneBase = ""; drumTab = null;
|
||||
stringCount = 6; // default until song_info arrives
|
||||
// Reset phrase ladder + filter (slopsmith#48). _mastery
|
||||
// Reset phrase ladder + filter (feedBack#48). _mastery
|
||||
// persists across arrangement switches — the slider's
|
||||
// position stays put. Filter rebuilds on the next `ready`
|
||||
// once the new arrangement's phrases arrive (or stays
|
||||
@@ -3070,7 +3070,7 @@ function createHighway() {
|
||||
|
||||
getLefty() { return _lefty; },
|
||||
|
||||
// Master-difficulty (slopsmith#48). Per-instance: splitscreen
|
||||
// Master-difficulty (feedBack#48). Per-instance: splitscreen
|
||||
// plugins that call createHighway() separately get their own
|
||||
// _mastery via closure.
|
||||
setMastery(fraction) {
|
||||
@@ -3201,7 +3201,7 @@ function createHighway() {
|
||||
}
|
||||
// Pick up the active arrangement's string count.
|
||||
// Prefer the explicit `stringCount` field (added
|
||||
// in slopsmith-plugin-3dhighway#7); fall back to
|
||||
// in feedBack-plugin-3dhighway#7); fall back to
|
||||
// `tuning.length` for older servers that haven't
|
||||
// started emitting it (works correctly for
|
||||
// GP-imported sources where tuning is already
|
||||
@@ -3322,7 +3322,7 @@ function createHighway() {
|
||||
? window._juceAudioUrl === msg.audio_url
|
||||
: (audio.src && audio.src.includes(audioFilename));
|
||||
if (!alreadyLoaded) {
|
||||
const juceApi = window.slopsmithDesktop?.audio;
|
||||
const juceApi = window.feedBackDesktop?.audio;
|
||||
if (isAudioUrl && juceApi) {
|
||||
// Run JUCE routing off the critical message-processing chain
|
||||
// so subsequent notes/chords/ready messages aren't blocked
|
||||
@@ -3347,19 +3347,19 @@ function createHighway() {
|
||||
// plugin barrier that never settles cannot wedge song entry;
|
||||
// the try/catch + Promise.resolve wrapper also covers a
|
||||
// synchronous throw from the barrier call.
|
||||
if (typeof window.slopsmithAudioBarrier === 'function') {
|
||||
if (typeof window.feedBackAudioBarrier === 'function') {
|
||||
let barrierTimer;
|
||||
let barrierTimedOut = false;
|
||||
try {
|
||||
await Promise.race([
|
||||
Promise.resolve().then(() => window.slopsmithAudioBarrier()),
|
||||
Promise.resolve().then(() => window.feedBackAudioBarrier()),
|
||||
new Promise((r) => { barrierTimer = setTimeout(() => { barrierTimedOut = true; r(); }, 3000); }),
|
||||
]);
|
||||
_reportAudioMonitoring('juce-audio-barrier', barrierTimedOut ? 'unavailable' : 'active', barrierTimedOut ? 'Audio barrier timed out before native route setup' : '');
|
||||
_reportAudioBridge('audio-monitoring.audio-barrier', 'audio-monitoring', 'window.slopsmithAudioBarrier', barrierTimedOut ? 'degraded' : 'handled', barrierTimedOut ? 'Audio barrier timed out before native route setup' : '');
|
||||
_reportAudioBridge('audio-monitoring.audio-barrier', 'audio-monitoring', 'window.feedBackAudioBarrier', barrierTimedOut ? 'degraded' : 'handled', barrierTimedOut ? 'Audio barrier timed out before native route setup' : '');
|
||||
} catch (_) {
|
||||
_reportAudioMonitoring('juce-audio-barrier', 'failed', 'Audio barrier failed before native route setup');
|
||||
_reportAudioBridge('audio-monitoring.audio-barrier', 'audio-monitoring', 'window.slopsmithAudioBarrier', 'failed', 'Audio barrier failed before native route setup');
|
||||
_reportAudioBridge('audio-monitoring.audio-barrier', 'audio-monitoring', 'window.feedBackAudioBarrier', 'failed', 'Audio barrier failed before native route setup');
|
||||
}
|
||||
// Promise.race doesn't cancel the loser — clear the timer
|
||||
// when the barrier wins so rapid song switches don't leak.
|
||||
@@ -3385,7 +3385,7 @@ function createHighway() {
|
||||
// track is loaded so song-to-song switches keep the same
|
||||
// user-selected level instead of the engine default.
|
||||
try {
|
||||
const apply = window.slopsmith?.audio?.applySongVolume;
|
||||
const apply = window.feedBack?.audio?.applySongVolume;
|
||||
if (typeof apply === 'function') {
|
||||
await apply();
|
||||
} else {
|
||||
@@ -3427,8 +3427,8 @@ function createHighway() {
|
||||
window._juceAudioUrl = null;
|
||||
audio.src = audioUrl;
|
||||
_reportAudioRoute('html5', pathLabel === '<missing>' ? 'available' : 'degraded', pathLabel === '<missing>' ? '' : 'JUCE fallback');
|
||||
if (typeof window.slopsmith?.audio?.applySongVolume === 'function') {
|
||||
void window.slopsmith.audio.applySongVolume();
|
||||
if (typeof window.feedBack?.audio?.applySongVolume === 'function') {
|
||||
void window.feedBack.audio.applySongVolume();
|
||||
}
|
||||
audio.load();
|
||||
_showAudioBufferingOverlay(audio);
|
||||
@@ -3454,8 +3454,8 @@ function createHighway() {
|
||||
window._juceAudioUrl = null;
|
||||
audio.src = msg.audio_url;
|
||||
_reportAudioRoute(isAudioUrl ? 'html5' : 'stems', 'available');
|
||||
if (typeof window.slopsmith?.audio?.applySongVolume === 'function') {
|
||||
void window.slopsmith.audio.applySongVolume();
|
||||
if (typeof window.feedBack?.audio?.applySongVolume === 'function') {
|
||||
void window.feedBack.audio.applySongVolume();
|
||||
}
|
||||
audio.load();
|
||||
_showAudioBufferingOverlay(audio);
|
||||
@@ -3485,10 +3485,10 @@ function createHighway() {
|
||||
}
|
||||
}
|
||||
// Plugin context API — broadcast current song state
|
||||
if (window.slopsmith) {
|
||||
if (window.feedBack) {
|
||||
const wsPath = ws.url.split('/ws/highway/')[1] || '';
|
||||
const filename = decodeURIComponent(wsPath.split('?')[0]);
|
||||
window.slopsmith.currentSong = {
|
||||
window.feedBack.currentSong = {
|
||||
filename,
|
||||
title: msg.title,
|
||||
artist: msg.artist,
|
||||
@@ -3512,7 +3512,7 @@ function createHighway() {
|
||||
// arrangement name.
|
||||
hasNotation: Boolean(msg.has_notation),
|
||||
};
|
||||
window.slopsmith.emit('song:loaded', window.slopsmith.currentSong);
|
||||
window.feedBack.emit('song:loaded', window.feedBack.currentSong);
|
||||
}
|
||||
break;
|
||||
case 'beats':
|
||||
@@ -3523,8 +3523,8 @@ function createHighway() {
|
||||
// streaming the beats array. Verify .emit is
|
||||
// callable too — the namespace can be partially
|
||||
// attached during early boot.
|
||||
if (window.slopsmith && typeof window.slopsmith.emit === 'function') {
|
||||
window.slopsmith.emit('beats:loaded', { count: beats.length });
|
||||
if (window.feedBack && typeof window.feedBack.emit === 'function') {
|
||||
window.feedBack.emit('beats:loaded', { count: beats.length });
|
||||
}
|
||||
break;
|
||||
case 'sections': sections = msg.data; break;
|
||||
@@ -3593,10 +3593,10 @@ function createHighway() {
|
||||
// app.js). Fires on every `ready`, including
|
||||
// arrangement switches — unlike `_onReady`,
|
||||
// which is a single-use callback slot.
|
||||
if (window.slopsmith) {
|
||||
if (window.feedBack) {
|
||||
// Reuse api.hasPhraseData so the emit and
|
||||
// the public getter agree on the sentinel.
|
||||
window.slopsmith.emit('song:ready', {
|
||||
window.feedBack.emit('song:ready', {
|
||||
hasPhraseData: api.hasPhraseData(),
|
||||
});
|
||||
}
|
||||
@@ -3699,14 +3699,14 @@ function createHighway() {
|
||||
if (elapsedMs > _CHART_MAX_INTERP_MS) return chartTime;
|
||||
// Scale by the observed playback rate so getTime stays
|
||||
// accurate across slowdowns / speedups (audio.playbackRate
|
||||
// != 1 is a first-class slopsmith feature). Add songOffset
|
||||
// != 1 is a first-class feedBack feature). Add songOffset
|
||||
// so interpolated chart time stays consistent with the
|
||||
// chartTime that setTime() / the early-return branches
|
||||
// expose — anchors are stored in raw audio time, so the
|
||||
// offset is applied on the way out.
|
||||
return _chartAnchorAudioT + (_chartObservedRate * elapsedMs) / 1000 + songOffset;
|
||||
},
|
||||
// Returns the slopsmith <audio> element so plugins don't have to
|
||||
// Returns the feedBack <audio> element so plugins don't have to
|
||||
// reach for `document.getElementById('audio')` directly. In JUCE
|
||||
// mode the same element is shimmed: `audio.currentTime` reads
|
||||
// jucePlayer's clock and writes go through the seek queue, and
|
||||
@@ -3714,7 +3714,7 @@ function createHighway() {
|
||||
// returned element behaves uniformly regardless of mode.
|
||||
getAudioElement() {
|
||||
if (typeof window !== 'undefined' && !_audioElementBridgeRecorded) {
|
||||
const playback = window.slopsmith && window.slopsmith.playback;
|
||||
const playback = window.feedBack && window.feedBack.playback;
|
||||
if (playback && typeof playback.recordBridgeHit === 'function') {
|
||||
// Consume the one-shot before recording so a reentrant poll
|
||||
// during the synchronous bridge-hit emit can't double-record;
|
||||
@@ -3803,7 +3803,7 @@ function createHighway() {
|
||||
},
|
||||
getSongInfo() { return songInfo; },
|
||||
// Number of strings on the active arrangement
|
||||
// (slopsmith-plugin-3dhighway#7). 4 for bass, 6 for guitar,
|
||||
// (feedBack-plugin-3dhighway#7). 4 for bass, 6 for guitar,
|
||||
// 7+ for extended-range GP imports. Plugins should size
|
||||
// string-indexed UI / geometry against THIS rather than
|
||||
// assuming 6. Defaults to 6 between songs (until the next
|
||||
@@ -3814,7 +3814,7 @@ function createHighway() {
|
||||
},
|
||||
removeDrawHook(fn) { _drawHooks = _drawHooks.filter(h => h !== fn); },
|
||||
/**
|
||||
* Register a per-note judgment-state provider (slopsmith#254).
|
||||
* Register a per-note judgment-state provider (feedBack#254).
|
||||
* `fn(note, chartTime)` is called by renderers for each visible
|
||||
* chart note and should return one of:
|
||||
* - falsy → no special state (render normally)
|
||||
@@ -3928,7 +3928,7 @@ function createHighway() {
|
||||
// calls that fire before the next song_info arrives don't
|
||||
// bias the clock with stale data.
|
||||
songOffset = 0.0;
|
||||
// Reset phrase ladder + filter (slopsmith#48). _mastery
|
||||
// Reset phrase ladder + filter (feedBack#48). _mastery
|
||||
// persists across arrangement switches — the slider's
|
||||
// position stays put. Filter rebuilds on the next `ready`
|
||||
// once the new arrangement's phrases arrive (or stays
|
||||
@@ -4009,7 +4009,7 @@ function createHighway() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Install a custom renderer. Contract (slopsmith#36):
|
||||
* Install a custom renderer. Contract (feedBack#36):
|
||||
* r.init(canvas, bundle) — one-time setup; owns getContext().
|
||||
* r.draw(bundle) — per rAF frame.
|
||||
* r.resize(w, h) — optional; called when canvas dims change.
|
||||
|
||||
+19
-19
@@ -3,14 +3,14 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Slopsmith</title>
|
||||
<title>FeedBack</title>
|
||||
<!-- Placeholder favicon — emoji SVG data URI. Swap for a real logo later. See #55. -->
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ctext y='14' font-size='14'%3E%F0%9F%8E%B8%3C/text%3E%3C/svg%3E">
|
||||
<!-- Tailwind utility classes are served from a prebuilt static
|
||||
stylesheet (regenerated by scripts/build-tailwind.sh). The old
|
||||
Play CDN (cdn.tailwindcss.com) JIT scanned the DOM ~1.8x/sec
|
||||
on the main thread, dropping ~26% of frames with the 3D
|
||||
highway running — see slopsmith-desktop#110. Theme extensions
|
||||
highway running — see feedBack-desktop#110. Theme extensions
|
||||
(dark/accent/gold colors, Inter font) live in tailwind.config.js. -->
|
||||
<link rel="stylesheet" href="/static/tailwind.min.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
@@ -19,7 +19,7 @@
|
||||
<link rel="stylesheet" href="/static/tour-engine.css">
|
||||
<!-- Diagnostics console capture must wrap console.* before any other
|
||||
script logs anything; load it as early as possible. See
|
||||
docs/diagnostics-bundle-spec.md (slopsmith#166). -->
|
||||
docs/diagnostics-bundle-spec.md (feedBack#166). -->
|
||||
<script src="/static/diagnostics.js"></script>
|
||||
<script src="/static/capabilities.js"></script>
|
||||
<script src="/static/capabilities/library.js"></script>
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||
<div class="flex items-end gap-1.5">
|
||||
<a href="#" onclick="showScreen('home');return false" class="text-xl font-bold bg-gradient-to-r from-accent-light to-purple-400 bg-clip-text text-transparent">
|
||||
Slopsmith
|
||||
FeedBack
|
||||
</a>
|
||||
<span id="app-version" class="text-xs text-gray-600 mb-0.5"></span>
|
||||
</div>
|
||||
@@ -126,7 +126,7 @@
|
||||
<!-- Tree controls -->
|
||||
<button onclick="toggleAllArtists(true)" class="lib-tree-ctrl bg-dark-700 border border-gray-800 rounded-xl px-3 py-2.5 text-sm text-gray-400 hover:text-white transition">Expand All</button>
|
||||
<button onclick="toggleAllArtists(false)" class="lib-tree-ctrl bg-dark-700 border border-gray-800 rounded-xl px-3 py-2.5 text-sm text-gray-400 hover:text-white transition">Collapse All</button>
|
||||
<!-- Filters drawer toggle (slopsmith#129) -->
|
||||
<!-- Filters drawer toggle (feedBack#129) -->
|
||||
<button onclick="toggleLibFilters()" id="btn-lib-filters"
|
||||
class="bg-dark-700 border border-gray-800 hover:border-accent/40 rounded-xl px-4 py-2.5 text-sm text-gray-300 transition flex items-center gap-2"
|
||||
title="Filter by parts, tuning, lyrics">
|
||||
@@ -139,7 +139,7 @@
|
||||
class="bg-dark-700 border border-gray-800 rounded-xl px-4 py-2.5 text-sm text-gray-300 placeholder-gray-600 focus:border-accent/50 focus:ring-1 focus:ring-accent/30 outline-none flex-1 md:w-60 transition">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Active-filter chip row (only visible when filters are set, slopsmith#129) -->
|
||||
<!-- Active-filter chip row (only visible when filters are set, feedBack#129) -->
|
||||
<div id="lib-filter-chips" class="hidden flex flex-wrap gap-2 mb-5"></div>
|
||||
<div id="lib-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
<!-- Cards populated by JS -->
|
||||
@@ -149,7 +149,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══ Filters drawer (slopsmith#129/#69/#22) ═════════════════════ -->
|
||||
<!-- ══ Filters drawer (feedBack#129/#69/#22) ═════════════════════ -->
|
||||
<div id="lib-filter-overlay" class="fixed inset-0 bg-black/40 z-40 hidden"
|
||||
onclick="toggleLibFilters(false)"></div>
|
||||
<aside id="lib-filter-drawer"
|
||||
@@ -249,7 +249,7 @@
|
||||
<div class="space-y-10">
|
||||
<!-- App Updates — Velopack auto-update, desktop only. Stays
|
||||
hidden in the plain web app; setupAppUpdates() unhides
|
||||
this block when window.slopsmithDesktop.update exists,
|
||||
this block when window.feedBackDesktop.update exists,
|
||||
and shows a disabled "not available on Linux" fallback
|
||||
when running on Linux. -->
|
||||
<div id="app-updates-block" class="hidden border border-gray-800 rounded-xl bg-dark-800/40 p-5">
|
||||
@@ -275,13 +275,13 @@
|
||||
<p id="app-update-status" class="text-xs text-gray-500 mt-3">Loading updater status…</p>
|
||||
<p id="app-update-linux-note" class="hidden text-xs text-yellow-300 mt-2">
|
||||
Auto-update is not available on Linux —
|
||||
<a href="https://github.com/got-feedback/feedback-desktop/releases" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">download new versions from GitHub Releases</a>.
|
||||
<a href="https://github.com/got-feedback/feedBack-desktop/releases" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">download new versions from GitHub Releases</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ── Core Slopsmith settings ─────────────────────────────── -->
|
||||
<!-- ── Core FeedBack settings ─────────────────────────────── -->
|
||||
<section>
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-4">Slopsmith</h3>
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-4">FeedBack</h3>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-400 mb-2 block">Library Folder Path</label>
|
||||
@@ -342,7 +342,7 @@
|
||||
class="flex-1 bg-dark-700 border border-gray-800 rounded-xl px-4 py-2.5 text-sm text-gray-300 placeholder-gray-600 focus:border-accent/50 outline-none">
|
||||
<button onclick="saveSettings()" class="bg-accent hover:bg-accent-light px-6 py-2.5 rounded-xl text-sm font-semibold text-white transition">Save</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mt-1">Optional. Run <a href="https://github.com/byrongamatos/slopsmith-demucs-server" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">slopsmith-demucs-server</a> on a machine with a GPU to offload stem splitting and avoid resource exhaustion on the host running Slopsmith.</p>
|
||||
<p class="text-xs text-gray-600 mt-1">Optional. Run <a href="https://github.com/got-feedBack/feedBack-demucs-server" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">feedBack-demucs-server</a> on a machine with a GPU to offload stem splitting and avoid resource exhaustion on the host running FeedBack.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-400 mb-2 block">Library</label>
|
||||
@@ -363,7 +363,7 @@
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mt-1">Export bundles server config, browser preferences, and opted-in plugin data into one JSON file. Import overwrites current settings and reloads.</p>
|
||||
</div>
|
||||
<!-- ── Diagnostics (slopsmith#166) ────────────────────── -->
|
||||
<!-- ── Diagnostics (feedBack#166) ────────────────────── -->
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-400 mb-2 block">Diagnostics</label>
|
||||
<div class="grid grid-cols-2 gap-2 mb-3 text-xs text-gray-400">
|
||||
@@ -407,10 +407,10 @@
|
||||
<section>
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-4">About</h3>
|
||||
<div class="space-y-2 text-sm text-gray-400">
|
||||
<div>Slopsmith <span id="app-version-about" class="text-gray-500"></span></div>
|
||||
<div>Licensed under <a id="about-license-link" href="https://github.com/got-feedback/feedback/blob/main/LICENSE" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">GNU AGPL v3.0</a>.</div>
|
||||
<div><a id="about-source-link" href="https://github.com/got-feedback/feedback" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">Source code repository</a></div>
|
||||
<p class="text-xs text-gray-600 mt-2">Slopsmith is free software. You can redistribute it and modify it under the terms of the AGPL. If you run a modified version that interacts with users over a network, you must make the modified source available to those users.</p>
|
||||
<div>FeedBack <span id="app-version-about" class="text-gray-500"></span></div>
|
||||
<div>Licensed under <a id="about-license-link" href="https://github.com/got-feedback/feedBack/blob/main/LICENSE" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">GNU AGPL v3.0</a>.</div>
|
||||
<div><a id="about-source-link" href="https://github.com/got-feedback/feedBack" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">Source code repository</a></div>
|
||||
<p class="text-xs text-gray-600 mt-2">FeedBack is free software. You can redistribute it and modify it under the terms of the AGPL. If you run a modified version that interacts with users over a network, you must make the modified source available to those users.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -543,7 +543,7 @@
|
||||
<span id="player-av-offset-label" class="text-xs text-gray-500 w-12 tabular-nums">+0ms</span>
|
||||
<div id="mixer-control">
|
||||
<div id="mixer-anchor" class="relative">
|
||||
<button id="btn-mixer" type="button" onclick="window.slopsmith.audio.toggleMixer()" class="px-3 py-1.5 bg-dark-600 hover:bg-dark-500 rounded-lg text-xs text-gray-300 transition" aria-haspopup="true" aria-expanded="false" aria-controls="mixer-popover" title="Audio mixer">Mixer ▾</button>
|
||||
<button id="btn-mixer" type="button" onclick="window.feedBack.audio.toggleMixer()" class="px-3 py-1.5 bg-dark-600 hover:bg-dark-500 rounded-lg text-xs text-gray-300 transition" aria-haspopup="true" aria-expanded="false" aria-controls="mixer-popover" title="Audio mixer">Mixer ▾</button>
|
||||
<div id="mixer-popover" class="hidden absolute right-0 bottom-full mb-2 z-50 bg-dark-700 border border-gray-800 rounded-xl shadow-xl" role="group" aria-label="Audio mixer"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -563,7 +563,7 @@
|
||||
<select id="viz-picker" onchange="setViz(this.value)" class="bg-dark-600 border border-gray-700 rounded-lg px-2 py-1.5 text-xs text-gray-300 outline-none" aria-labelledby="viz-picker-label" title="Visualization">
|
||||
<option value="auto">Auto (match arrangement)</option>
|
||||
<option value="default">Classic 2D Highway</option>
|
||||
<!-- Additional entries populated on load from /api/plugins (slopsmith#36).
|
||||
<!-- Additional entries populated on load from /api/plugins (feedBack#36).
|
||||
The bundled 3D Highway plugin (plugins/highway_3d/) registers as
|
||||
`highway_3d` and is the default selection on fresh installs — see
|
||||
_populateVizPicker() in app.js. -->
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
return !!(window.lottie && typeof window.lottie.loadAnimation === 'function');
|
||||
}
|
||||
|
||||
window.slopsmithLottie = {
|
||||
window.feedBackLottie = {
|
||||
isAvailable: _isAvailable,
|
||||
defaults: {
|
||||
basePath: DEFAULT_BASE_PATH,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"op": 120,
|
||||
"w": 120,
|
||||
"h": 120,
|
||||
"nm": "slopsmith-spinner",
|
||||
"nm": "feedBack-spinner",
|
||||
"ddd": 0,
|
||||
"assets": [],
|
||||
"layers": [
|
||||
|
||||
File diff suppressed because one or more lines are too long
+2
-2
@@ -42,7 +42,7 @@ html { scroll-behavior: smooth; }
|
||||
don't paint over the picker / mixer / lyrics toggle. Without this,
|
||||
the bottom controls bar would silently sit underneath the canvas
|
||||
and become click-through invisible.
|
||||
Folds in the fix from got-feedback/feedback#99. */
|
||||
Folds in the fix from got-feedback/feedBack#99. */
|
||||
/* The footer pins the section-practice bar + transport row to the bottom of
|
||||
#player. margin-top:auto lives here (not on #player-controls) so the bar,
|
||||
which now sits outside #player-controls, stays directly above the row. */
|
||||
@@ -570,7 +570,7 @@ html { scroll-behavior: smooth; }
|
||||
#player-controls button { padding: 4px 8px; font-size: 11px; }
|
||||
}
|
||||
|
||||
/* Library filter drawer (slopsmith#129/#69) */
|
||||
/* Library filter drawer (feedBack#129/#69) */
|
||||
#lib-filter-drawer.open { transform: translateX(0); }
|
||||
#lib-filter-drawer .filter-pill {
|
||||
display: inline-flex;
|
||||
|
||||
+22
-22
@@ -5,7 +5,7 @@
|
||||
text, fb-gold #e8c040 badge. Plain CSS (no Tailwind classes) so it needs no
|
||||
stylesheet rebuild. Per CLAUDE.md → Frontend Conventions. */
|
||||
|
||||
.slopsmith-tour-menu-btn {
|
||||
.feedBack-tour-menu-btn {
|
||||
position: fixed;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
@@ -28,14 +28,14 @@
|
||||
justify-content: center;
|
||||
transition: box-shadow 0.2s, transform 0.15s;
|
||||
}
|
||||
.slopsmith-tour-menu-btn:hover {
|
||||
.feedBack-tour-menu-btn:hover {
|
||||
box-shadow: 0 0 12px #0ea5e9aa;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.slopsmith-tour-menu-btn.has-unseen {
|
||||
.feedBack-tour-menu-btn.has-unseen {
|
||||
animation: tour-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
.slopsmith-tour-menu-btn.has-unseen::after {
|
||||
.feedBack-tour-menu-btn.has-unseen::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
@@ -51,7 +51,7 @@
|
||||
50% { box-shadow: 0 0 16px #0ea5e9cc; }
|
||||
}
|
||||
|
||||
.slopsmith-tour-menu-popover {
|
||||
.feedBack-tour-menu-popover {
|
||||
position: fixed;
|
||||
bottom: 56px;
|
||||
right: 12px;
|
||||
@@ -72,7 +72,7 @@
|
||||
color: #f8fafc;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-header {
|
||||
.feedBack-tour-menu-popover .tour-menu-header {
|
||||
padding: 4px 10px 6px;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
@@ -81,13 +81,13 @@
|
||||
border-bottom: 1px solid #334155;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-empty {
|
||||
.feedBack-tour-menu-popover .tour-menu-empty {
|
||||
padding: 10px;
|
||||
color: #94a3b8;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-item {
|
||||
.feedBack-tour-menu-popover .tour-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -103,23 +103,23 @@
|
||||
cursor: pointer;
|
||||
transition: background 0.12s;
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-item:hover {
|
||||
.feedBack-tour-menu-popover .tour-menu-item:hover {
|
||||
background: #334155;
|
||||
color: #f8fafc;
|
||||
}
|
||||
/* Keyboard focus gets an explicit ring instead of relying on the hover
|
||||
background — matches the :focus-visible treatment elsewhere in the
|
||||
app (style.css). Pointer focus is left alone. */
|
||||
.slopsmith-tour-menu-popover .tour-menu-item:focus-visible {
|
||||
.feedBack-tour-menu-popover .tour-menu-item:focus-visible {
|
||||
background: #334155;
|
||||
color: #f8fafc;
|
||||
outline: 2px solid #0ea5e9;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-item-label {
|
||||
.feedBack-tour-menu-popover .tour-menu-item-label {
|
||||
flex: 1;
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-item-status {
|
||||
.feedBack-tour-menu-popover .tour-menu-item-status {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 2px 6px;
|
||||
@@ -127,18 +127,18 @@
|
||||
min-width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-item-status.is-new {
|
||||
.feedBack-tour-menu-popover .tour-menu-item-status.is-new {
|
||||
background: #e8c040;
|
||||
color: #0f172a;
|
||||
}
|
||||
.slopsmith-tour-menu-popover .tour-menu-item-status.is-seen {
|
||||
.feedBack-tour-menu-popover .tour-menu-item-status.is-seen {
|
||||
background: #0b1220;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* ── First-visit toast — anchored above the menu button ── */
|
||||
|
||||
.slopsmith-tour-prompt {
|
||||
.feedBack-tour-prompt {
|
||||
position: fixed;
|
||||
bottom: 56px;
|
||||
right: 12px;
|
||||
@@ -154,21 +154,21 @@
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
.slopsmith-tour-prompt.fading {
|
||||
.feedBack-tour-prompt.fading {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.slopsmith-tour-prompt .tour-prompt-more {
|
||||
.feedBack-tour-prompt .tour-prompt-more {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.slopsmith-tour-prompt .tour-prompt-buttons {
|
||||
.feedBack-tour-prompt .tour-prompt-buttons {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.slopsmith-tour-prompt button {
|
||||
.feedBack-tour-prompt button {
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
@@ -176,15 +176,15 @@
|
||||
border: none;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.slopsmith-tour-prompt button:hover {
|
||||
.feedBack-tour-prompt button:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
.slopsmith-tour-prompt button[data-action="start"] {
|
||||
.feedBack-tour-prompt button[data-action="start"] {
|
||||
background: #0ea5e9;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.slopsmith-tour-prompt button[data-action="dismiss"] {
|
||||
.feedBack-tour-prompt button[data-action="dismiss"] {
|
||||
background: #334155;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
+28
-28
@@ -2,7 +2,7 @@
|
||||
'use strict';
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Slopsmith tour engine — consolidated menu version.
|
||||
// FeedBack tour engine — consolidated menu version.
|
||||
//
|
||||
// One floating ? button at the bottom-right of the viewport. Click it to
|
||||
// see every tour that's relevant to the current screen. Click a tour to
|
||||
@@ -12,7 +12,7 @@
|
||||
// - has_screen: true → tour relevant on the plugin's own dedicated
|
||||
// screen (`plugin-<id>`)
|
||||
// - otherwise → tour relevant on the player screen
|
||||
// - plugins can override via slopsmithTour.register(id, { screens: [...] })
|
||||
// - plugins can override via feedBackTour.register(id, { screens: [...] })
|
||||
//
|
||||
// First-visit toast: on screen:changed, if any relevant tour is unseen
|
||||
// and un-dismissed, a small "Take a quick tour of X?" prompt pops next
|
||||
@@ -40,8 +40,8 @@
|
||||
|
||||
// ── localStorage helpers ──────────────────────────────────────────────
|
||||
|
||||
function _seenKey(id) { return 'slopsmith_tour_seen_' + id; }
|
||||
function _dismissedKey(id) { return 'slopsmith_tour_dismissed_' + id; }
|
||||
function _seenKey(id) { return 'feedBack_tour_seen_' + id; }
|
||||
function _dismissedKey(id) { return 'feedBack_tour_dismissed_' + id; }
|
||||
|
||||
function hasSeen(pluginId) {
|
||||
try { return !!localStorage.getItem(_seenKey(pluginId)); } catch { return false; }
|
||||
@@ -93,7 +93,7 @@
|
||||
? Array.from(picker.options).map(o => o.value).filter(v => v !== 'auto' && v !== 'default')
|
||||
: Object.keys(_tourPlugins).filter(id => _tourPlugins[id].is_viz);
|
||||
for (const pluginId of candidateIds) {
|
||||
const factory = window['slopsmithViz_' + pluginId];
|
||||
const factory = window['feedBackViz_' + pluginId];
|
||||
if (typeof factory !== 'function') continue;
|
||||
const predicate = factory.matchesArrangement;
|
||||
if (typeof predicate !== 'function') continue;
|
||||
@@ -141,11 +141,11 @@
|
||||
function _ensureMenu() {
|
||||
if (_menuBtn) return;
|
||||
_menuBtn = document.createElement('button');
|
||||
_menuBtn.className = 'slopsmith-tour-menu-btn';
|
||||
_menuBtn.className = 'feedBack-tour-menu-btn';
|
||||
_menuBtn.setAttribute('aria-label', 'Available tours');
|
||||
_menuBtn.setAttribute('aria-haspopup', 'dialog');
|
||||
_menuBtn.setAttribute('aria-expanded', 'false');
|
||||
_menuBtn.setAttribute('aria-controls', 'slopsmith-tour-menu-popover');
|
||||
_menuBtn.setAttribute('aria-controls', 'feedBack-tour-menu-popover');
|
||||
_menuBtn.title = 'Available tours';
|
||||
_menuBtn.textContent = '?';
|
||||
_menuBtn.style.display = 'none';
|
||||
@@ -153,8 +153,8 @@
|
||||
document.body.appendChild(_menuBtn);
|
||||
|
||||
_menuPopover = document.createElement('div');
|
||||
_menuPopover.id = 'slopsmith-tour-menu-popover';
|
||||
_menuPopover.className = 'slopsmith-tour-menu-popover';
|
||||
_menuPopover.id = 'feedBack-tour-menu-popover';
|
||||
_menuPopover.className = 'feedBack-tour-menu-popover';
|
||||
// Plain popover; not a WAI-ARIA "menu" — that role implies roving
|
||||
// focus + arrow-key navigation we don't implement (the rows are
|
||||
// ordinary <button>s, traversed via normal tab order).
|
||||
@@ -168,7 +168,7 @@
|
||||
if (_menuPopover.style.display === 'none') return;
|
||||
const t = e.target;
|
||||
if (t && typeof t.closest === 'function' &&
|
||||
(t.closest('.slopsmith-tour-menu-popover') || t.closest('.slopsmith-tour-menu-btn'))) return;
|
||||
(t.closest('.feedBack-tour-menu-popover') || t.closest('.feedBack-tour-menu-btn'))) return;
|
||||
_hideMenu();
|
||||
}, true);
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
// Three states: NEW (never seen + never dismissed), ✓ (completed
|
||||
// at least once), or no badge at all (dismissed without taking
|
||||
// the tour). The "dismissed but not seen" case used to render
|
||||
// an empty span — slopsmith#272 review.
|
||||
// an empty span — feedBack#272 review.
|
||||
const seen = hasSeen(p.id);
|
||||
const dismissed = hasDismissed(p.id);
|
||||
if (!seen && !dismissed) {
|
||||
@@ -309,7 +309,7 @@
|
||||
if (!_activeToastPluginId) return;
|
||||
const id = _activeToastPluginId;
|
||||
_activeToastPluginId = null;
|
||||
document.querySelectorAll('.slopsmith-tour-prompt').forEach(el => {
|
||||
document.querySelectorAll('.feedBack-tour-prompt').forEach(el => {
|
||||
if (el.dataset.pluginId === id) {
|
||||
el.classList.add('fading');
|
||||
setTimeout(() => el.remove(), 500);
|
||||
@@ -335,7 +335,7 @@
|
||||
const plugin = unseen[0];
|
||||
|
||||
const prompt = document.createElement('div');
|
||||
prompt.className = 'slopsmith-tour-prompt';
|
||||
prompt.className = 'feedBack-tour-prompt';
|
||||
prompt.dataset.pluginId = plugin.id;
|
||||
const text = document.createElement('span');
|
||||
text.textContent = 'Take a quick tour of ';
|
||||
@@ -410,7 +410,7 @@
|
||||
const steps = await _registry[pluginId].buildSteps();
|
||||
if (steps && steps.length) return steps;
|
||||
} catch (e) {
|
||||
console.warn('[slopsmithTour] buildSteps() threw for', pluginId, e);
|
||||
console.warn('[feedBackTour] buildSteps() threw for', pluginId, e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -419,7 +419,7 @@
|
||||
const data = await resp.json();
|
||||
return Array.isArray(data.tour) ? data.tour : [];
|
||||
} catch (e) {
|
||||
console.warn('[slopsmithTour] Failed to load steps for', pluginId, e);
|
||||
console.warn('[feedBackTour] Failed to load steps for', pluginId, e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -473,7 +473,7 @@
|
||||
try { document.querySelector(sel); }
|
||||
catch (e) {
|
||||
selectorOk = false;
|
||||
console.warn('[slopsmithTour] step', raw.id, 'waitFor selector is invalid:', sel, e);
|
||||
console.warn('[feedBackTour] step', raw.id, 'waitFor selector is invalid:', sel, e);
|
||||
}
|
||||
if (selectorOk) {
|
||||
opts.beforeShowPromise = () => new Promise(resolve => {
|
||||
@@ -520,7 +520,7 @@
|
||||
|
||||
async function start(pluginId) {
|
||||
if (typeof window.Shepherd === 'undefined' || !window.Shepherd.Tour) {
|
||||
console.error('[slopsmithTour] Shepherd.js not loaded — cannot start tour for', pluginId);
|
||||
console.error('[feedBackTour] Shepherd.js not loaded — cannot start tour for', pluginId);
|
||||
return false;
|
||||
}
|
||||
if (_activeTour) {
|
||||
@@ -530,7 +530,7 @@
|
||||
|
||||
const rawSteps = await _loadSteps(pluginId);
|
||||
if (!rawSteps.length) {
|
||||
console.warn('[slopsmithTour] No steps found for', pluginId);
|
||||
console.warn('[feedBackTour] No steps found for', pluginId);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -610,9 +610,9 @@
|
||||
!_deprecationWarned.has(pluginId)) {
|
||||
_deprecationWarned.add(pluginId);
|
||||
console.warn(
|
||||
'[slopsmithTour] register(' + JSON.stringify(pluginId) + '): ' +
|
||||
'[feedBackTour] register(' + JSON.stringify(pluginId) + '): ' +
|
||||
'injectTriggerInto / injectTriggerOpts are no longer honored — ' +
|
||||
'the consolidated tour menu (slopsmith#272) manages the ? button ' +
|
||||
'the consolidated tour menu (feedBack#272) manages the ? button ' +
|
||||
'for every plugin. Remove these options from your register() call.'
|
||||
);
|
||||
}
|
||||
@@ -652,7 +652,7 @@
|
||||
const toRemove = [];
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const k = localStorage.key(i);
|
||||
if (k && k.startsWith('slopsmith_tour_')) toRemove.push(k);
|
||||
if (k && k.startsWith('feedBack_tour_')) toRemove.push(k);
|
||||
}
|
||||
toRemove.forEach(k => localStorage.removeItem(k));
|
||||
}
|
||||
@@ -660,17 +660,17 @@
|
||||
_updateMenuVisibility();
|
||||
}
|
||||
|
||||
window.slopsmithTour = { register, start, hasSeen, hasDismissed, reset };
|
||||
window.feedBackTour = { register, start, hasSeen, hasDismissed, reset };
|
||||
|
||||
// ── Initialise after DOM ──────────────────────────────────────────────
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (!window.Shepherd) {
|
||||
console.error('[slopsmithTour] Shepherd.js not loaded — tour engine disabled');
|
||||
console.error('[feedBackTour] Shepherd.js not loaded — tour engine disabled');
|
||||
return;
|
||||
}
|
||||
if (!window.slopsmith) {
|
||||
console.error('[slopsmithTour] window.slopsmith not found — tour engine disabled');
|
||||
if (!window.feedBack) {
|
||||
console.error('[feedBackTour] window.feedBack not found — tour engine disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -687,16 +687,16 @@
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[slopsmithTour] Failed to load plugin list:', e);
|
||||
console.warn('[feedBackTour] Failed to load plugin list:', e);
|
||||
return;
|
||||
}
|
||||
|
||||
_ensureMenu();
|
||||
window.slopsmith.on('screen:changed', _onScreenChanged);
|
||||
window.feedBack.on('screen:changed', _onScreenChanged);
|
||||
// song:ready can change the auto-mode viz match without a screen
|
||||
// change — re-evaluate relevance so the menu picks up the new
|
||||
// active viz on the next song load.
|
||||
window.slopsmith.on('song:ready', () => {
|
||||
window.feedBack.on('song:ready', () => {
|
||||
_dismissToast();
|
||||
_updateMenuVisibility();
|
||||
_maybeShowToast();
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
if (!sm || !sm.libraryCardActions) return;
|
||||
|
||||
const STD_RETUNABLE = ['Eb Standard', 'D Standard', 'C# Standard', 'C Standard'];
|
||||
const reg = sm.libraryCardActions;
|
||||
const hooks = window.__slopsmithV3CoreCardActions || (window.__slopsmithV3CoreCardActions = {});
|
||||
const hooks = window.__feedBackV3CoreCardActions || (window.__feedBackV3CoreCardActions = {});
|
||||
if (hooks.installed) return;
|
||||
hooks.installed = true;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
const enc = encodeURIComponent;
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
const name = (profile && profile.display_name) || 'there';
|
||||
const ver = (version && version.version) || '';
|
||||
const changelogUrl = ((version && version.source_url) || 'https://github.com/got-feedback/feedback') + '/blob/main/CHANGELOG.md';
|
||||
const changelogUrl = ((version && version.source_url) || 'https://github.com/got-feedback/feedBack') + '/blob/main/CHANGELOG.md';
|
||||
const songCount = (libStats && (libStats.total_songs ?? libStats.total)) || 0;
|
||||
const pluginCount = Array.isArray(plugins)
|
||||
? plugins.filter((p) => (p && (p.status || 'ready') === 'ready')).length : 0;
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
*
|
||||
* A vanilla-JS reskin of the bundled `minigames` plugin's screen into the fb-*
|
||||
* design (constitution P-II). It REUSES the minigames backend + the
|
||||
* window.slopsmithMinigames SDK — it does not fork scoring/run logic and adds
|
||||
* window.feedBackMinigames SDK — it does not fork scoring/run logic and adds
|
||||
* no second XP curve. XP stays unified through the core store (P15): the header
|
||||
* reads /api/profile/progress (the same data the topbar profile badge shows),
|
||||
* while per-game bests + cross-game unlocks come from the minigames /profile.
|
||||
*
|
||||
* UI placement is a DEFERRED capability domain (design/05): we use the legacy
|
||||
* plugin loader + /api/plugins/minigames/* routes, NOT capability dispatch.
|
||||
* Launch delegates to slopsmithMinigames.start(gameId), which mounts the
|
||||
* Launch delegates to feedBackMinigames.start(gameId), which mounts the
|
||||
* plugin's own #mg-stage overlay (eagerly injected at boot) — so this screen
|
||||
* never needs to visit the legacy #plugin-minigames hub.
|
||||
*/
|
||||
@@ -18,7 +18,7 @@
|
||||
'use strict';
|
||||
|
||||
const SCREEN_ID = 'v3-feedbarcade';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
const enc = encodeURIComponent;
|
||||
@@ -32,7 +32,7 @@
|
||||
let _stageWasVisible = false;
|
||||
let _refreshTimer = null;
|
||||
|
||||
function sdk() { return window.slopsmithMinigames || null; }
|
||||
function sdk() { return window.feedBackMinigames || null; }
|
||||
function registeredList() {
|
||||
const s = sdk();
|
||||
try { return (s && typeof s.listRegistered === 'function') ? s.listRegistered() : []; }
|
||||
@@ -271,7 +271,7 @@
|
||||
// The minigames plugin publishes its SDK + injects #mg-stage at boot and
|
||||
// fires this once ready; re-render so late-registered specs appear and
|
||||
// the stage observer attaches.
|
||||
window.addEventListener('slopsmith-minigames-ready', () => { ensureStageObserver(); refresh(); });
|
||||
window.addEventListener('feedBack-minigames-ready', () => { ensureStageObserver(); refresh(); });
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot, { once: true });
|
||||
|
||||
+21
-21
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
fee[dB]ack v0.3.0 shell (SLOPSMITH_UI=v3 / GET /v3).
|
||||
fee[dB]ack v0.3.0 shell (FEEDBACK_UI=v3 / GET /v3).
|
||||
|
||||
This is a re-chromed copy of the legacy static/index.html: the v0.3.0
|
||||
sidebar + topbar replace the (hidden) legacy navbar, new #v3-* screens are
|
||||
@@ -9,7 +9,7 @@
|
||||
boots UNMODIFIED and the whole engine — player/highway, plugin loader,
|
||||
capabilities, audio, library, settings — is reused as-is. Navigation is the
|
||||
shared window.showScreen across both #v3-* and legacy/#plugin-* screens.
|
||||
See ~/Repositories/slopsmith-feedback-v030/prompts/12-app-shell.md.
|
||||
See ~/Repositories/feedBack-feedback-v030/prompts/12-app-shell.md.
|
||||
-->
|
||||
<html lang="en" class="dark scroll-smooth">
|
||||
<head>
|
||||
@@ -69,7 +69,7 @@
|
||||
stylesheet (regenerated by scripts/build-tailwind.sh). The old
|
||||
Play CDN (cdn.tailwindcss.com) JIT scanned the DOM ~1.8x/sec
|
||||
on the main thread, dropping ~26% of frames with the 3D
|
||||
highway running — see slopsmith-desktop#110. Theme extensions
|
||||
highway running — see feedBack-desktop#110. Theme extensions
|
||||
(dark/accent/gold colors, Inter font) live in tailwind.config.js. -->
|
||||
<link rel="stylesheet" href="/static/tailwind.min.css">
|
||||
<!-- UI font: Rubik (Google Fonts, OFL). Applied via fontFamily.display in tailwind.config.js. -->
|
||||
@@ -82,7 +82,7 @@
|
||||
<link rel="stylesheet" href="/static/v3/v3.css">
|
||||
<!-- Diagnostics console capture must wrap console.* before any other
|
||||
script logs anything; load it as early as possible. See
|
||||
docs/diagnostics-bundle-spec.md (slopsmith#166). -->
|
||||
docs/diagnostics-bundle-spec.md (feedBack#166). -->
|
||||
<script src="/static/diagnostics.js"></script>
|
||||
<script src="/static/capabilities.js"></script>
|
||||
<script src="/static/capabilities/library.js"></script>
|
||||
@@ -143,7 +143,7 @@
|
||||
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||
<div class="flex items-end gap-1.5">
|
||||
<a href="#" onclick="showScreen('home');return false" class="text-xl font-bold bg-gradient-to-r from-accent-light to-purple-400 bg-clip-text text-transparent">
|
||||
Slopsmith
|
||||
FeedBack
|
||||
</a>
|
||||
<span id="app-version" class="text-xs text-gray-600 mb-0.5"></span>
|
||||
</div>
|
||||
@@ -229,7 +229,7 @@
|
||||
<!-- Tree controls -->
|
||||
<button onclick="toggleAllArtists(true)" class="lib-tree-ctrl bg-dark-700 border border-gray-800 rounded-xl px-3 py-2.5 text-sm text-gray-400 hover:text-white transition">Expand All</button>
|
||||
<button onclick="toggleAllArtists(false)" class="lib-tree-ctrl bg-dark-700 border border-gray-800 rounded-xl px-3 py-2.5 text-sm text-gray-400 hover:text-white transition">Collapse All</button>
|
||||
<!-- Filters drawer toggle (slopsmith#129) -->
|
||||
<!-- Filters drawer toggle (feedBack#129) -->
|
||||
<button onclick="toggleLibFilters()" id="btn-lib-filters"
|
||||
class="bg-dark-700 border border-gray-800 hover:border-accent/40 rounded-xl px-4 py-2.5 text-sm text-gray-300 transition flex items-center gap-2"
|
||||
title="Filter by parts, tuning, lyrics">
|
||||
@@ -242,7 +242,7 @@
|
||||
class="bg-dark-700 border border-gray-800 rounded-xl px-4 py-2.5 text-sm text-gray-300 placeholder-gray-600 focus:border-accent/50 focus:ring-1 focus:ring-accent/30 outline-none flex-1 md:w-60 transition">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Active-filter chip row (only visible when filters are set, slopsmith#129) -->
|
||||
<!-- Active-filter chip row (only visible when filters are set, feedBack#129) -->
|
||||
<div id="lib-filter-chips" class="hidden flex flex-wrap gap-2 mb-5"></div>
|
||||
<div id="lib-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
<!-- Cards populated by JS -->
|
||||
@@ -252,7 +252,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══ Filters drawer (slopsmith#129/#69/#22) ═════════════════════ -->
|
||||
<!-- ══ Filters drawer (feedBack#129/#69/#22) ═════════════════════ -->
|
||||
<div id="lib-filter-overlay" class="fixed inset-0 bg-black/40 z-40 hidden"
|
||||
onclick="toggleLibFilters(false)"></div>
|
||||
<aside id="lib-filter-drawer"
|
||||
@@ -352,7 +352,7 @@
|
||||
<div class="space-y-10">
|
||||
<!-- App Updates — Velopack auto-update, desktop only. Stays
|
||||
hidden in the plain web app; setupAppUpdates() unhides
|
||||
this block when window.slopsmithDesktop.update exists,
|
||||
this block when window.feedBackDesktop.update exists,
|
||||
and shows a disabled "not available on Linux" fallback
|
||||
when running on Linux. -->
|
||||
<div id="app-updates-block" class="hidden border border-gray-800 rounded-xl bg-dark-800/40 p-5">
|
||||
@@ -378,13 +378,13 @@
|
||||
<p id="app-update-status" class="text-xs text-gray-500 mt-3">Loading updater status…</p>
|
||||
<p id="app-update-linux-note" class="hidden text-xs text-yellow-300 mt-2">
|
||||
Auto-update is not available on Linux —
|
||||
<a href="https://github.com/got-feedback/feedback-desktop/releases" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">download new versions from GitHub Releases</a>.
|
||||
<a href="https://github.com/got-feedback/feedBack-desktop/releases" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">download new versions from GitHub Releases</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ── Core Slopsmith settings ─────────────────────────────── -->
|
||||
<!-- ── Core FeedBack settings ─────────────────────────────── -->
|
||||
<section>
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-4">Slopsmith</h3>
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-4">FeedBack</h3>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-400 mb-2 block">Library Folder Path</label>
|
||||
@@ -455,7 +455,7 @@
|
||||
class="flex-1 bg-dark-700 border border-gray-800 rounded-xl px-4 py-2.5 text-sm text-gray-300 placeholder-gray-600 focus:border-accent/50 outline-none">
|
||||
<button onclick="saveSettings()" class="bg-accent hover:bg-accent-light px-6 py-2.5 rounded-xl text-sm font-semibold text-white transition">Save</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mt-1">Optional. Run <a href="https://github.com/byrongamatos/slopsmith-demucs-server" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">slopsmith-demucs-server</a> on a machine with a GPU to offload stem splitting and avoid resource exhaustion on the host running Slopsmith.</p>
|
||||
<p class="text-xs text-gray-600 mt-1">Optional. Run <a href="https://github.com/got-feedBack/feedBack-demucs-server" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">feedBack-demucs-server</a> on a machine with a GPU to offload stem splitting and avoid resource exhaustion on the host running FeedBack.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-400 mb-2 block">Library</label>
|
||||
@@ -476,7 +476,7 @@
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mt-1">Export bundles server config, browser preferences, and opted-in plugin data into one JSON file. Import overwrites current settings and reloads.</p>
|
||||
</div>
|
||||
<!-- ── Diagnostics (slopsmith#166) ────────────────────── -->
|
||||
<!-- ── Diagnostics (feedBack#166) ────────────────────── -->
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-400 mb-2 block">Diagnostics</label>
|
||||
<div class="grid grid-cols-2 gap-2 mb-3 text-xs text-gray-400">
|
||||
@@ -520,10 +520,10 @@
|
||||
<section>
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-4">About</h3>
|
||||
<div class="space-y-2 text-sm text-gray-400">
|
||||
<div>Slopsmith <span id="app-version-about" class="text-gray-500"></span></div>
|
||||
<div>Licensed under <a id="about-license-link" href="https://github.com/got-feedback/feedback/blob/main/LICENSE" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">GNU AGPL v3.0</a>.</div>
|
||||
<div><a id="about-source-link" href="https://github.com/got-feedback/feedback" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">Source code repository</a></div>
|
||||
<p class="text-xs text-gray-600 mt-2">Slopsmith is free software. You can redistribute it and modify it under the terms of the AGPL. If you run a modified version that interacts with users over a network, you must make the modified source available to those users.</p>
|
||||
<div>FeedBack <span id="app-version-about" class="text-gray-500"></span></div>
|
||||
<div>Licensed under <a id="about-license-link" href="https://github.com/got-feedback/feedBack/blob/main/LICENSE" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">GNU AGPL v3.0</a>.</div>
|
||||
<div><a id="about-source-link" href="https://github.com/got-feedback/feedBack" target="_blank" rel="noopener" class="text-accent hover:text-accent-light underline">Source code repository</a></div>
|
||||
<p class="text-xs text-gray-600 mt-2">FeedBack is free software. You can redistribute it and modify it under the terms of the AGPL. If you run a modified version that interacts with users over a network, you must make the modified source available to those users.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -701,7 +701,7 @@
|
||||
<select id="viz-picker" onchange="setViz(this.value)" class="v3-pop-select" aria-labelledby="viz-picker-label" title="Visualization">
|
||||
<option value="auto">Auto (match arrangement)</option>
|
||||
<option value="default">Classic 2D Highway</option>
|
||||
<!-- Additional entries populated on load from /api/plugins (slopsmith#36).
|
||||
<!-- Additional entries populated on load from /api/plugins (feedBack#36).
|
||||
The bundled 3D Highway plugin (plugins/highway_3d/) registers as
|
||||
`highway_3d` and is the default on fresh installs — see
|
||||
_populateVizPicker() in app.js. -->
|
||||
@@ -762,7 +762,7 @@
|
||||
<div id="v3-rail-pop-mixer" class="v3-rail-pop hidden" role="group" aria-label="Mixer">
|
||||
<div id="mixer-control">
|
||||
<div id="mixer-anchor" class="relative">
|
||||
<button id="btn-mixer" type="button" onclick="window.slopsmith.audio.toggleMixer()" class="v3-pop-btn" aria-haspopup="true" aria-expanded="false" aria-controls="mixer-popover" title="Audio mixer">Mixer ▾</button>
|
||||
<button id="btn-mixer" type="button" onclick="window.feedBack.audio.toggleMixer()" class="v3-pop-btn" aria-haspopup="true" aria-expanded="false" aria-controls="mixer-popover" title="Audio mixer">Mixer ▾</button>
|
||||
<div id="mixer-popover" class="hidden absolute left-0 top-full mt-2 z-50 bg-dark-700 border border-gray-800 rounded-xl shadow-xl" role="group" aria-label="Audio mixer"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -858,7 +858,7 @@
|
||||
<script src="/static/tour-engine.js"></script>
|
||||
<!-- fee[dB]ack v0.3.0 shell: brand helper, then the shell (sidebar/topbar/
|
||||
routing). Loaded after app.js/audio-mixer so window.showScreen and
|
||||
window.slopsmith(.audio) exist; dashboard.js is filled in prompt 13. -->
|
||||
window.feedBack(.audio) exist; dashboard.js is filled in prompt 13. -->
|
||||
<script src="/static/v3/brand.js"></script>
|
||||
<script src="/static/v3/shell.js"></script>
|
||||
<!-- Progression (spec 010): theme-core before profile.js so the equipped
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const PLUGIN_ID = 'tutorials';
|
||||
const API = '/api/plugins/' + PLUGIN_ID;
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
@@ -228,16 +228,16 @@
|
||||
// the first navigation that leaves the launch flow (any screen other
|
||||
// than the tutorials waypoint or the player that consumes it).
|
||||
try {
|
||||
if (window.slopsmith && typeof window.slopsmith.setReturnScreen === 'function') {
|
||||
window.slopsmith.setReturnScreen('v3-lessons');
|
||||
if (window.feedBack && typeof window.feedBack.setReturnScreen === 'function') {
|
||||
window.feedBack.setReturnScreen('v3-lessons');
|
||||
if (sm && typeof sm.on === 'function' && typeof sm.off === 'function') {
|
||||
const clearStale = (e) => {
|
||||
const id = e && e.detail && e.detail.id;
|
||||
if (id === 'plugin-tutorials') return; // expected waypoint — keep waiting
|
||||
// 'player' means playSong already consumed the override;
|
||||
// anything else means the launch was abandoned.
|
||||
if (id !== 'player' && window.slopsmith._nextReturnScreen === 'v3-lessons') {
|
||||
window.slopsmith.setReturnScreen(null);
|
||||
if (id !== 'player' && window.feedBack._nextReturnScreen === 'v3-lessons') {
|
||||
window.feedBack.setReturnScreen(null);
|
||||
}
|
||||
sm.off('screen:changed', clearStale);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
(function (root) {
|
||||
'use strict';
|
||||
|
||||
const KEY = 'slopsmith-live-guitar-tone-source';
|
||||
const KEY = 'feedBack-live-guitar-tone-source';
|
||||
|
||||
const SOURCES = Object.freeze({
|
||||
INTERNAL: 'internal',
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
const boot = () => {
|
||||
const sm = root && root.slopsmith;
|
||||
const sm = root && root.feedBack;
|
||||
if (sm) bindRuntime(sm);
|
||||
};
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*
|
||||
* Vanilla JS, no framework (constitution P-II). Animation + accent colors are
|
||||
* inline styles so the prebuilt Tailwind stylesheet needs no new utilities.
|
||||
* Self-contained: it subscribes through window.slopsmith.on, degrading to a
|
||||
* Self-contained: it subscribes through window.feedBack.on, degrading to a
|
||||
* no-op when the bus or DOM isn't present (SSR/headless safety, P15).
|
||||
*/
|
||||
(function () {
|
||||
@@ -95,7 +95,7 @@
|
||||
window.fbNotify = { show: show, clear: clear };
|
||||
|
||||
// ── Progression wiring ────────────────────────────────────────────────────
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
if (!sm || typeof sm.on !== 'function') return; // no bus → toasts API still usable
|
||||
|
||||
const periodLabel = (p) => (p === 'weekly' ? 'Weekly Quest' : p === 'daily' ? 'Daily Quest' : 'Quest');
|
||||
@@ -106,7 +106,7 @@
|
||||
};
|
||||
|
||||
// The bus delivers a CustomEvent; the progression payload is e.detail
|
||||
// (matches every other window.slopsmith.on consumer, e.g. progress.js).
|
||||
// (matches every other window.feedBack.on consumer, e.g. progress.js).
|
||||
sm.on('progression:quest-progressed', (e) => {
|
||||
const q = e && e.detail;
|
||||
if (!q) return;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* fee[dB]ack v0.3.0 — first-run home tour.
|
||||
*
|
||||
* Registers a spotlight tour over the home-page cards with the shared tour
|
||||
* engine (window.slopsmithTour / Shepherd) and auto-runs it once, the first
|
||||
* engine (window.feedBackTour / Shepherd) and auto-runs it once, the first
|
||||
* time the user lands on the home page after completing onboarding. It stays
|
||||
* replayable forever from the per-screen "?" tour menu (registered with
|
||||
* screens: ['v3-home']).
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
|
||||
function register() {
|
||||
var t = window.slopsmithTour;
|
||||
var t = window.feedBackTour;
|
||||
if (!t || typeof t.register !== 'function') return false;
|
||||
t.register(TOUR_ID, {
|
||||
name: 'Welcome tour', // label in the "?" tour menu
|
||||
@@ -88,7 +88,7 @@
|
||||
// honour the engine's own seen/dismissed state so it never repeats and a
|
||||
// dismissal isn't nagged. Stays replayable from the "?" menu either way.
|
||||
function startFirstRun() {
|
||||
var t = window.slopsmithTour;
|
||||
var t = window.feedBackTour;
|
||||
if (!t || typeof t.start !== 'function') return;
|
||||
try {
|
||||
if (t.hasSeen(TOUR_ID) || t.hasDismissed(TOUR_ID)) return;
|
||||
@@ -118,12 +118,12 @@
|
||||
// can't start the tour immediately — there is no home screen to spotlight
|
||||
// yet. Arm it to run the first time the user lands back on v3-home.
|
||||
function armPendingFirstRun() {
|
||||
var t = window.slopsmithTour;
|
||||
var t = window.feedBackTour;
|
||||
if (!t || typeof t.start !== 'function') return;
|
||||
try {
|
||||
if (t.hasSeen(TOUR_ID) || t.hasDismissed(TOUR_ID)) return;
|
||||
} catch (e) { /* private mode — fall through */ }
|
||||
var sm = window.slopsmith;
|
||||
var sm = window.feedBack;
|
||||
if (!sm || typeof sm.on !== 'function') return;
|
||||
var onScreen = function (e) {
|
||||
if (!(e && e.detail && e.detail.id === 'v3-home')) return;
|
||||
@@ -135,7 +135,7 @@
|
||||
|
||||
window.v3OnboardingTour = { startFirstRun: startFirstRun, armPendingFirstRun: armPendingFirstRun };
|
||||
|
||||
// tour-engine.js assigns window.slopsmithTour at script-eval time, so if it
|
||||
// tour-engine.js assigns window.feedBackTour at script-eval time, so if it
|
||||
// is loaded before us register() succeeds immediately; otherwise retry once
|
||||
// the DOM (and the engine) are ready.
|
||||
if (!register()) {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
// the pedal photos measure ~57.5% down), so they stay aligned at any size.
|
||||
var JACK_FRAC = 0.575;
|
||||
|
||||
var sm = window.slopsmith;
|
||||
var sm = window.feedBack;
|
||||
|
||||
// ---- pure geometry helpers (exported for tests) -----------------------
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
let openPop = null; // { btn, pop }
|
||||
|
||||
// ── v3 UI signal + plugin-control slot API ───────────────────────────────
|
||||
// Lets plugins detect v3 (window.slopsmith.uiVersion === 'v3') and mount
|
||||
// Lets plugins detect v3 (window.feedBack.uiVersion === 'v3') and mount
|
||||
// controls into a stable slot instead of the auto-hiding transport.
|
||||
window.slopsmith = window.slopsmith || {};
|
||||
window.slopsmith.uiVersion = 'v3';
|
||||
window.slopsmith.ui = window.slopsmith.ui || {};
|
||||
window.slopsmith.ui.version = 'v3';
|
||||
window.slopsmith.ui.playerControlSlot = function () { return $('v3-plugin-controls-slot'); };
|
||||
window.feedBack = window.feedBack || {};
|
||||
window.feedBack.uiVersion = 'v3';
|
||||
window.feedBack.ui = window.feedBack.ui || {};
|
||||
window.feedBack.ui.version = 'v3';
|
||||
window.feedBack.ui.playerControlSlot = function () { return $('v3-plugin-controls-slot'); };
|
||||
|
||||
// ── Plugin-control re-homing shim ────────────────────────────────────────
|
||||
// Legacy plugins inject controls into #player-controls (now an auto-hiding
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
var sm = window.slopsmith;
|
||||
var sm = window.feedBack;
|
||||
var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
||||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); };
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
return '<div class="v3-pedal' + (off ? ' v3-pedal-off' : '') + '" data-id="' + esc(p.id) + '" tabindex="0" role="button" ' +
|
||||
'aria-label="' + esc((p.name || p.id) + action) + '" title="' + esc(tip) + '"' + style + '>' +
|
||||
'<span class="v3-pedal-glow" aria-hidden="true"></span>' +
|
||||
(p.bundled ? '<span class="v3-pedal-bundled" title="Ships with Slopsmith core">core</span>' : '') +
|
||||
(p.bundled ? '<span class="v3-pedal-bundled" title="Ships with FeedBack core">core</span>' : '') +
|
||||
'<span class="v3-pedal-offbadge" aria-hidden="true">off</span>' +
|
||||
'<div class="v3-pedal-label">' +
|
||||
'<img class="v3-pedal-thumb" alt="" loading="lazy" src="' + esc(thumbUrl(p)) + '">' +
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
// public global is set at the end of the plugin's screen.js, after the
|
||||
// owner is registered, so it is a reliable readiness signal.
|
||||
function waitForInputSetup(timeoutMs) {
|
||||
const ready = () => !!(window.slopsmithInputSetup && typeof window.slopsmithInputSetup.launch === 'function');
|
||||
const ready = () => !!(window.feedBackInputSetup && typeof window.feedBackInputSetup.launch === 'function');
|
||||
return new Promise((resolve) => {
|
||||
if (ready()) { resolve(true); return; }
|
||||
const t0 = Date.now();
|
||||
@@ -222,9 +222,9 @@
|
||||
if (!instruments.length) return;
|
||||
// Don't let a plugin-load race skip the mandatory input-setup step.
|
||||
if (!(await waitForInputSetup(8000))) return;
|
||||
const caps = window.slopsmith && window.slopsmith.capabilities;
|
||||
const caps = window.feedBack && window.feedBack.capabilities;
|
||||
if (!caps || typeof caps.command !== 'function') {
|
||||
try { await window.slopsmithInputSetup.launch(instruments); } catch (e) { /* proceed */ }
|
||||
try { await window.feedBackInputSetup.launch(instruments); } catch (e) { /* proceed */ }
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
@@ -459,7 +459,7 @@
|
||||
songDirEl.addEventListener('input', () => { songDir = songDirEl.value.trim(); refreshSubmit(); });
|
||||
}
|
||||
// Native folder picker on desktop; web users type/paste the path.
|
||||
const _desktop = window.slopsmithDesktop;
|
||||
const _desktop = window.feedBackDesktop;
|
||||
if (songDirBrowse && _desktop && typeof _desktop.pickDirectory === 'function') {
|
||||
songDirBrowse.classList.remove('hidden');
|
||||
songDirBrowse.addEventListener('click', async () => {
|
||||
@@ -510,7 +510,7 @@
|
||||
}
|
||||
renderBadge();
|
||||
renderProfileScreen();
|
||||
if (window.slopsmith && window.slopsmith.emit) window.slopsmith.emit('v3:profile-updated', _profile);
|
||||
if (window.feedBack && window.feedBack.emit) window.feedBack.emit('v3:profile-updated', _profile);
|
||||
// First-run only: after a genuine onboarding completion (not a
|
||||
// profile edit), kick off the one-time home tour — but NOT when we're
|
||||
// about to launch the diagnostic ("Play it now"), which navigates to
|
||||
@@ -627,9 +627,9 @@
|
||||
renderProfileScreen();
|
||||
// Rank / dB / frames re-render whenever progression state or equipped
|
||||
// cosmetics move (progression-core.js / theme-core.js own the fetches).
|
||||
if (window.slopsmith && typeof window.slopsmith.on === 'function') {
|
||||
window.slopsmith.on('progression:updated', () => { renderBadge(); renderProfileScreen(); });
|
||||
window.slopsmith.on('v3:cosmetics-applied', () => { renderBadge(); renderProfileScreen(); });
|
||||
if (window.feedBack && typeof window.feedBack.on === 'function') {
|
||||
window.feedBack.on('progression:updated', () => { renderBadge(); renderProfileScreen(); });
|
||||
window.feedBack.on('v3:cosmetics-applied', () => { renderBadge(); renderProfileScreen(); });
|
||||
}
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const SCREEN_ID = 'v3-progress';
|
||||
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* (buy/equip require authorization:'user-action')
|
||||
*
|
||||
* Lifecycle events are emitted on the capability surface and mirrored on
|
||||
* window.slopsmith as `progression:*` for non-capability consumers:
|
||||
* window.feedBack as `progression:*` for non-capability consumers:
|
||||
* challenge-completed, quest-completed, quest-progressed, path-level-up,
|
||||
* path-progressed, rank-changed, db-changed, calibration-completed,
|
||||
* cosmetic-equipped (+ progression:updated whenever fresh state lands).
|
||||
@@ -24,7 +24,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith = window.slopsmith || {};
|
||||
const sm = window.feedBack = window.feedBack || {};
|
||||
|
||||
let _state = null; // last /api/progression payload
|
||||
let _fetching = null; // in-flight refresh (coalesced)
|
||||
@@ -151,7 +151,7 @@
|
||||
if (!diagnostics || typeof diagnostics.contribute !== 'function' || !_state) return;
|
||||
try {
|
||||
diagnostics.contribute('progression', {
|
||||
schema: 'slopsmith.progression.diag.v1',
|
||||
schema: 'feedBack.progression.diag.v1',
|
||||
mastery_rank: _state.mastery_rank,
|
||||
calibration_status: (_state.onboarding || {}).calibration_status,
|
||||
paths: (_state.paths || []).map((p) => ({ id: p.id, level: p.level, max_level: p.max_level })),
|
||||
|
||||
+3
-3
@@ -287,7 +287,7 @@
|
||||
|
||||
// ── showScreen wrapper (idempotent rehydration — design/05 §Rehydration) ─
|
||||
function installShowScreenHook() {
|
||||
const hooks = window.__slopsmithV3ShellHooks || (window.__slopsmithV3ShellHooks = {});
|
||||
const hooks = window.__feedBackV3ShellHooks || (window.__feedBackV3ShellHooks = {});
|
||||
hooks.syncActive = syncActive; // always point at the latest impl
|
||||
if (hooks.installed) return;
|
||||
hooks.installed = true;
|
||||
@@ -359,8 +359,8 @@
|
||||
// The "Welcome back, {name}!" title needs the profile, which loads
|
||||
// async — refresh once it's in (and whenever it changes).
|
||||
function refreshHomeTitle() { if (currentScreenId() === 'v3-home') setTopbarTitle(titleFor('v3-home')); }
|
||||
if (window.slopsmith && typeof window.slopsmith.on === 'function') {
|
||||
window.slopsmith.on('v3:profile-updated', refreshHomeTitle);
|
||||
if (window.feedBack && typeof window.feedBack.on === 'function') {
|
||||
window.feedBack.on('v3:profile-updated', refreshHomeTitle);
|
||||
}
|
||||
setTimeout(refreshHomeTitle, 700);
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const SCREEN_ID = 'v3-shop';
|
||||
|
||||
let _data = null; // last GET /api/shop payload
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
||||
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
const enc = encodeURIComponent;
|
||||
@@ -266,7 +266,7 @@
|
||||
async function jget(url) { try { const r = await fetch(url); return r.ok ? r.json() : null; } catch (e) { return null; } }
|
||||
|
||||
// ── Provider-aware song helpers ────────────────────────────────────────
|
||||
// Remote library providers (slopsmith-plugin-remote-library-*) expose songs
|
||||
// Remote library providers (feedBack-plugin-remote-library-*) expose songs
|
||||
// by provider-owned id with their own art/sync/play flow. Reuse the legacy
|
||||
// app.js globals (the shared engine) so v3 behaves identically for remote
|
||||
// providers instead of assuming every row is a local file. All degrade to
|
||||
@@ -835,7 +835,7 @@
|
||||
try {
|
||||
const lp = sm && sm.libraryProviders;
|
||||
// refresh() re-fetches /api/library/providers so REMOTE providers
|
||||
// (registered by slopsmith-plugin-remote-library-*) appear — list()
|
||||
// (registered by feedBack-plugin-remote-library-*) appear — list()
|
||||
// returns only the capability's initial local-only snapshot.
|
||||
const fn = lp && (typeof lp.refresh === 'function' ? lp.refresh : (typeof lp.list === 'function' ? lp.list : null));
|
||||
if (fn) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
*
|
||||
* Bridges the highway note-detection scorer to the core song_stats store
|
||||
* (POST /api/stats). The scorer lives in the OPTIONAL external plugin
|
||||
* slopsmith-plugin-notedetect, which emits `note:hit` / `note:miss` per note
|
||||
* on window.slopsmith — note-detection is a DEFERRED capability domain, so we
|
||||
* feedBack-plugin-notedetect, which emits `note:hit` / `note:miss` per note
|
||||
* on window.feedBack — note-detection is a DEFERRED capability domain, so we
|
||||
* use those legacy events directly (design/05-capability-pipelines.md). If the
|
||||
* plugin isn't installed, no note events fire and nothing is recorded
|
||||
* (graceful degrade — the dashboard simply shows no accuracy).
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
const sm = window.slopsmith;
|
||||
const sm = window.feedBack;
|
||||
if (!sm || typeof sm.on !== 'function') return;
|
||||
|
||||
let cur = null; // active session
|
||||
|
||||
@@ -116,8 +116,8 @@
|
||||
if (r.ok) {
|
||||
const profile = await r.json();
|
||||
applyCosmetics(profile.cosmetics);
|
||||
if (window.slopsmith && typeof window.slopsmith.emit === 'function') {
|
||||
window.slopsmith.emit('v3:cosmetics-applied', profile.cosmetics || {});
|
||||
if (window.feedBack && typeof window.feedBack.emit === 'function') {
|
||||
window.feedBack.emit('v3:cosmetics-applied', profile.cosmetics || {});
|
||||
}
|
||||
}
|
||||
} catch (e) { /* offline — keep current look */ }
|
||||
@@ -134,7 +134,7 @@
|
||||
refresh();
|
||||
// Re-apply when an equip/unequip happens anywhere (shop screen, capability
|
||||
// command from a plugin).
|
||||
if (window.slopsmith && typeof window.slopsmith.on === 'function') {
|
||||
window.slopsmith.on('progression:cosmetic-equipped', refresh);
|
||||
if (window.feedBack && typeof window.feedBack.on === 'function') {
|
||||
window.feedBack.on('progression:cosmetic-equipped', refresh);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
(function (root) {
|
||||
'use strict';
|
||||
|
||||
const KEY = 'slopsmith-venue-mood-fx';
|
||||
const MOTION_KEY = 'slopsmith-venue-motion';
|
||||
const KEY = 'feedBack-venue-mood-fx';
|
||||
const MOTION_KEY = 'feedBack-venue-motion';
|
||||
const SETTINGS = Object.freeze({ OFF: 'off', SUBTLE: 'subtle', FULL: 'full' });
|
||||
const DEFAULT = SETTINGS.SUBTLE;
|
||||
const MOTION_DEFAULT = SETTINGS.SUBTLE;
|
||||
@@ -508,7 +508,7 @@
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
const boot = () => {
|
||||
const sm = root && root.slopsmith;
|
||||
const sm = root && root.feedBack;
|
||||
if (sm) bindRuntime(sm);
|
||||
};
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
? root.highway.getSongInfo()
|
||||
: null;
|
||||
if (si && si.arrangement) return si.arrangement;
|
||||
const cs = root.slopsmith && root.slopsmith.currentSong;
|
||||
const cs = root.feedBack && root.feedBack.currentSong;
|
||||
if (cs && cs.arrangement) return cs.arrangement;
|
||||
if (cs && cs.arrangementSmartName) return cs.arrangementSmartName;
|
||||
} catch (_) { /* visual-only */ }
|
||||
@@ -166,7 +166,7 @@
|
||||
function bindRuntime() {
|
||||
if (_bound) return;
|
||||
_bound = true;
|
||||
const sm = root && root.slopsmith;
|
||||
const sm = root && root.feedBack;
|
||||
if (sm && typeof sm.on === 'function') {
|
||||
sm.on('v3:live-performance-state', onPerformanceState);
|
||||
sm.on('song:loaded', () => {
|
||||
|
||||
Reference in New Issue
Block a user