mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
77458b393194258e37340ff5e111eaa51affcdf3
22
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7a629f5fe5 |
fix(dashboard): hero legibility in the light theme and stable page heading (#1811)
UI-24 from the UI consistency audit.
- No-artwork slides paint their gradient in CSS from the slide hue: a light
tint in the light theme, unchanged near-black in the dark one. The dark
gradient under the light page-coloured scrim read as a grey slab.
- The side scrim holds 88% of the page colour up to the slide's right edge
(inset + min(560px, 55%)), so the end of a full slide no longer sits on
about 45%.
- Narrow layout (container <= 720px): a full-bleed 90% scrim behind the text
block, a scrim-coloured text shadow, and an entrance without a fade so
that scrim never flashes the art on a rotation.
- --hero-body is 85% of the heading colour (was 72%).
- Light --app-rating-color #a16207 -> #7a4a00: measured 3.36:1 on the chip
over artwork, now 5.10:1. The details pages share the chip and token.
- Buttons cap at the slide width and end long labels in an ellipsis.
- The page gets one visually hidden h1 ("Dashboard"); slide titles are h2.
- One live region outside the re-created slide announces slide changes;
progress bars are named and VOD ones read "N% watched"; dots are 24px.
dashboard-hero-legibility.e2e.ts replaces every image with a checkerboard
and measures each piece of slide text from the screen in both themes, wide
and narrow, for backdrop, poster, no-artwork and live slides. On master the
worst cases were 2.35:1 (body text) and 2.65:1 (pills).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ab8460338a | feat(ui): cinematic movie and series details pages and dashboard hero (#1792) | ||
|
|
c9d169e3dc |
fix(dashboard): scroll a focused rail card fully into view (#1785)
* fix(dashboard): scroll a focused rail card fully into view Chromium skips its focus scroll when 32px or more of the element already shows, so Tab onto the last source card of a rail that overflows by less than a card left it half-hidden under the edge fade. The rail track now handles focusin and scrolls to the first card-start snap position that reveals the whole card; a plain "nearest" scroll is not enough because mandatory snapping can round it back (seen on the live channel rail). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): keep mouse clicks on partly hidden rail cards A mouse press focuses the card link on mousedown. Revealing the card at that moment could slide it from under the pointer when the target snap position overshoots (the live channel rail moves 316px for a 306px card), so the click landed elsewhere. The rail now reveals a card only for keyboard and programmatic focus, using the CDK FocusMonitor origin. Adds an Electron E2E that checks the final layout after snapping: Tab and focus() leave the last source card fully visible, and a mouse press keeps the rail still and still opens the source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): tell pointer focus apart without touching the DOM FocusMonitor toggles cdk-*-focused classes on the monitored track, so a mouse press on a source card mutated the DOM before the click. The J2 "open a source" performance journey rejects iterations with DOM activity between its settle snapshot and the click. Read the input modality from the CDK InputModalityDetector in a focusin handler instead: it only listens, so the rail stays untouched until the click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): reveal script-focused rail cards after a mouse click The input modality stays "mouse" after any click, so a later focus() on a partly hidden card left it clipped. The rail now skips the reveal only for focus caused by a press inside the track: the focus has to arrive within 100ms of that pointerdown (650ms for touch, whose focus comes with the tap's compatibility mouse events, as in the CDK FocusMonitor). Only event timestamps are compared, so the DOM still stays untouched before the click. The E2E now clicks elsewhere before the script focus, and unit tests cover a tap and focus() after an earlier mouse press. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): keep an over-wide focused rail card in view In a window narrower than a card (or under zoom), a focused card could never fit, so its own snap offset fell short of the needed scroll and the rail jumped to the next card's snap point, moving the focused card offscreen. Such a card is now aligned at its own start instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(dashboard): select rail internals through stable test ids The dashboard contract makes data-test-id hooks the supported Electron E2E selector surface. The rail now exposes -viewport, -track and -card-link hooks next to its existing ones, and the focus E2E selects those (and the rail heading by role) instead of internal class names. The dashboard doc lists the new hooks and records the focus-reveal contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b6357da6af | fix(dashboard): keep the hero skeleton until every hero source has loaded (#1782) | ||
|
|
0524e72b9f |
perf(dashboard): fill the hero rotation dot on the compositor (#1758)
* perf(dashboard): fill the hero rotation dot on the compositor The active hero dot animated `width` 0 → 18px for every 8 s rotation, so an idle dashboard with two or more slides ran style, layout and paint on every frame. The fill is now a full-width bar that slides in with `transform` under the pill's rounded clip. The `animationend` advance, the pause and reduced-motion behaviour are unchanged. Measured on the E2E build (visible, four slides, 120 s): layouts 10,405-10,677 -> 366-369, renderer process CPU 14.2-16.4 s -> 3.7-4.1 s, GPU process CPU 14.8-18.8 s -> 14.0-14.8 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): drive the real hero rotation animation The unit specs dispatch `animationend` on the dot span by hand, so a fill whose real event no longer reached the handler would still pass. The new Electron spec shortens `--hero-rotation-ms` and checks that the running `::before` fill advances the slide, that pause holds it and that Play resumes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
97f56e219c | perf(dashboard): stop idle dashboard ticks that have nothing to update (#1722) | ||
|
|
0f768b6e20 | fix(dashboard): extend rail edge fades to the track clipping edge (#1731) | ||
|
|
d8229b98fa |
feat(playback): record recently viewed only after the stream plays (#1732)
* feat(playback): record recently viewed only after the stream plays A channel, movie or episode used to enter Recently Viewed (and the dashboard's Continue Watching hero) the moment it was selected or its link was resolved, so streams that failed straight away cluttered the history. Writers now defer the write to a root PlaybackHistoryGate, keyed by the stream URL and/or the playback session key. The inline players confirm those keys once the owned engine's position has advanced by two seconds (seeks, stalls, pauses and a previous stream's progress do not count), the radio player does the same, and a launched MPV/VLC session confirms on `opened`/`playing`. M3U with MPV/VLC configured keeps recording on selection. Covers M3U (live, radio, movie detail), Stalker (live, radio, VOD, series), Xtream VOD and series, and the global live collection. The M3U host's embeddedPlayback is now compared by value: the history write updates the playlist meta mid-playback, and a new but identical playback object remounted the engine and restarted the stream. E2E flows that relied on recording-on-click now play local fixtures (HLS/TS/WebM routed in place of unreachable or public streams) and wait for confirmed playback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): tighten recently viewed confirmation per review - Correlate by session key first: when both the deferred write and the confirmation carry a playbackSessionKey, only that is compared, so the same stream URL played in another playlist no longer records a failed attempt. URLs remain the fallback (portal writes, MPV/VLC sessions). The M3U radio player now receives the host's session key. - Count only playing progress: engines report `playing` (not paused, not seeking) with each time update, so short seeks of paused media no longer confirm a view. - Xtream: a write confirmed after a playlist switch still saves to its own playlist but no longer replaces the current playlist's recent list. - Global live tab: a row confirmed after another row was selected still moves to the top of an open Recently Viewed list (only a disposed tab skips the notification). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): confirm session-keyed history only by its own session A write deferred with a playback session key (M3U) is now confirmed only by that key. The app-wide MPV/VLC session confirmation carries just the URL, so opening the same stream externally from another playlist could still commit an abandoned attempt. An "Open in MPV/VLC" recovery launch is instead confirmed by the WebPlayerViewComponent that requested it, under its own session key, once the launch has opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(playback): keep the history gate off the initial bundle The `@iptvnator/services` barrel ships in the initial bundle, so adding PlaybackHistoryGate there (and subscribing to it from the app-wide ExternalPlaybackService) grew renderer.initialBytes by 1,141 bytes. - Move the gate to a new lazy-only `playback-data-access` project (`@iptvnator/playback/data-access`; scope:shared, domain:playback, type:data-access) and register it in the coverage policy. - The gate subscribes to MPV/VLC session updates itself; it is created by the first deferred write, which precedes the launch it waits for. ExternalPlaybackService is back to master. - The Xtream "playlist switched before confirmation" check moves to the lazy helper; the initial-path store only takes a `skipListRefresh` flag. Net effect on this branch: +27 bytes over master (master itself is 108 bytes over the ratchet baseline already). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(playback): drop late Xtream confirmations off the initial path The Xtream store ships in the initial bundle, so even the small `skipListRefresh` flag cost 27 bytes there. A confirmation can only arrive after a switch to another playlist from a slow MPV/VLC launch (the inline player goes with the page), so the lazy helper now drops it instead: recording it would misfile the item or replace the other playlist's recent list. with-recent-items is back to master. This branch is now 3 bytes below master on renderer.initialBytes; the ratchet still reports master's pre-existing overage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(playback): pass the spec type-check gate from master - playback-data-access: align tsconfig.spec.json with the epg-data-access config #1705 updated (bundler resolution, global.d.ts for window.electron). - M3U recent-history spec: type the selectSignal override. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): keep late Xtream confirmations in their own playlist history A confirmation that arrives after a switch to another playlist (a slow MPV/VLC launch) is no longer dropped: the lazy helper saves it to the captured playlist through the data source, without reloading the store's recent list, which belongs to the other playlist by then. The store and its barrel ship in the initial bundle, so the save path stays in the feature helper; renderer.initialBytes stays under the baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): correlate global live-tab history by its session key The unified Favorites/Recent live tab deferred its history write by stream URL only, so the same URL played from another playlist could confirm a failed selection, and a switch to catch-up before confirmation could never match. It now defers with the tab's playlist-scoped playbackSessionKey (the key its players confirm with), and the tab's radio player receives it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): keep MPV/VLC rows of the live tab confirmable by URL The live tab's session key can only be confirmed by its own inline players; MPV/VLC confirm the launched URL alone. A row that goes to an external player (also later, after a double-click) now defers by URL, and only rows played inline carry the session key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): per-channel M3U history attempts, capability-based Xtream fallback - M3U: the recently-viewed dedupe key now includes the channel id, so a second row of the same URL defers its own write (its session key) and is recorded when it plays after the first row failed. - Xtream late write: key uncached content by Xtream id per supportsXtreamSqliteDataSource (the data-source factory's contract), not by a generic Electron bridge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
887ac64d18 | feat(workspace): cinematic rotating dashboard hero (#1725) | ||
|
|
faad8fd8fd |
docs(agents): compact root guidance and preserve task-specific knowledge (#1645)
* docs(agents): compact root guidance and preserve task-specific knowledge * fix(agents): parse guidance navigation with Markdown tokens * fix(agents): validate generic literal repository paths * fix(agents): distinguish code symbols and shortcut images * fix(agents): recognize SCSS filename literals * fix(agents): handle fenced imports and encoded paths * fix(agents): parse prose and rendered HTML anchors * fix(agents): validate rendered HTML navigation * fix(agents): use GitHub-compatible heading slugs * fix(agents): require standalone top-level Claude import * fix(agents): exclude HTML-contained guidance imports * fix(agents): handle image fragments and quoted imports * fix(agents): validate visible HTML and image source sets * fix(agents): recognize package scopes and route source work * fix(agents): parse JSONC and constrain package exemptions * fix(agents): decode link entities and allow package subpaths * fix(agents): route source work and check extensionless files * fix(agents): support package versions and source fragments * fix(agents): accept qualified package prose * fix(agents): retain rendered context for Markdown references * fix(agents): validate visible headings and spaced paths * fix(agents): validate media and hyphenated literal paths * fix(agents): decode full HTML entities and media assets * fix(agents): recognize possessive package mentions * fix(agents): validate extensionless imports and version comparators * fix(agents): retain visible backticks and explicit path punctuation * fix(agents): validate image-map navigation targets * fix(agents): count all Markdown line endings in budgets * fix(agents): delimit package prose at Unicode punctuation * fix(agents): normalize punctuation for extensionless imports * fix(agents): preserve filenames across prose punctuation * fix(agents): validate iframe document references * fix(agents): inspect document suffix before URL fragments * fix(agents): unify Markdown suffix and encoded import guards * fix(agents): handle wildcard versions and alternate documents * fix(agents): validate document formats and trim HTML URLs * fix(agents): cover document families and guidance basenames * fix(agents): require files for media references * fix(agents): preserve block boundaries and validate embeds * fix(agents): normalize internal HTML URL whitespace * fix(agents): reject empty media and ignore URL at-signs * fix(agents): validate srcdoc references and empty srcset * fix(agents): honor HTML bases and preserve adjacent imports * fix(agents): convert base file URLs to native paths * fix(agents): preserve imports after bare URL punctuation * fix(agents): exclude opaque URI prose from import scans * fix(agents): keep import tokens outside URI scheme matches * fix(agents): restrict opaque URI exemptions to parsed links * fix(agents): handle opening prose delimiters * fix(agents): scan nested imports and share document suffixes * fix(agents): reject pathless media and direct file URLs * fix(agents): reject file bases and preserve quoted URL boundaries * fix(agents): distinguish URL quotes and cover guidance variants * fix(agents): validate SVG images and conventional guides * fix(agents): handle declared package names handles and SVG use * fix(agents): normalize closing punctuation on federated handles * fix(agents): normalize Unicode punctuation on handles * fix(agents): normalize possessive federated handles * fix(agents): separate parenthetical prose from handles * fix(agents): exclude www autolinks from import scanning * ci: allow manual CodeQL validation of PR branches * fix(agents): reject nonportable Windows drive links |
||
|
|
6578e4073c |
feat(dashboard): portal EPG on the live rails, loaded lazily per visible card (#1638)
Xtream and Stalker live cards on the dashboard carried no XMLTV key, so the rails never asked anything for them and showed only the LIVE chip. Their programme now comes from the portal, one card at a time and only once the card is on screen. - `lib-dashboard-rail` reports the cards inside its viewport through an IntersectionObserver rooted at the track; `DashboardLiveEpgPresenter` unions them with the pinned hero row and hands the set to `DashboardPortalLiveEpgPresenter`. - `DashboardPortalLiveEpgService` runs the bounded queue — two requests in flight, 200 ms apart, one card each — through `StreamResolverService.loadEpgForItems`, publishing every answer the moment it lands, so the page never waits and a slow portal delays no other card. A card scrolled past before its turn is never requested. - A programme is trusted for 60 s, an empty answer for 30 s (the resolver reports a dead portal and a guide-less channel identically), and a completion captures both the display offset and the EPG source revision, requeueing itself when either moved. - The presenter hands its wanted set back on destroy. Desktop only: the shared collection resolver is gated on the local XMLTV bridge. - A shimmer placeholder shows only before a card's first portal answer; M3U cards keep the batched XMLTV lookup. |
||
|
|
4e575a810e |
feat(dashboard): say where you left off instead of which provider it came from (#1646)
Every dashboard title carried a "Xtream · Series" / "Stalker · Movie" subtitle. Provider kind and content kind are the app's own taxonomy, not a property of the title, and they are identical on every card in a rail — so the one line that could tell two cards apart said nothing. The hero now shows the source name alone, through `playlistDisplayLabel` (a stored playlist name is routinely the pasted URL with credentials, or a MAC). Continue Watching cards show what actually varies: the "S1·E5" chip plus "12 min left". Favorites keep the title alone, Recently Added keeps the source name, and a meta row with nothing in it is no longer rendered. Stalker shows filed under Movies had no badge, no progress and no resume. An embedded-VOD row announces its episodes through a `series[]` array and carries no `is_series` flag, so `extractStalkerItemType` reports `movie` on purpose — the item must keep routing to the VOD catalog — while its progress is a set of episode positions keyed by the parent id, which the dashboard was looking up as a single `vod` row and never finding. Split the two questions: `PortalActivityItem.watch_kind` records the progress model when it differs from the routing type, and every reader that has to choose goes through `resolvePortalActivityWatchKind` instead of `type`. That makes the resume handoff reachable for Stalker, so wire it through `STALKER_SERIES_RESUME_TARGET`: consumed once after the series positions are read, hydrating a lazy Ministra season first with a bounded two-attempt retry, and playing nothing at all when the position read failed rather than restarting the episode from zero. Also fixes the global-recent route template, which bound `[seriesResume]` only on its Xtream branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4bcd4bd390 |
feat(dashboard): add TMDB "Because you watched" recommendations rail (#1419)
* feat(dashboard): add TMDB "Because you watched" recommendations rail
TMDB has no account-free "for you" endpoint, so the rail seeds per-title
recommendations from up to 3 recently watched movies/series. Seeds resolve
through the enrichment facade via a shared lookup-attempt builder (extracted
from the hero service), and recommendations already ride in every cached
details payload, so watched seeds cost zero network. Per-seed lists are
interleaved round-robin, deduplicated by id and normalized title, stripped
of watched/favorited titles, and matched against imported libraries with one
batched DB_MATCH_TITLES request; only year-compatible matches render and
fewer than 5 cards hides the rail. Loads are keyed by the seed set, and a
load where no seed resolved retries instead of latching.
The header names the seed ("Because you watched X") when exactly one seed
contributed, else falls back to the generic "Recommended for you". New
dashboardRails.tmdbRecommendations toggle (default on) in Settings ->
Dashboard; 4 new i18n keys translated across all 19 locales.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): harden recommendations rail reload semantics
Address Codex review findings: the load latch is now keyed by the seed
set PLUS the watched/favorited exclusion set, so favoriting a recommended
title re-filters the rail instead of being ignored by the seed-only memo;
an emptied watch history clears the root-provided service's items and
seed titles instead of leaving a stale rail; and a load requested while
one is in flight is queued and re-run afterwards, so a mid-flight history
change cannot commit results for an obsolete seed set. The dashboard
effect now also tracks favorites. Three regression tests added.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): catalog-aware invalidation, no empty latch, original-title aliases
Address Codex round-2 findings: the load key now includes the
imported-playlist id set, so importing or deleting a playlist re-runs the
catalog matching instead of leaving dead links or hiding fresh matches; a
below-threshold (or transiently failed) match result hides the rail
WITHOUT latching, mirroring the trending rail's retry-on-empty semantics,
since matchTitles maps worker failures to an empty list; and matching plus
watched/favorited exclusion now work through both the localized TMDB title
and the original-title alias, so a catalog named in the original language
still matches while cards keep displaying the localized form. Regression
tests added for all three.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): reset latch on hide, alias-year fallback, language-keyed loads
Address Codex round-3 findings: hiding the rail below the match threshold
now also resets the saved load key, so returning to a previously
successful input set (un-favoriting, restoring a playlist) reloads instead
of dying on the equality guard; alias matching picks the first alias whose
match is also year-compatible, so a same-named different-year row hit by
the localized title no longer vetoes the correct original-title match; and
the load key now includes the effective TMDB language (exposed on the
enrichment facade), so switching the app language re-localizes the cards
instead of keeping the previous language all session. Regression tests
added for all three.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): two-tier watched-title exclusion, drop ES2019 flatMap
Address the Codex round-4 finding: a provider stores whatever the panel
named the file, so a watched "Inception 2010" never matched TMDB's
canonical "Inception" by exact key. Exclusion now runs on two tiers —
exact normalized title plus a year-gated base tier — so the year-suffixed
shape is caught while a stored "Blade Runner 2049" still cannot swallow
the 1982 film. An unknown year on either side counts as agreeing, since
re-recommending something already watched is the worse failure.
Also replaces the alias query builder's flatMap with a loop: the web app
compiles this lib against lib: es2018, where Array.prototype.flatMap does
not exist, which broke the web build and every job downstream of it.
Both exclusion tiers are pinned by mutation-verified regression tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): index recommendation exclusions the way TMDB looks them up
Address Codex round-5 findings. The watched/favorited exclusion index is
now built through the same lookup-attempt builder the seeds and the hero
use, so an activity row is indexed under the media type the detail view
enriched with rather than its routing verdict — a Stalker embedded-VOD
series routes as 'movie' but is a show to TMDB, so its recommendation
looked up series: and sailed past a movie:-only entry — and under its
stored original-language title (info.o_name), which a translated
recommendation shares no key with. Only the builder's PRIMARY attempt is
indexed: the second is a fallback guess, and indexing it would let a
watched film exclude the same-named show.
Adds Electron E2E for the new setting: the toggle now appears in the
disabled-when-dashboard-off assertion (with the trending toggle, which
was also missing), plus a restart-persistence test. Rail rendering stays
unit-covered — it needs the TMDB opt-in, live TMDB data and catalog
matches, which would make an E2E network-dependent and flaky.
All three new unit tests are mutation-verified, including one that was
passing vacuously before this round.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): keep every catalog row until the year gate has chosen
Address the Codex round-6 finding: buildTitleMatchIndex collapses to one
row per key before the candidate's year is known, so a catalog holding
both "Dune 1984" and "Dune 2021" keeps whichever the worker returned
first and a 2021 recommendation then fails the year check with the right
row already discarded. The rail now groups the rows per key itself and
lets the year gate pick, still preferring an exact-title match over a
year-stripped one so the shared helper's precedence is preserved.
Mutation-verified regression test.
The trending rail shares the same collapse-then-check shape and is
unaffected by this PR; flagged separately as a follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): survive a failed refresh, document the new rail
Address Codex round-7 findings.
A refresh that cannot reach TMDB no longer leaves the rail untouched, but
it does not blank it either: a failed request is not a verdict that there
is nothing to recommend, and removing still-valid cards is the worse
answer for an offline user. What the failure cannot excuse is a card the
user has since watched or favorited, so the retained cards are re-filtered
against the fresh exclusion index and the rail hides if too few survive.
The key stays unlatched, so the next visit still retries.
Also documents the rail in the two canonical dashboard docs I missed:
the surface diagram and render rules in docs/architecture/workspace-dashboard.md
and the rail list in the feature README. Both had also never mentioned the
sibling trending rail, so that gap is closed in the same pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): year-aware exclusions, remake-safe dedupe, key reset
Address Codex round-8 findings.
The exclusion index now records each row's release year (Stalker's
info.releasedate, else a year read off the title) with every key, and both
tiers gate on it, so a watched 1954 "Godzilla" no longer excludes the 2014
one. A row that states no year records null and keeps excluding
unconditionally, so the conservative behaviour survives where nothing is
known.
Candidate dedupe is by TMDB id only; title collisions are resolved after
matching, by the catalog row a candidate resolved to. Same-titled remakes
("Dune" 1984 and 2021) are different films and must both reach the
matcher — collapsing them beforehand let whichever arrived first fail the
year gate on behalf of the one the library actually holds — while two
candidates landing on one row would render as duplicate cards.
The offline re-filter now clears the saved load key, so restoring those
exact inputs (un-favoriting the title) rebuilds the rail instead of
hitting the equality guard.
Splits the pure helpers and data shapes into dashboard-recommendations.util.ts:
the service had crossed the 400-line production limit. All three fixes are
mutation-verified, including one test that only became real after the
mutation showed it passing on the wrong ordering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): do not latch a partially resolved seed set
Address the Codex round-9 finding: when several seeds load and only some
resolve, latching marked the whole set complete, so a seed that failed
transiently lost its recommendations for the rest of the session. The load
now latches only once every seed has answered.
A seed with no TMDB match never resolves either, so that user's rail
re-runs on each dashboard visit. That is bounded work — the enrichment
misses are cached and the catalog match is one batched worker call — and
it matches the rail's existing policy of not latching on uncertainty.
Mutation-verified regression test, plus one pinning that a fully resolved
set still latches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): trust only stated years on the exact exclusion tier
Address Codex round-10 findings.
The first is a regression I introduced last round: recording a
title-inferred year with the exact exclusion key meant a watched
"Blade Runner 2049" carried year 2049, disagreed with TMDB's actual 2017,
and stopped excluding the very film the user had just watched. The exact
tier now gates only on a year the row STATES in a metadata field
(Stalker's info.releasedate) — the rule releaseTagYear already documents:
on a whole-title match a trailing number belongs to the name and nothing
can settle it. The base tier keeps its stripped trailing year, which is a
suffix by construction, so the Godzilla 1954/2014 case still holds.
The offline re-filter also drops cards whose playlist has been deleted.
That path is the only one that can reach retained cards without the
catalog key rebuilding the rail, so those cards would otherwise navigate
to a dead route.
Both fixes are mutation-verified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): resolved media type replaces the routing one; prefer year-tagged rows
Address Codex round-11 findings.
The exclusion index no longer indexes an activity row under BOTH its
routing type and its resolved media type. A Stalker embedded-VOD series
routes as 'movie' on positive series evidence, so keeping that key made a
watched show exclude an unrelated film of the same name — and, with no
release date to gate on, unconditionally. The resolved type now replaces
the routing one; a row the builder cannot classify keeps its routing type,
which is then the only thing known.
Catalog matching now prefers a row whose stripped year IS the candidate's
over an untagged one: an untagged "Dune" row could be either cut, so
linking a 2021 recommendation to it while "Dune 2021" also exists throws
away the better evidence. Untagged rows stay next in precedence, which is
also the only tier reachable when the candidate's year is unknown.
Both mutation-verified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): no TV retry for catalog-classified Xtream rows
Address the Codex round-12 finding: the movie -> tv lookup retry exists
because a Stalker embedded-VOD series is stored as a 'movie' activity row,
but an Xtream row's type comes from a catalog that files movies and series
apart, so there 'movie' is evidence rather than a default. The retry let a
same-titled show answer for a film — the mirror of the existing rule that
a 'tv' verdict never retries as 'movie'.
The lookup item type had dropped the `source` field that distinguishes
them; restoring it is enough to gate the retry. This also tightens the
hero rail, which shares the builder. Mutation-verified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): confirmed movies skip the TV retry; key by the whole attempt chain
Address Codex round-13 findings, both consequences of last round's change.
A stored Stalker `info.tmdb_id` is never a provider claim — the contract
says its only source is a match this app already gated, under that very
media type — so such a row's 'movie' verdict is no longer the ambiguous
default the TV retry exists for. Retrying it let a same-titled show answer
for a film whenever the movie lookup transiently returned null. The retry
now runs only for rows nothing has confirmed.
The lookup key is now the whole attempt sequence rather than the primary
attempt alone: two rows can share title, year and id yet differ in whether
a TV fallback follows, and callers cache by this key — the hero's
root-level memo would otherwise serve a Stalker row's TV answer as an
Xtream movie's metadata, and selectSeeds() would collapse two seeds that
do not perform the same lookup.
Both mutation-verified. One existing hero test asserted the retry for a
fixture that carries a stored id; it now pins the confirmed-identity
behaviour instead, with a separate test for the id-less retry it used to
cover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dashboard): rank catalog matches by year evidence across aliases
Address the Codex round-14 finding: match selection returned as soon as
any alias had a compatible row, so an untagged row under the localized
title beat a row the original-title alias found carrying the candidate's
own year — the wrong remake when both cuts exist. Compatible rows from
every alias now form one pool ranked by evidence, with alias order kept
only as the tiebreaker inside a tier. The nested loop collapses into a
single pass in the process. Mutation-verified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
59c15493a7 |
docs: sync CLAUDE.md, AGENTS.md and architecture docs with actual code
Full audit of CLAUDE.md, AGENTS.md, README.md and docs/architecture/ against the codebase; every fix is backed by current code: - remove documented-but-unimplemented IPTVNATOR_DISABLE_HARDWARE_ACCELERATION flag (no reads anywhere in apps/, libs/, tools/) - CLAUDE.md: add epg_channel_mappings to the schema table list - m3u-playlist-module: *-tab dirs -> *-view (+recent-view), selectActivePlaylist, real PlaylistState shape, ChannelEpgMetadata instead of removed EnrichedChannel, actual /workspace/playlists routes, per-view outputs, live-epg-panel-state key - workspace-dashboard: per-rail Settings.dashboardRails toggles, three missing rails in the diagram, split live-favorites/recent-live rails, welcome-dashboard empty-state type, RECENTLY_WATCHED_LIVE_TV title key - stalker-portal: CategoryContentViewComponent for vod/series, collection-route components for favorites/recent, corrected series-view/favorites-button paths, actor/:personId route, epg panel selectors - category-management: reloadCategories lives in with-content.feature.ts, workspace-context-panel owns the dialog, XtreamPendingRestoreService flow - stalker-mock-server (+app README): scenario-seeded faker, resetAll() clears content cache too, ordinal season episode ids, handlers/ dir location - sqlite-db-worker: cancellation shipped (drop from out-of-scope), full operations module list - portal-detail-navigation: replace three removed component paths - tmdb-metadata-enrichment: details cache keys are id:<tmdbId>|v2 - electron-security: CSP frame-src youtube-nocookie exception, sandbox: !frameCopyExperiment nuance - download-manager: libs/portal/xtream instead of xtream-electron folder, data-driven downloads nav, drop removed app-search-result-item note - playlist-backup-restore: settings-backup facade owns the import handoff - workspace-shell: functional workspaceEntryRedirect, playlists route children - iptvnator-ui-guidelines: EPG card radius 11px, detail-view mixin is `base` - embedded-mpv-native, player-controls-contract: minor precision fixes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d308749e2c | fix(stalker): preserve is_series episode metadata (#1218) | ||
|
|
643dee1be3 |
feat(xtream): resume the latest series episode (#1187)
Dashboard Continue Watching now carries the exact saved season/episode into Xtream series details and starts it at the persisted offset. Successful external MPV/VLC launches persist the launched episode and retarget the series CTA to "Play episode N". Recent-history rows keyed by an episode id resolve their parent series before navigation. Includes maintainer follow-ups: no zero-offset resume on failed position loads, seriesXtreamId-gated resume targets for legacy rows, and patch coverage raised from 76.7% to 93.9%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b6c5367d63 | fix(dashboard): speed up startup rails | ||
|
|
2ecb5c28f4 | fix(dashboard): address PR review feedback | ||
|
|
549bdd7a9e | docs: update navigation and dashboard documentation for clarity and accuracy | ||
|
|
2edb5fe296 |
docs: update portal detail navigation and workspace dashboard architecture for collection detail handling
Entire-Checkpoint: c6e522b4276c |
||
|
|
d366672506 |
docs: refactor and document architecture for Stalker and Workspace components
- Moved Stalker-related components and services from apps/web to libs/portal for better modularity. - Introduced SQLite DB Worker to handle non-EPG database operations, improving UI responsiveness. - Updated documentation for the Workspace Dashboard and Shell, detailing current implementation and routing structure. - Added new EPG fixture scenarios to the Xtream mock server for testing purposes. - Enhanced the overall architecture documentation to reflect recent changes and improvements. |
||
|
|
4211f22a69 |
feat(dashboard): create separate workspace-dashboard lib with widgets
- Implemented `DashboardActivityItemsComponent` with styles and functionality for displaying activity items in list and grid views. - Created `DashboardWidgetShellComponent` for consistent widget layout with header and content areas. - Developed `GlobalFavoritesWidgetComponent` to manage and display global favorites with filtering options. - Added `RecentSourcesWidgetComponent` to show recently accessed sources with links to manage them. - Introduced `RecentlyWatchedWidgetComponent` to track and display recently watched content with filtering capabilities. - Created `SourceStatsWidgetComponent` to present statistics of different source types. - Established TypeScript configuration files for the dashboard UI library. |