Commit Graph
297 Commits
Author SHA1 Message Date
4grayandClaude Opus 4.8 0b526b119a feat(epg): add vertical list view for the live EPG panel (#1115)
* feat(epg): add vertical list view for the live EPG panel

Add an EPG list view — a vertical, single-day programme list — as an
alternative rendering of the live EPG panel, selectable via a new
Settings → EPG → "Guide view" toggle (epgViewMode: 'timeline' | 'list',
default 'timeline' so existing users see no change).

- New EpgListViewComponent (app-epg-list-view) mirrors
  EpgTimelineComponent's input/output contract 1:1, so all four live
  hosts (M3U player, unified live tab, Xtream, Stalker) swap the panel
  with a plain @if and identical bindings.
- Reuses the shared view-agnostic EPG modules (classifyTimelineWhen,
  hasProgramsForDateKey, epg-archive.util catch-up gating,
  epg-summary.util collapsed-summary maths, epg-date helpers,
  EpgProgrammeDialogService, app-epg-timeline-empty-state) — no
  duplicated logic.
- Rows show time range, title, optional description, live progress on
  the on-air row, catch-up "Watch" on past rows when archive playback
  is available, and a details dialog; keyboard activation guards
  nested buttons (target === currentTarget).
- Auto-focuses the on-air row on channel select, restores it across
  collapse/expand remounts, and shows a sticky in-flow "On now" strip
  (never overlaying rows) when the current programme is scrolled away;
  all scroll maths is rect-based relative to the scroller.
- List mode raises only the inline panel height via an epg--list
  modifier (--epg-inline-height clamp); timeline and collapsed heights
  are unchanged.
- Setting flows end-to-end (Settings interface → DEFAULT_SETTINGS →
  SettingsStore/StorageMap → segmented control in the EPG section);
  Electron-only UI, PWA stays on the timeline default. i18n keys added
  to all 18 locales.
- Tests: new component/row/utils/scroll-controller specs, settings
  persistence spec, swap tests in all four host specs, and Electron
  E2E for the settings round-trip and the rendered list view. Docs
  updated (m3u-playlist-module.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(epg): address list-view review findings from Codex and Greptile

- Reset the list view to today when a new channel's programme set
  arrives while the user is parked on another day (timeline parity):
  the scroll controller now keys by the full programme-set identity
  (programsFocusKey) and commits today before focusing, instead of
  silently stranding the new channel on the stale day. (Codex P2)
- Centralise the 'timeline' fallback as a resolvedEpgViewMode computed
  on SettingsStore; the four live hosts consume the derived signal
  instead of duplicating the `?? 'timeline'` expression. (Greptile P2)
- Extract the component's reactive plumbing into
  registerEpgListViewEffects(), bringing the component back under the
  300-line guideline (290). (Greptile P2)
- Controller spec rewritten around programme-set fixtures with new
  coverage: return-to-today on channel switch, day navigation left
  alone, no-takeover when today has no data, empty-set no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(epg): drop malformed programmes from the list-view day filter

Reject programmes whose stop is not after their start in
buildEpgListRows — same as the timeline's buildTimelineBlocks. Bad
provider data would otherwise render impossible time ranges and could
even be offered as catch-up playable. (Codex P2 on e6fd0d08)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 10:05:08 +02:00
4gray c0e9cd5de3 fix(settings): show current release notes (#1101)
* fix(settings): show current release notes

* fix(updater): use app version for local release notes

* fix(settings): constrain release note images

* fix(settings): prefer current notes without updates
2026-06-29 11:03:15 +02:00
4gray b1119189e2 feat(updater): add GitHub releases desktop updater
Adds the GitHub Releases desktop updater with release notes, startup notification, packaging metadata, tests, and CI fixes for Electron E2E.
2026-06-28 22:21:20 +02:00
4gray 1679373bb2 feat(xtream): auto-select stream output format 2026-06-27 12:42:53 +02:00
4gray e801028005 feat(epg): support playlist-scoped sources
Add playlist-scoped EPG source support for M3U playlists.
2026-06-21 22:53:07 +02:00
4gray 89916af9d8 feat(search): add workspace global search with M3U support
Moves global search into a routed workspace view, adds M3U live/radio results, lazy pagination, and DB-backed matching improvements.
2026-06-20 23:05:48 +02:00
4gray ce78669c17 fix(electron): disable service worker in desktop runtime 2026-06-14 15:59:03 +02:00
4gray 1444047a1d refactor: split dashboard rails and settings logic
Split dashboard rails and settings logic into focused helpers/facades while preserving behavior.
2026-06-14 13:34:23 +02:00
4gray adf37e7504 feat(dashboard): add configurable dashboard rails
* feat(dashboard): add configurable dashboard rails

* fix(dashboard): address rail review feedback
2026-06-14 11:52:44 +02:00
4grayand4gray e8aa7c3a34 [codex] Add scoped EPG security trust controls (#1054)
* Add scoped EPG security trust controls

* fix: address scoped trust review feedback

---------

Co-authored-by: 4gray <fourgray@proton.me>
2026-06-12 20:46:24 +02:00
4grayandClaude Fable 5 8e0abe6feb feat(ui): custom title bar with window controls for Windows and Linux (#1042)
* feat(ui): add custom title bar window controls for Windows and Linux

Hide the native title bar on win32/linux (titleBarStyle: 'hidden', frame
untouched so native resize borders and snapping keep working) and render
minimize / maximize-restore / close buttons in the renderer, mirroring the
existing macOS traffic-light setup.

- New WINDOW:* IPC contract (minimize, toggle-maximize, close, get-state)
  handled in window.events.ts, resolved from the sender WebContents;
  close goes through win.close() so window-bounds persistence still runs.
- WINDOW:STATE_CHANGED pushed on maximize/unmaximize/fullscreen so the
  maximize/restore glyph stays correct for OS-triggered changes; controls
  hide while fullscreen.
- WindowControlsComponent mounts once in app-root as a manual popover so
  it stays in the browser top layer above CDK overlays (dialogs,
  multi-EPG) - same behavior as macOS traffic lights.
- Theme-aware via CSS vars (--app-on-surface, --app-hover-overlay);
  Windows-red close hover. Drag regions get right padding through a
  body-level frameless-platform class.
- Gated by RuntimeCapabilitiesService.usesCustomWindowControls; PWA and
  macOS never mount the controls.

Includes unit specs for the component and IPC handlers, an Electron E2E
suite (window-controls.e2e.ts), and a window-chrome section in
docs/architecture/workspace-shell.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(linux): upgrade Electron to 41 for frameless window decorations on Wayland

With the native title bar hidden, Linux windows lost the WM-drawn shadow
and rounded corners. Electron draws client-side decorations only on
native Wayland, and frameless-window CSD (GTK drop shadow + extended
resize boundaries) landed in Electron 41 - before that, frameless
windows render as plain rectangles.

- electron ^39.8.5 -> ^41.7.2 (Wayland auto-detected since 38.2; X11
  sessions remain undecorated, matching other frameless Electron apps;
  Windows keeps its DWM shadow and rounded corners).
- better-sqlite3 pinned to exactly 12.9.0: the last release shipping
  prebuilt binaries for both Node 20 (ABI 115, Jest) and Electron 41
  (ABI 145, runtime). 12.10.0 dropped the Node 20 prebuilds, forcing a
  from-source build that fails without a C++ toolchain.
- pnpm override node-abi 3.85.0 -> 3.92.0 so electron-builder
  install-app-deps can map Electron 41 to ABI 145.

Reviewed Electron 40/41 breaking changes: only the renderer clipboard
deprecation, which this app does not use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(e2e): address review feedback and window-managerless Linux CI

- Skip the three window-manager-dependent E2E assertions (maximize
  toggle, main-process state sync, minimize) on Linux CI: GitHub's
  ubuntu runners drive Electron under xvfb without a window manager, so
  maximize/minimize state never materializes there. Windows CI and
  local Linux/macOS runs keep the coverage.
- WINDOW:TOGGLE_MAXIMIZE now returns the requested state instead of
  re-reading isMaximized() right after the call, which races on Linux
  window managers where maximize()/unmaximize() complete
  asynchronously; the WINDOW:STATE_CHANGED push stays authoritative.
- Skip attaching window-state push listeners on macOS, where the
  custom controls never mount and the IPC traffic had no subscriber.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): gate custom window controls on the full bridge surface

Include getWindowState and onWindowStateChange in the
usesCustomWindowControls capability check — the controls rely on both
for initial state and for keeping the maximize/restore glyph in sync
with OS-triggered changes, so a partial bridge should not mount them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:03:27 +02:00
4gray 06700b318a feat(electron): add embedded mpv support for windows and linux (#1031)
Adds experimental embedded MPV support for Windows and Linux, expands packaging/runtime validation, and updates CI coverage for the cross-platform native addon.
2026-06-09 08:58:38 +02:00
4gray 1badb9a057 refactor(electron): share typed preload bridge contract (#1018)
* refactor(electron): share typed preload bridge contract

* refactor(electron): tighten bridge review fixes

* refactor(electron): tighten playlist bridge returns

* refactor(electron): use bridge epg progress type

* refactor(epg): alias progress bridge types
2026-06-01 09:52:22 +02:00
4gray 701cf23299 chore(frontend): clean logging and lint suppressions 2026-05-26 20:18:12 +02:00
4gray 697eab6e73 refactor(epg): route renderer calls through runtime bridge
Add typed EPG runtime bridge, split EPG runtime capabilities, migrate renderer EPG callers away from direct window.electron access, and address Greptile review feedback.
2026-05-24 13:45:51 +03:00
4gray 0fb4c7d13e fix(settings): gate external player choices by launch support 2026-05-22 14:39:20 +03:00
4gray 959f6061f0 fix(runtime): address greptile capability feedback 2026-05-22 14:37:06 +03:00
4gray ed8680116c fix(runtime): address consolidated review feedback 2026-05-22 14:02:56 +03:00
4gray 582422c512 refactor(runtime): gate settings backup file save by capability 2026-05-22 13:41:24 +03:00
4gray c9a0d5210e refactor(runtime): gate settings sections by capability 2026-05-22 13:41:24 +03:00
4gray 09e2e08fc1 refactor(runtime): gate settings external players by capability 2026-05-22 13:40:14 +03:00
4gray 78a5a7af74 refactor(settings): use runtime capabilities for platform state 2026-05-22 13:33:31 +03:00
4gray b51cbcae15 refactor(app): use runtime capabilities for shell gates 2026-05-22 13:08:00 +03:00
4gray 1806cb46aa refactor(web): harden runtime service payloads 2026-05-22 11:20:50 +03:00
4gray 36bce47764 merge: resolve master conflicts for pwa hardening
- merge origin/master into PR #964 and keep embedded MPV test on the isolated playback sub-entrypoint

- centralize EPG capability through DataService.supportsEpg and update PWA web-e2e expectations

- split BrowserAccessError copy between Electron and PWA diagnostics
2026-05-22 10:20:03 +03:00
4gray 5d355b6f10 fix(web): address strict mode review feedback 2026-05-22 03:11:14 +03:00
4gray 4ab8915483 chore(web): enable strict TypeScript mode 2026-05-22 02:58:12 +03:00
4gray 55efc24608 fix(pwa): harden self-hosted runtime boundaries 2026-05-22 02:09:57 +03:00
4grayandClaude Opus 4.7 27becf5b2a fix(settings): remove transient box-shadow pulse on scroll
The settings-section-scroll directive ran a 260ms Element.animate() on
whichever section became "active" as the user scrolled — three
keyframes painting an `inset 0 0 0 1px var(--settings-group-active-ring)`
ring plus a soft glow on the section block. With the new flat layout
(no card chrome, no static active ring), that pulse drew a brief 1px
border around each block as you scrolled past — the user-reported
"short border kind of highlight around blocks" effect.

The rail's left active state already announces which section you're on,
so the inline pulse is redundant. Removed the entire active-section
animation effect (the pendingScrollTarget scroll-into-view effect is
untouched — that's the click-the-nav-and-jump behaviour).

CDP verifies: all .settings-group elements report `box-shadow: none`
after scrolling. 50/50 settings tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 02:00:56 +02:00
4grayandClaude Opus 4.7 734ab3c580 refactor(settings): align look-and-feel with v0.22 mockup
The earlier Settings polish (kill triple header, compact theme picker,
sticky footer, hide playlist switcher) addressed structure. This round
addresses typography and chrome to bring the look in line with the
v0.22 mockup spec captured in redesign-screens-2.jsx.

Three changes:

1. Flat sections — no card chrome
   - Each .settings-group was a rounded panel (border-radius:22px,
     filled background, box-shadow, .settings-group--active ring +
     glow). Combined with the per-section icon-circle next to the
     title, every section looked like a feature card, not a settings
     region anchor. Strip all of it: the rail's active state on the
     left already announces "you are here".
   - Hide .settings-group__header-icon via display:none so the icon-
     circle markup in each section template stays intact while the
     visual chrome goes away — saves touching 6 templates for a
     CSS-only change.

2. Promote section titles to a large flat heading
   - Was h3 1.08rem 700 weight nested in the card chrome. Now h3
     1.5rem (24px) 600 weight with a 1px bottom-border separator,
     matching the mockup's 28px h1 + sub anchor pattern (24px is a
     reasonable density compromise for the denser app layout). Active-
     section variant keeps the blue title accent so users scrolling
     the right pane don't lose the anchor.
   - .setting-item drops the `margin: 0 18px` card inset so rows
     align flush with the section title — flat list rhythm.

3. Quiet mono eyebrow for the left nav title
   - .panel-title was a 20px 500 weight h2 ("Settings") competing for
     "biggest text on screen" against the section titles on the right.
     Switch to mono 11px uppercase 0.1em letter-spaced text-secondary
     — quiet rail header per the mockup. Now the section titles on
     the right are unambiguously the dominant heading.

Visual confirmation via agent-browser shows a much closer match to the
mockup: subtle SETTINGS eyebrow above the rail list, large flat
"General" anchor with bottom border, compact pickers, full-width sticky
footer.

50/50 settings tests still pass; build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:41:42 +02:00
4grayandClaude Opus 4.7 5a1f699379 fix(settings): Remote nav now scrolls to the right section
The Remote Control nav item carried id: '@iptvnator/ui/remote-control'
— the NX library name pasted in place of the section's HTML id. The
settings-section-scroll directive resolves the target with
document.getElementById(), which returned null for that string, so
clicking "Remote" silently no-op'd. The active-state binding
([class.settings-group--active]="activeSection() === 'remote-control'")
also never lit up because the nav reported a different id than the
section template uses.

Change the nav id to 'remote-control' so it matches the section's
id="remote-control" attribute.

Added a focused regression spec (settings-options.spec.ts) that:
- asserts the desktop-only items light up only when isDesktop is true,
- reads each section component's root <section id="…"> at test time and
  cross-checks the nav id set against it, so any future drift between
  the two (renamed section, new section without nav entry, copy-paste
  of a library path into the id) fails the build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:37:19 +02:00
4grayandClaude Opus 4.7 115911d2a6 refactor(settings): kill triple header, compact theme picker, real footer
Four UX-audit fixes for the Settings page:

1. Header dedupe
   - The page used to stack THREE headers vertically: the workspace rail's
     "Settings" entry, a page-level <h1>Settings</h1> + subtitle, and the
     General section's <h3>General</h3> + subtitle. The page-level and
     section-level subtitles both used SETTINGS.GENERAL_SUBTITLE
     ("Change the configuration of the application"), word-for-word.
   - Drop the visible page-level header entirely. Keep an a11y-only
     <span class="visually-hidden"> for the data-test-id hook so the
     existing settings.component.spec selector still resolves. Left rail
     announces "Settings"; section header is now the page anchor.
   - Add a global .visually-hidden helper to styles.scss for re-use.

2. Theme + Cover-size pickers shrink to a real segmented control
   - .theme-switcher was a 3-column grid of ~78px-tall buttons making
     "Choose theme" look like the most important action in Settings.
     Replaced with a 30px-tall segmented control (track + thumb with
     box-shadow on the selected option), matching the standard Material
     "compact row" cadence everywhere else.
   - Shortened all 18 locales' THEMES.* labels from "Light theme / Dark
     theme / System theme" to "Light / Dark / System". The "theme" word
     duplicated the section's own h3 ("Visual theme") and forced labels
     to wrap into two lines in narrow columns.

3. Save bar becomes a real sticky footer
   - The action bar was a `border-radius:18px; margin-left:auto;
     position:sticky` chip floating bottom-right that clipped the last
     form rows underneath. Now spans the full content column with a
     top border separator — sits flush with the bottom edge while
     still sticky on scroll. The save-button gradient also drops the
     hand-rolled #3b82f6 in favour of var(--app-selection-color) so it
     matches the unified blue primary used everywhere else.

4. Playlist switcher hidden on /settings
   - Workspace shell header gains an isSettingsRoute input that hides
     the playlist switcher block AND the leading actions group
     (+ Add source / header shortcut / bulk action). Settings is a
     global page — those controls were implying that switching
     playlists scopes settings, which it doesn't. Wired through from
     the shell facade's existing isSettingsRoute computed.
   - Mock header in workspace-shell.component.spec gains the matching
     input() declaration so the binding resolves under tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:33:40 +02:00
4grayandClaude Opus 4.7 810d9170f0 fix(add-playlist): widen dialog and use card-friendly labels
The 5-card method picker was rendering at the old 560px dialog width
which left each card too narrow — titles wrapped to 2-3 lines and the
subtitles to 6, all visible in the user-reported screenshot.

Three fixes:

1. Bump the dialog width from 560px → 780px to match the v0.22 mockup.
   maxWidth: 92vw keeps the responsive collapse path intact.

2. Replace the inherited "Add via URL" / "Add via file upload" /
   "Add Xtreme Code" / "Add Stalker Portal" / "Import from text" tab
   labels with short noun-only card titles: "M3U URL", "M3U file",
   "Xtream credentials", "Stalker portal", "Raw m3u text". The dialog
   itself is titled "Add playlist" so the "Add" prefix on every card
   was redundant. The "Xtreme" misspelling on the old key disappears
   as a side effect.

3. Lock cards to min-height: 132px so the URL card (short subtitle)
   stops collapsing while the Xtream/Stalker cards (longer subtitle)
   stretch — selection states now look visually consistent. Slightly
   tighter padding (10/11/12 vs 12/14) to fit the new width without
   feeling cramped.

i18n: 5 new METHOD_*_LABEL keys translated across all 17 locales by
per-locale agents; placeholder integrity verified across all 920+
leaf keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 01:15:18 +02:00
4gray 32cc0b74e8 feat: add self-hosted PWA backend runtime (#944)
* feat(web-backend): add self-hosted proxy app

* feat(web): enable runtime PWA configuration

* test(web-e2e): cover self-hosted backend proxy

* test(web-e2e): clean self-hosted lint warnings

* fix(web-backend): validate proxied provider URLs

* fix(web-backend): proxy through registered provider targets

* fix(web-backend): document CodeQL SSRF validation boundary

* test(web-e2e): mock provider target registration

* fix(web): address self-hosted review feedback
2026-05-16 00:10:03 +02:00
4gray 2d5c4fa4f9 chore: tighten validation and runtime logging
* chore: tighten validation and runtime logging

* fix(i18n): localize new settings labels
2026-05-15 17:43:42 +02:00
4gray d24c77a143 chore(nx): enforce scoped workspace boundaries (#942) 2026-05-15 09:59:06 +02:00
4gray a40d5447e7 feat(settings): add external player arguments (#932)
* feat(settings): add external player arguments

* fix(settings): address external player argument review

* fix(settings): require external player argument settings

* feat(settings): add external player argument placeholders

Closes https://github.com/4gray/iptvnator/issues/835
2026-05-14 11:04:01 +02:00
4gray e7db920543 fix(settings): clarify external player path hints (#929)
* fix(settings): clarify external player path hints

* fix(settings): gate external player hints to desktop
2026-05-12 19:49:14 +02:00
4gray 6ae1251ad1 fix(playback): use inline player for collection VOD (#928)
* feat(portal): add series quick start CTA
Entire-Checkpoint: f957cd9849e0

* fix(playback): use inline player for collection vod

* fix(playback): address inline collection review feedback

* fix(playback): ignore stale vod position loads
2026-05-12 19:26:40 +02:00
4gray 6305dcf00e Add browser playback diagnostics and MPV/VLC fallback (#921)
* feat(playback): add codec diagnostics fallback
Entire-Checkpoint: f957cd9849e0

* fix(playback): address diagnostics review feedback
Entire-Checkpoint: f957cd9849e0

* fix(playback): ignore recoverable hls diagnostics
Entire-Checkpoint: f957cd9849e0

* fix(playback): polish diagnostic fallback banner
Entire-Checkpoint: f957cd9849e0

* fix(xtream): preserve live category search scope
Entire-Checkpoint: f957cd9849e0
2026-05-10 17:27:54 +02:00
4gray 724e4b1ab3 feat(playback): add embedded mpv (macos) stream recording (#916)
* feat(playback): add embedded mpv stream recording
Entire-Checkpoint: f957cd9849e0

* fix(playback): address embedded mpv recording review
Entire-Checkpoint: f957cd9849e0

* fix(playback): track mpv recording auto-stop replies
Entire-Checkpoint: f957cd9849e0
2026-05-10 12:27:09 +02:00
4gray 9261794b4a Merge pull request #902 from weeco/feat/xtream-xmltv-epg-fallback
feat(xtream): fall back to uploaded XMLTV when provider has no EPG
2026-05-09 13:06:51 +02:00
4gray 5dbf258a2b refine double-click setting visibility 2026-05-08 19:51:06 +02:00
4gray e350a2ea96 fix double-click external playback handling 2026-05-08 10:59:52 +02:00
Tedd Johnson 626948f2f5 Add double-click stream open setting 2026-05-07 20:03:33 -07:00
weeco e751e82c07 feat(xtream): fall back to uploaded XMLTV when provider has no EPG
Live TV channels in Xtream playlists go blank whenever the provider's
get_short_epg returns nothing, even when the user has working XMLTV
URLs in Settings. The XMLTV pipeline already populates epg_programs
but only the M3U module was reading it.

Wire those uploads up as a fallback for Xtream too: when the provider
returns nothing for a channel with an epg_channel_id, look it up in
the local table. A settings toggle flips the priority for users whose
curated XMLTV is better than the provider's auto guide.

EpgQueueService.enqueue() became async to batch the XMLTV lookup once
per viewport change. To keep the queue consistent under fast scroll,
all shared-state mutations now happen behind a generation counter so
only the latest call commits. Per-method bridge gating in the new
XtreamXmltvFallbackService keeps each path working when the preload
exposes only one of the two endpoints.
2026-05-07 08:52:59 -07:00
4grayandClaude Opus 4.7 e192cba776 feat(player): add VLC reuse-instance setting (#893)
VLC was unconditionally spawned per click — VLC's own single-instance
preference fails because the per-launch RC args defeat its D-Bus
forwarder. Mirror the existing MPV reuse pattern so users can opt in to
driving one tracked VLC via its RC interface (clear + add) instead of
opening a new window every stream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f957cd9849e0
2026-05-05 00:55:04 +02:00
4gray 4a21579aff fix: clear external player paths on save
Entire-Checkpoint: f957cd9849e0
2026-05-04 23:26:56 +02:00
4grayandClaude Opus 4.7 bbbe7b4036 perf(renderer): inline splash + sync language hint + drop legacy polyfill
Three first-paint fixes for cold app start.

#1 — Inline splash in index.html
Cold start used to show a blank Material-grey background until the
~1.5MB preloaded chunk + 300KB styles.css downloaded and Angular
bootstrapped. On slow disks/networks that's 1-3+ s of "is the app
frozen?" before any pixel of UI appears.

Add a self-contained splash (inline CSS, no extra HTTP, no assets) —
"IPTVnator" wordmark + a CSS spinner — that paints immediately from
the parsed HTML. Removed in main.ts after bootstrapApplication()
resolves, gated on requestAnimationFrame so the swap happens after
AppComponent's first paint (no flash of empty background between
splash removal and the real UI).

Respects prefers-reduced-motion (spinner stops, opacity dims).

#3 — Drop legacy `global = window` polyfill
The inline <script> in <head> ("if (global === undefined) var global =
window") was a workaround for an older library expecting Node's
`global`. Verified nothing in the current bundle reads window.global
(the only matches are TypeScript `declare global { ... }` blocks and
unrelated route paths). Removes one render-blocking inline <script>
parse from the critical path.

#5 — Synchronous language hint
Settings live in IndexedDB (via @ngx-pwa/local-storage), which the
TranslateModule cannot read synchronously at bootstrap. So the first
render historically used English regardless of the user's preference,
then re-rendered every TranslatePipe in the tree once initSettings()
finished — visible flash for non-English users.

Mirror the active language to localStorage in initSettings() once the
IDB-backed settings resolve. Add getInitialLanguage() in app.config.ts
that reads that key synchronously before bootstrap and passes it to
TranslateModule.forRoot({ defaultLanguage }). First-ever boot still
falls back to English (no hint yet); every subsequent boot renders in
the saved language from the very first frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f957cd9849e0
2026-05-03 00:00:06 +02:00
4gray ec2f6a702a chore: resolve master conflicts for embedded mpv branch
Entire-Checkpoint: 5b514fe72836
2026-05-02 02:19:54 +02:00