Skip to content

Sidebar refactor + app multilanguage + popup resize/drag#8

Open
FieteGM wants to merge 5 commits into
mainfrom
refactor/sidebar-i18n-resize
Open

Sidebar refactor + app multilanguage + popup resize/drag#8
FieteGM wants to merge 5 commits into
mainfrom
refactor/sidebar-i18n-resize

Conversation

@FieteGM
Copy link
Copy Markdown
Owner

@FieteGM FieteGM commented May 28, 2026

Summary

  • Module extraction: pull helpers out of bingewatcher.py into bw/{constants,settings_types,shutdown,session,episode_logic,player_loop}.py. The 10.5k-line god-file shrinks to ~8.8k.
  • JS out of Python: sidebar JS strings move to bw/assets/*.js and the main bundle is split into 7 chunk files under bw/assets/sidebar/, assembled by bw/ui/{header,series_list,cast_modal,tools_menu,settings_panel}.py. bw/ui_sidebar.py becomes a re-export shim, byte-identity verified by tests.
  • Test infra: tests/ with pytest wrappers, ruff + mypy config, CI matrix, slow-marker for live-network selftests. 159 tests pass (1 known network deselect).
  • JS bug fixes: 8 SyntaxErrors where i18n fallbacks used ${...} in single-quoted strings instead of backticks (privacy popover, cast modal, bookmarks label, audio hint). node --check clean.
  • Popup UX: resize grip + persisted size for Tools, Settings, Skip-Times. Settings X anchored right; Tools menu gets drag handle + close X; dropdowns get fixed 180px column with dark theme + custom chevron + dark <option> background.
  • i18n sweep: ~80 new EN+DE keys (456 = 456 parity). Covers cast modal, OS-hardening modal, right-click context menu, Tools menu, privacy session popover, FilmPalast empty state, bookmarks, audio picker, skip-times, captcha banner, s.to gate, Firefox-detect CLI help. Toolbar strings translated directly (iframe-isolated, no window.__bwI18n access).

Notable behaviour-preservation guarantees

  • bingewatcher.play_episodes_loop is bw.player_loop.play_episodes_loop → same function object, just relocated
  • ui_sidebar.SIDEBAR_INJECT_JS byte-identical to the on-disk bw/assets/sidebar_inject.js, asserted by tests/test_imports.py::test_sidebar_bundle_byte_identity
  • Captcha / s.to-gate Python messages resolve via i18n.t() at call time using the user's selected language, with English fallback if the catalog can't load

Test plan

  • pytest tests/ -m "not slow" — 137 fast tests
  • pytest tests/ --deselect "tests/test_selftests.py::test_selftest_phase[selftest_screen_pipeline]" — 159 tests
  • node --check bw/assets/sidebar_inject.js
  • ruff check bw/ui/ bw/player_loop.py bw/ui_sidebar.py tests/
  • Bundle byte-identity: SHA + length match between assembled and disk
  • Manual: open Settings, Tools, Skip-Times — drag, resize, double-click reset, dark dropdown, X stays on right edge when resized wider
  • Manual: switch language EN ↔ DE — cast modal, OS-hardening modal, context menu, Tools/Settings all flip
  • Manual on a fresh machine: Firefox-not-found CLI message (English now)

Module extraction
- Extract bingewatcher.py helpers into bw/{constants,settings_types,
  shutdown,session,episode_logic,player_loop}.py
- Move sidebar JS out of inline Python r-strings into bw/assets/
  (sidebar_inject.js, sidebar_polish.js, continue_watching.js)
- Split sidebar_inject.js into 7 chunk files under bw/assets/sidebar/
  and aggregate via bw/ui/{header,series_list,cast_modal,tools_menu,
  settings_panel}.py. bw/ui_sidebar.py becomes a re-export shim.
- bingewatcher.py shrinks ~10.5k -> ~8.8k lines

Test + lint infrastructure
- Add tests/ with pytest wrappers around the legacy selftest_phase*.py
  modules, plus targeted suites (imports, i18n parity, settings
  validation, JS asset integrity, shutdown markers, episode logic,
  Selenium smoke against tests/fixtures/jw_player.html)
- pyproject.toml: ruff + mypy config, per-file overrides
- pytest.ini: slow marker for live-network tests
- .github/workflows/ci.yml: lint, typecheck, fast+slow matrix
- scripts/_split_sidebar_inject.py, _extract_player_loop.py and
  friends — reproducible build steps for the JS chunking and the
  play_loop helper-binding migration

JS bug fixes
- Fix 8 SyntaxErrors in sidebar_inject.js where i18n fallback
  expressions used template literal ${...} syntax inside
  single-quoted strings instead of backticks (privacy popover, cast
  modal, audio hint, bookmarks label). Validated with node --check.

UX polish
- Add window.__bwAttachResize helper: bottom-right grip on Tools,
  Settings, and SeriesSkip popups. Minimum size = default; max =
  viewport - 16. Persists per-popup to localStorage. Double-click
  to reset.
- Tools menu: drag handle on TOOLS heading with grip glyph + drag
  hint, close X anchored right via margin-left:auto
- Settings panel: width bumped 480 -> min(540, viewport-40); X
  pinned to right edge; dropdowns get fixed 180px column,
  color-scheme:dark + custom chevron + dark-styled <option>; text
  inputs match the dropdown column; ellipsis on long labels
- Cast modal: "Auf Handy schauen" -> cast_modal_title ("Stream to
  another device" / "Auf anderes Gerat streamen")
- Watchlist cleanup: drop entries matching auto-add fingerprint
  (S1E1, pos=0, ts=0)

i18n sweep
- Add ~80 keys (EN+DE parity, 456 = 456). Covers: cast modal body
  + buttons + privacy details, OS-hardening modal (status,
  counts, fix instructions, buttons), context menu (pin,
  watchlist, mark watched, reset progress, delete), Tools menu
  drag hint + close, resize grip hint, privacy session popover
  (verdict, host table headers, route badges), FilmPalast
  no-results, bookmarks label, audio picker, skip-times heading,
  toolbar strings (translated directly since the iframe context
  cant reach window.__bwI18n)
- Python side: bw/captcha.py and bw/sto_player_gate.py resolve
  user-facing messages via i18n.t() at call time
- bw/firefox_detect.py, bw/screen_stream.py CLI help fully
  translated to English
- bw/player_loop.py, bingewatcher.py docstrings: German -> English

Selftest updates
- selftest_phase1, selftest_phase4: also scan bw/player_loop.py
  for content that moved out of bingewatcher.py
- selftest_privacy_ui: scan ui_sidebar.SIDEBAR_INJECT_JS (the
  assembled bundle) instead of just ui_sidebar.py source, and
  align checked IDs with the post-Task-13 privacy tab redesign

Verification
- 159 tests pass (1 deselected: selftest_screen_pipeline needs
  live HLS, pre-existing)
- node --check on bundle: clean
- i18n parity: EN 456 / DE 456
- Bundle byte-identity preserved between bw/assets/sidebar/* chunks
  and bw/assets/sidebar_inject.js
Comment on lines +309 to +655
p.innerHTML = (
'<style>' + (
'#bwSettingsPanel .bw-set-head {' +
'display:flex;align-items:center;gap:10px;padding:14px 18px 12px;' +
'border-bottom:1px solid rgba(255,255,255,.08);cursor:move;flex-shrink:0;' +
'}' +
'#bwSettingsPanel .bw-set-title {' +
'font:700 16px/1 system-ui;' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' +
'-webkit-background-clip:text;-webkit-text-fill-color:transparent;flex-shrink:0;' +
'}' +
'#bwSettingsPanel .bw-set-search {' +
'flex:1 1 auto;min-width:0;width:100%;' +
'padding:6px 10px;border-radius:8px;border:1px solid rgba(255,255,255,.10);' +
'background:rgba(2,6,23,.55);color:#e2e8f0;font:500 12px/1.2 system-ui;outline:none;' +
'cursor:text;' +
'}' +
'#bwSettingsPanel .bw-set-search:focus { border-color:rgba(99,102,241,.55); }' +
// Close button — ``margin-left:auto`` keeps it
// pinned to the right edge of the header even
// when the user resizes the panel wider than
// the search field naturally fills.
'#bwSettingsPanel .bw-set-close {' +
'margin-left:auto;flex-shrink:0;' +
'background:transparent;border:0;color:#94a3b8;cursor:pointer;' +
'font-size:22px;line-height:1;padding:2px 6px;border-radius:6px;' +
'}' +
'#bwSettingsPanel .bw-set-close:hover { background:rgba(255,255,255,.08); color:#f8fafc; }' +
// Tabs
'#bwSettingsPanel .bw-set-tabs {' +
'display:flex;gap:2px;padding:0 12px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.18);' +
'flex-shrink:0;' +
'}' +
'#bwSettingsPanel .bw-set-tab {' +
'flex:1;padding:10px 8px;background:transparent;border:0;border-bottom:2px solid transparent;' +
'color:#94a3b8;font:600 12px/1.2 system-ui;cursor:pointer;display:flex;align-items:center;gap:6px;justify-content:center;' +
'transition:color .15s ease,border-color .15s ease;letter-spacing:.2px;' +
'}' +
'#bwSettingsPanel .bw-set-tab:hover { color:#cbd5e1; }' +
'#bwSettingsPanel .bw-set-tab.active { color:#f8fafc; border-bottom-color:#6366f1; }' +
'#bwSettingsPanel .bw-set-tab .bw-tab-count {' +
'display:inline-flex;align-items:center;justify-content:center;' +
'min-width:18px;height:18px;padding:0 5px;border-radius:9px;' +
'background:rgba(99,102,241,.22);color:#c7d2fe;font:600 10px/1 ui-monospace,Menlo;' +
'}' +
'#bwSettingsPanel .bw-set-tab.active .bw-tab-count {' +
'background:rgba(99,102,241,.42);color:#e0e7ff;' +
'}' +
// Body
'#bwSettingsPanel .bw-set-body {' +
'padding:14px 18px 16px;overflow-y:auto;flex:1;min-height:0;' +
'}' +
'#bwSettingsPanel .bw-set-pane[hidden] { display:none; }' +
'#bwSettingsPanel .bw-set-section-title {' +
'font:700 11px/1 system-ui;letter-spacing:.6px;text-transform:uppercase;' +
'color:#64748b;margin:8px 4px 8px;' +
'}' +
// Row — two columns: label (grows) + control (fixed).
// ``min-width:0`` on the label lets long labels
// truncate with an ellipsis rather than pushing the
// control off the right edge of the panel.
'#bwSettingsPanel .bw-set-row {' +
'display:flex;align-items:center;gap:14px;padding:10px 6px;' +
'border-radius:8px;transition:background .15s ease;' +
'}' +
'#bwSettingsPanel .bw-set-row[hidden] { display:none; }' +
'#bwSettingsPanel .bw-set-row:hover { background:rgba(255,255,255,.03); }' +
'#bwSettingsPanel .bw-set-row.bw-search-hit { background:rgba(99,102,241,.10); }' +
'#bwSettingsPanel .bw-set-row > label {' +
'flex:1;min-width:0;display:flex;align-items:center;gap:8px;' +
'cursor:pointer;color:#e2e8f0;' +
'}' +
'#bwSettingsPanel .bw-set-label {' +
'font:600 13px/1.35 system-ui;' +
'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;' +
'min-width:0;' +
'}' +
// Info icon button
'#bwSettingsPanel .bw-set-info {' +
'width:18px;height:18px;border-radius:50%;padding:0;' +
'background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);' +
'color:#94a3b8;font:700 11px/1 ui-serif,Georgia;cursor:pointer;font-style:italic;' +
'display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;' +
'transition:background .15s ease,color .15s ease,border-color .15s ease;' +
'}' +
'#bwSettingsPanel .bw-set-info:hover {' +
'background:rgba(99,102,241,.22);color:#c7d2fe;border-color:rgba(99,102,241,.45);' +
'}' +
// Restart badge
'#bwSettingsPanel .bw-set-restart {' +
'display:inline-flex;align-items:center;justify-content:center;' +
'width:18px;height:18px;border-radius:50%;' +
'background:rgba(245,158,11,.16);color:#fbbf24;' +
'font:600 11px/1 system-ui;cursor:help;flex-shrink:0;' +
'}' +
// Toggle switch (reused from old)
'#bwSettingsPanel .bw-set-toggle { position:relative;width:34px;height:20px;flex-shrink:0;cursor:pointer; }' +
'#bwSettingsPanel .bw-set-toggle input { opacity:0;width:0;height:0; }' +
'#bwSettingsPanel .bw-set-toggle .bw-set-slider {' +
'position:absolute;inset:0;background:rgba(255,255,255,.14);' +
'border-radius:99px;transition:background .2s ease;' +
'}' +
'#bwSettingsPanel .bw-set-toggle .bw-set-slider::before {' +
'content:"";position:absolute;top:2px;left:2px;' +
'width:16px;height:16px;border-radius:50%;background:#fff;' +
'box-shadow:0 1px 3px rgba(0,0,0,.35);transition:left .2s cubic-bezier(.22,.61,.36,1);' +
'}' +
'#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider {' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' +
'}' +
'#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider::before { left:16px; }' +
// Select — explicit dark theming so the native
// dropdown panel (the OS popup) stays dark too.
// ``color-scheme:dark`` switches the UA picker
// to a dark palette on Chromium/Edge; Firefox
// honours the styled ``option`` elements below.
//
// FIXED-WIDTH control column: ``flex:0 0 180px``
// keeps every dropdown at the same predictable
// width regardless of the longest option, which
// means rows align cleanly and there's no
// horizontal scrollbar from a stretched select.
'#bwSettingsPanel select {' +
'flex:0 0 180px;width:180px;box-sizing:border-box;' +
'background-color:rgba(255,255,255,.06);' +
'border:1px solid rgba(255,255,255,.14);' +
'color:#e2e8f0;border-radius:8px;padding:6px 30px 6px 12px;' +
'font:600 12px/1.2 system-ui;cursor:pointer;' +
'color-scheme:dark;' +
'-webkit-appearance:none;-moz-appearance:none;appearance:none;' +
'background-image:url("data:image/svg+xml;utf8,' +
'<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'6\' viewBox=\'0 0 10 6\'>' +
'<path fill=\'%2394a3b8\' d=\'M0 0l5 6 5-6z\'/></svg>");' +
'background-repeat:no-repeat;background-position:right 10px center;' +
'transition:border-color .15s ease,background-color .15s ease;' +
'text-overflow:ellipsis;' +
'}' +
'#bwSettingsPanel select:hover {' +
'border-color:rgba(99,102,241,.45);background-color:rgba(255,255,255,.08);' +
'}' +
'#bwSettingsPanel select:focus {' +
'border-color:rgba(99,102,241,.65);outline:none;' +
'box-shadow:0 0 0 2px rgba(99,102,241,.22);' +
'}' +
// Firefox respects styled options. Chromium also
// picks these up when the OS supports dark UA.
'#bwSettingsPanel select option {' +
'background-color:#0f172a;color:#e2e8f0;' +
'padding:6px 10px;' +
'}' +
'#bwSettingsPanel select option:checked {' +
'background:linear-gradient(0deg,rgba(99,102,241,.35),rgba(99,102,241,.35)),#0f172a;' +
'color:#ffffff;' +
'}' +
'#bwSettingsPanel select option:disabled {' +
'color:#475569;' +
'}' +
// Range slider — fixed 140px so it lines up with
// the dropdown column. The companion value
// (.bw-set-range-val) sits to the right.
'#bwSettingsPanel input[type=range] {' +
'flex:0 0 140px;width:140px;height:4px;-webkit-appearance:none;appearance:none;' +
'background:rgba(255,255,255,.10);border-radius:99px;outline:none;cursor:pointer;' +
'}' +
'#bwSettingsPanel input[type=range]::-webkit-slider-thumb {' +
'-webkit-appearance:none;width:14px;height:14px;border-radius:50%;' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' +
'}' +
'#bwSettingsPanel input[type=range]::-moz-range-thumb {' +
'width:14px;height:14px;border-radius:50%;' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' +
'}' +
'#bwSettingsPanel .bw-set-range-val {' +
'flex:0 0 38px;font:600 12px/1 system-ui;color:#cbd5e1;' +
'font-variant-numeric:tabular-nums;text-align:right;' +
'}' +
// Text input — same fixed 180px column as the
// selects so the right edge of every control
// lines up across all rows.
'#bwSettingsPanel input[type=text] {' +
'flex:0 0 180px;width:180px;box-sizing:border-box;' +
'padding:6px 10px;border-radius:8px;' +
'border:1px solid rgba(255,255,255,.12);background:rgba(2,6,23,.45);' +
'color:#e2e8f0;font:500 12px/1.3 system-ui;min-width:0;' +
'transition:border-color .15s ease,background-color .15s ease;' +
'}' +
'#bwSettingsPanel input[type=text]:hover {' +
'border-color:rgba(99,102,241,.4);background-color:rgba(2,6,23,.6);' +
'}' +
'#bwSettingsPanel input[type=text]:focus {' +
'border-color:rgba(99,102,241,.65);outline:none;' +
'box-shadow:0 0 0 2px rgba(99,102,241,.22);' +
'}' +
// Privacy score
'#bwSettingsPanel .bw-priv-score {' +
'padding:14px 14px;border-radius:12px;cursor:pointer;' +
'background:linear-gradient(135deg,rgba(99,102,241,.10),rgba(139,92,246,.06));' +
'border:1px solid rgba(99,102,241,.22);margin-bottom:14px;' +
'}' +
'#bwSettingsPanel .bw-priv-score:hover { border-color:rgba(99,102,241,.40); }' +
'#bwSettingsPanel .bw-priv-score-row {' +
'display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;' +
'}' +
'#bwSettingsPanel .bw-priv-score-heading { font:700 13px/1 system-ui;color:#cbd5e1; }' +
'#bwSettingsPanel .bw-priv-score-num {' +
'font:800 28px/1 system-ui;font-variant-numeric:tabular-nums;' +
'}' +
'#bwSettingsPanel .bw-priv-score-bar {' +
'width:100%;height:6px;border-radius:3px;background:rgba(2,6,23,.5);overflow:hidden;' +
'}' +
'#bwSettingsPanel .bw-priv-score-fill {' +
'height:100%;transition:width .35s ease,background .25s ease;' +
'}' +
'#bwSettingsPanel .bw-priv-score-sub {' +
'font:500 11px/1.4 system-ui;color:#94a3b8;margin-top:6px;' +
'}' +
// Presets
'#bwSettingsPanel .bw-priv-preset-block { margin-bottom:14px; }' +
'#bwSettingsPanel .bw-priv-presets {' +
'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;' +
'}' +
'#bwSettingsPanel .bw-priv-preset {' +
'display:flex;flex-direction:column;align-items:flex-start;gap:4px;' +
'padding:10px 10px;border-radius:10px;cursor:pointer;text-align:left;' +
'background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);' +
'color:#cbd5e1;transition:all .15s ease;' +
'}' +
'#bwSettingsPanel .bw-priv-preset:hover {' +
'background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.18);' +
'}' +
'#bwSettingsPanel .bw-priv-preset.active {' +
'background:linear-gradient(135deg,rgba(99,102,241,.18),rgba(139,92,246,.10));' +
'border-color:rgba(99,102,241,.55);color:#e0e7ff;' +
'}' +
'#bwSettingsPanel .bw-priv-preset-name { font:700 12px/1 system-ui; }' +
'#bwSettingsPanel .bw-priv-preset-desc {' +
'font:500 10px/1.3 system-ui;color:#94a3b8;' +
'}' +
'#bwSettingsPanel .bw-priv-preset.active .bw-priv-preset-desc { color:#c7d2fe; }' +
'#bwSettingsPanel .bw-priv-custom-pill {' +
'display:inline-block;margin-top:8px;padding:2px 8px;border-radius:8px;' +
'background:rgba(245,158,11,.16);border:1px solid rgba(245,158,11,.30);' +
'color:#fbbf24;font:600 11px/1.4 system-ui;' +
'}' +
// Show advanced toggle
'#bwSettingsPanel .bw-priv-advanced-toggle {' +
'display:flex;align-items:center;gap:8px;width:100%;' +
'padding:9px 12px;border-radius:10px;cursor:pointer;' +
'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);' +
'color:#94a3b8;font:600 12px/1 system-ui;text-align:left;margin-bottom:10px;' +
'transition:background .15s ease,color .15s ease;' +
'}' +
'#bwSettingsPanel .bw-priv-advanced-toggle:hover {' +
'background:rgba(255,255,255,.06);color:#cbd5e1;' +
'}' +
'#bwSettingsPanel .bw-priv-advanced-toggle.open { color:#cbd5e1; }' +
'#bwSettingsPanel .bw-priv-advanced-chev {' +
'font-size:10px;color:#94a3b8;' +
'}' +
// Forensik card
'#bwSettingsPanel .bw-forensik-card {' +
'padding:12px;border-radius:12px;' +
'background:rgba(2,6,23,.4);border:1px solid rgba(255,255,255,.08);' +
'margin-bottom:10px;' +
'}' +
'#bwSettingsPanel .bw-forensik-card-head {' +
'display:flex;align-items:center;gap:6px;margin-bottom:2px;' +
'}' +
'#bwSettingsPanel .bw-forensik-card-sub {' +
'font:500 11px/1.4 system-ui;color:#64748b;margin-bottom:10px;' +
'}' +
'#bwSettingsPanel .bw-forensik-tiles {' +
'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:8px;' +
'}' +
'#bwSettingsPanel .bw-forensik-tile {' +
'display:flex;flex-direction:column;align-items:center;gap:4px;' +
'padding:10px 6px;border-radius:10px;cursor:pointer;text-align:center;' +
'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);' +
'color:#cbd5e1;transition:all .15s ease;' +
'}' +
'#bwSettingsPanel .bw-forensik-tile:hover {' +
'background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.20);' +
'}' +
'#bwSettingsPanel .bw-forensik-tile.active {' +
'background:linear-gradient(135deg,rgba(99,102,241,.16),rgba(139,92,246,.08));' +
'border-color:rgba(99,102,241,.55);' +
'}' +
'#bwSettingsPanel .bw-forensik-tile-label {' +
'font:700 12px/1 system-ui;color:#f1f5f9;margin-top:2px;' +
'}' +
'#bwSettingsPanel .bw-forensik-tile-desc {' +
'font:500 10px/1.3 system-ui;color:#94a3b8;' +
'}' +
'#bwSettingsPanel .bw-forensik-sub[data-show-mode]:not([data-active="1"]) { display:none; }' +
// Undo toast
'#bwSettingsPanel .bw-set-undo {' +
'position:absolute;left:50%;bottom:12px;transform:translateX(-50%);' +
'padding:8px 14px;border-radius:8px;background:rgba(15,23,42,.95);' +
'border:1px solid rgba(99,102,241,.45);color:#cbd5e1;' +
'font:600 12px/1 system-ui;display:flex;align-items:center;gap:8px;' +
'box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:0;pointer-events:none;' +
'transition:opacity .2s ease;z-index:2;' +
'}' +
'#bwSettingsPanel .bw-set-undo.show { opacity:1;pointer-events:auto; }' +
'#bwSettingsPanel .bw-set-undo button {' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);color:#fff;' +
'border:0;border-radius:6px;padding:4px 10px;cursor:pointer;font:700 11px/1 system-ui;' +
'}' +
'#bwSettingsPanel .bw-set-search-empty {' +
'padding:20px 12px;color:#94a3b8;font:500 12px/1.4 system-ui;text-align:center;' +
'}' +
// Info popover
'#bwSetInfoPopover {' +
'position:fixed;width:300px;padding:14px 16px;border-radius:12px;' +
'background:linear-gradient(180deg,rgba(15,23,42,.99),rgba(2,6,23,.99));' +
'border:1px solid rgba(99,102,241,.42);color:#e2e8f0;' +
'box-shadow:0 16px 48px rgba(0,0,0,.55), 0 0 0 1px rgba(99,102,241,.12);' +
'z-index:2147483647;font:500 12px/1.5 system-ui;' +
'}' +
'#bwSetInfoPopover .bw-info-close {' +
'position:absolute;right:6px;top:4px;background:transparent;border:0;' +
'color:#94a3b8;cursor:pointer;font-size:18px;padding:2px 6px;' +
'}'
) + '</style>' +
'<div class="bw-set-head" id="bwSettingsDragHandle">' +
'<div class="bw-set-title">Settings</div>' +
'<input id="bwSetSearch" type="text" class="bw-set-search" placeholder="' + T('settings_search_placeholder','Search settings…').replace(/"/g, '&quot;') + '" autocomplete="off"/>' +
'<button id="bwCloseSettings" class="bw-set-close" type="button" aria-label="' + T('hotkey_close','Close') + '">&times;</button>' +
'</div>' +
'<div class="bw-set-tabs" role="tablist">' +
'<button class="bw-set-tab active" data-tab="daily">' + T('settings_tab_daily','Daily') + ' <span class="bw-tab-count" data-count-for="daily">0</span></button>' +
'<button class="bw-set-tab" data-tab="playback">' + T('settings_tab_playback','Playback') + ' <span class="bw-tab-count" data-count-for="playback">0</span></button>' +
'<button class="bw-set-tab" data-tab="privacy">' + T('settings_tab_privacy','Privacy') + ' <span class="bw-tab-count" data-count-for="privacy">0</span></button>' +
'<button class="bw-set-tab" data-tab="advanced">' + T('settings_tab_advanced','Advanced') + ' <span class="bw-tab-count" data-count-for="advanced">0</span></button>' +
'</div>' +
'<div class="bw-set-body" id="bwSetBody">' +
'<div class="bw-set-pane" data-tab-pane="daily">' + dailyPaneHTML() + '</div>' +
'<div class="bw-set-pane" data-tab-pane="playback" hidden>' + playbackPaneHTML() + '</div>' +
'<div class="bw-set-pane" data-tab-pane="privacy" hidden>' + privacyPaneHTML() + '</div>' +
'<div class="bw-set-pane" data-tab-pane="advanced" hidden>' + advancedPaneHTML() + '</div>' +
'<div class="bw-set-search-empty" id="bwSetSearchEmpty" hidden>' + T('settings_search_no_results','No settings match your search.') + '</div>' +
'</div>' +
'<div class="bw-set-undo" id="bwSetUndo" role="status" aria-live="polite">' +
'<span id="bwSetUndoMsg">' + T('settings_saved_undo','Saved · Undo') + '</span>' +
'<button type="button" id="bwSetUndoBtn">' + T('settings_undo_btn','Undo') + '</button>' +
'</div>'
);
Comment on lines +309 to +655
p.innerHTML = (
'<style>' + (
'#bwSettingsPanel .bw-set-head {' +
'display:flex;align-items:center;gap:10px;padding:14px 18px 12px;' +
'border-bottom:1px solid rgba(255,255,255,.08);cursor:move;flex-shrink:0;' +
'}' +
'#bwSettingsPanel .bw-set-title {' +
'font:700 16px/1 system-ui;' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' +
'-webkit-background-clip:text;-webkit-text-fill-color:transparent;flex-shrink:0;' +
'}' +
'#bwSettingsPanel .bw-set-search {' +
'flex:1 1 auto;min-width:0;width:100%;' +
'padding:6px 10px;border-radius:8px;border:1px solid rgba(255,255,255,.10);' +
'background:rgba(2,6,23,.55);color:#e2e8f0;font:500 12px/1.2 system-ui;outline:none;' +
'cursor:text;' +
'}' +
'#bwSettingsPanel .bw-set-search:focus { border-color:rgba(99,102,241,.55); }' +
// Close button — ``margin-left:auto`` keeps it
// pinned to the right edge of the header even
// when the user resizes the panel wider than
// the search field naturally fills.
'#bwSettingsPanel .bw-set-close {' +
'margin-left:auto;flex-shrink:0;' +
'background:transparent;border:0;color:#94a3b8;cursor:pointer;' +
'font-size:22px;line-height:1;padding:2px 6px;border-radius:6px;' +
'}' +
'#bwSettingsPanel .bw-set-close:hover { background:rgba(255,255,255,.08); color:#f8fafc; }' +
// Tabs
'#bwSettingsPanel .bw-set-tabs {' +
'display:flex;gap:2px;padding:0 12px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.18);' +
'flex-shrink:0;' +
'}' +
'#bwSettingsPanel .bw-set-tab {' +
'flex:1;padding:10px 8px;background:transparent;border:0;border-bottom:2px solid transparent;' +
'color:#94a3b8;font:600 12px/1.2 system-ui;cursor:pointer;display:flex;align-items:center;gap:6px;justify-content:center;' +
'transition:color .15s ease,border-color .15s ease;letter-spacing:.2px;' +
'}' +
'#bwSettingsPanel .bw-set-tab:hover { color:#cbd5e1; }' +
'#bwSettingsPanel .bw-set-tab.active { color:#f8fafc; border-bottom-color:#6366f1; }' +
'#bwSettingsPanel .bw-set-tab .bw-tab-count {' +
'display:inline-flex;align-items:center;justify-content:center;' +
'min-width:18px;height:18px;padding:0 5px;border-radius:9px;' +
'background:rgba(99,102,241,.22);color:#c7d2fe;font:600 10px/1 ui-monospace,Menlo;' +
'}' +
'#bwSettingsPanel .bw-set-tab.active .bw-tab-count {' +
'background:rgba(99,102,241,.42);color:#e0e7ff;' +
'}' +
// Body
'#bwSettingsPanel .bw-set-body {' +
'padding:14px 18px 16px;overflow-y:auto;flex:1;min-height:0;' +
'}' +
'#bwSettingsPanel .bw-set-pane[hidden] { display:none; }' +
'#bwSettingsPanel .bw-set-section-title {' +
'font:700 11px/1 system-ui;letter-spacing:.6px;text-transform:uppercase;' +
'color:#64748b;margin:8px 4px 8px;' +
'}' +
// Row — two columns: label (grows) + control (fixed).
// ``min-width:0`` on the label lets long labels
// truncate with an ellipsis rather than pushing the
// control off the right edge of the panel.
'#bwSettingsPanel .bw-set-row {' +
'display:flex;align-items:center;gap:14px;padding:10px 6px;' +
'border-radius:8px;transition:background .15s ease;' +
'}' +
'#bwSettingsPanel .bw-set-row[hidden] { display:none; }' +
'#bwSettingsPanel .bw-set-row:hover { background:rgba(255,255,255,.03); }' +
'#bwSettingsPanel .bw-set-row.bw-search-hit { background:rgba(99,102,241,.10); }' +
'#bwSettingsPanel .bw-set-row > label {' +
'flex:1;min-width:0;display:flex;align-items:center;gap:8px;' +
'cursor:pointer;color:#e2e8f0;' +
'}' +
'#bwSettingsPanel .bw-set-label {' +
'font:600 13px/1.35 system-ui;' +
'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;' +
'min-width:0;' +
'}' +
// Info icon button
'#bwSettingsPanel .bw-set-info {' +
'width:18px;height:18px;border-radius:50%;padding:0;' +
'background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);' +
'color:#94a3b8;font:700 11px/1 ui-serif,Georgia;cursor:pointer;font-style:italic;' +
'display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;' +
'transition:background .15s ease,color .15s ease,border-color .15s ease;' +
'}' +
'#bwSettingsPanel .bw-set-info:hover {' +
'background:rgba(99,102,241,.22);color:#c7d2fe;border-color:rgba(99,102,241,.45);' +
'}' +
// Restart badge
'#bwSettingsPanel .bw-set-restart {' +
'display:inline-flex;align-items:center;justify-content:center;' +
'width:18px;height:18px;border-radius:50%;' +
'background:rgba(245,158,11,.16);color:#fbbf24;' +
'font:600 11px/1 system-ui;cursor:help;flex-shrink:0;' +
'}' +
// Toggle switch (reused from old)
'#bwSettingsPanel .bw-set-toggle { position:relative;width:34px;height:20px;flex-shrink:0;cursor:pointer; }' +
'#bwSettingsPanel .bw-set-toggle input { opacity:0;width:0;height:0; }' +
'#bwSettingsPanel .bw-set-toggle .bw-set-slider {' +
'position:absolute;inset:0;background:rgba(255,255,255,.14);' +
'border-radius:99px;transition:background .2s ease;' +
'}' +
'#bwSettingsPanel .bw-set-toggle .bw-set-slider::before {' +
'content:"";position:absolute;top:2px;left:2px;' +
'width:16px;height:16px;border-radius:50%;background:#fff;' +
'box-shadow:0 1px 3px rgba(0,0,0,.35);transition:left .2s cubic-bezier(.22,.61,.36,1);' +
'}' +
'#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider {' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);' +
'}' +
'#bwSettingsPanel .bw-set-toggle input:checked + .bw-set-slider::before { left:16px; }' +
// Select — explicit dark theming so the native
// dropdown panel (the OS popup) stays dark too.
// ``color-scheme:dark`` switches the UA picker
// to a dark palette on Chromium/Edge; Firefox
// honours the styled ``option`` elements below.
//
// FIXED-WIDTH control column: ``flex:0 0 180px``
// keeps every dropdown at the same predictable
// width regardless of the longest option, which
// means rows align cleanly and there's no
// horizontal scrollbar from a stretched select.
'#bwSettingsPanel select {' +
'flex:0 0 180px;width:180px;box-sizing:border-box;' +
'background-color:rgba(255,255,255,.06);' +
'border:1px solid rgba(255,255,255,.14);' +
'color:#e2e8f0;border-radius:8px;padding:6px 30px 6px 12px;' +
'font:600 12px/1.2 system-ui;cursor:pointer;' +
'color-scheme:dark;' +
'-webkit-appearance:none;-moz-appearance:none;appearance:none;' +
'background-image:url("data:image/svg+xml;utf8,' +
'<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'6\' viewBox=\'0 0 10 6\'>' +
'<path fill=\'%2394a3b8\' d=\'M0 0l5 6 5-6z\'/></svg>");' +
'background-repeat:no-repeat;background-position:right 10px center;' +
'transition:border-color .15s ease,background-color .15s ease;' +
'text-overflow:ellipsis;' +
'}' +
'#bwSettingsPanel select:hover {' +
'border-color:rgba(99,102,241,.45);background-color:rgba(255,255,255,.08);' +
'}' +
'#bwSettingsPanel select:focus {' +
'border-color:rgba(99,102,241,.65);outline:none;' +
'box-shadow:0 0 0 2px rgba(99,102,241,.22);' +
'}' +
// Firefox respects styled options. Chromium also
// picks these up when the OS supports dark UA.
'#bwSettingsPanel select option {' +
'background-color:#0f172a;color:#e2e8f0;' +
'padding:6px 10px;' +
'}' +
'#bwSettingsPanel select option:checked {' +
'background:linear-gradient(0deg,rgba(99,102,241,.35),rgba(99,102,241,.35)),#0f172a;' +
'color:#ffffff;' +
'}' +
'#bwSettingsPanel select option:disabled {' +
'color:#475569;' +
'}' +
// Range slider — fixed 140px so it lines up with
// the dropdown column. The companion value
// (.bw-set-range-val) sits to the right.
'#bwSettingsPanel input[type=range] {' +
'flex:0 0 140px;width:140px;height:4px;-webkit-appearance:none;appearance:none;' +
'background:rgba(255,255,255,.10);border-radius:99px;outline:none;cursor:pointer;' +
'}' +
'#bwSettingsPanel input[type=range]::-webkit-slider-thumb {' +
'-webkit-appearance:none;width:14px;height:14px;border-radius:50%;' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' +
'}' +
'#bwSettingsPanel input[type=range]::-moz-range-thumb {' +
'width:14px;height:14px;border-radius:50%;' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);cursor:pointer;border:0;' +
'}' +
'#bwSettingsPanel .bw-set-range-val {' +
'flex:0 0 38px;font:600 12px/1 system-ui;color:#cbd5e1;' +
'font-variant-numeric:tabular-nums;text-align:right;' +
'}' +
// Text input — same fixed 180px column as the
// selects so the right edge of every control
// lines up across all rows.
'#bwSettingsPanel input[type=text] {' +
'flex:0 0 180px;width:180px;box-sizing:border-box;' +
'padding:6px 10px;border-radius:8px;' +
'border:1px solid rgba(255,255,255,.12);background:rgba(2,6,23,.45);' +
'color:#e2e8f0;font:500 12px/1.3 system-ui;min-width:0;' +
'transition:border-color .15s ease,background-color .15s ease;' +
'}' +
'#bwSettingsPanel input[type=text]:hover {' +
'border-color:rgba(99,102,241,.4);background-color:rgba(2,6,23,.6);' +
'}' +
'#bwSettingsPanel input[type=text]:focus {' +
'border-color:rgba(99,102,241,.65);outline:none;' +
'box-shadow:0 0 0 2px rgba(99,102,241,.22);' +
'}' +
// Privacy score
'#bwSettingsPanel .bw-priv-score {' +
'padding:14px 14px;border-radius:12px;cursor:pointer;' +
'background:linear-gradient(135deg,rgba(99,102,241,.10),rgba(139,92,246,.06));' +
'border:1px solid rgba(99,102,241,.22);margin-bottom:14px;' +
'}' +
'#bwSettingsPanel .bw-priv-score:hover { border-color:rgba(99,102,241,.40); }' +
'#bwSettingsPanel .bw-priv-score-row {' +
'display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;' +
'}' +
'#bwSettingsPanel .bw-priv-score-heading { font:700 13px/1 system-ui;color:#cbd5e1; }' +
'#bwSettingsPanel .bw-priv-score-num {' +
'font:800 28px/1 system-ui;font-variant-numeric:tabular-nums;' +
'}' +
'#bwSettingsPanel .bw-priv-score-bar {' +
'width:100%;height:6px;border-radius:3px;background:rgba(2,6,23,.5);overflow:hidden;' +
'}' +
'#bwSettingsPanel .bw-priv-score-fill {' +
'height:100%;transition:width .35s ease,background .25s ease;' +
'}' +
'#bwSettingsPanel .bw-priv-score-sub {' +
'font:500 11px/1.4 system-ui;color:#94a3b8;margin-top:6px;' +
'}' +
// Presets
'#bwSettingsPanel .bw-priv-preset-block { margin-bottom:14px; }' +
'#bwSettingsPanel .bw-priv-presets {' +
'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;' +
'}' +
'#bwSettingsPanel .bw-priv-preset {' +
'display:flex;flex-direction:column;align-items:flex-start;gap:4px;' +
'padding:10px 10px;border-radius:10px;cursor:pointer;text-align:left;' +
'background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);' +
'color:#cbd5e1;transition:all .15s ease;' +
'}' +
'#bwSettingsPanel .bw-priv-preset:hover {' +
'background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.18);' +
'}' +
'#bwSettingsPanel .bw-priv-preset.active {' +
'background:linear-gradient(135deg,rgba(99,102,241,.18),rgba(139,92,246,.10));' +
'border-color:rgba(99,102,241,.55);color:#e0e7ff;' +
'}' +
'#bwSettingsPanel .bw-priv-preset-name { font:700 12px/1 system-ui; }' +
'#bwSettingsPanel .bw-priv-preset-desc {' +
'font:500 10px/1.3 system-ui;color:#94a3b8;' +
'}' +
'#bwSettingsPanel .bw-priv-preset.active .bw-priv-preset-desc { color:#c7d2fe; }' +
'#bwSettingsPanel .bw-priv-custom-pill {' +
'display:inline-block;margin-top:8px;padding:2px 8px;border-radius:8px;' +
'background:rgba(245,158,11,.16);border:1px solid rgba(245,158,11,.30);' +
'color:#fbbf24;font:600 11px/1.4 system-ui;' +
'}' +
// Show advanced toggle
'#bwSettingsPanel .bw-priv-advanced-toggle {' +
'display:flex;align-items:center;gap:8px;width:100%;' +
'padding:9px 12px;border-radius:10px;cursor:pointer;' +
'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);' +
'color:#94a3b8;font:600 12px/1 system-ui;text-align:left;margin-bottom:10px;' +
'transition:background .15s ease,color .15s ease;' +
'}' +
'#bwSettingsPanel .bw-priv-advanced-toggle:hover {' +
'background:rgba(255,255,255,.06);color:#cbd5e1;' +
'}' +
'#bwSettingsPanel .bw-priv-advanced-toggle.open { color:#cbd5e1; }' +
'#bwSettingsPanel .bw-priv-advanced-chev {' +
'font-size:10px;color:#94a3b8;' +
'}' +
// Forensik card
'#bwSettingsPanel .bw-forensik-card {' +
'padding:12px;border-radius:12px;' +
'background:rgba(2,6,23,.4);border:1px solid rgba(255,255,255,.08);' +
'margin-bottom:10px;' +
'}' +
'#bwSettingsPanel .bw-forensik-card-head {' +
'display:flex;align-items:center;gap:6px;margin-bottom:2px;' +
'}' +
'#bwSettingsPanel .bw-forensik-card-sub {' +
'font:500 11px/1.4 system-ui;color:#64748b;margin-bottom:10px;' +
'}' +
'#bwSettingsPanel .bw-forensik-tiles {' +
'display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:8px;' +
'}' +
'#bwSettingsPanel .bw-forensik-tile {' +
'display:flex;flex-direction:column;align-items:center;gap:4px;' +
'padding:10px 6px;border-radius:10px;cursor:pointer;text-align:center;' +
'background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);' +
'color:#cbd5e1;transition:all .15s ease;' +
'}' +
'#bwSettingsPanel .bw-forensik-tile:hover {' +
'background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.20);' +
'}' +
'#bwSettingsPanel .bw-forensik-tile.active {' +
'background:linear-gradient(135deg,rgba(99,102,241,.16),rgba(139,92,246,.08));' +
'border-color:rgba(99,102,241,.55);' +
'}' +
'#bwSettingsPanel .bw-forensik-tile-label {' +
'font:700 12px/1 system-ui;color:#f1f5f9;margin-top:2px;' +
'}' +
'#bwSettingsPanel .bw-forensik-tile-desc {' +
'font:500 10px/1.3 system-ui;color:#94a3b8;' +
'}' +
'#bwSettingsPanel .bw-forensik-sub[data-show-mode]:not([data-active="1"]) { display:none; }' +
// Undo toast
'#bwSettingsPanel .bw-set-undo {' +
'position:absolute;left:50%;bottom:12px;transform:translateX(-50%);' +
'padding:8px 14px;border-radius:8px;background:rgba(15,23,42,.95);' +
'border:1px solid rgba(99,102,241,.45);color:#cbd5e1;' +
'font:600 12px/1 system-ui;display:flex;align-items:center;gap:8px;' +
'box-shadow:0 8px 24px rgba(0,0,0,.5);opacity:0;pointer-events:none;' +
'transition:opacity .2s ease;z-index:2;' +
'}' +
'#bwSettingsPanel .bw-set-undo.show { opacity:1;pointer-events:auto; }' +
'#bwSettingsPanel .bw-set-undo button {' +
'background:linear-gradient(135deg,#3b82f6,#8b5cf6);color:#fff;' +
'border:0;border-radius:6px;padding:4px 10px;cursor:pointer;font:700 11px/1 system-ui;' +
'}' +
'#bwSettingsPanel .bw-set-search-empty {' +
'padding:20px 12px;color:#94a3b8;font:500 12px/1.4 system-ui;text-align:center;' +
'}' +
// Info popover
'#bwSetInfoPopover {' +
'position:fixed;width:300px;padding:14px 16px;border-radius:12px;' +
'background:linear-gradient(180deg,rgba(15,23,42,.99),rgba(2,6,23,.99));' +
'border:1px solid rgba(99,102,241,.42);color:#e2e8f0;' +
'box-shadow:0 16px 48px rgba(0,0,0,.55), 0 0 0 1px rgba(99,102,241,.12);' +
'z-index:2147483647;font:500 12px/1.5 system-ui;' +
'}' +
'#bwSetInfoPopover .bw-info-close {' +
'position:absolute;right:6px;top:4px;background:transparent;border:0;' +
'color:#94a3b8;cursor:pointer;font-size:18px;padding:2px 6px;' +
'}'
) + '</style>' +
'<div class="bw-set-head" id="bwSettingsDragHandle">' +
'<div class="bw-set-title">Settings</div>' +
'<input id="bwSetSearch" type="text" class="bw-set-search" placeholder="' + T('settings_search_placeholder','Search settings…').replace(/"/g, '&quot;') + '" autocomplete="off"/>' +
'<button id="bwCloseSettings" class="bw-set-close" type="button" aria-label="' + T('hotkey_close','Close') + '">&times;</button>' +
'</div>' +
'<div class="bw-set-tabs" role="tablist">' +
'<button class="bw-set-tab active" data-tab="daily">' + T('settings_tab_daily','Daily') + ' <span class="bw-tab-count" data-count-for="daily">0</span></button>' +
'<button class="bw-set-tab" data-tab="playback">' + T('settings_tab_playback','Playback') + ' <span class="bw-tab-count" data-count-for="playback">0</span></button>' +
'<button class="bw-set-tab" data-tab="privacy">' + T('settings_tab_privacy','Privacy') + ' <span class="bw-tab-count" data-count-for="privacy">0</span></button>' +
'<button class="bw-set-tab" data-tab="advanced">' + T('settings_tab_advanced','Advanced') + ' <span class="bw-tab-count" data-count-for="advanced">0</span></button>' +
'</div>' +
'<div class="bw-set-body" id="bwSetBody">' +
'<div class="bw-set-pane" data-tab-pane="daily">' + dailyPaneHTML() + '</div>' +
'<div class="bw-set-pane" data-tab-pane="playback" hidden>' + playbackPaneHTML() + '</div>' +
'<div class="bw-set-pane" data-tab-pane="privacy" hidden>' + privacyPaneHTML() + '</div>' +
'<div class="bw-set-pane" data-tab-pane="advanced" hidden>' + advancedPaneHTML() + '</div>' +
'<div class="bw-set-search-empty" id="bwSetSearchEmpty" hidden>' + T('settings_search_no_results','No settings match your search.') + '</div>' +
'</div>' +
'<div class="bw-set-undo" id="bwSetUndo" role="status" aria-live="polite">' +
'<span id="bwSetUndoMsg">' + T('settings_saved_undo','Saved · Undo') + '</span>' +
'<button type="button" id="bwSetUndoBtn">' + T('settings_undo_btn','Undo') + '</button>' +
'</div>'
);
@FieteGM FieteGM changed the title Sidebar refactor + i18n sweep + popup resize/drag Sidebar refactor + app multilanguage + popup resize/drag May 28, 2026
FieteGM added 4 commits May 29, 2026 00:44
- bw/assets/sidebar_polish.js + continue_watching.js: fix 11 more
  template-literal ${...} syntax errors inside single-quoted strings
  (privacy modal, network monitor, new-episode toast, stats strip)
- bw/assets/sidebar/06_settings_panel.js + sidebar_inject.js: add
  escapeHtml() and route _priv.forensikStashDir, _s.firefox_path
  text-input values through it (CodeQL js/xss-through-dom alerts
  #30/#36)
- bw/assets/continue_watching.js: move "Diese Woche / Streak / Heute"
  + empty-state strings to i18n keys; +6 EN/DE pairs
- .github/workflows/ci.yml lint: mirror style.yml rule set
  (--select=E,F,W,B,C4,UP --ignore=E501,E701,...) so the two jobs
  agree; format check downgraded to warning
- .github/workflows/ci.yml typecheck: add --follow-imports=silent
  so strict mypy only checks the 6 declared modules, not the
  legacy tree it transitively imports
- .github/workflows/security.yml privacy-invariant: exclude tests/
  from the useTorProxy grep — test_settings.py uses the literal
  to assert the legacy key is stripped on validate()
- bw/shutdown.py: typing.Callable -> collections.abc.Callable (UP035)
- tests/test_js_assets.py: bump asset baselines (374k/203k/16.6k)
  to reflect the post-fix sizes; ±5% drift window unchanged
mypy strict on Linux can't resolve ctypes.windll (Windows-only).
Add # type: ignore[attr-defined,unused-ignore] to the 3 windll
calls in is_pid_alive(). The unused-ignore variant keeps Windows
mypy happy too — there windll IS defined so the ignore would
otherwise be flagged as unused.
- Add window.__bwEscapeHtml once at sidebar init so every click
  handler can reach it
- Escape ctx.title and series in the filmpalast + series-row
  right-click context menus before they go into template literals
  (openFpContextMenu, series ctx menu)
- Escape seriesName in the per-series Skip-Times panel heading
- Guard the filmpalast row data-url click handler with a
  ^https?:// regex so a manipulated localStorage entry can't fire
  javascript: / data: via location.href
- sidebar_polish.js: also escape backslashes when building CSS
  attribute selectors from a series slug (was only escaping " ;
  a slug with a literal \ would break out of the selector)
window.X = function() ... aliasing isn't followed by CodeQL's
taint tracker, so the 4 sinks (filmpalast ctx, series ctx,
skip-times heading, settings panel via row helper) kept showing
up as XSS even after the previous fix.

Switch to an IIFE-scoped const __bwEscapeHtml = (s) => ... .
Same behaviour, but a local const the taint analyzer can follow
through every call site.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants