Fix 404 handler swallowing SPA fallback under ui.run_with(root=...)#5999
Fix 404 handler swallowing SPA fallback under ui.run_with(root=...)#5999
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the intermediate `endpoint = request.scope.get('endpoint')` line
and binds inline with `(endpoint := ...) is not None`. Behavior is
identical (Starlette's `Mount.matches` and `Route.matches` always
write a non-None value to `scope['endpoint']`); this just trims the
diff to one line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TL;DRReviewed empirically (cloned, ran tests on I pushed one tiny commit on top ( Full review — un-blackboxing Starlette + variant comparisonStarlette code path, citedWhen
Contrast: when a real inner Cross-version verificationReproduced regression on
Same failing assertion in both: Empirical truth table (7 scenarios)Verified across
Also verified The three forms — pick oneA. Two-line / explicit local (your original commit): endpoint = request.scope.get('endpoint')
if endpoint is not None and endpoint is not app and not request.scope.get('nicegui_page_path') and isinstance(exception, StarletteHTTPException):B. Walrus (what I just pushed in if (endpoint := request.scope.get('endpoint')) is not None and endpoint is not app and not request.scope.get('nicegui_page_path') and isinstance(exception, StarletteHTTPException):C. Non-walrus minimal — preserves the existing if 'endpoint' in request.scope and request.scope['endpoint'] is not app and not request.scope.get('nicegui_page_path') and isinstance(exception, StarletteHTTPException):
Equivalence: Starlette only ever writes a non- If walrus-in-a-long-boolean reads worse to you than |
evnchn
left a comment
There was a problem hiding this comment.
TL-DR: The previous report means a LGTM
Motivation
Closes #5998.
#5974 changed the 404 handler to return JSON when an endpoint matched but no
nicegui_page_pathwas set, so that@app.getroutes raisingHTTPException(404)would no longer be served NiceGUI's HTML error page. The discriminator was'endpoint' in request.scope.That works under plain
ui.run(). Underui.run_with(parent_app, root=root)it breaks SPA fallback: Starlette'sMount.matches()populatesscope["endpoint"]with the mounted app reference before the inner router runs (see starlette/routing.py L429). When the inner router doesn't match anything (e.g./auth/loginis only known to a client-sideui.sub_pages), thatendpointsurvives into the 404 handler. The new branch then incorrectly fires and returns{"detail":"Not Found"}instead of falling through to renderroot.Implementation
Tighten the discriminator: only treat the request as "an endpoint raised 404" when
scope["endpoint"]is set and is notcore.appitself. A real innerRoutematch overwritesendpointwith the handler function, so this distinguishes the two cases cleanly.Added two regression tests in
tests/test_run_with.pyexercising the mounted-app code path that the existingtests/test_page.pytests don't cover:test_run_with_unknown_path_falls_through_to_root— the actual regression guard (fails onmain)test_run_with_api_endpoint_404_still_returns_json— guards Return JSON from the 404 handler for non-page endpoints #5974's intent underui.run_withProgress