mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 12:36:15 -08:00
1a6af75761094c7c5edeaae2c8f916f30d7d3501
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1a6af75761 |
feat(settings): per-section pages with unsaved-changes bar (#1384)
* feat(settings): split settings into per-section pages with an unsaved-changes bar Replace the single scrolling settings page with routed section pages (/workspace/settings/:section): the context-panel rail links each section, only the active section renders, and unknown or capability-gated sections redirect to General. The shared form lives on the parent component, so staged edits survive section switches; a floating unsaved-changes bar (Save/Discard) replaces the always-visible footer Save button. Rail links navigate with replaceUrl so Back still leaves settings in one step. Along the way: - delete the unreachable settings dialog mode and the dead AppPortalNavigationActionsService with both of its never-injected DI tokens (PORTAL_NAVIGATION_ACTIONS, PLAYLIST_PLAYER_ACTIONS) - delete the scroll-spy directive and pendingScrollTarget plumbing - revive the EPG panel's "Open EPG settings" empty-state button as a deep link to /workspace/settings/epg; the M3U player now reports m3u-needs-setup only when the channel has no programmes and no EPG source exists in settings or on the playlist itself - load TMDB cache stats when the Metadata page opens (the section component now only exists while its page is open) - add SETTINGS.UNSAVED_CHANGES / SETTINGS.DISCARD_CHANGES to all 19 locales Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(settings): confirm before leaving with unsaved changes Add settingsUnsavedChangesGuard (canDeactivate on the :section route) with a three-action dialog: save and leave, leave without saving, keep editing. The guard only intercepts leaving the settings AREA — section switches share the one settings form and pass unconditionally, so the dialog can never nag while moving between pages. A failed save cancels the navigation instead of silently dropping the edits it promised to keep; leaving without saving also reverts the live theme preview. Save-and-leave is disabled while the form is invalid, with a hint explaining why. New SETTINGS.UNSAVED_DIALOG_* keys in all 19 locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(settings): stage cover size and EPG view mode; adapt e2e to section pages Cover size and EPG view mode were the only two controls that persisted eagerly on click, which made Discard (and leave-without-saving) unable to revert them: hydrateFromStore() faithfully reloaded the just-persisted edit. They now stage in the form like every other setting and reach the store on Save. Review finding by Greptile (P1) and Codex. E2E suites that walk through settings are updated for one-section-page rendering (epg, backup-roundtrip, xtream-epg, remote-control) and for the staged cover size (downloads asserts the dataset after Save); the EPG icon fallback test saves before leaving settings so the new unsaved-changes dialog does not block its navigation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
dc05a2566e |
feat(tmdb): opt-in TMDB metadata enrichment for Xtream and Stalker portals (#1123)
* feat(tmdb): opt-in TMDB metadata enrichment for Xtream and Stalker portals Adds an opt-in TMDB integration (Settings > Metadata) that enriches detail views with a field-level merge — the provider stays authoritative for stream data, TMDB fills editorial fields when the match is confident. Enrichment: - Movie/series details: plot, cast (avatar chips), director, genres, rating, poster/backdrop, official YouTube trailers - Confidence-gated matching: provider tmdb_id trusted; otherwise normalized-title search with year gate (±1; series accept earlier premieres), season-suffix stripping, Cyrillic search-language override, and language-prefix fallback variants - Lazy season/episode enrichment: real episode names, overviews, stills - "Similar" rail (Xtream): TMDB recommendations matched to the catalog - Actor pages per portal with full filmography, availability filter and an Electron-only "All portals" scope backed by a batched DB_MATCH_TITLES worker op over the trigram FTS index Infrastructure: - SQLite cache table tmdb_metadata (details, search verdicts, seasons, persons; per-language, TTL-guarded), in-memory fallback for the PWA - Settings: enable toggle, own-API-key override with a live "check key" button; TMDB attribution in Settings and About - Embedded key stays an empty placeholder; CI injects TMDB_API_KEY via tools/tmdb/inject-tmdb-key.mjs when the secret is configured - normalizeTitle shared between renderer and DB worker - CSP: allow YouTube embeds (frame-src was 'none'; trailers never worked) Fixes and refactors along the way: - fix(stalker): Advanced Search sent bare get_ordered_list requests and skipped the auth handshake when isFullStalkerPortal was missing on the active-playlist meta — full portals answered "Authorization failed." and search looked empty; now mirrors the catalog request shape and routes through makeAuthenticatedRequest with URL-based detection - fix(stalker): TMDB fields survive info re-normalization; detail views prefer the store copy patched by async enrichment over stale snapshots - refactor(xtream): split oversized vod/serial detail components into component-scoped playback services; detail routes re-initialize on route param changes (router reuses them for detail-to-detail nav) - i18n: all new keys translated across the 18 locales Docs: docs/architecture/tmdb-metadata-enrichment.md + CLAUDE.md updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): provide route params observable to inline collection details, linearize regexes The global-collection inline detail host builds a fake ActivatedRoute for VodDetailsRouteComponent/SerialDetailsComponent with only snapshot.params. Since the detail components now read route.params via toSignal() (detail-> detail re-init), the missing observable crashed component construction and the content hero never rendered — broke dashboard-activation, favorites and recent Electron E2E on all platforms. Provide the params observable alongside the snapshot and assert it in the component spec. Also resolves both CodeQL js/polynomial-redos alerts: bracket-stripping in normalizeTitle now excludes opening delimiters inside the classes, and youtubeEmbedUrl extracts watch?v= ids with a linear two-pass match instead of "watch\?.*v=". Combining-diacritics range rewritten as explicit \u escapes (greptile note). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): surface TMDB-only VOD score in the rating badge, drop youtube.com from CSP Review follow-ups on PR #1123: the Xtream VOD detail badge renders rating_imdb, but the merge wrote the TMDB score only into `rating`, so a TMDB-only score was never displayed (Codex P2) — fill rating_imdb when the provider left it empty, mirroring the Stalker merge. All trailer iframes are normalized to youtube-nocookie.com, so the extra youtube.com frame-src allowance was dead surface (greptile) — removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): resolve confirmed review findings — matching correctness, race guards, cache schema Fixes the confirmed findings from the PR #1123 code review: - Stalker search: setSelectedContentType now runs BEFORE setSelectedItem, so the TMDB enrichment gate in the selection hook no longer sees the content type of the previously open tab (wrong/no enrichment after ITV -> search -> movie). - Title normalization is now two-tier (normalizeTitleKeys): the exact normalized form keeps a trailing year, the base form strips it and remembers the tag. Year stripping is anchored to the end of the title ("2001: A Space Odyssey" keeps its year) and language-prefix stripping is UPPERCASE-only ("It: Chapter Two" is no longer amputated). - All catalog matching (similar rail, actor pages, DB worker DB_MATCH_TITLES) compares exact forms first and only accepts year-stripped matches when the stripped tag is year-compatible (+-1) with the TMDB year — "Blade Runner" (1982) can no longer claim a catalog "Blade Runner 2049". CatalogTitleMatch carries the stripped trailingYear so the renderer can apply the guard to worker matches. - mergedBackdrops tolerates a plain-string backdrop_path; enrichment merge+patch blocks are wrapped in try/catch so a malformed provider payload can no longer become an unhandled rejection. - loadGlobalMatches (both actor routes) guards against actor->actor navigation races — a slow match for the previous person no longer overwrites the current one's results. - tmdb_metadata media_type CHECK widened to ('movie','tv','person') and person rows now use the honest 'person' type (TmdbCacheMediaType). Pre-release dev DBs with the narrow CHECK are rebuilt in place — the table is a pure cache, so the migration is a self-healing drop-and-recreate keyed off sqlite_master. Docs updated (tmdb-metadata-enrichment.md, CLAUDE.md). New regression coverage: title-normalization.util.spec.ts, two-tier cases in tmdb-similar.util.spec.ts and title-match.operations.spec.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |