# 020 — P2 Webshare availability (built 2026-10-09)

Only titles that can be played are shown (decision 3). `wcs/foryou/availability.py`.

## How a title is checked

- The same search as playback (`wcs/ui/dialogs/DialogWcsSearch.py`): the dialog's own query (`<title> <year>`,
  `<series> S01E01`), `ranking.movie_queries` / `episode_queries` with the Czech and the original title, the dialog's
  fallbacks (title without the year; `do_webshare_search_with_retry` for episodes), and `ranking.rank` with the strict
  target (only files that are this film / this episode). "Available" therefore means the play dialog will have that
  file on top.
- A series is checked on S01E01.
- The best playable file decides the language: `CZ_DUB` and bare `CZ` → `cz_dub`, `CZ_SUB` → `cz_sub`,
  `SK_DUB` → `sk_dub` (user decision 2026-10-09: CZ + SK dubbing). EN / unlabelled / SK subtitles = not shown.
- Password-protected files do not count.
- Ambiguous one-word Czech titles that differ from the original ("Rodiče" = Breeders) need a file that also names the
  original title or the year.
- Playback now gets the exact TMDb id (`utils.search_and_select_movie_file` / `_episode_file`, `resolver`), so the
  dialog's original title and logo come from the same title that was checked.

## Cache and budget

- `meta` key `avail:<media>:<id>`: found 7 days, missing 3 days, missing and released within 120 days 12 h; errors are
  never cached; one retry on a garbage answer from Webshare.
- Order of checks (`engine._check_order`): the chart, the best 40 new and 40 gem candidates, then by score; all ~300
  titles of the detailed pool. First build ~300 checks in ~25 s (4 threads), later ones a few seconds.
- No Webshare account / login fails → the page is built unverified (`snapshot.verified = false`).
- Fewer than 40 playable titles → `NotEnough`: the previous snapshot stays (a Webshare outage never empties the page).

## In the page

- `Nově s CZ dabingem` takes only `cz_dub`; elsewhere the Czech version wins ties (score × 1.0 / 0.92 / 0.85 for
  dub / subs / SK dub).
- Snapshot items carry `lang` and `quality`; the hero kicker ends with `CZ DABING · 4K` (quality only for 4K / 1080p).

## Found and fixed on the way (2026-10-09)

- Tracker: a stream that broke off seconds after the start ("eof") counted as a natural end = watched. Now an end
  with a fresh position far from the end is a broken stream (`Engine._broke_off`); a cut file keeps its resume point.
- Taste: a resume point can come from a seek, so "seen" uses the really played seconds of the play log
  (min(position, session wall time) per session).
- Open, separate task: `NotifyAll failed to parse data` (wcs/skin/refresh.py notify sends URL-quoted JSON).
