- Веб-UI: под шапкой страницы загрузки — дата добавления и относительная давность («N дней назад»), как в карточках списка. - Баг: ссылка-dereferrer TVDB для фильма вела на /series/; теперь строится по типу запроса (/movie/ либо /series/). Тест + правка спеки metadata-match. - Telegram: заголовки уведомлений (готово/ошибка/рассинхрон) берутся из display_name — консистентно с веб-UI; сезон подтягивается автоматически. - Telegram: в сообщениях об ошибке — и заголовок, и #id загрузки (ULID) для быстрого поиска по логам. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
485 lines
38 KiB
Markdown
485 lines
38 KiB
Markdown
# Беклог
|
||
|
||
Единый список будущих задач по проекту: то, что уже решили сделать, и
|
||
идеи, которые ещё надо обдумать. Это не план реализации (он — в
|
||
[drafts/roadmap.md](drafts/roadmap.md)) и не источник истины: принятое и
|
||
реализованное переезжает в `docs/specs`/`docs/adr`.
|
||
|
||
Приоритет — грубая оценка «ценность / стоимость», не обязательство к
|
||
порядку. Спекулятивные пункты (ещё без решения «делаем») помечены
|
||
_(идея)_ — их сперва надо проработать.
|
||
|
||
## Высокий
|
||
|
||
### Проблема второго сезона
|
||
|
||
Если первый сезон сериала уже разложен, а мы добавляем второй/третий/…,
|
||
новый сезон должен лечь в **ту же** папку сериала, а не завести рядом почти
|
||
одинаковую вторую. Разбор ([drafts/logical-title-model.md](drafts/logical-title-model.md))
|
||
показал: проблема не в группировке, а в **сходимости папки** — папка каждый
|
||
раз печатается заново из выхода LLM, и совпадение `provider_id` не
|
||
гарантирует совпадение строки («Fargo» vs «Фарго», год сезона vs год
|
||
сериала). Отдельная сущность «тайтл» **не вводится**; решение — правило
|
||
сходимости при построении плана: при подтверждённом матче наследовать базу
|
||
папки от живых `file_link`'ов загрузок с тем же `(provider, provider_id)`,
|
||
игнорируя LLM-выход; якоря нет — папка из распознавания, как сейчас (первая
|
||
загрузка «печатает» имя).
|
||
|
||
- [ ] lookup живых ссылок по `(provider, provider_id)` через current recognition
|
||
- [ ] наследование базы папки (имя + год) при построении плана раскладки
|
||
- [ ] рассинхрон (несколько живых папок с одним матчем) → review, не молча
|
||
- [ ] тесты: сходимость, отсутствие якоря (свежая папка), смена провайдера
|
||
|
||
Связано: [drafts/logical-title-model.md](drafts/logical-title-model.md) §5.2,
|
||
[recognition.md](specs/recognition.md) (модель уверенности,
|
||
матч в базе), [jellyfin-layout.md](specs/jellyfin-layout.md) (папка
|
||
сериала с провайдер-id).
|
||
|
||
### Раздачи с докачиванием (слияние при повторном добавлении)
|
||
|
||
Свежий сериал раздают по мере выхода: торрент содержит 5 эпизодов из 10,
|
||
позже его перезаливают целиком, и пользователь добавляет раздачу повторно.
|
||
Решение проработано ([drafts/logical-title-model.md](drafts/logical-title-model.md)
|
||
§6.2): новая загрузка приходит в ту же папку за счёт правила сходимости, а
|
||
раскладка становится **merge** — доложить только недостающее. Существующие
|
||
пути не трогаем (never-overwrite, владение остаётся у старой загрузки),
|
||
новые кладём (владеет новая). Split-ownership сезона (серии поделены между
|
||
загрузками) принят как норма per-path модели; обе раздачи сидируют
|
||
независимо.
|
||
|
||
- [ ] в плане раскладки отличать «путь занят живой ссылкой того же матча»
|
||
(→ пропустить) от настоящей коллизии (→ review, как сейчас)
|
||
- [ ] merge-раскладка: существующее пропустить, недостающее доложить
|
||
- [ ] показать итог в карточке: сколько доложено, сколько уже было
|
||
- [ ] решить «слияние загрузок» при перезаливе той же вещи (одна строка
|
||
`download` + новый infohash vs новая загрузка) — открытый вопрос
|
||
черновика §10
|
||
|
||
Зависит от правила сходимости ([«Проблема второго
|
||
сезона»](#проблема-второго-сезона)) и выигрывает от ULID-идентичности.
|
||
|
||
Связано: [jellyfin-layout.md](specs/jellyfin-layout.md) (раскладка,
|
||
идемпотентность), [workflow.md](specs/workflow.md) (повторный прогон
|
||
загрузки).
|
||
|
||
### Удаление средствами jellybit («единое окно», path 2)
|
||
|
||
Распознавание **ручного** удаления (источник из qBittorrent / цель из
|
||
Jellyfin) и пометка рассинхрона уже сделаны: фоновая сверка по матрице
|
||
«источник × цель» → состояния `target_missing`/`orphaned`/`deleted`,
|
||
безопасный `undo` (не снимает последнюю копию, `nlink <= 1`), синхронный
|
||
preflight перед действиями. См. `openspec/specs/state-reconciliation/`,
|
||
[workflow.md](specs/workflow.md) → «Сверка с реальностью».
|
||
|
||
Осталось (path 2) — продолжение «единого окна»: удалять просмотренное
|
||
**из самого jellybit**, не идя руками в qBittorrent/Jellyfin. Решения из
|
||
разбора ([drafts/logical-title-model.md](drafts/logical-title-model.md)
|
||
§5.3, §6.4): «тайтл» — вычисляемая группа загрузок по
|
||
`(provider, provider_id)` / общей папке, без новой сущности; удаление
|
||
целиком — обход загрузок группы штатным undo; удаление раздачи из
|
||
qBittorrent — осознанный выход за инвариант «источник неприкосновенен»,
|
||
только по явному подтверждению (не случайному клику).
|
||
|
||
- [ ] удаление одной загрузки: снять её живые хардлинки (штатный undo,
|
||
`superseded` пропускаем, `nlink`-гард) + опц. удалить раздачу из
|
||
qBittorrent с файлами — с осознанным подтверждением
|
||
- [ ] вычисляемая группа «тайтл» в UI: состав сериала/фильма (загрузки,
|
||
сезоны, файлы) одним экраном
|
||
- [ ] удаление тайтла целиком: обход загрузок группы + опц. снос
|
||
опустевшей папки
|
||
- [ ] после полного удаления память о тайтле не остаётся (линза без
|
||
содержимого не нужна)
|
||
|
||
Связано: [drafts/logical-title-model.md](drafts/logical-title-model.md),
|
||
[ADR-2026-06-13-hardlinks](adr/ADR-2026-06-13-hardlinks.md),
|
||
[architecture.md](specs/architecture.md) → «Раскладка файлов»,
|
||
[workflow.md](specs/workflow.md).
|
||
|
||
### Ретеншн и очистка БД
|
||
|
||
Терминальные задачи (`done`/`cancelled`/`failed`/`reverted`), их попытки
|
||
`recognition` с сырыми ответами LLM и `metadata_candidate` копятся вечно —
|
||
со временем БД и список загрузок распухают и становятся нечитаемыми. Нужна
|
||
авточистка старше N дней (с настройкой в `[storage]` или `[worker]`) и/или
|
||
ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере
|
||
эксплуатации.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Хранилище» (таблицы
|
||
`download`/`recognition`/`metadata_candidate`/`file_link`), пакет `store`.
|
||
|
||
### Eval-харнес распознавания (корпус кейсов + метрика точности)
|
||
|
||
Распознавание — ядро продукта, но смена модели или правка промпта сейчас
|
||
вслепую: регрессий не видно. Нужен корпус размеченных кейсов (русские
|
||
релизы, аниме, сезон-паки, репаки, спецвыпуски) и прогон распознавания по
|
||
нему с метрикой точности (тип/название/год/нумерация). Тогда можно
|
||
сравнивать LLM-провайдеры и версии промпта по числам, а не на ощупь.
|
||
Прогон — отдельной командой (`jellybit eval` или тестом), на фикстурах, без
|
||
реального qBittorrent.
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (конвейер, модель
|
||
уверенности), пакет `recognize`.
|
||
|
||
### НФТ: масштаб до 100 одновременных загрузок (потолок — 1000)
|
||
|
||
Сейчас потолок по нагрузке нигде не зафиксирован: воркер, поллинг
|
||
qBittorrent, пул LLM-вызовов и запись в SQLite спроектированы «на глаз».
|
||
Записать в **нефункциональные требования** целевой ориентир — архитектура
|
||
держит до **100 одновременных загрузок** в работе (приём → распознавание →
|
||
раскладка), план-максимум — **1000**. Сама запись требования дешева и
|
||
высокоценна: она задаёт рамку для решений ниже по списку. Отдельно (уже
|
||
дороже) — аудит узких мест под эту цифру: одиночное соединение SQLite и
|
||
сериализация записи, конкурентность воркера и лимит параллельных
|
||
распознаваний, частота/стоимость поллинга и дедуп при наплыве.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Отслеживание
|
||
загрузки»/«Хранилище», пакеты `worker`, `store`, `qbt`, `llm`.
|
||
|
||
## Средний
|
||
|
||
### Словарь единого языка (ubiquitous language)
|
||
|
||
Свести термины домена в один глоссарий, чтобы пользователь, документация,
|
||
код и агент говорили на одном языке: загрузка, раздача, распознавание, матч,
|
||
кандидат, раскладка, источник/цель, хардлинк, ревью, переход состояния и
|
||
т.д. — русский термин, английский идентификатор в коде, краткое определение.
|
||
Сейчас наименования расходятся между спеками, UI и кодом, и в диалоге с
|
||
агентом приходится каждый раз сверять понятия. Глоссарий — источник истины
|
||
по именам; на нём же строится агент-ревьювер наименований (см.
|
||
[«Агенты-ревьюверы качества»](#агенты-ревьюверы-качества-наименования-архитектура-конвенции-стиль)).
|
||
|
||
Связано: [docs/conventions](conventions/README.md) (кросс-каттинг),
|
||
[architecture.md](specs/architecture.md) (домен), новый файл-глоссарий.
|
||
|
||
### Агенты-ревьюверы качества (наименования, архитектура, конвенции, стиль)
|
||
|
||
Набор узких сабагентов-ревьюверов поверх ревью-процесса из `CLAUDE.md`,
|
||
каждый со своей оптикой: соответствие наименований словарю единого языка,
|
||
соблюдение архитектурных границ (единое ядро/тонкие транспорты, инварианты
|
||
безопасности данных), конвенций (ошибки, логирование, конфиг, TZ), стиля
|
||
кода на высоком уровне и поиск дублирования. Запускаются как чекпоинт перед
|
||
`archive`/коммитом. Развивает ревью-процесс OpenSpec в сторону
|
||
воспроизводимых автоматических проверок, не заменяя человеческое ревью.
|
||
|
||
Связано: `CLAUDE.md` (ревью-процесс, конвенции),
|
||
[docs/conventions](conventions/README.md),
|
||
[«Словарь единого языка»](#словарь-единого-языка-ubiquitous-language).
|
||
|
||
### Сила совпадения кандидата и пересмотр распознавания/матчинга _(идея)_
|
||
|
||
Сейчас у кандидата метабазы нет метрики силы совпадения (`metadata_candidate`
|
||
хранит provider/id/title/year/url), а решение «авто vs review» — по правилу
|
||
«единственный сильный матч + валидация», не по числовой уверенности. Для
|
||
ревью это значит: список кандидатов нечем отсортировать/подсветить по
|
||
уверенности — берём порядок сбора. Идея — ввести на этапе матча **силу
|
||
совпадения кандидата** (точное совпадение названия+года vs частичное) для
|
||
сортировки и подсказки в UI. Шире — отдельно продумать **сам процесс
|
||
распознавания и матчинга**: границы «разбор LLM / поиск в базе / сверка»,
|
||
что храним у кандидата, как считаем и показываем уверенность. Требует
|
||
проработки перед реализацией.
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (модель уверенности),
|
||
[ADR-2026-06-13-auto-link-requires-db-match](adr/ADR-2026-06-13-auto-link-requires-db-match.md),
|
||
[review-ux.md](specs/review-ux.md) (выбор источника в ревью, реализовано),
|
||
[«Пересмотр набора capabilities»](#пересмотр-набора-capabilities-и-рефакторинг-спек).
|
||
|
||
### История переходов загрузки
|
||
|
||
Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто
|
||
инициировал — воркер, человек, сверка), а не только текущее состояние.
|
||
Сейчас по задаче виден лишь актуальный статус, а разбор «как мы сюда попали»
|
||
идёт по логам сервера. Отдельная таблица истории даёт лог переходов в
|
||
карточке/расширенной информации и фундамент для метрик длительности стадий.
|
||
Естественно ложится на собственный идентификатор загрузки.
|
||
|
||
Связано: детальный экран загрузки (`/download/{id}`) уже реализован — лог
|
||
переходов ложится в него;
|
||
[drafts/logical-title-model.md](drafts/logical-title-model.md) §5.4 (схема
|
||
`state_transition`, actor `worker|human|reconcile`),
|
||
[workflow.md](specs/workflow.md) (граф состояний),
|
||
[database.md](specs/database.md), пакеты `worker`, `store`.
|
||
|
||
### Машина состояний на go-библиотеке
|
||
|
||
Сейчас FSM реализована вручную в `worker`. Выбрать подходящую go-библиотеку
|
||
для описания воркфлоу/машины состояний и перевести переходы на неё — ради
|
||
декларативности, проверяемости переходов и единого места правды. Кандидаты
|
||
для оценки: `looplab/fsm`, `qmuntal/stateless` (и аналоги). Граф и переходы
|
||
уже формализованы — переносим один в один.
|
||
|
||
Связано: [workflow.md](specs/workflow.md) (текущий граф состояний).
|
||
|
||
### Привязка уведомлений к источнику в ботах (мульти-бот)
|
||
|
||
Уведомления и запросы подтверждения должен получать тот, кто прислал
|
||
загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней.
|
||
Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и
|
||
др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся
|
||
**единым для всех** и точкой правды по функциональности (боты — тонкие
|
||
адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и
|
||
идентификатор отправителя, маршрутизировать пинги по нему.
|
||
|
||
Связано: [review-ux.md](specs/review-ux.md) (разделение труда транспортов,
|
||
веб = точные правки), [architecture.md](specs/architecture.md) →
|
||
«Транспорты».
|
||
|
||
### Улучшения UI: показывать матч с записью метабазы
|
||
|
||
Web-сторона реализована: страница загрузки `/download/{id}` и экран ревью
|
||
показывают, **с какой именно записью** метабазы (TMDB/TVDB/IMDb) сматчилась
|
||
загрузка — провайдер, id и ссылку на запись. Осталось довести то же в
|
||
**Telegram**: в уведомлениях/подтверждениях показывать запись матча (название,
|
||
год, провайдер-id, ссылку), чтобы ошибочную привязку было видно и из бота.
|
||
Полный выбор источника в вебе уже реализован — см.
|
||
[review-ux.md](specs/review-ux.md).
|
||
|
||
Связано: [review-ux.md](specs/review-ux.md), [recognition.md](specs/recognition.md)
|
||
(матч в базе), [architecture.md](specs/architecture.md) → «Транспорты».
|
||
|
||
### Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки _(идея)_
|
||
|
||
Обычный случай сериальной раздачи — один сезон (его номер надо сразу видеть
|
||
глазами и сверять на ревью — под это сделана сводка сезонов в инфо-части, см.
|
||
`openspec/specs/review`). Но в редких заказах раздача бывает сложнее: **все
|
||
сезоны сериала разом**, **пак нескольких сезонов**, смешанная нумерация, вложенные
|
||
папки сезонов, разнобойные имена файлов. Сейчас `PlanFile.Season` задаётся на
|
||
каждом файле (мультисезон в принципе выразим), но целостно эти сценарии не
|
||
проработаны: как надёжно распознать многосезонную раздачу, как показать её на
|
||
ревью (сводка — лишь страховка, не полноценный разбор по сезонам), как разложить
|
||
и как это стыкуется со сходимостью папки и merge-докачиванием. Проработать
|
||
крайние случаи и решить, что поддерживаем явно, а что уводим в ревью как «сложную
|
||
раскладку».
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (сезон-паки, нумерация),
|
||
[jellyfin-layout.md](specs/jellyfin-layout.md) (раскладка сезонов),
|
||
[review-ux.md](specs/review-ux.md) (крайние сценарии, сводка сезонов),
|
||
[«Проблема второго сезона»](#проблема-второго-сезона),
|
||
[«Раздачи с докачиванием»](#раздачи-с-докачиванием-слияние-при-повторном-добавлении).
|
||
|
||
### Аниме с абсолютной нумерацией
|
||
|
||
Релизы аниме часто нумеруют серии сквозным числом (`#137`) без сезонов, а
|
||
Jellyfin ждёт `SxxEyy`. Нужен пересчёт абсолютной нумерации в сезон/серию —
|
||
надёжнее всего через TVDB (там есть absolute order). Отдельный крайний
|
||
случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E».
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (конвейер, сезон-паки),
|
||
[jellyfin-layout.md](specs/jellyfin-layout.md) (нумерация серий),
|
||
[review-ux.md](specs/review-ux.md) (крайние сценарии).
|
||
|
||
### Добавление торрентов файлом/ссылкой — «единое окно»
|
||
|
||
Поддержать источники помимо magnet: `.torrent`-файл и URL (отдаём их в
|
||
qBittorrent, без исходящих запросов на пользовательский URL — SSRF
|
||
исключён). Идеал — одно поле «единого окна»: кидаем туда текст или файл, а
|
||
сервис сам разбирает, что это (magnet / ссылка / .torrent / сообщение
|
||
бота), и заводит загрузку.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Транспорты»
|
||
(`source_type = magnet|torrent|url` уже в схеме), пакет `ingest` (сейчас
|
||
поддержан только magnet).
|
||
|
||
### Бэкап SQLite
|
||
|
||
`architecture.md` требует «бекапить data-том», но *как* — не описано. Без
|
||
понятной стратегии сбой или редеплой стирают всё in-flight состояние.
|
||
Зафиксировать решение и реализовать: периодический `VACUUM INTO` в
|
||
`/data/backups` по расписанию (с ротацией) либо потоковая репликация
|
||
(litestream). Лучше сделать, пока БД маленькая.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Деплой» (data-том,
|
||
«бекапить-и-не-терять»), пакет `store`.
|
||
|
||
### Глубокий healthcheck и статус зависимостей
|
||
|
||
`/healthz` проверяет только сам сервис. Если qBittorrent, LLM или метабаза
|
||
недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка
|
||
ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent
|
||
недоступен»), чтобы причина простоя была видна сразу.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Деплой» (healthcheck),
|
||
пакеты `qbt`, `llm`, `metadata`, `httpapi`.
|
||
|
||
### Обучение на правках человека (few-shot из прошлых ревью)
|
||
|
||
Когда человек поправил матч, тип или нумерацию — сохранять это как пример и
|
||
подмешивать похожие в будущие промпты. Системно повышает точность на «твоих»
|
||
трекерах и форматах имён без смены модели. Развитие идеи многоступенчатой
|
||
верификации, но дешевле: учимся на уже собранных `hint`/`override`.
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (конвейер, промпт),
|
||
[«Многоступенчатая верификация»](#многоступенчатая-верификация-привязки-идея),
|
||
[architecture.md](specs/architecture.md) → «Хранилище» (`hint`, `override`).
|
||
|
||
### Гейт авто-раскладки по `confidence`: спека vs код
|
||
|
||
Аудит спек↔код (2026-07-03) нашёл расхождение в модели уверенности.
|
||
Спека `recognition` (унаследовано из `recognition.md`) утверждает, что
|
||
самооценка LLM `confidence` — **вспомогательный сигнал, НЕ единственный гейт**:
|
||
при подтверждённом матче в базе + чистой структурной валидации + согласованности
|
||
сигналов авто-раскладка допускается. Код же (`internal/recognize/validate.go`,
|
||
`confidence < AutoThreshold`, дефолт 0.85) делает `confidence` **жёстким
|
||
блокирующим условием**: план с матчем и чистой валидацией, но `confidence` 0.5
|
||
уйдёт в review вопреки сценарию спеки. Нужно определиться, что правда: либо
|
||
признать порог `AutoThreshold` в спеке как легитимный гейт (скорее так — код его
|
||
осознанно ввёл конфигом), либо ослабить код. Заодно `AutoThreshold` как
|
||
конфигурируемый гейт спекой не описан.
|
||
|
||
Связано: `openspec/specs/recognition` (требование «Модель уверенности и решение
|
||
auto/review»),
|
||
[ADR-2026-06-13-auto-link-requires-db-match](adr/ADR-2026-06-13-auto-link-requires-db-match.md),
|
||
пакет `recognize`.
|
||
|
||
### Привязка внешних субтитров к серии (сериалы)
|
||
|
||
Аудит спек↔код (2026-07-03): спека `recognition` требует «внешние субтитры SHALL
|
||
привязываться к соответствующему видео». Для **фильма** это работает — раскладка
|
||
именует субтитр по базе видеофайла. Для **сериала** связь субтитр→конкретная
|
||
серия не выражена: в `PlanFile` (`internal/recognize`) нет поля привязки, и нет
|
||
логики спаривания VobSub `.idx`+`.sub`. Нужно смоделировать привязку субтитра к
|
||
эпизоду (поле на `PlanFile` или роль с указанием `season`/`episode`) и спаривание
|
||
`.idx`+`.sub`, либо — если поддержку откладываем — сузить формулировку спеки до
|
||
реального поведения.
|
||
|
||
Связано: `openspec/specs/recognition` (требование «Роли файлов на краях»),
|
||
[jellyfin-layout.md](specs/jellyfin-layout.md) (имена субтитров), пакеты
|
||
`recognize`, `layout`.
|
||
|
||
### Раздачи-копии диска (DVD/BluRay: VIDEO_TS/BDMV)
|
||
|
||
Иногда для очень редких фильмов скачивается не один видеофайл, а **полная копия
|
||
диска** — структура `VIDEO_TS/` (DVD: `VIDEO_TS.IFO`, `VTS_01_1.VOB`…) или
|
||
`BDMV/` (BluRay: `BDMV/STREAM/*.m2ts`, `index.bdmv`). Сейчас распознавание и
|
||
раскладка заточены под пофайловый разбор (один main-видеофайл фильма / серии
|
||
сериала), а тут «фильм» — это **каталог целиком**. Jellyfin такие раскладки
|
||
поддерживает (папка фильма с вложенным `VIDEO_TS`/`BDMV`), нам нужно: распознать,
|
||
что раздача — это образ диска (по наличию `VIDEO_TS`/`BDMV`), не пытаться
|
||
разбирать её по отдельным VOB/m2ts как серии, и разложить весь каталог диска
|
||
хардлинками в папку фильма Jellyfin (`Название (Год)/VIDEO_TS/…`). Крайний, но
|
||
реальный случай для редких изданий; частота низкая, поэтому в «Среднем».
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (роли файлов, что игнорируем),
|
||
[jellyfin-layout.md](specs/jellyfin-layout.md) (раскладка фильма, крайние
|
||
случаи), пакеты `recognize`, `layout`.
|
||
|
||
## Низкий
|
||
|
||
### Панель действий ревью вне htmx-свопа блока источника
|
||
|
||
При выборе источника одним кликом обновляется только блок источника
|
||
(`#source-block`) htmx-свопом, а нижняя панель действий (кнопка «Применить»,
|
||
завязанная на `HasLinks`) — вне блока и не обновляется до полной перезагрузки.
|
||
Практически не мешает (хардлинки только по явному «Применить»,
|
||
`Apply` без плана вернёт ошибку), но в краевом случае (источник с пустым
|
||
предпросмотром из-за коллизии) кнопка «Применить» может остаться/пропасть не
|
||
синхронно. Решение намечено в дизайне `review-unified-source-block`
|
||
(Risks/Trade-offs): обновлять панель `hx-swap-oob` из того же партиала.
|
||
|
||
Связано: `openspec/specs/review`, `openspec/specs/web-ui`, пакет `httpapi`.
|
||
|
||
### Мгновенные обновления через SSE
|
||
|
||
Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто
|
||
и работает, но с задержкой в интервал опроса и холостыми запросами.
|
||
Перевести динамический контент (прогресс загрузки, смена статуса, раздача)
|
||
на Server-Sent Events, чтобы обновления приходили почти мгновенно и без
|
||
лишнего поллинга. Поллинг работает, поэтому это улучшение, а не блокер; SSE —
|
||
один долгоживущий ответ на соединение, ложится на server-rendered UI без
|
||
тяжёлого фронтенда.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Транспорты»,
|
||
[review-ux.md](specs/review-ux.md), пакет `httpapi`.
|
||
|
||
### Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p)
|
||
|
||
По калибровке болей (2026-07-02,
|
||
[drafts/logical-title-model.md](drafts/logical-title-model.md) §6.3) —
|
||
**не боль**, из приоритета выпало. Сосуществование версий доступно уже
|
||
сейчас (Jellyfin multi-version, другой целевой путь), коллизия на тот же
|
||
путь штатно уходит в review. Явный replace (undo старого хардлинка → lay
|
||
нового → супересид владения путём) — отдельный change, если/когда станет
|
||
болью.
|
||
|
||
Связано: [«Раздачи с докачиванием»](#раздачи-с-докачиванием-слияние-при-повторном-добавлении),
|
||
[jellyfin-layout.md](specs/jellyfin-layout.md) (never-overwrite, коллизия).
|
||
|
||
### Многоступенчатая верификация привязки _(идея)_
|
||
|
||
Несколько раз извлекать данные из раздачи и контекста разными промптами,
|
||
искать в метабазах, затем сводить результаты в общий вердикт
|
||
(голосование/консенсус) — выше точность ценой нескольких вызовов LLM и
|
||
запросов к базам. Требует проработки: когда включать, как мерджить
|
||
расхождения, стоимость/латентность.
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (конвейер и модель
|
||
уверенности).
|
||
|
||
### Выбор из нескольких находок метабазы в Telegram
|
||
|
||
Когда распознавание даёт несколько подходящих кандидатов в метабазе,
|
||
предлагать их в Telegram списком (кнопки) для ручного выбора, а не молча
|
||
брать первый/лучший. Веб остаётся точкой точных правок (полный выбор
|
||
источника уже реализован — см. [review-ux.md](specs/review-ux.md)), бот —
|
||
быстрый выбор из готового короткого списка.
|
||
|
||
Связано: [review-ux.md](specs/review-ux.md) (боты — быстрые действия, веб —
|
||
точные правки), [recognition.md](specs/recognition.md) (кандидаты матча).
|
||
|
||
### Проверка свободного места перед copy-fallback
|
||
|
||
Когда хардлинк невозможен (`EXDEV`/`ENOTSUP`/…), `layout` копирует файл,
|
||
дублируя место на диске. На забитом диске это упрётся в полку посреди
|
||
раскладки. Перед копированием проверять доступное место и при нехватке
|
||
внятно уходить в `failed` с понятной причиной, а не падать на полпути.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Раскладка файлов»
|
||
(фолбэк-копирование), пакет `layout`.
|
||
|
||
### Кэш метабаз (и опционально LLM)
|
||
|
||
Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и
|
||
тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать
|
||
заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее
|
||
полезен, т.к. вход меняется подсказками).
|
||
|
||
Связано: [recognition.md](specs/recognition.md) (сверка с базой), пакеты
|
||
`metadata`, `llm`.
|
||
|
||
### guessit как сервис-спутник _(идея)_
|
||
|
||
`go-ptn` слабее питоновского `guessit`. Если точности пред-парса не
|
||
хватит — завернуть `guessit` в крошечный HTTP-сервис (один файл,
|
||
поставляется рядом с бинарём jellybit) и спрашивать его на шаге
|
||
пред-парса. Сохраняет «доставку копированием»: два файла вместо одного.
|
||
|
||
Связано: [recognition.md](specs/recognition.md) → «На будущее» (пред-парс).
|
||
|
||
### Завершение загрузки через webhook _(идея)_
|
||
|
||
Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд.
|
||
Альтернатива: «Run external program on torrent completion» в qBittorrent
|
||
дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом
|
||
qBittorrent. Решим по опыту эксплуатации.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Отслеживание загрузки»,
|
||
пакет `worker`.
|
||
|
||
### Авторизация веб-UI (на будущее)
|
||
|
||
Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей
|
||
(`http.trusted_subnets`) — как умеет qBittorrent. Если понадобится защита:
|
||
токен/Basic в самом приложении или вынос за reverse-proxy с
|
||
аутентификацией.
|
||
|
||
Связано: [architecture.md](specs/architecture.md) → «Транспорты» (доступ к
|
||
веб-UI), пакет `httpapi`.
|
||
|
||
### Современный Web-UI как PWA
|
||
|
||
Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое,
|
||
отзывчивое, удобное с телефона). Текущий server-rendered UI функционален,
|
||
поэтому это улучшение, а не блокер; большой объём работы.
|
||
|
||
Связано: [review-ux.md](specs/review-ux.md) (веб = точные правки),
|
||
пакет `httpapi`.
|