fix(webui): serve frontend dist resolved per-request, not cached at startup#1741
Conversation
After npm run build completes in the background, a browser refresh now serves the real SPA without restarting the backend. Previously the StaticFiles mount for /assets and the serve_spa route were registered only when dist/ existed at create_app() time, caching a "no build found" decision for the entire process lifetime. The fix replaces the startup-conditional block with a single always-registered serve_spa catch-all that re-checks dist/index.html on every request and falls through to the actionable fallback HTML when the build is absent. The /assets StaticFiles mount is replaced with a per-request route reusing the existing _sanitize_static_path path-traversal protection. A startup-time warning is retained so users with a missing pip wheel still see a clear diagnostic log. Fixes #1088.
SummarySolid, well-scoped fix for a real first-run papercut: No security regressions found. No prompt-injection content in the diff. Issues Found🟡 Important — missing
|
kovtcharov-amd
left a comment
There was a problem hiding this comment.
Approving — per-request dist resolution is correct, and the security-sensitive bits (token-cookie bootstrap, _sanitize_static_path traversal guard) are preserved through the refactor. StaticFiles is still needed for the uploads mount, so no dead import. The empty → build-appears → served transition is well covered by the new tests.
Two trivial, non-blocking nits:
- The
serve_spamiss branch comment says "Log only on first miss to avoid flooding logs during a build," but the branch doesn't actually log — drop the comment or add the log. - Missing
/assets/*now falls through toindex.html(200) rather than 404, since theStaticFilesmount is gone. That's conventional SPA behavior, just noting the change.
Review follow-up on #1088. Collapsing the /assets StaticFiles mount into the SPA catch-all changed 404 semantics: a missing hashed chunk fell through to index.html (200, text/html), which the browser parses as JS (Uncaught SyntaxError: Unexpected token '<'). Guard /assets/* so a missing asset is a real 404; route paths still fall back to the SPA. Also: drop a stale comment claiming log throttling that never existed, and an obsolete noqa:F401 on sanitize_static_path (now unconditionally used).
|
Thanks — addressed in 20baf94:
Not changed: the double
|
Closes #1088
Why this matters
A first-run
gaia chat --uiuser on the silent npm-autoinstall path would see the "no frontend build found" fallback forever: the build finished in the background, but the server had cached the dist-missing decision at startup, so refreshing never picked it up — only a manual restart did. Now the server resolves the frontend dist per request, so the moment a build appears the next refresh serves the real UI, with no restart.Evidence
Live test on this branch — one server process,
--ui-distpointed at an empty dir, dist populated in-place mid-run (no restart):Before→after screenshots captured during validation: the fallback page becomes the full Agent UI in the same process (same PID, no restart).
How tested:
python -m gaia.ui.server --ui-dist <empty-dir>on the branch, thencpthe built dist into that dir and refresh the browser.Tests
tests/unit/chat/ui/test_spa_hot_dist.py— 5/5 pass (empty→fallback, build-appears→real SPA without restart, asset served post-build, healthy startup unchanged, path-traversal rejected)pytest -k "server or static or spa"— 361 passed, 38 skipped (3 failures are pre-existingNo module named 'mcp', present onmain)python util/lint.py --all— clean