Viktor: "make screens foldable so they can be viewed on small screens." The VM-control sheet packed Inspect + 4 power buttons + a long output dump into one scroll on a phone. Made the dense sections collapsible with native <details>/<summary> (zero-JS, accessible):
- Inspect and Power are foldable groups, open by default (nothing important hidden), tap the caret header to collapse the one you are not using.
- Command output (e.g. a long forensics dump) is a foldable block; its <pre> is capped at 46vh with internal scroll so it never runs off the page.
Verified via Playwright at 390x844: tapping Power collapses it to its header; the forensics output folds and scrolls within a bounded box. Works on desktop too (side panel stays expanded).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mobile is the primary client for fixing the devvm, but the first cut was
desktop-first and the chat input was unreachable on a phone:
- Root cause: the shell used height:100%/100vh, so on mobile browsers the
composer at the bottom sat behind the address/tool bar — you saw the VM
buttons but no place to type. Switched #app to 100dvh (dynamic viewport
height) with a 100vh fallback; body no longer scrolls (chat scrolls
internally), killing iOS rubber-banding.
- Layout is now mobile-first single-column: the chat fills the screen with
the composer pinned at the bottom and always visible. The VM power controls
moved into a slide-up bottom sheet behind a compact "⚡ VM" header button
(backdrop + close + grab handle). At ≥900px the sheet becomes a static side
column again and the toggle is hidden — desktop unchanged.
- Touch targets ≥40px; composer textarea bumped to 16px so iOS Safari doesn't
auto-zoom on focus (which itself shoved the composer out of view).
Verified at 390×844 (iPhone) and 1280×800 via Playwright: input box renders at
y=723–821 (inside the 844 viewport), sheet slides in on tap, desktop keeps the
2-column side panel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Viktor wanted a web UI on the claude service to act as his breakglass when
the devvm is down: open it, have Claude SSH in to diagnose/repair, and
power-cycle the VM via the Proxmox host if needed. This is the app half
(the infra stack + host bootstrap live in the infra repo).
New, ISOLATED ASGI app under app/breakglass/ (never imports app.main, so the
untrusted-input agents — recruiter-triage, nextcloud-todos — can't share a
process with the root-on-devvm / PVE-reset SSH key):
- pve.py: the LLM-independent power-verb path (status|forensics|reset|stop|
start|cycle on VM 102), whitelist-validated client-side, executed over the
forced-command SSH key (list argv, no shell).
- agent_session.py: multi-turn streamed chat — claude -p --session-id /
--resume with --output-format stream-json, translated to a small SSE
vocabulary (session/text/tool/result/error/done).
- auth.py: edge Authentik header OR bearer; fail-closed.
- server.py: FastAPI (session/chat-SSE/pve-verb routes) + serves the Svelte UI.
- Svelte SPA (frontend/, built into app/breakglass/static/ and committed — no
in-cluster build, per ADR-0002): streamed chat + danger-styled manual VM
controls with confirm-on-mutate.
- agents/breakglass.md: narrow tools (Bash/Read/Grep/Glob, no web), taught the
ssh devvm / ssh pve aliases and cycle-vs-reset.
- docker-entrypoint-breakglass.sh: ssh-agent bootstrap from the mounted key +
ssh aliases, then uvicorn app.breakglass.server. The breakglass Deployment
overrides the image CMD with this; the existing service is untouched.
26 new tests (verb whitelist incl. injection attempts, stream-json→SSE
translation, auth gating, route behaviour); full suite 58 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>