SvelteKit's adapter-static emits the SPA fallback (200.html) with absolute
asset paths (/_app/...) and a hardcoded base: "", since it can't know the
deploy base path at build time. Prerendered per-route documents use relative
paths and a runtime-computed base, so they work under any base path, but the
fallback is used for non-prerendered dynamic routes (/spool/show/<id>,
/filament/show/<id>) -- exactly the URLs printed into QR labels. Under a base
path those direct loads 404'd every asset and rendered a blank page.
Rewrite the fallback server-side using the base path the backend already
knows, mirroring the legacy-client asset rewrite: prefix /_app/ and /favicon
references and set the runtime base. Prerendered docs are still served
verbatim, and the legacy client is unaffected (its tweaked_html is always
produced when rewrite_asset_paths is on, so the file_response guard change is
equivalent there).
Build the new Svelte client (client_v2) in CI, bake it into the Docker
image alongside the legacy React client, and serve it by default. Set
SPOOLMAN_LEGACY_CLIENT=TRUE to fall back to the old client.
SinglePageApplication is generalized to serve either client: the React
client keeps its index.html + base-path rewriting, while the Svelte
client uses a 200.html SPA fallback, serves prerendered per-route pages,
and serves relative-path assets verbatim. The tweaked-HTML store is moved
off self.html, which StaticFiles uses as its HTML-mode flag.
The Playwright frontend suite targets the React DOM, so it's pinned to
the legacy client via SPOOLMAN_LEGACY_CLIENT for now.
Claude-Session: https://claude.ai/code/session_01VQQtwPBPiMCoZCpBbbqS64
Add a browser-driven integration suite (tests_frontend/) that runs against
the real production image + PostgreSQL and drives the UI with Playwright:
a smoke test that navigates every page via the sidebar with no console
errors, and a CRUD test that creates a vendor -> filament -> spool entirely
through the UI. Wired up as `poe itest-frontend` and a new `test-frontend`
CI job (reusing the image built for the backend tests, gating releases).
Navigation goes through real UI buttons (language-independent link targets)
and the app language is forced to English so label matchers are stable
regardless of the runner's browser locale.
This immediately caught a real regression: Starlette 1.3.1 (pulled by a
recent dep bump) removed the `method` kwarg from FileResponse, so
SinglePageApplication 500'd on every static asset and the whole UI was dead.
Drop the removed kwarg — Starlette now derives HEAD handling from the scope.
Claude-Session: https://claude.ai/code/session_01BQjJiwEbu6bHbeDjPPDm3V