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:
Bret Mogilefsky
2026-06-23 11:03:01 +02:00
committed by GitHub
co-authored by Claude Opus 4.8 byrongamatos
parent a8ad02739a
commit af2949677a
257 changed files with 2389 additions and 2293 deletions
+50 -50
View File
@@ -18,7 +18,7 @@ function registerProvider(api, overrides = {}) {
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'plan-1',
routeKey: 'desktop-main',
providerId: overrides.providerId || 'rig-builder',
@@ -42,18 +42,18 @@ function registerProvider(api, overrides = {}) {
test('audio-effects host registers active domain and contributes diagnostics', () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const pipeline = api.inspect('audio-effects');
const diagnostics = window.slopsmith.diagnostics.snapshotContributions();
const diagnostics = window.feedBack.diagnostics.snapshotContributions();
assert.equal(pipeline.review.lifecycle, 'active');
assert.equal(pipeline.participants.some(p => p.pluginId === 'core.audio.effects' && p.roles.includes('owner')), true);
assert.equal(diagnostics['audio-effects'].schema, 'slopsmith.audio_effects.diagnostics.v1');
assert.equal(diagnostics['audio-effects'].schema, 'feedBack.audio_effects.diagnostics.v1');
});
test('audio-effects runtime providers and executors are capability participants', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerProvider(api, {
providerId: 'nam-tone',
@@ -91,7 +91,7 @@ test('audio-effects runtime providers and executors are capability participants'
test('unregistering a provider drops its role/operations and clearing all removes the participant', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerProvider(api, {
providerId: 'nam-tone',
@@ -144,7 +144,7 @@ test('unregistering a provider drops its role/operations and clearing all remove
test('host runtime overlay preserves a plugin-declared audio-effects manifest entry', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// The plugin declares its own audio-effects participancy in its manifest before any runtime
// registration goes through the host.
@@ -181,7 +181,7 @@ test('host runtime overlay preserves a plugin-declared audio-effects manifest en
test('select-chain requires user action and records selected provider', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerProvider(api);
const denied = await api.dispatch({ capability: 'audio-effects', command: 'select-chain', source: 'test', payload: { routeKey: 'desktop-main' } });
@@ -197,7 +197,7 @@ test('select-chain requires user action and records selected provider', async ()
test('resolve-plan calls selected provider and returns constrained plan without storing raw payload in diagnostics', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerProvider(api);
await api.dispatch({ capability: 'audio-effects', command: 'select-chain', source: 'test', payload: { routeKey: 'desktop-main', authorization: 'user-action' } });
@@ -206,7 +206,7 @@ test('resolve-plan calls selected provider and returns constrained plan without
const encoded = JSON.stringify(snapshot);
assert.equal(resolved.outcome, 'handled');
assert.equal(resolved.payload.plan.schema, 'slopsmith.audio_effects.chain_plan.v1');
assert.equal(resolved.payload.plan.schema, 'feedBack.audio_effects.chain_plan.v1');
assert.equal(resolved.payload.plan.stages.length, 3);
assert.deepEqual(JSON.parse(JSON.stringify(resolved.payload.plan.segments[0].stageBypass)), { 'pre-1': true, 'amp-1': false });
assert.equal(snapshot.routes[0].state, 'resolved');
@@ -218,13 +218,13 @@ test('resolve-plan calls selected provider and returns constrained plan without
test('resolve-plan rejects raw file paths and records fallback state', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerProvider(api, {
operationHandlers: {
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'bad-plan',
routeKey: 'desktop-main',
providerId: 'rig-builder',
@@ -247,9 +247,9 @@ test('resolve-plan rejects raw file paths and records fallback state', async ()
test('load-plan calls trusted executor with provider-private assets without diagnostic leakage', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
let executorRequest = null;
window.slopsmithDesktop = {
window.feedBackDesktop = {
audioEffects: {
loadChainPlan(request) {
executorRequest = request;
@@ -262,7 +262,7 @@ test('load-plan calls trusted executor with provider-private assets without diag
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'private-plan',
routeKey: 'desktop-main',
providerId: 'rig-builder',
@@ -303,9 +303,9 @@ test('load-plan calls trusted executor with provider-private assets without diag
test('route gain and release delegate to the selected executor', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
window.slopsmithDesktop = {
window.feedBackDesktop = {
audioEffects: {
loadChainPlan() { calls.push(['load']); return { outcome: 'handled', status: 'loaded', payload: { slotsLoaded: 1 } }; },
setRouteGain(request) { calls.push(['gain', request.gains]); return { outcome: 'handled', payload: { gains: request.gains } }; },
@@ -314,9 +314,9 @@ test('route gain and release delegate to the selected executor', async () => {
};
await registerProvider(api);
const loaded = await api.dispatch({ capability: 'audio-effects', command: 'load-plan', source: 'test', payload: { routeKey: 'desktop-main', authorization: 'playback-session' } });
const gained = await window.slopsmith.audioEffects.setRouteGain({ routeKey: 'desktop-main', authorization: 'playback-session', gains: { input: 3, chain: 2 } });
const released = await window.slopsmith.audioEffects.releaseRoute({ routeKey: 'desktop-main', authorization: 'playback-session' });
const inspected = await window.slopsmith.audioEffects.inspectRoute({ routeKey: 'desktop-main' });
const gained = await window.feedBack.audioEffects.setRouteGain({ routeKey: 'desktop-main', authorization: 'playback-session', gains: { input: 3, chain: 2 } });
const released = await window.feedBack.audioEffects.releaseRoute({ routeKey: 'desktop-main', authorization: 'playback-session' });
const inspected = await window.feedBack.audioEffects.inspectRoute({ routeKey: 'desktop-main' });
assert.equal(loaded.outcome, 'handled');
assert.equal(gained.outcome, 'handled');
@@ -327,8 +327,8 @@ test('route gain and release delegate to the selected executor', async () => {
test('provider unregister clears route plan and executor state', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
window.slopsmithDesktop = {
const api = window.feedBack.capabilities;
window.feedBackDesktop = {
audioEffects: {
loadChainPlan() { return { outcome: 'handled', status: 'loaded', payload: { slotsLoaded: 1 } }; },
},
@@ -360,7 +360,7 @@ test('provider unregister clears route plan and executor state', async () => {
test('registry caps still allow provider and executor refreshes', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
for (let i = 0; i < 50; i++) {
const result = await registerProvider(api, { providerId: `provider-${i}`, pluginId: `plugin-${i}` });
assert.equal(result.outcome, 'handled');
@@ -400,8 +400,8 @@ test('registry caps still allow provider and executor refreshes', async () => {
test('route restore preserves loaded state when an executor remains active', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
window.slopsmithDesktop = {
const api = window.feedBack.capabilities;
window.feedBackDesktop = {
audioEffects: {
loadChainPlan() { return { outcome: 'handled', status: 'loaded', payload: { slotsLoaded: 1 } }; },
},
@@ -419,7 +419,7 @@ test('route restore preserves loaded state when an executor remains active', asy
test('load-plan uses a registered compatible executor when Desktop is unavailable', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
let executorRequest = null;
await registerProvider(api, {
providerId: 'nam-tone',
@@ -428,7 +428,7 @@ test('load-plan uses a registered compatible executor when Desktop is unavailabl
'chain.resolve': request => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'browser-plan',
routeKey: request.routeKey,
providerId: 'nam-tone',
@@ -482,7 +482,7 @@ test('load-plan uses a registered compatible executor when Desktop is unavailabl
test('load-plan redacts circular executor payloads without overflowing', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const circular = { loaded: true };
circular.self = circular;
await registerProvider(api);
@@ -516,7 +516,7 @@ test('load-plan redacts circular executor payloads without overflowing', async (
test('load-plan does not use an executor registered for a different provider', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerProvider(api, {
providerId: 'rig-builder',
pluginId: 'rig_builder',
@@ -524,7 +524,7 @@ test('load-plan does not use an executor registered for a different provider', a
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'rig-plan',
routeKey: 'desktop-main',
providerId: 'rig-builder',
@@ -562,7 +562,7 @@ test('load-plan does not use an executor registered for a different provider', a
test('load-plan rejects executors that cannot support the resolved stage kinds', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
let called = false;
await registerProvider(api, {
providerId: 'rig-builder',
@@ -571,7 +571,7 @@ test('load-plan rejects executors that cannot support the resolved stage kinds',
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'rig-vst-plan',
routeKey: 'desktop-main',
providerId: 'rig-builder',
@@ -611,7 +611,7 @@ test('load-plan rejects executors that cannot support the resolved stage kinds',
test('load-plan can fall back to a compatible provider executor when the selected provider has none', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
let loadedProviderId = '';
await registerProvider(api, {
providerId: 'rig-builder',
@@ -621,7 +621,7 @@ test('load-plan can fall back to a compatible provider executor when the selecte
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'rig-plan',
routeKey: 'desktop-main',
providerId: 'rig-builder',
@@ -638,7 +638,7 @@ test('load-plan can fall back to a compatible provider executor when the selecte
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'nam-plan',
routeKey: 'desktop-main',
providerId: 'nam-tone',
@@ -678,7 +678,7 @@ test('load-plan can fall back to a compatible provider executor when the selecte
test('load-plan can fall back from a selected provider when its executor cannot load the plan', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
let loadedProviderId = '';
await registerProvider(api, {
providerId: 'rig-builder',
@@ -688,7 +688,7 @@ test('load-plan can fall back from a selected provider when its executor cannot
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'rig-vst-plan',
routeKey: 'desktop-main',
providerId: 'rig-builder',
@@ -705,7 +705,7 @@ test('load-plan can fall back from a selected provider when its executor cannot
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'nam-plan',
routeKey: 'desktop-main',
providerId: 'nam-tone',
@@ -759,14 +759,14 @@ test('load-plan can fall back from a selected provider when its executor cannot
test('provider operations route stage and segment changes through selected provider', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
await registerProvider(api, {
operationHandlers: {
'chain.resolve': () => ({
outcome: 'handled',
plan: {
schema: 'slopsmith.audio_effects.chain_plan.v1',
schema: 'feedBack.audio_effects.chain_plan.v1',
planId: 'switch-plan',
routeKey: 'desktop-main',
providerId: 'rig-builder',
@@ -793,7 +793,7 @@ test('provider operations route stage and segment changes through selected provi
test('mapping helpers call core mapping API with provider-tagged payloads', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
window.fetch = async (url, options = {}) => {
calls.push({ url: String(url), options });
@@ -815,7 +815,7 @@ test('mapping helpers call core mapping API with provider-tagged payloads', asyn
};
};
const saved = await window.slopsmith.audioEffects.upsertMapping({
const saved = await window.feedBack.audioEffects.upsertMapping({
song_key: 'settings-v1-song',
filename: 'Artist - Song_p.archive',
tone_key: 'Dist',
@@ -829,8 +829,8 @@ test('mapping helpers call core mapping API with provider-tagged payloads', asyn
source: 'rig_builder',
payload: { song_key: 'settings-v1-song', provider_id: 'rig-builder' },
});
const activated = await window.slopsmith.audioEffects.activateMapping({ mappingId: 7, providerId: 'rig-builder' });
const cleared = await window.slopsmith.audioEffects.clearActiveMapping({ songKey: 'settings-v1-song', toneKey: 'Dist' });
const activated = await window.feedBack.audioEffects.activateMapping({ mappingId: 7, providerId: 'rig-builder' });
const cleared = await window.feedBack.audioEffects.clearActiveMapping({ songKey: 'settings-v1-song', toneKey: 'Dist' });
assert.equal(saved.outcome, 'handled');
assert.equal(saved.payload.mapping.provider_ref, 'chain:99');
@@ -856,21 +856,21 @@ test('mapping helpers forward present falsey fields to the server instead of swa
// A falsey non-string provider_id must reach the server (which rejects it) rather than being
// coerced to '' client-side, which would become a silent unscoped activate.
await window.slopsmith.audioEffects.activateMapping({ mappingId: 7, providerId: false });
await window.feedBack.audioEffects.activateMapping({ mappingId: 7, providerId: false });
assert.equal(JSON.parse(calls[0].options.body).provider_id, false);
// A present falsey query filter is forwarded (stringified) rather than dropped.
await window.slopsmith.audioEffects.listMappings({ provider_id: 0 });
await window.feedBack.audioEffects.listMappings({ provider_id: 0 });
assert.match(calls[1].url, /provider_id=0/);
// An omitted filter stays omitted (no spurious empty filter).
await window.slopsmith.audioEffects.listMappings({});
await window.feedBack.audioEffects.listMappings({});
assert.equal(calls[2].url, '/api/audio-effects/mappings');
});
test('bridge hits are safe and diagnosable', async () => {
const window = loadAudioEffects();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const result = await api.dispatch({
capability: 'audio-effects',
@@ -902,7 +902,7 @@ test('bridge hits are safe and diagnosable', async () => {
routeKey: 'desktop-main',
bridgeId: 'audio-effects.legacy-native-load',
pluginId: 'nam_tone',
legacySurface: 'window.slopsmithDesktop.audio.loadPreset /Users/example/model.nam',
legacySurface: 'window.feedBackDesktop.audio.loadPreset /Users/example/model.nam',
},
});
const encoded = JSON.stringify(diagnosticsSnapshot(window));
@@ -910,7 +910,7 @@ test('bridge hits are safe and diagnosable', async () => {
assert.equal(result.outcome, 'handled');
assert.equal(dbResult.outcome, 'handled');
assert.equal(nativeResult.outcome, 'handled');
const sharedShim = window.slopsmith.capabilities.snapshotDiagnostics().compatibilityShims.find(entry => entry.shimId === 'audio-effects.legacy-nam-routing');
const sharedShim = window.feedBack.capabilities.snapshotDiagnostics().compatibilityShims.find(entry => entry.shimId === 'audio-effects.legacy-nam-routing');
assert.equal(sharedShim.status, 'used');
assert.equal(sharedShim.hitCount >= 1, true);
assert.equal(encoded.includes('audio-effects.legacy-nam-routing'), true);
+1 -1
View File
@@ -16,7 +16,7 @@ function loadAudioEffects(options = {}) {
}
function diagnosticsSnapshot(window) {
return window.slopsmith.audioEffects.snapshot();
return window.feedBack.audioEffects.snapshot();
}
module.exports = { loadAudioEffects, diagnosticsSnapshot, ROOT };
+21 -21
View File
@@ -26,7 +26,7 @@ test('legacy fader API remains compatible while bridge hits are attributed', asy
runBrowserScript(window, 'static/audio-mixer.js');
let volume = 0.5;
window.slopsmith.audio.registerFader({
window.feedBack.audio.registerFader({
id: 'plugin.delay',
label: 'Delay',
min: 0,
@@ -37,8 +37,8 @@ test('legacy fader API remains compatible while bridge hits are attributed', asy
setValue: value => { volume = value; },
});
const snapshot = window.slopsmith.audioSession.snapshot();
assert.equal(window.slopsmith.audio.getFaders().some(fader => fader.id === 'plugin.delay'), true);
const snapshot = window.feedBack.audioSession.snapshot();
assert.equal(window.feedBack.audio.getFaders().some(fader => fader.id === 'plugin.delay'), true);
assert.equal(snapshot.domains['audio-mix'].participants.some(participant => participant.participantId === 'fader.plugin.delay'), true);
assert.equal(snapshot.domains['audio-mix'].bridges.some(bridge => bridge.bridgeId === 'audio-mix.fader-registry'), true);
});
@@ -48,9 +48,9 @@ test('legacy analyser fallback records bridge status without losing analyser out
installAnalyserDom(window);
runBrowserScript(window, 'plugins/highway_3d/screen.js');
const analyser = window.slopsmithViz_highway_3d.__test.getAnalyserForBridgeTest();
const bands = window.slopsmithViz_highway_3d.__test.readBandsForBridgeTest();
const bridge = window.slopsmith.audioSession.snapshot().domains['audio-mix'].bridges.find(entry => entry.bridgeId === 'audio-mix.analyser');
const analyser = window.feedBackViz_highway_3d.__test.getAnalyserForBridgeTest();
const bands = window.feedBackViz_highway_3d.__test.readBandsForBridgeTest();
const bridge = window.feedBack.audioSession.snapshot().domains['audio-mix'].bridges.find(entry => entry.bridgeId === 'audio-mix.analyser');
assert.equal(analyser.source, 'core');
assert.equal(bands.bass > 0, true);
@@ -59,9 +59,9 @@ test('legacy analyser fallback records bridge status without losing analyser out
test('barrier and input compatibility surfaces are visible in diagnostics', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.recordBridgeHit({ domain: 'audio-monitoring', bridgeId: 'audio-monitoring.audio-barrier', legacySurface: 'window.slopsmithAudioBarrier', participantId: 'note_detect', outcome: 'degraded', reason: 'timeout' });
audioSession.recordBridgeHit({ domain: 'audio-monitoring', bridgeId: 'audio-monitoring.audio-barrier', legacySurface: 'window.feedBackAudioBarrier', participantId: 'note_detect', outcome: 'degraded', reason: 'timeout' });
audioSession.recordBridgeHit({ domain: 'audio-input', bridgeId: 'audio-input.legacy-source', legacySurface: 'navigator.mediaDevices.getUserMedia', participantId: 'note_detect', outcome: 'denied', reason: 'permission denied' });
const snapshot = audioSession.snapshot();
@@ -71,7 +71,7 @@ test('barrier and input compatibility surfaces are visible in diagnostics', () =
test('a legacy bridge hit with unsafe fields never leaks a path/token into diagnostics', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.recordBridgeHit({
domain: 'audio-input',
@@ -91,7 +91,7 @@ test('a legacy bridge hit with unsafe fields never leaks a path/token into diagn
test('audio-input explicit enumeration registers provider sources without list prompting', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const provider = makeInputProvider({
providerId: 'desktop_audio',
sourceId: 'bootstrap-source',
@@ -106,7 +106,7 @@ test('audio-input explicit enumeration registers provider sources without list p
assert.equal(listed.payload.sources.some(source => source.logicalSourceKey === 'desktop:instrument:secondary'), false);
assert.deepEqual(provider.calls, []);
const enumerated = await window.slopsmith.audioSession.enumerateInputSources({ providerId: 'desktop_audio', explicit: true, requesterId: 'settings' });
const enumerated = await window.feedBack.audioSession.enumerateInputSources({ providerId: 'desktop_audio', explicit: true, requesterId: 'settings' });
const after = await api.dispatch({ capability: 'audio-input', command: 'list-sources', source: 'test' });
assert.equal(enumerated.outcome, 'handled');
@@ -117,7 +117,7 @@ test('audio-input explicit enumeration registers provider sources without list p
test('audio-input native source wins over compatibility-backed duplicate', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await api.dispatch({
capability: 'audio-input',
@@ -150,7 +150,7 @@ test('audio-input native source wins over compatibility-backed duplicate', async
});
const listed = await api.dispatch({ capability: 'audio-input', command: 'list-sources', source: 'test' });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-input'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-input'];
assert.equal(listed.payload.sources.length, 1);
assert.equal(listed.payload.sources[0].providerId, 'native_input');
@@ -160,7 +160,7 @@ test('audio-input native source wins over compatibility-backed duplicate', async
test('audio-monitoring native provider wins over legacy compatibility provider and overshadows its compatibility bridge', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const legacy = makeMonitoringProvider({
providerId: 'legacy_monitor',
logicalMonitoringKey: 'shared:monitor:primary',
@@ -177,7 +177,7 @@ test('audio-monitoring native provider wins over legacy compatibility provider a
await api.dispatch({ capability: 'audio-monitoring', command: 'register-provider', source: 'native_monitor', payload: native.provider });
const listed = await api.dispatch({ capability: 'audio-monitoring', command: 'list-providers', source: 'test' });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
// The compatibility bridge below is the one the registration/supersession path actually produces;
// asserting only on it (not on manually pre-seeded bridge hits) keeps this test honest if the
@@ -192,10 +192,10 @@ test('legacy registerFader callbacks are usable through audio-mix get and set op
const window = loadAudioSession();
installMixerDom(window);
runBrowserScript(window, 'static/audio-mixer.js');
window.slopsmith.audioSession.startSession({ sessionId: 'main:test-song' });
window.feedBack.audioSession.startSession({ sessionId: 'main:test-song' });
let gain = 0.35;
window.slopsmith.audio.registerFader({
window.feedBack.audio.registerFader({
id: 'plugin.gain',
label: 'Plugin Gain',
min: 0,
@@ -206,7 +206,7 @@ test('legacy registerFader callbacks are usable through audio-mix get and set op
setValue: value => { gain = Math.min(0.9, value); return gain; },
});
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const listed = await api.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
const read = await api.dispatch({ capability: 'audio-mix', command: 'get-fader-value', source: 'test', payload: { participantId: 'fader.plugin.gain', faderId: 'plugin.gain' } });
const written = await api.dispatch({ capability: 'audio-mix', command: 'set-fader-value', source: 'test', payload: { participantId: 'fader.plugin.gain', faderId: 'plugin.gain', value: 1 } });
@@ -216,7 +216,7 @@ test('legacy registerFader callbacks are usable through audio-mix get and set op
assert.equal(written.payload.committedValue, 0.9);
assert.equal(gain, 0.9);
window.slopsmith.audio.unregisterFader('plugin.gain');
assert.equal(window.slopsmith.audio.getFaders().some(fader => fader.id === 'plugin.gain'), false);
assert.equal(window.slopsmith.audioSession.snapshot().domains['audio-mix'].participants.some(participant => participant.participantId === 'fader.plugin.gain'), false);
window.feedBack.audio.unregisterFader('plugin.gain');
assert.equal(window.feedBack.audio.getFaders().some(fader => fader.id === 'plugin.gain'), false);
assert.equal(window.feedBack.audioSession.snapshot().domains['audio-mix'].participants.some(participant => participant.participantId === 'fader.plugin.gain'), false);
});
+13 -13
View File
@@ -4,8 +4,8 @@ const { loadAudioSession, diagnosticsSnapshot, makeInputProvider, makeMonitoring
test('audio session host registers active core domains and contributes diagnostics', () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const diagnostics = window.slopsmith.diagnostics.snapshotContributions();
const api = window.feedBack.capabilities;
const diagnostics = window.feedBack.diagnostics.snapshotContributions();
for (const domain of ['audio-mix', 'audio-input', 'audio-monitoring']) {
const pipeline = api.inspect(domain);
@@ -15,12 +15,12 @@ test('audio session host registers active core domains and contributes diagnosti
const stemsPipeline = api.inspect('stems');
assert.equal(stemsPipeline.review.lifecycle, 'active');
assert.equal(stemsPipeline.participants.some(p => p.pluginId === 'core.audio.session' && p.roles.includes('coordinator') && !p.roles.includes('owner')), true);
assert.equal(diagnostics['audio-session'].schema, 'slopsmith.audio_session.diagnostics.v1');
assert.equal(diagnostics['audio-session'].schema, 'feedBack.audio_session.diagnostics.v1');
});
test('audio session lifecycle and snapshots redact source identity with per-snapshot pseudonyms', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.startSession({ sessionId: 'main:/Users/example/DLC/song.archive', songKey: '/Users/example/DLC/song.archive', songFormat: 'archive' });
audioSession.setRoute({ routeKind: 'html5', availability: 'available', deviceLabel: 'Scarlett 2i2 Serial 1234' });
@@ -36,7 +36,7 @@ test('audio session lifecycle and snapshots redact source identity with per-snap
test('audio-input diagnostics redact source ids labels handles and bounded reasons', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await api.dispatch({
capability: 'audio-input',
@@ -72,7 +72,7 @@ test('audio-input diagnostics redact source ids labels handles and bounded reaso
test('audio-input shares compatible open sessions and closes provider after last release', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
await api.dispatch({
@@ -110,7 +110,7 @@ test('audio-input shares compatible open sessions and closes provider after last
test('audio diagnostics record bounded runtime outcomes and domain statuses', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
for (let i = 0; i < 120; i += 1) {
audioSession.recordOutcome({ domain: 'audio-input', operation: 'select-source', participantId: 'test', outcome: 'degraded', status: 'unavailable', reason: `missing-${i}` });
@@ -124,8 +124,8 @@ test('audio diagnostics record bounded runtime outcomes and domain statuses', ()
test('disabled missing incompatible unsupported and timeout paths are diagnosable', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
audioSession.registerMixParticipant({ participantId: 'disabled-fader', availability: 'disabled' });
assert.equal(audioSession.snapshot().domains['audio-mix'].participants[0].availability, 'disabled');
@@ -154,8 +154,8 @@ test('disabled missing incompatible unsupported and timeout paths are diagnosabl
test('audio-mix diagnostics include faders routes analysers bridge hits and redacted outcomes', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
audioSession.startSession({ sessionId: 'main:/Users/example/DLC/song.archive', songKey: '/Users/example/DLC/song.archive' });
audioSession.setRoute({ routeKind: 'desktop', availability: 'degraded', deviceLabel: 'Secret Studio Output', fallbackReason: 'fallback token=abc123 at /Users/example/device' });
audioSession.setAnalyser({ source: 'plugin', availability: 'available', participantId: 'plugin.visualizer', reason: 'ok', rawFft: [1, 2, 3] });
@@ -197,7 +197,7 @@ test('audio-mix diagnostics include faders routes analysers bridge hits and reda
test('audio-monitoring diagnostics redact provider source session handles and private payloads', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const input = makeInputProvider({
providerId: 'secret_input',
sourceId: 'USB Interface Hardware ABC1234',
@@ -228,7 +228,7 @@ test('audio-monitoring diagnostics redact provider source session handles and pr
delete providerPayload.safeLabel;
await api.dispatch({ capability: 'audio-monitoring', command: 'register-provider', source: monitoring.provider.providerId, payload: providerPayload });
const started = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { requesterId: 'user', authorization: 'user-action', requiredChannelShape: 'mono' } });
window.slopsmith.audioSession.recordOutcome({ domain: 'audio-monitoring', operation: 'start', participantId: 'secret_monitor', providerId: 'secret_monitor', monitoringId: started.payload.monitoringId, sourceId: 'USB Interface Hardware ABC1234', openSessionId: 'open raw id 1234', requesterId: 'user', outcome: 'failed', status: 'timeout', reason: 'failed at /Users/barlind/private secret=abc123' });
window.feedBack.audioSession.recordOutcome({ domain: 'audio-monitoring', operation: 'start', participantId: 'secret_monitor', providerId: 'secret_monitor', monitoringId: started.payload.monitoringId, sourceId: 'USB Interface Hardware ABC1234', openSessionId: 'open raw id 1234', requesterId: 'user', outcome: 'failed', status: 'timeout', reason: 'failed at /Users/barlind/private secret=abc123' });
const snapshot = diagnosticsSnapshot(window);
const encoded = JSON.stringify(snapshot);
+55 -55
View File
@@ -26,7 +26,7 @@ async function registerSource(api, payload = {}) {
test('audio-input requires sourceId providerId and logicalSourceKey', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const missingSource = await api.dispatch({ capability: 'audio-input', command: 'register-source', source: 'test', payload: { providerId: 'test', logicalSourceKey: 'test:key' } });
const missingProvider = await api.dispatch({ capability: 'audio-input', command: 'register-source', source: 'test', payload: { sourceId: 'source-1', logicalSourceKey: 'test:key' } });
@@ -41,7 +41,7 @@ test('audio-input requires sourceId providerId and logicalSourceKey', async () =
test('audio-input registration list inspect select and snapshots pseudonymize source identity', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const registeredEvents = captureEvents(window, 'audio-input:source-registered');
const selectedEvents = captureEvents(window, 'audio-input:source-selected');
@@ -62,7 +62,7 @@ test('audio-input registration list inspect select and snapshots pseudonymize so
assert.equal(registeredEvents.length, 1);
assert.equal(selectedEvents.length, 1);
const encoded = JSON.stringify(window.slopsmith.audioSession.snapshot().domains['audio-input']);
const encoded = JSON.stringify(window.feedBack.audioSession.snapshot().domains['audio-input']);
assert.equal(encoded.includes('source-raw-id-12345'), false);
assert.equal(encoded.includes('Scarlett'), false);
assert.equal(encoded.includes('987654'), false);
@@ -70,7 +70,7 @@ test('audio-input registration list inspect select and snapshots pseudonymize so
test('audio-input pseudonyms are per-bundle: distinct within a snapshot, never leak raw identity', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.registerInputSource({ sourceId: 'mic-A', providerId: 'note_detect', logicalSourceKey: 'note_detect:mic-a', label: '/Users/me/My Songs/mic A' });
audioSession.registerInputSource({ sourceId: 'mic-B', providerId: 'note_detect', logicalSourceKey: 'note_detect:mic-b', label: 'device B' });
@@ -90,7 +90,7 @@ test('audio-input pseudonyms are per-bundle: distinct within a snapshot, never l
test('audio-input degraded select and unknown unregister never leak the raw source id', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
const degraded = audioSession.selectInputSource('/Users/me/secret-device', 'note_detect');
const removed = audioSession.unregisterInputSource('/Users/me/secret-device');
@@ -107,7 +107,7 @@ test('audio-input degraded select and unknown unregister never leak the raw sour
test('unknown unregister echoes the requested logicalSourceKey/providerId without pseudonymizing them', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
const removed = audioSession.unregisterInputSource({ logicalSourceKey: 'note_detect:instrument:primary', providerId: 'note_detect' });
@@ -121,7 +121,7 @@ test('unknown unregister echoes the requested logicalSourceKey/providerId withou
test('unregister-source uses providerId to target the right source among logical-key duplicates', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.registerInputSource({ sourceId: 'native-x', logicalSourceKey: 'dup:disambig', providerId: 'native_p', kind: 'instrument', safeLabel: 'N' });
audioSession.registerInputSource({ sourceId: 'compat-x', logicalSourceKey: 'dup:disambig', providerId: 'compat_p', compatibilitySource: 'legacy', kind: 'instrument', safeLabel: 'C' });
@@ -136,7 +136,7 @@ test('unregister-source uses providerId to target the right source among logical
test('register-source rejects a sourceId already owned by another provider', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const first = await api.dispatch({ capability: 'audio-input', command: 'register-source', source: 'prov_a', payload: { sourceId: 'shared-id', logicalSourceKey: 'a:key', providerId: 'prov_a' } });
const collision = await api.dispatch({ capability: 'audio-input', command: 'register-source', source: 'prov_b', payload: { sourceId: 'shared-id', logicalSourceKey: 'b:key', providerId: 'prov_b' } });
@@ -153,20 +153,20 @@ test('register-source rejects a sourceId already owned by another provider', asy
test('register-source rejects a logicalSourceKey that is not redaction-safe', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const result = await api.dispatch({ capability: 'audio-input', command: 'register-source', source: 'p', payload: { sourceId: 's1', providerId: 'p', logicalSourceKey: '/Users/me/secret token=abc123' } });
assert.equal(result.outcome, 'failed');
// The unsafe key must not be stored or leak into diagnostics.
const encoded = JSON.stringify(window.slopsmith.audioSession.snapshot());
const encoded = JSON.stringify(window.feedBack.audioSession.snapshot());
assert.equal(encoded.includes('/Users/me'), false);
assert.equal(encoded.includes('token=abc123'), false);
});
test('enumerate denied with an unsafe providerId never leaks it into diagnostics', async () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
// Missing explicit/userInitiated -> denied; the caller-supplied providerId is unsafe and is
// recorded as the outcome participantId/providerId, so it must be bounded in the snapshot.
@@ -182,20 +182,20 @@ test('enumerate denied with an unsafe providerId never leaks it into diagnostics
test('a malicious dispatch source is redacted before becoming a requesterId in diagnostics', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'req-raw', logicalSourceKey: 'test:req' });
// The capability dispatch `source` (the requester identity) is attacker-controlled here.
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: '/Users/me/plugin token=abc123', payload: { logicalSourceKey: 'test:req' } });
const encoded = JSON.stringify(window.slopsmith.audioSession.snapshot());
const encoded = JSON.stringify(window.feedBack.audioSession.snapshot());
assert.equal(encoded.includes('/Users/me'), false);
assert.equal(encoded.includes('abc123'), false);
});
test('caller-provided logical keys are bounded so a path/token cannot leak into diagnostics on a miss', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
// Untrusted callers may pass an unsafe value as a logical key; select/unregister misses must not
// echo or record it raw into the redaction-safe diagnostics snapshot.
@@ -211,7 +211,7 @@ test('caller-provided logical keys are bounded so a path/token cannot leak into
test('audio-monitoring distinguishes a failed state from transient unavailability', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api);
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'test:instrument:primary' } });
@@ -231,7 +231,7 @@ test('audio-monitoring distinguishes a failed state from transient unavailabilit
test('inspect list and select do not call provider enumeration or open handlers', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const provider = makeInputProvider({ providerId: 'note_detect', logicalSourceKey: 'note_detect:instrument:primary' });
await registerSource(api, provider.source);
@@ -244,7 +244,7 @@ test('inspect list and select do not call provider enumeration or open handlers'
test('open-source and close-source record outcomes events and no live handles', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const openedEvents = captureEvents(window, 'audio-input:source-opened');
const closedEvents = captureEvents(window, 'audio-input:source-closed');
const degradedEvents = captureEvents(window, 'audio-input:source-open-degraded');
@@ -275,7 +275,7 @@ test('open-source and close-source record outcomes events and no live handles',
assert.equal(incompatible.payload.state, 'incompatible');
assert.equal(degradedEvents.length >= 1, true);
const encoded = JSON.stringify(window.slopsmith.audioSession.snapshot());
const encoded = JSON.stringify(window.feedBack.audioSession.snapshot());
assert.equal(encoded.includes('mediaStream'), false);
assert.equal(encoded.includes('nativeHandle'), false);
assert.equal(encoded.includes('token=abc'), false);
@@ -283,7 +283,7 @@ test('open-source and close-source record outcomes events and no live handles',
test('open-source reports no-owner no-handler unsupported failed and malformed provider data distinctly', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const noOwner = await api.dispatch({ capability: 'audio-input', command: 'open-source', source: 'note_detect', payload: { requesterId: 'note_detect' } });
assert.equal(noOwner.outcome, 'no-owner');
@@ -308,7 +308,7 @@ test('open-source reports no-owner no-handler unsupported failed and malformed p
const malformed = await api.dispatch({ capability: 'audio-input', command: 'open-source', source: 'note_detect', payload: { requesterId: 'note_detect' } });
assert.equal(malformed.outcome, 'handled');
const outcomes = window.slopsmith.audioSession.snapshot().recentOutcomes.filter(outcome => outcome.domain === 'audio-input');
const outcomes = window.feedBack.audioSession.snapshot().recentOutcomes.filter(outcome => outcome.domain === 'audio-input');
assert.equal(outcomes.some(outcome => outcome.status === 'no-owner' || outcome.outcome === 'no-owner'), true);
assert.equal(outcomes.some(outcome => outcome.outcome === 'unsupported-command'), true);
assert.equal(outcomes.some(outcome => outcome.outcome === 'no-handler'), true);
@@ -317,7 +317,7 @@ test('open-source reports no-owner no-handler unsupported failed and malformed p
test('open-source never switches to a non-selected source addressed by raw sourceId or logical key', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'primary-raw', logicalSourceKey: 'test:primary' });
await registerSource(api, { sourceId: 'other-raw', logicalSourceKey: 'test:other' });
@@ -335,7 +335,7 @@ test('open-source never switches to a non-selected source addressed by raw sourc
test('open-source emits an open-session-shaped payload (with requester attribution) for a pre-denied source', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const deniedEvents = captureEvents(window, 'audio-input:permission-denied');
await registerSource(api, { sourceId: 'predenied-raw', logicalSourceKey: 'test:predenied', availability: 'denied', reason: 'permission denied' });
@@ -353,7 +353,7 @@ test('open-source emits an open-session-shaped payload (with requester attributi
test('open-source source-open-degraded uses an open-session-shaped payload when nothing is selected', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const degraded = captureEvents(window, 'audio-input:source-open-degraded');
// No source selected -> degraded; the event must share the OpenInputSessionSummary schema.
@@ -369,7 +369,7 @@ test('open-source source-open-degraded uses an open-session-shaped payload when
test('open-source reports the selected source as unavailable when no matching source is registered', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'gone-raw', logicalSourceKey: 'test:gone' });
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'test:gone' } });
@@ -385,7 +385,7 @@ test('open-source reports the selected source as unavailable when no matching so
test('open-source and close-source ignore a payload requesterId so callers cannot spoof session ownership', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'owned-raw', logicalSourceKey: 'test:owned' });
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'test:owned' } });
@@ -405,7 +405,7 @@ test('open-source and close-source ignore a payload requesterId so callers canno
test('open-source and close-source propagate a provider non-handled outcome exactly', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// source.open returns an explicit non-denied/failed outcome -> must propagate, not collapse to degraded.
await registerSource(api, { sourceId: 'po-raw', logicalSourceKey: 'test:po', operationHandlers: {
@@ -430,7 +430,7 @@ test('open-source and close-source propagate a provider non-handled outcome exac
test('close-source accepts logicalKey/sourceKey aliases like the other audio-input paths', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'alias-raw', logicalSourceKey: 'test:alias' });
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'test:alias' } });
@@ -445,7 +445,7 @@ test('close-source accepts logicalKey/sourceKey aliases like the other audio-inp
test('close-source resolves by logicalSourceKey when requiredChannelShape is omitted', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// Default source is stereo; open without a channel-shape hint so the session is keyed by the
// source's resolved shape, then close by logical key alone (requiredChannelShape is optional).
@@ -461,7 +461,7 @@ test('close-source resolves by logicalSourceKey when requiredChannelShape is omi
test('close-source with an explicit wrong requiredChannelShape does not close a differently-shaped session', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// Default source supports mono+stereo; open as mono so the session is keyed by 'mono'.
await registerSource(api, { sourceId: 'shaped-raw', logicalSourceKey: 'test:shaped' });
@@ -472,7 +472,7 @@ test('close-source with an explicit wrong requiredChannelShape does not close a
// An explicit, wrong shape must NOT fall back and close the mono session.
const wrongShape = await api.dispatch({ capability: 'audio-input', command: 'close-source', source: 'note_detect', payload: { requesterId: 'note_detect', logicalSourceKey: 'test:shaped', requiredChannelShape: 'stereo' } });
assert.equal(wrongShape.outcome, 'no-handler');
assert.equal(window.slopsmith.audioSession.snapshot().domains['audio-input'].totalOpenSessions, 1);
assert.equal(window.feedBack.audioSession.snapshot().domains['audio-input'].totalOpenSessions, 1);
// The original session still closes via openSessionId.
const right = await api.dispatch({ capability: 'audio-input', command: 'close-source', source: 'note_detect', payload: { requesterId: 'note_detect', openSessionId: open.payload.openSessionId } });
@@ -481,7 +481,7 @@ test('close-source with an explicit wrong requiredChannelShape does not close a
test('open-source rejects a non-selected duplicate sharing the logical key, addressed by sourceId', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// Native + compatibility-backed duplicate share one logical key; the native source wins.
await registerSource(api, { sourceId: 'native-raw', logicalSourceKey: 'dup:key', providerId: 'native_provider' });
@@ -500,7 +500,7 @@ test('open-source rejects a non-selected duplicate sharing the logical key, addr
test('inspect resolves a raw sourceId to its source via the stable logical key', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'inspect-raw-id', logicalSourceKey: 'test:inspectable' });
@@ -516,7 +516,7 @@ test('inspect resolves a raw sourceId to its source via the stable logical key',
test('inspect by a shared logical key returns the native winner, not a suppressed duplicate', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// Register the compatibility duplicate FIRST so a naive logicalSourceKey-only match would pick it.
await registerSource(api, { sourceId: 'compat-first', logicalSourceKey: 'dup:inspect', providerId: 'compat_p', compatibilitySource: 'legacy' });
@@ -531,7 +531,7 @@ test('inspect by a shared logical key returns the native winner, not a suppresse
test('enumerate preserves a provider-supplied safeLabel through redaction', async () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
const provider = makeInputProvider({
providerId: 'labelled',
sourceId: 'labelled-bootstrap',
@@ -551,7 +551,7 @@ test('enumerate preserves a provider-supplied safeLabel through redaction', asyn
test('enumerate returns no-handler when providers exist but none support source.enumerate', async () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.registerInputSource({ sourceId: 'nohandler-raw', logicalSourceKey: 'nh:key', providerId: 'nh_provider', operations: ['source.open'], operationHandlers: { 'source.open': () => ({ outcome: 'handled', status: 'open' }) } });
@@ -561,7 +561,7 @@ test('enumerate returns no-handler when providers exist but none support source.
test('open-source hint mismatch echoes a pseudonymized sourceId hint without leaking the raw id', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'sel-raw', logicalSourceKey: 'test:sel' });
await registerSource(api, { sourceId: 'other-raw', logicalSourceKey: 'test:other' });
@@ -571,13 +571,13 @@ test('open-source hint mismatch echoes a pseudonymized sourceId hint without lea
assert.equal(open.outcome, 'degraded');
assert.match(open.payload.sourceId, /^source-\d+$/);
const encoded = JSON.stringify({ open, snapshot: window.slopsmith.audioSession.snapshot() });
const encoded = JSON.stringify({ open, snapshot: window.feedBack.audioSession.snapshot() });
assert.equal(encoded.includes('other-raw'), false);
});
test('enumerate propagates a provider source.enumerate denial instead of empty success', async () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
const provider = makeInputProvider({
providerId: 'denier',
sourceId: 'denier-bootstrap',
@@ -608,7 +608,7 @@ test('enumerate propagates a provider source.enumerate denial instead of empty s
test('enumerateInputSources returns distinct sourceId pseudonyms across sources', async () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
const provider = makeInputProvider({
providerId: 'multi_provider',
sourceId: 'mp-bootstrap',
@@ -631,7 +631,7 @@ test('enumerateInputSources returns distinct sourceId pseudonyms across sources'
test('open-session ids correlate between openSessions and recentOutcomes in a snapshot', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'corr-raw', logicalSourceKey: 'test:corr' });
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'test:corr' } });
@@ -639,7 +639,7 @@ test('open-session ids correlate between openSessions and recentOutcomes in a sn
assert.equal(open.outcome, 'handled');
const openId = open.payload.openSessionId;
const snap = window.slopsmith.audioSession.snapshot();
const snap = window.feedBack.audioSession.snapshot();
assert.equal(snap.domains['audio-input'].openSessions[0].openSessionId, openId);
const openOutcome = snap.recentOutcomes.find(outcome => outcome.operation === 'open-source' && outcome.status === 'open' && outcome.openSessionId);
assert.ok(openOutcome);
@@ -649,8 +649,8 @@ test('open-session ids correlate between openSessions and recentOutcomes in a sn
test('startSession closes open input sessions from the previous session before replacing it', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
let providerClosed = 0;
await registerSource(api, {
@@ -677,8 +677,8 @@ test('a persisted selected-source key that is not redaction-safe is ignored on r
const window = loadAudioSession();
// Simulate a tampered/mutated localStorage entry.
window.localStorage.setItem('slopsmith.audioInput.selectedLogicalSourceKey', '/Users/me/evil token=zzz999');
const snap = window.slopsmith.audioSession.startSession({ sessionId: 'main:restore-test' });
window.localStorage.setItem('feedBack.audioInput.selectedLogicalSourceKey', '/Users/me/evil token=zzz999');
const snap = window.feedBack.audioSession.startSession({ sessionId: 'main:restore-test' });
assert.equal(snap.domains['audio-input'].selected, null);
const encoded = JSON.stringify(snap);
@@ -688,8 +688,8 @@ test('a persisted selected-source key that is not redaction-safe is ignored on r
test('stopSession closes open input sessions and notifies providers', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
const closedEvents = captureEvents(window, 'audio-input:source-closed');
let providerClosed = 0;
@@ -716,17 +716,17 @@ test('stopSession closes open input sessions and notifies providers', async () =
test('startSession keeps the in-memory selection when persistence has failed, ignoring stale storage', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// Seed storage with a stale key, then make subsequent writes fail.
window.localStorage.setItem('slopsmith.audioInput.selectedLogicalSourceKey', 'stale:old-input');
window.localStorage.setItem('feedBack.audioInput.selectedLogicalSourceKey', 'stale:old-input');
window.localStorage.setItem = () => { throw new Error('quota'); };
await registerSource(api, { sourceId: 'current-raw', logicalSourceKey: 'current:input' });
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'current:input' } });
assert.equal(window.slopsmith.audioSession.snapshot().domains['audio-input'].storageStatus, 'failed');
assert.equal(window.feedBack.audioSession.snapshot().domains['audio-input'].storageStatus, 'failed');
const snap = window.slopsmith.audioSession.startSession({ sessionId: 'main:after-fail' });
const snap = window.feedBack.audioSession.startSession({ sessionId: 'main:after-fail' });
// Must keep the in-memory 'current:input', not revert to the stale storage key.
assert.equal(snap.domains['audio-input'].selected.logicalSourceKey, 'current:input');
@@ -735,20 +735,20 @@ test('startSession keeps the in-memory selection when persistence has failed, ig
test('selected source persistence restore and storage-unavailable fallback are stable', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerSource(api, { sourceId: 'persisted-source', logicalSourceKey: 'persisted:input' });
await api.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'persisted:input' } });
const afterStart = window.slopsmith.audioSession.startSession({ sessionId: 'main:next-song' });
const afterStart = window.feedBack.audioSession.startSession({ sessionId: 'main:next-song' });
assert.equal(afterStart.domains['audio-input'].selected.logicalSourceKey, 'persisted:input');
assert.equal(afterStart.domains['audio-input'].selected.restoreStatus, 'restored');
const noStorageWindow = loadAudioSession();
noStorageWindow.localStorage.setItem = () => { throw new Error('blocked'); };
const noStorageApi = noStorageWindow.slopsmith.capabilities;
const noStorageApi = noStorageWindow.feedBack.capabilities;
await registerSource(noStorageApi, { sourceId: 'session-source', logicalSourceKey: 'session:input' });
await noStorageApi.dispatch({ capability: 'audio-input', command: 'select-source', source: 'user', payload: { logicalSourceKey: 'session:input' } });
const noStorageSnapshot = noStorageWindow.slopsmith.audioSession.startSession({ sessionId: 'main:no-storage-song' });
const noStorageSnapshot = noStorageWindow.feedBack.audioSession.startSession({ sessionId: 'main:no-storage-song' });
assert.equal(noStorageSnapshot.domains['audio-input'].storageStatus, 'failed');
assert.equal(noStorageSnapshot.domains['audio-input'].selected.logicalSourceKey, 'session:input');
});
+16 -16
View File
@@ -4,9 +4,9 @@ const { loadAudioSession } = require('./audio_session_test_harness');
test('audio-mix commands inspect register and unregister participants', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = [];
window.slopsmith.on('audio-mix:participant-registered', event => events.push(event.detail.payload.participantId));
window.feedBack.on('audio-mix:participant-registered', event => events.push(event.detail.payload.participantId));
const registered = await api.dispatch({
capability: 'audio-mix',
@@ -29,12 +29,12 @@ test('audio-mix commands inspect register and unregister participants', async ()
assert.equal(inspected.payload.participants.some(p => p.participantId === 'plugin.delay'), true);
assert.equal(events.includes('plugin.delay'), true);
assert.equal(removed.status, 'applied');
assert.equal(window.slopsmith.audioSession.snapshot().domains['audio-mix'].participants.some(p => p.participantId === 'plugin.delay'), false);
assert.equal(window.feedBack.audioSession.snapshot().domains['audio-mix'].participants.some(p => p.participantId === 'plugin.delay'), false);
});
test('audio-mix registration reports incompatible participants explicitly', async () => {
const window = loadAudioSession();
const result = await window.slopsmith.capabilities.dispatch({
const result = await window.feedBack.capabilities.dispatch({
capability: 'audio-mix',
command: 'register-participant',
source: 'test',
@@ -43,13 +43,13 @@ test('audio-mix registration reports incompatible participants explicitly', asyn
assert.equal(result.status, 'incompatible-version');
assert.equal(result.outcome, 'incompatible-version');
assert.equal(window.slopsmith.audioSession.snapshot().recentOutcomes.at(-1).outcome, 'incompatible-version');
assert.equal(window.feedBack.audioSession.snapshot().recentOutcomes.at(-1).outcome, 'incompatible-version');
});
test('audio-mix lists required participant kinds and commits provider fader values', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
audioSession.startSession({ sessionId: 'main:test-song', songKey: 'test-song', songFormat: 'sloppak' });
let pluginValue = 0.25;
@@ -99,10 +99,10 @@ test('audio-mix lists required participant kinds and commits provider fader valu
test('audio-mix reports invalid unavailable and timed-out fader operations', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = [];
window.slopsmith.on('audio-mix:fader-unavailable', event => events.push(event.detail.payload.participantId));
window.slopsmith.audioSession.startSession({ sessionId: 'main:test-song' });
window.feedBack.on('audio-mix:fader-unavailable', event => events.push(event.detail.payload.participantId));
window.feedBack.audioSession.startSession({ sessionId: 'main:test-song' });
await api.dispatch({
capability: 'audio-mix',
@@ -150,7 +150,7 @@ test('audio-mix reports invalid unavailable and timed-out fader operations', asy
test('audio-mix keeps pre-session participants pending then attaches them on session start', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await api.dispatch({
capability: 'audio-mix',
@@ -166,7 +166,7 @@ test('audio-mix keeps pre-session participants pending then attaches them on ses
},
});
const pending = await api.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
window.slopsmith.audioSession.startSession({ sessionId: 'main:next-song' });
window.feedBack.audioSession.startSession({ sessionId: 'main:next-song' });
const active = await api.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
assert.equal(pending.payload.faders.find(fader => fader.participantId === 'plugin.presession').availability, 'pending');
@@ -175,7 +175,7 @@ test('audio-mix keeps pre-session participants pending then attaches them on ses
test('audio-mix registration is idempotent and song switching keeps known participants without stale route', async () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.startSession({ sessionId: 'main:first-song', songKey: 'first-song' });
audioSession.setRoute({ routeKind: 'stems', availability: 'available' });
@@ -189,11 +189,11 @@ test('audio-mix registration is idempotent and song switching keeps known partic
operations: ['fader.get-value', 'fader.set-value'],
});
}
const beforeStop = await window.slopsmith.capabilities.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
const beforeStop = await window.feedBack.capabilities.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
audioSession.stopSession('song switch');
const stopped = audioSession.snapshot();
audioSession.startSession({ sessionId: 'main:second-song', songKey: 'second-song' });
const afterStart = await window.slopsmith.capabilities.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
const afterStart = await window.feedBack.capabilities.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
assert.equal(beforeStop.payload.faders.filter(fader => fader.participantId === 'plugin.rehydrated').length, 1);
assert.equal(stopped.domains['audio-mix'].route.availability, 'unavailable');
@@ -207,7 +207,7 @@ test('re-registering a mix participant without handlers preserves the existing s
// wiped the fader.set-value handler installed at init — the mixer slider
// then moved visually but never applied the volume (archive and sloppak).
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.startSession({ sessionId: 'main:test-song', songKey: 'test-song', songFormat: 'sloppak' });
const applied = [];
+51 -51
View File
@@ -23,7 +23,7 @@ async function installMonitoring(api, overrides = {}) {
test('audio-monitoring starts and stops through selected provider and source', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const startedEvents = captureEvents(window, 'audio-monitoring:monitoring-started');
const stoppedEvents = captureEvents(window, 'audio-monitoring:monitoring-stopped');
const input = await installInput(api);
@@ -31,7 +31,7 @@ test('audio-monitoring starts and stops through selected provider and source', a
const active = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { requesterId: 'user', authorization: 'user-action', requiredChannelShape: 'mono' } });
const stopped = await api.dispatch({ capability: 'audio-monitoring', command: 'stop', source: 'user', payload: { requesterId: 'user', monitoringId: active.payload.monitoringId } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(active.outcome, 'handled');
assert.equal(active.payload.state, 'active');
@@ -46,7 +46,7 @@ test('audio-monitoring starts and stops through selected provider and source', a
test('audio-monitoring redacts circular provider payloads without overflowing', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const circular = { directMonitor: { state: 'muted' }, latencySummary: { bucket: 'low' } };
circular.self = circular;
await installInput(api);
@@ -57,7 +57,7 @@ test('audio-monitoring redacts circular provider payloads without overflowing',
});
const active = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { requesterId: 'user', authorization: 'user-action', requiredChannelShape: 'mono' } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(active.outcome, 'handled');
assert.equal(snapshot.sessions[0].state, 'active');
@@ -65,12 +65,12 @@ test('audio-monitoring redacts circular provider payloads without overflowing',
test('audio-monitoring snapshots redact session identifiers and source refs', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api);
const active = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { requesterId: 'user', authorization: 'user-action', requiredChannelShape: 'mono' } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
const session = snapshot.sessions[0];
const encoded = JSON.stringify(snapshot);
@@ -83,7 +83,7 @@ test('audio-monitoring snapshots redact session identifiers and source refs', as
test('audio-monitoring reports no provider unavailable degraded denied failed user action and reload boundaries', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const degradedEvents = captureEvents(window, 'audio-monitoring:monitoring-degraded');
const deniedEvents = captureEvents(window, 'audio-monitoring:monitoring-denied');
const unavailableEvents = captureEvents(window, 'audio-monitoring:monitoring-unavailable');
@@ -125,25 +125,25 @@ test('audio-monitoring reports no provider unavailable degraded denied failed us
assert.equal(failedEvents.length >= 1, true);
assert.equal(activeBeforeSwitch.outcome, 'handled');
window.slopsmith.audioSession.startSession({ sessionId: 'main:next-song' });
const afterSongSwitch = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
window.feedBack.audioSession.startSession({ sessionId: 'main:next-song' });
const afterSongSwitch = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(afterSongSwitch.sessions.some(session => session.state === 'active'), true);
const restoredWindow = loadAudioSession({ storage: storageEntries(window) });
const restored = restoredWindow.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const restored = restoredWindow.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(restored.sessions.length, 0);
});
test('audio-monitoring provider registration is idempotent and selected provider is deterministic', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
const legacy = await installMonitoring(api, { providerId: 'legacy_monitor', logicalMonitoringKey: 'shared:monitor', sourceMode: 'compatibility', compatibilitySource: 'legacy.monitor' });
const native = await installMonitoring(api, { providerId: 'native_monitor', logicalMonitoringKey: 'shared:monitor', sourceMode: 'native', safeLabel: 'Native Monitor' });
await api.dispatch({ capability: 'audio-monitoring', command: 'register-provider', source: 'native_monitor', payload: { ...native.provider, availability: 'pending' } });
const listed = await api.dispatch({ capability: 'audio-monitoring', command: 'list-providers', source: 'test' });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(listed.payload.providers.length, 1);
assert.equal(listed.payload.providers[0].providerId, 'native_monitor');
assert.equal(snapshot.providers.some(provider => provider.providerId === 'legacy_monitor' && provider.supersededBy), true);
@@ -160,7 +160,7 @@ test('audio-monitoring provider registration is idempotent and selected provider
test('audio-monitoring shares compatible sessions and stops provider after final requester', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
const monitoring = await installMonitoring(api);
@@ -179,14 +179,14 @@ test('audio-monitoring shares compatible sessions and stops provider after final
const activeAgain = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { requesterId: 'user', authorization: 'user-action', requiredChannelShape: 'mono' } });
await api.dispatch({ capability: 'audio-monitoring', command: 'unregister-provider', source: 'system', payload: { providerId: monitoring.provider.providerId } });
const afterDisappear = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const afterDisappear = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(activeAgain.outcome, 'handled');
assert.equal(afterDisappear.sessions.some(session => session.state === 'orphaned'), true);
});
test('audio-monitoring owner can retry a stop after a transient provider stop failure', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
let stopCalls = 0;
await installMonitoring(api, {
@@ -213,7 +213,7 @@ test('audio-monitoring owner can retry a stop after a transient provider stop fa
test('audio-monitoring surfaces a provider denial reason even when a direct-monitor conflict is present', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, { startResult: { outcome: 'handled', status: 'denied', reason: 'microphone permission blocked' } });
@@ -229,11 +229,11 @@ test('audio-monitoring surfaces a provider denial reason even when a direct-moni
test('audio-monitoring normalizes an unsafe provider id before storing it', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await api.dispatch({ capability: 'audio-monitoring', command: 'register-provider', source: 'evil', payload: { providerId: '/Users/secret token=abcdef0123456789 monitor', logicalMonitoringKey: 'evil:main', operations: ['monitoring.start'], operationHandlers: { 'monitoring.start': () => ({ outcome: 'handled', status: 'active' }) } } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
const provider = snapshot.providers[0];
// The surfaced providerId must be redacted + charset-restricted, never the raw path/token.
@@ -244,7 +244,7 @@ test('audio-monitoring normalizes an unsafe provider id before storing it', asyn
test('audio-monitoring bounds caller-supplied identifiers reflected back in error reasons', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
const evil = '/Users/secret/private token=supersecretvalue123 ' + 'x'.repeat(400);
@@ -266,7 +266,7 @@ test('audio-monitoring bounds caller-supplied identifiers reflected back in erro
test('audio-monitoring attaching to a shared session honors a conflicting direct-monitor requirement', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api);
@@ -284,7 +284,7 @@ test('audio-monitoring attaching to a shared session honors a conflicting direct
test('audio-monitoring start never lets a caller inject raw sourceRef fields', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api);
@@ -302,7 +302,7 @@ test('audio-monitoring start never lets a caller inject raw sourceRef fields', a
test('audio-monitoring start treats a void provider result as failed', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, { providerId: 'void_monitor', logicalMonitoringKey: 'void:main', operationHandlers: { 'monitoring.start': () => undefined } });
@@ -314,14 +314,14 @@ test('audio-monitoring start treats a void provider result as failed', async ()
test('audio-monitoring stopAll requires explicit user action', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api);
const active = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { authorization: 'user-action', requiredChannelShape: 'mono' } });
// A background requester must not be able to tear down everyone's monitoring.
const background = await api.dispatch({ capability: 'audio-monitoring', command: 'stop', source: 'note_detect', payload: { stopAll: true } });
const mid = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const mid = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
// An explicit user action can.
const user = await api.dispatch({ capability: 'audio-monitoring', command: 'stop', source: 'user', payload: { stopAll: true, authorization: 'user-action' } });
@@ -333,7 +333,7 @@ test('audio-monitoring stopAll requires explicit user action', async () => {
test('audio-monitoring stop does not report stopped when the provider reports a terminal status', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, { stopResult: { outcome: 'handled', status: 'failed', reason: 'device fell off the bus' } });
@@ -348,7 +348,7 @@ test('audio-monitoring stop does not report stopped when the provider reports a
test('audio-monitoring stop reports no-owner when the provider has disappeared', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
const monitoring = await installMonitoring(api);
@@ -364,7 +364,7 @@ test('audio-monitoring stop reports no-owner when the provider has disappeared',
test('audio-monitoring stop reports unsupported-command when the provider has no stop operation', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, { providerId: 'nostop_monitor', logicalMonitoringKey: 'nostop:main', operations: ['monitoring.start'] });
@@ -378,7 +378,7 @@ test('audio-monitoring stop reports unsupported-command when the provider has no
test('audio-monitoring events emit redaction-safe sessions', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const startedEvents = captureEvents(window, 'audio-monitoring:monitoring-started');
await installInput(api);
await installMonitoring(api);
@@ -397,7 +397,7 @@ test('audio-monitoring events emit redaction-safe sessions', async () => {
test('audio-monitoring rejects a providerId collision from a different owner', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
const first = makeMonitoringProvider({ providerId: 'shared_id', ownerPluginId: 'plugin_a', logicalMonitoringKey: 'a:main' });
const second = makeMonitoringProvider({ providerId: 'shared_id', ownerPluginId: 'plugin_b', logicalMonitoringKey: 'b:main' });
@@ -408,7 +408,7 @@ test('audio-monitoring rejects a providerId collision from a different owner', a
const sneaky = makeMonitoringProvider({ providerId: 'shared_id', logicalMonitoringKey: 'c:main' });
delete sneaky.provider.ownerPluginId;
const reg3 = await api.dispatch({ capability: 'audio-monitoring', command: 'register-provider', source: 'plugin_c', payload: sneaky.provider });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(reg1.outcome, 'handled');
assert.equal(reg2.outcome, 'failed');
@@ -420,12 +420,12 @@ test('audio-monitoring rejects a providerId collision from a different owner', a
test('audio-monitoring session keeps openInputSessionId verbatim for cross-domain correlation', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api);
const active = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { authorization: 'user-action', requiredChannelShape: 'mono' } });
const session = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'].sessions.at(-1);
const session = window.feedBack.audioSession.snapshot().domains['audio-monitoring'].sessions.at(-1);
assert.equal(active.outcome, 'handled');
assert.match(active.payload.openInputSessionId, /^input-open-\d+$/);
@@ -437,12 +437,12 @@ test('audio-monitoring session keeps openInputSessionId verbatim for cross-domai
test('audio-monitoring status refresh tolerates a void provider result without faking active', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, { availability: 'available', operationHandlers: { 'monitoring.status': () => undefined } });
await api.dispatch({ capability: 'audio-monitoring', command: 'inspect', source: 'user', payload: { includeStatus: true } });
const outcomes = window.slopsmith.audioSession.snapshot().recentOutcomes.filter(entry => entry.domain === 'audio-monitoring' && entry.operation === 'status');
const outcomes = window.feedBack.audioSession.snapshot().recentOutcomes.filter(entry => entry.domain === 'audio-monitoring' && entry.operation === 'status');
// A void status reply is tolerated but must not be recorded as an 'active' session.
assert.equal(outcomes.length >= 1, true);
@@ -451,7 +451,7 @@ test('audio-monitoring status refresh tolerates a void provider result without f
test('audio-monitoring status refresh does not leak the raw device sourceId to providers', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
let statusSourceRef = null;
await installMonitoring(api, { operationHandlers: { 'monitoring.status': (request) => { statusSourceRef = request.sourceRef; return { outcome: 'handled', status: 'active' }; } } });
@@ -466,7 +466,7 @@ test('audio-monitoring status refresh does not leak the raw device sourceId to p
test('audio-monitoring status refresh does not downgrade availability on a non-state provider reply', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, { availability: 'available', statusResult: { outcome: 'no-handler' } });
@@ -480,7 +480,7 @@ test('audio-monitoring status refresh does not downgrade availability on a non-s
test('audio-monitoring re-keys active sessions on a preference change so requesters still attach', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api);
@@ -499,13 +499,13 @@ test('audio-monitoring re-keys active sessions on a preference change so request
test('audio-monitoring does not assume direct-monitor applied without provider confirmation', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, { directMonitorResult: { outcome: 'handled' } });
await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { authorization: 'user-action', requiredChannelShape: 'mono' } });
const changed = await api.dispatch({ capability: 'audio-monitoring', command: 'set-direct-monitor', source: 'user', payload: { state: 'unmuted' } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(changed.outcome, 'handled');
// The provider handled the request but did not confirm application, so applied must stay false.
@@ -515,7 +515,7 @@ test('audio-monitoring does not assume direct-monitor applied without provider c
test('audio-monitoring direct-monitor summary preserves an unavailable control state', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, {
directMonitorResult: { outcome: 'handled', summary: { directMonitor: { state: 'unmuted', control: 'unavailable', applied: false, reason: 'temporarily unavailable' } } },
@@ -523,7 +523,7 @@ test('audio-monitoring direct-monitor summary preserves an unavailable control s
await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { authorization: 'user-action', requiredChannelShape: 'mono' } });
const changed = await api.dispatch({ capability: 'audio-monitoring', command: 'set-direct-monitor', source: 'user', payload: { state: 'unmuted' } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
// A session that reports control 'unavailable' must not be collapsed to 'unknown' in the rollup.
assert.equal(changed.payload.directMonitor.control, 'unavailable');
@@ -533,7 +533,7 @@ test('audio-monitoring direct-monitor summary preserves an unavailable control s
test('audio-monitoring direct-monitor summary reflects a provider that handles but does not apply', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
await installMonitoring(api, {
directMonitorResult: { outcome: 'handled', summary: { directMonitor: { state: 'unmuted', control: 'supported', applied: false, reason: 'hardware busy' } } },
@@ -541,7 +541,7 @@ test('audio-monitoring direct-monitor summary reflects a provider that handles b
await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { authorization: 'user-action', requiredChannelShape: 'mono' } });
const changed = await api.dispatch({ capability: 'audio-monitoring', command: 'set-direct-monitor', source: 'user', payload: { state: 'unmuted' } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(changed.outcome, 'handled');
// Provider handled the request but reported it was not applied; the domain summary must not
@@ -556,13 +556,13 @@ test('audio-monitoring direct-monitor summary reflects a provider that handles b
test('audio-monitoring direct monitor preference is user authoritative', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await installInput(api);
const monitoring = await installMonitoring(api, { directMonitor: { state: 'muted', control: 'supported', preference: 'muted', applied: true } });
const changed = await api.dispatch({ capability: 'audio-monitoring', command: 'set-direct-monitor', source: 'user', payload: { state: 'unmuted' } });
const startConflict = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'note_detect', payload: { requesterId: 'note_detect', authorization: 'user-action', directMonitorRequirement: 'muted', requiredChannelShape: 'mono' } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(changed.outcome, 'handled');
assert.equal(startConflict.outcome, 'degraded');
@@ -574,14 +574,14 @@ test('audio-monitoring direct monitor preference is user authoritative', async (
test('audio-monitoring direct monitor unsupported control remains diagnosable', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const changedEvents = captureEvents(window, 'audio-monitoring:direct-monitor-changed');
await installInput(api);
await installMonitoring(api, { providerId: 'noctl_monitor', logicalMonitoringKey: 'noctl:monitor', operations: ['monitoring.start', 'monitoring.stop'] });
const active = await api.dispatch({ capability: 'audio-monitoring', command: 'start', source: 'user', payload: { providerId: 'noctl_monitor', requesterId: 'user', authorization: 'user-action', requiredChannelShape: 'mono' } });
const changed = await api.dispatch({ capability: 'audio-monitoring', command: 'set-direct-monitor', source: 'user', payload: { state: 'unmuted' } });
const snapshot = window.slopsmith.audioSession.snapshot().domains['audio-monitoring'];
const snapshot = window.feedBack.audioSession.snapshot().domains['audio-monitoring'];
assert.equal(active.outcome, 'handled');
assert.equal(changed.outcome, 'unsupported-command');
@@ -592,7 +592,7 @@ test('audio-monitoring direct monitor unsupported control remains diagnosable',
test('audio-monitoring distinguishes failure outcomes and prompt-free status inspection', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const input = await installInput(api, { channelSummary: { channelCount: 1, channelShape: 'mono', supports: ['mono'] } });
const monitoring = await installMonitoring(api);
@@ -621,7 +621,7 @@ test('audio-monitoring distinguishes failure outcomes and prompt-free status ins
assert.equal(monitoring.calls.some(call => call[0] === 'monitoring.status'), true);
assert.equal(monitoring.calls.some(call => call[0] === 'monitoring.start'), false);
const outcomes = window.slopsmith.audioSession.snapshot().recentOutcomes.filter(entry => entry.domain === 'audio-monitoring');
const outcomes = window.feedBack.audioSession.snapshot().recentOutcomes.filter(entry => entry.domain === 'audio-monitoring');
for (const outcome of ['handled', 'unsupported-command', 'incompatible', 'incompatible-version', 'no-handler']) {
assert.equal(outcomes.some(entry => entry.outcome === outcome), true, `missing outcome ${outcome}`);
}
@@ -629,12 +629,12 @@ test('audio-monitoring distinguishes failure outcomes and prompt-free status ins
test('audio-session diagnostics remain bounded during frequent input monitoring updates', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
for (let index = 0; index < 80; index += 1) {
await api.dispatch({ capability: 'audio-input', command: 'register-source', source: 'bench', payload: { sourceId: `source-${index}`, logicalSourceKey: `bench:source:${index}`, providerId: 'bench', safeLabel: `/Users/example/private-${index}`, channelSummary: { channelCount: 1, channelShape: 'mono', supports: ['mono'] }, operations: ['source.open'], operationHandlers: { 'source.open': () => ({ outcome: 'handled' }) } } });
await api.dispatch({ capability: 'audio-monitoring', command: 'register-provider', source: 'bench', payload: { providerId: `monitor-${index}`, logicalMonitoringKey: `bench:monitor:${index}`, operations: ['monitoring.start'], operationHandlers: { 'monitoring.start': () => ({ outcome: 'handled', status: 'active' }) } } });
}
const snapshot = window.slopsmith.audioSession.snapshot();
const snapshot = window.feedBack.audioSession.snapshot();
const encoded = JSON.stringify(snapshot);
assert.equal(snapshot.recentOutcomes.length <= 100, true);
assert.equal(encoded.length < 96 * 1024, true);
+17 -17
View File
@@ -4,7 +4,7 @@ const { loadAudioSession, runBrowserScript, installMixerDom } = require('./audio
test('audio session records route transitions without blocking callers', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
const html5 = audioSession.setRoute({ routeKind: 'html5', availability: 'available', selectedByUser: true });
const stems = audioSession.setRoute({ routeKind: 'stems', availability: 'available', selectedByUser: true });
@@ -24,10 +24,10 @@ test('legacy song fader registration is bridged into audio-mix participants and
window.localStorage.setItem('volume', '65');
runBrowserScript(window, 'static/audio-mixer.js');
assert.equal(typeof window.slopsmith.audio.applySongVolume, 'function');
assert.equal(typeof window.feedBack.audio.applySongVolume, 'function');
await window.slopsmith.audio.applySongVolume(72);
const snapshot = window.slopsmith.audioSession.snapshot();
await window.feedBack.audio.applySongVolume(72);
const snapshot = window.feedBack.audioSession.snapshot();
const songParticipant = snapshot.domains['audio-mix'].participants.find(p => p.participantId === 'core.song');
assert.equal(audio.volume, 0.72);
@@ -43,27 +43,27 @@ test('song volume persists through html5 stems and desktop routes', async () =>
const stemsCalls = [];
const desktopCalls = [];
window.localStorage.setItem('volume', '41');
window.slopsmith.stems = { setMasterVolume(value) { stemsCalls.push(value); return Promise.resolve(); } };
window.slopsmithDesktop = { audio: { setGain(name, value) { desktopCalls.push([name, value]); return Promise.resolve(); } } };
window.feedBack.stems = { setMasterVolume(value) { stemsCalls.push(value); return Promise.resolve(); } };
window.feedBackDesktop = { audio: { setGain(name, value) { desktopCalls.push([name, value]); return Promise.resolve(); } } };
runBrowserScript(window, 'static/audio-mixer.js');
assert.equal(window.slopsmith.audio.readSongVolume(), 41);
assert.equal(window.feedBack.audio.readSongVolume(), 41);
await window.slopsmith.audio.applySongVolume(55);
await window.feedBack.audio.applySongVolume(55);
assert.equal(audio.volume, 0.55);
assert.equal(stemsCalls.at(-1), 0.55);
assert.equal(window.slopsmith.audioSession.snapshot().domains['audio-mix'].route.routeKind, 'stems');
assert.equal(window.feedBack.audioSession.snapshot().domains['audio-mix'].route.routeKind, 'stems');
window._juceMode = true;
delete window.slopsmith.stems;
await window.slopsmith.audio.applySongVolume(66);
delete window.feedBack.stems;
await window.feedBack.audio.applySongVolume(66);
assert.deepEqual(desktopCalls.at(-1), ['backing', 0.66]);
assert.equal(window.slopsmith.audioSession.snapshot().domains['audio-mix'].route.routeKind, 'juce');
assert.equal(window.feedBack.audioSession.snapshot().domains['audio-mix'].route.routeKind, 'juce');
});
test('stems provider ownership remains separate from audio-mix stem participation', async () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.startSession({ sessionId: 'main:stems-song' });
audioSession.registerStemOwner({ ownerId: 'stems_plugin', stemIds: ['guitar', 'bass'], availability: 'available' });
audioSession.registerMixParticipant({
@@ -76,8 +76,8 @@ test('stems provider ownership remains separate from audio-mix stem participatio
operations: ['fader.get-value', 'fader.set-value'],
});
const stemsInspect = await window.slopsmith.capabilities.dispatch({ capability: 'stems', command: 'inspect', source: 'test' });
const mixInspect = await window.slopsmith.capabilities.dispatch({ capability: 'audio-mix', command: 'inspect', source: 'test' });
const stemsInspect = await window.feedBack.capabilities.dispatch({ capability: 'stems', command: 'inspect', source: 'test' });
const mixInspect = await window.feedBack.capabilities.dispatch({ capability: 'audio-mix', command: 'inspect', source: 'test' });
assert.equal(stemsInspect.payload.owner.ownerId, 'stems_plugin');
assert.equal(mixInspect.payload.faders.some(fader => fader.kind === 'stem' && fader.ownerPluginId === 'stems_plugin'), true);
@@ -85,8 +85,8 @@ test('stems provider ownership remains separate from audio-mix stem participatio
test('audio-input selection and registered providers survive song session switches without live sessions', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
audioSession.startSession({ sessionId: 'main:first-song', songKey: 'first-song.sloppak', songFormat: 'sloppak' });
await api.dispatch({
+6 -6
View File
@@ -4,8 +4,8 @@ const { loadAudioSession } = require('./audio_session_test_harness');
test('stem owner claim restore orphan and manual override lifecycle is recorded', async () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
const noOwner = await api.dispatch({ capability: 'stems', command: 'mute', source: 'nam_tone', payload: { stemIds: ['guitar'] } });
assert.equal(noOwner.outcome, 'no-owner');
@@ -26,8 +26,8 @@ test('stem owner claim restore orphan and manual override lifecycle is recorded'
test('audio session coordinates stems without replacing the active stems owner', () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
let stemsPipeline = api.inspect('stems');
let coordinator = stemsPipeline.participants.find(entry => entry.pluginId === 'core.audio.session');
@@ -46,8 +46,8 @@ test('audio session coordinates stems without replacing the active stems owner',
test('stem automation claims become orphaned when owner disappears', () => {
const window = loadAudioSession();
const api = window.slopsmith.capabilities;
const audioSession = window.slopsmith.audioSession;
const api = window.feedBack.capabilities;
const audioSession = window.feedBack.audioSession;
const unavailableEvents = [];
api.subscribe('stems:owner-unavailable', detail => unavailableEvents.push(detail));
+4 -4
View File
@@ -6,13 +6,13 @@ test('Stems master-volume compatibility bridge hit is attributed through audio s
const window = loadAudioSession();
const calls = [];
installMixerDom(window);
window.slopsmith.stems = { setMasterVolume(value) { calls.push(value); } };
window.feedBack.stems = { setMasterVolume(value) { calls.push(value); } };
runBrowserScript(window, 'static/audio-mixer.js');
await window.slopsmith.audio.applySongVolume(50);
await window.feedBack.audio.applySongVolume(50);
const snapshot = window.slopsmith.audioSession.snapshot();
const snapshot = window.feedBack.audioSession.snapshot();
assert.deepEqual(calls, [0.5]);
assert.equal(snapshot.domains.stems.bridges.some(bridge => bridge.bridgeId === 'stems.master-volume'), true);
assert.equal(window.slopsmith.capabilities.snapshotDiagnostics().compatibilityShims.some(shim => shim.shimId === 'stems.master-volume' && shim.hitCount >= 1), true);
assert.equal(window.feedBack.capabilities.snapshotDiagnostics().compatibilityShims.some(shim => shim.shimId === 'stems.master-volume' && shim.hitCount >= 1), true);
});
+3 -3
View File
@@ -23,12 +23,12 @@ function runBrowserScript(window, relativePath) {
function captureEvents(window, eventName) {
const events = [];
window.slopsmith.on(eventName, event => events.push(event.detail));
window.feedBack.on(eventName, event => events.push(event.detail));
return events;
}
function diagnosticsSnapshot(window) {
return window.slopsmith.audioSession.snapshot();
return window.feedBack.audioSession.snapshot();
}
function storageEntries(window) {
@@ -175,7 +175,7 @@ function installMixerDom(window) {
function loadAudioMixer(window) {
runBrowserScript(window, path.relative(ROOT, AUDIO_MIXER_JS));
return window.slopsmith.audio;
return window.feedBack.audio;
}
module.exports = {
+5 -5
View File
@@ -51,7 +51,7 @@ function runOverlay(nodes) {
function runResolve({ override = null, screens = [], active = null } = {}) {
const fnSrc = extractFunction(SRC, 'function _resolvePlayerOrigin(');
const sandbox = {
window: { slopsmith: { _nextReturnScreen: override } },
window: { feedBack: { _nextReturnScreen: override } },
document: {
getElementById: (id) => (screens.includes(id) ? { id } : null),
querySelector: () => (active ? { id: active } : null),
@@ -59,16 +59,16 @@ function runResolve({ override = null, screens = [], active = null } = {}) {
};
vm.createContext(sandbox);
vm.runInContext(fnSrc + '\nglobalThis.__r = _resolvePlayerOrigin();', sandbox);
return { result: sandbox.__r, override: sandbox.window.slopsmith._nextReturnScreen };
return { result: sandbox.__r, override: sandbox.window.feedBack._nextReturnScreen };
}
// holdAutoExit() + _clearAutoExit() share module state; assemble them in one
// sandbox to exercise the generation guard on the returned release handle.
function buildHoldSandbox() {
const clearSrc = extractFunction(SRC, 'function _clearAutoExit(');
const holdSrc = extractFunction(SRC, 'window.slopsmith.holdAutoExit = function ()');
const holdSrc = extractFunction(SRC, 'window.feedBack.holdAutoExit = function ()');
const sandbox = { __closeCount: 0 };
sandbox.window = { slopsmith: {}, closeCurrentSong: () => { sandbox.__closeCount++; } };
sandbox.window = { feedBack: {}, closeCurrentSong: () => { sandbox.__closeCount++; } };
vm.createContext(sandbox);
vm.runInContext(`
var _autoExitTimer = null;
@@ -78,7 +78,7 @@ function buildHoldSandbox() {
${clearSrc}
${holdSrc}
globalThis.__clearAutoExit = _clearAutoExit;
globalThis.__hold = window.slopsmith.holdAutoExit;
globalThis.__hold = window.feedBack.holdAutoExit;
`, sandbox);
return sandbox;
}
+11 -11
View File
@@ -31,13 +31,13 @@ function getCaseBlock(src, label) {
test('beats:loaded emit is wired into the WS beats case', () => {
// Source-level guard: catch a future contributor removing the emit
// (regression) or replacing window.slopsmith.emit with something
// (regression) or replacing window.feedBack.emit with something
// else (intentional refactor — this test then needs updating).
const src = fs.readFileSync(HIGHWAY_JS, 'utf8');
const block = getCaseBlock(src, 'beats');
assert.match(
block,
/window\.slopsmith\.emit\(\s*['"]beats:loaded['"]/,
/window\.feedBack\.emit\(\s*['"]beats:loaded['"]/,
'beats case must emit beats:loaded',
);
assert.match(
@@ -47,33 +47,33 @@ test('beats:loaded emit is wired into the WS beats case', () => {
);
});
test('beats:loaded emit is guarded against missing window.slopsmith', () => {
// The WS handler can fire before the slopsmith namespace is defined
test('beats:loaded emit is guarded against missing window.feedBack', () => {
// The WS handler can fire before the feedBack namespace is defined
// (early in app boot). The emit must be guarded so a missing
// namespace doesn't throw inside the WS message dispatcher.
// Looser pattern accepts any guard that reads window.slopsmith
// Looser pattern accepts any guard that reads window.feedBack
// (including typeof checks and combined conditions) rather than
// mandating the exact `if (window.slopsmith)` form.
// mandating the exact `if (window.feedBack)` form.
const src = fs.readFileSync(HIGHWAY_JS, 'utf8');
const block = getCaseBlock(src, 'beats');
assert.match(
block,
/if\s*\(\s*[^)]*window\.slopsmith\b[^)]*\)/,
'beats:loaded emit must be guarded against a missing window.slopsmith',
/if\s*\(\s*[^)]*window\.feedBack\b[^)]*\)/,
'beats:loaded emit must be guarded against a missing window.feedBack',
);
});
test('beats:loaded guard verifies emit is callable (typeof check)', () => {
// A partially-attached namespace (window.slopsmith exists but emit
// A partially-attached namespace (window.feedBack exists but emit
// isn't a function yet during early boot) would throw without this
// extra check. A truthy check (`window.slopsmith.emit && ...`) lets
// extra check. A truthy check (`window.feedBack.emit && ...`) lets
// non-callable values pass; require an explicit typeof === 'function'
// check so the guard catches that real edge.
const src = fs.readFileSync(HIGHWAY_JS, 'utf8');
const block = getCaseBlock(src, 'beats');
assert.match(
block,
/typeof\s+window\.slopsmith\.emit\s*===\s*['"]function['"]/,
/typeof\s+window\.feedBack\.emit\s*===\s*['"]function['"]/,
'guard must use typeof === \'function\' (not just truthy) to confirm emit is callable',
);
});
+2 -2
View File
@@ -24,7 +24,7 @@ function registerStemsOwner(api, calls) {
test('claim dispatch release records lifecycle and removes active claim', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
registerStemsOwner(api, calls);
@@ -48,7 +48,7 @@ test('claim dispatch release records lifecycle and removes active claim', async
test('manual override is terminal for matching active claim target', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
registerStemsOwner(api, calls);
api.claim({ capability: 'stems', claimId: 'nam.amp-active', requester: 'nam_tone', target: { kind: 'guitar' } });
+9 -9
View File
@@ -4,7 +4,7 @@ const { loadCapabilities } = require('./capabilities_test_harness');
test('diagnostics snapshots redact paths and trim recent decisions under 64 KB', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipant('owner', {
stems: {
roles: ['owner'],
@@ -26,7 +26,7 @@ test('diagnostics snapshots redact paths and trim recent decisions under 64 KB',
test('compatibility shim hit counts and attribution are exported', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerCompatibilityShim({ shimId: 'stems:legacy-window', source: 'stems', capability: 'stems', legacySurface: 'window._stemsState', status: 'active', reason: 'legacy global bridge' });
api.registerCompatibilityShim({ shimId: 'stems:legacy-window', source: 'stems', capability: 'stems', legacySurface: 'window._stemsState', status: 'used', used: true });
api.registerCompatibilityShim({ shimId: 'stems:legacy-window', source: 'stems', capability: 'stems', legacySurface: 'window._stemsState', status: 'used', hit: true });
@@ -39,7 +39,7 @@ test('compatibility shim hit counts and attribution are exported', () => {
test('diagnostics export expected compatibility shim surfaces', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const expected = api.snapshotDiagnostics().expectedCompatibilityShims;
assert.ok(Array.isArray(expected));
@@ -52,7 +52,7 @@ test('diagnostics export expected compatibility shim surfaces', () => {
test('diagnostics include active playback but exclude deferred and documentation-only future core domains', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipant('playback_probe', {
playback: {
roles: ['provider'],
@@ -95,7 +95,7 @@ test('diagnostics include active playback but exclude deferred and documentation
test('server-reported shim hit counts are not inflated by refresh registration', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const shim = {
shimId: 'stems:legacy-window',
source: 'stems',
@@ -114,7 +114,7 @@ test('server-reported shim hit counts are not inflated by refresh registration',
test('recordLegacyHit preserves non-library shim attribution', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const first = api.recordLegacyHit({
capability: 'stems',
legacySurface: 'window._stemsState',
@@ -136,7 +136,7 @@ test('recordLegacyHit preserves non-library shim attribution', () => {
test('recordLegacyHit counts runtime use and preserves used status across active refreshes', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const first = api.recordLegacyHit({
capability: 'stems',
legacySurface: 'window._stemsState',
@@ -168,9 +168,9 @@ test('recordLegacyHit counts runtime use and preserves used status across active
test('capability diagnostics emit a changed event after runtime updates', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
let changes = 0;
window.addEventListener('slopsmith:capabilities:changed', () => { changes += 1; });
window.addEventListener('feedBack:capabilities:changed', () => { changes += 1; });
api.recordLegacyHit({
capability: 'stems',
+11 -11
View File
@@ -2,26 +2,26 @@ const { test } = require('node:test');
const assert = require('node:assert/strict');
const { loadCapabilities } = require('./capabilities_test_harness');
test('capability runtime installs early slopsmith event bus', () => {
test('capability runtime installs early feedBack event bus', () => {
const window = loadCapabilities();
const events = [];
const onceEvents = [];
window.slopsmith.on('screen:changed', event => events.push(event.detail));
window.slopsmith.on('song:ready', event => onceEvents.push(event.detail), { once: true });
window.feedBack.on('screen:changed', event => events.push(event.detail));
window.feedBack.on('song:ready', event => onceEvents.push(event.detail), { once: true });
window.slopsmith.emit('screen:changed', { id: 'home' });
window.slopsmith.emit('song:ready', { title: 'First' });
window.slopsmith.emit('song:ready', { title: 'Second' });
window.feedBack.emit('screen:changed', { id: 'home' });
window.feedBack.emit('song:ready', { title: 'First' });
window.feedBack.emit('song:ready', { title: 'Second' });
assert.deepEqual(events, [{ id: 'home' }]);
assert.deepEqual(onceEvents, [{ title: 'First' }]);
assert.equal(typeof window.slopsmith.off, 'function');
assert.equal(typeof window.feedBack.off, 'function');
});
test('unregistering requester releases its claims', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipant('stems', { stems: { roles: ['owner'], commands: ['mute'], handlers: { mute: () => ({ outcome: 'handled' }) }, runtime: true } });
api.registerParticipant('nam_tone', { stems: { roles: ['requester'], commands: ['mute'], runtime: true } });
api.claim({ capability: 'stems', claimId: 'nam.amp-active', requester: 'nam_tone' });
@@ -34,7 +34,7 @@ test('unregistering requester releases its claims', () => {
test('unregistering owner or handler orphans claim and prevents dispatch', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipant('stems', { stems: { roles: ['owner'], commands: ['mute'], handlers: { mute: () => ({ outcome: 'handled' }) }, runtime: true } });
api.claim({ capability: 'stems', claimId: 'nam.amp-active', requester: 'nam_tone' });
api.unregisterParticipant('stems');
@@ -51,7 +51,7 @@ test('unregistering owner or handler orphans claim and prevents dispatch', async
test('runtime enable disable is lifecycle state rather than user override', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipant('plugin_a', { stems: { roles: ['provider'], commands: ['inspect'], runtime: true } });
const disabled = api.setParticipantEnabled('plugin_a', 'stems', false, { requester: 'test' });
const enabled = api.setParticipantEnabled('plugin_a', 'stems', true, { requester: 'test' });
@@ -65,7 +65,7 @@ test('runtime enable disable is lifecycle state rather than user override', () =
test('failed no-op registrations do not block reload and rehydrate replacement', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipant('', { stems: { roles: ['owner'] } });
api.registerParticipant('stems', { stems: { roles: ['owner'], commands: ['mute'], handlers: { mute: () => ({ outcome: 'handled', payload: { generation: 1 } }) }, runtime: true } });
api.unregisterParticipant('stems');
@@ -12,7 +12,7 @@ function fixture(name) {
test('manifest participants are visible before runtime handlers register', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipants([fixture('valid_owner_provider.json'), fixture('valid_requester_observer.json')]);
const stems = api.inspect('stems');
@@ -27,7 +27,7 @@ test('manifest participants are visible before runtime handlers register', () =>
test('runtime registration refreshes an existing manifest participant without duplicating it', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipants([fixture('valid_owner_provider.json')]);
api.registerParticipant('stems', {
capabilities: {
@@ -74,7 +74,7 @@ test('native library provider capability coordinates providers', async () => {
}
throw new Error(`unexpected fetch ${text}`);
};
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = [];
api.subscribe('library:source-changed', event => events.push(event));
@@ -101,7 +101,7 @@ test('native library provider capability coordinates providers', async () => {
assert.equal(selected.payload.current, 'remote:frodo');
assert.equal(events.length, 1);
assert.equal(events[0].payload.to, 'remote:frodo');
assert.equal(window.slopsmith.libraryProviders.snapshot().current, 'remote:frodo');
assert.equal(window.feedBack.libraryProviders.snapshot().current, 'remote:frodo');
const syncResult = await api.command('library', 'sync-song', {
requester: 'test',
@@ -123,7 +123,7 @@ test('removed library providers are unregistered as library participants on refr
if (String(url) === '/api/library/providers') return { ok: true, json: async () => ({ providers: providerSet }) };
throw new Error(`unexpected fetch ${url}`);
};
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await api.command('library', 'refresh-providers', { requester: 'test' });
let ids = api.inspect('library').participants.map(p => p.pluginId);
@@ -160,7 +160,7 @@ test('a plugin with non-provider library roles is not wiped when its provider di
if (String(url) === '/api/library/providers') return { ok: true, json: async () => ({ providers: providerSet }) };
throw new Error(`unexpected fetch ${url}`);
};
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// The same plugin also declares an observer library role via its manifest —
// legitimate participation the provider-cleanup path must not delete.
@@ -187,7 +187,7 @@ test('a plugin with non-provider library roles is not wiped when its provider di
test('runtime domain library declarations appear as library participants', () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const touched = api.registerParticipants([{
id: 'remote-library-client',
+1 -1
View File
@@ -4,7 +4,7 @@ const { loadCapabilities } = require('./capabilities_test_harness');
test('multi-provider participants use deterministic order without duplicate-owner conflict', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
api.registerParticipant('provider_b', { 'shared-viz': { roles: ['owner', 'provider'], ownership: 'multi-provider', commands: ['register-provider'], order: { after: ['provider_a'] }, handlers: { 'register-provider': () => { calls.push('b'); return { outcome: 'passed' }; } }, runtime: true } });
api.registerParticipant('provider_a', { 'shared-viz': { roles: ['owner', 'provider'], ownership: 'multi-provider', commands: ['register-provider'], handlers: { 'register-provider': () => { calls.push('a'); return { outcome: 'handled' }; } }, runtime: true } });
+2 -2
View File
@@ -4,7 +4,7 @@ const { loadCapabilities } = require('./capabilities_test_harness');
test('exclusive duplicate owners report conflict and degrade dispatch', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
api.registerParticipant('owner_a', { stems: { roles: ['owner'], commands: ['mute'], handlers: { mute: () => ({ outcome: 'handled' }) }, runtime: true } });
api.registerParticipant('owner_b', { stems: { roles: ['owner'], commands: ['mute'], handlers: { mute: () => ({ outcome: 'handled' }) }, runtime: true } });
@@ -18,7 +18,7 @@ test('exclusive duplicate owners report conflict and degrade dispatch', async ()
test('no-owner no-handler and unsupported-command outcomes are explicit', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
let result = await api.dispatch({ capability: 'missing-domain', command: 'mute', source: 'test' });
assert.equal(result.status, 'no-owner');
assert.equal(result.outcome, 'no-owner');
+1 -1
View File
@@ -45,7 +45,7 @@ function createWindow(options = {}) {
document: {
getElementById(id) { return elements.get(id) || null; },
},
slopsmith: {
feedBack: {
emit(type, detail) {
window.dispatchEvent(new CustomEvent(type, { detail }));
},
+1 -1
View File
@@ -6,7 +6,7 @@ const { loadCapabilities, ROOT } = require('./capabilities_test_harness');
test('unsupported capability-pipelines versions are incompatible and do not execute handlers', async () => {
const window = loadCapabilities();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const fixture = JSON.parse(fs.readFileSync(path.join(ROOT, 'tests', 'fixtures', 'plugin_capabilities', 'unsupported_capability_version.json'), 'utf8'));
let invoked = false;
fixture.capabilities.stems.handlers = { mute: () => { invoked = true; return { outcome: 'handled' }; } };
+39 -39
View File
@@ -58,7 +58,7 @@ function loadInspector(snapshot, options = {}) {
for (const handler of (listeners.get(event.type) || []).slice()) handler(event);
return true;
},
slopsmith: {
feedBack: {
capabilities: {
snapshotDiagnostics: () => (typeof snapshot === 'function' ? snapshot() : snapshot),
},
@@ -90,7 +90,7 @@ test('capability inspector renders playback session route loop bridges and outco
expectedCompatibilityShims: [],
};
const playbackSnapshot = {
schema: 'slopsmith.playback.diagnostics.v1',
schema: 'feedBack.playback.diagnostics.v1',
state: {
sessionId: 'playback-1',
state: 'playing',
@@ -158,8 +158,8 @@ test('capability inspector renders shims inside their capability domain', () =>
lastHitAt: '2026-05-24T00:00:00.000Z',
},
{
shimId: 'runtime:library:refresh:window.slopsmith.libraryProviders.refresh',
source: 'window.slopsmith.libraryProviders.refresh',
shimId: 'runtime:library:refresh:window.feedBack.libraryProviders.refresh',
source: 'window.feedBack.libraryProviders.refresh',
capability: 'library',
legacySurface: 'refresh',
status: 'used',
@@ -243,8 +243,8 @@ test('capability inspector renders shims inside their capability domain', () =>
assert.doesNotMatch(content, /id="capability-domain-library-graph-frame"/);
assert.doesNotMatch(content, /Domain summary/);
window.__slopsmithCapabilityInspector.expandedDomains = { library: true, playback: true, 'custom.practice': true };
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.expandedDomains = { library: true, playback: true, 'custom.practice': true };
window.__feedBackCapabilityInspector.render();
const expandedContent = elements.get('capability-inspector-content').innerHTML;
assert.match(expandedContent, /aria-expanded="true"/);
@@ -307,7 +307,7 @@ test('capability inspector renders shims inside their capability domain', () =>
assert.doesNotMatch(expandedContent, /data-copy-surface=/);
filterElement.value = 'playback';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
const selectedContent = elements.get('capability-inspector-content').innerHTML;
assert.match(selectedContent, /data-domain-graph="playback"/);
@@ -400,11 +400,11 @@ test('capability inspector renders shims inside their capability domain', () =>
assert.ok(selectedContent.indexOf('data-legend-icon="operation"') < selectedContent.indexOf('data-legend-icon="command"'));
assert.doesNotMatch(selectedContent, /Domain group/);
window.__slopsmithCapabilityInspector.graphCollapsedGroups = {
window.__feedBackCapabilityInspector.graphCollapsedGroups = {
'playback|provider|all|command': true,
'playback|participant|2|event': true,
};
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
const collapsedGroupContent = elements.get('capability-inspector-content').innerHTML;
assert.match(collapsedGroupContent, /data-graph-group-collapsed="true"/);
assert.match(collapsedGroupContent, /data-graph-capability-port="group:command"/);
@@ -416,12 +416,12 @@ test('capability inspector renders shims inside their capability domain', () =>
assert.doesNotMatch(collapsedParticipantButton, /<svg class="h-3\.5 w-3\.5/);
assert.doesNotMatch(collapsedParticipantButton, /<path d="m9 6 6 6-6 6"\/>/);
window.__slopsmithCapabilityInspector.graphCollapsedGroups = {};
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.graphCollapsedGroups = {};
window.__feedBackCapabilityInspector.render();
filterElement.value = 'library';
window.__slopsmithCapabilityInspector.domainGraphFilter = 'operations';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.domainGraphFilter = 'operations';
window.__feedBackCapabilityInspector.render();
const operationsOnlyContent = elements.get('capability-inspector-content').innerHTML;
assert.match(operationsOnlyContent, /aria-pressed="true" class="rounded border px-2 py-1 transition bg-purple-500\/40 text-white border-purple-400\/40">Operations/);
assert.match(operationsOnlyContent, /data-capability-node="operation:query-page"/);
@@ -430,25 +430,25 @@ test('capability inspector renders shims inside their capability domain', () =>
assert.doesNotMatch(operationsOnlyContent, /data-capability-node="event:providers-refreshed"/);
filterElement.value = 'playback';
window.__slopsmithCapabilityInspector.domainGraphFilter = 'events';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.domainGraphFilter = 'events';
window.__feedBackCapabilityInspector.render();
const eventsOnlyContent = elements.get('capability-inspector-content').innerHTML;
assert.match(eventsOnlyContent, /aria-pressed="true" class="rounded border px-2 py-1 transition bg-purple-500\/40 text-white border-purple-400\/40">Events/);
assert.match(eventsOnlyContent, /data-capability-node="event:song:ready"/);
assert.doesNotMatch(eventsOnlyContent, /data-capability-node="command:play"/);
filterElement.value = 'library';
window.__slopsmithCapabilityInspector.domainGraphFilter = 'shimmed';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.domainGraphFilter = 'shimmed';
window.__feedBackCapabilityInspector.render();
const shimmedOnlyContent = elements.get('capability-inspector-content').innerHTML;
assert.match(shimmedOnlyContent, /data-link-kind="shimmed"/);
assert.doesNotMatch(shimmedOnlyContent, /data-link-kind="observed"/);
assert.match(shimmedOnlyContent, /title="4 participants" aria-label="4 participants" role="img"/);
filterElement.value = '';
window.__slopsmithCapabilityInspector.expandedDomains = {};
window.__slopsmithCapabilityInspector.domainGraphFilter = 'all';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.expandedDomains = {};
window.__feedBackCapabilityInspector.domainGraphFilter = 'all';
window.__feedBackCapabilityInspector.render();
const collapsedFilteredContent = elements.get('capability-inspector-content').innerHTML;
assert.match(collapsedFilteredContent, /data-domain-graph="playback" data-domain-graph-expanded="false"[\s\S]*?title="3 participants" aria-label="3 participants" role="img"/);
});
@@ -468,7 +468,7 @@ test('capability inspector drops stale future-domain filter options', () => {
filter.innerHTML += '<option value="ui.player-panels">ui.player-panels</option>';
filter.value = 'ui.player-panels';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
assert.equal(filter.value, '');
assert.doesNotMatch(filter.innerHTML, /ui\.player-panels/);
@@ -492,9 +492,9 @@ test('capability inspector refreshes collapsed counts after runtime capability c
currentSnapshot = {
...currentSnapshot,
participants: [{ pluginId: 'core' }, { pluginId: 'window.slopsmith.libraryProviders.refresh' }, { pluginId: 'remote_library_client' }],
participants: [{ pluginId: 'core' }, { pluginId: 'window.feedBack.libraryProviders.refresh' }, { pluginId: 'remote_library_client' }],
compatibilityShims: [
{ shimId: 'runtime:library:refresh:window.slopsmith.libraryProviders.refresh', source: 'window.slopsmith.libraryProviders.refresh', capability: 'library', legacySurface: 'refresh', status: 'used', hitCount: 1 },
{ shimId: 'runtime:library:refresh:window.feedBack.libraryProviders.refresh', source: 'window.feedBack.libraryProviders.refresh', capability: 'library', legacySurface: 'refresh', status: 'used', hitCount: 1 },
{ shimId: 'runtime:library:select:remote_library_client', source: 'remote_library_client', capability: 'library', legacySurface: 'select', status: 'used', hitCount: 1 },
],
expectedCompatibilityShims: [
@@ -502,7 +502,7 @@ test('capability inspector refreshes collapsed counts after runtime capability c
{ capability: 'library', legacySurface: 'select', reason: 'legacy library provider selector calls are counted as library.select command use' },
],
};
window.dispatchEvent(new window.CustomEvent('slopsmith:capabilities:changed'));
window.dispatchEvent(new window.CustomEvent('feedBack:capabilities:changed'));
const refreshedContent = elements.get('capability-inspector-content').innerHTML;
assert.match(refreshedContent, /data-domain-graph="library" data-domain-graph-expanded="false"[\s\S]*?title="2 participants"/);
@@ -518,7 +518,7 @@ test('capability inspector links library legacy command surfaces to canonical en
],
participants: [{ pluginId: 'core' }],
compatibilityShims: [
{ shimId: 'runtime:library:refresh:window.slopsmith.libraryProviders.refresh', source: 'window.slopsmith.libraryProviders.refresh', capability: 'library', legacySurface: 'refresh', status: 'used', hitCount: 1 },
{ shimId: 'runtime:library:refresh:window.feedBack.libraryProviders.refresh', source: 'window.feedBack.libraryProviders.refresh', capability: 'library', legacySurface: 'refresh', status: 'used', hitCount: 1 },
{ shimId: 'runtime:library:select:remote_library_client', source: 'remote_library_client', capability: 'library', legacySurface: 'select', status: 'used', hitCount: 1 },
{ shimId: 'runtime:library:sync-song:remote_library_client', source: 'remote_library_client', capability: 'library', legacySurface: 'sync-song', status: 'used', hitCount: 1 },
],
@@ -532,9 +532,9 @@ test('capability inspector links library legacy command surfaces to canonical en
const filter = elements.get('capability-inspector-filter');
filter.value = 'library';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
const libraryContent = elements.get('capability-inspector-content').innerHTML;
assert.match(libraryContent, /data-domain-participant-card="window\.slopsmith\.libraryProviders\.refresh"/);
assert.match(libraryContent, /data-domain-participant-card="window\.feedBack\.libraryProviders\.refresh"/);
assert.match(libraryContent, /data-domain-participant-card="remote_library_client"/);
assert.match(libraryContent, /data-link-kind="shimmed"[^>]*>refresh<\/span>/);
assert.match(libraryContent, /data-link-kind="shimmed"[^>]*>select<\/span>/);
@@ -575,7 +575,7 @@ test('selected domain participant count includes shim-only graph participants',
],
participants: [{ pluginId: 'core' }],
compatibilityShims: [
{ shimId: 'runtime:library:refresh:window.slopsmith.libraryProviders.refresh', source: 'window.slopsmith.libraryProviders.refresh', capability: 'library', legacySurface: 'refresh', status: 'used', hitCount: 1 },
{ shimId: 'runtime:library:refresh:window.feedBack.libraryProviders.refresh', source: 'window.feedBack.libraryProviders.refresh', capability: 'library', legacySurface: 'refresh', status: 'used', hitCount: 1 },
{ shimId: 'runtime:library:select:remote_library_client', source: 'remote_library_client', capability: 'library', legacySurface: 'select', status: 'used', hitCount: 1 },
],
expectedCompatibilityShims: [],
@@ -583,12 +583,12 @@ test('selected domain participant count includes shim-only graph participants',
const { window, elements } = loadInspector(snapshot);
const filter = elements.get('capability-inspector-filter');
filter.value = 'library';
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
const selectedContent = elements.get('capability-inspector-content').innerHTML;
assert.match(selectedContent, /title="2 participants"/);
assert.doesNotMatch(selectedContent, /2 Participants/);
assert.match(selectedContent, /data-domain-participant-card="window\.slopsmith\.libraryProviders\.refresh"/);
assert.match(selectedContent, /data-domain-participant-card="window\.feedBack\.libraryProviders\.refresh"/);
assert.match(selectedContent, /data-domain-participant-card="remote_library_client"/);
});
@@ -604,9 +604,9 @@ test('capability inspector renders audio-mix fader diagnostics from audio-sessio
expectedCompatibilityShims: [],
};
const { window, elements } = loadInspector(snapshot);
window.slopsmith.audioSession = {
window.feedBack.audioSession = {
snapshot: () => ({
schema: 'slopsmith.audio_session.diagnostics.v1',
schema: 'feedBack.audio_session.diagnostics.v1',
session: { route: { routeKind: 'desktop', availability: 'degraded' }, analyser: { source: 'plugin', availability: 'available' } },
domains: {
'audio-mix': {
@@ -627,7 +627,7 @@ test('capability inspector renders audio-mix fader diagnostics from audio-sessio
}),
};
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
const content = elements.get('capability-inspector-content').innerHTML;
assert.match(content, /data-audio-session-support/);
@@ -649,9 +649,9 @@ test('capability inspector renders audio-input sources selection sessions bridge
expectedCompatibilityShims: [],
};
const { window, elements } = loadInspector(snapshot);
window.slopsmith.audioSession = {
window.feedBack.audioSession = {
snapshot: () => ({
schema: 'slopsmith.audio_session.diagnostics.v1',
schema: 'feedBack.audio_session.diagnostics.v1',
session: { route: { routeKind: 'html5', availability: 'available' }, analyser: { source: 'none', availability: 'unavailable' } },
domains: {
'audio-mix': { participants: [], faders: [], route: { routeKind: 'html5', availability: 'available' }, analyser: { source: 'none', availability: 'unavailable' }, bridges: [] },
@@ -673,7 +673,7 @@ test('capability inspector renders audio-input sources selection sessions bridge
}),
};
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
const content = elements.get('capability-inspector-content').innerHTML;
assert.match(content, /Input: Input 1:available:mono:native, Legacy Input:available:stereo:compatibility:superseded/);
@@ -695,9 +695,9 @@ test('capability inspector renders audio-monitoring providers sessions direct mo
expectedCompatibilityShims: [],
};
const { window, elements } = loadInspector(snapshot);
window.slopsmith.audioSession = {
window.feedBack.audioSession = {
snapshot: () => ({
schema: 'slopsmith.audio_session.diagnostics.v1',
schema: 'feedBack.audio_session.diagnostics.v1',
session: { route: { routeKind: 'desktop', availability: 'available' }, analyser: { source: 'plugin', availability: 'available' } },
domains: {
'audio-mix': { participants: [], faders: [], route: { routeKind: 'desktop', availability: 'available' }, analyser: { source: 'plugin', availability: 'available' }, bridges: [] },
@@ -726,7 +726,7 @@ test('capability inspector renders audio-monitoring providers sessions direct mo
}),
};
window.__slopsmithCapabilityInspector.render();
window.__feedBackCapabilityInspector.render();
const content = elements.get('capability-inspector-content').innerHTML;
assert.match(content, /Monitoring providers: Native Monitor:available:native, Legacy Monitor:available:compatibility:superseded/);
+5 -5
View File
@@ -7,9 +7,9 @@ const { createWindow, ROOT } = require('./capabilities_test_harness');
function loadDiagnostics() {
const window = createWindow();
// diagnostics.js short-circuits if window.slopsmith.diagnostics already
// diagnostics.js short-circuits if window.feedBack.diagnostics already
// exists (idempotent guard); the harness stubs it, so clear it first.
window.slopsmith.diagnostics = undefined;
window.feedBack.diagnostics = undefined;
window.navigator = { userAgent: 'test' };
const context = vm.createContext(window);
const source = fs.readFileSync(path.join(ROOT, 'static', 'diagnostics.js'), 'utf8');
@@ -18,7 +18,7 @@ function loadDiagnostics() {
}
test('summarizeRuntimeDomains counts actual UI contributions, not the {declared,legacy} wrapper keys', () => {
const { summarizeRuntimeDomains } = loadDiagnostics().slopsmith.diagnostics;
const { summarizeRuntimeDomains } = loadDiagnostics().feedBack.diagnostics;
const snapshot = {
plugins: [
{
@@ -49,7 +49,7 @@ test('summarizeRuntimeDomains counts actual UI contributions, not the {declared,
});
test('summarizeRuntimeDomains tolerates a flat region→contributions map', () => {
const { summarizeRuntimeDomains } = loadDiagnostics().slopsmith.diagnostics;
const { summarizeRuntimeDomains } = loadDiagnostics().feedBack.diagnostics;
const summary = summarizeRuntimeDomains({
plugins: [{ ui_contributions: { 'ui.navigation': [{ id: 'a' }, { id: 'b' }] } }],
});
@@ -57,7 +57,7 @@ test('summarizeRuntimeDomains tolerates a flat region→contributions map', () =
});
test('summarizeRuntimeDomains counts only array region values (malformed values are ignored)', () => {
const { summarizeRuntimeDomains } = loadDiagnostics().slopsmith.diagnostics;
const { summarizeRuntimeDomains } = loadDiagnostics().feedBack.diagnostics;
// Non-array region values (a stray object/string from a malformed payload)
// must not inflate the count — only the two real array entries count.
const declaredSummary = summarizeRuntimeDomains({
+1 -1
View File
@@ -1,5 +1,5 @@
// Pins the arpeggio chord-gem deferral gating in plugins/highway_3d/screen.js
// (slopsmith#262). Without these guards, an over-eager `deferChordGems` makes
// (feedBack#262). Without these guards, an over-eager `deferChordGems` makes
// arpeggio frames empty when standalone notes don't actually cover the shape,
// and an under-eager one duplicates gems on top of the standalone passage.
//
+1 -1
View File
@@ -1,4 +1,4 @@
// Pins 3D Highway left-handed fret ordering (slopsmith#321).
// Pins 3D Highway left-handed fret ordering (feedBack#321).
// Source-level only, matching the other tests/js/ regression guards: the
// runtime path is browser/WebGL-heavy, so these tests preserve the exact
// contracts that keep the lefty geometry coherent.
+4 -4
View File
@@ -1,4 +1,4 @@
// Contract test for 3D Highway per-panel control metadata (slopsmith#247).
// Contract test for 3D Highway per-panel control metadata (feedBack#247).
// The plugin script is evaluated in a vm sandbox so factory statics are
// tested without constructing a renderer instance or calling init().
@@ -22,7 +22,7 @@ function loadHighway3dStatics() {
// semantic anchor inside the IIFE — so harmless footer edits (a trailing
// sourceMappingURL comment, extra whitespace, a different IIFE close
// style) do not break this contract test.
const ANCHOR = 'window.slopsmithViz_highway_3d = createFactory;';
const ANCHOR = 'window.feedBackViz_highway_3d = createFactory;';
assert.equal(
src.split(ANCHOR).length - 1,
1,
@@ -46,7 +46,7 @@ function loadHighway3dStatics() {
},
performance: { now: () => 0 },
window: {
slopsmithTour: {
feedBackTour: {
register() {},
},
},
@@ -79,7 +79,7 @@ function assertOptionObject(option, controlKey) {
test('3D Highway exposes static panelControls descriptors for per-panel hosts', () => {
const window = loadHighway3dStatics();
const factory = window.slopsmithViz_highway_3d;
const factory = window.feedBackViz_highway_3d;
assert.equal(typeof factory, 'function', 'screen.js must register the 3D Highway factory');
assert.ok(
+1 -1
View File
@@ -1,5 +1,5 @@
// Source-level guards for the pool().warm() helper added in
// slopsmith#226 — locks in:
// feedBack#226 — locks in:
// 1. The factory exposes .warm() (so future refactors don't quietly
// remove the boardInit pre-allocation strategy).
// 2. warm() coerces its argument via `cap | 0` + `Math.max(0, …)` so a
@@ -6,7 +6,7 @@
// pause the 3D highway crept forward against a frozen audio clock and then
// snapped back to raw — a visible twitch on every pause.
//
// The fix wires a host pause signal (slopsmith core's bundle.isPlaying) into
// The fix wires a host pause signal (feedBack core's bundle.isPlaying) into
// smoothNow: when the chart clock is not advancing, return raw immediately
// and re-anchor. These tests lock in both halves of the contract by
// inspecting source (the createHighway / renderer closures own WebGL + audio
+1 -1
View File
@@ -1,4 +1,4 @@
// Source-level guards for the load-adaptive render scale (slopsmith#654).
// Source-level guards for the load-adaptive render scale (feedBack#654).
// The createHighway closure owns the rAF loop + WebGL sizing that's too
// heavy for a vm sandbox, so — like highway_visibility.test.js — these
// lock in the wiring rather than execute it.
+6 -6
View File
@@ -1,5 +1,5 @@
// Pins the native-audio barrier ordering in static/highway.js
// (slopsmith-desktop#117). The highway must await window.slopsmithAudioBarrier
// (feedBack-desktop#117). The highway must await window.feedBackAudioBarrier
// before touching the JUCE backing engine, otherwise a NAM tone graph build
// that restarts the native audio device races the backing-track load.
//
@@ -12,13 +12,13 @@ const path = require('node:path');
const HIGHWAY_JS = path.join(__dirname, '..', '..', 'static', 'highway.js');
test('highway awaits slopsmithAudioBarrier before the JUCE backing path', () => {
test('highway awaits feedBackAudioBarrier before the JUCE backing path', () => {
const src = fs.readFileSync(HIGHWAY_JS, 'utf8');
const barrierIdx = src.indexOf('window.slopsmithAudioBarrier');
const barrierIdx = src.indexOf('window.feedBackAudioBarrier');
const isRunningIdx = src.indexOf('juceApi.isAudioRunning()');
const loadIdx = src.indexOf('juceApi.loadBackingTrack');
assert.ok(barrierIdx !== -1, 'highway must reference window.slopsmithAudioBarrier');
assert.ok(barrierIdx !== -1, 'highway must reference window.feedBackAudioBarrier');
assert.ok(isRunningIdx !== -1, 'highway must still call juceApi.isAudioRunning()');
assert.ok(loadIdx !== -1, 'highway must still call juceApi.loadBackingTrack');
assert.ok(barrierIdx < isRunningIdx,
@@ -29,8 +29,8 @@ test('highway awaits slopsmithAudioBarrier before the JUCE backing path', () =>
test('the barrier await is timeout-guarded so a stuck plugin barrier cannot wedge song entry', () => {
const src = fs.readFileSync(HIGHWAY_JS, 'utf8');
const start = src.indexOf('window.slopsmithAudioBarrier');
assert.ok(start !== -1, 'highway must reference window.slopsmithAudioBarrier');
const start = src.indexOf('window.feedBackAudioBarrier');
assert.ok(start !== -1, 'highway must reference window.feedBackAudioBarrier');
const region = src.slice(start, start + 600);
// Catching rejections alone does not cover a never-settling promise — the
// await must be raced against a local timeout.
+1 -1
View File
@@ -3,7 +3,7 @@
// regression that drops one of the three keys, or forgets to reset the
// derived state, will fail in CI.
//
// Background: see slopsmith#412 and the Copilot review thread that
// Background: see feedBack#412 and the Copilot review thread that
// surfaced the `chordTemplates` ordering edge case (templates can land
// after the final `chords` chunk; `isOpen()`-derived `nonZeroNotes`
// would otherwise stay stale until the next chord transition).
+4 -4
View File
@@ -1,4 +1,4 @@
// The public plugin API: window.slopsmith.highwayColors. The facade is a thin,
// The public plugin API: window.feedBack.highwayColors. The facade is a thin,
// stable wrapper over the (private) string-color manager in app.js. These tests
// extract _hwcInstallFacade and run it against a fake window/bus with stubbed
// manager functions, so the documented surface + wiring are locked in.
@@ -49,7 +49,7 @@ function buildFacade() {
emit(e, d) { (listeners[e] || []).slice().forEach((f) => f({ detail: d })); },
_count: (e) => (listeners[e] || []).length,
};
const win = { slopsmith: bus, highway: { getStringColors: () => ['#aaaaaa'] } };
const win = { feedBack: bus, highway: { getStringColors: () => ['#aaaaaa'] } };
const HWC_SLOTS = [
{ key: 'highE', label: 'High E', sub: '1st' }, { key: 'B', label: 'B', sub: '2nd' },
{ key: 'G', label: 'G', sub: '3rd' }, { key: 'D', label: 'D', sub: '4th' },
@@ -74,7 +74,7 @@ function buildFacade() {
stubs.applyHighwayStringColors, stubs.encodeHighwayColorShare, stubs.decodeHighwayColorShare,
);
installer();
return { api: win.slopsmith.highwayColors, win, bus, calls, installer, stubs };
return { api: win.feedBack.highwayColors, win, bus, calls, installer, stubs };
}
test('initHighwayColors installs the facade', () => {
@@ -159,5 +159,5 @@ test('repeated onChange with the same handler unsubscribes independently (no lea
test('install is idempotent (does not replace an existing facade)', () => {
const { api, win, installer } = buildFacade();
installer();
assert.equal(win.slopsmith.highwayColors, api, 'second install must be a no-op');
assert.equal(win.feedBack.highwayColors, api, 'second install must be a no-op');
});
+3 -3
View File
@@ -1,4 +1,4 @@
// Source-level guards for the per-note judgment hook (slopsmith#254):
// Source-level guards for the per-note judgment hook (feedBack#254):
// highway.setNoteStateProvider / getNoteStateProvider / getNoteState,
// bundle.getNoteState, isDefaultRenderer, and the _noteState
// normalization rules. The createHighway closure owns canvas + WebGL
@@ -54,7 +54,7 @@ test('_makeBundle exposes getNoteState (stable reference, no per-frame alloc)',
assert.match(fn, /getNoteState:\s*_noteState\b/, 'bundle.getNoteState must be the stable _noteState reference');
});
test('_makeBundle exposes getNoteStateProvider as a stable reference (slopsmith#254)', () => {
test('_makeBundle exposes getNoteStateProvider as a stable reference (feedBack#254)', () => {
const src = fs.readFileSync(highwayJs, 'utf8');
const fn = extractBlock(src, 'function _makeBundle()');
// Same allocation discipline as getNoteState: highway_3d uses this
@@ -106,7 +106,7 @@ test('3D highway captures bundle.getNoteState and overrides legacy hit/miss with
assert.match(src, /const\s+_showHit\s*=\s*\(\s*_ndState\s*===\s*['"]miss['"]\s*\)\s*\?\s*false\s*:\s*\(\s*_ndState\s*\?\s*_ndGood\s*:\s*\(\s*hit\s*\|\|\s*\(\s*n\.f\s*>\s*0\s*&&\s*inGhostWin\s*\)\s*\)\s*\)/, '_showHit must honor a provider "miss" and fall back to the hit/ghost heuristic only with no verdict');
});
test('3D highway captures _ndHasProvider via bundle.getNoteStateProvider (slopsmith#254)', () => {
test('3D highway captures _ndHasProvider via bundle.getNoteStateProvider (feedBack#254)', () => {
const src = fs.readFileSync(highway3dJs, 'utf8');
// Detect-mode behavior — verdict-window cull extension, chord-frame
// hold floor, and the smart drawNote cull — must be gated on a real
+1 -1
View File
@@ -1,5 +1,5 @@
// Source-level guards for the playback-aware paused-render throttle
// (slopsmith#654). The createHighway closure owns the rAF loop + WebGL
// (feedBack#654). The createHighway closure owns the rAF loop + WebGL
// context lifecycle that's too heavy to reproduce in a vm sandbox, so —
// like highway_visibility.test.js — these checks lock in the wiring.
+2 -2
View File
@@ -101,8 +101,8 @@ test('app.js color manager name-maps to both highways, with identity no-op + bui
const idfn = extractBlock(src, 'function _hwcMappingIsIdentity(sc, isBass)');
assert.match(idfn, /return isBass \? sc <= 4 : sc <= 6/, 'identity must be 4-string bass / ≤6-string guitar');
// Re-apply on song load (string count can change the slot→index mapping).
assert.match(src, /window\.slopsmith\.on\('viz:renderer:ready', reapplyHighwayStringColors\)/, 'must re-apply when a viz renderer becomes ready');
assert.match(src, /window\.slopsmith\.on\('song:loaded', reapplyHighwayStringColors\)/, 'must re-apply on song load');
assert.match(src, /window\.feedBack\.on\('viz:renderer:ready', reapplyHighwayStringColors\)/, 'must re-apply when a viz renderer becomes ready');
assert.match(src, /window\.feedBack\.on\('song:loaded', reapplyHighwayStringColors\)/, 'must re-apply on song load');
});
// ── Executable: dim/bright derivation math ────────────────────────────────
+8 -8
View File
@@ -1,5 +1,5 @@
// Source-level guards for the visibility-aware rAF skip and the
// highway:visibility event (slopsmith#246). The createHighway closure
// highway:visibility event (feedBack#246). The createHighway closure
// owns the canvas + WebGL context lifecycle that's too heavy to
// reproduce in a vm sandbox — these checks lock in the wiring instead.
@@ -52,7 +52,7 @@ test('_emitVisibilityIfChanged is transition-only (no per-frame spam)', () => {
assert.match(fn, /_lastVisible\s*=\s*v/, 'must update _lastVisible after a transition');
assert.match(
fn,
/window\.slopsmith\.emit\(\s*['"]highway:visibility['"][\s\S]*?visible:\s*v[\s\S]*?canvas/,
/window\.feedBack\.emit\(\s*['"]highway:visibility['"][\s\S]*?visible:\s*v[\s\S]*?canvas/,
'must emit highway:visibility with { visible, canvas }',
);
});
@@ -74,7 +74,7 @@ test('rAF draw() loop calls _emitVisibilityIfChanged and skips when hidden', ()
assert.ok(readyIdx < drawIdx, 'ready gate must run before renderer.draw');
});
test('draw() keeps an active custom renderer painting through an override-hide (slopsmith#819)', () => {
test('draw() keeps an active custom renderer painting through an override-hide (feedBack#819)', () => {
// The `_rendering` decision must distinguish a renderer-set override-hide
// (setVisible(false) — canvas occluded by an opaque overlay, but the
// active custom renderer still paints its own surface, e.g. Tab View's
@@ -140,12 +140,12 @@ test('3D Highway subscribes to highway:visibility and toggles wrap on hide', ()
// Listener registration with the documented event name (in init).
assert.match(
initSceneBlock,
/window\.slopsmith\.on\(\s*['"]highway:visibility['"]/,
/window\.feedBack\.on\(\s*['"]highway:visibility['"]/,
'initScene must subscribe to highway:visibility',
);
// Handler filters by canvas identity so splitscreen panels don't
// hide each other's overlays — every instance receives every event
// on the shared slopsmith bus, so this gate is essential.
// on the shared feedBack bus, so this gate is essential.
assert.match(
initSceneBlock,
/e\.detail\.canvas\s*!==\s*highwayCanvas/,
@@ -170,7 +170,7 @@ test('3D Highway subscribes to highway:visibility and toggles wrap on hide', ()
// context-type-driven canvas swap. Per CLAUDE.md plugin contract.
assert.match(
initSceneBlock,
/window\.slopsmith\.on\(\s*['"]highway:canvas-replaced['"]/,
/window\.feedBack\.on\(\s*['"]highway:canvas-replaced['"]/,
'initScene must track canvas swaps so the visibility gate keeps matching',
);
assert.match(
@@ -181,12 +181,12 @@ test('3D Highway subscribes to highway:visibility and toggles wrap on hide', ()
// Teardown unbinds both listeners.
assert.match(
teardownBlock,
/window\.slopsmith\.off\(\s*['"]highway:visibility['"]/,
/window\.feedBack\.off\(\s*['"]highway:visibility['"]/,
'teardown must unbind highway:visibility',
);
assert.match(
teardownBlock,
/window\.slopsmith\.off\(\s*['"]highway:canvas-replaced['"]/,
/window\.feedBack\.off\(\s*['"]highway:canvas-replaced['"]/,
'teardown must unbind highway:canvas-replaced',
);
});
+4 -4
View File
@@ -90,8 +90,8 @@ function makeSandbox({ isAudioRunning, loadBackingTrack }) {
};
sandbox.window = sandbox;
sandbox.window.jucePlayer = jucePlayer;
sandbox.window.slopsmithDesktop = { audio: juceApi };
sandbox.window.slopsmith = { audio: {} };
sandbox.window.feedBackDesktop = { audio: juceApi };
sandbox.window.feedBack = { audio: {} };
const src = fs.readFileSync(APP_JS, 'utf8');
const iife = extractWatcherIIFE(src);
@@ -217,7 +217,7 @@ test('stale-abort during a swap-then-restore is NOT memoised as a JUCE reject',
sb.window._currentSongAudio = snapA;
let firstLoad = true;
sb.window.slopsmithDesktop.audio.loadBackingTrack = (p) => {
sb.window.feedBackDesktop.audio.loadBackingTrack = (p) => {
sb.__calls.loadBackingTrack.push(p);
if (firstLoad) {
firstLoad = false;
@@ -353,7 +353,7 @@ test('song change mid-flight aborts the switch without mutating routing', async
sb.window._currentSongAudio = original;
// Swap the current song the moment loadBackingTrack is consulted, so the
// post-await staleness check sees a different _currentSongAudio identity.
sb.window.slopsmithDesktop.audio.loadBackingTrack = () => {
sb.window.feedBackDesktop.audio.loadBackingTrack = () => {
sb.window._currentSongAudio = { url: '/audio/song-b.ogg', juceEligible: true };
return Promise.resolve(true);
};
+12 -12
View File
@@ -6,7 +6,7 @@ const { loadAudioSession } = require('./audio_session_test_harness');
test('active audio domains expose expected legacy shim metadata', () => {
const window = loadAudioSession();
const shims = window.slopsmith.capabilities.snapshotDiagnostics().compatibilityShims;
const shims = window.feedBack.capabilities.snapshotDiagnostics().compatibilityShims;
for (const shimId of ['audio-mix.fader-registry', 'audio-mix.song-volume', 'audio-mix.analyser', 'audio-input.legacy-source', 'audio-monitoring.audio-barrier', 'stems.master-volume', 'stems.private-state']) {
assert.equal(shims.some(shim => shim.shimId === shimId && shim.status === 'active'), true, shimId);
}
@@ -14,12 +14,12 @@ test('active audio domains expose expected legacy shim metadata', () => {
test('legacy bridge hit counts are attributed to canonical audio domains', () => {
const window = loadAudioSession();
const audioSession = window.slopsmith.audioSession;
const audioSession = window.feedBack.audioSession;
audioSession.recordBridgeHit({ domain: 'audio-mix', bridgeId: 'audio-mix.analyser', legacySurface: 'HTMLAudioElement analyser tap', participantId: 'highway_3d' });
audioSession.recordBridgeHit({ domain: 'audio-input', bridgeId: 'audio-input.legacy-source', legacySurface: 'navigator.mediaDevices.getUserMedia', participantId: 'note_detect' });
audioSession.recordBridgeHit({ domain: 'audio-monitoring', bridgeId: 'audio-monitoring.audio-barrier', legacySurface: 'window.slopsmithAudioBarrier', participantId: 'note_detect' });
audioSession.recordBridgeHit({ domain: 'audio-monitoring', bridgeId: 'audio-monitoring.audio-barrier', legacySurface: 'window.feedBackAudioBarrier', participantId: 'note_detect' });
const shims = window.slopsmith.capabilities.snapshotDiagnostics().compatibilityShims;
const shims = window.feedBack.capabilities.snapshotDiagnostics().compatibilityShims;
assert.equal(shims.find(shim => shim.shimId === 'audio-mix.analyser').hitCount, 1);
assert.equal(shims.find(shim => shim.shimId === 'audio-input.legacy-source').capability, 'audio-input');
assert.equal(shims.find(shim => shim.shimId === 'audio-monitoring.audio-barrier').hitCount, 1);
@@ -30,9 +30,9 @@ test('native audio-mix participant suppresses matching legacy fader and records
const window = loadAudioSession();
installMixerDom(window);
runBrowserScript(window, 'static/audio-mixer.js');
window.slopsmith.audioSession.startSession({ sessionId: 'main:test-song' });
window.feedBack.audioSession.startSession({ sessionId: 'main:test-song' });
window.slopsmith.audio.registerFader({
window.feedBack.audio.registerFader({
id: 'delay.wet',
label: 'Delay Wet Legacy',
min: 0,
@@ -43,7 +43,7 @@ test('native audio-mix participant suppresses matching legacy fader and records
getValue: () => 0.2,
setValue: () => {},
});
window.slopsmith.audioSession.registerMixParticipant({
window.feedBack.audioSession.registerMixParticipant({
participantId: 'plugin.delay.native',
ownerPluginId: 'delay',
label: 'Delay Wet',
@@ -54,8 +54,8 @@ test('native audio-mix participant suppresses matching legacy fader and records
operations: ['fader.get-value', 'fader.set-value'],
});
const listed = await window.slopsmith.capabilities.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
const snapshot = window.slopsmith.audioSession.snapshot();
const listed = await window.feedBack.capabilities.dispatch({ capability: 'audio-mix', command: 'list-faders', source: 'test' });
const snapshot = window.feedBack.audioSession.snapshot();
const legacy = snapshot.domains['audio-mix'].participants.find(participant => participant.participantId === 'fader.delay.wet');
assert.equal(listed.payload.faders.some(fader => fader.participantId === 'plugin.delay.native'), true);
@@ -86,8 +86,8 @@ function region(src, needle, length = 1200) {
test('plugin script hydration exposes the current plugin id for legacy registrations', () => {
const src = source(APP_JS);
const block = region(src, 'script.src = `/api/plugins/${plugin.id}/screen.js');
assert.match(block, /window\.slopsmith\._loadingPluginId\s*=\s*plugin\.id/);
assert.match(block, /delete\s+window\.slopsmith\._loadingPluginId/);
assert.match(block, /window\.feedBack\._loadingPluginId\s*=\s*plugin\.id/);
assert.match(block, /delete\s+window\.feedBack\._loadingPluginId/);
});
test('library providers route through native library capability', () => {
@@ -100,7 +100,7 @@ test('library providers route through native library capability', () => {
assert.match(librarySrc, /capabilities\.registerOwner\(['"]library['"]/);
assert.match(librarySrc, /kind:\s*['"]provider-coordinator['"]/);
assert.match(librarySrc, /'library\.read': \['query-page', 'query-artists', 'query-stats', 'tuning-names'\]/);
assert.match(librarySrc, /window\.slopsmith\.libraryProviders\s*=\s*providerApi/);
assert.match(librarySrc, /window\.feedBack\.libraryProviders\s*=\s*providerApi/);
assert.match(loader, /api\.refresh\(\{ restoreSaved \}\)/);
assert.match(selector, /capabilityApi\.command\(['"]library['"],\s*['"]select-provider['"]/);
assert.match(sync, /capabilityApi\.command\(['"]library['"],\s*['"]sync-song['"]/);
+1 -1
View File
@@ -28,7 +28,7 @@ test('shouldSuppressMonitorMuteHint only for external modes', () => {
});
test('labels include internal, external, and spark options', () => {
assert.match(toneSource.LABELS.internal, /feed\[dB\]ack internal tone/i);
assert.match(toneSource.LABELS.internal, /fee\[dB\]ack internal tone/i);
assert.match(toneSource.LABELS.external_hardware, /External amp/i);
assert.match(toneSource.LABELS.spark_control_x, /Spark LIVE/i);
});
+9 -9
View File
@@ -1,5 +1,5 @@
// Verify the plugin-facing loop API: setLoop / clearLoop / getLoop on
// window.slopsmith, the input validation in setLoop, and the
// window.feedBack, the input validation in setLoop, and the
// loadSavedLoop refactor that funnels through setLoop.
//
// Same isolation strategy as loop_restart.test.js — extract relevant
@@ -82,7 +82,7 @@ function buildSandbox() {
// assert on the label text in these tests, so a stub is enough.
formatTime: (s) => String(s),
window: {
slopsmith: {
feedBack: {
playback: {
transportEvent: (...args) => transportEvents.push(args),
},
@@ -236,18 +236,18 @@ test('loop helpers emit transport snapshots by default and can suppress adapter
assert.equal(sandbox.transportEvents.length, 0);
});
test('window.slopsmith API surface declares setLoop/clearLoop/getLoop', () => {
test('window.feedBack API surface declares setLoop/clearLoop/getLoop', () => {
// Source-level assertion: the plugin-facing namespace must expose
// these three methods. Catches a future contributor moving them or
// renaming silently.
const src = fs.readFileSync(APP_JS, 'utf8');
// Find the slopsmith Object.assign block and check method presence.
const m = src.match(/window\.slopsmith\s*=\s*Object\.assign\(_slopsmithBus,\s*\{([\s\S]*?)\}\);\s*if \(_slopsmithExisting/);
assert.ok(m, 'slopsmith Object.assign block not found');
// Find the feedBack Object.assign block and check method presence.
const m = src.match(/window\.feedBack\s*=\s*Object\.assign\(_feedBackBus,\s*\{([\s\S]*?)\}\);\s*if \(_feedBackExisting/);
assert.ok(m, 'feedBack Object.assign block not found');
const block = m[1];
assert.match(block, /setLoop\s*\(/, 'setLoop method missing from slopsmith API');
assert.match(block, /clearLoop\s*\(/, 'clearLoop method missing from slopsmith API');
assert.match(block, /getLoop\s*\(/, 'getLoop method missing from slopsmith API');
assert.match(block, /setLoop\s*\(/, 'setLoop method missing from feedBack API');
assert.match(block, /clearLoop\s*\(/, 'clearLoop method missing from feedBack API');
assert.match(block, /getLoop\s*\(/, 'getLoop method missing from feedBack API');
});
test('loadSavedLoop funnels through setLoop (no duplicated UI mutation)', () => {
+4 -4
View File
@@ -72,7 +72,7 @@ function buildSandbox() {
// we don't need them to fire — the emit happens before beginCount.
setTimeout: () => 0,
// Stubbed slopsmith DOM dependencies.
// Stubbed feedBack DOM dependencies.
audio: { pause() {} },
jucePlayer: { pause: () => Promise.resolve(), play: () => Promise.resolve(true) },
highway: { setTime() {}, getBPM: () => 120 },
@@ -98,7 +98,7 @@ function buildSandbox() {
// Spy: records every emit call so the test can assert.
window: {
slopsmith: {
feedBack: {
emit(event, detail) { emitCalls.push({ event, detail }); },
isPlaying: false,
},
@@ -121,7 +121,7 @@ test('loop:restart fires once when wrap path runs', async () => {
// accidental revert before we even run the behavior assertion.
assert.match(
startCountInSrc,
/window\.slopsmith\.emit\(\s*['"]loop:restart['"]/,
/window\.feedBack\.emit\(\s*['"]loop:restart['"]/,
'startCountIn is missing the loop:restart emit',
);
@@ -229,7 +229,7 @@ test('loop:restart fires after highway.setTime, before beginCount', () => {
const setTimeIdx = setTimeMatches.length
? wrapStart + setTimeMatches[setTimeMatches.length - 1].index
: -1;
const emitRel = wrapSlice.search(/window\.slopsmith\.emit\(\s*['"]loop:restart['"]/);
const emitRel = wrapSlice.search(/window\.feedBack\.emit\(\s*['"]loop:restart['"]/);
const emitIdx = emitRel === -1 ? -1 : wrapStart + emitRel;
const afterEmit = emitIdx === -1 ? '' : fn.slice(emitIdx);
const beginCallMatch = afterEmit.match(/(?<!function\s)\bbeginCount\s*\(/);
+31 -31
View File
@@ -19,7 +19,7 @@ function loadMidiInput(options = {}) {
// A fake provider whose enumerate/open/close are observable by the test.
function fakeProvider(window, overrides = {}) {
const calls = { enumerate: 0, open: [], close: [] };
window.slopsmith.midiInput.registerProvider({
window.feedBack.midiInput.registerProvider({
providerId: 'web-midi',
label: 'Web MIDI',
participantId: 'input_setup',
@@ -33,7 +33,7 @@ function fakeProvider(window, overrides = {}) {
test('midi-input registers an active sensitive provider-coordinator', () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const pipeline = api.inspect('midi-input');
assert.ok(pipeline, 'midi-input pipeline exists');
const owner = (pipeline.participants || []).find(p => p.pluginId === 'core.midi-input');
@@ -43,12 +43,12 @@ test('midi-input registers an active sensitive provider-coordinator', () => {
for (const cmd of ['inspect', 'list-sources', 'discover', 'select-source', 'open-source', 'close-source']) {
assert.ok(owner.commands.includes(cmd), `owner exposes ${cmd}`);
}
assert.equal(window.slopsmith.midiInput.version, 1);
assert.equal(window.feedBack.midiInput.version, 1);
});
test('list-sources and select-source are prompt-free (never enumerate)', async () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = fakeProvider(window);
const listed = await api.dispatch({ capability: 'midi-input', command: 'list-sources', source: 'tester' });
assert.equal(listed.outcome, 'handled');
@@ -57,12 +57,12 @@ test('list-sources and select-source are prompt-free (never enumerate)', async (
test('discover is the permission boundary and surfaces sources', async () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = fakeProvider(window);
const r = await api.dispatch({ capability: 'midi-input', command: 'discover', source: 'tester' });
assert.equal(r.outcome, 'handled');
assert.equal(calls.enumerate, 1, 'discover requests MIDI access exactly once');
const sources = window.slopsmith.midiInput.listSources();
const sources = window.feedBack.midiInput.listSources();
assert.equal(sources.length, 1);
assert.equal(sources[0].logicalSourceKey, 'web-midi::dev1');
assert.equal(sources[0].kind, 'midi');
@@ -71,31 +71,31 @@ test('discover is the permission boundary and surfaces sources', async () => {
test('re-discovery drops sources for devices that vanished', async () => {
const window = loadMidiInput();
let devices = [{ sourceId: 'dev1', label: 'A' }, { sourceId: 'dev2', label: 'B' }];
window.slopsmith.midiInput.registerProvider({
window.feedBack.midiInput.registerProvider({
providerId: 'web-midi', label: 'Web MIDI',
enumerate: async () => devices,
open: async () => ({ addListener() {}, removeListener() {} }),
close: () => {},
});
await window.slopsmith.midiInput.discover();
assert.equal(window.slopsmith.midiInput.listSources().length, 2);
await window.feedBack.midiInput.discover();
assert.equal(window.feedBack.midiInput.listSources().length, 2);
devices = [{ sourceId: 'dev1', label: 'A' }]; // dev2 unplugged
await window.slopsmith.midiInput.discover();
const keys = window.slopsmith.midiInput.listSources().map((s) => s.logicalSourceKey);
await window.feedBack.midiInput.discover();
const keys = window.feedBack.midiInput.listSources().map((s) => s.logicalSourceKey);
assert.equal(keys.length, 1, 'vanished device is dropped from the source list');
assert.equal(keys[0], 'web-midi::dev1');
});
test('discover with no provider reports unavailable', async () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const r = await api.dispatch({ capability: 'midi-input', command: 'discover', source: 'tester' });
assert.equal(r.outcome, 'unavailable');
});
test('discover surfaces denied when MIDI access is rejected', async () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
fakeProvider(window, { handlers: { enumerate: async () => { throw new Error('SecurityError: permission denied'); } } });
const r = await api.dispatch({ capability: 'midi-input', command: 'discover', source: 'tester' });
assert.equal(r.outcome, 'denied');
@@ -104,21 +104,21 @@ test('discover surfaces denied when MIDI access is rejected', async () => {
test('select-source persists by logicalSourceKey', async () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
fakeProvider(window);
await api.dispatch({ capability: 'midi-input', command: 'discover', source: 'tester' });
const sel = await api.dispatch({ capability: 'midi-input', command: 'select-source', source: 'tester', payload: { logicalSourceKey: 'web-midi::dev1' } });
assert.equal(sel.outcome, 'handled');
assert.equal(window.__storage.get('slopsmith.midiInput.selectedLogicalSourceKey'), 'web-midi::dev1');
assert.ok(window.slopsmith.midiInput.listSources()[0].selected);
assert.equal(window.__storage.get('feedBack.midiInput.selectedLogicalSourceKey'), 'web-midi::dev1');
assert.ok(window.feedBack.midiInput.listSources()[0].selected);
});
test('open/close share one session and release on the last requester', async () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = fakeProvider(window);
await window.slopsmith.midiInput.discover();
await window.slopsmith.midiInput.select('web-midi::dev1');
await window.feedBack.midiInput.discover();
await window.feedBack.midiInput.select('web-midi::dev1');
const a = await api.dispatch({ capability: 'midi-input', command: 'open-source', source: 'reqA', payload: { logicalSourceKey: 'web-midi::dev1' } });
const b = await api.dispatch({ capability: 'midi-input', command: 'open-source', source: 'reqB', payload: { logicalSourceKey: 'web-midi::dev1' } });
assert.equal(a.outcome, 'handled');
@@ -133,20 +133,20 @@ test('open/close share one session and release on the last requester', async ()
test('concurrent opens for one source coalesce onto a single provider.open', async () => {
const window = loadMidiInput();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// A provider whose open() stays pending until we release it, so both
// dispatches are genuinely in flight at the same time.
let release;
const gate = new Promise((r) => { release = r; });
const calls = { open: 0, close: 0 };
window.slopsmith.midiInput.registerProvider({
window.feedBack.midiInput.registerProvider({
providerId: 'web-midi', label: 'Web MIDI',
enumerate: async () => [{ sourceId: 'dev1', label: 'My Keyboard' }],
open: async () => { calls.open += 1; await gate; return { addListener() {}, removeListener() {} }; },
close: () => { calls.close += 1; },
});
await window.slopsmith.midiInput.discover();
await window.slopsmith.midiInput.select('web-midi::dev1');
await window.feedBack.midiInput.discover();
await window.feedBack.midiInput.select('web-midi::dev1');
const p1 = api.dispatch({ capability: 'midi-input', command: 'open-source', source: 'reqA', payload: { logicalSourceKey: 'web-midi::dev1' } });
const p2 = api.dispatch({ capability: 'midi-input', command: 'open-source', source: 'reqB', payload: { logicalSourceKey: 'web-midi::dev1' } });
release();
@@ -165,9 +165,9 @@ test('concurrent opens for one source coalesce onto a single provider.open', asy
test('public open() surfaces the live handle (in-page only)', async () => {
const window = loadMidiInput();
fakeProvider(window);
await window.slopsmith.midiInput.discover();
await window.slopsmith.midiInput.select('web-midi::dev1');
const res = await window.slopsmith.midiInput.open({ requester: 'input_setup', logicalSourceKey: 'web-midi::dev1' });
await window.feedBack.midiInput.discover();
await window.feedBack.midiInput.select('web-midi::dev1');
const res = await window.feedBack.midiInput.open({ requester: 'input_setup', logicalSourceKey: 'web-midi::dev1' });
assert.equal(res.outcome, 'handled');
assert.ok(res.handle && typeof res.handle.addListener === 'function', 'live handle exposed via public global');
});
@@ -193,12 +193,12 @@ test('built-in Web-MIDI provider self-registers + discovers, filtering loopback
{ id: 'kb1', name: 'My Keyboard' },
{ id: 'thru', name: 'Midi Through Port-0' }, // loopback → filtered out
]);
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
assert.ok(api.inspect('midi-input').participants.some(p => p.pluginId === 'core.midi-input'),
'built-in provider registered without any plugin');
const r = await api.dispatch({ capability: 'midi-input', command: 'discover', source: 'tester' });
assert.equal(r.outcome, 'handled');
const sources = window.slopsmith.midiInput.listSources();
const sources = window.feedBack.midiInput.listSources();
assert.equal(sources.length, 1, 'loopback/passthrough ports are filtered');
assert.equal(sources[0].logicalSourceKey, 'web-midi::kb1');
});
@@ -206,10 +206,10 @@ test('built-in Web-MIDI provider self-registers + discovers, filtering loopback
test('diagnostics are redaction-safe (no device labels, no raw messages)', async () => {
const window = loadMidiInput();
fakeProvider(window);
await window.slopsmith.midiInput.discover();
const contrib = window.slopsmith.diagnostics.snapshotContributions()['midi-input-capability'];
await window.feedBack.midiInput.discover();
const contrib = window.feedBack.diagnostics.snapshotContributions()['midi-input-capability'];
assert.ok(contrib, 'midi-input contributes diagnostics');
assert.equal(contrib.schema, 'slopsmith.midi_input.diagnostics.v1');
assert.equal(contrib.schema, 'feedBack.midi_input.diagnostics.v1');
const serialized = JSON.stringify(contrib);
assert.ok(!serialized.includes('My Keyboard'), 'device labels are redacted from diagnostics');
for (const s of contrib.sources) assert.ok(!('label' in s), 'source entries carry no label');
+31 -31
View File
@@ -34,19 +34,19 @@ async function registerMidiProvider(api) {
test('note-detection domain registers an active sensitive provider-coordinator', () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const pipeline = api.inspect('note-detection');
assert.ok(pipeline, 'note-detection pipeline exists');
const owner = (pipeline.participants || []).find(p => p.pluginId === 'core.note-detection');
assert.ok(owner, 'core.note-detection owner registered');
assert.equal(owner.safety, 'sensitive');
assert.ok(owner.commands.includes('open-binding'));
assert.equal(window.slopsmith.noteDetection.version, 1);
assert.equal(window.feedBack.noteDetection.version, 1);
});
test('open-binding without a provider reports unavailable, never a silent verdict', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const result = await api.dispatch({
capability: 'note-detection', command: 'open-binding',
source: 'keys_highway_3d', payload: { context: { arrangement: 'keys' } },
@@ -57,7 +57,7 @@ test('open-binding without a provider reports unavailable, never a silent verdic
test('provider registration + binding lifecycle with per-binding context', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = captureEvents(api, [
'note-detection:provider-registered',
'note-detection:binding-opened',
@@ -104,7 +104,7 @@ test('provider registration + binding lifecycle with per-binding context', async
test('concurrent bindings keep independent contexts (FR-003)', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerMidiProvider(api);
const a = await api.dispatch({
capability: 'note-detection', command: 'open-binding',
@@ -114,7 +114,7 @@ test('concurrent bindings keep independent contexts (FR-003)', async () => {
capability: 'note-detection', command: 'open-binding',
source: 'slopscale', payload: { context: { arrangement: 'bass', stringCount: 4, capo: 0 } },
});
const snapshot = window.slopsmith.noteDetection.snapshot();
const snapshot = window.feedBack.noteDetection.snapshot();
const ctxA = snapshot.bindings.find(x => x.id === a.payload.bindingId).context;
const ctxB = snapshot.bindings.find(x => x.id === b.payload.bindingId).context;
assert.equal(ctxA.arrangement, 'guitar');
@@ -125,18 +125,18 @@ test('concurrent bindings keep independent contexts (FR-003)', async () => {
test('unregistering a provider closes its bindings and flips availability', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = captureEvents(api, ['note-detection:availability-changed', 'note-detection:binding-closed']);
await registerMidiProvider(api);
const openResult = await api.dispatch({ capability: 'note-detection', command: 'open-binding', source: 'keys_highway_3d', payload: {} });
assert.equal(openResult.outcome, 'handled');
assert.equal(window.slopsmith.noteDetection.snapshot().bindings.length, 1);
assert.equal(window.feedBack.noteDetection.snapshot().bindings.length, 1);
const result = await api.dispatch({
capability: 'note-detection', command: 'unregister-provider',
source: 'keys_highway_3d', payload: { providerId: 'keys-midi' },
});
assert.equal(result.outcome, 'handled');
assert.equal(window.slopsmith.noteDetection.snapshot().bindings.length, 0);
assert.equal(window.feedBack.noteDetection.snapshot().bindings.length, 0);
const availability = events.filter(e => e.event === 'availability-changed').map(e => e.payload.available);
assert.deepEqual(JSON.parse(JSON.stringify(availability)), [true, false]);
assert.ok(events.some(e => e.event === 'binding-closed' && e.payload.reason === 'provider-unregistered'));
@@ -149,10 +149,10 @@ test('unregistering a provider closes its bindings and flips availability', asyn
test('hit/miss reports flow as observability events with bounded fields', () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = captureEvents(api, ['note-detection:hit', 'note-detection:miss']);
window.slopsmith.noteDetection.reportHit({ bindingId: 'ndb-1', providerId: 'keys-midi', midi: 64, hit: true, secretDevice: 'Yamaha P-125' });
window.slopsmith.noteDetection.reportMiss({ bindingId: 'ndb-1', providerId: 'keys-midi', midi: 65, hit: false });
window.feedBack.noteDetection.reportHit({ bindingId: 'ndb-1', providerId: 'keys-midi', midi: 64, hit: true, secretDevice: 'Yamaha P-125' });
window.feedBack.noteDetection.reportMiss({ bindingId: 'ndb-1', providerId: 'keys-midi', midi: 65, hit: false });
assert.equal(events.length, 2);
assert.equal(events[0].payload.midi, 64);
// Unknown fields are dropped — payloads stay bounded and device-label free.
@@ -162,27 +162,27 @@ test('hit/miss reports flow as observability events with bounded fields', () =>
test('diagnostics contribution is redaction-safe', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerMidiProvider(api);
await api.dispatch({
capability: 'note-detection', command: 'open-binding',
source: 'keys_highway_3d', payload: { context: { arrangement: 'keys', deviceLabel: 'Yamaha P-125' } },
});
window.slopsmith.noteDetection.reportHit({ bindingId: 'ndb-1', midi: 60, hit: true });
window.feedBack.noteDetection.reportHit({ bindingId: 'ndb-1', midi: 60, hit: true });
const contribution = window.__diagnosticsContributions.get('note-detection-capability');
assert.equal(contribution.schema, 'slopsmith.note_detection_capability.v1');
assert.equal(contribution.schema, 'feedBack.note_detection_capability.v1');
const serialized = JSON.stringify(contribution);
assert.ok(!/Yamaha|deviceLabel|filename|\.sloppak|\.archive/i.test(serialized), serialized);
});
test('legacy setNoteStateProvider surface is wrapped and accounted', () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// Simulate highway.js arriving after the host, then the notedetect
// plugin installing its chart-coupled provider.
let installed = null;
window.highway = { setNoteStateProvider(fn) { installed = fn; } };
window.slopsmith.emit('song:loaded', {});
window.feedBack.emit('song:loaded', {});
const provider = () => ({ state: 'hit' });
window.highway.setNoteStateProvider(provider);
assert.equal(installed, provider, 'legacy behavior preserved');
@@ -196,7 +196,7 @@ test('legacy setNoteStateProvider surface is wrapped and accounted', () => {
test('unsupported binding/provider ids degrade with bounded reasons (FR-008)', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerMidiProvider(api);
const badProvider = await api.dispatch({
capability: 'note-detection', command: 'open-binding',
@@ -212,7 +212,7 @@ test('unsupported binding/provider ids degrade with bounded reasons (FR-008)', a
test('_contextSummary whitelists arrangement kind — unknown values are dropped', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerMidiProvider(api);
// Known arrangement kinds pass through.
const open = await api.dispatch({
@@ -220,7 +220,7 @@ test('_contextSummary whitelists arrangement kind — unknown values are dropped
source: 'caller', payload: { context: { arrangement: 'keys', stringCount: 6 } },
});
assert.equal(open.outcome, 'handled');
const snap = window.slopsmith.noteDetection.snapshot();
const snap = window.feedBack.noteDetection.snapshot();
const ctx = snap.bindings.find(b => b.id === open.payload.bindingId).context;
assert.equal(ctx.arrangement, 'keys');
@@ -230,28 +230,28 @@ test('_contextSummary whitelists arrangement kind — unknown values are dropped
source: 'caller', payload: { context: { arrangement: '/Users/victim/song.archive' } },
});
assert.equal(open2.outcome, 'handled');
const snap2 = window.slopsmith.noteDetection.snapshot();
const snap2 = window.feedBack.noteDetection.snapshot();
const ctx2 = snap2.bindings.find(b => b.id === open2.payload.bindingId).context;
assert.equal(ctx2.arrangement, undefined, 'path-bearing arrangement must be dropped');
});
test('snapshot primitives are deep-copied — caller cannot mutate provider internals', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerMidiProvider(api);
const snap1 = window.slopsmith.noteDetection.snapshot();
const snap1 = window.feedBack.noteDetection.snapshot();
const providerEntry = snap1.providers.find(p => p.id === 'keys-midi');
assert.ok(Array.isArray(providerEntry.primitives));
// Mutate the copy — must not affect subsequent snapshots.
providerEntry.primitives.push('injected');
const snap2 = window.slopsmith.noteDetection.snapshot();
const snap2 = window.feedBack.noteDetection.snapshot();
const providerEntry2 = snap2.providers.find(p => p.id === 'keys-midi');
assert.ok(!providerEntry2.primitives.includes('injected'), 'live primitives must not be mutated via snapshot');
});
test('close-binding and set-target enforce requester ownership', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerMidiProvider(api);
const open = await api.dispatch({
capability: 'note-detection', command: 'open-binding',
@@ -283,7 +283,7 @@ test('close-binding and set-target enforce requester ownership', async () => {
test('register-provider rejects cross-owner re-registration', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// First registration by plugin-a.
const first = await api.dispatch({
capability: 'note-detection', command: 'register-provider',
@@ -311,7 +311,7 @@ test('register-provider rejects cross-owner re-registration', async () => {
test('unregister-provider rejects cross-owner unregister', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
await registerMidiProvider(api);
// A different participant must not unregister a provider it does not own.
@@ -322,7 +322,7 @@ test('unregister-provider rejects cross-owner unregister', async () => {
assert.equal(steal.outcome, 'degraded', 'cross-owner unregister must be rejected');
// Provider must still be present after the failed cross-owner attempt.
const snap = window.slopsmith.noteDetection.snapshot();
const snap = window.feedBack.noteDetection.snapshot();
assert.ok(snap.providers.some(p => p.id === 'keys-midi'), 'provider must survive cross-owner unregister attempt');
// The original owner can still unregister.
@@ -335,7 +335,7 @@ test('unregister-provider rejects cross-owner unregister', async () => {
test('availability-changed fires only on 0→1 and 1→0 transitions', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = captureEvents(api, ['note-detection:availability-changed']);
// First registration: 0→1, should emit.
@@ -364,7 +364,7 @@ test('availability-changed fires only on 0→1 and 1→0 transitions', async ()
test('unregistering one of two providers from the same participant keeps participant in the pipeline', async () => {
const window = loadNoteDetection();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
// One plugin registers two providers under the same participantId.
await api.dispatch({
@@ -386,7 +386,7 @@ test('unregistering one of two providers from the same participant keeps partici
});
assert.equal(unreg.outcome, 'handled');
const snap = window.slopsmith.noteDetection.snapshot();
const snap = window.feedBack.noteDetection.snapshot();
assert.ok(!snap.providers.some(p => p.id === 'multi-provider-a'), 'provider-a must be removed');
assert.ok(snap.providers.some(p => p.id === 'multi-provider-b'), 'provider-b must still be present');
const participants = api.inspect('note-detection').participants || [];
+1 -1
View File
@@ -21,7 +21,7 @@ function loadCables(opts) {
opts = opts || {};
const rafCalls = [];
const created = []; // every createElementNS node
const win = { slopsmith: null };
const win = { feedBack: null };
win.addEventListener = () => {};
win.matchMedia = () => ({ matches: !!opts.reduce });
const doc = {
+2 -2
View File
@@ -26,7 +26,7 @@ function buildReadySandbox() {
const listeners = new Map();
const sandbox = {
window: {
slopsmith: {
feedBack: {
on(event, fn) { listeners.set(event, fn); },
off(event, fn) { if (listeners.get(event) === fn) listeners.delete(event); },
},
@@ -84,5 +84,5 @@ test('playback adapter suppresses duplicate HTML5 pause events before emitting c
const src = fs.readFileSync(APP_JS, 'utf8');
const fn = extractFunction(src, 'function _installPlaybackTransportAdapter()');
assert.match(fn, /if \(!window\._juceMode && wasPlaying\) \{\s*isPlaying = false;\s*window\.slopsmith\.isPlaying = false;\s*audio\.pause\(\);\s*_markPlaybackPaused\(\);\s*\}/);
assert.match(fn, /if \(!window\._juceMode && wasPlaying\) \{\s*isPlaying = false;\s*window\.feedBack\.isPlaying = false;\s*audio\.pause\(\);\s*_markPlaybackPaused\(\);\s*\}/);
});
+11 -11
View File
@@ -25,14 +25,14 @@ test('plugin fresh starts require user action and incompatible playback particip
const fixture = JSON.parse(fs.readFileSync(path.join(ROOT, 'tests', 'fixtures', 'plugin_capabilities', 'unsupported_capability_version.json'), 'utf8'));
fixture.id = 'future_playback';
fixture.capabilities = { playback: { roles: ['owner'], commands: ['inspect'], runtime: true, version: 999, handlers: { inspect: () => ({ outcome: 'handled' }) } } };
incompatibleWindow.slopsmith.capabilities.registerParticipants([fixture]);
const result = await incompatibleWindow.slopsmith.capabilities.dispatch({ capability: 'playback', command: 'inspect', requester: 'test' });
incompatibleWindow.feedBack.capabilities.registerParticipants([fixture]);
const result = await incompatibleWindow.feedBack.capabilities.dispatch({ capability: 'playback', command: 'inspect', requester: 'test' });
assert.equal(result.status, 'incompatible-version');
});
test('same-priority latest controls remain non-stale while user-priority commands deny background automation', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const firstPause = await dispatch(window, 'pause', { requesterId: 'plugin.a', priority: 'normal' });
const normalResume = await dispatch(window, 'resume', { requesterId: 'plugin.b', priority: 'normal' });
@@ -48,7 +48,7 @@ test('legacy bridge hits are attributed to playback compatibility shims', () =>
const window = loadPlayback();
const bridgeEvents = captureEvents(window, 'playback:bridge-hit');
window.slopsmith.playback.recordBridgeHit({
window.feedBack.playback.recordBridgeHit({
bridgeId: 'playback.window-play-song',
legacySurface: 'window.playSong',
source: 'core.app',
@@ -56,7 +56,7 @@ test('legacy bridge hits are attributed to playback compatibility shims', () =>
});
const playback = diagnosticsSnapshot(window);
const runtime = window.slopsmith.capabilities.snapshotDiagnostics();
const runtime = window.feedBack.capabilities.snapshotDiagnostics();
const shim = runtime.compatibilityShims.find(item => item.capability === 'playback' && item.legacySurface === 'window.playSong');
assert.equal(bridgeEvents.length, 1);
@@ -68,10 +68,10 @@ test('legacy bridge hits are attributed to playback compatibility shims', () =>
test('legacy song events update playback state without exposing raw filenames', () => {
const window = loadPlayback();
window.slopsmith.emit('song:loading', { filename: '/Users/example/Secret Folder/Artist - Song_p.archive', arrangement: 0 });
window.slopsmith.emit('song:loaded', makeTarget({ filename: '/Users/example/Secret Folder/Artist - Song_p.archive' }));
window.slopsmith.emit('song:play', { time: 4, audioT: 4, chartT: 4 });
window.slopsmith.emit('song:seek', { from: 4, to: 12, reason: 'seek-by' });
window.feedBack.emit('song:loading', { filename: '/Users/example/Secret Folder/Artist - Song_p.archive', arrangement: 0 });
window.feedBack.emit('song:loaded', makeTarget({ filename: '/Users/example/Secret Folder/Artist - Song_p.archive' }));
window.feedBack.emit('song:play', { time: 4, audioT: 4, chartT: 4 });
window.feedBack.emit('song:seek', { from: 4, to: 12, reason: 'seek-by' });
const playback = diagnosticsSnapshot(window);
const encoded = JSON.stringify(playback);
@@ -89,8 +89,8 @@ test('route changes are captured as redaction-safe playback lifecycle events', (
const changing = captureEvents(window, 'playback:route-changing');
const changed = captureEvents(window, 'playback:route-changed');
window.slopsmith.playback.recordRouteChange({ routeKind: 'desktop-native', state: 'switching', preservedTime: true, safeReason: 'desktop engine active' });
window.slopsmith.playback.recordRouteChange({ routeKind: 'desktop-native', state: 'active', preservedTime: true, safeReason: 'desktop route active' });
window.feedBack.playback.recordRouteChange({ routeKind: 'desktop-native', state: 'switching', preservedTime: true, safeReason: 'desktop engine active' });
window.feedBack.playback.recordRouteChange({ routeKind: 'desktop-native', state: 'active', preservedTime: true, safeReason: 'desktop route active' });
const playback = diagnosticsSnapshot(window);
assert.equal(changing.length, 1);
+10 -10
View File
@@ -4,7 +4,7 @@ const { loadPlayback, captureEvents, dispatch, diagnosticsSnapshot, makeTarget,
test('exported diagnostics pseudonymize targets while local inspector may show display names', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', {
authorization: 'user-action',
requesterId: 'core.player.controls',
@@ -33,7 +33,7 @@ test('exported diagnostics pseudonymize targets while local inspector may show d
test('diagnostic history is bounded for current and stopped sessions', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
for (let index = 0; index < 7; index += 1) {
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget({ filename: `song-${index}.archive`, title: `Song ${index}` }) });
@@ -55,11 +55,11 @@ test('diagnostic history is bounded for current and stopped sessions', async ()
test('diagnostics contribution is exported under playback schema', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const contribution = window.slopsmith.diagnostics.snapshotContributions().playback;
assert.equal(contribution.schema, 'slopsmith.playback.diagnostics.v1');
const contribution = window.feedBack.diagnostics.snapshotContributions().playback;
assert.equal(contribution.schema, 'feedBack.playback.diagnostics.v1');
assert.equal(contribution.domain, 'playback');
assert.equal(contribution.exportMode, 'exported');
assert.match(contribution.state.target.settingsKey, /^settings-v1-[a-z0-9]{7}$/);
@@ -68,11 +68,11 @@ test('diagnostics contribution is exported under playback schema', async () => {
test('diagnostics redact caller-supplied route and stale session ids', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
await dispatch(window, 'pause', { sessionId: '/Users/example/private-session?token=secret' }, 'plugin.remote');
window.slopsmith.playback.recordRouteChange({ routeId: '/Users/example/native-route?token=secret', routeKind: 'desktop-native', state: 'active', safeReason: 'ok' });
window.feedBack.playback.recordRouteChange({ routeId: '/Users/example/native-route?token=secret', routeKind: 'desktop-native', state: 'active', safeReason: 'ok' });
const snapshot = diagnosticsSnapshot(window);
const encoded = JSON.stringify(snapshot);
@@ -87,11 +87,11 @@ test('diagnostics redact caller-supplied route and stale session ids', async ()
test('diagnostics redact requester ids and raw camel-case payload keys', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', { authorization: 'user-action', target: makeTarget() }, '/Users/example/plugin token=secret');
const degradedEvents = captureEvents(window, 'playback:degraded');
window.slopsmith.playback.transportEvent('degraded', {
window.feedBack.playback.transportEvent('degraded', {
requesterId: '/Users/example/transport token=secret',
accessToken: 'plain-secret-token',
nativeHandleRef: 'native-secret-handle',
@@ -99,7 +99,7 @@ test('diagnostics redact requester ids and raw camel-case payload keys', async (
reason: '/Users/example/private song.archive token=secret',
safeDetail: 'safe value',
});
window.slopsmith.playback.recordBridgeHit({
window.feedBack.playback.recordBridgeHit({
bridgeId: '/Users/example/bridge token=secret',
legacySurface: 'window.playSong',
source: '/Users/example/source token=secret',
+23 -23
View File
@@ -5,12 +5,12 @@ const { loadCapabilities } = require('./capabilities_test_harness');
test('playback reports no-owner no-handler and unsupported command outcomes explicitly', async () => {
const noOwnerWindow = loadCapabilities();
const noOwner = await noOwnerWindow.slopsmith.capabilities.dispatch({ capability: 'playback', command: 'inspect', requester: 'test' });
const noOwner = await noOwnerWindow.feedBack.capabilities.dispatch({ capability: 'playback', command: 'inspect', requester: 'test' });
assert.equal(noOwner.status, 'no-owner');
const noHandlerWindow = loadCapabilities();
noHandlerWindow.slopsmith.capabilities.registerOwner('playback', { pluginId: 'test-owner', commands: ['inspect'], events: [] });
const noHandler = await noHandlerWindow.slopsmith.capabilities.dispatch({ capability: 'playback', command: 'inspect', requester: 'test' });
noHandlerWindow.feedBack.capabilities.registerOwner('playback', { pluginId: 'test-owner', commands: ['inspect'], events: [] });
const noHandler = await noHandlerWindow.feedBack.capabilities.dispatch({ capability: 'playback', command: 'inspect', requester: 'test' });
assert.equal(noHandler.status, 'no-handler');
const unsupportedWindow = loadPlayback();
@@ -20,7 +20,7 @@ test('playback reports no-owner no-handler and unsupported command outcomes expl
test('playback registers as an active core owner', async () => {
const window = loadPlayback();
const snapshot = window.slopsmith.capabilities.snapshotDiagnostics();
const snapshot = window.feedBack.capabilities.snapshotDiagnostics();
const playback = snapshot.pipelines.find(pipeline => pipeline.name === 'playback');
assert.ok(playback, 'playback pipeline exists');
@@ -40,7 +40,7 @@ test('start requires a target and explicit user authorization for fresh audible
assert.equal(noGesture.status, 'user-action-required');
const adapter = makeAdapter();
window.slopsmith.playback.registerTransportAdapter(adapter);
window.feedBack.playback.registerTransportAdapter(adapter);
const events = captureEvents(window, 'playback:ready');
const result = await dispatch(window, 'start', { requesterId: 'core.player.controls', authorization: 'user-action', target: makeTarget() });
@@ -56,7 +56,7 @@ test('start requires a target and explicit user authorization for fresh audible
test('settings key is stable across arrangements while target id remains arrangement scoped', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
const base = makeTarget({ filename: '/Users/example/DLC/Artist - Song_p.archive', arrangement: 'Lead', arrangementIndex: 0 });
await dispatch(window, 'start', { requesterId: 'core.player.controls', authorization: 'user-action', target: base });
@@ -71,7 +71,7 @@ test('settings key is stable across arrangements while target id remains arrange
test('unsafe caller-supplied settings keys are hashed before exposure', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', {
requesterId: 'core.player.controls',
@@ -86,7 +86,7 @@ test('unsafe caller-supplied settings keys are hashed before exposure', async ()
test('unsafe caller-supplied target ids are hashed before exposure', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', {
requesterId: 'core.player.controls',
@@ -101,7 +101,7 @@ test('unsafe caller-supplied target ids are hashed before exposure', async () =>
test('dispatch requester owns playback command attribution', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
const started = await dispatch(window, 'start', { requesterId: 'core.player.controls', authorization: 'user-action', target: makeTarget() }, 'plugin.remote');
const paused = await dispatch(window, 'pause', { requesterId: 'core.player.controls' }, 'plugin.remote');
@@ -118,10 +118,10 @@ test('dispatch requester owns playback command attribution', async () => {
test('transport commands emit ordered lifecycle events and normalize outcomes', async () => {
const window = loadPlayback();
const adapter = makeAdapter({ duration: 10 });
window.slopsmith.playback.registerTransportAdapter(adapter);
window.feedBack.playback.registerTransportAdapter(adapter);
const events = [];
for (const eventName of ['playback:requested', 'playback:loading', 'playback:ready', 'playback:paused', 'playback:resumed', 'playback:seeking', 'playback:seeked', 'playback:loop-set', 'playback:loop-cleared']) {
window.slopsmith.on(eventName, event => events.push(event.type.replace('playback:', '')));
window.feedBack.on(eventName, event => events.push(event.type.replace('playback:', '')));
}
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
@@ -141,22 +141,22 @@ test('transport commands emit ordered lifecycle events and normalize outcomes',
test('seek preserves pre-seek playback state and external seek events update state', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter({ startPlaying: true, seekResult: { completed: true, from: 1, to: 5 } }));
window.feedBack.playback.registerTransportAdapter(makeAdapter({ startPlaying: true, seekResult: { completed: true, from: 1, to: 5 } }));
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const seek = await dispatch(window, 'seek', { requesterId: 'core.player.controls', time: 5 });
assert.equal(seek.status, 'completed');
assert.equal(diagnosticsSnapshot(window).state.state, 'playing');
window.slopsmith.playback.transportEvent('seeking', { requesterId: 'core.player.controls', media: { currentTime: 5 }, isPlaying: true });
window.feedBack.playback.transportEvent('seeking', { requesterId: 'core.player.controls', media: { currentTime: 5 }, isPlaying: true });
assert.equal(diagnosticsSnapshot(window).state.state, 'seeking');
window.slopsmith.playback.transportEvent('seeked', { requesterId: 'core.player.controls', media: { currentTime: 9 }, isPlaying: true });
window.feedBack.playback.transportEvent('seeked', { requesterId: 'core.player.controls', media: { currentTime: 9 }, isPlaying: true });
assert.equal(diagnosticsSnapshot(window).state.state, 'playing');
});
test('clear-loop requires an active playback session', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
const cleared = await dispatch(window, 'clear-loop', { requesterId: 'core.player.controls' });
assert.equal(cleared.status, 'no-target');
@@ -164,13 +164,13 @@ test('clear-loop requires an active playback session', async () => {
test('ended transport events and seek failure/rollback outcomes are distinguishable', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter({ seekResult: { completed: true, from: 5, to: 4.5 } }));
window.feedBack.playback.registerTransportAdapter(makeAdapter({ seekResult: { completed: true, from: 5, to: 4.5 } }));
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const rolledBack = await dispatch(window, 'seek', { requesterId: 'core.player.controls', time: 8 });
assert.equal(rolledBack.status, 'rolled-back');
const failedWindow = loadPlayback();
failedWindow.slopsmith.playback.registerTransportAdapter(makeAdapter({ seekError: 'seek failed' }));
failedWindow.feedBack.playback.registerTransportAdapter(makeAdapter({ seekError: 'seek failed' }));
await dispatch(failedWindow, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const failed = await dispatch(failedWindow, 'seek', { requesterId: 'core.player.controls', time: 3 });
assert.equal(failed.status, 'failed');
@@ -178,26 +178,26 @@ test('ended transport events and seek failure/rollback outcomes are distinguisha
assert.equal(diagnosticsSnapshot(failedWindow).state.state, 'paused');
const unsupportedWindow = loadPlayback();
unsupportedWindow.slopsmith.playback.registerTransportAdapter({ inspect: () => ({ currentTime: 0, duration: 120, isPlaying: true }), start: () => ({ currentTime: 0, duration: 120, isPlaying: true }) });
unsupportedWindow.feedBack.playback.registerTransportAdapter({ inspect: () => ({ currentTime: 0, duration: 120, isPlaying: true }), start: () => ({ currentTime: 0, duration: 120, isPlaying: true }) });
await dispatch(unsupportedWindow, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const unsupported = await dispatch(unsupportedWindow, 'seek', { requesterId: 'core.player.controls', time: 3 });
assert.equal(unsupported.status, 'unsupported-command');
assert.equal(diagnosticsSnapshot(unsupportedWindow).state.state, 'playing');
const malformedWindow = loadPlayback();
malformedWindow.slopsmith.playback.registerTransportAdapter(makeAdapter({ seekResult: { completed: true, from: 2, to: NaN } }));
malformedWindow.feedBack.playback.registerTransportAdapter(makeAdapter({ seekResult: { completed: true, from: 2, to: NaN } }));
await dispatch(malformedWindow, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const malformed = await dispatch(malformedWindow, 'seek', { requesterId: 'core.player.controls', time: 6 });
assert.equal(malformed.status, 'failed');
assert.match(malformed.reason, /malformed seek result/i);
window.slopsmith.playback.transportEvent('ended', { requesterId: 'core.player.controls', currentTime: 120 });
window.feedBack.playback.transportEvent('ended', { requesterId: 'core.player.controls', currentTime: 120 });
assert.equal(diagnosticsSnapshot(window).state.state, 'ended');
});
test('invalid loop boundaries do not mutate an active loop', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
await dispatch(window, 'set-loop', { requesterId: 'core.player.controls', startTime: 2, endTime: 4 });
@@ -212,7 +212,7 @@ test('invalid loop boundaries do not mutate an active loop', async () => {
test('normal resume is denied after a user-priority pause until a user action resumes', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter());
window.feedBack.playback.registerTransportAdapter(makeAdapter());
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
await dispatch(window, 'pause', { requesterId: 'core.player.controls', priority: 'user' });
@@ -225,7 +225,7 @@ test('normal resume is denied after a user-priority pause until a user action re
test('stale and cancelled operations are reported distinctly', async () => {
const window = loadPlayback();
window.slopsmith.playback.registerTransportAdapter(makeAdapter({ seekResult: { completed: false, from: 2, to: NaN } }));
window.feedBack.playback.registerTransportAdapter(makeAdapter({ seekResult: { completed: false, from: 2, to: NaN } }));
await dispatch(window, 'start', { authorization: 'user-action', requesterId: 'core.player.controls', target: makeTarget() });
const sessionId = diagnosticsSnapshot(window).state.sessionId;
+3 -3
View File
@@ -27,16 +27,16 @@ function loadInspector(window) {
function captureEvents(window, eventName) {
const events = [];
window.slopsmith.on(eventName, event => events.push(event.detail));
window.feedBack.on(eventName, event => events.push(event.detail));
return events;
}
function diagnosticsSnapshot(window, options = {}) {
return window.slopsmith.playback.snapshot(options);
return window.feedBack.playback.snapshot(options);
}
function dispatch(window, command, payload = {}, requester = 'test') {
return window.slopsmith.capabilities.dispatch({ capability: 'playback', command, args: payload, requester });
return window.feedBack.capabilities.dispatch({ capability: 'playback', command, args: payload, requester });
}
function makeTarget(overrides = {}) {
+1 -1
View File
@@ -21,7 +21,7 @@ const SRC = path.join(__dirname, '..', '..', 'static', 'v3', 'plugins-page.js');
function loadPage(opts) {
opts = opts || {};
const store = opts.store || {};
const win = { slopsmith: null };
const win = { feedBack: null };
win.localStorage = {
getItem: (k) => (k in store ? store[k] : null),
setItem: (k, v) => { if (opts.throwOnSet) throw new Error('quota'); store[k] = String(v); },
+10 -10
View File
@@ -129,7 +129,7 @@ function createProgressWindow(progressionState) {
refresh() { return Promise.resolve(progressionState); },
},
playSong() {},
slopsmith: {
feedBack: {
on(type, handler) {
const list = listeners.get(type) || [];
list.push(handler);
@@ -163,14 +163,14 @@ test('progression:calibration-attempt with 0.92 shows retry overlay and So close
mastery_rank: 0,
onboarding: {
calibration_status: 'pending',
diagnostic_filename: 'diagnostics-builtin/slopsmith-diagnostic-basic-guitar.sloppak',
diagnostic_filename: 'diagnostics-builtin/feedBack-diagnostic-basic-guitar.sloppak',
},
paths: [],
wallet: { balance: 0, lifetime_db: 0 },
quests: {},
});
win.slopsmith.emit('progression:calibration-attempt', { accuracy: 0.92 });
win.feedBack.emit('progression:calibration-attempt', { accuracy: 0.92 });
const overlay = win.document.getElementById('v3-calibration-retry');
assert.ok(overlay, 'retry overlay should exist');
@@ -184,14 +184,14 @@ test('progression:calibration-completed shows success overlay and Setup verified
mastery_rank: 0,
onboarding: {
calibration_status: 'pending',
diagnostic_filename: 'diagnostics-builtin/slopsmith-diagnostic-basic-guitar.sloppak',
diagnostic_filename: 'diagnostics-builtin/feedBack-diagnostic-basic-guitar.sloppak',
},
paths: [],
wallet: { balance: 0, lifetime_db: 0 },
quests: {},
});
win.slopsmith.emit('progression:calibration-completed', {});
win.feedBack.emit('progression:calibration-completed', {});
const overlay = win.document.getElementById('v3-calibration-success');
assert.ok(overlay, 'success overlay should exist');
@@ -205,14 +205,14 @@ test('success overlay for skipped state does not claim rank-up', () => {
mastery_rank: 1,
onboarding: {
calibration_status: 'skipped',
diagnostic_filename: 'diagnostics-builtin/slopsmith-diagnostic-basic-guitar.sloppak',
diagnostic_filename: 'diagnostics-builtin/feedBack-diagnostic-basic-guitar.sloppak',
},
paths: [],
wallet: { balance: 0, lifetime_db: 0 },
quests: {},
});
win.slopsmith.emit('progression:calibration-completed', {});
win.feedBack.emit('progression:calibration-completed', {});
const overlay = win.document.getElementById('v3-calibration-success');
assert.ok(overlay);
@@ -226,15 +226,15 @@ test('calibration-completed does not stack duplicate success overlays', () => {
mastery_rank: 1,
onboarding: {
calibration_status: 'skipped',
diagnostic_filename: 'diagnostics-builtin/slopsmith-diagnostic-basic-guitar.sloppak',
diagnostic_filename: 'diagnostics-builtin/feedBack-diagnostic-basic-guitar.sloppak',
},
paths: [],
wallet: { balance: 0, lifetime_db: 0 },
quests: {},
});
win.slopsmith.emit('progression:calibration-completed', {});
win.slopsmith.emit('progression:calibration-completed', {});
win.feedBack.emit('progression:calibration-completed', {});
win.feedBack.emit('progression:calibration-completed', {});
const overlays = win.__bodyChildren.filter((el) => el.id === 'v3-calibration-success');
assert.equal(overlays.length, 1);
+1 -1
View File
@@ -54,7 +54,7 @@ function load(progressionState) {
// Deliver a CustomEvent-like wrapper ({detail}), exactly as the real bus
// does (capabilities.js: bus.on → addEventListener, fn gets a CustomEvent).
// Test call sites pass the raw payload; the handler must unwrap e.detail.
sandbox.window.slopsmith = { on: (name, fn) => { handlers[name] = (payload) => fn({ detail: payload }); } };
sandbox.window.feedBack = { on: (name, fn) => { handlers[name] = (payload) => fn({ detail: payload }); } };
sandbox.window.v3Progression = { get: () => progressionState };
vm.createContext(sandbox);
vm.runInContext(SRC, sandbox);
+1 -1
View File
@@ -23,7 +23,7 @@ function load(states) {
fetch: async () => ({ ok: true, json: async () => states[Math.min(i++, states.length - 1)] }),
};
sandbox.window = sandbox;
sandbox.window.slopsmith = { emit: (name, detail) => events.push({ name, detail }) };
sandbox.window.feedBack = { emit: (name, detail) => events.push({ name, detail }) };
sandbox.document = { readyState: 'complete', addEventListener: () => {} };
vm.createContext(sandbox);
vm.runInContext(SRC, sandbox);
+2 -2
View File
@@ -62,10 +62,10 @@ function loadClose(sandbox, src) {
vm.runInContext(code, sandbox);
}
test('closeCurrentSong is exported on window and window.slopsmith', () => {
test('closeCurrentSong is exported on window and window.feedBack', () => {
const src = fs.readFileSync(APP_JS, 'utf8');
assert.match(src, /window\.closeCurrentSong\s*=\s*closeCurrentSong/);
assert.match(src, /window\.slopsmith\.closeCurrentSong\s*=\s*closeCurrentSong/);
assert.match(src, /window\.feedBack\.closeCurrentSong\s*=\s*closeCurrentSong/);
});
test('closeCurrentSong uses _playerOriginScreen when set', async () => {
+3 -3
View File
@@ -112,8 +112,8 @@ test('every song:play/pause/ended emit uses _songEventPayload', () => {
const src = fs.readFileSync(APP_JS, 'utf8');
const lines = src.split('\n');
// Accept aliased calls like `sm.emit(...)` (the JUCE shim caches
// window.slopsmith in `sm`) — not just literal `window.slopsmith.emit`.
const emitRe = /(?:window\.slopsmith|\w+)\.emit\(\s*['"]song:(play|pause|ended)['"]/;
// window.feedBack in `sm`) — not just literal `window.feedBack.emit`.
const emitRe = /(?:window\.feedBack|\w+)\.emit\(\s*['"]song:(play|pause|ended)['"]/;
const okRe = /_songEventPayload\(\)|,\s*payload\s*\)/;
const offending = [];
for (const line of lines) {
@@ -134,7 +134,7 @@ test('there are at least 8 song:* emit sites threaded through the helper', () =>
// count drops, someone removed an emit (regression) or refactored an
// event away (intentional — this test then needs updating).
const src = fs.readFileSync(APP_JS, 'utf8');
const matches = src.match(/(?:window\.slopsmith|\w+)\.emit\(\s*['"]song:(play|pause|ended)['"][^)]*\)/g) || [];
const matches = src.match(/(?:window\.feedBack|\w+)\.emit\(\s*['"]song:(play|pause|ended)['"][^)]*\)/g) || [];
assert.ok(
matches.length >= 8,
`expected ≥8 song:* emits, found ${matches.length}`,
+3 -3
View File
@@ -27,7 +27,7 @@ function buildSandbox({ loopA = null, loopB = null, isPlaying = false } = {}) {
__togglePlayCalls: 0,
__clearLoopCalls: 0,
window: {
slopsmith: {
feedBack: {
getLoop() {
return { loopA: sandbox.loopA, loopB: sandbox.loopB };
},
@@ -68,10 +68,10 @@ function loadRestart(sandbox, src, { audioSeekImpl } = {}) {
vm.runInContext(code, sandbox);
}
test('restartCurrentSong is exported on window and window.slopsmith', () => {
test('restartCurrentSong is exported on window and window.feedBack', () => {
const src = fs.readFileSync(APP_JS, 'utf8');
assert.match(src, /window\.restartCurrentSong\s*=\s*restartCurrentSong/);
assert.match(src, /window\.slopsmith\.restartCurrentSong\s*=\s*restartCurrentSong/);
assert.match(src, /window\.feedBack\.restartCurrentSong\s*=\s*restartCurrentSong/);
});
test('no loop: seeks to 0 with song-restart and starts playback when stopped', async () => {
+1 -1
View File
@@ -55,7 +55,7 @@ function buildSandbox({ juceMode = false, currentTime = 10, duration = Infinity
jucePlayer,
window: {
_juceMode: juceMode,
slopsmith: {
feedBack: {
emit(event, detail) { emitCalls.push({ event, detail }); },
},
},
+2 -2
View File
@@ -74,11 +74,11 @@ function buildSandbox({ juceMode = false } = {}) {
_juceAudioUrl: juceMode ? '/audio/old-song.ogg' : null,
_currentSongAudio: { url: '/audio/old-song.ogg' },
_clearJuceRerouteMemo() {},
slopsmith: {
feedBack: {
isPlaying: true,
emit() {},
},
slopsmithDesktop: {
feedBackDesktop: {
audio: {
setBackingSpeed(rate) {
backingCalls.push(['setBackingSpeed', rate]);
+4 -4
View File
@@ -1,5 +1,5 @@
// Source-level guards for the consolidated tour menu (slopsmith#272).
// The engine lives in a DOMContentLoaded handler that wires window.slopsmith,
// Source-level guards for the consolidated tour menu (feedBack#272).
// The engine lives in a DOMContentLoaded handler that wires window.feedBack,
// localStorage, and Shepherd — too much browser surface to reproduce cleanly
// in a vm sandbox. These checks lock in the contract (viz relevance gating,
// complete-vs-cancel semantics, waitFor validation, focus management,
@@ -115,9 +115,9 @@ test('_updateMenuVisibility dismisses orphan toast when relevance drops to zero'
test('popover has role=dialog with aria-controls wired from the trigger', () => {
const fn = extractBlock(SRC, 'function _ensureMenu()');
assert.match(fn, /setAttribute\(\s*['"]aria-controls['"]\s*,\s*['"]slopsmith-tour-menu-popover['"]/,
assert.match(fn, /setAttribute\(\s*['"]aria-controls['"]\s*,\s*['"]feedBack-tour-menu-popover['"]/,
'trigger must wire aria-controls to the popover id');
assert.match(fn, /_menuPopover\.id\s*=\s*['"]slopsmith-tour-menu-popover['"]/,
assert.match(fn, /_menuPopover\.id\s*=\s*['"]feedBack-tour-menu-popover['"]/,
'popover must carry the matching id');
assert.match(fn, /setAttribute\(\s*['"]role['"]\s*,\s*['"]dialog['"]/,
'popover must use role=dialog (not the menu role we don\'t implement)');
+9 -9
View File
@@ -12,19 +12,19 @@ const TUNER_SCREEN_JS = path.join(__dirname, '..', '..', 'plugins', 'tuner', 'sc
function loadTuningHelpers() {
const src = fs.readFileSync(APP_JS, 'utf8');
const start = src.indexOf('function isBassArrangement(');
const endMarker = 'window.slopsmith.parseRawTuningOffsets = parseRawTuningOffsets;';
const endMarker = 'window.feedBack.parseRawTuningOffsets = parseRawTuningOffsets;';
const end = src.indexOf(endMarker);
if (start === -1 || end === -1) throw new Error('tuning helper block not found in app.js');
const sandbox = { window: { slopsmith: {} }, exports: {} };
const sandbox = { window: { feedBack: {} }, exports: {} };
vm.createContext(sandbox);
vm.runInContext(
src.slice(start, end + endMarker.length),
sandbox
);
return sandbox.window.slopsmith;
return sandbox.window.feedBack;
}
const slopsmithHelpers = loadTuningHelpers();
const feedBackHelpers = loadTuningHelpers();
function createTunerSandbox() {
const enableCalls = [];
@@ -101,7 +101,7 @@ function createTunerSandbox() {
__setPlayerActive(v) { playerActive = v; },
__setSongInfo(info) {
songInfo = info;
sandbox.window.slopsmith.currentSong = info ? {
sandbox.window.feedBack.currentSong = info ? {
filename: info.filename || 'song.sloppak',
arrangementIndex: info.arrangement_index,
tuning: info.tuning,
@@ -111,8 +111,8 @@ function createTunerSandbox() {
};
sandbox.window = sandbox;
sandbox.window.slopsmith = {
...slopsmithHelpers,
sandbox.window.feedBack = {
...feedBackHelpers,
on() {},
off() {},
currentSong: null,
@@ -318,8 +318,8 @@ test('song:loading clears dismiss state for next load', async () => {
test('screen.js registers song:loading and song:ready auto-open listeners at boot', () => {
const src = fs.readFileSync(TUNER_SCREEN_JS, 'utf8');
assert.match(src, /function _installAutoOpenListeners/);
assert.match(src, /window\.slopsmith\.on\('song:loading', _onAutoOpenSongLoading\)/);
assert.match(src, /window\.slopsmith\.on\('song:ready', _onAutoOpenSongReady\)/);
assert.match(src, /window\.feedBack\.on\('song:loading', _onAutoOpenSongLoading\)/);
assert.match(src, /window\.feedBack\.on\('song:ready', _onAutoOpenSongReady\)/);
assert.match(src, /function _tuningIdentityKey/);
assert.doesNotMatch(src, /restartCurrentSong/);
});
+3 -3
View File
@@ -35,7 +35,7 @@ function loadTuningDisplayHelpers() {
extractBlock(src, 'function parseRawTuningOffsets('),
extractBlock(src, 'function displayTuningName('),
].join('\n');
const sandbox = { window: { slopsmith: {} }, exports: {} };
const sandbox = { window: { feedBack: {} }, exports: {} };
vm.createContext(sandbox);
vm.runInContext(block + '\nexports.displayTuningName = displayTuningName;', sandbox);
return sandbox.exports;
@@ -57,7 +57,7 @@ test('displayTuningName sanitizes raw offset strings to Custom Tuning', () => {
assert.equal(displayTuningName('-3,-1,0,1,2,3'), 'Custom Tuning');
});
test('displayTuningName names a known raw offset string (slopsmith#867)', () => {
test('displayTuningName names a known raw offset string (feedBack#867)', () => {
// Now that the API serves raw offsets, a known tuning passed as a raw
// string must resolve to its real name, not collapse to Custom Tuning.
assert.equal(displayTuningName('-1 -1 -1 -1 -1 -1'), 'Eb Standard');
@@ -67,7 +67,7 @@ test('displayTuningName names a known raw offset string (slopsmith#867)', () =>
assert.equal(displayTuningName('-2 0 0 0 -2 1'), 'Custom Tuning');
});
test('displayTuningName recognizes 4/5-string uniform standard (slopsmith#867)', () => {
test('displayTuningName recognizes 4/5-string uniform standard (feedBack#867)', () => {
// A normal 4-string bass [0,0,0,0] must not fall through to Custom Tuning.
assert.equal(displayTuningName(null, [0, 0, 0, 0]), 'E Standard');
assert.equal(displayTuningName(null, [-2, -2, -2, -2]), 'D Standard');
+2 -2
View File
@@ -16,10 +16,10 @@ const V2_HTML = path.join(__dirname, '..', '..', 'static', 'index.html');
function loadTuningHelpers() {
const src = fs.readFileSync(APP_JS, 'utf8');
const start = src.indexOf('function isBassArrangement(');
const endMarker = 'window.slopsmith.parseRawTuningOffsets = parseRawTuningOffsets;';
const endMarker = 'window.feedBack.parseRawTuningOffsets = parseRawTuningOffsets;';
const end = src.indexOf(endMarker);
if (start === -1 || end === -1) throw new Error('tuning helper block not found in app.js');
const sandbox = { window: { slopsmith: {} }, exports: {} };
const sandbox = { window: { feedBack: {} }, exports: {} };
vm.createContext(sandbox);
vm.runInContext(
src.slice(start, end + endMarker.length) + '\n'
+2 -2
View File
@@ -28,10 +28,10 @@ function extractBlock(src, startMarker) {
function loadTuningHelpers() {
const src = fs.readFileSync(APP_JS, 'utf8');
const start = src.indexOf('function _looksLikeRawTuningOffsets(');
const endMarker = 'window.slopsmith.parseRawTuningOffsets = parseRawTuningOffsets;';
const endMarker = 'window.feedBack.parseRawTuningOffsets = parseRawTuningOffsets;';
const end = src.indexOf(endMarker);
if (start === -1 || end === -1) throw new Error('tuning helpers not found');
const sandbox = { window: { slopsmith: {} }, exports: {} };
const sandbox = { window: { feedBack: {} }, exports: {} };
vm.createContext(sandbox);
vm.runInContext(
src.slice(start, end + endMarker.length) + '\n'
+8 -8
View File
@@ -23,12 +23,12 @@ test('venueMoodClassForState maps performance states', () => {
assert.equal(venue.venueMoodClassForState('unknown'), 'venue-mood-state-idle');
});
test('setting persistence key is slopsmith-venue-mood-fx', () => {
assert.equal(venue.KEY, 'slopsmith-venue-mood-fx');
test('setting persistence key is feedBack-venue-mood-fx', () => {
assert.equal(venue.KEY, 'feedBack-venue-mood-fx');
});
test('venue motion setting key is slopsmith-venue-motion', () => {
assert.equal(venue.MOTION_KEY, 'slopsmith-venue-motion');
test('venue motion setting key is feedBack-venue-motion', () => {
assert.equal(venue.MOTION_KEY, 'feedBack-venue-motion');
assert.equal(venue.MOTION_DEFAULT, 'subtle');
});
@@ -296,7 +296,7 @@ test('venue visualization session hides strip and marks scene pending', () => {
sceneWash.classList.add('hidden');
const vizPicker = new El();
vizPicker.value = 'venue';
const storage = new Map([['slopsmith-venue-mood-fx', 'full']]);
const storage = new Map([['feedBack-venue-mood-fx', 'full']]);
const origDocument = global.document;
global.document = {
getElementById(id) {
@@ -404,7 +404,7 @@ test('window.v3VenueMoodFx exposes getState with venue flag', () => {
assert.equal(typeof st.isVenueVisualization, 'boolean');
});
test('setMotion persists slopsmith-venue-motion and syncs renderer', () => {
test('setMotion persists feedBack-venue-motion and syncs renderer', () => {
const storage = new Map();
let synced = null;
global.localStorage = {
@@ -416,7 +416,7 @@ test('setMotion persists slopsmith-venue-motion and syncs renderer', () => {
global.document = { getElementById: () => null };
try {
assert.equal(venue.setMotion('full'), 'full');
assert.equal(storage.get('slopsmith-venue-motion'), 'full');
assert.equal(storage.get('feedBack-venue-motion'), 'full');
assert.equal(synced, 'full');
assert.equal(venue.setMotion('nope'), 'subtle');
assert.equal(synced, 'subtle');
@@ -448,7 +448,7 @@ test('bindRuntime wires venue motion select', () => {
let synced = null;
globalThis.h3dVenueSceneSetMotionMode = (mode) => { synced = mode; };
global.localStorage = {
getItem(k) { return k === 'slopsmith-venue-motion' ? 'subtle' : null; },
getItem(k) { return k === 'feedBack-venue-motion' ? 'subtle' : null; },
setItem() {},
};
global.document = {
+11 -11
View File
@@ -119,7 +119,7 @@ test('venue-scene-3d syncs instrument POV from arrangement signal', () => {
global.highway = { getSongInfo: () => ({ arrangement: 'Bass' }) };
global.v3VenueViz = venueViz;
global.v3VenueInstrumentPov = pov;
global.slopsmith = { on() {} };
global.feedBack = { on() {} };
try {
venueScene.activate();
assert.equal(global._venuePovInput, 'Bass');
@@ -140,7 +140,7 @@ test('venue-scene-3d syncs instrument POV from arrangement signal', () => {
delete global.highway;
delete global.v3VenueViz;
delete global.v3VenueInstrumentPov;
delete global.slopsmith;
delete global.feedBack;
delete global._venuePovInput;
}
});
@@ -156,7 +156,7 @@ test('lyrics visibility during guitar practice does not force vocals POV', () =>
};
global.v3VenueViz = venueViz;
global.v3VenueInstrumentPov = pov;
global.slopsmith = { on() {} };
global.feedBack = { on() {} };
try {
venueScene.activate();
assert.equal(global._venuePovInput, 'Lead');
@@ -174,7 +174,7 @@ test('lyrics visibility during guitar practice does not force vocals POV', () =>
delete global.highway;
delete global.v3VenueViz;
delete global.v3VenueInstrumentPov;
delete global.slopsmith;
delete global.feedBack;
delete global._venuePovInput;
}
});
@@ -212,7 +212,7 @@ test('syncViz activates only for venue visualization id', () => {
global.h3dVenueSceneGetState = () => ({ active: !!global._h3dActive, assetsLoaded: false, loadFailed: false });
global.v3VenueViz = venueViz;
global.v3VenueInstrumentPov = pov;
global.slopsmith = { on() {} };
global.feedBack = { on() {} };
try {
venueScene.deactivate();
venueScene.syncViz('highway_3d');
@@ -229,7 +229,7 @@ test('syncViz activates only for venue visualization id', () => {
delete global.h3dVenueSceneGetState;
delete global.v3VenueViz;
delete global.v3VenueInstrumentPov;
delete global.slopsmith;
delete global.feedBack;
delete global._h3dActive;
delete global._h3dMood;
}
@@ -262,7 +262,7 @@ test('live performance state forwards mood to highway venue scene API', () => {
isVenueVisualization: () => true,
readVizSelection: () => 'venue',
};
global.slopsmith = { on() {} };
global.feedBack = { on() {} };
try {
venueScene.activate();
venueScene.onPerformanceState({ detail: { state: 'fire' } });
@@ -276,7 +276,7 @@ test('live performance state forwards mood to highway venue scene API', () => {
delete global.h3dVenueSceneSetInstrumentPov;
delete global.h3dVenueSceneGetState;
delete global.v3VenueViz;
delete global.slopsmith;
delete global.feedBack;
delete global._mood;
}
});
@@ -325,7 +325,7 @@ test('venue-scene-3d syncs motion on activate', () => {
global.v3VenueMoodFx = { getMotion: () => 'full' };
global.v3VenueViz = venueViz;
global.v3VenueInstrumentPov = pov;
global.slopsmith = { on() {} };
global.feedBack = { on() {} };
try {
venueScene.activate();
assert.equal(global._venueMotion, 'full');
@@ -342,7 +342,7 @@ test('venue-scene-3d syncs motion on activate', () => {
delete global.v3VenueMoodFx;
delete global.v3VenueViz;
delete global.v3VenueInstrumentPov;
delete global.slopsmith;
delete global.feedBack;
delete global._venueMotion;
}
});
@@ -360,7 +360,7 @@ test('runtime safety: motion pass does not touch scoring detection timing or aud
'plugins/scoring',
'static/audio',
'AudioEngine',
'slopsmith-desktop',
'feedBack-desktop',
];
const allowedTouched = [
'plugins/highway_3d/screen.js',
+5 -5
View File
@@ -145,7 +145,7 @@ test('app.js adds Venue visualization option and adapter', () => {
assert.match(src, /opt\.value = 'venue'/);
assert.match(src, /opt\.textContent = 'Venue'/);
assert.match(src, /if \(id === 'venue'\)/);
assert.match(src, /slopsmithViz_highway_3d/);
assert.match(src, /feedBackViz_highway_3d/);
assert.match(src, /localStorage\.setItem\('vizSelection', 'venue'\)/);
assert.match(src, /_installVizRenderer\(venueRenderer, 'highway_3d'\)/);
assert.match(src, /onVenueVisualizationSelected/);
@@ -170,15 +170,15 @@ test('onVenueVisualizationSelected defaults mood to full only on first selection
};
try {
// No stored preference yet → default the mood to FULL.
storage.delete('slopsmith-venue-mood-fx');
storage.delete('feedBack-venue-mood-fx');
venue.onVenueVisualizationSelected();
assert.equal(venue.get(), 'full');
// An explicit 'subtle' choice must be preserved, not clobbered to full.
storage.set('slopsmith-venue-mood-fx', 'subtle');
storage.set('feedBack-venue-mood-fx', 'subtle');
venue.onVenueVisualizationSelected();
assert.equal(venue.get(), 'subtle');
// 'off' likewise preserved.
storage.set('slopsmith-venue-mood-fx', 'off');
storage.set('feedBack-venue-mood-fx', 'off');
venue.onVenueVisualizationSelected();
assert.equal(venue.get(), 'off');
} finally {
@@ -213,7 +213,7 @@ test('venue mood source documents strip overlay disabled', () => {
test('app.js preserves plugin viz population for drum/tab/piano highways', () => {
const src = fs.readFileSync(APP_JS, 'utf8');
assert.match(src, /p\.type === 'visualization'/);
assert.match(src, /slopsmithViz_/);
assert.match(src, /feedBackViz_/);
assert.match(src, /BUILTIN_OPT_VALUES/);
});
+36 -36
View File
@@ -26,7 +26,7 @@ function captureEvents(api, eventNames) {
test('visualization domain registers an active provider-coordinator owner', () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const pipeline = api.inspect('visualization');
assert.ok(pipeline, 'visualization pipeline exists');
const owner = (pipeline.participants || []).find(p => p.pluginId === 'core.visualization');
@@ -36,12 +36,12 @@ test('visualization domain registers an active provider-coordinator owner', () =
[...owner.commands].sort(),
['clear-renderer', 'inspect', 'list-providers', 'select-renderer'],
);
assert.equal(window.slopsmith.vizDomain.version, 1);
assert.equal(window.feedBack.vizDomain.version, 1);
});
test('visualization is no longer a reserved future domain', async () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const result = await api.dispatch({ capability: 'visualization', command: 'inspect', source: 'test' });
assert.equal(result.outcome, 'handled');
assert.equal(result.payload.current, 'default');
@@ -49,14 +49,14 @@ test('visualization is no longer a reserved future domain', async () => {
test('refreshProviders registers participants with factory metadata', () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const highway3d = () => ({});
highway3d.contextType = 'webgl2';
highway3d.matchesArrangement = () => true;
window.slopsmithViz_highway_3d = highway3d;
window.slopsmithViz_piano = () => ({});
window.feedBackViz_highway_3d = highway3d;
window.feedBackViz_piano = () => ({});
window.slopsmith.vizDomain.refreshProviders([
window.feedBack.vizDomain.refreshProviders([
{ id: 'highway_3d', label: '3D Highway' },
{ id: 'piano', label: 'Piano' },
{ id: 'auto', label: 'Auto' }, // built-ins are skipped
@@ -68,7 +68,7 @@ test('refreshProviders registers participants with factory metadata', () => {
assert.ok(ids.includes('highway_3d') && ids.includes('piano'));
assert.ok(!ids.includes('auto') && !ids.includes('default'));
const snapshot = window.slopsmith.vizDomain.snapshot();
const snapshot = window.feedBack.vizDomain.snapshot();
const h3d = snapshot.providers.find(p => p.id === 'highway_3d');
assert.equal(h3d.contextType, 'webgl2');
assert.equal(h3d.claims, true);
@@ -79,9 +79,9 @@ test('refreshProviders registers participants with factory metadata', () => {
test('refreshProviders surfaces declared per-instance settings in the snapshot', () => {
const window = loadVisualization();
const caps = window.slopsmith.capabilities;
window.slopsmithViz_highway_3d = () => ({});
window.slopsmithViz_piano = () => ({});
const caps = window.feedBack.capabilities;
window.feedBackViz_highway_3d = () => ({});
window.feedBackViz_piano = () => ({});
const settings = [
{ key: 'palette', label: 'Palette', type: 'select', default: 'default',
@@ -95,7 +95,7 @@ test('refreshProviders surfaces declared per-instance settings in the snapshot',
// normalizes them, and the host reads them back from the participant.
caps.registerParticipant('highway_3d', { visualization: { roles: ['provider'], settings } });
window.slopsmith.vizDomain.refreshProviders([
window.feedBack.vizDomain.refreshProviders([
{ id: 'highway_3d', label: '3D Highway' },
{ id: 'piano', label: 'Piano' }, // no settings declared
]);
@@ -115,7 +115,7 @@ test('refreshProviders surfaces declared per-instance settings in the snapshot',
// list-providers carries the descriptors for consuming hosts; providers
// without a declared settings list omit the field entirely.
const snapshot = window.slopsmith.vizDomain.snapshot();
const snapshot = window.feedBack.vizDomain.snapshot();
const h3d = snapshot.providers.find(p => p.id === 'highway_3d');
// Value-equal (the host deep-clones, so compare plain values, not refs).
assert.deepEqual(JSON.parse(JSON.stringify(h3d.settings)), settings);
@@ -142,17 +142,17 @@ test('refreshProviders surfaces declared per-instance settings in the snapshot',
test('refreshProviders unregisters providers that disappeared', () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
window.slopsmithViz_gone = () => ({});
window.slopsmith.vizDomain.refreshProviders([{ id: 'gone', label: 'Gone' }]);
const api = window.feedBack.capabilities;
window.feedBackViz_gone = () => ({});
window.feedBack.vizDomain.refreshProviders([{ id: 'gone', label: 'Gone' }]);
assert.ok(api.inspect('visualization').participants.some(p => p.pluginId === 'gone'));
window.slopsmith.vizDomain.refreshProviders([]);
window.feedBack.vizDomain.refreshProviders([]);
assert.ok(!api.inspect('visualization').participants.some(p => p.pluginId === 'gone'));
});
test('select-renderer degrades on unknown provider and missing picker surface', async () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const unknown = await api.dispatch({
capability: 'visualization', command: 'select-renderer',
source: 'test', payload: { providerId: 'nope' },
@@ -160,8 +160,8 @@ test('select-renderer degrades on unknown provider and missing picker surface',
assert.equal(unknown.outcome, 'degraded');
assert.match(unknown.reason, /Unknown visualization provider/);
window.slopsmithViz_piano = () => ({});
window.slopsmith.vizDomain.refreshProviders([{ id: 'piano', label: 'Piano' }]);
window.feedBackViz_piano = () => ({});
window.feedBack.vizDomain.refreshProviders([{ id: 'piano', label: 'Piano' }]);
const noSurface = await api.dispatch({
capability: 'visualization', command: 'select-renderer',
source: 'test', payload: { providerId: 'piano' },
@@ -172,11 +172,11 @@ test('select-renderer degrades on unknown provider and missing picker surface',
test('select-renderer and clear-renderer delegate to the app picker', async () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const calls = [];
window.setViz = (id) => calls.push(id);
window.slopsmithViz_piano = () => ({});
window.slopsmith.vizDomain.refreshProviders([{ id: 'piano', label: 'Piano' }]);
window.feedBackViz_piano = () => ({});
window.feedBack.vizDomain.refreshProviders([{ id: 'piano', label: 'Piano' }]);
const select = await api.dispatch({
capability: 'visualization', command: 'select-renderer',
@@ -192,15 +192,15 @@ test('select-renderer and clear-renderer delegate to the app picker', async () =
test('renderer change and failure are emitted as domain events', () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const events = captureEvents(api, [
'visualization:renderer-changed',
'visualization:renderer-failed',
]);
window.slopsmith.vizDomain.notifyRendererChanged('highway_3d', 'auto-match');
window.slopsmith.vizDomain.notifyRendererChanged('highway_3d', 'auto-match'); // same id — no event
window.slopsmith.vizDomain.notifyRendererFailed('highway_3d', 'init threw');
window.feedBack.vizDomain.notifyRendererChanged('highway_3d', 'auto-match');
window.feedBack.vizDomain.notifyRendererChanged('highway_3d', 'auto-match'); // same id — no event
window.feedBack.vizDomain.notifyRendererFailed('highway_3d', 'init threw');
const changed = events.filter(e => e.event === 'renderer-changed');
assert.equal(changed.length, 1);
@@ -215,9 +215,9 @@ test('renderer change and failure are emitted as domain events', () => {
test('legacy shim accounting appears in the diagnostics snapshot', () => {
const window = loadVisualization();
const api = window.slopsmith.capabilities;
window.slopsmithViz_piano = () => ({});
window.slopsmith.vizDomain.refreshProviders([{ id: 'piano', label: 'Piano' }]);
const api = window.feedBack.capabilities;
window.feedBackViz_piano = () => ({});
window.feedBack.vizDomain.refreshProviders([{ id: 'piano', label: 'Piano' }]);
const snapshot = api.snapshotDiagnostics();
const shims = (snapshot.compatibilityShims || []).filter(s => s.capability === 'visualization');
@@ -226,19 +226,19 @@ test('legacy shim accounting appears in the diagnostics snapshot', () => {
const ids = JSON.parse(JSON.stringify(shims.map(s => s.shimId).sort()));
assert.deepEqual(ids, [
'visualization:type-visualization-manifest',
'visualization:window.slopsmithViz_*',
'visualization:window.feedBackViz_*',
]);
const windowShim = shims.find(s => s.shimId === 'visualization:window.slopsmithViz_*');
const windowShim = shims.find(s => s.shimId === 'visualization:window.feedBackViz_*');
assert.equal(windowShim.status, 'used');
assert.ok(windowShim.hitCount >= 1);
});
test('diagnostics contribution is redaction-safe (no song identity)', () => {
const window = loadVisualization();
window.slopsmith.vizDomain.noteAutoMatch('piano', true);
window.slopsmith.vizDomain.notifyRendererFailed('piano', 'draw threw');
window.feedBack.vizDomain.noteAutoMatch('piano', true);
window.feedBack.vizDomain.notifyRendererFailed('piano', 'draw threw');
const contribution = window.__diagnosticsContributions.get('visualization-capability');
assert.equal(contribution.schema, 'slopsmith.visualization_capability.v1');
assert.equal(contribution.schema, 'feedBack.visualization_capability.v1');
const serialized = JSON.stringify(contribution);
assert.ok(!/filename|title|artist|\.sloppak|\.archive/i.test(serialized), serialized);
assert.deepEqual(
@@ -251,7 +251,7 @@ test('a still-reserved future domain rejects dispatch', async () => {
// note-detection was promoted by the spec-009 slice; backend.routes is
// the canary that the reserved list still guards unpromoted domains.
const window = loadVisualization();
const api = window.slopsmith.capabilities;
const api = window.feedBack.capabilities;
const result = await api.dispatch({ capability: 'backend.routes', command: 'inspect', source: 'test' });
assert.notEqual(result.outcome, 'handled');
});