Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.7 KiB
REPORT — two UI cleanups + M3 live re-verification (2026-06-15)
Deployed: controller v0.68.3 on guest 9201 / felhom-pve (bootstrap-managed; healthy).
Commits: 37ed757 (v0.68.2 CSS) · a821a9d (v0.68.3 refresh fix). Trunk-based, pushed to main.
Part 1 — stack-card state-badge clipping fix (v0.68.2, CSS-only)
Symptom: on the /stacks page, an unhealthy app's state badge clipped — "Nem egészséges" rendered as "Ner…". Healthy / not-deployed cards were fine.
Root cause (confirmed): .stack-detail-header is flex / space-between holding .stack-title-row
(logo + title + subdomain link + the route-unpublished warning) and .stack-state-badge. Only on an
unhealthy app does the long "⚠ URL nem elérhető – útvonal nincs publikálva" warning render, inflating the
title-row. The title-row had no min-width:0 (so it wouldn't shrink below its content) and the
white-space:nowrap badge had no flex-shrink:0 (so the flexbox compressed the badge instead → clip).
Fix (internal/web/templates/style.css):
.stack-title-row { flex: 1; min-width: 0; } /* shrinks + wraps its own content */
.stack-state-badge { flex-shrink: 0; } /* never compressed */
Browser-verified on /stacks (demo, guest 9201):
- BEFORE (v0.68.1): komga badge clipped to "Ner…" at the card's right edge (screenshot captured live).
- AFTER (v0.68.2): komga badge reads the full "Nem egészséges"; the "⚠ URL nem elérhető…" warning wraps within the title column (screenshot captured live).
- Regression: Mealie ("Fut", healthy) and all "Nincs telepítve" cards visually unchanged.
Defensive note (not applied): the spec offered adding flex-shrink:0 to
.badge-missing-storage/.badge-orphaned if a card showing BOTH unhealthy AND a missing-storage/
orphaned badge still clipped. No such card exists live right now, and the primary fix's mechanism
(title-row min-width:0 absorbing all shrink) already shields sibling badges, so it was left out — a
one-liner if that combo ever appears.
Part 2 — M3 live re-verification (no code change — the migrate path is correct)
Goal: prove the migrate merge-walk re-asserts the 2775-setgid/gid-1000 convention on a
pre-existing STALE dir (the B3 calibre case), which the acceptance audit had only covered by unit
test + static convention.
Method: two full migrate-all runs driven through the dashboard UI (the real
/api/storage/migrate endpoint, all 8 flash apps: audiobookshelf, calibre-web, immich, jellyfin, komga,
paperless-ngx, radarr, romm): flash→usb, then usb→flash (restoring apps to the default drive).
Before each, the migrate target was pre-seeded host-side with deliberately-stale 0755,
non-setgid userdata dirs; stat recorded.
Result — PASS (clean, non-hollow):
- Pre-seeded
userdata/documentsat755 gid1000— a dir no app mounts — became2775 gid1000after migrate (theEnsureUserdataDirre-assert inwalkMerge, migrate.go:854-859). Same for the seededuserdataroot anduserdata/import. Whole target userdata tree =2775. - Integrity: static seeded files (
b3-document.txt,b3-movie.txt,b3-photo.txt,demo.jpg) had identical sha256 before/after. - All 8 apps redeployed healthy on the target (komga "unhealthy" = its pre-existing
/api/v1/actuator/health401 — serving, unrelated to migration).
Secondary finding — the import/calibre 755 is an APP clobber, not a migrate bug. In both runs
userdata/import/calibre ended at 755. Isolated live: calibre-web (CWA) bind-mounts
${USERDATA_PATH}/import/calibre as /cwa-book-ingest and chmods it to 755 (strips setgid) on every
startup — proven by setting it to 2775 and restarting calibre-web alone (reverted to 755, no
migration). The merge-walk does re-assert 2775 during copy; calibre-web re-clobbers it during the
flip/redeploy phase (this is also why the source flash calibre was already 755 pre-migration).
migrate.go was therefore NOT modified — the M3 path works. If the convention must hold for that
single-app ingest dir, the fix belongs in the catalog/app layer (an umask/entrypoint wrapper for CWA),
not in migrate.
Part 3 — Beállítások endless-refresh loop fix (v0.68.3) — found during Part 2
Symptom (user-reported): after any migration finished, the settings page reloaded itself every ~1.5 s, forever.
Root cause: MigrationStatus keeps returning the last done job indefinitely (never cleared). The
settings page's resume-view IIFE called migWatch() for any returned job, and migWatch's done
branch does setTimeout(()=>location.reload(),1500). So every load saw the persisted done job → watched
it → reloaded → saw it again → endless loop.
Fix (internal/web/templates/settings.html): the resume-view now starts the watcher only for an
in-progress job (phase!=='done' && phase!=='aborted'). The one-time post-completion reload still fires
from the active watcher started by storageMigrateAll, so a real migration refreshes drive state once
when it finishes — but a stale terminal job no longer triggers the loop.
Browser-verified on v0.68.3: with a persisted done job present in the journal, the settings page
stayed put for 25 s (navType:navigate, planted marker survived, migration panel idle) — no reload.
Resting state
- Apps healthy on felhom-flash (the default drive); felhom-usb cleaned/empty.
- Controller v0.68.3 deployed live on guest 9201.
- Build/deploy: built+pushed on 192.168.0.180, deployed via the bootstrap mechanism
(
docker pull→/etc/felhom-controller-image→ restartfelhom-controller-bootstrap.service).