Add an error boundary and split the bundle (0.50.0)
CI / check (push) Successful in 7m23s
CI / build-and-push (push) Successful in 1m56s

F15 — Two unrelated frontend weaknesses.

A render error unmounted the whole React tree: a white window, no navigation,
no indication of what happened, and the only way out was knowing to reload.
ErrorBoundary now shows the message with a retry and a reload, and clears itself
when resetKey (the route) changes, so navigating to a working page just works
instead of staying stuck. There are two: one inside AppShell around the routed
pages, one at the root for the shell itself and the login screen, which sit
outside it.

The bundle was one 841 kB file (234 kB gzipped) every visitor downloaded in
full, with Vite warning about it on every build. Routes are lazy now and the
entry chunk is 377 kB (120 kB gzipped) — a 55% cut, warning gone.

Measuring first changed what to split. Monaco turned out not to be in the bundle
at all: @monaco-editor/react loads it from cdn.jsdelivr.net, so only the small
wrapper ships. xterm.js *is* bundled, all 294 kB of it, and it was reachable
from ContainerCard — which renders on every stack detail page — so every visitor
paid for a terminal most never open. It is lazy now and lands in its own chunk.

(Worth knowing separately: the compose editor therefore needs jsdelivr.net
reachable. For a self-hosted tool on an air-gapped network that is a real
limitation, but vendoring Monaco means +3 MB and is its own change.)

Adding a boundary whose behaviour I could only reason about was not good enough,
and the missing frontend test runner was already flagged as the gap from 0.49.0.
So this also sets up vitest + jsdom + testing-library and covers the boundary:
that it renders the error rather than a blank page, offers a way out, clears on
navigation, and stays put on an unrelated re-render. CI runs `npm test` next to
pytest.

One snag worth recording: installing the dev dependencies triggered npm's
optional-dependency pruning and dropped @rollup/rollup-linux-x64-gnu, which
broke the build. Reinstalling it directly put a linux-x64-glibc binary in
package.json, which would have broken `npm ci` on every other platform — so that
was backed out and the lockfile now carries the bindings as rollup's optional
deps, where they belong. Verified with a clean `npm ci` in a scratch copy:
install, typecheck, build and test all pass from the committed lockfile.

758 backend tests, 7 frontend tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dk43rmEeRfYi5wsLDbmfyG
This commit is contained in:
menzelj
2026-08-31 15:38:14 +02:00
co-authored by Claude Opus 5
parent fb2eefb0e1
commit a25741f579
12 changed files with 1843 additions and 32 deletions
+24 -2
View File
@@ -13,6 +13,19 @@ as intuitive as Dockge, as capable as Portainer for Compose workflows.
> (Auto-update) + Phase 23 (Secrets & configs) + Phase 24 (Design System v2)
> complete.
## Upgrading to 0.50.0 — nothing to do
Two robustness fixes, no configuration changes.
- **A render error no longer blanks the window.** Any exception thrown while
rendering used to unmount the whole React tree: a white page with no
navigation and no clue what happened. An error boundary now shows what broke
with a way out, and clears itself when you navigate to another page.
- **The bundle is split.** It was one 841 kB file every visitor downloaded in
full; the entry chunk is now 377 kB (120 kB gzipped) with each page fetched on
first open. xterm.js, at 294 kB the single largest piece, loads only when
somebody actually opens a container terminal.
## Upgrading to 0.49.0 — nothing to do
The UI now refreshes when Docker changes instead of asking every few seconds.
@@ -152,6 +165,10 @@ it is what your saved destination credentials are encrypted with.
holds across workers and across a restart) and a second one gets `409` while
it is held; auto-update skips a stack somebody is already deploying. Locks
carry an expiry, so a worker killed mid-deploy does not strand a stack.
- **Resilient UI** — a render error shows what broke and offers a way out
instead of blanking the window, and clears itself when you navigate away.
Routes are code-split, so the entry bundle is 377 kB rather than 841 kB and
the container terminal's xterm.js only loads when a terminal is opened.
- **Event-driven UI** — a single `/ws/events` connection carries Docker's own
container / image / network / volume events; the client drops the matching
caches so pages refresh the moment something changes, instead of every page
@@ -534,10 +551,15 @@ cd backend
pip install -r requirements-dev.txt
pytest # 758 tests, no Docker daemon needed
ruff check .
cd ../frontend && npx tsc --noEmit -p tsconfig.json
cd ../frontend && npx tsc --noEmit -p tsconfig.json && npm test
```
The suite drives the app through `TestClient` **without** the lifespan, so it
The frontend has a small vitest suite alongside it (`npm test`, jsdom). It
covers behaviour the typechecker cannot see — currently the error boundary:
that it renders the error instead of a blank page, offers a way out, and clears
on navigation so one broken page does not strand you.
The backend suite drives the app through `TestClient` **without** the lifespan, so it
never opens a Docker socket and never starts the background loops; `conftest.py`
points `DATA_DIR`/`STACKS_DIR` at a temp directory before anything is imported.