Server-runtime focus; supplements doc 13. Headline: 4 concurrent ffmpeg
processes for ONE viewer all transcoding 1080p->2160p with PGS subtitle
burn-in, on uncapped jellyfin container sharing 12-core host with
uncapped Forgejo BlueBuild CI runner (88-99 % CPU). Load avg 15.4 on 12
cores. Throttling+SegmentDeletion still off (doc 13 finding 03 now
non-optional). Top quick-win: enable transcode throttling + segment
deletion + cap RemoteClientBitrateLimit.
Edge audit complementing doc 13 (server-side perf). Confirms cold-load
"feels slow" perception is dominated by:
- no HTTP compression at Traefik (2.74 MiB raw JS bundles per cold load)
- no Cache-Control on hashed-asset URLs (28 conditional GETs per warm load)
- first-fetch poster image transcode ~385 ms (server-side, doc 13 #02)
TLS, MTU, HTTP/2, cert chain, middleware chain, Pi-hole hairpin all
audited and clean. Pi-hole missing local DNS rewrite for arrflix.s8n.ru
(LAN clients hairpin via WAN unless /etc/hosts pin in place).
Top quick win: add `compress@file` middleware in
/opt/docker/traefik/config/dynamic.yml + reference from Jellyfin router
label. ~70 % cold-load wire-size reduction (2.74 MiB to ~0.82 MiB
gzip / ~0.69 MiB brotli). One file edit, no architectural change.
No fixes applied. No state mutated. No Traefik reload.
doc 20 covers the multi-layer pin (server / per-user / web SPA / Accept-
Language), the idempotent re-apply runner, drift-check curl one-liners,
known gaps, and a systemd-timer suggestion for weekly auto re-application.
bin/english-lockdown-runner.sh: idempotent runner that POSTs server-wide
UICulture / PreferredMetadataLanguage / MetadataCountryCode and per-user
UICulture / Audio+Subtitle prefs / PlayDefaultAudioTrack. Reads
JELLYFIN_API_TOKEN from env (set -u, refuses to run without it). One-line
summary per surface; exit 0 on full success, 1 on any failure.
doc 15 prefaced with a "Status as of 2026-05-08" section noting the
multi-agent lockdown sweep and cross-linking the audit baseline (doc 19,
sibling) and the new lockdown procedure (doc 20). Original body preserved
verbatim as historical context.
Replaced 25 occurrences across README, docs/00-overview, and
docs/04-theming-and-users. Removed the obsolete tv→arrflix rename
blockquote (rename complete) and deduped the Live-at bullet.
Drawer Settings href is literally '#' (route via JS click handler keyed
off data-itemid='settings'). Old href*=mypreferencesmenu rules matched
zero elements in live DOM. Fix verified on dev with headless A/B (doc
17 commit 4772ddf).
Token 76858153...f8b1 was committed across 9 docs + 1 snapshot RESTORE.md
and exposed via the brief public window of this repo. Replaced with
`<JELLYFIN_API_TOKEN>` placeholder.
WARNING: this commit only redacts HEAD — the token remains in git history.
Anyone who cloned during the public window has the full value. Treat the
old token as compromised and rotate at Jellyfin Dashboard > API Keys.
Original value backed up to private s8n/secrets/ARRFLIX/.
Owner saw "Abspielen" on the Play button — caused by every user having
Configuration.UICulture absent, so the web SPA falls back to browser
Accept-Language. No server-side flag exists to override this.
Adds docs/15-force-english.md with the per-user forcing mechanism,
limits (pre-auth splash bundle still uses navigator.language), and a
ready-to-execute bash script bin/force-english-all-users.sh that pins
UICulture=en-US on every user via POST /Users/{id}/Configuration.
Plan-only commit — no live config changed. Owner triggers when ready.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Live render audited at https://arrflix.s8n.ru. Owner believed NeutralFin
was applied; live /Branding/Configuration shows Cineplex v1.0.6 — a
sibling POST won the race per §3b operational rule. Audit covers visual
contract, drift table for every CustomCss + critical-path index.html
rule, NeutralFin variable conflicts, logo aspect ratio (235x85, fits),
ranked fix list. No state mutated; recommendation pending owner sign-off.
Migrated active CSS theme from Cineplex v1.0.6 to ElegantFin v25.12.31
with Netflix-red #E50914 accent overrides over ElegantFin's default
Jellyseerr-blue/violet palette. ARRFLIX wordmark logo preserved on both
.adminDrawerLogo img and .pageTitleWithLogo selectors (split-rule form).
Eight accent variables overridden at :root: --uiAccentColor, --activeColor
(+Alpha), --osdSeekBarPlayedColor, --checkboxCheckedBgColor,
--highlightOutlineColor, --btnSubmitColor, --btnSubmitBorderColor.
All prior custom blocks preserved verbatim: cast/crew hide, Quick Connect
hide, header-icon hide (§3c), white slider thumbs (§3d), pure-black bg
(§3d), Settings drawer hide, count badge hide, ARRFLIX logo override.
LoginDisclaimer + SplashscreenEnabled untouched.
POST → 204; GET /Branding/Configuration confirms no Cineplex import,
ElegantFin pinned to v25.12.31, all overrides intact. Smoke test on
https://arrflix.s8n.ru/ → HTTP 302 (baseline). No container restart.
Restructured §1: Cineplex content moved to §1 'Previous themes'
subsection (#### Why Cineplex won, #### Tradeoffs, #### What it looked
like, #### Theme history) with the new ElegantFin+recolor stack as
the canonical current theme.
Snapshot tag for rollback: snapshot-2026-05-08-pre-elegantfin
Captures the full Cineplex-themed deploy state for rollback before
swapping the active CSS theme to ElegantFin with ARRFLIX recolor.
- branding.json: full /System/Configuration/branding payload (CustomCss + LoginDisclaimer + SplashscreenEnabled)
- index.html: bind-mounted Jellyfin web shim from nullstone
- docker-compose.yml: jellyfin compose file from nullstone
- users.json + libraries.json: reference state (not for direct restore)
- displayprefs-<userid>.json x5: per-user DisplayPreferences
- RESTORE.md: three concrete rollback commands
CSS selectors in CustomCss (a[href*=mypreferencesmenu], :has(...) wrappers)
weren't reliably hiding the entry — bundle renders it via MUI ListItemButton
+ React Router NavLink and the rendered DOM didn't match the wrapper rules.
Add nukeSettings() to the runtime shim: queries any
a[href*=mypreferencesmenu] / [to*=mypreferencesmenu], walks up to closest
li/.MuiListItem-root/[role=menuitem] and sets display:none. Wired into
start(), a new MutationObserver on document.body, and the existing 1s
setInterval. CustomCss rules left in place as belt-and-braces.
Doc: extend 10-spa-runtime-shim.md with the diagnosis, the bind-mount inode
gotcha (single-file binds + os.replace orphans the container's view), and
the nsenter-based recovery path.
Two cosmetic CSS tweaks appended to CustomCss via standard
GET-edit-POST flow (HTTP 204, all prior blocks preserved).
Thumbs: scope to .MuiSlider-thumb / .osdPositionSlider / .osdVolumeSlider
(plus legacy emby-slider .sliderThumb) -> #fff. Avoids touching
--mui-palette-primary-main so buttons/focus rings stay untinted.
Background: force --primary-background-color + --background-color and
html/body/.skinHeader/.mainAnimatedPages/#reactRoot/.dashboardDocument
to #000 so OLED + Netflix-style artwork fades read clean.
Last branding POST in this sequence.
Three top-right header icons hidden via CSS appended to existing CustomCss:
.headerSyncButton, .headerCastButton, .headerUserButton. Search button
.headerSearchButton intentionally untouched so its event handler still
fires. Selectors confirmed by grepping live JF 10.10.3 web bundle.
Branding POST returned 204; verified via GET that Cineplex @import,
cast/crew hide, ARRFLIX logo override, Quick Connect hide, Settings
drawer hide, LoginDisclaimer, and SplashscreenEnabled all preserved.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Branding endpoint accepts full-object POSTs only — no field-level merge,
no ETag locking. Concurrent agents writing to /System/Configuration/branding
race; last writer wins. Lock-in: branding-CSS POST must be the final POST
in any sequence touching that endpoint.
The static <title>ARRFLIX</title> patch wasn't enough - Jellyfin's bundle
calls document.title=... on hydrate and per-route, so the tab kept showing
'Jellyfin'. Add a self-contained inline IIFE in <head> that:
- Replaces 'Jellyfin' with 'ARRFLIX' on the title (incl. ' - Jellyfin' suffix)
- Pins favicon hrefs to the existing data: URL already in the page
- Watches <head> via MutationObserver for SPA churn
- Has a 1s setInterval safety net for late-binding navigations
- One-shot unregisters the Jellyfin service worker so old clients reload fresh
bin/inject-shim.py is the source of truth - idempotent (replaces marker block).
docs/10-spa-runtime-shim.md covers root cause, deploy flow, SW eviction, and
how to extend the shim on Jellyfin upgrade.
- EnableUserPreferenceAccess=false on guest + 5 (hides Display, Home,
Playback, Subtitles pref pages — owner controls UX centrally).
- Wrapper bin/add-jellyfin-user.sh updated to bake this into all future
non-admin user creations.
- ROADMAP entries (added by sibling import agents):
- Imported: The Incredible Hulk (2008), TMDB 1724, 4 images
- Imported: Idiocracy (2006), TMDB 7512 (NOT 1542 = Office Space)
- Imported: American Dad! (2005) S01-S04, 58 eps, TMDB 1433
- WAN exposure docs added (doc 09, 256 lines): Gandi A record live,
no-guest middleware dropped, lockout=5 baked in. Owner still must
port-forward 80/443 on home router for actual public access.
Owner flipped libraries to PreferredMetadataLanguage=en, MetadataCountryCode=US.
New-user defaults must match. Wrapper now sets:
AudioLanguagePreference=eng (was pol)
SubtitleLanguagePreference=eng
SubtitleMode=Default (was Always — let player decide based on audio)
Single page that links to docs/01..08 + bin/. Five operational rules
locked: wrapper-only user creation, stage-before-import, verify-before-
delete, no-public-DNS, repo-as-truth. Daily-ops table, troubleshooting
matrix, emergency rollback procedures, current-state snapshot.
README now points new readers at ADMIN-GUIDE.md first.
Jellyfin has no native global default for new-user DisplayPreferences;
home-layout defaults are baked into the web bundle. This wrapper layers
the s8n canonical prefs on top after user creation:
- Home sections: resume, resumeaudio, nextup, latestmedia, none x6
(drops the 'My Media' tile row — sidebar already exposes libraries)
- SubtitleMode=Always, SubtitleLanguagePreference=eng
- AudioLanguagePreference=pol
Use for every new user from now on; achieves the 'global default' the
admin wanted without patching the web bundle.
Already retroactively applied to s8n + guest.
Architecture A trimmed to: movies, tv, anime, musicvideos. Removed
mkdir/compose-mount/library-creation curl for home + music. Sections 6
(Music) and the Home-videos category remain in this doc as reference for
re-introduction later.
- 05-file-structure-rules: 1165-line authoritative ruleset covering 11 media
categories (movies, tv, anime, stand-up, concerts, docs, home video, extras,
subs, artwork, NFO). Architecture A flat layout chosen at
/home/user/media/{movies,tv,anime,musicvideos,music,home}. Top 3 gotchas
surfaced: no-per-item-folder breaks extras/NFO; year must be in parens;
anime absolute numbering past 99 needs Shoko or split-by-TVDB-season.
- 06-per-library-themes: 319-line research memo. Verdict: partially feasible.
No native per-library theming; CustomCss is global, Jellyfin web sets no
body class for libraryId/collectionType. Recommended approach: JS Injector
plugin (n00bcodr fork, MIT, last release 2025-12-08) + ~30-line shim that
mirrors topParentId/collectionType from URL hash to body class, plus three
scoped CSS blocks (body.lib-movies, body.lib-anime, body.lib-music).
Reaches 'tinted, branded, recognisable' — not pixel-perfect. Pixel-perfect
fidelity needs subdomain split (3 instances) at ~100x maintenance.