- Waiting plans (approval / disk space / automation off / queued) are
active-segment rows on /download-tasks/board, merged by created_at,
counted, with approve / cancel actions; ?plan= deep links and legacy
/downloads/plans routes redirect to the panel
- Download detail shows "why this download" (source, purpose, replaced
version, reasons); raw evaluation in tech info
- Failed / cancelled / expired plans appear in the entity download
history; rejected / revoked / expired approvals close draft plans
- New GET /acquisition-plans/{id}/why, POST /acquisition-plans/{id}/cancel
- User-facing copy no longer says 下载计划; glossary updated
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- sidebar: 概览 / 获取 / 整理 / 系统. Tasks moves to 系统; sites,
subscriptions and rules form the 获取 group (ACQUIRE_GROUP_TITLE in
nav.ts, the single place to rename it); standalone RSS nav item removed
- site detail gets an RSS tab with everything the RSS page had (create,
edit, enable, fetch now, fetch state) plus recent items; tabs and
header actions follow the site's capabilities
- old /console/sources links redirect to the site's RSS tab (?feed= looks
up the owning site) or the site list; task links to RSS point at sites
- site list shows and filters by type; add-site wizard starts with a type
step (PT still picks from the definition library; public BT / custom
RSS source only need name, URL and RSS URL)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- snapshots only for sites with list; feed polling only for RSS kinds;
user data / health checks only for sites with login; search site set
only includes searchable sites; live search / snapshot skip with
reason unsupported instead of hitting the site
- refresh-login / credentials need login, manual snapshot needs list (400)
- POST /sites accepts kind and rssUrl (site + feed in one transaction,
key generated from host); RSS belongs to a site: feeds created without
siteId go to the same-host custom RSS source site, siteId cannot be
cleared
- fix: secret name clash when a site gets a second default-named feed
- command palette regrouped; agent site.list returns kind and
capabilities; system prompt notes RSS lives under sites
Co-Authored-By: Claude Opus 5.5 <[email protected]>
site.kind: pt / public_bt / rss_feed / torznab. Each kind declares the
acquisition modes it may support (search, list, rss, detail, login,
userinfo); a site's effective capabilities = kind ∩ site definition.
torznab only defines TorznabSiteConfig and cannot be added yet.
Migration 0055: generic_rss sites become rss_feed, the rest pt; feeds
without a site are grouped by host into new custom RSS source sites
(cursor window and conditional headers cleared so the next fetch writes
releases). User DB read-only dry-run: 0 orphan feeds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- H&R becomes a quick-filter facet (yes/no/unknown, with counts) on search
results and the site resources tab; drop the old free/H&R selects, keep
size range and sort on the same row (mobile: drawer), single clear button
- legacy free/hr URL params migrate into quick filters; stored selections
keep the same localStorage key
- search timing and query terms move into TechInfo
- library episode versions: click the whole row to expand, versions listed
as sibling rows, details only under the selected version, raw values in
TechInfo
- slot comparison gaps get reason codes (matchGapText in contracts
reasons.ts): "可换成 2160p", "缺 HDR 版本"
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- board organize hint uses DEAI-2 reasonLine; add transfer_failed, blocked,
blocked_by_conflict, main_failed, not_imported_kind, target_root_not_managed,
download_removed to the reason table with tests
- download detail / plans tab: internal ids, raw codes and JSON moved into TechInfo
- library versions tech block switched to TechInfo (TODO(DEAI-2))
- copy: glossary terms (no 接管/候选), plan reason codes no longer shown raw
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- GET /download-tasks/board?state=active|completed: per-torrent dl/up speed,
ETA, seeds/leechs, downloader total speed (transfer/info), counts for both
groups, latest organize result (status, reason code, open decision)
- live qB data cached per instance for 2 s with in-flight coalescing
- panel: total speed line, 进行中 / 已完成 segments remembered locally,
4 s polling while the page is visible, mobile layout, "—" for zero speed
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Files page uses a wide layout (route handle layout:"wide"): no max width,
full-height directory tree docked next to the sidebar, resizable
(200-520px, remembered, double-click resets) and collapsible; mobile keeps
the tree in a drawer.
- GET /files/preview (admin console session only, same path guards as
download): text/NFO with encoding detection (BOM/UTF-16/UTF-8/GB18030/Big5),
subtitles first 200 cues (SRT/VTT/ASS/SSA, PGS timings), .torrent file
list/trackers (passkey masked)/infohash, image dimensions. Reads at most
1 MiB of text; .torrent capped at 4 MiB.
- Download also allows text files, .torrent and avif/gif.
- Research notes in docs/design/file-preview-research.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- domain versionSpecOf: file name + ffprobe + ingest quality -> version spec;
summarizeVersions; matchVersions (satisfied / missing / upgradeable, single target)
- GET /library/{id}/versions: per-season/episode versions, summaries, matches[]
- organize writes full spec into media_asset.delivery (specVersion 1)
- scripts/asset-spec-backfill.ts: dry-run by default, --probe local only, --apply
- web: season summary line, one-line spec per episode, N versions expand,
tech info details (TODO: DEAI-2 TechInfo), movie versions panel
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- compose: app gets a default NO_PROXY (UMT_NO_PROXY) listing compose
service names incl. qbittorrent / qbittorrent-nox / qb / transmission /
deluge / aria2, so a proxy override never swallows downloader traffic.
- runbook: proxy and test-qB override templates use the same list.
- Settings: one-line hint on library root "管理方式" (managed vs
register_only).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Takeover identify (item 2):
- Adopted tasks without source release / plan: on completion planImport
parses main video path (file + parent + grandparent dirs, like MP
MetaInfoPath) merged with the torrent name, then runs ID-2 matching via
identifyParsed (local recall + TMDB; trigger "takeover" may query TMDB).
auto -> bind/create entity, backfill download_task.media_entity_id and
organize right away; no pool entry, no identity decision.
- Failure / ambiguity: organize_identity decision with evidence and
options: candidates, select entity (local / TMDB), provisional entity,
dismiss.
Decision closure (item 3):
- decision-hooks registry: the decisions resolve route runs the owning
module's executor before closing; errors keep the decision open.
- organize_identity: backfill entity, freeze manual plan, enqueue run.
- organize_blocked with target_exists: replace (verify + trash old) /
keep both (rename "<name> (n)" with sidecars) / skip.
- Successful organize closes both organize_identity and organize_blocked
of the same download task (resolution manual_organize), also for manual
path organizes matched to download tasks via path mappings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Migration 0053: download_task.media_entity_id (FK, set null on delete),
backfilled from plan slot / source release / latest organize record.
- Submitted and adopted tasks carry the entity; entity merge follows it.
- GET /library/{id}: downloads also matched by dt.media_entity_id (adopted
takeover tasks), plus organizationRecords (incl. manual path organizes);
library list / inventory counts use it too.
- Web detail: "接管" marker and a list of records without a download task.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- domain release-facets: bucket resolution / codec / release type / site / promotion / HDR /
release group / season / episode; OR within a dimension, AND across; disjunctive counts
- GET /search/releases and GET /sites/{id}/releases accept qf and return facets; site list
facets come from local source_release rows only (no site requests), paging stays consistent
- web QuickFilterBar shared by search results and site resources tab: per-dimension popover
(bottom sheet on phones) with counts, removable tags, clear all, localStorage memory per page
- runbook: quick filter params and bucket definitions
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adapters (pure functions + tests):
- detail-description: strip HTML/BBCode residue, drop site template blocks and
ad/signature lines (configurable rules), parse PT info blocks (◎ / 【】 / 『』 /
「键:」) into infoBlock and body paragraphs
- mediainfo: MediaInfo and BDInfo (quick summary + full report) into a summary
of container/duration/bitrate/size, video (HDR/DV), audio and subtitle tracks
- file-tree: file list into a flattened directory tree with sizes and
main/sample/subtitle/extra roles
- formatDetailPage combines them; the server computes it when reading the
cached detail page, so existing caches benefit without a refetch
Contracts: SiteReleasePage gains infoBlock, paragraphs, media, fileTree.
Web: drawer now reads top-down: meta row (promo/H&R, size, peers, upload time,
IMDb/TMDB/豆瓣 tags) → 资料 → 简介 (collapsible) → 技术规格 (raw folded) →
截图 grid with keyboard lightbox → 文件 tree; inventory overlay moved into the
fixed footer next to 下载.
Fixtures: 6 desensitized samples from the local cache (site.detail and RSS
descriptions) plus synthetic multi-track MediaInfo, BDInfo, bracket blocks and
directory file lists.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- metadata.includeSpecials (default true): full refresh and the language
backfill fetch TMDB season 0 (specials) when present, like MP; season 0
stays out of the episode-total ledger
- refreshMetadata promotes provisional season/episode units that match the
TMDB season/episode layout to confirmed (source=tmdb), keeping ids and all
references; non-matching units stay provisional
- content_unit.source (migration 0053): release / file / tmdb / user
- duplicate units (same entity/kind/season/episode/content type/segment) are
merged as a refresh step and via `scripts/identity-cleanup.ts
--merge-duplicates` (dry-run by default); keeper = most referenced, then
confirmed, then earliest; references auto-discovered via FKs and repointed
- ensureUnits takes a per-unit advisory xact lock to stop concurrent lazy
creation from producing duplicates
- runbooks updated; user DB read-only dry-run: 征途 S01E09/E10 4 units each
-> 1 each (6 removed), not applied
Co-Authored-By: Claude Opus 5.5 <[email protected]>