[2026-09-22] eagle: personal/plans/watchlist-tmdb-missing-marker.md personal/tech/lampac-deployment.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-22 18:24:10 +06:00
1 parent d10bc1a5d0
commit a334332c5b
2 files changed
+382 -6

No files matched your search

+219 -3
View File
@@ -2,8 +2,8 @@
# Watchlist — автопоиск TMDB + маркер «нет в базе» (без ручных правок)
**Дата:** 2026-09-22
**Статус:** Шаги 1, 1.3, 2, 3 ВЫПОЛНЕНЫ И ПРОВЕРЕНЫ, оба репо запушены.
Осталось только: деплой на Кра́кен + имя в карточке из TMDB/КП.
**Статус:** ✅ Шаги 1, 1.3, 2, 3 ВЫПОЛНЕНЫ, ЗАДЕПЛОЕНЫ НА КРА́КЕН И ПРИНЯТЫ В UI.
Осталось: коммит правок в `lampac-scripts`, имя в карточке из TMDB/КП, порядок категорий в ленте.
**Связано:** [[personal/plans/lampac-watchlist-plugin]], [[family/projects/watchlist-automation]], [[family/projects/watchlist-sync]], [[personal/tech/lampac-deployment]]
---
@@ -257,6 +257,218 @@ Alex: *«В developer. Пуш на gitea.»* Репо на gitea (private, HTTP
---
## ✅ ПОСТ-ДЕПЛОЙНАЯ ИТЕРАЦИЯ (2026-09-22, поздний вечер)
### 🔴 Генератор НЕ ЧИСТИЛ кэш промахов — исправлено
Alex спросил прямо: *«че блядь значит чистка кэша обязательна?! скрипт блядь её делает?!»* —
и был прав: **не делал**.
```python
key = f"{title.lower().strip()}|{year or ''}"
if key in cache:
return tuple(v) if v else None # промах вернулся, в сеть не пошли
```
Инвалидации не было. Промах (`None`) писался **навсегда** → правка названия, не
меняющая ключ кэша (регистр, пробел, порядок слов), `❓` не снимала.
**Фикс:** `clear_failed_cache()` — вызывается в `main()` сразу после `load_cache()`.
Удаляет **все `None`**, успешные резолвы (~350) не трогает. Промахи (~50)
перезапрашиваются на каждом прогоне.
Залито на Кра́кен, сумма `c556c52b8cf4714686add4083c4afc09` (совпала с локальной).
Патч: `~/lampac-tmp/patch_autoclear_cache.py`, бэкап `.bak-cacheclear`.
⚠️ **Ручная чистка `clear_cache_kraken.py` больше не нужна** — это часть прогона.
Подробности: [[personal/tech/lampac-deployment]], раздел «Кэш промахов».
### Правки Alex в `.md`: он делает их САМ, скрипт подхватывает
Alex уточнил три названия — **добавил год, снял `❓` и подсказки-подбуллеты**:
| Было | Стало |
|---|---|
| `❓ Everything Everywhere All at Once 2021` + 2 подбуллета | `Everything Everywhere All at Once 2022` |
| `❓ Духless` + 2 подбуллета | `Духless 2012` |
| `❓ Бумбараш` + 2 подбуллета | `Бумбараш 1972` |
🔴 **Синк vault — почасовой.** Alex правит в Obsidian на Mac, на Кра́кен уезжает
через `sync-vault.sh` (крон `0 * * * *`). Дифф `.md` до синка **пустой** — прогонять
«после правок» нужно ПОСЛЕ синка, иначе генератор увидит старую версию.
Правки вручную гоняются: `ssh kraken 'bash ~/scripts/sync-vault.sh'`.
### Ответы на вопросы Alex (проверено `crontab -l` и кодом)
1. **Порядок в ленте — БАГ, не задумано.** `watchlist.yaml`: `displayindex: 1` —
самый низкий из всех (`rezka 10`, `kinobase 15`, `filmix 20`, `vokino 30`,
`eneyida 35`, `kinoukr 40`, `getstv 45`, `aniliberty 60`). «Мой список» должен
быть ПЕРВЫМ, но оказался ниже «Сейчас Смотрят» и «Рекомендуем посмотреть».
Эти два блока — **встроенные каталоги Lampa, не из `sites/`**, поэтому
`displayindex` их не сдвигает. `watchlist.yaml` в этой сессии НЕ правился.
Требует отдельного разбора.
2. **Маркеры — да, так.** `❓` = резолв не нашёл, не в каталоге. `🗓️` = не вышел,
не в каталоге. Без маркера = вышел и найден → в каталоге.
3. **Прогон после правок сработает** — при условии, что синк прошёл и кэш промахов
очищен (теперь автоматически).
4. **Radarr и лимит — это РАЗНЫЕ лимиты, не один.**
- `batch=2` (снижен с 5 после инцидента 2026-09-21, диск 100%) — сколько позиций
**уходит в Radarr на закачку** за прогон. Плюс `_free_gb()` — стоп при нехватке места.
- **Выдача в Lampa лимита НЕ имеет** — отдаётся весь `watchlist.json`.
- Каталог строится из `.md` → `watchlist.json`; **Radarr в отображении не участвует**.
- «Один скрипт в кроне» — неверно, их **пять** (см. таблицу ниже).
### Осталось
1. **Коммит в `lampac-scripts`** — на gitea лежит `1765c05`; **не закоммичены**:
фикс `.env`-пути, `clear_failed_cache()`, переделанный `mark_missing.py`.
Это единственный реально висящий технический долг.
2. **Имя в карточке из TMDB/КП** — «отображать как в tmdb/kp, ничего не укорачивать».
Сейчас генератор пишет `it["name"]` = название из `.md`.
3. **Порядок категорий в ленте** — разобраться со встроенными блоками Lampa.
4. **Доки** — обновлены (этот файл + [[personal/tech/lampac-deployment]]).
---
## ✅ ДЕПЛОЙ НА КРА́КЕН ВЫПОЛНЕН (2026-09-22, вечер)
### 🔴🔴 Главная находка деплоя: генератор на Кра́кене НИКОГДА не резолвил
`load_env()` искал `.env` **только по Mac-пути**:
```python
for p in (Path("/Users/admin/Developer/watchlist-sync/.env"),):
```
На Кра́кене такого пути нет → `TMDB_KEY` не найден → `[ERR] нет TMDB_KEY в .env` →
**каталог собирался на одних Кинопоиск-ссылках, без TMDB-ID и постеров.**
Это объясняет, почему до сессии в JSON не было TMDB-ID у половины позиций: дело было
не в слабом резолве, а в том, что резолв **вообще не запускался**.
**Фикс:** `load_env()` перебирает 5 мест (последний найденный по ключу побеждает),
печатает, какие файлы прочитаны:
```python
candidates = [
os.environ.get("WATCHLIST_ENV", ""),
"/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync/.env",
"/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/watchlist-sync/.env",
str(Path.home() / "Developer" / "watchlist-sync" / ".env"),
str(Path.home() / ".config" / "watchlist-sync" / ".env"),
]
```
Скрипт патча: `~/lampac-tmp/patch_envpath.py`. Бэкап: `.bak-envpath`.
### Боевой прогон на реальном `.md` (не тест)
```bash
ssh kraken "cd $H && python3 $H/scripts/gen_watchlist.py $W $H/wwwroot/watchlist.json"
```
| Метрика | Значение |
|---|---|
| позиций в каталоге | **373** |
| без TMDB-ID | **0** |
| films / new / cartoons / anime | 331 / 32 / 3 / 7 |
| постеров | 327 / 32 / 3 / 7 |
| `missing.json` | **30** |
| источники | Radarr 9, TMDB 343, автопоиск **2b: 17**, **2c: 4**, miss 30 |
Размеры: `watchlist.json` 102 456 Б, `missing.json` 1 239 Б.
### Что задеплоено на Кра́кен
| Что | Путь | Права | Сумма |
|---|---|---|---|
| генератор | `$H/scripts/gen_watchlist.py` | `kraken:kraken 600` | `3c7d4df3…` |
| маркер | `$H/scripts/mark_missing.py` | `kraken:kraken 600` | `43b3bb15…` |
| крон-скрипт | `~/scripts/watchlist-catalog.sh` | `root:root 755` | `17672d62…` |
| бэкап генератора | `$H/scripts/gen_watchlist.py.bak-20260922-175754` | | |
`$H` = `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac`
Суммы сверены `md5` локально и на Кра́кене — **совпали**. `py_compile` + `bash -n` — OK.
🔴 **Права критичны:** установка только через
`sudo install -o kraken -g kraken -m 600` — каталог `$H/scripts/` принадлежит
`kraken:kraken`, но `~/scripts/` — `root:root 755`. `cp` без sudo падает на
`Permission denied` (грабля: бэкап крона-скрипта из-за этого не сделался с первого раза).
### `watchlist-catalog.sh` — обновлён
Добавлен блок маркера между генерацией и перезапуском lampac:
```bash
python3 "$GEN" "$WATCHLIST" "$OUT" 2>&1 | tail -12 || { log "ERR: генерация упала"; exit 1; }
if [ -f "$MISSING" ] && [ -f "$MARK" ]; then
log "Простановка ❓ по missing.json"
python3 "$MARK" --missing "$MISSING" "$WATCHLIST" --apply 2>&1 | tail -8 \
|| log "WARN: маркер не проставился (не фатально)"
fi
docker restart lampac # сброс кэша Catalog
```
`MISSING=$LAMPAC/wwwroot/missing.json`. Нефатально для маркера — генерация важнее.
### Крон на Кра́кене — полная картина (проверено `crontab -l`)
```
0 1 * * * watchlist-nightly.sh process-thumbs → resolve → sync-down → sync-up
0 9 * * 0 watchlist-discover.sh новинки из KP (воскресенье)
0 2 * * * watchlist-cleanup.sh удаление просмотренного при диске >90%
30 2 * * * watchlist-catalog.sh JSON-каталог для Lampa ← ОБНОВЛЁН
0 * * * * sync-vault.sh синк vault
0 * * * * pgs-to-srt.sh (новая загрузка) — не watchlist
0 3 * * * pgs-to-srt.sh (вся библиотека) — не watchlist
```
⚠️ **«Один скрипт в кроне» — неверно, их пять по watchlist.** Разделение ролей:
`discover` **только пишет в `.md`**, в закачку не отправляет. Закачку отправляет
`sync-down` через **Radarr** (`radarr_add()` + `radarr_search()`, `sync_down.py:155,158`).
**Лимит есть:** batch снижен `5 → 2` после инцидента 2026-09-21 (диск 100%),
плюс проверка свободного места `_free_gb()`.
### Осталось открытым
1. **Коммит в `lampac-scripts`** — `mark_missing.py` (переделка) и фикс `.env`-пути
**ещё не закоммичены**. В gitea лежит `1765c05`, там `mark_missing.py` старой версии.
2. **Имя в карточке из TMDB/КП** — требование Alex «отображать как в tmdb/kp,
ничего не укорачивать». Сейчас генератор пишет `it["name"]` = название из `.md`.
3. **Порядок в ленте Lampa** — «Мой список» (`displayindex: 1`, самый низкий из всех)
оказался **ниже** встроенных блоков «Сейчас Смотрят» и «Рекомендуем посмотреть».
Встроенные каталоги Lampa не из `sites/`, поэтому `displayindex` их не сдвигает.
Требует отдельного разбора — правился ли `watchlist.yaml` в этой сессии: НЕТ.
### Приёмка Alex (частично)
Alex подтвердил: *«вижу фильмы в ленте, работают»* — то есть каталог в UI **виден
и играет**. Это закрывает приёмку пунктов 1–3 из плана.
Осталось: **имя в карточке** и **порядок категорий**.
### Порядок резолва — финальная реализация в коде
```python
# 1) Radarr по названию
# 2) TMDB по названию как есть
# 2c) kp_id_from_url(it["kp_href"]) -> kp_search_by_id() -> _docs_to_tmdb()
# 2b) tmdb_via_kp() с KP_SIM_THRESHOLD
# 3) miss -> missing.json
```
Функции: `kp_id_from_url()`, `kp_search_by_id()`, `_docs_to_tmdb()`, `tmdb_via_kp_id()`,
`tmdb_via_kp()`, `kp_search()`, `title_sim()`. Константы: `KP_BASE`, `UA`,
`KP_SIM_THRESHOLD = 0.75`, `KP_ID_IN_URL`.
`_docs_to_tmdb()` — общая логика для 2b и 2c. Параметр `_by_id` на документе
отключает проверку порога: **если фильм пришёл по kp_id, близость названия не важна —
id и есть доказательство.**
---
## Требование Alex (дословно)
> «Нет блядь, нахуя мне в плеере заглушки?! Нормальный автопоиск! Никаких БЛЯДЬ ручных
@@ -534,7 +746,11 @@ def tmdb_via_kp(title, year, tmdb_key, kp_key, cache):
| `~/Developer/lampac-scripts/scripts/mark_missing.py` | маркер ❓ по `missing.json` (переделан, `--missing`) |
| `~/Developer/lampac-scripts/tests/accept_step1*.py` | приёмки автопоиска (3/3) |
| `~/lampac-tmp/` | рабочая свалка + все патч-скрипты сессии (НЕ git) |
| Кра́кен: `lampac/scripts/gen_watchlist.py` | **ЗАДЕПЛОЕННАЯ старая версия** — ждёт обновления |
| Кра́кен: `lampac/scripts/gen_watchlist.py` | **ЗАДЕПЛОЕНА НОВАЯ ВЕРСИЯ** (`3c7d4df3…`, `kraken:kraken 600`), бэкап старой `.bak-20260922-175754` |
| Кра́кен: `lampac/scripts/mark_missing.py` | задеплоен (`43b3bb15…`), ставит `❓` по `missing.json` |
| Кра́кен: `~/scripts/watchlist-catalog.sh` | обновлён (`17672d62…`) — генерация → маркер → restart lampac |
| Кра́кен: `lampac/wwwroot/missing.json` | снимок нерезолвленных (30 позиций) |
| `~/lampac-tmp/patch_envpath.py` | фикс `load_env()` — путь `.env` для Кра́кена (КРИТИЧНО) |
| `~/Developer/watchlist-sync/watchlist_sync/resolver.py` | ЭТАЛОН cross-reference |
| `~/Developer/watchlist-sync/watchlist_sync/discover.py` | TMDB-фильтр (Шаг 3, `_resolvable()`) |
| `~/Developer/watchlist-sync/watchlist_sync/cli.py` | `discover` передаёт `TMDB_KEY` |
+163 -3
View File
@@ -14,6 +14,7 @@ tags:
related:
- '[[personal/plans/lampac-kraken]]'
- '[[personal/plans/lampac-watchlist-plugin]]'
- '[[personal/plans/watchlist-tmdb-missing-marker]]'
- '[[personal/tech/multistream-single-flow-limit]]'
- '[[family/how-to/kraken-network]]'
- '[[family/how-to/tv-luna-send]]'
@@ -82,6 +83,88 @@ related:
подстановка `$( … | sed … )` вырезается фильтром безопасности — питфолл №2 из
[[family/how-to/gitea-config.md]]).
## 🚀 Деплой генератора на Кра́кен (проверено 2026-09-22)
### 🔴🔴 Питфолл: `load_env()` искал `.env` только по Mac-пути
```python
# БЫЛО — генератор на Кра́кене не находил ключей вообще
for p in (Path("/Users/admin/Developer/watchlist-sync/.env"),):
```
Симптом на Кра́кене: `[ERR] нет TMDB_KEY в .env` → **каталог собирался на одних
Кинопоиск-ссылках, без TMDB-ID и постеров.** Никакой резолв не выполнялся, хотя
код для него был. Именно это объясняет, почему в JSON не было TMDB-ID у половины
позиций — не «слабый резолв», а его полное отсутствие.
**СТАЛО** — перебор 5 мест, печать прочитанных файлов в stderr:
```python
candidates = [
os.environ.get("WATCHLIST_ENV", ""),
"/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync/.env",
"/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/watchlist-sync/.env",
str(Path.home() / "Developer" / "watchlist-sync" / ".env"),
str(Path.home() / ".config" / "watchlist-sync" / ".env"),
]
```
Патч: `~/lampac-tmp/patch_envpath.py`. **Урок:** при деплое скрипта на другую
машину — первым делом проверять абсолютные пути к конфигам, а не логику.
### Установка: только `sudo install`, не `cp`
```bash
scp -q ~/Developer/lampac-scripts/scripts/gen_watchlist.py kraken:/tmp/gen_watchlist.py
ssh kraken "sudo install -o kraken -g kraken -m 600 /tmp/gen_watchlist.py \
$H/scripts/gen_watchlist.py"
```
`$H` = `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac`
🔴 **Права — источник немого провала.** `$H/scripts/` = `kraken:kraken`, но
`~/scripts/` = `root:root 755`. Простой `cp` в `~/scripts/` падает на
`Permission denied` (бэкап крона-скрипта так и не сделался с первого раза).
Скрипты генератора — `600 kraken:kraken`; крон-обёртки в `~/scripts/` — `755 root:root`.
**Проверка после заливки — три уровня, все обязательны:**
```bash
md5 -q <локальный файл> # сверка с Кра́кеном
ssh kraken "md5sum $H/scripts/gen_watchlist.py" # суммы должны совпасть
ssh kraken "python3 -m py_compile $H/scripts/gen_watchlist.py && echo OK"
ssh kraken "bash -n ~/scripts/watchlist-catalog.sh && echo OK"
```
### Что задеплоено (2026-09-22)
| Файл | Путь | Права | md5 |
|---|---|---|---|
| генератор | `$H/scripts/gen_watchlist.py` | `kraken:kraken 600` | `3c7d4df34ee20f84b92c1b4be46d8382` |
| маркер | `$H/scripts/mark_missing.py` | `kraken:kraken 600` | `43b3bb15e2aaad1ccb352d705990406c` |
| крон-скрипт | `~/scripts/watchlist-catalog.sh` | `root:root 755` | `17672d62285de5f33e0a89776ba34416` |
Бэкап прежнего генератора: `$H/scripts/gen_watchlist.py.bak-20260922-175754`.
### Боевой прогон (эталон для сравнения)
```bash
ssh kraken "cd $H && python3 $H/scripts/gen_watchlist.py \
/home/kraken/obsidian/family/documents/movies-watchlist.md \
$H/wwwroot/watchlist.json"
```
| Метрика | Значение |
|---|---|
| позиций | **373** |
| без TMDB-ID | **0** |
| films / new / cartoons / anime | 331 / 32 / 3 / 7 |
| постеров | 327 / 32 / 3 / 7 |
| `missing.json` | 30 |
| источники | Radarr 9 · TMDB 343 · автопоиск 2b 17 · 2c 4 · miss 30 |
`watchlist.json` 102 456 Б · `missing.json` 1 239 Б · карточек в `cards/` — 373.
## 🔎 Резолв позиций в каталоге — три уровня (2026-09-22)
Генератор ищет фильм по цепочке, от точного к эвристике:
@@ -109,6 +192,35 @@ related:
⚠️ Порог и `kp_id` — **числовые/машинные критерии**, не ручной словарь соответствий.
Инвариант «никаких ручных правок» не нарушен.
### 🔴 Кэш промахов — генератор чистит его САМ
`~/.lampac_tmdb_cache.json` (на Кра́кене) кэширует и успехи, и **промахи (`None`)**.
Проблема: промах писался **навсегда**. Если Alex правит название так, что ключ кэша
не меняется (регистр, пробел, порядок слов) — запрос в сеть **не уходит**,
возвращается старый `None`, и `❓` не снимается никогда. Плюс копятся `None`
по давно не используемым названиям.
**Решение — `clear_failed_cache(cache)` в генераторе**, вызывается в `main()` сразу
после `load_cache()`:
```python
cleared = clear_failed_cache(cache) # удаляет все None
if cleared:
print(f"[i] кэш: снято промахов {cleared} (будут перепроверены)", file=sys.stderr)
```
- Успешные резолвы (числовой TMDB-ID) **не трогаются** — они верны, а повторная
проверка ~350 записей на каждом прогоне бессмысленна.
- Промахи (~50) перезапрашиваются всегда — это ровно те позиции, ради которых
правки и делаются. Стоимость: секунды.
⚠️ **Ручная чистка больше не нужна.** До этого фикса приходилось гонять
`clear_cache_kraken.py` руками — теперь это часть прогона.
Патч: `~/lampac-tmp/patch_autoclear_cache.py`. Бэкап: `.bak-cacheclear`.
Сумма после заливки: `c556c52b8cf4714686add4083c4afc09`.
### `missing.json` и маркер ❓
Генератор пишет снимок нерезолвленных рядом с каталогом:
@@ -130,14 +242,35 @@ python3 scripts/mark_missing.py --missing <wwwroot>/missing.json <путь.md> [
## 🔄 Ночной крон на Кра́кене — что делает автоматика
**По watchlist заданий ПЯТЬ, а не одно** (проверено `crontab -l`, 2026-09-22):
```
0 1 * * * watchlist-nightly.sh process-thumbs → resolve → sync-down → sync-up
0 9 * * 0 watchlist-discover.sh новинки из KP (воскресенье)
0 2 * * * watchlist-cleanup.sh сносит просмотренное при диске >90%
30 2 * * * watchlist-catalog.sh JSON-каталог для Lampa
0 * * * * sync-vault.sh синк vault (не watchlist, но связан)
```
`~/scripts/watchlist-nightly.sh`, cron **01:00**:
```
sync-vault (pull) → process-thumbs --apply → resolve --recheck --apply
→ sync-down --apply --batch 2 → sync-up --apply → sync-vault (push)
```
`~/scripts/watchlist-catalog.sh`, cron **30 2 * * *** — генерация JSON + рестарт lampac.
`~/scripts/watchlist-cleanup.sh`, cron **0 2 * * *** — сносит просмотренное при заполнении диска.
### 🔴 Разделение ролей: кто пишет в `.md`, кто отправляет в закачку
| Скрипт | Что делает | Закачка |
|---|---|---|
| `discover` (вс 09:00) | **только пишет в `.md`** — добавляет новинки | ❌ ничего не отправляет |
| `sync-down` (в nightly) | шлёт в **Radarr**: `radarr_add()` + `radarr_search()` (`sync_down.py:155,158`) | ✅ отправляет, **но через Radarr**, не напрямую в torrent |
| `resolve --recheck` | резолвит позиции, **снимает маркер сам**, когда фильм найден | ❌ |
| `cleanup` (02:00) | удаляет файлы просмотренного при заполнении диска | ❌ |
| `catalog` (02:30) | генерация JSON + `mark_missing` + `docker restart lampac` | ❌ |
🔴 **Лимит закачки ЕСТЬ.** `--batch 2` — снижен с `5` после инцидента 2026-09-21
(диск 100%). Плюс проверка свободного места `_free_gb()` в `sync_down.py`.
«Проходит весь список без лимитов» — **неверно**, идёт по 2 позиции за прогон.
🔴 **`resolve --recheck --apply` снимает маркер сам**, когда фильм находится.
Это работа резолвера `watchlist_sync/resolver.py` — НЕ генератора каталога и НЕ ручная.
@@ -229,8 +362,35 @@ services:
WAF на префиксе `/catalog/` — **10 запросов/сек**.
### 🔴 Питфолл №1: первая категория игнорирует `format`
### ⚠️ Открытый вопрос: `displayindex` не поднимает «Мой список» выше встроенных блоков
Замечено Alex 2026-09-22: каталог watchlist в ленте идёт **после** блоков
«Сейчас Смотрят» и «Рекомендуем посмотреть». Это **не задумано**.
`watchlist.yaml`: `displayindex: 1` — **самый низкий** из всех каталогов:
| `displayindex` | Каталог |
|---|---|
| **1** | **watchlist («Мой список»)** |
| 10 | Rezka |
| 15 | Kinobase |
| 20 | Filmix, FilmixApp |
| 25 | KinoPub |
| 30 | VoKino |
| 35 | Eneyida |
| 40 | KinoUkr |
| 45 | GetsTV |
| 60 | AniLiberty |
«Сейчас Смотрят» и «Рекомендуем посмотреть» — **встроенные каталоги Lampa**,
их нет в `catalog/sites/`, поэтому `displayindex` их не сдвигает: Lampa держит
свои блоки отдельным списком.
**Что проверять при разборе:** доступен ли приоритет встроенных блоков в
`init.conf`/настройках Lampa, или встроенные всегда идут первыми.
Понижать `displayindex` ниже 1 бессмысленно — он уже минимальный.
### 🔴 Питфолл №1: первая категория игнорирует `format`
`ListController.cs:50` строит базовый URL **до** обработки `format`:
```csharp