Skip to content

Serve the frontend with correct MIME types, whatever the host registry says - #620

Merged
thcp merged 1 commit into
mainfrom
fix/js-mime-type-from-registry
Sep 12, 2026
Merged

thcp merged 1 commit into
mainfrom
fix/js-mime-type-from-registry

Conversation

@thcp

@thcp thcp commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Closes #617

The problem

StaticFiles asks mimetypes for a content type, and on Windows mimetypes reads HKEY_CLASSES_ROOT. Any program that ever registered .js as text/plain leaves it that way for every application on that machine. StemDeck then served its own ES modules as plain text there, and nowhere else.

Browsers enforce strict MIME checking on module scripts and refuse to execute one served as text/plain:

Failed to load module script: Expected a JavaScript-or-Wasm module script but the
server responded with a MIME type of "text/plain". Strict MIME type checking is
enforced for module scripts per HTML spec.

Every module is blocked, so nothing wires itself up. The app renders completely and responds to nothing. Hover and text selection keep working because the engine does those itself; buttons and file drop do nothing because no listener was ever attached.

Nothing in the response is malformed on a healthy machine, which is exactly why this survived CI, every developer box, and the whole e2e suite. The bug is the dependency on a registry StemDeck does not control.

Diagnosed by @DrSiemer, who supplied the console output and identified the cause.

The fix

_pin_static_mime_types() registers the handful of types the app actually serves, before the StaticFiles mount: .js, .mjs, .css, .json, .svg, .wasm.

It sits next to the mount it protects rather than at the top of the module, so the reason is visible from the thing it affects.

Test plan

tests/test_static_mime.py, 11 tests. They reproduce the broken host rather than trusting the one they run on.

  • Two tests deliberately cannot fail on a correct registry, and say so in their docstrings. They guard the healthy case: the type after import, and a real /js/main.js request. On a sane machine these pass with or without the fix, and pretending otherwise would be worse than saying it.
  • The tests that carry the weight install the text/plain mapping first. One parametrised case per pinned extension, plus an end-to-end request that asserts the response really does come back text/plain before asserting the fix corrects it.
  • Verified the tests can fail. With add_type replaced by pass, 8 of 11 fail, including the end-to-end one.
  • Full suite: 999 passed
  • ruff check and ruff format --check clean
  • bandit -r app/ -ll clean
  • git diff --stat uv.lock empty, so the desktop in-app updater is unaffected

Pre-existing, unrelated

8 failures in test_click_render.py and test_transpose_export.py on my Windows box. The identical 8 fail on clean main; they are ffmpeg/rubberband environment issues, not this change.

Reproducing it by hand

On Windows, point .js at text/plain and start the app:

reg add "HKCR\.js" /ve /d "text/plain" /f

Before this change the window renders and ignores every click. After it, the app works. Restore with /d "text/javascript".

Relationship to #619

#619 is a separate problem found while investigating this one: the app could not report a boot failure at all, because main.js registers its error handlers after the wiring run that fails. That is why this arrived with clean logs. The two are independent and neither replaces the other. With #619 in place, this failure would have named itself on screen instead of presenting as a dead window.

…y says

StaticFiles asks `mimetypes` for a content type, and on Windows `mimetypes`
reads HKEY_CLASSES_ROOT. Any program that ever registered `.js` as `text/plain`
leaves it that way for every application on that machine, so StemDeck served
its own ES modules as plain text there and nowhere else.

Browsers enforce strict MIME checking on module scripts and refuse to execute
one served as text/plain. Every module was blocked, so nothing wired itself up.
The app rendered completely and responded to nothing: hover and text selection
still worked because the engine does those itself, while buttons and file drop
did nothing because no listener had ever been attached.

Nothing in the response is malformed on a healthy machine, which is exactly why
this survived CI and every developer box. The dependency on a registry StemDeck
does not control is the bug; pinning the handful of types the app actually
serves removes it.

Diagnosed by Dick Siemer, who supplied the console output and the cause.

The tests reproduce the broken host rather than trusting the one they run on.
Two of them deliberately cannot fail on a correct registry and say so in their
docstrings: they guard the healthy case. The ones that carry the weight install
the text/plain mapping first, including an end-to-end request that asserts the
broken response before asserting the fixed one. Verified by neutering the fix:
8 of 11 fail, including that one.

Closes #617
@thcp thcp mentioned this pull request Sep 12, 2026
2 tasks done
@thcp
thcp merged commit db154b8 into main Sep 12, 2026
10 checks passed
@thcp
thcp deleted the fix/js-mime-type-from-registry branch September 12, 2026 02:00
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.

[Bug]: Nothing responds on boot

1 participant