v1.8.0 — Mode + palette themes, accessibility, tablet layout
🎨 Theme mode + palette, split apart
The theme picker was one flat list of 23 entries. It's now a Light / Dark / System toggle plus an 11-item palette dropdown, in both the header pill and the Customize dialog.
- System mode composes with any palette. Previously "System" meant Amber and nothing else — now System + Nord, System + Forest, and every other pairing works, resolving before first paint so there's still no flash.
- Decorative motion (the idle logo float, the breathing state messages) is now opt-in and off by default, under Customize → Decorative motion. Functional motion — the reconnecting pulse, live-activity dots — is untouched.
♿ Accessibility
A full pass against WCAG 2.2 AA, verified in-browser across all 22 themes.
- Focus is visible again. The integration form set
outline: noneon every input with no replacement, so keyboard users lost their place mid-task. One ring now covers the app, coloured from the active theme's ink so it clears 3:1 everywhere. - Live state is announced. Labby streams everything over SSE and previously said none of it out loud. Stream connection and monitor counts are live regions; all 15 widgets announce their error state.
- Status isn't colour-only. Up/warning/down was a 9px dot and nothing else; it now carries text for screen readers. Download and playback bars expose real progress values.
- Contrast holds on every theme.
--ok,--warn,--downand--idlewere declared once and never re-tuned, so the eleven light palettes rendered status text between 1.71:1 and 2.99:1. All 176 token/theme pairs now clear AA, with 4.75:1 as the floor. A test parsesapp.cssand does the WCAG maths, so it can't silently regress. - Native
<dialog>semantics for modals: real focus containment, Escape to close, focus returned to whatever opened it. - The dashboard has an
<h1>.
📱 Responsive
- A tablet tier. The board jumped from three columns straight to one at 1080px, so iPad portrait and half-screen windows got a single stretched column. The ladder is now 1 / 2 / 3 / 4 columns.
- Phones no longer scroll sideways. Compact density forced two columns into a 342px card and each row burst its track, making the page 8px wider than the viewport.
- Tap targets measured on a real 390px viewport: the buttons already passed, but the service-name links were 19px tall and the "View all" links 16px. Both now clear the 24px minimum.
🔒 Security
GET/POST/PUT /api/integrationsno longer return stored secrets to the browser. Blank secret fields on update preserve the stored value instead of wiping it.POST /api/backupwrites atomically toconfig/backups/and returns only the path — never the backup body, which contains credentials in plaintext.
🐛 Fixes
- Custom CSS cancel now discards the draft. Preview was mutating the saved config on every keystroke, so Cancel had nothing left to restore.
- Header health chips were monitor-only while claiming to cover everything. Relabeled to "Monitored sites", and the row hides when no monitor is configured instead of showing a misleading 0/0/0.
- Docker widget counted exited containers as "Running" when configured with
show: all. - Reorder buttons on Manage Services are reachable by keyboard on desktop again — they were hidden in favour of mouse-only drag.
⚡ Under the hood
- Response compression (
hono/compress, no new dependency). JavaScript goes out at 61KB instead of 273KB, CSS at 12KB instead of 67KB. SSE is correctly excluded. - Measured performance on a throttled mid-range phone: LCP 2.0s, CLS 0. Three proposed optimizations —
transform: scaleXfor progress bars,content-visibilityon cards, preloading the webfont — were each benchmarked and rejected, because every one measured as noise or slower. The reasoning is recorded in the code so it doesn't get "fixed" later. prefers-reduced-motionwas killing every transition, including the colour changes that acknowledge a click. It now removes movement and keeps feedback.- 218 tests, up from 207.
Image
ghcr.io/samuelloranger/labby:1.8.0 (also tagged 1.8, 1, latest) — multi-arch linux/amd64 + arm64.
Full changelog: v1.7.1...v1.8.0