feat(anime): filter episodes and mark them watched by hand

- Add a filter box above the episode list for a number, range, or name substring, with a "N of M" counter
- Read watch marks from the immersion tracker stats and show them per episode, with a watched count in the header, refreshed on window focus
- Add a right-click menu to mark one episode or a whole catch-up span (this and everything below) watched/unwatched
- Add setWatched/getWatchState IPC plumbing so a manual mark creates the stats row for an episode that was never played
This commit is contained in:
2026-08-15 21:44:51 -07:00
parent 3dd0750b98
commit 8d55d64ee0
23 changed files with 1601 additions and 111 deletions
@@ -1,4 +1,11 @@
import type { AnimeStreamMetadata } from '../../anime-bridge/episode-metadata';
import type {
StreamPlaybackMetadataInput,
StreamWatchState,
} from '../../core/services/immersion-tracker-service';
/** What the stats store needs to record a mark against one episode. */
export type StreamWatchMark = StreamPlaybackMetadataInput;
import type { PlaybackEndFileEvent } from '../../anime-bridge/playback-outcome';
import type { SubtitleCacheIo } from '../../anime-bridge/subtitle-cache';
import type { BundleBinaries } from '../../anime-bridge/sidecar-bundle';
@@ -29,6 +36,18 @@ export interface AnimeBrowserRuntimeDeps {
showVisibleOverlay?: () => void;
/** Publishes stream identity before loadfile starts the stats session. */
onPlaybackMetadata?: (metadata: AnimeStreamMetadata) => void;
/**
* Watch state for the given stats paths, from the same store playback writes
* to. Absent (or resolving empty) when stats tracking is disabled, which the
* browser shows as "no watch history" rather than as an error.
*/
getWatchState?: (statsPaths: string[]) => Promise<Map<string, StreamWatchState>>;
/**
* Sets or clears the watch mark by hand. Marking creates the stats row for an
* episode nobody has played yet, which is what makes catching up on a series
* watched elsewhere possible.
*/
setWatchState?: (episodes: StreamWatchMark[], watched: boolean) => Promise<number>;
/** Lets tests drive the pause between loadfile and track attachment. */
wait?: (ms: number) => Promise<void>;
/** Overrides the filesystem/network the subtitle cache uses. Tests only. */