Commit graph

74 commits

Author SHA1 Message Date
s8n
7ce1539ea7 processes/subtitles: STYLE.md — USER-G style for picks + display
Codifies the bar every fetch must hit:
- one plain English .srt per episode, named <base>.eng.srt
- drop SDH / HearingImpaired / MachineTranslated / AiTranslated / Forced
- prefer fps-matched, then highest download count
- UI label collapsed to just "English" via web-overrides shim

README.md now points at STYLE.md as a hard prereq before any fetch run.
The picker logic in v1/v2/v3 helpers already encodes these rules; STYLE.md
is the canonical source of truth that they should be checked against if
anyone (or future-me) is tempted to relax them.
2026-05-10 00:13:51 +01:00
s8n
b3ead71b7e shim: shorten subtitle stream labels in detail dropdowns
Stock Jellyfin renders subtitle stream entries as
"<lang> - <CODEC> - (External|Internal|Embedded)[ - flag]". For ARRFLIX,
which has at most one subtitle format per language, the codec and source
suffix are noise. Add a small JS shim to web-overrides/index.html that
matches that exact shape and collapses the label to "<lang>" (with
"(Forced)" / "(SDH)" / "(Hearing Impaired)" if those flags are
present; "Default" is dropped since it's redundant when there's only one
stream of that language).

Audio labels like "5.1Ch Surround Sound - English - AAC - Stereo - Default"
have a different number of segments and don't match, so they pass through
untouched.

The shim runs after DOMContentLoaded and re-walks any nodes added later
via MutationObserver (covers actionsheet dropdowns that mount lazily).

Bracketed by /* SUB-LABEL-SHIM-BEGIN */ and /* SUB-LABEL-SHIM-END */
markers; bin/revert-sub-label-shim.sh deletes between the markers in one
sed pass and saves a timestamped backup. No container restart needed
(index.html is bind-mounted).
2026-05-10 00:05:16 +01:00
s8n
43f55643be processes/subtitles: v3 Addic7ed fetcher + AD 49/58 subbed
Adds lib/sub-a7d-fetch.py: free, no-daily-cap path via subliminal's
addic7ed provider (anonymous). Uses OpenSubtitles REST search-only (no
quota cost) to translate library S/E to the show's primary catalogue
numbering, then drives subliminal to download from Addic7ed and writes
sidecars direct to nullstone via SSH.

Picker quirks: subliminal series-name matcher is broken by '!' in the
title, so the script strips it before building the synthetic
Video.fromname() string. OS feature_details S/E happens to align with
Addic7ed's indexing for the test show (American Dad).

Recipe README now reflects three paths in cheapest-first order: v3
Addic7ed, v2 OS REST (20/day), v1 plugin. American Dad run log updated
to 49/58 (S01 7/7 v1, S02 16/16 mixed v2/v3, S03 16/19 v3, S04 10/16
v3). 9 misses identified, deferred to next OS REST quota window.
2026-05-09 23:31:10 +01:00
s8n
23520df2df processes/subtitles: v2 REST fetcher + AD S02E01-E12 subbed
Adds lib/sub-rest-fetch.py: direct OpenSubtitles REST, looks up subs by
per-episode IMDB id (e.g. tt0511631) instead of the plugin's
(parent_imdb_id, season, episode) combo path. This sidesteps shows where
library numbering diverges from OpenSubtitles' catalogued numbering --
American Dad uses Hulu S1=7 eps; OS uses Fox S1=23 eps; the plugin path
returns 0 hits past S01E07 even though every per-episode IMDB id is
correct.

Recipe README updated to surface the two paths (v1 plugin / v2 REST) and
recommend v2 by default. American Dad run log now shows 19/58 episodes
subbed (S01 7/7 via v1, S02E01-E12 via v2). S02E13-S04 (39 eps) deferred
to next 20/day quota windows.

Quirk fixed in v2: OpenSubtitles /download endpoint consistently returns
HTTP 503 to Python urllib.request despite identical headers/body via curl.
_curl() shim routes all OS API calls through curl. Each 503 still
consumes a download slot, so urllib path was unsafe to retry on.
2026-05-09 23:09:09 +01:00
s8n
fedf3388b8 processes: subtitle acquisition v1 + AD S01 run
Adds processes/ umbrella for repeatable acquisition workflows. First child
is subtitles/, with recipe README (executable by Claude Code), CHANGELOG,
per-show run logs, and a tested helper at lib/sub-fetch.sh.

Run on American Dad: S01 (7 eps) passed, S02-S04 (51 eps) broke. Library
uses Hulu/DSP season ordering; OpenSubtitles indexes by Fox airing order;
plugin queries by (parent_imdb_id, season, episode) so library S02E01
returns 0 hits. v2 design = direct OpenSubtitles REST with per-episode
imdb_id lookup; pending API-key registration.
2026-05-09 22:56:31 +01:00
s8n
1ed55152b7 fix: drop video z-index hack + heavy comments + doc 31 layer model
Image-12 incident: I'd set <video> z-index:9999, which covers the OSD
scrubber + buttons (Jellyfin's stock OSD controls live at z-index
1100-2000, above the 1000 of .videoPlayerContainer but BELOW our
9999). Drop the lift entirely. Stock z-index hierarchy already has
controls floating on top. The fix for black-screen was always
transparent ancestor backgrounds (L1+L2), never z-index.

Reorganized inject-middle-theme.py CSS string from one-line dense
concat into a triple-quoted multi-line block with header comments
explaining each section + the layer model + DO-NOT rules. Same
output bytes (verified md5 deterministic). Added long-form comment
header to JS too.

Doc 31 (new): "Theme layer model + edit guide" — comprehensive
checklist for any future CSS edit. Covers:
  - Stacking order layer 0..7 with stock Jellyfin z-indexes
  - The two body classes (.arrflix-themed, .arrflix-video-active)
  - Specificity tiers + cascade order (L1 vs L2)
  - CSS load order (inline < bundle < branding.xml)
  - Recurring bug list (6 incidents now, all same anti-pattern)
  - DO NOT DO foot-gun list
  - 4-step smoke verify procedure
  - CI gates still TODO

Snapshot bumped to md5 2da61583. Prod+dev byte-identical.
2026-05-09 22:41:51 +01:00
s8n
4f13db63f9 fix v7: html-bg pin + correct video class names
Two bugs in prior video-isolation fix (452ce68):

1. .htmlVideoPlayer wrong class — Jellyfin actually uses
   .videoPlayerContainer + <video class="htmlvideoplayer"> (lowercase).
   :has(.htmlVideoPlayer) never matched, so L1 :not(:has) was always
   true — body never excluded from #000 paint, L2 (lower specificity
   0,2,1) lost to L1 (0,3,1). Replaced :has() gate with :not(.arrflix
   -video-active) — JS body class is reliable signal.

2. html background-color stayed transparent on details/video pages
   despite 5 stylesheet rules saying #000 !important. Headless Chrome
   reported computed bg as rgba(0,0,0,0) — root canvas propagation
   bug or cascade quirk. Fix: JS shim sets inline style on
   <html> with !important. Inline styles win against any stylesheet.

Result: html paints #000 (so letterbox bars are black not white),
body transparent during playback (L2 wins on equal specificity via
source order), videoPlayerContainer + <video> transparent → frames
visible. Off-video: body opaque #000 (L1 fires).

Verified DOM probe: html bg rgb(0,0,0), body rgba(0,0,0,0) when
arrflix-video-active. Both flip when class removed.
2026-05-09 22:33:45 +01:00
s8n
452ce68d7a isolate video player against opaque-bg regressions (recurring INC class)
Two-layer defense for the recurring "black screen during playback"
bug class (5+ occurrences in 24h per doc 26/28/30):

L1 (prevention): scope every black-bg rule with
:not(:has(.htmlVideoPlayer)):not(:has(#videoOsdPage)) so the rules
self-disable while a player is in the DOM. Covers body,
#reactRoot, .skinBody, .backgroundContainer, .mainAnimatedPage,
.mainAnimatedPages, .pageContainer.

L2 (override): when JS-toggled body.arrflix-video-active is set,
high-specificity (0,3,2 + tag) transparent rule wins against any
ancestor opaque-bg rule (including future regressions someone adds
without scoping). Covers all known wrappers, the
videoPlayerContainer + videoPlayerContainer-onTop, #videoOsdPage,
.libraryPage, .htmlVideoPlayer.

L3 (z-index lift): force .htmlVideoPlayer + child <video> to
z-index:9999 + isolation:isolate during playback so it floats above
any opaque ancestor that still leaks through.

Verified in playwright: with arrflix-video-active + .htmlVideoPlayer
mounted, all 7 ancestors return rgba(0,0,0,0). Without — all 7
return rgb(0,0,0). Self-disabling works.

Lesson reinforced (doc 30 roadmap open): add darkPct assertion to
bin/headless-test-v2.py + xmllint CI gate. Five recurrences without
those gates says we keep relearning this. TODO next.
2026-05-09 22:18:31 +01:00
s8n
e9d209da73 polish: pure-black bg + search bar fix + variant E nav glow + My Media hide
Grey #101010 stripe at bottom of pages: jellyfin-web theme.css:44-50
sets .backgroundContainer + html to #101010, neither Cineplex nor
prior overlay overrode it. Added rule forcing #000 across html, body,
.backgroundContainer, .skinBody, .mainAnimatedPages, .pageContainer,
#reactRoot under body.arrflix-themed.

Search input: stock cyan focus ring (#00a4dc, theme.css:262-272)
swapped for borderless slab + 2px red bottom on focus + soft red
box-shadow halo. Cineplex/Netflix-faithful.

Movies/Series nav: variant E "cinematic glow" picked from preview.
Active state = red text + text-shadow halo + font-weight 700. JS
hash matcher toggles .arrflix-nav.active when location.hash matches
#/movies.html or #/tv.html (and short forms). hashchange listener +
existing setInterval keep state in sync.

My Media row: .homePage .homeSectionsContainer .verticalSection.section0
hidden — matches prod which had it hidden via different mechanism.

Snapshot at snapshots/2026-05-09-v6-stable/index.html bumped to
md5 2c8f5d5f7c99611fa93d15c34fbe35d1; same as prod and dev.
2026-05-09 21:47:00 +01:00
s8n
92e2426734 hide back arrow on themed pages
Stock Jellyfin shows .headerBackButton on library pages
(Movies/Series). With our nav links already in headerLeft, the back
arrow is redundant clutter and confuses the home button intent.

Add .headerBackButton to the existing display:none rule under
body.arrflix-themed. Verified visually on dev (md5 c99aca0f), then
shipped to prod with overlay swap (md5 364cc890 -> c99aca0f). Both
sides byte-identical.

Snapshot at snapshots/2026-05-09-v6-stable/index.html updated.
2026-05-09 19:43:27 +01:00
s8n
a943933363 doc 30 v6-stable success + snapshot save state
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.
2026-05-09 12:52:44 +01:00
s8n
9003b55c81 favicon hijack + tonemap fix shipped to prod
Favicon: prod's older lockFavicon() shim was clobbering our injected
A-logo <link> tags every head mutation. Tag our links with
data-arrflix-icon="A" + add a hijack IIFE that re-pins the A URL on
matching tags AND removes any other large data:image/png link tags.

Tonemap: encoding.xml flipped EnableTonemapping false to true on dev
+ prod (server-side, not in repo). Doc 21 documented this fix
2026-05-08; prod was still grey-washing HDR10 sources because
setparams was relabeling PQ pixels as bt709 without zscale + tonemap
conversion. API now reports EnableTonemapping=True. Next HDR10
transcode gets the proper zscale -> tonemap -> format ffmpeg chain.

Both verified on dev first then promoted. Prod overlay md5 c6c85076
to 364cc890. dev and prod overlay byte-identical.
2026-05-09 10:17:40 +01:00
s8n
83fbfbf35e readme: add 3 ARRFLIX screenshots (detail, playback, search)
assets/screenshots/01-search.png       — search + suggestions list
assets/screenshots/02-detail-mandalorian.png — Mandalorian detail w/ backdrop
assets/screenshots/03-playback-sassy.png      — Sassy the Sasquatch playback

Embedded in README.md above 'What you get' so the repo landing page on
git.s8n.ru reads as a finished product rather than text only.
2026-05-09 10:10:38 +01:00
s8n
0c7d0aef14 middle-theme v6 + branding.xml video escape
Add ARRFLIX wordmark center, Movies/Series nav left, search right,
favicon=A-mark, auth gate so login stays stock, hide on video page.

Side-effect of branding.xml escape (<video> → &lt;video&gt;): prod's
CustomCss block now actually loads, so the INC7 transparent-video
rule reaches the browser. /Branding/Css.css 0 B → 36 256 B; doc-28
black-screen issue closed at the delivery layer.

Markers: ARRFLIX-MIDDLE-THEME-BEGIN/END (style + script) and
ARRFLIX-FAVICON-BEGIN/END (link). Idempotent.

See docs/29 for design + deploy procedure + recovery quirk.
2026-05-09 04:01:49 +01:00
s8n
3d388e8de7 doc 28 INC7-final: CSS overlay covering <video> was actual cause
Agent 6 applied SW-pin fix and marked verified via element state
(currentTime advancing, videoWidth=1920, readyState=4). Headless pixel
histogram still showed darkPct=100% — element decoded fine but CSS
overlay covered it.

Real cause: branding.xml BLACK-PASS paints .libraryPage with
#000 !important. Jellyfin OSD page renders <div id=videoOsdPage
class=libraryPage>; class match -> opaque black div above <video>.

Fix: extend transparent-scope using :has(.htmlVideoPlayer) +
#videoOsdPage selector. Post-fix darkPct=9.8% (was 100%), MNS S1E4
video frame visually paints.

Removed INC6 clear-cache-only middleware (no longer needed, was
burning HTTP cache every visit).

bin/apply-26-incident-fixes.sh extended with INC7 patch (idempotent
re-apply if branding.xml ever drifts back).

Lesson: video-element state alone is insufficient verification.
Always sample pixel histogram + canvas drawImage on the painted
viewport.
2026-05-09 03:04:41 +01:00
s8n
a4ababcfbf doc 28: record commit hash + nullstone backup path for INC7 fix 2026-05-09 02:50:21 +01:00
s8n
3a7d96aacb doc 28 + INC7: fix prod black-screen via SW cache pin
Five sibling agents converged on root cause:
jellyfin-asset-immutable Traefik router (priority 90) was matching
/web/serviceworker.js (Jellyfin PWA's actual SW filename), pinning it
with Cache-Control: public, max-age=31536000, immutable. The
priority-100 jellyfin-html-nocache router only excluded the literal
path /web/sw.js, missing serviceworker.js.

Stale SWs from earlier ARRFLIX iterations intercepted /Videos/* and
/web/* fetch events, returning cached/empty bytes. Result:
MediaSource appendBuffer got bad data -> black <video>. INC6's
Clear-Site-Data: "cache" couldn't fix it (per MDN spec, "cache"
excludes SW registrations; "storage" would have worked).

Fix: added jellyfin-sw-nocache router at priority 250 in
/opt/docker/traefik/config/dynamic.yml on nullstone, forcing
cache-no-store@file on /web/serviceworker.js + /web/sw.js. Hot-reload
via Traefik file provider, no docker restart.

Verified at the wire (curl -I /web/serviceworker.js now returns
no-cache, no-store, must-revalidate; main.jellyfin.bundle.js still
immutable as intended) and via headless Chromium probe of MNS S1E4
(33s of currentTime advance, readyState 4, videoWidth 1920x1080,
no errors, both s8n admin and USER-F user).

bin/prod-vs-dev-compare.py also lands as a one-shot diff helper used
during the investigation.
2026-05-09 02:50:00 +01:00
s8n
af2d625e4f doc 27: status snapshot 2026-05-09 visual
Visual status board: symptoms killed (8/8), roadmap (done /
pending / high / medium / deferred / strategic), library codec
matrix, repo file tree, next-click. Point-in-time after doc-26
incident closed. For ongoing roadmap see ROADMAP.md.
2026-05-09 02:23:53 +01:00
s8n
648e5d5238 doc 26 INC6: Clear-Site-Data cache-only for shim deploy
INC5 fmp4-disable shim required browser hard-reload to fire. Owner's
MNS S1E4 re-test still black-screened because cached index.html ran
old shim + fmp4-HLS bug recurred. Add Traefik response header
'Clear-Site-Data: "cache"' on /web/index.html. Cache-only is safe
(no cookie/storage wipe -> auth + localStorage preserved). One fresh
visit refetches index.html with new shim. Remove middleware after
owner confirms working, otherwise every revisit re-flushes cache.
2026-05-09 02:10:52 +01:00
s8n
b5e0d95826 doc 26: add INC5 scrollbar grey-strip subsection
Symptom + root cause + fix + lesson for the scrollbar grey strip at
very bottom of page. Patch already in bin/apply-26-incident-fixes.sh
and live in branding.xml on prod from earlier commit; this is the
documentation subsection that was missing dedicated coverage.
2026-05-09 02:05:16 +01:00
s8n
557317d104 doc 26: case CLOSED — final state + 8 forbidden patterns
All 8 owner-reported symptoms resolved across 5 iterations (INC1-5):

INC1 — index.html drift revert + :has() transparent-scope + Cineplex
       Abspielen override + encoding.xml HLS 499 fix
INC2 — pin .backdropContainer position:fixed (persistent backdrop)
INC3 — extend transparent-scope through detail-page sub-sections
INC4 — .emby-scroller transparent (kill black band behind carousels) +
       EnableTonemapping=false + 20Mbps RemoteClientBitrateLimit cap +
       headless-test-v2.py (admin+USER-F+click-play+bg-sweep)
INC5 — AV1 source re-encode (MNS S1E2/E4/E5 to H.264/AAC) +
       enableHlsFmp4=false localStorage shim +
       ::-webkit-scrollbar styled to ARRFLIX palette

Verification: headless playwright on Chrome + Firefox UA confirms MNS
S1E4 plays 1920x1080 readyState=4 currentTime advancing. Owner
double-confirmed solved.

Doc 26 final state section + 18-item forbidden-pattern checklist added
for future operators.
2026-05-09 02:03:39 +01:00
s8n
3c7cf64d1e doc 26 INC5: disable fMP4-HLS client-side + AV1 force-transcode
Two parallel fixes for MNS S1E4 black-video bug. Belt+braces.

INC5 fmp4-disable (this agent):
- Add localStorage.setItem('enableHlsFmp4', 'false') shim to
  web-overrides/index.html (idempotent, marker INC5 fmp4=false 2026-05-09)
- Forces TS segments instead of fMP4 for all HLS transcodes,
  works around upstream black-video bug with HEVC+fMP4
  (jellyfin-webos#126, jellyfin#16612)
- Browser localStorage verified false via headless playwright;
  server confirmed emitting -hls_segment_type fmp4 before fix
- Repo + deployed file md5 match: 5b212d7d60b8a2b910a2f47dd0470a09

INC5 AV1 force-transcode (parallel agent):
- Re-encoded MNS S1E1-5 AV1->H.264 in container; PlaybackInfo
  now returns DirectStream/DirectPlay=true on S1E4
- Doc additions covering the AV1 work included here since
  same file (already authored by parallel agent, not yet committed)
2026-05-09 01:58:45 +01:00
s8n
439b600740 doc 26 INC4: black band + 4K HDR slow transcode + v2 test + methodology audit
Two regressions slipped through INC1-3:

INC4a -- BLACK BAND behind every detail-page carousel
  Pre-existing 2026-05-08 home-page rule painted .emby-scroller {bg:#000
  !important} UNSCOPED. Hits every carousel inside .itemDetailPage incl
  admin-only More from Season N, More Like This. INC1-3 transparent-scope
  list missed .emby-scroller / .verticalSection / .padded-top-focusscale.
  Fixed by extending scope.

INC4b -- VIDEO 'BLACK SCREEN' on play
  Not actually black-screen. CPU-only nullstone cannot sustain real-time
  4K HEVC HDR tonemap+x264 transcode -- 0.5x realtime, ffmpeg takes ~6s
  per 3s segment. With user resume seeks adding restart overhead, total
  wait ~18s before browser readyState rises. User saw black, gave up.
  Fix: disable EnableTonemapping (R&M fake HDR per doc 21) + cap
  RemoteClientBitrateLimit=20Mbps on every user (1080p target, no 4K
  scale). Headless v2 test confirms HEVC + AV1 episodes now hit
  readyState=3/4 within wait window; 4K HDR R&M still slow (heaviest).

INC4 testing methodology audit -- bin/headless-test-v2.py
  v1 only logged in as USER-F and never clicked Play. v2 runs both admin
  and USER-F, walks 3 codec-tagged items per role (HEVC/AV1/H.264),
  clicks Play, captures <video> state, sweeps DOM for opaque bgs over
  backdrop layer. False positives: off-viewport #reactRoot + collapsed
  .mainDrawer (negative coords). Allowlist refinement TODO.

Open: 4K HDR sources still slow even post-fix. Real fix path = pre-
transcode masters to 1080p H.264 SDR via separate batch, OR migrate to
10.11.8 with vaapi/qsv driver fixed.
2026-05-09 01:46:47 +01:00
s8n
9fe7644701 doc 26 INC2+INC3: pin backdrop, transparent sub-sections
After INC1 fixed the Abspielen + first-fold backdrop, owner reported black
band hiding artwork in More from Season 1 / below-fold sections. Two more
patches required:

INC2 — pin .backdropContainer + .backgroundContainer position:fixed; height
100vh so backdrop persists during scroll. Added vertical fade ::after.

INC3 — extend transparent-scope to ALL detail-page sub-sections
(.detailVerticalSection, .scrollSlider, .padded-bottom-page,
.itemsContainer etc) so section wrappers don't paint over the pinned
backdrop section by section.

bin/headless-test.py now takes top + scrolled viewport screenshots.
full_page=True hides position:fixed regressions, dual-screenshot exposes
them. Use both to bisect.

bin/apply-26-incident-fixes.sh updated with INC2+INC3.

Open: AV1+Opus playback (Mike Nolan Show) still tracked for 10.11.8
migration. .detailLogo regression possible — test in actual browser.
2026-05-09 01:21:01 +01:00
s8n
0dd3539838 doc 26 + bin: incident 2026-05-09 + headless smoke-test
Symptoms: Page Unresponsive on poster grid, posters missing then black
backdrops, 'Abspielen' German Play button surviving Traefik+force-english
chases, video black-screen on play.

Root causes (different from initial guesses):
- Browser hangs: deployed index.html drifted ahead of repo; uncommitted
  forceEnglishUI() text-walker MutationObserver froze main thread on poster
  lazy-load. Reverted to repo HEAD.
- 'Abspielen': Cineplex theme HARDCODES German via 'content:' ::after rule
  -- not a Jellyfin locale issue. Doc 25 already proved per-user UICulture
  is theatre. Override CSS with content: 'Play'.
- Backdrops black: BLACK-PASS CustomCss block paints #000 !important on
  .layout-desktop / .pageContainer -- occludes backdrop layer (z-index:-1).
  Existing transparent-scope rule used body.itemDetailPage selector that
  doesn't match in 10.10.3 (body class is libraryDocument). Replaced with
  :has(.itemDetailPage) ancestor scoping.
- HLS 499: encoding.xml had EnableThrottling+EnableSegmentDeletion=true,
  segments reaped before browser re-request. Disabled both.

Verified via new bin/headless-test.py (playwright Chromium login + screenshot
+ computed-style probe). Fixes idempotent and re-runnable via new
bin/apply-26-incident-fixes.sh.

Open: AV1+Opus items still black-screen in Chrome due to DirectStream
codec-tag mislabel bug. Tracked for 10.11.8 migration.
2026-05-09 01:11:38 +01:00
s8n
1bfd122f6a force English everywhere on all 9 users + wrapper
AudioLanguagePreference=eng, SubtitleLanguagePreference=eng,
SubtitleMode=Default, PlayDefaultAudioTrack=true, UICulture=en-US.
Per-user Configuration POST applied to all 9 existing users + wrapper
updated for future creations.
2026-05-08 23:46:13 +01:00
s8n
f01cdd4007 doc 25: english leak deep-dive (Abspielen post-lockdown) 2026-05-08 22:09:59 +01:00
s8n
c972605af9 import-log: youtube-sassy-the-sasquatch 2026-05-08 — eps 1-5 (ep6 age-restricted) 2026-05-08 22:03:42 +01:00
s8n
3dd5d3c6f2 doc 22: jellyfin runtime perf audit (read-only)
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.
2026-05-08 17:51:06 +01:00
s8n
ea8dc71617 doc 23: arrflix edge / network / browser-load-path perf audit (read-only)
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.
2026-05-08 17:50:52 +01:00
s8n
7e63e5e236 audit: storage I/O for arrflix media path 2026-05-08 17:47:19 +01:00
s8n
16b3f831d9 audit: rick-and-morty color/HDR diagnosis 2026-05-08 17:45:34 +01:00
s8n
494ee4e814 ROADMAP: action owner = s8n 2026-05-08 17:25:56 +01:00
s8n
d7aa38909d ROADMAP: rewrite with snapshot table, status emojis, Open above Done
- Snapshot table at top (live stats: prod/dev URLs, theme, eps, disk, users)
- Open items first (high/med/low + effort + blocker)
- Blocked + Deferred middle
- Done section moved to bottom per owner request
- Visual: emoji-tagged severity, tables, scannable
- Updated counts: 6 series, 175 eps, 156G free, 9 users, 17 docs
2026-05-08 17:20:37 +01:00
s8n
21a69672f1 doc 19: english-only lockdown audit (read-only baseline)
Cross-layer audit supplementing docs 15 and 16. Confirms doc-15 root
cause still live (8/8 users have UICulture absent; force-english script
unrun), enumerates 93 served locale chunks (de-json contains 'Abspielen'),
and proposes 4-pronged remediation: per-user POST + wrapper patch +
Traefik Accept-Language rewrite + navigator.language shim.

Read-only. No Jellyfin mutations performed.
2026-05-08 17:05:11 +01:00
s8n
27b737ff2f docs+bin: English-only lockdown — re-apply runner + doc 20
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.
2026-05-08 17:04:12 +01:00
s8n
395c6d2833 web: english-lockdown shim — pin locale + hide switchers 2026-05-08 17:04:03 +01:00
s8n
fbe0eca509 readme: tighten tagline to match repo bio (no compromise) 2026-05-08 16:54:40 +01:00
s8n
5026669f13 drop tv.s8n.ru — arrflix.s8n.ru is canonical
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.
2026-05-08 16:46:26 +01:00
s8n
d5eff7cb27 strip: remove Claude attribution from ROADMAP + audit docs
ROADMAP owner column 's8n' (was 'claude'). Audit-run-by lines in
docs/{11,14,16} reattributed to s8n. Removed CLAUDE.md memory ref
from docs/04 hosts-pin note.
2026-05-08 16:44:49 +01:00
s8n
1a69930120 docs: add 00-overview as technical landing page 2026-05-08 16:37:30 +01:00
s8n
12e9880d76 readme: rebrand as ARRFLIX brand-facing landing 2026-05-08 16:36:43 +01:00
s8n
25b278f9cb Settings drawer hide v2: target a.btnSettings + data-itemid
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 f3a32c3).
2026-05-08 16:05:36 +01:00
s8n
6c66f601c6 redact: scrub leaked Jellyfin admin API token from public repo
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/.
2026-05-08 15:36:14 +01:00
s8n
f3a32c335c doc 17: dev mirror + Settings drawer leak fix (dev only, no prod swap) 2026-05-08 13:34:04 +01:00
s8n
742dbc68c7 doc 16: Jellyfin branding leaks audit (read-only) 2026-05-08 04:29:26 +01:00
s8n
9e7d4077d7 doc 14: theme audit + detail-page backdrop diagnosis (read-only) 2026-05-08 04:27:28 +01:00
s8n
722c447c40 doc 13: read-only optimization audit 2026-05-08 04:24:21 +01:00
s8n
6b1eea2d2b doc 15: force English UI for all users (plan + script)
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>
2026-05-08 04:22:04 +01:00
s8n
22236e2dc8 doc 11: NeutralFin render audit (read-only)
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.
2026-05-08 04:18:07 +01:00