Repository navigation
Say why the app did not start, instead of showing a dead one - #619
Merged
Merged
Conversation
main.js wires the UI in a synchronous run of fifteen wireX() calls and only registers its own error handlers at the end of the module. A throw anywhere in that run does two things at once: every listener after it never attaches, and the handlers that would have reported it never register either. What the user gets is an app that hovers and lets them type, because the engine does that itself, but whose buttons and file drop do nothing because nothing is listening. Nothing is written anywhere. There was no earlier error capture in the frontend, and CSP script-src 'self' forbids adding an inline one to the HTML. boot-diagnostics.js is loaded before main.js and shows the failure in the page. It follows three rules so it still works when main.js does not: it imports nothing, since an import is a second thing that can fail in exactly these circumstances; it styles itself inline, since it cannot assume a stylesheet loaded; and it listens in the capture phase, because a module that 404s fires its error on the element and never bubbles to window. Its strings are English and do not go through i18n. That is deliberate and is the one place in the UI where it is correct: i18n.js is itself a module that can fail to load, and a banner that renders nothing when translation is broken defeats the point of having it. One honest limit, asserted in the tests rather than papered over: when a dependency 404s, the banner names main.js and not the missing file. The module loader fetches dependencies itself, so no per-file error event exists to catch. Which file is missing is a uvicorn access line in backend.log, which is why the banner points at /api/logs/backend rather than pretending to know. Four browser tests cover a module that fails to load, a throw during wiring, that the detail is selectable and the banner dismissable, and that a healthy page shows nothing. The first three fail without the script tag; verified by removing it. Refs #618, and the investigation behind #617.
This was referenced Sep 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #618
The problem
main.jswires the UI in a synchronous run of fifteenwireX()calls (lines 207-243) and registers its ownerrorandunhandledrejectionhandlers at lines 904-907, near the end of the module.A throw anywhere in that run does two damaging things at once:
What the user gets is an app that hovers and lets them type, because the engine does that itself, but whose buttons and file drop do nothing because nothing is listening. Nothing is written anywhere.
There was no earlier error capture anywhere in the frontend, and CSP
script-src 'self'forbids adding an inline one to the HTML, so there was no way to add it there.configure_logging()attaches the file handler to thestemdecklogger only, sostemdeck.lognever sees frontend problems at all. A reporter checking the obvious log finds nothing unusual, which is exactly what #617 describes.The fix
static/js/boot-diagnostics.js, loaded beforemain.js, shows the failure in the page.Three constraints shaped it, since it has to work in the circumstances where
main.jsdoes not:<script>block.window, so a normal listener would miss one of the two causes.One deliberate i18n exception
Its strings are English and do not go through
t(). This is the one place in the UI where that is correct:i18n.jsis itself a module that can fail to load, and a banner that renders nothing when translation is broken defeats the point of having it. The reasoning is recorded in the file so it does not read as an oversight.One honest limit
When a dependency 404s, the banner names
main.jsand not the missing file. The module loader fetches dependencies itself, so no per-file error event exists to catch.That is asserted in the tests rather than papered over. Which file is missing is answerable, just not from the page: it is a uvicorn access line in
backend.log, which is why the banner points at/api/logs/backendinstead of pretending to know.Test plan
tests/e2e/boot-failure.spec.mjs, four cases: a module that fails to load, a throw during wiring, the detail being selectable and the banner dismissable, and a healthy page showing nothingindex.htmland re-running: 3 failed, 1 passed. The healthy-page case passes either way by design, since it guards the opposite regressionnode --checkon everystatic/js/*.jstests/js/*.test.mjsall passgit diff --stat uv.lockempty, so the desktop in-app updater is unaffectedruff checkandruff format --checkcleanNot fixed here
This does not close #617. It makes that class of failure reportable; the reporter's specific trigger is still unknown, and I have asked them for
backend.logto tell the two causes apart.Pre-existing, unrelated
8 pytest failures in
test_click_render.pyandtest_transpose_export.pyon this machine. The identical 8 fail on cleanmain; they are ffmpeg/rubberband environment issues, not this change. No Python was touched.How to try it by hand
Run the app, then in devtools:
Or simulate the real thing: rename
static/js/catalog.js, reload, and the banner should name the failure and point at the backend log. Rename it back afterwards.