A stopped or mid-transient (restarting / rebuilding) container's
web server isn't answering, so its <url>/icon background-image
just failed to an empty box on the card.
When the container isn't reachable (not running, or a transient
is in flight) the icon now falls back to the dimmed hyperhive
mark — /favicon.svg, served by the dashboard itself so it's
always loadable — greyscaled + lowered opacity via the
.icon-unreachable class.
closes#195
Follow-up to #188. Two additions to the side-panel file preview:
- Markdown files get a rendered/plain tabbed view (was: always
rendered, no way to see source) — same tab pattern as SVG.
- Raster images (png/jpg/gif/webp/bmp/ico/avif) render as an
<img>. /api/state-file previously from_utf8_lossy-stringified
every file and served text/plain, which corrupts binary; it
now serves image files as raw bytes with their real
content-type (over-cap images are rejected, not truncated —
a clipped binary is corrupt).
buildSvgPanel generalised to buildTabbedPreview, shared by SVG +
markdown. .svg-host/.svg-render renamed .preview-host/.img-preview
since they now back images + md too.
closes#192
SVG files in the side-panel file preview showed only raw source.
Add a rendered/source tabbed view: 'rendered' (default) shows the
image, 'source' shows the markup.
The image loads via an <img> data: URI — <img>-loaded SVG runs in
the browser's secure static mode (scripts + external fetches
disabled), so an untrusted SVG from an agent's state dir can't
execute code in the dashboard origin. Tabs reuse the existing
diff-base-tab styling; a checkerboard backs the image so
transparent regions read clearly.
closes#188
Per review: build the full forge profile URL in the harness instead
of the client. /api/state now returns forge_url: Option<String>
(assembled from the request Host header — resolves against whatever
host the operator reached the page on), replacing the forge_present
bool. The JS just links forge_url when present — no client-side URL
construction.
Add a '⬡ forge ↗' link to the per-agent page's meta row, next to
the stats + screen links. It opens the agent's Forgejo profile
(http://<host>:3000/<label> — the per-agent forge user is named
after the agent) in a new tab.
- web_ui.rs: StateSnapshot gains forge_present, true when the
agent's forge-token file exists in the state dir (same signal
that tells the agent it has a forge account).
- index.html / app.js: hidden link, shown + href-filled when
forge_present, mirroring the existing gui_enabled/screen-link
pattern. Host comes from window.location so it works off
whatever host the page is served from.
closes#185
PR #171 made relayoutCanvas() set canvas.style.width/height
explicitly, but the canvas is a flex item: flex items default to
min-width/min-height: auto, which resolves to the canvas's intrinsic
framebuffer resolution and clamps the JS-set display size right back
up to native — so fit mode still did nothing (#133, "still same
behavior").
Add min-width/min-height: 0 (+ flex: none) on the canvas in fit mode
so the explicit downscaled size actually sticks. Scoped to
#canvas-wrap.fit so non-fit mode keeps native size + scroll.
re #133
The VNC desktop faded to black after weston's default 300s idle
timeout, and on wake desktop-shell showed its click-to-unlock lock
screen (a green circle) — pointless for an agent desktop viewed
over /screen, and confusing for the operator (issue #180).
Add [core] idle-time=0 to the generated weston.ini. Verified against
weston 14.0.1: idle-time parses to compositor->idle_time, and
weston_compositor_wake arms the idle timer with idle_time*1000 ms;
0 ms makes wl_event_source_timer_update disarm the timer, so the
compositor never transitions to IDLE and desktop-shell never locks.
closes#180
The agent icon was a 26px <img> inline in the card head, hidden via
onerror when a stopped container's web server didn't answer — which
collapsed the slot and shifted the row.
Restructure the live container card as icon-left / body-right:
- the icon is a background-image div with aspect-ratio 1 and
align-self stretch — full card height, square, and (being a
background) it has no intrinsic size, so loading or failing it
can never reflow the row;
- a failed load (stopped container) falls through to a placeholder
fill instead of collapsing;
- the three content lines move into a .card-body column.
Tombstone rows keep the plain stacked layout (:not(.tombstone)).
closes#177