Skip to content

feat(rig-builder): override active tone with preset (in-song player control) - #62

Open
Duali98 wants to merge 1 commit into
got-feedBack:mainfrom
Duali98:feat/override-active-tone-with-preset
Open

feat(rig-builder): override active tone with preset (in-song player control)#62
Duali98 wants to merge 1 commit into
got-feedBack:mainfrom
Duali98:feat/override-active-tone-with-preset

Conversation

@Duali98

@Duali98 Duali98 commented Jul 28, 2026

Copy link
Copy Markdown

Adds a name-independent way to permanently swap a song's specific tone for another preset's gear, right from the player while the song is playing.

Background

A Clean/OD/Distortion tone-name-classification approach was tried first (matching a song's own tone_key against those 3 words) but dropped after testing against a real library: RS tone-naming conventions are inconsistent enough ("Clean"/"CLEAN"/"orion_dist"/"bass_base" all coexist in the same collection) that name matching missed or misfired on real charts. This feature is the more universal replacement - it doesn't classify anything by name at all, it just targets whatever tone is actually playing right now.

Changes

  • New POST /override_active_tone: given {filename, tone_key, source, name}, clones the chosen preset's ENTIRE preset_pieces (gear, VST state, gain_db/pan/phase_inv - everything) onto that specific song tone's own saved preset, via the existing _persist_preset_chain/_resolve_tone_preset_id primitives - no new DB schema, no lossy enrich/de-enrich round-trip (reads the source's raw preset_pieces columns directly). Same destructive semantics as the existing per-piece "🔁 Swap" button (/gear/replace_with) - this is the whole-tone equivalent, and just as permanent (no separate override layer, no built-in undo).
  • New in-song player control: a <select> next to "Rig Tones On" in the Plugin Controls panel, labeled with whichever tone_key is CURRENTLY ACTIVE. Picking "Default tone" or a saved tone shows a confirmation (this is destructive) and, on confirm, POSTs the swap and rebuilds the song's mega-chain so the new gear is heard immediately. Works on any tone_key regardless of what it's named - the control doesn't need to know or care.

Verified live

Enabling this against a real song's actively-playing tone correctly replaced its gear while every other tone in the same song stayed completely untouched.

Test plan

  • Play a song, open Plugin Controls, confirm the select shows the currently-active tone's name.
  • Pick a saved tone from the dropdown, confirm the dialog, confirm the new gear plays immediately.
  • Confirm a different tone_key in the same song (that wasn't targeted) still plays its own original gear.

Summary by CodeRabbit

  • New Features

    • Added an option to permanently replace the active song tone with the gear and preset content from another saved tone.
    • Added progress handling while the tone override is applied and refreshed the active sound chain afterward.
    • Updated live previews to immediately reflect gear swaps affecting the currently previewed tone.
  • Bug Fixes

    • Corrected the default input drive value to prevent unintended overdrive during early chain loading.

…ontrol)

Adds a name-independent way to permanently swap a song's specific tone for
another preset's gear, from right inside the player while the song is
playing.

A Clean/OD/Distortion tone-name-classification approach was tried first
(matching a song's own tone_key against those 3 words) but dropped after
testing against a real library: RS tone-naming conventions are inconsistent
enough ("Clean"/"CLEAN"/"orion_dist"/"bass_base" all coexist) that name
matching missed or misfired on real charts. This is the more universal
replacement: it doesn't classify anything by name at all.

- New POST /override_active_tone: given {filename, tone_key, source, name},
  clones the chosen preset's ENTIRE preset_pieces (gear, VST state, gain_db/
  pan/phase_inv — everything) onto that specific song tone's own saved
  preset, via the existing _persist_preset_chain/_resolve_tone_preset_id
  primitives (no new DB schema, no lossy enrich/de-enrich round-trip — reads
  the source's raw preset_pieces columns directly). Same destructive
  semantics as the existing per-piece "🔁 Swap" button (routes.py's
  /gear/replace_with) — this is the whole-tone equivalent, and just as
  permanent (no separate override layer, no built-in undo).
- New in-song player control: a select next to "Rig Tones On" in the
  Plugin Controls panel, labeled with whichever tone_key is CURRENTLY
  ACTIVE. Picking "Default tone" or a saved tone shows a confirmation
  (this is destructive) and, on confirm, POSTs the swap and rebuilds the
  song's mega-chain so the new gear is heard immediately. Works on any
  tone_key regardless of what it's named — the control doesn't need to
  know or care.

Verified live: enabling this against a real song's active tone correctly
replaced its gear while every other tone in the same song stayed untouched.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an endpoint and in-song control to replace the active tone with a saved or default preset, preserves existing gate settings, corrects the chain input fallback, and refreshes live previews after gear swaps.

Changes

Active tone preset override

Layer / File(s) Summary
Override API persistence
routes.py
Adds POST /api/plugins/rig_builder/override_active_tone, copying source preset pieces into the active tone as manual gear while preserving gate settings.
In-song override control
screen.js
Adds and synchronizes the override select, submits the replacement request, handles errors, and rebuilds the song MegaChain after success.
Playback state corrections
screen.js
Changes the input-drive fallback to 1.0 and reloads the listening tone preview after affected gear swaps.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant OverrideControl
  participant override_active_tone
  participant MegaChain
  Player->>OverrideControl: Select source tone preset
  OverrideControl->>override_active_tone: POST active tone override
  override_active_tone-->>OverrideControl: Return new preset_id
  OverrideControl->>MegaChain: Rebuild song chain
  MegaChain-->>Player: Use replaced tone preset
Loading

Possibly related PRs

Suggested reviewers: jafz2001, ifritisz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: overriding the active tone with a preset via an in-song player control.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@routes.py`:
- Around line 9984-10008: Remove gain_db, pan, and phase_inv from the
preset_pieces SELECT, row unpacking, and pieces dictionaries in the copy path.
Keep the selected fields aligned with the columns persisted by
_persist_preset_chain, preserving all other copied gear properties.
- Around line 10009-10019: Update override_active_tone to mirror the permanent
tone override across both sibling container formats, matching save_preset. Use
_canonical_song_key to derive the shared song identity, construct each
psarc/sloppak filename, call _persist_preset_chain for both, and collect their
preset IDs. Return the collected IDs while preserving the existing error
handling and piece count response.

In `@screen.js`:
- Around line 3738-3801: Track the override request state for the select created
by rbInjectOverrideActiveToneControl, and update
rbUpdateOverrideActiveToneControl so refreshes preserve sel.disabled while a
request is in flight. Set the pending state before the override_active_tone
fetch and clear it in finally, ensuring unrelated control refreshes cannot
re-enable or permit another submission until the first request completes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c5f69088-605e-4681-8383-8518b87a1341

📥 Commits

Reviewing files that changed from the base of the PR and between 8126051 and 62535fe.

📒 Files selected for processing (2)
  • routes.py
  • screen.js

Comment thread routes.py
Comment on lines +9984 to +10008
conn = _get_conn()
rows = conn.execute(
"SELECT slot, rs_gear_type, kind, file, params_json, tone3000_id, "
"assigned_mode, bypassed, vst_path, vst_format, vst_state, "
"gain_db, pan, phase_inv "
"FROM preset_pieces WHERE preset_id = ? ORDER BY slot_order",
(src_pid,),
).fetchall()
if not rows:
return JSONResponse({"error": "source tone has no gear to copy"}, 400)
pieces = []
for (slot, rs_gear_type, kind, file, params_json, tone3000_id,
assigned_mode, bypassed, vst_path, vst_format, vst_state,
gain_db, pan, phase_inv) in rows:
try:
params = json.loads(params_json) if params_json else {}
except (ValueError, TypeError):
params = {}
pieces.append({
"slot": slot, "rs_gear_type": rs_gear_type, "kind": kind,
"file": file, "params": params, "tone3000_id": tone3000_id,
"assigned_mode": assigned_mode, "bypassed": bool(bypassed),
"vst_path": vst_path, "vst_format": vst_format, "vst_state": vst_state,
"gain_db": gain_db, "pan": pan, "phase_inv": phase_inv,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'gain_db|phase_inv|\bpan\b|CREATE TABLE preset_pieces|ALTER TABLE preset_pieces ADD' routes.py

Repository: got-feedBack/feedBack-plugin-rig-builder

Length of output: 1190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== preset_pieces schema/migrations around ALTER TABLE preset_pieces ADD =="
sed -n '850,910p' routes.py | cat -n

echo
echo "== _persist_preset_chain implementation =="
sed -n '4720,4795p' routes.py | cat -n

echo
echo "== override_active_tone implementation =="
sed -n '9960,10025p' routes.py | cat -n

echo
echo "== all preset_pieces column occurrences =="
python3 - <<'PY'
from pathlib import Path
p=Path('routes.py')
text=p.read_text()
lines=text.splitlines()
for i,l in enumerate(lines,1):
    if 'preset_pieces' in l:
        print(f"{i}: {l}")
PY

Repository: got-feedBack/feedBack-plugin-rig-builder

Length of output: 23743


Drop/deprecate the unused gain_db/pan/phase_inv fields on this copy path.

These columns are not in preset_pieces and _persist_preset_chain only inserts slot_order, slot, rs_gear_type, kind, file, params_json, tone3000_id, assigned_mode, bypassed, vst_path, vst_format, vst_state; including them here makes this endpoint fail on existing databases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@routes.py` around lines 9984 - 10008, Remove gain_db, pan, and phase_inv from
the preset_pieces SELECT, row unpacking, and pieces dictionaries in the copy
path. Keep the selected fields aligned with the columns persisted by
_persist_preset_chain, preserving all other copied gear properties.

Comment thread routes.py
Comment on lines +10009 to +10019
fname_key = _db_song_key(filename)
target_name = f"{fname_key}::{tone_key}"
try:
preset_id = _persist_preset_chain(
filename=fname_key, tone_key=tone_key, name=target_name,
pieces=pieces, assigned_mode="manual",
)
except Exception as e:
log.exception("override_active_tone failed")
return JSONResponse({"error": f"{type(e).__name__}: {e}"}, 500)
return {"ok": True, "preset_id": preset_id, "piece_count": len(pieces)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "assigned_mode.*'auto'|'auto'.*assigned_mode" routes.py -C3

Repository: got-feedBack/feedBack-plugin-rig-builder

Length of output: 1753


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant functions by name and then inspect focused line ranges.
rg -n "def (save_preset|override_active_tone|_persist_preset_chain|_canonical_song_key|_tone_mapping_filename_filter)" routes.py -C 2

# Show focused ranges for the two functions (adjust after grep if needed).
start_save=$(rg -n "def save_preset" routes.py | head -1 | cut -d: -f1)
start_override=$(rg -n "def override_active_tone" routes.py | head -1 | cut -d: -f1)
start_persist=$(rg -n "def _persist_preset_chain" routes.py | head -1 | cut -d: -f1)

echo "---- save_preset around $start_save ----"
sed -n "$((start_save-10)),$((start_save+140))p" routes.py

echo "---- override_active_tone around $start_override ----"
sed -n "$((start_override-20)),$((start_override+80))p" routes.py

echo "---- _persist_preset_chain around $start_persist ----"
sed -n "$((start_persist-20)),$((start_persist+180))p" routes.py

Repository: got-feedBack/feedBack-plugin-rig-builder

Length of output: 24098


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for deterministic remap/batch paths that handle assigned_mode or replace pieces.
rg -n "remap|batch|assigned_mode|replace_with|gear/replace_with|replace_with" routes.py -C 4

# Show focused ranges around candidates found, limited to lines.
candidates=$(rg -n "def .*remap|def .*batch|def .*replace_with|assigned_mode|replace_with" routes.py | cut -d: -f1 | sort -n | uniq)
python3 - <<'PY'
lines = sorted({int(ln.split(':')[0].split(',')[0].rstrip()) for ln in __import__('subprocess').check_output(['rg','-n','assigned_mode|replace_with|remap|batch'], 'routes.py').decode().splitlines()})
print('\n'.join(str(l) for l in lines[:40]))
PY

Repository: got-feedBack/feedBack-plugin-rig-builder

Length of output: 50396


Mirror tone overrides to the sibling container.

save_preset mirrors the target preset to the same song in the other container format (psarcsloppak), but override_active_tone only updates the requested filename. Since this is also a destructive permanent write, mirroring with _canonical_song_key and collecting each sibling preset ID will prevent playback divergence when the sibling file is loaded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@routes.py` around lines 10009 - 10019, Update override_active_tone to mirror
the permanent tone override across both sibling container formats, matching
save_preset. Use _canonical_song_key to derive the shared song identity,
construct each psarc/sloppak filename, call _persist_preset_chain for both, and
collect their preset IDs. Return the collected IDs while preserving the existing
error handling and piece count response.

Comment thread screen.js
Comment on lines +3738 to 3801
function rbInjectOverrideActiveToneControl(controls, closeBtn) {
if (document.getElementById('rb-override-active-tone')) { rbUpdateOverrideActiveToneControl(); return; }
const sel = document.createElement('select');
sel.id = 'rb-override-active-tone';
sel.className = 'px-2 py-1.5 bg-dark-600 hover:bg-dark-500 rounded-lg text-xs text-gray-300 transition';
sel.addEventListener('change', async () => {
const value = sel.value;
sel.value = ''; // one-shot picker — always snaps back to the placeholder
if (!value) return;
const state = window.RbMegaChain && typeof window.RbMegaChain.state === 'function'
? window.RbMegaChain.state() : null;
const activeToneKey = state && state.activeToneKey;
const cur = window.slopsmith && window.slopsmith.currentSong;
const filename = cur && cur.filename;
if (!activeToneKey || !filename) {
alert('No tone is actively playing right now.');
return;
}
const isDefault = value === '__default__';
const targetLabel = isDefault ? 'your Default tone' : `saved tone "${value}"`;
if (!confirm(`Permanently replace the CURRENTLY PLAYING tone ("${activeToneKey}") with ${targetLabel}?\n\n`
+ `This overwrites this song's own saved gear for "${activeToneKey}" — there's no built-in undo `
+ `(re-map/re-batch the song to restore the original).`)) {
return;
}
sel.disabled = true;
try {
const r = await fetch(`${window.RB_API}/override_active_tone`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename, tone_key: activeToneKey,
source: isDefault ? 'default' : 'saved',
name: isDefault ? '' : value,
}),
});
if (!r.ok) {
const err = await r.json().catch(() => ({}));
alert(`Override failed: ${err.error || r.status}`);
return;
}
if (typeof RbMegaChain !== 'undefined') RbMegaChain.buildForSong(filename).catch(() => {});
} catch (e) {
alert(`Override failed: ${e.message || e}`);
} finally {
sel.disabled = false;
}
});
if (closeBtn && closeBtn.parentElement === controls) controls.insertBefore(sel, closeBtn);
else controls.appendChild(sel);
rbUpdateOverrideActiveToneControl();
}
function rbUpdateOverrideActiveToneControl() {
const sel = document.getElementById('rb-override-active-tone');
if (!sel) return;
const state = window.RbMegaChain && typeof window.RbMegaChain.state === 'function'
? window.RbMegaChain.state() : null;
const activeToneKey = state && state.activeToneKey;
sel.disabled = !activeToneKey;
const label = activeToneKey ? `Override "${activeToneKey}" with preset…` : 'Override active tone with preset…';
sel.innerHTML = `<option value="">${rbEsc(label)}</option>`
+ `<option value="__default__">→ Default tone</option>`
+ (rbState.savedTones || []).map(t => `<option value="${rbEsc(t.name)}">→ ${rbEsc(t.name)}</option>`).join('');
sel.value = '';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

rbUpdateOverrideActiveToneControl can re-enable the select mid-request.

The change handler sets sel.disabled = true before fetch and clears it in finally (lines 3763, 3782). But rbUpdateOverrideActiveToneControl unconditionally does sel.disabled = !activeToneKey and rebuilds innerHTML (lines 3795-3800), with no awareness of an in-flight request. This function is called from other paths independent of the fetch lifecycle — rbInjectPlayerToneButton's update branch (line 3699, likely on a frequent player-state refresh) and rbStudioLoadSavedTones (line 8732, triggered by unrelated tone-save/delete actions elsewhere in the UI). If either fires while an override POST is in flight, it silently flips the select back to enabled, letting the user fire a second overlapping destructive "permanently replace this tone's gear" request before the first completes.

🔒 Guard the disabled state while a request is pending
         sel.disabled = true;
+        sel.dataset.overriding = '1';
         try {
             const r = await fetch(`${window.RB_API}/override_active_tone`, {
                 ...
             });
             ...
         } catch (e) {
             alert(`Override failed: ${e.message || e}`);
         } finally {
             sel.disabled = false;
+            delete sel.dataset.overriding;
         }
     });
     ...
 }
 function rbUpdateOverrideActiveToneControl() {
     const sel = document.getElementById('rb-override-active-tone');
     if (!sel) return;
+    if (sel.dataset.overriding) return;   // don't clobber disabled state mid-request
     const state = window.RbMegaChain && typeof window.RbMegaChain.state === 'function'
         ? window.RbMegaChain.state() : null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function rbInjectOverrideActiveToneControl(controls, closeBtn) {
if (document.getElementById('rb-override-active-tone')) { rbUpdateOverrideActiveToneControl(); return; }
const sel = document.createElement('select');
sel.id = 'rb-override-active-tone';
sel.className = 'px-2 py-1.5 bg-dark-600 hover:bg-dark-500 rounded-lg text-xs text-gray-300 transition';
sel.addEventListener('change', async () => {
const value = sel.value;
sel.value = ''; // one-shot picker — always snaps back to the placeholder
if (!value) return;
const state = window.RbMegaChain && typeof window.RbMegaChain.state === 'function'
? window.RbMegaChain.state() : null;
const activeToneKey = state && state.activeToneKey;
const cur = window.slopsmith && window.slopsmith.currentSong;
const filename = cur && cur.filename;
if (!activeToneKey || !filename) {
alert('No tone is actively playing right now.');
return;
}
const isDefault = value === '__default__';
const targetLabel = isDefault ? 'your Default tone' : `saved tone "${value}"`;
if (!confirm(`Permanently replace the CURRENTLY PLAYING tone ("${activeToneKey}") with ${targetLabel}?\n\n`
+ `This overwrites this song's own saved gear for "${activeToneKey}" — there's no built-in undo `
+ `(re-map/re-batch the song to restore the original).`)) {
return;
}
sel.disabled = true;
try {
const r = await fetch(`${window.RB_API}/override_active_tone`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename, tone_key: activeToneKey,
source: isDefault ? 'default' : 'saved',
name: isDefault ? '' : value,
}),
});
if (!r.ok) {
const err = await r.json().catch(() => ({}));
alert(`Override failed: ${err.error || r.status}`);
return;
}
if (typeof RbMegaChain !== 'undefined') RbMegaChain.buildForSong(filename).catch(() => {});
} catch (e) {
alert(`Override failed: ${e.message || e}`);
} finally {
sel.disabled = false;
}
});
if (closeBtn && closeBtn.parentElement === controls) controls.insertBefore(sel, closeBtn);
else controls.appendChild(sel);
rbUpdateOverrideActiveToneControl();
}
function rbUpdateOverrideActiveToneControl() {
const sel = document.getElementById('rb-override-active-tone');
if (!sel) return;
const state = window.RbMegaChain && typeof window.RbMegaChain.state === 'function'
? window.RbMegaChain.state() : null;
const activeToneKey = state && state.activeToneKey;
sel.disabled = !activeToneKey;
const label = activeToneKey ? `Override "${activeToneKey}" with preset…` : 'Override active tone with preset…';
sel.innerHTML = `<option value="">${rbEsc(label)}</option>`
+ `<option value="__default__">→ Default tone</option>`
+ (rbState.savedTones || []).map(t => `<option value="${rbEsc(t.name)}">→ ${rbEsc(t.name)}</option>`).join('');
sel.value = '';
}
function rbInjectOverrideActiveToneControl(controls, closeBtn) {
if (document.getElementById('rb-override-active-tone')) { rbUpdateOverrideActiveToneControl(); return; }
const sel = document.createElement('select');
sel.id = 'rb-override-active-tone';
sel.className = 'px-2 py-1.5 bg-dark-600 hover:bg-dark-500 rounded-lg text-xs text-gray-300 transition';
sel.addEventListener('change', async () => {
const value = sel.value;
sel.value = ''; // one-shot picker — always snaps back to the placeholder
if (!value) return;
const state = window.RbMegaChain && typeof window.RbMegaChain.state === 'function'
? window.RbMegaChain.state() : null;
const activeToneKey = state && state.activeToneKey;
const cur = window.slopsmith && window.slopsmith.currentSong;
const filename = cur && cur.filename;
if (!activeToneKey || !filename) {
alert('No tone is actively playing right now.');
return;
}
const isDefault = value === '__default__';
const targetLabel = isDefault ? 'your Default tone' : `saved tone "${value}"`;
if (!confirm(`Permanently replace the CURRENTLY PLAYING tone ("${activeToneKey}") with ${targetLabel}?\n\n`
`This overwrites this song's own saved gear for "${activeToneKey}" — there's no built-in undo `
`(re-map/re-batch the song to restore the original).`)) {
return;
}
sel.disabled = true;
sel.dataset.overriding = '1';
try {
const r = await fetch(`${window.RB_API}/override_active_tone`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename, tone_key: activeToneKey,
source: isDefault ? 'default' : 'saved',
name: isDefault ? '' : value,
}),
});
if (!r.ok) {
const err = await r.json().catch(() => ({}));
alert(`Override failed: ${err.error || r.status}`);
return;
}
if (typeof RbMegaChain !== 'undefined') RbMegaChain.buildForSong(filename).catch(() => {});
} catch (e) {
alert(`Override failed: ${e.message || e}`);
} finally {
sel.disabled = false;
delete sel.dataset.overriding;
}
});
if (closeBtn && closeBtn.parentElement === controls) controls.insertBefore(sel, closeBtn);
else controls.appendChild(sel);
rbUpdateOverrideActiveToneControl();
}
function rbUpdateOverrideActiveToneControl() {
const sel = document.getElementById('rb-override-active-tone');
if (!sel) return;
if (sel.dataset.overriding) return; // don't clobber disabled state mid-request
const state = window.RbMegaChain && typeof window.RbMegaChain.state === 'function'
? window.RbMegaChain.state() : null;
const activeToneKey = state && state.activeToneKey;
sel.disabled = !activeToneKey;
const label = activeToneKey ? `Override "${activeToneKey}" with preset…` : 'Override active tone with preset…';
sel.innerHTML = `<option value="">${rbEsc(label)}</option>`
`<option value="__default__">→ Default tone</option>`
(rbState.savedTones || []).map(t => `<option value="${rbEsc(t.name)}">→ ${rbEsc(t.name)}</option>`).join('');
sel.value = '';
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 3796-3798: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: sel.innerHTML = <option value="">${rbEsc(label)}</option>
+ <option value="__default__">→ Default tone</option>
+ (rbState.savedTones || []).map(t => <option value="${rbEsc(t.name)}">→ ${rbEsc(t.name)}</option>).join('')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@screen.js` around lines 3738 - 3801, Track the override request state for the
select created by rbInjectOverrideActiveToneControl, and update
rbUpdateOverrideActiveToneControl so refreshes preserve sel.disabled while a
request is in flight. Set the pending state before the override_active_tone
fetch and clear it in finally, ensuring unrelated control refreshes cannot
re-enable or permit another submission until the first request completes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant