fix: multiple bug fixes found during comprehensive test plan sweep
CI / test (push) Has been cancelled

- Fix #5: Preset state lost on service restart — call restore_state()
  in PresetManager.__init__() so last active preset is tracked after boot
- Fix #14: Block bypass/toggle WS sync — add WebSocket connection
  in SPA with block_toggled handler that calls loadState()
- Fix #15: IR file naming — add display_name property to IRFile
  dataclass that strips common IR prefixes/suffixes
- Fix: block_id regeneration on every preset load — pass block_id
  from stored JSON data to FXBlock constructor in _preset_from_dict
  (both preset chain and snapshot chain)
This commit is contained in:
2026-06-18 19:14:01 -04:00
parent d152ecd1fc
commit 47bb535b08
3 changed files with 72 additions and 1 deletions
+48
View File
@@ -43,6 +43,54 @@ class IRFile:
length_ms: float
channels: int = 1
@property
def display_name(self) -> str:
"""Return a clean human-readable name by stripping common IR prefixes/suffixes.
Strips leading/trailing markers commonly found in IR filenames
(e.g. ``IR_Marshall_1960A.wav`` → ``Marshall 1960A``,
``cab_Vox_AC30_48k.wav`` → ``Vox AC30``).
"""
name = self.name
# Common IR prefixes (case-insensitive)
_prefixes = [
"IR_", "ir_", "IR-", "ir-",
"Impulse_Response_", "impulse_response_",
"Impulse-Response-", "impulse-response-",
"Cab_", "cab_", "Cab-", "cab-",
]
# Common IR suffixes (case-insensitive)
_suffixes = [
"_IR", "_ir", "-IR", "-ir",
"_Impulse", "_impulse", "-Impulse", "-impulse",
"_Cab", "_cab", "-Cab", "-cab",
"_Stereo", "_stereo", "-Stereo", "-stereo",
"_Mono", "_mono", "-Mono", "-mono",
"_48k", "_48K", "-48k", "-48K",
"_44100", "-44100",
"_48kHz", "-48kHz",
"_16bit", "_24bit", "-16bit", "-24bit",
"_LD", "_SD", "-LD", "-SD",
]
for prefix in _prefixes:
if name.lower().startswith(prefix.lower()):
name = name[len(prefix):]
break
for suffix in _suffixes:
if name.lower().endswith(suffix.lower()):
name = name[:-len(suffix)]
break
# Replace separators with spaces and clean up
name = name.replace("_", " ").replace("-", " ")
name = " ".join(name.split()).strip()
return name if name else self.name
def _next_pow2(n: int) -> int:
"""Return the smallest power of 2 >= n."""
+6
View File
@@ -104,6 +104,7 @@ def _preset_from_dict(data: dict) -> Preset:
params=dict(block_data.get("params", {})),
nam_model_path=block_data.get("nam_model_path", ""),
ir_file_path=block_data.get("ir_file_path", ""),
block_id=block_data.get("block_id", ""),
)
)
@@ -128,6 +129,7 @@ def _preset_from_dict(data: dict) -> Preset:
params=dict(bd.get("params", {})),
nam_model_path=bd.get("nam_model_path", ""),
ir_file_path=bd.get("ir_file_path", ""),
block_id=bd.get("block_id", ""),
)
)
snapshots[int(slot_str)] = Snapshot(
@@ -191,6 +193,10 @@ class PresetManager:
# Migrate legacy flat presets → gtr channel on first boot
self._migrate_legacy_presets()
# Restore last active preset after boot so the pedal comes back
# to the same (channel, bank, program) it was on before shutdown.
self.restore_state()
logger.info("PresetManager root: %s (channel=%s)", self._dir, self._current_channel.value)
# ── Public navigation API ───────────────────────────────────────────────
+18 -1
View File
@@ -945,6 +945,22 @@ async function pollTuner(){
function startPolling(){stopPolling();loadState();pollTimer=setInterval(loadState,2000);}
function stopPolling(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}
// ═══ WebSocket — real-time updates ═══
let ws=null;
function setupWebSocket(){
if(ws)try{ws.close()}catch(e){}
const proto=location.protocol==='https:'?'wss:':'ws:';
ws=new WebSocket(proto+'//'+location.host+'/ws');
ws.onmessage=(ev)=>{
try{
const msg=JSON.parse(ev.data);
if(msg.type==='block_toggled'){loadState();}
}catch(e){}
};
ws.onclose=()=>{setTimeout(setupWebSocket,3000);};
ws.onerror=()=>{};
}
// ═══ Init! ═══
function init(){
// Restore saved channel
@@ -1243,8 +1259,9 @@ function init(){
document.getElementById('dlSearchBtn').onclick=doDlSearch;
document.getElementById('dlSearch').onkeydown=(e)=>{if(e.key==='Enter')doDlSearch();};
// Start polling
// Start polling + WebSocket
startPolling();
setupWebSocket();
loadPresets();
loadSnapshots();
}