Replace masonry grid with an absolute-positioned 12-col canvas: drag to
move, corner to resize, per-card free/overlap toggle (Alt = no-snap).
Geometry persisted (migration 027: dashboard_layout.geom + extras).
Two new addable decorative cards: blank spacer + animated blackflame
(canvas particle flame). Old layout auto-migrates by flow-placement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cross-references each candidate host IP with lan_devices (known) so a tile shows
e.g. 'H Tower' instead of '192.168.1.15:32400'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Service tiles now have an inline edit (name/category/url/icon, save/delete) like the
Devices band — fixes 'can't edit after adding'. Touch-visible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
migration 026 backup_runs; POST ingest (owner) from offsite-backup.sh, GET for the
Sacred Valley card showing last run, per-guest sizes, Farm free, schedule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icon route used Cache-Control: public, max-age=86400, so changed icons stayed
stuck in CF + browser caches for a day. Switch to no-cache (revalidate; Express
ETag => 304 when unchanged) so icon edits show up immediately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tabler icons use currentColor which doesn't inherit through <img>, so they
rendered black on the dark theme. Bake --text (#e8e6ed) into the 15 bundled
SVGs and bump icon sizes (tile 20->30px, picker 22->28px).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously iconSetsPanel() was called eagerly on settings render,
triggering a GET /api/icon-sets request even while the section was
collapsed. Now the panel is created and appended on first toggle-open,
with subsequent clicks toggling display as before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icons.display path only handles svg/png, so jpg-backed icons never
rendered. Remove jpg/jpeg: drop from EXT map and magicOk in ingest.js,
narrow FILE regex in sets.js to (svg|png), update the file input
accept attribute in icon_sets_panel.js, and simplify the content-type
ternary in the icon_sets route (jpeg branch was now unreachable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add exported isBlockedAddress(ip) helper covering loopback, 0.0.0.0,
private v4 (10/8, 172.16-31, 192.168/16), link-local (169.254/16,
fe80::/10), and IPv6 ULA (fc00::/7). In fetchUrl, after the existing
literal-hostname fast-reject, resolve the hostname via dns.lookup
(all:true) when using the real fetcher and block if any resolved
address isBlockedAddress. Injected fetcher (tests) skips DNS.
Drop unused contentType from return value.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers GET (open, returns bundled devices set), POST without auth
(must return 401 not 500), POST with owner bearer (uploads icon,
returns set), and GET /:set/:file (serves with correct content-type).
Uses _setDirs for temp-dir isolation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the softAuth middleware from devices.js into a new shared
lib/api/soft_auth.js module. Apply router.use(softAuth) and
router.use(errorMiddleware) to icon_sets.js so that POST/DELETE
owner-only routes return 401 (not 500) when no auth is present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Export reusable iconRef zod validator (set:<set>:<name> | brand:<slug> | null)
and add it as an optional field to patchBody so PATCH /devices/:mac accepts icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design for: per-device icon (type-set or brand logo), "seen Nh ago" on
absent tiles, and a Settings "Icon sets" panel with multi-file/zip/URL ingest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read-only Proxmox storage health (same PROXMOX_RO_TOKEN as the cluster card):
ZFS pool health+usage, dropped zfspool storages (the donatello/leonardo SATA
signal), and per-LXC rootfs fill, with a HEALTHY/WATCH/ATTENTION roll-up.
Closes the monitoring gap from the 2026-06-09 audit (C1 + H2 were invisible).
Pure normalizeStorage() unit-tested (4 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Embed MagicMirror² (CT 111) via the shared embedView factory, exposed at
mirror.hynesy.com through Traefik + CF Access. Traefik mirror-frame middleware
swaps MM's X-Frame-Options for a CSP frame-ancestors allowing the Void origins.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stock Kutt (bare-metal LXC, Postgres on void-db), blackflame via custom CSS (no
fork), private-first via CF Access with a public-later toggle. Hybrid Void
integration: embedded themed Kutt + a native card (update-tracker + quick-add).
Repos: Hynes/URLShortener-void-kutt + void-v2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Scan Now' triggers POST /api/devices/scan from the band header. '+ Add by MAC'
renamed '+ Manual Add' with an optional IP field (addBody/addManual accept ip)
and a MAC input that auto-inserts colons as you type. Frontend test 4/4; DB-backed
api/repo tests written (run with the suite — skipped locally to avoid colliding
with a concurrent test run on void_test).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'+ Add by MAC' in the band header → POST /api/devices → lan_devices.addManual
(status=known, present=false; enriched on next scan). Repo + API + frontend tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Known device tiles get a ✎ edit affordance using the existing PATCH/DELETE
/api/devices/:mac endpoints. Previously devices could only be named at promote time.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an OBD2 item to the Apps rail; with no records UI deployed yet it links to
the OBD2 Telemetry project + tasks and the research/wiki page rather than
embedding. Swap to embedView once LubeLogger/Tracktor is up. → 2.1.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standing rule: no work is done until it's documented in both the Void wiki
(page API) and git (code + spec/plan/CHANGELOG), pushed to Gitea. Verbose-first;
consolidate later.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The scan was surfacing every Proxmox container/host as a 'new' device. Filter
the scan against the network_hosts inventory and the Proxmox guest OUI so the
devices band stays IoT/personal-only, per the spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>