* feat: mobile web UI + network access toggle
Add a phone-optimized web UI and let other devices on the LAN reach a
StemDeck instance, so the app is usable end-to-end from a phone.
Mobile UI (static/mobile/, vanilla JS to match the stack):
- Library, Mixer, and Extract screens wired to the real API. Library lists
/api/jobs with swipe-to-delete; Mixer reuses the desktop Web Audio engine
(audioEngine.js, now accepting a shared gesture-unlocked AudioContext for
iOS) with faders/mute/solo/seek, real analysis, and mixdown/MP4 export;
Extract submits URL/upload and follows SSE progress.
- Served by a user-agent check on "/" (phones get mobile, everyone else the
DAW; ?ui= overrides). Shared DOM-free helpers in static/js/shared/jobs.js.
- Ported from the design prototype kept under design/mobile/.
Network access (app/core/settings.py, app/main.py):
- Backend always binds 0.0.0.0; a runtime gate decides whether non-host
requests are served (default off, opt-in). The host machine (loopback or
its own LAN IP) is always allowed, so it can't be locked out.
- Settings dialog reorganized into General / Advanced tabs: General holds
max track length (<=20 min) and MP4 video quality; Advanced holds the
network toggle (with the LAN address list) and out-of-sync resync.
- Runtime settings (allow_network, max_duration_sec, video_max_height) are
persisted and read live via GET/POST /api/settings, no restart needed.
Performance: stem MP3s are transcoded once and cached on disk (was re-encoded
on every request), so loading a track on mobile is fast and re-loads instant.
Desktop: start_backend binds 0.0.0.0; adds a local_ip command.
* chore: address code-quality bot — document suppressed excepts; untrack design refs
- _local_ips() and settings _load()/_save(): replace bare `except: pass` with
an explanatory comment + logging.debug/warning(exc_info=True); behavior
unchanged (still best-effort).
- _load(): handle the no-file case explicitly (FileNotFoundError) vs. logging
genuinely corrupt files.
- Untrack design/ (the imported Claude Design prototype) and gitignore it — it's
a local spec reference, not shipped code, and the static analyzer's "no-effect
expression" flags on its <x-dc> template bindings were false positives.
---------
Co-authored-by: Thales <>