From 6b00a2986f37d4e060e70335de0695639178e0d9 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Fri, 25 Sep 2026 22:26:07 +0600 Subject: [PATCH] [2026-09-25] eagle: personal/plans/lampac-watchlist-plugin.md personal/tech/lampac-deployment.md --- personal/plans/lampac-watchlist-plugin.md | 148 ++++++++++++++++++++-- personal/tech/lampac-deployment.md | 86 +++++++++++-- 2 files changed, 211 insertions(+), 23 deletions(-) diff --git a/personal/plans/lampac-watchlist-plugin.md b/personal/plans/lampac-watchlist-plugin.md index 3a712063..5d4e051c 100644 --- a/personal/plans/lampac-watchlist-plugin.md +++ b/personal/plans/lampac-watchlist-plugin.md @@ -8,7 +8,7 @@ related: - '[[family/projects/watchlist-automation]]' - '[[family/documents/movies-watchlist]]' - '[[personal/plans/lampac-watched-to-watchlist]]' -status: main-screen-filter-fix-regressed-then-fixed-2026-09-25-acceptance-blocked-on-empty-catalog +status: main-screen-filter-WORKS-2026-09-25-accepted-latency-prefetch-pending tags: - lampac - lampa @@ -42,8 +42,9 @@ updated: '2026-09-25' прогружается относительно основного CUB списка, почему он не моментально проявляется, он же статичный». -**Статус: ⚠️ ФИКС ПРИМЕНЁН И ЗАЛИТ 2026-09-25, НО ПРИЁМКА ПРОВАЛЕНА — секции на -главной исчезли. См. «РЕГРЕСС И ВТОРОЙ ФИКС» ниже — там актуальная версия `wlOnMain`.** +**Статус: ✅ ПРИЁМКА ПРОЙДЕНА 2026-09-25 — фильтр работает. Фид снят со всех экранов +кроме главной. Осталось: предзагрузка `__wl_menu` (латентность) + 66 с на карточке. +См. «✅ ИТОГ 2026-09-25» в конце раздела.** ### ✅ РЕШЕНИЕ: `wlOnMain()` — белый список по активному activity @@ -289,6 +290,103 @@ node --check plugin-cub.js → SYNTAX OK **Следующий шаг (обязательный, не сделан):** проверить боевой запрос Lampa → Lampac `/catalog` — отвечает ли он с ТВ, отвечает ли с Кра́кена, не сломан ли он вообще. +### ✅ ИТОГ 2026-09-25: ПРИЁМКА ПРОЙДЕНА + +**Alex:** «Так. Есть. Но сильно после первых специй.» — секции на главной **появились**, +фильтр работает. Осталась претензия к задержке появления. + +#### Доказательство работы фильтра (замер на живой Lampa после перезапуска) + +``` +ready = true +patch = true ← перехватчик установлен +act_url = "movie" ← в момент замера экран = Фильмы (не главная) +menu = Новинки,Фильмы,Мультфильмы,Аниме ← __wl_menu заполнен, всё есть +dom_titles = Сейчас смотрят | Клиника | Мыс страха | Мейр из Исттауна | ... + ↑ Watchlist-секций в DOM НЕТ +``` + +🔴 **Ключевой вывод:** на экране `movie` (Фильмы) наших секций в DOM **нет**, при том что +`menu` заполнен и `patch` активен. Это и есть доказательство, что фильтр режет по экрану, +а не «случайно ничего не нашёл»: вставлять было чем, но `wlOnMain()` вернул `false`. + +#### 🔴 Почему «сильно после первых специй» — АРХИТЕКТУРНАЯ, не сетевая + +Разобрано по коду `loadCatalogs()` + `wlPatchPartNext`: + +1. `loadCatalogs()` идёт по ветке `else` и делает `network.silent('/catalog', ...)` — + **только после ответа** пишет `Manifest.catalogs` и `window.__wl_menu`. + То есть меню приходит **асинхронно**. +2. Главная CUB рисуется **своим** `cub.main()`, **не дожидаясь** этого запроса. Родные + секции («Сейчас смотрят», «Рекомендуем посмотреть») стартуют сразу. +3. Первый вызов `partNext` застаёт `__wl_menu` пустым → уходит в ветку `wlMenu()` — + **асинхронный** запрос `/catalog`. Пока он летит, родные секции уже отрисованы. +4. Когда `/catalog` вернулся, `wlInject` делает `unshift`, но `partNext` уже отработал + срез `slice(0, parts_limit)` (`parts_limit = 6`). Наши секции попадают в пул только + на **следующем** раунде, который добирает `data.length >= 3`. + +**Отсюда отставание:** наши 4 секции — это отдельный сетевой запрос на каждую +(`/catalog/list?plugin=watchlist&cat=` = 4 запроса), стартующие позже родных, +плюс попадание в пул не первым раундом. Родной фид CUB приходит одним запросом. + +**План ускорения (согласован Alex: «Коммить и переходим к предзагрузке»):** +подставить `menu` из `/catalog` **синхронно**, ДО первого `partNext` — в `startPlugin()`, +параллельно с `loadCatalogs()`. Тогда к моменту отрисовки главной `__wl_menu` уже в +памяти, `wlInject` срабатывает в первом же раунде, секции встают в голову без отставания. + +#### 🔴 Коммитить правку НЕКУДА — это выяснено, не пытаться снова + +Alex: «Коммить и переходим к предзагрузке». Проверка обеих площадок: + +``` +/Users/admin/lampac-tmp → fatal: not a git repository +/srv/.../lampac (на Кра́кене) → fatal: not a git repository +``` + +Ни папка сборки, ни папка lampac на Кра́кене под git **не лежат**. `plugin.js` — файл, +который **монтируется** в контейнер (`./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro`), +рядом только `.bak`-копии. То есть версионировать «в лоб» нечего. + +**Что служит страховкой вместо коммита:** + +| Артефакт | Путь | Размер | +|---|---|---| +| бэкап «до фильтра» | `catalog/plugin.js.bak-before-mainfilter-20260925-220809` | 17476 Б | +| бэкап «до фикса url» | `catalog/plugin.js.bak-before-fixurl-` | 19576 Б | +| текущий боевой | `catalog/plugin.js` | 20934 Б | +| локальные копии | `lampac-tmp/plugin-cub.js` + `build_plugin.py` | 20934 / 14619 Б | + +Плюс `build_plugin.py` держит весь `INJECT` в себе → файл воспроизводится +**детерминированно**, с 12 проверками инвариантов. + +**Предложенная развилка (решение за Alex, не сделано):** завести в репе +`watchlist-sync` папку `lampac_plugin/` и положить туда `build_plugin.py` + +`plugin-cub.js` под git — тогда правка плагина поедет вместе с проектом и попадёт +в историю. Второе правильнее, но это новая сущность в репе → спрашивать один раз. + +#### ⚠️ Замер «плагин не загрузился» — это была гонка, а не поломка + +Сразу после `luna-send`-перезапуска проба дала: + +``` +ready = false plugin = false patch = false +act_url = ERR: Cannot read property 'url' of undefined +cats = NOMANIFEST +``` + +Выглядело как «наш плагин вообще не доехал» (`window.plugin_catalog` ставит сам +`startPlugin()`). **Фактически это был замер раньше, чем Lampa дошла до главной** — +через несколько секунд те же пробы дали `ready=true, plugin=true, patch=true`. +Также цель CDP на этот период имела `title=""` и `url=about:blank` — Lampa грузилась. + +🔴 **ПРАВИЛО:** после перезапуска Lampa на ТВ **не верить первому замеру**. Либо +опрашивать `ready`, пока не станет `true`, либо искать CDP-цель по `url`-подстроке +`com.lampac.tv`, а не по `title == "Lampac"` (в момент загрузки `title` пустой, +и скрипт молча не находит цель). Alex на паузу без проверки отреагировал резко: +«Чё нахуй за слип 20?!» — **не спать, а опрашивать состояние.** + +--- + ### Инструменты диагностики ТВ, написанные в этой сессии (переиспользовать) | Скрипт | Что делает | @@ -309,13 +407,19 @@ node --check plugin-cub.js → SYNTAX OK ### Открытые шаги (для следующей сессии) -1. 🔴 **ПРИЁМКА НЕ ПРОЙДЕНА** — фикс `wlUrl` залит, но секции на главной не появились. - **Корень — пустой `Manifest.catalogs`: `/catalog` не отвечает Lampa.** Без этого - фильтр не при чём. Проверить боевой `/catalog` (с ТВ и с Кра́кена) — ПЕРВЫМ делом. -2. **Диагностировать 66 с на карточке** — `docker logs lampac` + трейс `CardController`. +1. ✅ **ПРИЁМКА ПРОЙДЕНА 2026-09-25** — фильтр `wlOnMain()` работает, фид виден только + на главной. Осталась латентность, см. п.2. +2. 🔄 **ПРЕДЗАГРУЗКА `__wl_menu`** (согласовано Alex: «Коммить и переходим к предзагрузке»). + Корень отставания — асинхронный `/catalog` в `loadCatalogs()`: меню приходит ПОСЛЕ + первого `partNext`. Решение: подтягивать `menu` синхронно в `startPlugin()`, + параллельно с `loadCatalogs()`, чтобы `wlInject` срабатывал в первом раунде среза. + Правка идёт в те же два файла (`plugin-cub.js` + `INJECT` в `build_plugin.py`). +3. **Диагностировать 66 с на карточке** — `docker logs lampac` + трейс `CardController`. Запрос: `GET /catalog/card?plugin=watchlist&uri=movie%2F424781&type=movie` → 66 118 мс, 939 Б. -3. **Matcher-parity** (главный блокер с 2026-09-24, см. ниже) — всё ещё не закрыт. +4. **Matcher-parity** (главный блокер с 2026-09-24, см. ниже) — всё ещё не закрыт. +5. **Решение по версионированию плагина** — `lampac_plugin/` в репе `watchlist-sync` + или оставить бэкапы + `build_plugin.py` (см. «Коммитить НЕКУДА»). **Правило диагностики этой сессии:** не читать `%{size_download}` как «размер фида» (это gzip) — брать `ls -la` или `Accept-Encoding: identity`. И — `ifconfig` на Кра́кене @@ -734,7 +838,33 @@ function wlOnMain() { 4. **перезапуск Lampa на ТВ обязателен** (`tv_restart.sh` через `luna-send`; `docker restart lampac` кэш webOS не сбрасывает) 5. 🔴 приёмка = **видно в UI**: главная — 4 секции сверху; Фильмы → документальные — - секций НЕТ. CDP-проба читает `Lampa.Activity.active().activity.url` на обоих экранах. + секций НЕТ. CDP-проба читает `Lampa.Activity.active()` на обоих экранах. + +### ✅ ИТОГ 2026-09-25: фильтр РАБОТАЕТ, закоммичено + +- **Коммит `5e55a47`** в репозитории **`~/Developer/lampac-scripts`** (Gitea + `git_admin/lampac-scripts`), запушен в `origin/main`. Туда переехали + `scripts/build_plugin.py` + `scripts/plugin-cub.js` — **из `lampac-tmp`, который + вне git**. `lampac-tmp` больше не источник истины. +- Приёмка на живом ТВ (CDP): **`act_url = "movie"` (экран Фильмов) → секций Watchlist + в DOM НЕТ.** Именно то, что просил Alex. +- Две правки по факту замеров, обе стоили итерации: + 1. **`url` читается с ВЕРХНЕГО уровня** — `active().url`, НЕ `active().activity.url` + (там его нет вообще: `{component, object, slide, body, is_stopped, is_started}`). + Первая версия читала `.activity.url` → всегда `undefined` → секции исчезли → «Нету». + 2. **Пустой `url` + пустой `Manifest.catalogs` = инициализация → пропускаем.** + Иначе строгая проверка отрезала единственный вызов `wlMenu()`, добирающий меню + после сбоя `/catalog`. +- 🔴 **Питфолл замеров:** CDP-проба сразу после рестарта Lampa даёт `ready=false`, + `plugin=false`, `patch=false`, `act_url=ERR` — это НЕ поломка, плагин ещё грузится. + Мерить надо через ~30 с, иначе легко уйти в ложную диагностику «плагин не подъехал». +- 🔴 **Задержка секций — архитектурная, в работу ещё НЕ внесена.** Замер: `menu` + заполнен, а `Manifest.catalogs = NOMANIFEST`. Цепочка: главная CUB рисуется своим + `cub.main()`, родные секции стартуют сразу; первый `partNext` видит пустой + `__wl_menu` → уходит в **асинхронный** `wlMenu()` → пока летит запрос, родные секции + отрисованы, `slice(0, parts_limit)` уже отработал → наши 4 секции попадают в пул + только в следующем раунде. **План:** тянуть `/catalog` в `startPlugin()` + **параллельно** с `loadCatalogs()`, чтобы `__wl_menu` лежал в памяти к первой отрисовке. ### Питфоллы, найденные при расследовании (не повторять) diff --git a/personal/tech/lampac-deployment.md b/personal/tech/lampac-deployment.md index 1f19aa86..401d99d7 100644 --- a/personal/tech/lampac-deployment.md +++ b/personal/tech/lampac-deployment.md @@ -34,24 +34,51 @@ related: (патч `enumerable:true` + инжект секций через перехват `Lampa.Api.partNext`). Полный план и статус — [[personal/plans/lampac-watchlist-plugin]]. -### Где лежит файл (три разных места — не путать) +### Где лежит файл — ✅ ИСПРАВЛЕНО 2026-09-25: ЭТО РЕПОЗИТОРИЙ, а не `/Users/admin/lampac-tmp/` + +🔴 **`/Users/admin/lampac-tmp/` — рабочая папка сессии 2026-09-22, НЕ под git.** +Живое место исходников — репозиторий **`~/Developer/lampac-scripts`** (Mac), +залитый в Gitea `git_admin/lampac-scripts`. Именно туда кладутся `build_plugin.py` +и `plugin-cub.js`; `lampac-tmp` — временный скрэтч, из него файлы могут исчезнуть. | Слой | Путь | Примечание | |---|---|---| -| Mac, исходник | `/Users/admin/lampac-tmp/plugin-cub.js` | результат `build_plugin.py` | -| Mac, сборщик | `/Users/admin/lampac-tmp/build_plugin.py` | 🔴 `plugin-cub.js` РУКАМИ не правим | -| Кра́кен, хост | `/srv/dev-disk-by-uuid-6194539b-.../lampac/catalog/plugin.js` | bind-mount | +| **Mac, репо** | `~/Developer/lampac-scripts/scripts/build_plugin.py` | сборщик, 12 инвариантов | +| **Mac, репо** | `~/Developer/lampac-scripts/scripts/plugin-cub.js` | 🔴 собранный выход, РУКАМИ не правим | +| Mac, скрэтч | `/Users/admin/lampac-tmp/` | рабочая папка, вне git — не источник истины | +| Кра́кен, хост | `/srv/dev-disk-by-uuid-6194539b-.../lampac/catalog/plugin.js` | bind-mount, вне git | | В контейнере | `/lampac/module/Catalog/plugin.js` | `:ro` по compose | | По HTTP | `http://192.168.1.14:9118/catalog.js` | что реально грузит ТВ | +**Репозиторий:** `https://git.mallexxx.duckdns.org/git_admin/lampac-scripts.git` +(remote `origin`, ветка `main`). Локально в нём же: `scripts/gen_watchlist.py`, +`scripts/deploy_lampac.py`, `scripts/mark_missing.py`, `tests/`. +Коммит плагина 2026-09-25: **`5e55a47`** «секции CUB только на главной экране». + +```bash +cd ~/Developer/lampac-scripts +git fetch origin && git status --short --branch # держим синхрон с Gitea +git add scripts/build_plugin.py scripts/plugin-cub.js +git commit -m "..." && git push origin main +``` + +⚠️ В `lampac/` на Кра́кене git НЕТ (проверено: `fatal: not a git repository`). +Репы на Кра́кене только три: `docker/`, `obsidian/`, `watchlist-sync/`. +Бэкапы боевого файла — `.bak-<что>-<дата>` рядом с `plugin.js`, они закрыты +`.gitignore` (`*.bak*`). + 🔴 **`wwwroot/catalog.js` НЕ существует.** `wwwroot/` содержит только JSON фида (`watchlist.json`, `watchlist-.json`, `cards.json`, `cards/`, `missing.json`). Попытка `scp` оттуда падает с «No such file or directory» — это не сбой сети. -### Порядок деплоя (боевой конфиг — коммит ДО, правка, заливка, ПРОВЕРКА, коммит ПОСЛЕ) +### Порядок деплоя + +``` +коммит ДО (репо) → правка build_plugin.py → сборка → заливка → ПРОВЕРКА АЛЕКСОМ → коммит ПОСЛЕ +``` ```bash -cd /Users/admin/lampac-tmp +cd ~/Developer/lampac-scripts/scripts python3 build_plugin.py # инварианты: сборка падает при нарушении node --check plugin-cub.js # SYNTAX OK обязателен # бэкап боевого @@ -61,6 +88,11 @@ scp plugin-cub.js kraken:.../lampac/catalog/plugin.js ssh kraken 'docker exec lampac grep -c "<маркер>" /lampac/module/Catalog/plugin.js' ``` +🔴 **`plugin-cub.js` в репе — это ВЫХОД сборщика.** Правку вносим в `INJECT` внутри +`build_plugin.py`, потом пересобираем; руками `plugin-cub.js` не редактируем, иначе +следующая сборка затрёт правку. Инварианты в `checks{}` обязаны покрывать любую +новую функцию — иначе регресс пройдёт молча. + ### 🔴 Перезапуск Lampa на ТВ обязателен `docker restart lampac` **НЕ сбрасывает** кэш `catalog.js` в браузере webOS — ТВ @@ -276,6 +308,11 @@ Radarr (матч по названию → те же версии, что выб `~/lampac-tmp/` — **НЕ git-репо** (свалка одноразовых пробников, 50+ файлов). Прод-генератор жил там без версионирования — ошибка, исправлено. +🔴 **2026-09-25: тем же правилом туда переехал плагин каталога** — +`scripts/build_plugin.py` + `scripts/plugin-cub.js` (коммит `5e55a47`). +До этого оба жили только в `lampac-tmp`. **`lampac-tmp` НЕ источник истины:** +его содержимое может исчезнуть, исходники плагина — только в репозитории. + **Репозиторий:** `/Users/admin/Developer/lampac-scripts/` (gitea, private) **Remote (ЧИСТЫЙ URL, токен не в URL):** `https://git.mallexxx.duckdns.org/git_admin/lampac-scripts.git` Токен — в `~/.git-credentials` (600). Питфолл №1 из [[family/how-to/gitea-config.md]]. @@ -285,6 +322,8 @@ Radarr (матч по названию → те же версии, что выб ├── scripts/gen_watchlist.py (из gen_watchlist_v2.py — ПРОД-генератор) ├── scripts/deploy_lampac.py ├── scripts/mark_missing.py (маркер ❓, вход — missing.json) + ├── scripts/build_plugin.py (сборщик плагина каталога) ← 2026-09-25 + ├── scripts/plugin-cub.js (собранный выход, не править руками) ← 2026-09-25 └── tests/accept_step1.py, tests/accept_step1_order.py ``` @@ -1648,13 +1687,24 @@ function wlAbs(url) { #### Сборщик `build_plugin.py` ```bash -cd /Users/admin/lampac-tmp -python3 build_plugin.py # plugin-patched.js + INJECT → plugin-cub.js (14376 Б) +cd ~/Developer/lampac-scripts/scripts +python3 build_plugin.py # plugin-patched.js + INJECT → plugin-cub.js node --check plugin-cub.js ``` +🔴 **2026-09-25: сборщик переехал из `/Users/admin/lampac-tmp/` в репозиторий** +`lampac-scripts/scripts/` — вместе с собранным `plugin-cub.js`. Скрэтч-папка +`lampac-tmp` вне git, файлы там не переживут чистку. Коммит: `5e55a47`. + Детерминирован: режет `[до startPlugin] + INJECT + [новый startPlugin] + '})();'`, -проверяет инварианты (`INJECT=1`, `def=1`, `startPlugin=1`, `network.silent в INJECT=0`). +проверяет инварианты. **Текущий набор (12):** `INJECT=1`, `partNext def=1`, +`wlInject def=1`, `unshift (не push)=1`, `push в INJECT=0`, `startPlugin=1`, +`нет network.silent в INJECT=0`, плюс добавленные 2026-09-25 — +**`wlOnMain def=1`, `wlOnMain guard=1`, `wlUrl def=1`, `wlUrl верхний уровень=1`**. + +🔴 **Правило: новая функция в INJECT = новый инвариант в `checks{}`.** Иначе +регресс (например, потерянный фильтр по экрану) пройдёт сборку молча и всплывёт +только у Alex в UI. 🔴 **Питфолл прошлого сборщика `patch_cub_inject.py`:** проверки `"X" not in s` шли **по уже модифицированной строке** → повторный запуск вставлял INJECT дважды → плюс лишняя @@ -1821,21 +1871,29 @@ docker compose down ### Развёртывание каталога watchlist (`deploy_lampac.py`) -Локальный скрипт `/Users/admin/lampac-tmp/deploy_lampac.py` — 7 шагов с бэкапами +🔴 **2026-09-25: файл в репозитории**, не в скрэтче — +`~/Developer/lampac-scripts/scripts/deploy_lampac.py`. 7 шагов с бэкапами и самопроверкой. Запуск: ```bash -python3 /Users/admin/lampac-tmp/deploy_lampac.py # полный цикл -python3 /Users/admin/lampac-tmp/deploy_lampac.py --skip-plugin # только JSON+YAML +python3 ~/Developer/lampac-scripts/scripts/deploy_lampac.py # полный цикл +python3 ~/Developer/lampac-scripts/scripts/deploy_lampac.py --skip-plugin # только JSON+YAML ``` +⚠️ Для скриптов прошлых сессий (пробники, разовые патчи) `lampac-tmp` остаётся +упомянут в исторических разделах ниже — это скрэтч вне git, ссылки оставлены +как есть. Источник истины для **плагина каталога и генератора** — только репозиторий. + Порядок: бэкап JSON в `wwwroot/.bak/` → заливка 5 JSON → `watchlist.yaml` → патченный `plugin.js` → volume в compose (если нет) → `docker compose up -d` → проверка (`/catalog`, 4 категории с подсчётом постеров и TMDB-ID, факт патча). -### Генератор каталога (`gen_watchlist_v2.py`) +### Генератор каталога (`gen_watchlist.py`) -`/Users/admin/lampac-tmp/gen_watchlist_v2.py ` +⚠️ **Устарело 2026-09-24:** генератор выпилен из пайплайна — фид Lampa строит +`watchlist-sync sync-down` (см. [[personal/tech/watchlist-sync-feed]]). +Файл остаётся в репе как исторический: `~/Developer/lampac-scripts/scripts/gen_watchlist.py` +(бывш. `gen_watchlist_v2.py`, скрэтч-копии в `lampac-tmp` — не источник истины). Читает `.md`, резолвит каждый фильм: **сначала Radarr** (мгновенно, там уже `tmdbId` и постер), **затем TMDB** по названию (2 языка × movie/tv), **фолбэк** — ссылка Кинопоиска.