Commit Graph
688 Commits
Author SHA1 Message Date
4gray 9aa93079bd fix(player): clear reused mpv request headers 2026-07-27 12:35:59 +02:00
4gray edf47b19b7 test(stalker): replay custom-prefix discovery 2026-07-27 12:31:49 +02:00
4gray 8569612dbd fix(stalker): harden provisional connection lifecycle 2026-07-27 12:29:46 +02:00
4gray 9204f400bb fix(stalker): rediscover stale learned MIME routes 2026-07-27 12:23:46 +02:00
4gray 6ebf5fd55a test(stalker): reject noncanonical do auth replay 2026-07-27 12:18:46 +02:00
4gray 317f4ffdad fix(stalker): surface watchdog session failures 2026-07-27 12:15:35 +02:00
4gray 4317380137 fix(stalker): rediscover after benign html endpoints 2026-07-27 12:13:17 +02:00
4gray a44f578ddc fix(stalker): clean sessions after playlist deletion 2026-07-27 12:07:41 +02:00
4gray 171095e091 fix(backup): harden portal credential restore 2026-07-27 12:06:56 +02:00
4gray 9988e506de fix(stalker): redetect session recipes atomically 2026-07-27 12:05:43 +02:00
4gray 91d116a572 fix(stalker): fail closed on incompatible responses 2026-07-27 12:01:23 +02:00
4gray f99380c65d fix(stalker): harden session identity boundaries 2026-07-27 11:54:56 +02:00
4gray 76101fb29b test(stalker): replay single-flight auth refresh 2026-07-27 11:53:02 +02:00
4gray 85bee2dbb4 test(stalker): replay catalog and playback auth 2026-07-27 11:45:48 +02:00
4gray 6d38a7cc4b fix(stalker): preserve exact portal principals 2026-07-27 11:43:15 +02:00
4gray dfdce824d9 feat(stalker): bootstrap main session runtime 2026-07-27 11:36:52 +02:00
4gray 68d1555975 fix(stalker): canonicalize replay MAC values 2026-07-27 11:36:20 +02:00
4gray 38c07e13a3 fix(stalker): omit empty handshake token 2026-07-27 11:30:33 +02:00
4gray e66ef84df1 feat(stalker): manage authenticated portal sessions in main 2026-07-27 11:29:41 +02:00
4gray 0ed6e67360 test(stalker): add authenticated session replay e2e 2026-07-27 11:28:40 +02:00
4gray c042af85db feat(stalker): load matching saved credentials 2026-07-27 11:22:50 +02:00
4gray a6fd436e35 feat(stalker): add route connection recovery flow 2026-07-27 11:19:02 +02:00
4gray 7ee6e97ceb fix(stalker): preserve VOD series season shape 2026-07-27 11:04:21 +02:00
4gray 9a69a6d46a feat(stalker): add challenge-driven import flow 2026-07-27 11:01:15 +02:00
4gray 91761f5abe feat(stalker): secure native playback contexts 2026-07-27 10:56:33 +02:00
4gray cede2c6bb3 fix(stalker): preserve catalog pagination metadata 2026-07-27 10:48:16 +02:00
4gray b76e65c2bf feat(stalker): expose typed session IPC 2026-07-27 10:41:32 +02:00
4gray 580a056035 feat(stalker): bind secret playback contexts in main 2026-07-27 10:37:27 +02:00
4gray d5acb329e0 feat(stalker): map typed portal operations in main 2026-07-27 10:32:14 +02:00
4gray 5b22bee0f4 feat(stalker): add session watchdog scheduler 2026-07-27 10:27:16 +02:00
4gray 97c50071ac feat(stalker): install discovered principals atomically 2026-07-27 10:23:44 +02:00
4gray 79729c4dfc test(stalker): add authentication replay corpus 2026-07-27 10:22:58 +02:00
4gray ffefe48992 feat(stalker): authenticate resolved portal sessions 2026-07-27 10:21:58 +02:00
4gray c12ad52019 feat(stalker): resolve portal endpoints safely 2026-07-27 10:17:21 +02:00
4gray 6726863081 feat(settings): secure playlist backup credentials 2026-07-27 10:15:27 +02:00
4gray c4974e9b93 feat(stalker): add cookie-aware portal HTTP session 2026-07-27 10:01:55 +02:00
4gray a2fd389e74 test(stalker): add fail-closed fixture validation 2026-07-27 09:55:53 +02:00
4gray 375169a69b fix(stalker): harden replay network boundaries 2026-07-27 09:54:30 +02:00
4gray f3acc2ce39 feat(stalker): serialize base identity mutations 2026-07-27 09:52:24 +02:00
4gray 2a88060fae feat(stalker): add one-time challenge registry 2026-07-27 09:49:41 +02:00
4gray 13f05d0a7e feat(stalker): validate portal transport inputs 2026-07-27 09:48:19 +02:00
4gray d0cf96caf8 feat(stalker): add isolated RFC cookie jar 2026-07-27 09:47:32 +02:00
4gray 9166d0f12c feat(stalker): add validated redirect session hooks 2026-07-27 09:35:43 +02:00
4gray 8009233220 refactor(stalker): extract replay fixture schema 2026-07-27 09:28:01 +02:00
4gray af0f210ea5 test(stalker): add replay listeners and control plane 2026-07-27 09:05:40 +02:00
4gray 51d879a734 test(stalker): add replay scenario core 2026-07-27 08:30:33 +02:00
4gray f147d4fe37 perf(m3u): stop cancelled refresh workers (#1268) 2026-07-26 09:25:51 +02:00
4gray c0e065da17 fix(window-controls): derive window-state pushes from events so controls reappear after fullscreen (#1178)
The custom minimize/maximize/close controls stayed hidden forever after
leaving HTML-element (video player) fullscreen on Windows: window state was
polled at event time, and isFullScreen() can still report the pre-transition
value while 'leave-full-screen' fires, leaving a stale push with no later
event to correct it. The same polling on the companion flag cleared
isMaximized during fullscreen transitions and stuck the maximize/restore
glyph on the wrong icon.

attachWindowStateEvents now seeds the state once at window creation and each
event patches only the flag it names, sending a copy per push. The
enter/leave-html-full-screen variants are wired too.

Regression coverage: app-window-state.spec.ts (9 cases, 6 of which fail
against the old implementation) and an Electron E2E case that toggles HTML
element fullscreen and asserts the controls come back.
2026-07-26 01:49:08 +02:00
4grayandClaude Opus 4.8 0b967d66d4 feat(tmdb): metadata cache panel with a clear button in settings (#1244)
* feat(tmdb): metadata cache panel with a clear button in settings

Adds "Metadata cache — N entries · X MB" with a Clear button to
Settings > Metadata (TMDB), next to the API key it belongs to.

Three things it is good for: dropping stale or wrong metadata so the next
open refetches it, seeing what the cache actually costs on disk, and
reclaiming rows that a lookup-key version bump has orphaned — a bump makes
rows unreachable, not deleted, so nothing else would ever collect them.

Sizing the cache is a full table scan (LENGTH() on TEXT counts characters,
so the SUM casts to BLOB to get bytes), which is why stats load lazily and
only once the TMDB section is the active one rather than on every settings
open. Clearing is always safe: enrichment refetches on demand, so the only
cost is the next few requests.

Works in both environments — the PWA has no bridge, so the service reports
and clears its session-scoped in-memory map instead.

i18n: 4 keys across all 19 locales via the tools/i18n workflow;
placeholder integrity verified. Contract fixtures updated for both the
preload bridge and the DB-worker payload shapes.

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

* fix(tmdb): make cache clearing durable and stop reporting failures as empty

Four review findings, all real:

- A metadata write already in flight when the user cleared would land
  afterwards and silently restore what they removed. Writes now carry the
  generation they started in; a write that outlives a clear is dropped
  (PWA) or undone (Electron).
- The PWA byte count used String.length, i.e. UTF-16 code units, so
  localized payloads under-reported and disagreed with the SQLite BLOB
  byte count. TextEncoder now measures actual bytes.
- A failed stats read returned a valid zero-entry result, so the panel
  claimed an empty cache and disabled Clear while rows were still there.
  getStats/clear now return null on failure and the panel says so instead
  of inventing state.
- No behavioural coverage existed for either side.

Tests: SQL ops (entry/byte reporting, empty table, missing row, delete
count) and the service (encoded bytes, clear count, and a write racing a
clear).

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

* fix(tmdb): make the cache clear precise and version skew visible

Review follow-ups on the cache panel:

- A write that was in flight when the user cleared used to trigger a
  second full-table clear once it landed, which also deleted anything
  written in between. clear() now waits for the writes issued before it
  and lets the single clear take them; later writes survive.
- An Electron shell without the maintenance ops fell through to the
  renderer map, which is always empty there — it reported an empty cache
  and disabled the Clear button while SQLite was full. Both operations
  now report unsupported instead.
- Component coverage for the panel (deferred scan, clear + re-read,
  failed clear, failed read) and Electron-path service coverage.
- The canonical IPC and settings sections of the enrichment doc, plus
  the matching CLAUDE.md lines, now list the maintenance ops.

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

* fix(tmdb): drop Promise.allSettled from the cache clear

The web target compiles against lib es2018, so allSettled broke the
Windows frontend build (TS2550). The pending writes swallow their own
errors, so a plain Promise.all over neutralized promises does the job.

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

* fix(tmdb): keep a synchronous bridge throw inside the cache write

Moving the write into a tracked promise dropped the try/catch that used
to cover the call itself, so a bridge that threw synchronously would
escape set(). Wrap it in an async IIFE, which turns that back into a
rejection the same handler swallows.

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

* fix(tmdb): retry the cache size read when the section is reopened

The effect skipped the read once cacheError was set, so one transient
IPC failure left the panel showing "could not read the cache" for the
life of the settings page — and the only enabled control that could
shift it was the destructive Clear button. Gate on the stats signal
alone: reopening the section retries.

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

* fix(tmdb): queue writes that start while the cache is being cleared

Awaiting the in-flight writes closed one side of the race and left the
other open: a set() that started during that wait dispatched its IPC
immediately, was absent from the snapshot, and could reach SQLite just
before the delete — so a row written after the user clicked Clear was
removed anyway.

clear() now holds its own promise for the whole operation and set() waits
on it, which puts such a write on the far side of the delete. Rows are
stamped when they are dispatched rather than when set() was called, since
a write may have waited. Covered by a test that fails without the guard.

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

* docs(tmdb): add the release note for the cache panel

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

* test(tmdb): cover the cache panel with an Electron E2E

The panel drives IPC and SQLite, and nothing exercised that path end to
end. The new test seeds a row through the preload bridge — enrichment
itself needs a TMDB key that CI does not have — then opens the section,
asserts the reported size, clears, and reads the database back to confirm
the row is gone rather than merely hidden.

Verified both ways: dropping the DELETE from clearTmdbMetadata fails it.

Settings nav buttons gained a data-test-id so the section can be opened
without matching translated labels.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 01:13:02 +02:00
4grayandClaude Opus 5 9885178f32 fix(stalker): refresh stale embedded-series snapshots from favorites and dashboard (#1253)
Favorites and recently-viewed rows store Stalker items as full JSON
snapshots, so a vclub-style embedded series[] episode list froze at the
moment the row was written: a series favorited when only episode 1 was out
kept showing one episode forever when opened from favorites, recents,
Continue Watching, or any dashboard rail.

New withStalkerSnapshotRefresh() store feature renders the stored snapshot
immediately and re-fetches the item from the portal in the background via a
title search (get_ordered_list&type=vod&search=..., matched by id, paginated
up to 5 pages, wildcard-category retry), patching fresh episodes and cmd into
the active selection. The patch is guarded on both the item id and the active
playlist id, since Stalker ids are only unique per portal.

Only the in-memory selection is patched — the stored snapshot row is
deliberately left alone, because every entry path into the detail view runs
this refresh and writing it back would add an uncontrolled background writer
to the whole-playlist read-modify-write that every favorite/recent mutation
performs.

Also fixes the stalker-mock-server embedded-series scenario, which generated
series[] as objects the app's vclub adapters filter out instead of the
episode-number arrays real portals send.

Regular type=series and Ministra is_series items are unaffected; Xtream is
unaffected (get_series_info is never cached).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 18:03:33 +02:00