Commit Graph
218 Commits
Author SHA1 Message Date
Mitchell-JohnsonandClaude Fable 5.1 05161bf7ef PLAN.md: the Files edge sweep
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 20:32:34 -07:00
Mitchell-JohnsonandClaude Fable 5.1 1038c010fa Files: a sweep of the edges
Found by walking every handler and the UI with odd inputs, then a battery of requests
against the live hub. Fixed:
- A save over the 8 MiB cap was cut short in place: the file was truncated, then the
  first 8 MiB written, and the caller told 204. Now the body is read whole first and a
  body over the cap is a 413 with the file untouched.
- A link to a file showed the link's own time; now the file's, so the row reads right
  and the preview key follows the file.
- Reading a link to nothing answered 200 with an empty body (the open failed after the
  headers in root mode); now a 404 that names the target.
- A play request with an unknown codec or size was answered by inspecting the file
  first, so an audio-only file said "no video stream" instead of what was wrong; the
  choice is checked before any work, and is a 400.
- A stream that ended in failure was handed back to the next identical request for ten
  minutes; a failed one is now dropped and a fresh one made.
- A player that left while its stream was still finding a first segment made the loop
  try the next recipe; it stops. A stream closed mid-start is not started again.
- Ending a root-mode ffmpeg could wait forever: the daemon cannot signal root's
  processes, so the wait is bounded and the helper's own grace does the rest.
- A page going away (a phone closing the tab) releases its stream by a beacon, rather
  than ten minutes later by the sweeper.
- The path box resolves like a shell — relative to the folder shown, . and .. applied,
  trailing slashes dropped — in the tab and in the Move picker, with a test. A typed path
  that is a file opens it; a remembered folder that is gone falls back to / once.
- A dropped folder arrived as a nameless empty file; it is refused with a word. The
  upload toast counts what was actually uploaded.
- The viewer names a device, pipe or socket, and a dangling link, instead of calling
  them binary files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 20:31:10 -07:00
Mitchell-JohnsonandClaude Fable 5.1 d200e5dc2e PLAN.md: re-encoding for the browser, on the machine's GPU
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 17:46:35 -07:00
Mitchell-JohnsonandClaude Fable 5.1 f3c547adfa Files: re-encode a video the browser can't play, on the machine's own GPU, and play that
The viewer tries the file as it is first. When the browser gives up — Safari and an MKV,
HEVC without a hardware decoder — or on a tap, a picker: codec (H.264, HEVC, AV1, or the
original codec repackaged) and size (original, 1080p, 720p, 480p), each codec labelled
with the engine this machine would use. POST /api/files/play runs ffmpeg into an HLS
event playlist of fragmented-MP4 segments under <data dir>/transcode/<id>/; Safari plays
HLS itself and everything else goes through hls.js, loaded only then. The recipes are
tried best first: a card on the bus before the integrated GPU, decoding on the same card
before decoding on the CPU and uploading, the software encoders last; a recipe that
produces no first segment is dropped for the next. The seekable range grows as ffmpeg
gets ahead; status says how far. Streams are reused for the same request, closed when
the player goes, swept after ten idle minutes, and the folder is emptied at start. Root
mode runs ffmpeg as root through a helper that ends when the daemon lets go of it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 17:42:25 -07:00
Mitchell-JohnsonandClaude Fable 5.1 aa4ae57c22 gofmt after the rename
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:54:52 -07:00
Mitchell-JohnsonandClaude Fable 5.1 af189b1fa1 internal/thumbs is internal/media, with Inspect: what a file holds, from ffprobe
The package was named for its first job; it now also decides what a file is for the
Files tab (image, video, audio) and reads its streams, which the viewer will need. Inspect
is the richer probe — codec, profile, pixel format, size, frame rate, HDR transfer, the
audio beside it, container and duration — run as root through sudo when asked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:54:15 -07:00
Mitchell-JohnsonandClaude Fable 5.1 beec9269fe PLAN.md: video and audio playback in the Files viewer, as the file is
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:43:03 -07:00
Mitchell-JohnsonandClaude Fable 5.1 d7f56ee0b3 Files: a seek costs nothing; the backend reopens only when a read wants somewhere else
ServeContent seeks to the end for the size before it seeks to the range, which was
throwing away the stream Check had just opened.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:41:54 -07:00
Mitchell-JohnsonandClaude Fable 5.1 a9bcb88212 Files: play video and audio in the viewer, as the file is; reads answer Range requests
The viewer hands a video or audio file to the browser untouched — container, codec and
all — and the browser plays what it can; when it can't, the viewer says so and offers the
download. What the server adds is the part a player needs: /api/files/read now goes
through http.ServeContent for a real file of known size, so Range requests (seeking) and
HEAD work, and a download can resume. The root backend's helper takes an offset for that,
and a Seeker opens the backend once, at the range asked for, instead of streaming from
the top. Audio files are a preview kind too: their cover art is the thumbnail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:40:42 -07:00
Mitchell-JohnsonandClaude Fable 5.1 c24f6f5cde Files: a move of something that isn't there is a 404, not a task that fails on arrival
The kernel answers EXDEV for two paths on different filesystems before it looks for the
source, so the cross-filesystem path was taken for a missing file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:30:04 -07:00
Mitchell-JohnsonandClaude Fable 5.1 b6d72cce9f PLAN.md: Move… is a folder picker; moves across filesystems copy then remove
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:29:27 -07:00
Mitchell-JohnsonandClaude Fable 5.1 fb3b8e4b4c Files: Move… opens a folder picker, and a move across filesystems copies then removes
The picker walks the machine like the tab does — back, forward, up, breadcrumbs, a path
box, a new folder — and "Move here" puts the file or folder in the folder being looked
at; it says why it can't when it can't (already here, into itself, a name clash). POST
/api/files/move is a rename when the two ends share a filesystem and otherwise a task:
on a NAS every move between datasets is a different filesystem, and a rename there is
EXDEV. The copy is built under a hidden temporary name beside its destination, renamed
into place once complete, and only then is the original removed, so a failure or a
cancel leaves the original as it was. Modes, times, symlinks and, as root, owners are kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:27:55 -07:00
Mitchell-JohnsonandClaude Fable 5.1 e5d3fb2afd PLAN.md: previews are cached in thumbs.db
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:05:17 -07:00
Mitchell-JohnsonandClaude Fable 5.1 e198471f8c Files previews: the cache is one database file, <data dir>/thumbs.db
bbolt instead of a tree of hash-named JPEGs: pure Go, so the binary stays static; one
file per machine; an entry carries where the preview came from (engine, codec, size,
when) beside the picture, and a count rides along so the tab can say what it holds
without walking anything. A miss never creates the file, so a machine nobody browses
with previews on never has one; a cache that cannot be opened is logged and previews
are still made and served, just not kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 16:04:12 -07:00
Mitchell-JohnsonandClaude Fable 5.1 51c718e8e3 PLAN.md: previews on the Files tab, shipped as v0.1.0-200
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 15:25:55 -07:00
Mitchell-JohnsonandClaude Fable 5.1 f8c9479e93 Files: previews — a toggle that shows a thumbnail per image and video, made by ffmpeg on the machine's GPU where it has one, cached under the data dir
A previews button on the Files tab turns the icon column into a 256 px thumbnail for
every image and video row, loaded as rows scroll near. internal/thumbs probes what the
machine can do rather than assuming it: ffmpeg and ffprobe, every /dev/dri/renderD*
opened and exercised, vainfo's decode profiles per node where it exists. A video goes to
the first GPU that lists its codec, with ffmpeg's own software fallback behind it and a
CPU retry behind that; images decode on the CPU, JPEGs at a reduced DCT size. Previews
are kept under <data dir>/thumbs/ keyed by path, size and mtime, with undecodable files
remembered too. Root mode makes them through the same root helper the file reads use.
Where ffmpeg is missing the tab offers to install it; where a render node exists but is
closed to the daemon's user, it offers a drop-in adding the group and a restart.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 15:20:27 -07:00
Mitchell-JohnsonandClaude Fable 5.1 e7034cd478 VIRTUALIZATION.md: grid's Incus removed for a fresh start; status
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 15:02:52 -07:00
Mitchell-JohnsonandClaude Opus 5.5 e068fbdb2f PLAN.md: copyparty retired ahead of the Files gaps; they stay as Files polish
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:54:27 -07:00
Mitchell-JohnsonandClaude Fable 5.1 faf444d8ce VIRTUALIZATION.md: storage decision revised — a Disk location picker, plain files on grid's root disk
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 14:36:06 -07:00
Mitchell-JohnsonandClaude Fable 5.1 ae72e45bf5 Virtualization: a disk-location picker on both create forms, locations by place, add or remove a location
Pools are shown by where their bytes land (plain files on this disk, a ZFS pool inside one
file, a dataset…) rather than by driver name, which read as the NAS pool. A location can be
added as a plain-files pool at any folder on the machine, and removed while nothing uses it.
The driver order at Install is as it was; the labels say what each one really is.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 14:33:10 -07:00
Mitchell-JohnsonandClaude Opus 5.5 a9b4d7969e PLAN.md: CPU power on the Processes tab, shipped as v0.1.0-194
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:00:05 -07:00
Mitchell-JohnsonandClaude Opus 5.5 bfedf622be Processes: CPU power beside the clock, from the packages' energy counters
New sensor cpu.power: watts from the RAPL energy counters (Intel, and AMD
Zen through the same driver), summed over every package and averaged into
history. Parts of a package, psys and the MMIO duplicate are left out.

The counters are root-only, so a daemon that isn't root reads them through
`hamlab cpu-energy`, a root helper started once with sudo or doas and asked
over a pipe each sample, the same shape as the GPU client walk's helper.
It reads only the counters it finds itself. No counters or no root means
no reading, and the CPU box shows nothing extra.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:58:35 -07:00
Mitchell-JohnsonandClaude Opus 5.5 d5680e8e41 VGA console: stop importing spice-html5's demo stylesheet, which restyled every button
spice.css sets body, *, button and canvas for the whole document. Bundled
into the App chunk since 43ebacd (deployed as v0.1.0-188), it turned every
button into a gradient pill, shifted 15% right and 8% up with a 150px
minimum width, broke the rail and header layout, and bordered every graph
canvas. The console is already styled by the .vga rules in styles.css.

The file is removed from the vendored copy, and a new web test fails on any
stylesheet import outside an allow-list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:19:20 -07:00
Mitchell-JohnsonandClaude Opus 5.5 ea84ee40c7 PLAN.md: the 2375 item deployed as v0.1.0-191 and checked on every machine
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:00:19 -07:00
Mitchell-JohnsonandClaude Opus 5.5 24a9979432 Docker updates: a machine without Docker answers empty, not 500
The hub used to answer this for its nodes and hid the node's 500. Now the
node answers itself, so it needs the same guard the stack list has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:58:13 -07:00
Mitchell-JohnsonandClaude Opus 5.5 c27d9f0ada Stacks: one machine, one set of files; default home is <data dir>/stacks
The hub no longer probes a node's Docker on tcp :2375, holds compose files
for a node, or runs compose against another machine's Docker. hubStacks,
nodeDocker, taskOnHub and the hub-side /api/nodes/{node}/docker/... routes
are gone; the generic node proxy hands those requests to the node's own
Ham Lab. Stack.remote / held_by and compose.Spec.Remote are removed.

New stacks default to <data dir>/stacks on every machine. Folders with two
or more deployed stacks are still offered, after it, never as default.
The ~/stacks fallback is gone. A missing home is skipped without a sudo find.

A schedule saved earlier with a tcp target is refused with a message
instead of being run against this machine's Docker.

PORTABILITY.md 3.1, 3.2, 3.3 and 9.2 done; 3.5 checked, no change needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:53:10 -07:00
Mitchell-JohnsonandClaude Fable 5.1 a0ce3e5eb9 VIRTUALIZATION.md: phase 2 deployed as v0.1.0-188 and verified live; what is left
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 22:34:05 -07:00
Mitchell-JohnsonandClaude Fable 5.1 80a5b4b464 VGA console: one console operation per VM, a data websocket per SPICE channel
spice-html5 opens a websocket per channel (main, display, inputs, cursor, playback), and
each one was making Ham Lab open a new console operation, which Incus refuses as already
connected. Now the first channel opens the operation and holds its control websocket; every
further channel is one more data websocket on the same operation, which Incus allows; the
operation is let go when the last channel closes. A stale operation from a previous Ham Lab
process is taken over with force.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 22:32:08 -07:00
Mitchell-JohnsonandClaude Fable 5.1 a5ebfdc9ce VGA console: agree to spice-html5's binary subprotocol; label layout and two labels in the detail pane
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 22:28:00 -07:00
Mitchell-JohnsonandClaude Fable 5.1 43ebacdd92 Virtualization phase 2: ISOs, New VM from ISO, the VGA console in the browser, nightly exports, mounts, instance sensors
- Library: an ISO shelf in virt/isos: upload (the Files uploader), fetch by URL with an
  optional sha256, import a file from the machine by copying it in, rename, delete (refused
  while attached), attach to a VM as a CD-ROM live or stopped with boot-first, eject, and
  New VM from ISO: an empty VM with the ISO as first boot device, Secure Boot on or off.
- The VGA console: Ham Lab asks Incus for the VM's SPICE console over its unix socket and
  bridges the display websocket to the browser, where spice-html5 (vendored from upstream
  b372aab, LGPL, loaded on demand) draws it; Ctrl+Alt+Del, fit-to-window.
- Exports: every instance as a tarball in virt/exports/<name>/, kept to a count, as a cron
  line in /etc/cron.d/hamlab-virt calling hamlab virt-export, settings and the last run
  as files beside the data; Export now per instance or for all.
- Disks tab per instance: the root disk, attached ISOs with Eject, bind mounts in and out
  (shift for containers, virtiofs for VMs).
- instance.* sensors (running, down, state, cpu, mem) and the VM-or-container-down alert
  default, offered once to installs seeded before it existed.
- cloud-init: Use my SSH keys writes a cloud-config from the account's public keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 22:21:57 -07:00
Mitchell-JohnsonandClaude Fable 5.1 66498a58c5 VIRTUALIZATION.md: phase 1 deployed as v0.1.0-184, Install verified live on grid
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 15:31:06 -07:00
Mitchell-JohnsonandClaude Fable 5.1 b790b0c31c virt: the description is an instance field, not a config key — set it with a PATCH
Found on the first live test on grid: incus config set refused 'description' and the whole
limits save with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 15:30:10 -07:00
Mitchell-JohnsonandClaude Fable 5.1 38675ce9af Portability audit: every lab-specific design choice, listed for generalisation
PORTABILITY.md sweeps the whole repo for assumptions tied to this lab's hosts,
network, hardware, distro, init system, orchestrator history and privilege
posture. Nothing is changed yet; this is the list to work from, graded A/B/C
with the general form of each.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 15:29:12 -07:00
Mitchell-JohnsonandClaude Fable 5.1 dfe2ca359a incus: syntax-check the generated install script for each machine shape; refusals; create args
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 14:16:05 -07:00
Mitchell-JohnsonandClaude Fable 5.1 289aa4aee3 VIRTUALIZATION.md: phase 1 built as v0.1.0-180, awaiting deploy
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 14:13:54 -07:00
Mitchell-JohnsonandClaude Fable 5.1 240129c83f Virtualization tab, phase 1: Incus per machine — detect, install, instances, consoles, snapshots, images
The tab reports one of four states for the selected machine (ready, repair, installable,
unavailable) from what it finds — /dev/kvm, the package manager's incus candidate, the
pool drivers the kernel has — and acts on that machine only. Install is one followed task:
packages, a pool whose backing file sits in Ham Lab's own virt/ folder (zfs pool on a file,
btrfs on a loop mount, or a plain dir), a bridge on a subnet picked clear of the machine's
routes and Docker's pools (never 10.x on a /8 LAN), the DOCKER-USER rules that keep
Docker's FORWARD drop off the bridge, the incus-admin group for Ham Lab's account, and a
restart. Instances list with live CPU and memory, create from the images: catalogue or a
local image (container or VM, limits, cloud-init), start/stop/restart/force-stop/rename/
delete, console and shell as terminal tabs, snapshots with Incus's own schedule and expiry,
limits editing. Images: local store, catalogue search and download (exported into the
library too), import a tarball or a VM disk from a path, publish an instance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 14:09:56 -07:00
Mitchell-JohnsonandClaude Fable 5.1 af53b45b67 Virtualization plan: every artefact under Ham Lab's data directory, one virt/ tree per machine
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 13:48:31 -07:00
Mitchell-JohnsonandClaude Fable 5.1 e772f7fae2 Virtualization plan: a Library for ISOs and images; VGA console moves to phase 2 with it
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 13:36:27 -07:00
Mitchell-JohnsonandClaude Fable 5.1 0fa6ab98e2 Virtualization plan: capability-gated per machine, never policy-gated; Install or report
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 12:56:18 -07:00
Mitchell-JohnsonandClaude Fable 5.1 16e85e1e42 Virtualization tab: scoping plan — Incus on grid, what is and isn't feasible
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 12:11:33 -07:00
Mitchell-JohnsonandClaude Opus 5.5 1fbf345166 BUGS: bug bash 6 and its quality-of-life builds deployed as v0.1.0-174
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:11:29 -07:00
Mitchell-JohnsonandClaude Opus 5.5 07645089d7 Version builds from the base tag, not from a published release's tag
git describe had started picking the nearest release tag (v0.1.0-137-gc129f65,
fetched from Gitea), so a build came out as v0.1.0-137-gc129f65-36-g7d0b87b,
which the version compare reads as build 137: older than the 169 it replaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:06:48 -07:00
Mitchell-JohnsonandClaude Opus 5.5 7d0b87bc45 A GPU with nothing measurable is one line, not half the Processes page
The Mac's Asahi GPU exposes no busy counter, clock, sensor or memory reading, so
its box was a graph that could never draw beside a column of dashes. A GPU with
nothing to read now gets one line (driver, "nothing measurable" or the stall,
and whether a process has it open), its row shrinks to fit, and the Gpu% column
and gpu sort are dropped when no GPU on the machine can light them. QoL #3 from
bug bash 6 (BUGS.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:31:25 -07:00
Mitchell-JohnsonandClaude Fable 5.1 f3ca3d8f73 Idle sessions sign themselves out; a KVM's login is remembered; the compose editor knows YAML
A session unused for seven days is dropped, the Sessions list marks a day's idleness and
can sign all of those out at once. The hub keeps a KVM's own cookies from any browser that
has them and lends them to one that hasn't, on the wire and back to the page. The stack
editor and the new-stack dialog use CodeEditor: line numbers, YAML and .env colour under a
real textarea, Tab/Shift+Tab/Enter indentation, and a key bar on touch screens.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 01:20:27 -07:00
Mitchell-JohnsonandClaude Fable 5.1 faaa6aaf9d The phone's ZFS table headers no longer collide; the bash notes that deploy is still owed
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 11:34:31 -07:00
Mitchell-JohnsonandClaude Fable 5.1 e0e2562f0a Bug bash 6: the phone fits ZFS, Usage and journal lines; a snapshot's default name is local time; twelve smaller faults
Found by a headless pass over every page on every machine at desktop and phone widths, then
every interaction on the hub. BUGS.md has the list, what was checked and found fine, and the
quality-of-life notes to discuss.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 11:28:44 -07:00
Mitchell-JohnsonandClaude Fable 5.1 3a58e265c6 A GPU is named from an embedded PCI ID table, the same on every machine
The router has no lspci, so its GeminiLake chip came up as 'Intel GPU
3185' beside the identical chip's 'Intel UHD Graphics 600' on hamtv.
A trimmed pci.ids — the graphics devices of Intel, AMD and NVIDIA, and the
virtual GPUs, 115 KB — rides in the binary and is consulted first; lspci
is the fallback for a card newer than the table. scripts/pci-ids-gpu.sh
regenerates it from the hub's copy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 23:48:05 -07:00
Mitchell-JohnsonandClaude Fable 5.1 b5cecce435 Never read sysfs through Go's poller, and a GPU that stops answering is skipped
On grid the sampler sat 42 minutes in runtime_pollWait on the Arc A380's
hwmon fan1_input: kernfs accepts epoll, so os.ReadFile made the fd
non-blocking, i915 answered EAGAIN once, and the poller waited for a
readiness kernfs only signals on sysfs_notify. Every graph on the machine
froze while the API carried on.

readStr now reads with plain blocking syscalls. Each GPU's sysfs reads run
under a three-second deadline; a device that does not answer is marked
stalled, logged, and left alone until restart, with the box saying so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 23:41:22 -07:00
Mitchell-JohnsonandClaude Fable 5.1 a8f4655c83 A graph's canvas is out of flow, so a box can never grow itself without end
The gpu box sat in an auto-height grid row. The canvas inside it was sized
from its box and also sized the box, so every resize fed the next and the
Processes tab grew for as long as it was open. The canvas is now absolute
within its box, and the gpu row has a floor instead of auto.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 22:54:49 -07:00
Mitchell-JohnsonandClaude Fable 5.1 7e070bd524 The gpu box's layout rules, which the first commit lost
The desktop grid placement, the stats panel, the engine label width and
the client rows' columns were never in the stylesheet: on a phone the
caption sat on the stats head, 'enhance' ran into its meter, and a client
row came out as one word. Restored, with the graph box as tall as the
cpu's so its caption always has a line of its own.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 22:53:50 -07:00