The card described the machine and said nothing about the thing whose dashboard
it is. Cache size matters most: VV_CACHE_ROOT is under /tmp, which on Unraid is
RAM, so it is shown against the rootfs percentage it counts against. Jobs are
locks with a live process — four lock files were sitting there and one job was
actually running.
Twenty-four fetch chains ended in an empty catch, which is not error handling
but error deletion: the request fails, nothing renders, nothing is logged, and
the surface sits on Loading forever. That is how the mesh chat's ReferenceError
read as a slow load for as long as it did.
Local stays the default and the cheap path; mesh asks each partner for its own
sessions over SSH, live rather than cached, because a stream is true for
minutes and a cached one would be confidently wrong about the only thing the
card exists to say.
HOST2 has UHD Graphics 730 and reported no GPU at all. An iGPU has no VRAM,
no sensor of its own and no encode/decode split, so it is drawn from the
per-engine busy figures Intel actually publishes and the fields it lacks are
null rather than a confident zero.
mdNumDisabled counts a slot that was never populated, so HOST2 reported
"Emulating 1 disk" for a parity2 slot with no disk in it. Emulation is a
data-disk state, and an assigned identity is what separates a slot that lost
its disk from one that never had one.
A mirror was probing an Ollama it does not have and an index it never builds,
so the card read "unreachable / not built" about the wrong machine while the
model it actually uses was up. The card now names whose figures it is showing.
Mesh is a mode rather than an AI profile — it has no model, tokens, reasoning or sources, so making it a profile would branch every profile-aware path on the one that has no model.
Profile order comes from the registry so sections keep their places as threads move, and the recents are capped to the same N rather than repeating everything above them.
A flat list buries 'where was I in General Chat' under whatever the tabs have been doing, since the busiest profile takes the top regardless of which card you are looking at.
PHP defaults to UTC on Unraid while every bash script stamps local time, and the
two write into the same files — conf_changes.log was four hours out from every
log you would correlate it against. The parsing half was worse: vv_ai_syslog_ts()
reads local-time syslog lines through strtotime() under UTC, landing every event
four hours early, and the repair sweep bounds its scan to "since the last pass"
— so a fault that had just happened could read as four hours old and fall
outside the window. Also fixes date-string comparisons against bandwidth and
cleanup dbs, which bash writes with local dates.
The API mixes units per field — memory in bytes, disk size in KiB, fsSize and
fsUsed in kB — and one helper was guessing which from the magnitude of the
number, which also read any memory total under 100 GB as KB.
The 55 alert() calls carried the same suppression as the confirms, and go wrong in the worse
direction: a silenced confirm makes a button do nothing, while a silenced alert lets the action
run and says nothing about it failing. vvAlert returns a promise nobody has to await, so these
converted by rename with no caller becoming async. The icon is inferred from the message rather
than asked of fifty call sites, and an explicit type still wins.
Every confirm() and prompt() in the plugin could be switched off from inside itself — one tick of
"prevent this page from creating additional dialogs" and all 32 of them returned false while
drawing nothing, across every tab, until a full reload. swal is already global on every webGUI
page and core uses it 370 times without a single confirm(), so this costs no new dependency.
vvConfirmRun() is the one that mattered: it returned a boolean to three callers testing !it, and
an unawaited promise is always truthy, so leaving those alone would have run every job without
asking. The wrapper's callback is a classic function expression on purpose — SweetAlert only
calls back on cancel when the callback's own source declares a parameter, and an arrow would
have hung the promise forever.
Several headers argued the Scheduler dock was deliberately separate, and the Scheduler's help
never mentioned the assistant at all — including the fix flow that just changed shape.
A stretched grid item is measured before it is stretched, so a long card grew the row it was
meant to fit inside and then filled it; the ceiling is taken from the Assistant instead.
The AI cards sat in the monitor grid, which caps every row at a quarter of the viewport and
clips the card, so expanding grew the transcript inside a box the row had already sized.
Every poll after the first asked for ?live=1, so the page paid a full collection — partner SSH
timeouts included — every two seconds while the tmpfs cache it was built around went unused.
Polls are now guarded against overlap and stop while the tab is hidden, repeated failures say so
instead of leaving the last good reading on screen, and two functions nothing called are gone.
The response was discarded, so a refused stop looked like a completed one, and the payload the
card redraws from is cached for 300s with no invalidation — the container carried on showing as
running until the once-a-minute writer caught up. Stop now confirms; start still does not.
The page interpolated media titles, partner hostnames read over the mesh, and docker folder
names straight into innerHTML — its own header claimed otherwise, and the helpers that would
have fixed it were defined in a page it never loads. Container WebUI values now get a scheme
check before they reach window.open().
A multipart POST to the plugin API hangs and never completes on this host: no
status code, and no server-side trace of any kind. The correlation was exact —
every page using URLSearchParams worked, every page using FormData hung, which
is why scheduler and partnership appeared fine while docker, rsync, settings,
auth, arrs, fallback and monitor did not. URLSearchParams has the same append
API and fetch sets the urlencoded content type for it, so each site is a
one-token change with the payload logic untouched.
Documents each tab's purpose, what it renders, and which endpoints it polls.
Pages that start with markup get the header in a <?php ?> block so it never
reaches the browser.
Also corrects the layer diagram in README-unraid.md: eight of eleven pages hold
no PHP logic and poll api/ for everything — only auth, monitor and scheduler
require an include/ file directly.
Web files now served via symlink to the git repo so git pull changes survive
reboots without rebuilding the txz. Also includes: docker pull/rebuild/restart
with live log streaming, arr_profile_enforcer for Sonarr/Radarr quality
profiles, monitor page cache fix (background writer now in cron), and
ARR_KIDS/SONARR/RADARR profile name vars in master.conf.
Live TV and Direct Stream sessions never appear in the transcoding
session list but do write segments to the ramdisk. Card now shows
the active segment count so the ramdisk usage is explained.
All bash scripts are now platform-neutral. Unraid-specific paths, commands,
and service checks moved to Plugin/unraid/adapter.sh. Core scripts call
platform_*() functions exclusively — no direct OS paths in runtime logic.
New adapter functions: platform_storage_path, platform_webui_install_path,
platform_scripts_dir_probe_cmd, platform_setup_db_path, platform_storage_healthy,
platform_is_service_enabled, platform_get_temp_thresholds, platform_disk_states_path,
platform_rebuild_container, platform_push_conf, platform_push_setup_state,
platform_get_templates_dir, platform_send_os_notification.
Partnership services stack (Emby/Jellyfin/Seerr/SeerrFin) added as third
onboarding stack alongside auth and arr stacks.
Split slow cached monitor endpoint from the live stats. monitor_fast.php
reads /proc/stat, /proc/meminfo, ZFS arcstats, and /proc/net/dev directly
— no cache wrapper, 87ms response. Docker/vm/swap pulled from last full
cache so mem card stays complete. Full monitor poll stays at 2s for
everything else (GPU, containers, storage, etc).
New span-1 card shows global RSYNC_ENABLED gate, per-window badges (C/D/I/W),
active profile names + elapsed time from lock files, and last-sync timestamp
per orchestrator window from script log files.
GPU shrunk from span 2 to span 1 to make room. Row 3 is now:
Rsync(1) | GPU(1) | Transcode(2) | Streams(4)
disk_io now returns {tr, tw} cumulative GB alongside {r, w} rates.
A centered span in each card h3 shows ↓total ↑total (since last boot),
updating each poll. Helpers updated for the new object format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows N/total ok · X% used · max Y° · ↓read ↑write in the Pools
card title, using SSD thresholds for temp coloring.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds vv_disk_io_rates() — snapshots /proc/diskstats each poll and
computes per-device MB/s. Exposed as disk_io in the monitor API.
JS:
- Per-disk rows: ↓read ↑write inline next to disk name (green/amber, hidden when idle)
- Pool rows: aggregate pool I/O next to pool name
- Array header: total array I/O in the subtitle line
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server badges + media type anchor to the left; device/res/codec anchor
to the right via justify-content:space-between. Right group still
disappears at ≤1024px (now a single display:none on .vv-stream-right).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the 4-item cap and +N more label. All sessions go into balanced
columns; the container max-height clips to 2 rows with a hidden scrollbar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Splits up to 4 sessions into two balanced columns (ceil/floor split),
separated by a subtle divider. A '+N more' line appears beyond 4.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parses the last 'Ramdisk freed' and 'SSD freed' values from the transcode
management log and displays them as small ↓XMB annotations inside each tag.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reduced max-height from 57px to 44px (2 × 22px rows) so the container
cleanly shows two entries at a time and scrolls when a third appears.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps each group (separator + chips) in a display:contents span so it
flows naturally at full width and collapses cleanly at the 1024px breakpoint,
leaving server badges and media type visible at intermediate sizes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>