Owner pronounced "near perfect". Save current state as the rollback target. Replace older 2026-05-08-pre-elegantfin snapshot. Snapshot md5 364cc890c58f02d07cf50b43b31a48f0 — matches both prod and dev deployed overlay. Doc 30 lists every file/path-of-record + rollback procedure + remaining roadmap items. Tag this commit v6-stable-2026-05-09 after push.
7.6 KiB
30 — v6-stable success — 2026-05-09
Save state. Owner pronounced "near perfect". Both servers (prod + dev) byte-identical overlay (
md5 364cc890c58f02d07cf50b43b31a48f0), both branding.xml parses cleanly, bothEnableTonemapping=true, both serve/Branding/Css.css36 256 B (Cineplex theme delivered to browser), playback verified visually green on dev and prod.
Tag: v6-stable-2026-05-09. Snapshot at snapshots/2026-05-09-v6-stable/index.html (md5 matches deployed overlay). Older snapshot snapshots/2026-05-08-pre-elegantfin/ removed — replaced by this one.
What works
| Surface | State |
|---|---|
| Logo center | ARRFLIX wordmark dead-center in header (235x85 PNG inlined as data-URL from branding.xml). |
| Nav left | MOVIES + SERIES uppercase links inside .headerLeft. Bare #/movies.html and #/tv.html hrefs (no topParentId query). |
| Search right | Stock .headerSearchButton pushed to flex-end via .headerRight{justify-content:flex-end}. |
| Login page | Stock-with-Cineplex (auth-gated body.arrflix-themed). ARRFLIX top-left red, Manual Login form, Welcome footer. No user picker, no Quick Connect. |
| Video player | .skinHeader hidden via body.arrflix-video-active — no theme bar leaking on top of <video>. Specificity (0,4,2) beats Cineplex's display:flex !important rule (0,3,2). |
| Favicon | A-mark (red Netflix-style "A") in browser tab. Hijack JS removes stock wordmark icon links + pins data-arrflix-icon="A" href against the older lockFavicon() shim's setInterval clobber. |
| Streaming | HDR10 sources tonemap correctly (EnableTonemapping=true flipped — see doc 21). Doc-28 INC7 transparent-<video> rule reaches browsers because branding.xml <video> literal is escaped to <video> so XML parser stops choking on the CustomCss block. |
Files of record
| File | md5 | Purpose |
|---|---|---|
web-overrides/index.html |
364cc890c58f02d07cf50b43b31a48f0 |
The compiled overlay. Deployed to both prod and dev (under different host filenames). |
snapshots/2026-05-09-v6-stable/index.html |
same as above | Frozen save state for rollback. |
bin/inject-middle-theme.py |
(current) | Builder. Idempotent. Reads web-overrides/assets/{arrflix-A.b64,arrflix-wordmark.b64-url}. Writes a backup *.bak.pre-middle-v6.<unix-ts> before overwriting. |
web-overrides/assets/arrflix-A.png |
(138x180 trimmed, transparent bg) | Source asset — favicon "A" mark. |
web-overrides/assets/arrflix-A.b64 |
29 192 chars | Inline-ready base64 of the A mark. |
web-overrides/assets/arrflix-wordmark.b64-url |
11 350 chars | Inline-ready data-URL of the ARRFLIX wordmark (235x85). Extracted from branding.xml. |
Server-side state (not in repo, document for rollback)
| Path | State |
|---|---|
/opt/docker/jellyfin/web-overrides/index.html (prod) |
md5 364cc890. owned root:root. Bind-mounted /jellyfin/jellyfin-web/index.html:ro. |
/opt/docker/jellyfin/web-overrides/index.html.bak.pre-favfix.1778318089 |
rollback target — pre-v6+favfix prod overlay. |
/opt/docker/jellyfin-dev/web-overrides/index-dev.html (dev) |
md5 364cc890. owned user:user. Same content as prod. |
/opt/docker/jellyfin-dev/web-overrides/index-dev.html.bak.pre-middle-v6 |
rollback target — pre-v6 dev overlay. |
/home/docker/jellyfin/config/config/branding.xml |
XML-valid (<video> escaped). 36 607 B. CustomCss reaches browsers via /Branding/Css.css. |
/home/docker/jellyfin/config/config/branding.xml.bak.pre-middle-v6.1778295444 |
rollback target — pre-escape. |
/home/docker/jellyfin/config/config/encoding.xml |
EnableTonemapping=true, TonemappingAlgorithm=bt2390, HardwareAccelerationType=none. |
/home/docker/jellyfin/config/config/encoding.xml.bak.pre-tonemap.1778318089 |
rollback target — pre-flip. |
/home/docker/jellyfin-dev/config/config/branding.xml |
Same content as prod. |
/home/docker/jellyfin-dev/config/config/branding.xml.bak.dev-pre-resync |
rollback target — pre-resync (dev's older minimal branding). |
/home/docker/jellyfin-dev/config/config/encoding.xml |
EnableTonemapping=true. |
Container: jellyfin |
jellyfin/jellyfin:10.10.3, healthy, restart unless-stopped. |
Container: jellyfin-dev |
same image. |
Accounts
Prod
s8n— admin. Hidden. Password is private.marco,house,guest,aloy,pet,5,64bitpotato,yummyhunny,Jayden,IX,ferghal— non-admin, hidden.Loseious— non-admin, hidden,EnablePlaybackRemuxing=true. Created 2026-05-09. Password is private.
Dev
test/123— admin, hidden. Single-account theme test sandbox.
Roadmap closed in this iteration
| Item | Status |
|---|---|
| Streaming on prod (doc 28) | ✅ closed — branding.xml XML escape was the missing delivery layer. INC7 transparent-<video> rule now reaches browsers. |
| Theme parity dev↔prod | ✅ overlay md5 identical. |
| Favicon = A-mark | ✅ hijack JS pins our data-arrflix-icon="A" links against lockFavicon clobber. |
| Tonemap HDR10 (doc 21) | ✅ EnableTonemapping=false → true on both servers. ffmpeg gains zscale → tonemap → format stage on next transcode of HDR10 source. |
| Quick Connect off + manual login | ✅ both prod and dev (QuickConnectAvailable=false in system.xml). All non-admin users IsHidden=true so no picker. |
| Video page header leak | ✅ body.arrflix-video-active toggle hides .skinHeader during playback; specificity (0,4,2) beats Cineplex (0,3,2). |
| Duplicate "Movies" h3 on library pages | ✅ body.arrflix-themed .skinHeader .headerLeft > h3.pageTitle:not(.pageTitleWithLogo){display:none!important}. |
Roadmap open (deferred — non-blocking)
| Item | Note |
|---|---|
compose-dev/docker-compose.yml in repo lacks the overlay bind-mount |
The host has it; repo is drift. |
| Locale-en-only chunk JS files (94 of them) bind-mounted on prod, absent on dev | Dev users get stock locale strings. Cosmetic only. |
xmllint --noout branding.xml in CI |
Silent XML parse failure cost a multi-hour debug cycle. |
bin/headless-test-v2.py darkPct assertion |
INC7 lesson — element state alone (currentTime, readyState) doesn't catch CSS-overlay-over-video. |
Movies/TV stuck-spinner from cached ?topParentId=movies URL |
Stock Jellyfin viewContainer.tryRestoreView quirk. Not a v6 regression. |
| Splashscreen blurred-poster login bg | Owner reference image #2 had it. Currently neither prod nor dev renders it. |
Lesson
The whole BLACK-PASS / INC7 / Traefik-SW chain in doc 26 + 28 was correctly diagnosed but the delivery layer was broken since 2026-05-08 by a single unescaped <video> literal in a CSS comment. xmllint --noout would have caught it instantly. Add it to CI. Silent XML parse failures with zero UI feedback are the worst class of bug — Jellyfin returned HTTP 200 with empty body for /Branding/Css.css, no banner, no admin alert.
Rollback
If anything regresses, restore in this order:
# Overlay rollback (prod)
docker run --rm --userns=host -v /opt/docker/jellyfin/web-overrides:/d:rw alpine \
sh -c 'cp /d/index.html.bak.pre-favfix.1778318089 /d/index.html && chown root:root /d/index.html'
# Branding rollback (prod) — only if XML escape causes new issues
docker run --rm --userns=host -v /home/docker/jellyfin/config/config:/d:rw alpine \
cp /d/branding.xml.bak.pre-middle-v6.1778295444 /d/branding.xml
# Tonemap rollback (prod)
docker run --rm --userns=host -v /home/docker/jellyfin/config/config:/d:rw alpine \
cp /d/encoding.xml.bak.pre-tonemap.1778318089 /d/encoding.xml
docker restart jellyfin
Or git-side rollback to the previous commit 52a7df6 (pre-favfix v6) and re-deploy.