From 70a8acfb18b98d55610de6cc0b2bd0359fc62301 Mon Sep 17 00:00:00 2001 From: Dymas Date: Sun, 17 May 2026 22:14:10 +0200 Subject: [PATCH] Simplify README structure and wording --- README.md | 434 +++++++++++++++++++++--------------------------------- 1 file changed, 169 insertions(+), 265 deletions(-) diff --git a/README.md b/README.md index 8c2e38f..ce5b4bc 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,60 @@ # ani-cli web -A local web UI for a system-wide `ani-cli` install. +Small local web UI for a system-wide `ani-cli` install. -Current version: `0.32.2` +Current version: `0.32.5` ## Project Layout ```text ani-cli-web/ - app.py Public entrypoint, runtime wiring, watchlist domain logic, and CLI startup. - app_support.py Shared runtime/state/config helpers plus queue and library file utilities. - Dockerfile Container image build for system-wide ani-cli plus /app ani-cli-web. - docker-compose.yaml Compose service for building and running the container with persistent mounts. - queue_jobs.py Download queue and background watchlist-refresh workers. - watchlist_identity.py Safer watchlist identity resolution for queued download jobs. - title_matching.py Shared anime-title normalization and matching helpers. - http_handler.py HTTP route handling, request parsing, and remote-access guard. - web_templates.py Shared Search, Config, and Watchlist page markup aggregation. - docker-entrypoint.sh Container startup wrapper with optional ani-cli self-update. - search_page.py Search page template. - config_page.py Config page template. - watchlist_page.py Watchlist page template. - template_helpers.py Shared page-shell navigation and sidebar brand helpers. - ani-cli-web Launcher script. - test_app.py Regression tests with isolated temp-state setup. - VERSION Project version. - CHANGELOG.md Change history. - README.md Project documentation. + app.py Main entrypoint and runtime wiring. + app_support.py Shared config, state, and helper utilities. + Dockerfile Container image build. + docker-compose.yaml Example Compose service. + queue_jobs.py Download queue and watchlist refresh workers. + watchlist_identity.py Download-to-watchlist identity matching. + title_matching.py Shared title normalization and matching. + http_handler.py HTTP routing and access control. + web_templates.py Shared page aggregation. + search_page.py Search page template. + config_page.py Config page template. + watchlist_page.py Watchlist page template. + docker-entrypoint.sh Container startup wrapper. + ani-cli-web Launcher script. + test_app.py Regression tests. + VERSION Project version. + CHANGELOG.md Change history. + README.md Project documentation. ``` -## Run +## Quick Start -Start the web app with: +Run the app: ```sh ./ani-cli-web ``` -For thumbnail debugging, run: +Run with debug logging: ```sh ./ani-cli-web --debug ``` -The launcher also accepts `---debug`. You can also enable debug logging with `ANI_CLI_WEB_DEBUG=1`. - -Then open: +Open: ```text http://127.0.0.1:8421/ ``` -By default the server binds to `127.0.0.1:8421`. +Default bind address is `127.0.0.1:8421`. ## Docker -The container image installs `ani-cli` system-wide and clones `ani-cli-web` into `/app` during `docker build`. +The image installs `ani-cli` system-wide and clones `ani-cli-web` into `/app` during build. -Default build arguments: +Default build args: ```text ANI_CLI=https://github.com/pystardust/ani-cli.git @@ -65,23 +62,23 @@ ANI_CLI_BRANCH=master ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git ``` -Build the image: +Build: ```sh docker build -t ani-cli-web . ``` -Override the `ani-cli` branch when needed: +Build with a custom `ani-cli` branch: ```sh docker build \ - --build-arg ANI_CLI_BRANCH=my-feature-branch \ + --build-arg ANI_CLI_BRANCH=my-branch \ --build-arg ANI_CLI=https://github.com/pystardust/ani-cli.git \ --build-arg ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git \ -t ani-cli-web . ``` -Run the container with the requested persistent mounts: +Run: ```sh docker run --rm \ @@ -94,318 +91,225 @@ docker run --rm \ ani-cli-web ``` -Or start the same setup with Compose: +Compose: ```sh docker compose up --build -d ``` -To keep mounted downloads owned by your host user, start Compose with: +Use host ownership for mounted downloads: ```sh USER_UID="$(id -u)" USER_GID="$(id -g)" docker compose up --build -d ``` -The included `docker-compose.yaml` uses the same defaults as the `Dockerfile`, mounts `./downloads` to `/downloads`, mounts `./.ani-cli-web` to `/app/.ani-cli-web`, and lets you override build/runtime settings through environment variables such as: - -```text -ANI_CLI=https://github.com/pystardust/ani-cli.git -ANI_CLI_BRANCH=master -ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git -ANI_CLI_WEB_PORT=8421 -UPDATE_ON_START=false -USER_UID= -USER_GID= -ANI_CLI_WEB_ALLOW_REMOTE=false -ANI_CLI_WEB_REMOTE_TOKEN= -``` - -Container defaults: +Container notes: - `ani-cli` is installed at `/usr/local/bin/ani-cli`. -- `ani-cli-web` is cloned into `/app` and started from there. -- Container startup now runs `python3 /app/app.py` directly, so it does not depend on the `ani-cli-web` launcher file having executable permissions inside the image. -- Downloads are written to `/downloads`, so bind-mount `./downloads:/downloads`. -- App state stays under `/app/.ani-cli-web`, so bind-mount `./.ani-cli-web:/app/.ani-cli-web`. -- The image sets `ANI_CLI_WEB_HOST=0.0.0.0` and exposes port `8421`. -- `UPDATE_ON_START=true` runs `sudo ani-cli --update` before the web app starts. -- Set `USER_UID` and `USER_GID` together to run the web app as a specific numeric host user/group, which keeps new files on mounted folders such as `./downloads` from being owned by `root`. +- `ani-cli-web` runs from `/app`. +- Downloads go to `/downloads`. +- App state lives in `/app/.ani-cli-web`. +- The container binds `0.0.0.0:8421`. +- `UPDATE_ON_START=true` runs `sudo ani-cli --update` before startup. +- `USER_UID` and `USER_GID` keep mounted files from being owned by `root`. -The installed `ani-cli` update URL is rewritten during the image build so `ani-cli --update` follows the same `ANI_CLI_BRANCH` you built into the image when the `ANI_CLI` remote is a GitHub repository. +## What It Does -## Overview +- Search AllAnime in `sub` or `dub`. +- Queue downloads with editable folder, quality, season, and episode range. +- Save defaults on `/config`. +- Track shows on `/watchlist`. +- Refresh watchlist episode counts in the background. +- Schedule periodic watchlist refresh jobs. +- Cache watchlist thumbnails locally. +- Sync successful downloads back into the watchlist. +- Keep queue and watchlist data in SQLite under `.ani-cli-web/`. -`ani-cli-web` keeps search and download management on `/`, saved defaults on `/config`, and the watchlist on `/watchlist`, all with the same sidebar navigation layout and a Tsuki-inspired visual style. +## Pages -The app currently includes: +### Search Page -- Search against AllAnime with `sub` or `dub` mode selection. -- Episode loading for the selected result. -- Queueing downloads with editable library name, season, episode range, quality, and target folder. -- A dedicated Config page for saved default folder, mode, and quality settings. -- Malformed config save requests now return `400 Bad Request` when the JSON body is not an object, instead of resetting values back to defaults. -- A paged SQLite-backed download queue with retry, cancel, remove, retry-failed, and clear-finished actions. -- A paged SQLite-backed watchlist with summary cards, per-show refresh, bulk refresh, source links, and thumbnail tools. -- Duplicate watchlist adds now return `200 OK` with `created: false`, keeping the API status aligned with the no-op result. -- Malformed watchlist add requests now also return `400 Bad Request` when the JSON body is not an object. -- Watchlist category tabs for `Watching`, `Planned`, `Finished`, and `Dropped`, with per-anime category editing. -- AnimeSchedule-backed watchlist completion tracking that compares live sub/dub availability with the expected total episode count when known. -- Automatic watchlist sync after successful downloads, including downloaded-vs-manual finished markers. -- Download sync now also reconciles unique title matches when ani-cli resolves a different `show_id`, so existing watchlist entries keep their category and still get marked as downloaded. -- Per-show background watchlist refresh queueing so adding a show or syncing a finished download does not block the request on upstream episode, schedule, and thumbnail lookups. -- Queued per-show watchlist refresh work now survives app restarts and resumes from rows still marked `queued`. -- Per-show watchlist refresh queueing now also deduplicates in-flight refreshes, so the same show is not fetched twice back-to-back while one refresh is already running. -- The watchlist page now silently refreshes itself while queued per-show refresh work is still being processed, so queued entries settle into their updated state without a manual reload. -- The watchlist page now also avoids re-triggering the same unresolved thumbnail warm-up request on every silent poll cycle while a show's thumbnail state is unchanged. -- Watchlist cards with both sub and dub fully ready now get a thin green border for quicker visual scanning, regardless of which category tab they are in. -- Background `Refresh All` execution with status polling, no inline thumbnail downloads during the bulk job, and a configurable delay between shows to reduce upstream request bursts. -- Optional scheduled background watchlist refreshes with Config page controls for enable/disable and refresh period. -- Scheduled background refresh runs now log queue, start, and completion status to stdout so container logs show whether they finished successfully, with errors, or failed. -- Routine browser polling endpoints such as the search-page queue poll and watchlist refresh-status poll now stay quiet in stdout request logs to reduce log spam. -- Queue job logs on the Search page now render newest lines first for easier at-a-glance progress checking. -- Search and Watchlist polling now use a shared serial browser polling helper so slow responses do not cause overlapping poll requests to pile up in the background. -- That shared browser polling helper now also tears itself down on page unload and uses chained `setTimeout` scheduling instead of a bare repeating interval. -- Controlled shutdown and runtime reset now cancel active download workers when waiting for teardown, reducing the chance of orphaned background downloads outliving the web process. -- Runtime reset now also refuses to clear the active global runtime when background workers fail to stop cleanly, preventing stale workers from outliving the runtime object that created them. -- Watchlist refresh networking now uses a shorter dedicated timeout, which helps per-show and bulk watchlist shutdown paths settle more predictably when upstream metadata services are slow. -- Persistent watchlist refresh history that survives restarts and marks interrupted bulk refresh jobs clearly. -- Local thumbnail caching plus manual thumbnail upload when automatic cover lookup fails. -- A refreshed glassy dark UI inspired by the Tsuki frontend design language. -- Shared page templates and backend services split into dedicated modules while keeping `app.py` and `web_templates.py` as stable public aggregation points. -- Broader regression coverage for higher-risk queue, watchlist, remote-access, and handler flows. -- Search and watchlist requests guard against stale out-of-order async responses during rapid repeated actions. -- Search-page queue polling also guards against stale out-of-order refresh responses now. -- Queue log writes are batched during active downloads to reduce SQLite churn on noisy jobs. -- Queue persistence now stores core job metadata and logs in structured SQLite columns instead of treating the whole job as one large JSON blob. -- Queue worker startup failures now degrade into a normal failed job state instead of crashing the background queue thread. -- Unexpected post-launch queue worker failures now also fail the active job cleanly instead of leaving it stuck in `running`. -- Failed and canceled downloads now clean their project-local staging directories automatically so partial files do not accumulate under `.ani-cli-web/staging/`. -- Failed thumbnail refresh attempts now record a retry timestamp, so unresolved covers back off instead of immediately requerying upstream sources on every later warm-up. -- Download-to-watchlist fallback matching now keeps season and part identity intact while still tolerating equivalent season notation such as `2nd Season` vs `Season 2`. -- Project-local runtime state under `.ani-cli-web/`. - -## Search Page - -The main page at `/` is where you search and queue downloads. - -Workflow: - -1. Search by anime title or keyword. -2. Pick a result to load available episodes. -3. Adjust library name, season, episode range, quality, and download folder. -4. Add the selected show either to the watchlist or directly to the queue. - -The page also includes: - -- A live queue view that refreshes automatically. - -## Config Page - -The Config page at `/config` is where saved defaults now live. +Path: `/` Use it to: -1. Set the default download folder. -2. Choose the default `sub` or `dub` search mode. -3. Choose the default search quality used to prefill the Search page. -4. Enable or disable periodic watchlist refresh checks. -5. Set the refresh period in minutes for that scheduled watchlist job. -6. Set the delay in seconds between shows during bulk watchlist refresh runs. +1. Search for an anime. +2. Load episodes. +3. Adjust folder, quality, season, and episode range. +4. Add the show to the queue or watchlist. -Those saved defaults are written into the project-local `config.json` and loaded automatically when the Search page opens. +Highlights: -The Config page also shows the current app version plus dependency status in a compact centered layout. +- Live queue view with automatic polling. +- Queue log shows newest lines first. +- Search polling avoids overlapping requests and stale responses. -## Access Guard +### Config Page -By default, `ani-cli-web` only serves loopback clients such as `127.0.0.1` and `::1`. +Path: `/config` -If you intentionally want LAN or remote access, set: +Use it to set: -```sh -ANI_CLI_WEB_ALLOW_REMOTE=1 -ANI_CLI_WEB_REMOTE_TOKEN=choose-a-long-random-token -``` +1. Default download folder. +2. Default `sub` or `dub` mode. +3. Default quality. +4. Scheduled watchlist refresh enable or disable. +5. Scheduled refresh period in minutes. +6. Delay between shows during `Refresh All`. -Non-local clients must then send either: +Saved settings are written to `.ani-cli-web/config.json`. -- `Authorization: Bearer ` -- `X-AniCli-Web-Token: ` +### Watchlist -For browser access from another machine, you can also open the app once with: +Path: `/watchlist` -```text -http://YOUR-HOST:8421/?token=your-token-here -``` - -The server will set an HTTP-only cookie and redirect to the same page without the token in the URL, so normal browser navigation and API polling continue to work afterward. - -When the app is placed behind a loopback reverse proxy, forwarded external client IP headers such as `X-Forwarded-For` are also treated as non-local, so proxied internet or LAN traffic still needs the token. - -This keeps the queue, config, and watchlist APIs safer when the app is intentionally exposed beyond localhost. - -Malformed watchlist action payloads now return `400 Bad Request` when required JSON fields are missing, instead of surfacing those client mistakes as `404 Not Found`. - -## Download Queue - -The queue is stored in SQLite and shown 10 jobs at a time. - -Core queue fields and logs are now stored in structured SQLite columns, while JSON payload storage is kept only for extra fields that do not have dedicated columns yet. - -Queue actions currently available: - -- `Retry` for finished or failed jobs. -- `Cancel` for pending or running jobs. -- `Remove` for completed or failed jobs. -- `Retry failed` for all failed jobs. -- `Remove finished` for completed jobs. - -Downloads are staged inside the project-local app state first. After `ani-cli` finishes, files are moved into a Plex/Jellyfin-friendly layout: - -```text -ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4 -``` - -## Watchlist - -The watchlist stores one row per show in SQLite, keeps separate `sub` and `dub` episode counts, and assigns each show to one of four categories: +Categories: - `Watching` - `Planned` - `Finished` - `Dropped` -You can add entries in two ways: +Ways to add shows: -1. From the Search page with `Add to watchlist`, choosing the category first. -2. Manually on `/watchlist` with a title, AllAnime `show_id`, and category. +1. From the Search page. +2. Manually on `/watchlist`. -Watchlist features: +Main behavior: -1. `Refresh` updates one tracked show. -2. `Refresh All` runs in the background, rejects duplicate queueing while a refresh is already pending or running, preserves interrupted job status across restarts, and refreshes shows one by one with a configurable delay without doing inline thumbnail downloads for every entry. -3. `Open` jumps back to the Search page with the title pre-filled. -4. Clicking the anime title opens the AllManga source page at `bangumi/` in a new tab. -5. `Remove` deletes the watchlist entry and clears its cached poster file. -6. Poster cards use a compact multi-column grid with pagination at 30 cards per page. -7. Summary cards show tracked shows plus total `sub` and `dub` episode counts. -8. Episode pills show `current / total` progress when AnimeSchedule exposes the planned episode count. -9. `Sub` and `Dub` pills turn green and gain `Sub ready` or `Dub ready` tags when that release track appears fully finished and ready for download. -10. Each category has its own tab on the watchlist page. -11. Every card includes an inline category selector so you can move tracked anime between tabs at any time. -12. Finished shows display a small check badge: green means the anime was downloaded through the app, gray means it was marked finished manually. +1. `Refresh` updates one show. +2. `Refresh All` runs in the background. +3. `Open` jumps back to Search. +4. Clicking the title opens the source page. +5. `Remove` deletes the entry and cached poster. +6. Each card has an inline category selector. +7. Summary cards show total tracked shows plus `sub` and `dub` counts. +8. Pagination shows 30 cards per page. -### Download Sync +Status behavior: -Successful downloads now feed back into the watchlist automatically. +- `Sub` and `Dub` pills turn green when that track looks complete. +- Cards with both `sub` and `dub` fully ready get a thin green border. +- Finished cards show a small check badge. +- Green check means downloaded through the app. +- Gray check means finished manually. -Current behavior: +Download sync: -- If the anime is already on the watchlist, a successful download marks it as downloaded and keeps its current category. -- If the anime is not yet on the watchlist, `ani-cli-web` creates a new watchlist entry in `Finished` after the download succeeds. -- Those add and download-sync paths now queue a background refresh for episode counts, AnimeSchedule metadata, and thumbnails instead of waiting for all upstream fetches before returning. -- If a queued job ever reaches the older search-based fallback path for `show_id` resolution, the app now only accepts that fallback when the queued title still matches the returned result confidently, preferring to fail safe over marking the wrong anime as downloaded. -- That fallback now also preserves season and part distinctions, so `Season 2` downloads do not silently sync against a different season with the same base title. -- When that safer fallback still cannot resolve a `show_id`, the failure now keeps the specific reason so logs and error messages are easier to diagnose. -- Existing watchlist entries from older versions are migrated into the `Watching` category automatically. +- Existing watchlist entries stay in their current category when a download succeeds. +- Missing entries are added to `Finished`. +- If ani-cli resolves a different `show_id`, the app can reconcile a unique title match instead of creating a duplicate finished row. -### Thumbnail Behavior +Thumbnail behavior: -Watchlist thumbnails use the local `/api/watchlist/thumb/...` route, not direct remote image URLs. +- Thumbnails are served from the local `/api/watchlist/thumb/...` route. +- Cached thumbnails are reused. +- Missing covers are warmed in small batches. +- Failed lookups back off before retrying. +- You can force `Reload` or use `Upload` when no thumbnail exists. -Current thumbnail behavior: +Refresh behavior: -- Cached thumbnails render from the local project cache when available. -- The page adds a fresh nonce to thumbnail URLs to avoid stale browser-cached misses. -- Missing visible covers are warmed up in small batches instead of one request per card all at once. -- The thumbnail warm-up API now requires `show_ids` to be a JSON array and accepts explicit boolean-style `force` values such as `true` and `false`. -- The page only retries an unresolved cover warm-up once per thumbnail state change during the current browser session, so silent watchlist polling does not keep hammering the same missing poster. -- If a show disappears while a thumbnail warm-up batch is running, that entry is skipped cleanly instead of failing the whole batch request. -- Thumbnail image tags only hit the local route for already-cached files, so opening the watchlist no longer triggers a per-card remote lookup burst. -- AnimeSchedule is tried first for cover lookup. -- Stale stored AnimeSchedule routes and AniDB IDs now fall back to fresh title-based lookups instead of aborting the whole thumbnail refresh attempt. -- If a season-labeled title fails as-is, lookup retries with simplified base-title queries. -- AniDB is used as a fallback when AnimeSchedule does not resolve a cover. -- AniDB fallback reuses the cached parsed title list directly to keep repeated lookups lighter. -- Failed automatic fetch attempts now start the retry window, so bad lookups back off instead of hammering AnimeSchedule and AniDB on every warm-up pass. +- Per-show refreshes are queued in the background. +- Queued refresh state survives restarts. +- Duplicate refresh queueing is avoided. +- Scheduled refresh runs log start, stop, success, and failure to stdout. +- `Refresh All` works one show at a time with a configurable delay. -### Watchlist Pagination +## Access Guard -Watchlist listing responses now clamp out-of-range page requests to the last available page before querying rows, so the returned `page` number and `items` stay consistent even when the browser or API client asks for a page past the end. +By default the app only serves loopback clients such as `127.0.0.1` and `::1`. -Per-card thumbnail tools: +To allow LAN or remote access: -1. `Reload` forces a fresh redownload attempt for that show's poster. -2. `Upload` appears when no thumbnail is currently available and lets you assign a local image manually. -3. Hovering the poster tile or card shows the current thumbnail source hint. +```sh +ANI_CLI_WEB_ALLOW_REMOTE=1 +ANI_CLI_WEB_REMOTE_TOKEN=choose-a-long-random-token +``` -Manual uploads are saved into the same local thumbnail cache used by automatic cover downloads. +Non-local clients must send one of: -### Completion Detection +- `Authorization: Bearer ` +- `X-AniCli-Web-Token: ` -Watchlist refresh still uses AllAnime, the same upstream source that `ani-cli` uses, to count which `sub` and `dub` episodes are actually available right now. +Browser bootstrap also works: -To decide whether a release track looks complete, `ani-cli-web` also checks AnimeSchedule for: +```text +http://YOUR-HOST:8421/?token=your-token-here +``` -- the anime's expected total episode count -- the upstream airing status such as `Finished` or `Ongoing` +The app stores the token in an HTTP-only cookie and redirects to the clean URL. -When AnimeSchedule reports a known total and `Finished`, the watchlist compares that total with the live AllAnime `sub` and `dub` counts: +## Download Queue -- `Sub` becomes ready when available sub episodes reach the expected total. -- `Dub` becomes ready when available dub episodes reach the expected total. +The queue is stored in SQLite and shown 10 jobs at a time. -If AnimeSchedule does not know the total yet, the watchlist keeps showing the plain available-count value without a completion highlight. +Available actions: + +- `Retry` +- `Cancel` +- `Remove` +- `Retry failed` +- `Remove finished` + +Downloads are staged first, then moved into a media-friendly layout: + +```text +ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4 +``` ## Runtime State -All runtime state is stored under: +All local state is stored under: ```text ani-cli-web/.ani-cli-web/ ``` -That folder currently holds: +Main contents: - `config.json` - `state.sqlite3` -- legacy `queue.json` migration input when present -- legacy `watchlist.json` migration input when present - `thumbnails/` - AniDB title cache data - staging files for active downloads -Legacy files from `~/.config/ani-cli-web/` and `~/.local/state/ani-cli-web/` are migrated into the project-local folder automatically on startup. -The earlier project-local `queue.sqlite3` filename is also migrated automatically into `state.sqlite3`. +Legacy state from older locations is migrated on startup. ## Configuration Environment variables: -- `ANI_CLI_BIN`: path or command name for `ani-cli`; defaults to `ani-cli` from `PATH`. -- `ANI_CLI_DOWNLOAD_DIR`: initial default download folder. -- `ANI_CLI_MODE`: initial mode, `sub` or `dub`. -- `ANI_CLI_QUALITY`: initial quality such as `best`, `1080`, or `720`. -- `ANI_CLI_WEB_HOST`: server host, default `127.0.0.1`. -- `ANI_CLI_WEB_PORT`: server port, default `8421`. -- `ANI_CLI_WEB_ALLOW_REMOTE`: allow non-loopback clients when set to `1`, `true`, `yes`, or `on`. -- `ANI_CLI_WEB_REMOTE_TOKEN`: required shared secret for non-loopback clients when remote access is enabled. -- `ANI_CLI_WEB_DEBUG`: enable debug logging when set to `1`, `true`, `yes`, or `on`. -- `ANI_CLI_WEB_STATE_ROOT`: override the project-local state directory path; useful for isolated test runs or custom storage locations. -- `UPDATE_ON_START`: when set to `1`, `true`, `yes`, or `on` in the container, runs `sudo ani-cli --update` before `ani-cli-web` starts. -- `USER_UID` and `USER_GID`: optional numeric UID/GID pair for running the container process as a specific host user/group so mounted files are created with that ownership. +- `ANI_CLI_BIN`: path or command name for `ani-cli` +- `ANI_CLI_DOWNLOAD_DIR`: initial default download folder +- `ANI_CLI_MODE`: initial mode, `sub` or `dub` +- `ANI_CLI_QUALITY`: initial quality such as `best`, `1080`, or `720` +- `ANI_CLI_WEB_HOST`: server host, default `127.0.0.1` +- `ANI_CLI_WEB_PORT`: server port, default `8421` +- `ANI_CLI_WEB_ALLOW_REMOTE`: allow non-loopback clients +- `ANI_CLI_WEB_REMOTE_TOKEN`: shared secret for remote access +- `ANI_CLI_WEB_DEBUG`: enable debug logging +- `ANI_CLI_WEB_STATE_ROOT`: override the project-local state directory +- `UPDATE_ON_START`: run `sudo ani-cli --update` in the container before startup +- `USER_UID` and `USER_GID`: run the container as a specific host user/group -Saved defaults from the UI are written into the project-local `config.json`. +Saved config fields include: -The saved config now also includes `watchlist_auto_refresh_enabled`, `watchlist_auto_refresh_minutes`, and `watchlist_refresh_delay_seconds`, which control the periodic background watchlist refresh schedule and the per-show pacing used by bulk refresh jobs. +- `watchlist_auto_refresh_enabled` +- `watchlist_auto_refresh_minutes` +- `watchlist_refresh_delay_seconds` + +## Testing + +Run the regression suite with: + +```sh +python3 -m unittest test_app.py +``` ## Notes -- Importing `app.py` no longer boots the runtime worker threads automatically; runtime setup is deferred until startup or first use. -- Importing `app_support.py` no longer migrates or creates runtime state on its own; migrations now happen during runtime bootstrap. -- Cheap routes such as the page shells, `/api/version`, `/api/dependencies`, and `/api/config` do not need to boot the full runtime before responding. -- The CLI startup path itself also stays lazy now; runtime bootstrap happens when the first runtime-backed request arrives. -- Watchlist and queue data are stored in the same project-local SQLite database file, `state.sqlite3`. -- Runtime folders such as `.ani-cli-web/`, `downloads/`, and Python bytecode cache directories at any depth are ignored by git. -- The Docker image clones both upstream repositories during build, so the app source inside the container comes from the configured git remotes rather than the local build context. -- Focused regression tests live in `test_app.py`, run against an isolated temporary state root, and can be started with `python3 -m unittest test_app.py`. +- Runtime boot is lazy. Importing `app.py` does not immediately start worker threads. +- Queue and watchlist data share the same SQLite database: `.ani-cli-web/state.sqlite3`. +- Runtime folders such as `.ani-cli-web/` and `downloads/` are ignored by git. +- The Docker image clones both upstream repositories during build.