Compare commits

..
Author SHA1 Message Date
Byron GamatosandBret Mogilefsky e2cf9fa9a2 fix(v3): topbar stacks above the Songs filter bar (instrument/tuner dropdowns)
The v3 Songs filter/search bar (added by #857) is `sticky top-0 z-20 backdrop-blur`
— the SAME z-index and stacking-context recipe as the global topbar, and being
later in source order it won the z-20 tie and painted over the topbar's whole
subtree. So the topbar's instrument-selector dropdown (`data-inst-menu`, z-50
*within* the topbar's z-20 context) rendered BEHIND the filter bar.

Bump the topbar to z-30 so it (and its dropdowns) stack above the per-screen
filter bar. `.z-30` is already in the prebuilt tailwind.min.css, so no rebuild.

Reproduced + verified headless: in the filter-bar/menu overlap region the
topmost element flips from the filter bar's <select> to the instrument menu.
(The tuner panel #tuner-plugin-ui is z-[1000] on document.body and was already
on top — unaffected.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 45bfd9cc9c6981a10a9f2441638b7e5bee760dc8)
2026-06-17 23:42:46 -07:00
2 changed files with 8 additions and 51 deletions
+2 -50
View File
@@ -934,16 +934,6 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
if not req_file.exists():
return True
# Packaged/distributed hosts (e.g. slopsmith-desktop) set
# SLOPSMITH_SKIP_PLUGIN_INSTALL to skip the blocking startup pip install:
# heavy optional deps (torch/whisperx/demucs) would otherwise download for
# minutes and hang the backend past the host app's readiness window. The
# plugin still loads and degrades gracefully when its optional deps are
# absent.
if os.environ.get("SLOPSMITH_SKIP_PLUGIN_INSTALL", "").strip().lower() not in ("", "0", "false", "no"):
log.info("Skipping requirement install for plugin %r (SLOPSMITH_SKIP_PLUGIN_INSTALL set)", plugin_id)
return True
_PIP_TARGET.mkdir(parents=True, exist_ok=True)
pip_target = str(_PIP_TARGET)
@@ -956,32 +946,9 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
# (PYTHONHASHSEED), so the marker would never match on restart and
# pip would re-resolve every plugin's requirements on every boot.
marker = _PIP_TARGET / f".installed_{plugin_id}"
fail_marker = _PIP_TARGET / f".failed_{plugin_id}"
req_hash = hashlib.sha256(req_file.read_bytes()).hexdigest()
def _marker_matches(m):
# Tolerate an unreadable/transiently-broken marker (permissions, I/O):
# treat it as "no match" and fall through to a normal install attempt
# rather than letting read_text() raise out of this function.
try:
return m.exists() and m.read_text().strip() == req_hash
except OSError:
return False
if _marker_matches(marker):
if marker.exists() and marker.read_text().strip() == req_hash:
return True # Already installed, same requirements
# A previous install of these exact requirements already failed. Don't
# re-attempt on every boot: that re-blocks startup for the full pip timeout
# each launch. Retry only when requirements.txt changes (new hash) or the
# .failed_ marker is cleared.
if _marker_matches(fail_marker):
return False
def _record_failure():
try:
fail_marker.write_text(req_hash)
except OSError:
pass # read-only target: nothing to persist
log.info("Installing requirements for plugin %r (this can take a while for large deps)...", plugin_id)
try:
@@ -993,20 +960,7 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
capture_output=True, text=True, timeout=1800,
)
if result.returncode == 0:
# Persisting markers is best-effort and must NOT fall through to the
# outer `except` (which would call _record_failure() and make a
# SUCCESSFUL install look like a sticky failure). The two writes are
# independent: clearing a stale .failed_ marker must still happen
# even if writing the success marker fails — otherwise a real
# success would stay recorded as a failure on the next boot.
try:
marker.write_text(req_hash)
except OSError:
pass
try:
fail_marker.unlink() # clear any stale failure record
except OSError:
pass
marker.write_text(req_hash)
log.info("Requirements installed for plugin %r", plugin_id)
return True
else:
@@ -1020,7 +974,6 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
)
else:
log.warning("Plugin %r: failed to install requirements: %s", plugin_id, result.stderr[:300])
_record_failure()
return False
except Exception as e:
err_lower = str(e).lower()
@@ -1033,7 +986,6 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
)
else:
log.warning("Plugin %r: error installing requirements: %s", plugin_id, e)
_record_failure()
return False
+6 -1
View File
@@ -145,7 +145,12 @@
function renderTopbar() {
const bar = document.getElementById('v3-topbar');
if (!bar) return;
bar.className = 'sticky top-0 z-20 bg-fb-sidebar/80 backdrop-blur';
// z-30 (not z-20): the global topbar must stack above per-screen sticky
// bars — notably the v3 Songs filter/search bar, which is also
// `sticky top-0 z-20 backdrop-blur` and, being later in source order,
// otherwise wins the z-index tie and paints over the topbar's instrument
// selector / tuner dropdowns (slopsmith#857 regression).
bar.className = 'sticky top-0 z-30 bg-fb-sidebar/80 backdrop-blur';
bar.innerHTML =
// Row 1 — top utility bar: search.
'<div class="flex items-center gap-4 px-4 md:px-8 pt-4">' +