Docs: sync with reality

README: storage is SQLite (aniweb.db), not JSON files; CDN serves a
single quality (no ABR) — replaced the wrong 1080/720/360 claim;
document the 16-lane parallel fetch, intro-skip, episode names,
buffering UI, /api/cdn + background probe, the ?v= cache-busting rule,
and the client self-report diagnostics.
SPEC: same single-rendition correction (2 places).
PLAN: marked complete (all items shipped; deferred list was stale).
This commit is contained in:
jozamudi committed 2026-09-01 21:25:13 +00:00
1 parent 0315356f89
commit 14bdb04ef6
3 files changed
+30 -5

No files matched your search

+4
View File
@@ -1,5 +1,9 @@
# Plan — UI/UX polish bundle (items 1–4 + 7) # Plan — UI/UX polish bundle (items 1–4 + 7)
**STATUS: complete** — every item below shipped (see git log, c2461f8 and
friends). Kept as the planning record; the "deferred" list at the bottom
is stale (favicon, a11y pass, autoplay-next were all done afterwards).
The agreed bundle from the polish list: The agreed bundle from the polish list:
1. **Loading states** — skeleton cards + player spinner 1. **Loading states** — skeleton cards + player spinner
+23 -3
View File
@@ -33,7 +33,7 @@ Account management (no self-signup by design):
``` ```
Security: passwords are PBKDF2-SHA256 (210k iters, per-user salts) in Security: passwords are PBKDF2-SHA256 (210k iters, per-user salts) in
`server/data/users.json` (0600). Sessions are signed 30-day `HttpOnly` `server/data/aniweb.db` (SQLite, 0600). Sessions are signed 30-day `HttpOnly`
`SameSite=Lax` cookies; `run.sh` sets `ANIWEB_SECURE=1` so cookies are `SameSite=Lax` cookies; `run.sh` sets `ANIWEB_SECURE=1` so cookies are
`Secure` and only work through your HTTPS proxy. Accounts deleted via `Secure` and only work through your HTTPS proxy. Accounts deleted via
the app or CLI are rejected immediately, even with an old cookie. the app or CLI are rejected immediately, even with an old cookie.
@@ -45,7 +45,7 @@ the app or CLI are rejected immediately, even with an old cookie.
Accounts are created from the CLI only — there is no signup screen, Accounts are created from the CLI only — there is no signup screen,
because the app is meant to sit behind your proxy on the open because the app is meant to sit behind your proxy on the open
internet. Each user gets a fully isolated watch history internet. Each user gets a fully isolated watch history
(`server/data/history/<user>.json`): nothing is stored in the browser, (`server/data/aniweb.db`, SQLite): nothing is stored in the browser,
so a new device picks up your exact watch state after login. Deleting so a new device picks up your exact watch state after login. Deleting
an account (hamburger menu → delete, or `server.users rm`) kills its an account (hamburger menu → delete, or `server.users rm`) kills its
session cookies immediately and removes its history. session cookies immediately and removes its history.
@@ -77,10 +77,20 @@ detail view: synopsis, stats, season tabs, and filler badges.
Playback is HLS through a backend proxy (`/stream/{token}`) — the Playback is HLS through a backend proxy (`/stream/{token}`) — the
video CDN sends no CORS headers, so the browser can't fetch it video CDN sends no CORS headers, so the browser can't fetch it
directly. hls.js handles ABR across 1080/720/360p. directly. The CDN serves a single quality (no ABR); the proxy pulls
each segment across 16 parallel byte-range lanes (falling back to a
single stream if the CDN refuses ranges), so a slow connection doesn't
stall playback.
- **Resume** — autoplay on open, resumes at the saved position; the - **Resume** — autoplay on open, resumes at the saved position; the
position is saved every ~10s position is saved every ~10s
- **Skip intro** — MAL/Jikan intro timings (30-day cache) drive a
"skip intro ⏭" pill while the intro window is on screen
- **Episode names** — MAL titles (via keyless Jikan) in the episode
tooltips and the "now playing" line
- **Buffering UI** — spinner overlay, a buffer-ahead gauge under the
video, and a CDN chip (live KB/s from `/api/cdn`, amber when the CDN
is degraded)
- **Dub/sub toggle**, **playback speed** (0.5–2×, remembered), and a - **Dub/sub toggle**, **playback speed** (0.5–2×, remembered), and a
**hide fillers** switch (fillers are badged and skipped by **hide fillers** switch (fillers are badged and skipped by
prev/next/auto-next) prev/next/auto-next)
@@ -117,6 +127,16 @@ SPEC.md design + the verified anidb.app data flow
backend HLS proxy (`/stream/{token}`), which rewrites playlist URIs. backend HLS proxy (`/stream/{token}`), which rewrites playlist URIs.
- `.venv/bin/python server/upstream.py` runs a self-check of the - `.venv/bin/python server/upstream.py` runs a self-check of the
fetchers (search → details → episodes → dub stream for Chainsaw Man). fetchers (search → details → episodes → dub stream for Chainsaw Man).
- A background probe fetches 64KB from the CDN every 3 min and logs
state flips to `server.log`; `GET /api/cdn` (public) returns the
latest throughput + ~1h history. Use it to check if "playback
broken" is the CDN again.
- The front-end is version-pinned: bump `?v=N` on `app.js`/`style.css`
in `index.html` on every static change — phones/proxies otherwise
serve stale JS (index.html itself is `no-store`).
- Playback failures self-report: the client posts a step timeline +
capability fingerprint to `/api/events` (see the `events` table),
so a broken browser is diagnosable from the server.
- `.venv/bin/pytest tests/ -q` runs the full test suite against a - `.venv/bin/pytest tests/ -q` runs the full test suite against a
throwaway server (:8399, temp data dir — never touches the real throwaway server (:8399, temp data dir — never touches the real
data). The API tests use stdlib only; the browser tests need data). The API tests use stdlib only; the browser tests need
+3 -2
View File
@@ -18,7 +18,7 @@ last 20), and the front-end lives in `static/` with the server code in
| 3. Episodes | `GET anidb.app/api/frontend/anime/{id}/episodes` | JSON `{episodes: [{id, number, number2, filler}]}` | | 3. Episodes | `GET anidb.app/api/frontend/anime/{id}/episodes` | JSON `{episodes: [{id, number, number2, filler}]}` |
| 4. Languages | `GET anidb.app/api/frontend/episode/{ep_id}/languages` | JSON `{languages: [{code: "jpn"\|"eng", name, embed_url}]}` — `jpn`=sub, `eng`=dub | | 4. Languages | `GET anidb.app/api/frontend/episode/{ep_id}/languages` | JSON `{languages: [{code: "jpn"\|"eng", name, embed_url}]}` — `jpn`=sub, `eng`=dub |
| 5. Embed page | `GET {embed_url}` | JS blob containing `file: '<master m3u8>'` | | 5. Embed page | `GET {embed_url}` | JS blob containing `file: '<master m3u8>'` |
| 6. Master playlist | `GET {master m3u8}` (hls.anidb.app) | variants: 1080p / 720p / 360p | | 6. Master playlist | `GET {master m3u8}` (hls.anidb.app) | single rendition (v1) — no ABR ladders |
| 7. Variants + segments | `GET ...m3u8`, `.ts` | streamable HLS — **NO CORS headers** | | 7. Variants + segments | `GET ...m3u8`, `.ts` | streamable HLS — **NO CORS headers** |
Key constraint: anidb.app sits behind Cloudflare (plain curl → 403). Key constraint: anidb.app sits behind Cloudflare (plain curl → 403).
@@ -85,7 +85,8 @@ produced black screens.
Flow: search box → results grid (poster + title, click) → detail view Flow: search box → results grid (poster + title, click) → detail view
(synopsis under the episode grid, season tabs) → episode grid (number (synopsis under the episode grid, season tabs) → episode grid (number
+ filler badge, sub/dub toggle) → `<video>` with hls.js pointed at + filler badge, sub/dub toggle) → `<video>` with hls.js pointed at
`/stream/{token}`; quality follows the master playlist (auto ABR). `/stream/{token}`; single quality — the proxy fetches each segment
across parallel byte-range lanes (single-stream fallback).
- Default play: opening an anime resumes the last watched episode of - Default play: opening an anime resumes the last watched episode of
the season (seeking to the saved position unless ≥95% through), the season (seeking to the saved position unless ≥95% through),