The problem
StemDeck has no response compression. Opening the UI on a phone pulls about 456 KB of JavaScript, CSS and HTML in the clear:
| file |
raw |
would gzip to |
static/js/i18n.js |
328 KB |
86 KB |
static/mobile/app.js |
46 KB |
14 KB |
| everything else |
82 KB |
26 KB |
| total |
456 KB |
126 KB |
Over loopback in the desktop webview this is invisible, which is why it has never been noticed. Over Wi-Fi to a phone it is the load time, and it shows up as the whole UI feeling sluggish, not just the first paint.
Two things make it worse than the raw number suggests:
app/main.py sets Cache-Control: no-cache, must-revalidate on every static asset. That is deliberate and correct (stale JS after an update silently breaks the app), but it costs a round trip per file on every load.
- Browsers generally refuse to keep a disk cache for an origin with a certificate error. On a self-signed https origin the revalidation that would normally answer
304 is likely to fetch the whole payload again, every visit.
Measured, so it is not confused with the other suspect
TLS itself is not the cause. On the same machine:
|
http |
https |
| 50 MB bulk |
200 MB/s |
171 MB/s |
| 880 KB range request |
48 ms |
55 ms |
15%, on a link far faster than any Wi-Fi. Compression is where the win is.
Constraints on any fix
- A range window of audio must never be compressed. The phone streams stems through
static/js/chunkedAudioEngine.js, which asks for five-second windows with a Range header and gets 206 Partial Content back. Compressing one rewrites Content-Length while Content-Range still describes the uncompressed bytes, and browsers do not agree on how to read that. The payload is PCM, so there is nothing to gain either.
- Event streams must not be buffered. The job and queue progress streams are
text/event-stream; a compressor's buffer there is a stall with no error.
- Whole audio files should be left alone too: WAV does not compress, and the machine paying for it may also be running Demucs.
- Any dependency added for this would change
uv.lock and break the desktop in-app updater, so it needs to come from what is already installed.
Not in scope
i18n.js is 72% of the payload because all eleven language tables ship to every user so that one of them can be read. Splitting it per locale would take 86 KB to roughly 8 KB, but that is a separate and much larger change.
The problem
StemDeck has no response compression. Opening the UI on a phone pulls about 456 KB of JavaScript, CSS and HTML in the clear:
static/js/i18n.jsstatic/mobile/app.jsOver loopback in the desktop webview this is invisible, which is why it has never been noticed. Over Wi-Fi to a phone it is the load time, and it shows up as the whole UI feeling sluggish, not just the first paint.
Two things make it worse than the raw number suggests:
app/main.pysetsCache-Control: no-cache, must-revalidateon every static asset. That is deliberate and correct (stale JS after an update silently breaks the app), but it costs a round trip per file on every load.304is likely to fetch the whole payload again, every visit.Measured, so it is not confused with the other suspect
TLS itself is not the cause. On the same machine:
15%, on a link far faster than any Wi-Fi. Compression is where the win is.
Constraints on any fix
static/js/chunkedAudioEngine.js, which asks for five-second windows with aRangeheader and gets206 Partial Contentback. Compressing one rewritesContent-LengthwhileContent-Rangestill describes the uncompressed bytes, and browsers do not agree on how to read that. The payload is PCM, so there is nothing to gain either.text/event-stream; a compressor's buffer there is a stall with no error.uv.lockand break the desktop in-app updater, so it needs to come from what is already installed.Not in scope
i18n.jsis 72% of the payload because all eleven language tables ship to every user so that one of them can be read. Splitting it per locale would take 86 KB to roughly 8 KB, but that is a separate and much larger change.