From f184b707a1e542f2105d04f055cea63b3628cba6 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Fri, 25 Sep 2026 22:41:20 +0600 Subject: [PATCH] [2026-09-25] eagle: personal/plans/lampac-watchlist-plugin.md personal/tech/lampac-deployment.md --- personal/plans/lampac-watchlist-plugin.md | 137 ++++++++++++++++++++-- personal/tech/lampac-deployment.md | 84 ++++++++++++- 2 files changed, 208 insertions(+), 13 deletions(-) diff --git a/personal/plans/lampac-watchlist-plugin.md b/personal/plans/lampac-watchlist-plugin.md index 9aedb017..d403b9ec 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-WORKS-2026-09-25-accepted-committed-5e55a47-prefetch-pending +status: third-fix-2026-09-25-component-filter+wlPrefetch-deployed-awaiting-verdict tags: - lampac - lampa @@ -444,14 +444,8 @@ cats = NOMANIFEST 1. ✅ **ПРИЁМКА ПРОЙДЕНА 2026-09-25** — фильтр `wlOnMain()` работает, фид виден только на главной. Доказательство: на экране `movie` секций в DOM нет при заполненном `menu`. -2. 🔄 **ПРЕДЗАГРУЗКА `__wl_menu`** — единственный незакрытый пункт этой задачи, - выполняется сразу после коммита `5e55a47`. Корень отставания — асинхронный `/catalog` - в `loadCatalogs()`: меню приходит ПОСЛЕ первого `partNext`. Решение: подтягивать - `menu` синхронно в `startPlugin()`, параллельно с `loadCatalogs()`, чтобы `wlInject` - срабатывал в первом раунде среза. Правка идёт в те же два файла в репе - (`lampac-scripts/scripts/plugin-cub.js` + `INJECT` в `build_plugin.py`) + новый - инвариант сборщика. Порядок деплоя — как обычно: сборка → `node --check` → бэкап → - `scp` → рестарт Lampa на ТВ → замер CDP. +2. 🔄 **ПРЕДЗАГРУЗКА `__wl_menu`** — ВЫПОЛНЕНА, но НЕ ЗАКРЫТА: см. «ТРЕТИЙ ФИКС» + ниже. Требуется ещё один заход (фильтр по `component` + вставка при возврате). 3. **Диагностировать 66 с на карточке** — `docker logs lampac` + трейс `CardController`. Запрос: `GET /catalog/card?plugin=watchlist&uri=movie%2F424781&type=movie` → 66 118 мс, 939 Б. @@ -460,6 +454,131 @@ cats = NOMANIFEST Развилка «`lampac_plugin/` в `watchlist-sync` vs бэкапы» **закрыта**: плагин принадлежит `lampac-scripts` (там же `gen_watchlist.py`, `deploy_lampac.py`). +--- + +## 🔴🔴 2026-09-25 (финал): ТРЕТИЙ ФИКС — фильтр по `component` + `wlPrefetch()` + +### Приёмка Alex частичная: два новых симптома + +Alex после заливки `wlPrefetch`: + +> «Так, хорошо, Сейчас при загрузке приложения сработало, коммить. +> Но когда я с главной ушел в раздел фильмы там снова были watchlist разделы +> А когда вернулся на главную опять разделы watchlist не сверху» + +То есть: **при холодном старте — правильно** (секции сверху, фильтр держит), но +навигация ломает обе части: уход в «Фильмы» → секции появились; возврат на главную → +секции не сверху. + +### 🔴 ГЛАВНАЯ НАХОДКА: на главной `url` ПУСТОЙ, а `component === 'main'` + +Замер на живой Lampa (CDP) в момент, когда открыта именно главная +(`?component=main&source=cub&page=1`): + +``` +active_full = url= | title=Главная - CUB | component=main | source=cub | page=1 | params=obj | activity=obj +act_url_direct = "" ← url ПУСТОЙ, а не 'main' +act_act_url = undefined ← вложенный activity.url тоже пуст +menu = Новинки,Фильмы,Мультфильмы,Аниме +cats = NOMANIFEST +``` + +🔴 **Вывод: проверка `url === 'main'` НЕ РАБОТАЕТ ВООБЩЕ.** Она не срабатывала ни разу — +её проходила только смягчающая ветка `if (!act) { if (!catalogs) return true; }`. +Как только `Manifest.catalogs` заполнится, **фильтр начал бы резать саму главную**. +Это была мина, а не рабочее условие. + +**Правильное поле — `component`:** главная `→ 'main'`, категория `→ 'category_full'` / +`'category'`. Оно стабильно на обоих экранах, в отличие от `url`. + +```js +function wlComp(a) { + if (!a) return ''; + if (a.component) return String(a.component); + if (a.activity && a.activity.component) return String(a.activity.component); + return ''; +} +// в wlOnMain(): if (comp === 'main') return true; +// смягчающая ветка: if (!comp) { пустые catalogs → true } +``` + +### Замер, доказавший что секции ВСЁ-ТАКИ вставлялись + +``` +cat_header = Сейчас смотрят | Рекомендуем посмотреть | Последнее добавление | Огонь! | + В высоком качестве | Новинки | Фильмы | Мультфильмы | Аниме | Фэнтези | ... + ↑↑↑↑↑↑↑↑ наши 4 секции — ЕСТЬ, но ПОСЛЕ родных +wl_done = false +``` + +То есть на главной секции присутствовали, но стояли **после** «В высоком качестве» — +ровно тот симптом «сильно после первых секций», который Alex и называл. + +### `wlPrefetch()` — предзагрузка меню при старте + +```js +function wlPrefetch() { + if (window.__wl_prefetched) return; + window.__wl_prefetched = true; + wlMenu(function (m) { console.log('[watchlist] меню предзагружено: ' + Object.keys(m || {}).join(',')); }); +} +// в startPlugin(), рядом с wlPatchPartNext(): +// if(window.appready){wlPatchPartNext();wlPrefetch();} else { … 'ready' → wlPatchPartNext();wlPrefetch(); } +``` + +`/catalog` тянется **параллельно** `loadCatalogs()`, чтобы `__wl_menu` лежал в памяти +к моменту первой отрисовки главной. Меряно: `menu` заполнен при +`Manifest.catalogs = NOMANIFEST` → **на `loadCatalogs()` полагаться нельзя**, нужен +независимый запрос. + +### Состояние сборки и деплоя + +``` +python3 build_plugin.py → 14/14 инвариантов, 18947 байт +node --check plugin-cub.js → SYNTAX OK +``` + +**Инварианты (14):** `INJECT=1`, `partNext def=1`, `wlInject def=1`, +`unshift (не push)=1`, `push в INJECT=0`, `startPlugin=1`, +`нет network.silent в INJECT=0`, **`wlOnMain def=1`**, **`wlComp def=1`**, +**`wlComp читает component=1`**, **`wlOnMain guard=1`**, **`wlPrefetch def=1`**, +**`wlPrefetch call=3`**. Инварианты `wlUrl def` / `wlUrl верхний уровень` — **убраны** +вместе с `wlUrl()` (поле оказалось не тем). + +**Деплой выполнен:** бэкап `catalog/plugin.js.bak-before-comp-` → +`scp` (18947 Б) → проверка в контейнере +`docker exec lampac grep -c "wlPrefetch\|wlComp" /lampac/module/Catalog/plugin.js` → **4** +→ рестарт Lampa на ТВ через `luna-send` (`--- закрываю --- --- запускаю --- --- готово ---`). + +### 🔴 Что ОСТАЛОСЬ (следующая сессия, начать отсюда) + +**Гипотезы по двум симптомам (НЕ проверены, гадать не нужно — мерить):** + +1. **Уход в «Фильмы» → секции снова появились.** Переход идёт через `category()` / + `category_full` (`plugin.js:36`). Фильтр по `component` там должен давать `false`, + НО `component` на экране Фильмов в замере **не логировался** — на тот момент + фиксировался только `url`. Возможные варианты: `component` на Фильмах тоже + `'main'`; либо вставка на этом экране идёт не из `partNext`. + → Проверить `component` на экране Фильмов ОТДЕЛЬНОЙ пробой. +2. **Возврат на главную → секции не сверху.** `wlPrefetch()` **одноразовый** + (`__wl_prefetched`). На возврате `__wl_menu` уже заполнен, но `wlInject` не + срабатывает в первом раунде; плюс `unshift` в голову `parts` работает, только если + `partNext` вызван **заново с новым массивом** — на возврате Lampa может + переиспользовать уже отрисованный пул. + → Нужен трейс порядка вызовов `partNext` на «главная → Фильмы → главная» + (`console.log` с номерами вызовов). + +**Порядок работ:** сначала ЗАМЕР по обоим пунктам (пока Lampa в текущем состоянии, +без рестарта), потом правка, потом обычный цикл: сборка → `node --check` → бэкап → +`scp` → рестарт Lampa на ТВ → замер CDP → приёмка Alex. + +**Коммит правки (третий фикс) НЕ сделан** — апрув истёк по таймауту. Файлы лежат в +`/Users/admin/lampac-tmp/`, в `~/Developer/lampac-scripts/scripts/` ещё НЕ скопированы. +После приёмки — скопировать и закоммитить тем же порядком, что `5e55a47`. + +**Правило:** `git commit` через `ssh`/длинную команду может уйти в таймаут апрува — +подтверждение Alex обязательно, повторять команду автоматически нельзя. + **Правило диагностики этой сессии:** не читать `%{size_download}` как «размер фида» (это gzip) — брать `ls -la` или `Accept-Encoding: identity`. И — `ifconfig` на Кра́кене отсутствует: каждая ssh-команда печатает `bash: line 1: ifconfig: command not found` diff --git a/personal/tech/lampac-deployment.md b/personal/tech/lampac-deployment.md index 6b26458f..70a14abd 100644 --- a/personal/tech/lampac-deployment.md +++ b/personal/tech/lampac-deployment.md @@ -43,7 +43,7 @@ related: | Слой | Путь | Примечание | |---|---|---| -| **Mac, репо** | `~/Developer/lampac-scripts/scripts/build_plugin.py` | сборщик, 12 инвариантов | +| **Mac, репо** | `~/Developer/lampac-scripts/scripts/build_plugin.py` | сборщик, 14 инвариантов | | **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 | @@ -71,6 +71,27 @@ git commit -m "..." && git push origin main (`watchlist.json`, `watchlist-.json`, `cards.json`, `cards/`, `missing.json`). Попытка `scp` оттуда падает с «No such file or directory» — это не сбой сети. +### 🔴 CDP-пробы на ТВ (порт 9998) — питфоллы, стоившие сессии + +| Питфолл | Симптом | Как правильно | +|---|---|---| +| **Цель искать по `url`, а не `title`** | сразу после рестарта Lampa `title=""`, `url=about:blank` → скрипт молча не находит цель, проба даёт пустоту | выбирать `"com.lampac.tv" in t.get("url")`, НЕ `title == "Lampac"` | +| **Первый замер — гонка** | `ready=false, plugin=false, patch=false, act_url=ERR` выглядит как «плагин не доехал» | подождать/опрашивать `ready`, пока не станет `true` | +| **`JSON.stringify(active())` падает** | `TypeError: Converting circular structure to JSON` | собирать поля по одному или строку вручную | +| **Отсутствующее поле без обёртки** | CDP отвечает без `result.value` — выглядит как «все пробы пустые» (получили 9 `` подряд) | каждую пробу оборачивать в `String(...)` / `typeof ...`, проверять `subtype == "error"` | +| **`Log.enable` + `Runtime.enable` дают 0 строк** | консольные сообщения, напечатанные ДО подключения, не отдаются | читать логи СРАЗУ после рестарта, либо подтверждать состояние пробами, а не логами | + +⚠️ **`docker restart lampac` НЕ сбрасывает кэш `catalog.js` в браузере webOS** — нужен +рестарт приложения на ТВ через `luna-send`. И **не спать** в ожидании: Alex на паузу +`sleep 20` отреагировал резко («Чё нахуй за слип 20?!») — вместо сна опрашивать состояние. + +🔴 **Правило диагностики:** не читать `curl -w %{size_download}` как «размер фида» — +это размер ПОСЛЕ распаковки gzip. Брать `ls -la` или `Accept-Encoding: identity`. +Пример: `watchlist-films.json` = 88 КБ несжатых при 347 позициях, а `cards.json` = 548 КБ. + +⚠️ **`ifconfig` на Кра́кене отсутствует** — каждая `ssh kraken` печатает +`bash: line 1: ifconfig: command not found` безвредным шумом. Это НЕ ошибка команды. + ### Порядок деплоя ``` @@ -93,6 +114,59 @@ ssh kraken 'docker exec lampac grep -c "<маркер>" /lampac/module/Catalog/p следующая сборка затрёт правку. Инварианты в `checks{}` обязаны покрывать любую новую функцию — иначе регресс пройдёт молча. +### 🔴🔴 Как определить активный экран Lampa — по `component`, НЕ по `url` + +**ИЗМЕРЕНО на живом ТВ 2026-09-25.** Форма объекта: + +``` +Lampa.Activity.active() → {url, title, component, source, page, params, activity} +Lampa.Activity.active().activity → {component, object, slide, body, is_started, is_stopped} +``` + +🔴 **На главной `url` ПУСТОЙ.** Реальный замер в момент, когда открыта главная +(`?component=main&source=cub&page=1`): + +``` +url = "" title = "Главная - CUB" component = "main" +source = "cub" page = 1 +``` + +`url === 'main'` **не срабатывает никогда.** Если строить фильтр секций на `url`, +он либо не работает, либо работает случайно — через смягчающую ветку «пусто + +`Manifest.catalogs` пуст». Как только манифест заполнится, **фильтр начнёт резать +саму главную** и секции исчезнут. + +**Правильное поле — `component`:** +| Экран | `component` | `url` | +|---|---|---| +| Главная | `main` | `""` (пусто) | +| Категория / Фильмы | `category_full` / `category` | `movie`, `cartoons`, … | + +```js +function wlComp(a) { + if (!a) return ''; + if (a.component) return String(a.component); + if (a.activity && a.activity.component) return String(a.activity.component); + return ''; +} +// фильтр: if (wlComp(Lampa.Activity.active()) === 'main') → вставляем +``` + +⚠️ `url` при этом остаётся полезным для другого: на экране Фильмов он даёт +`"movie"`. Замер `act_url = "movie"` использовался как доказательство работы фильтра. + +### 🔴 `Manifest.catalogs` и `window.__wl_menu` рассинхронизированы + +Меряно одновременно: `menu = Новинки,Фильмы,Мультфильмы,Аниме` при +`Manifest.catalogs = NOMANIFEST`. То есть `__wl_menu` заполнен, а штатный манифест — +нет. **На `loadCatalogs()` полагаться нельзя**, для меню нужен независимый запрос +(`wlPrefetch()` в `startPlugin()`, параллельно `loadCatalogs()`). + +Без предзагрузки первый `partNext` застаёт `__wl_menu` пустым → уходит в +асинхронный `wlMenu()` → пока летит запрос, родной `cub.main()` уже отрисовал свои +секции и `slice(0, parts_limit)` отработал → наши секции попадают в пул только +следующим раундом. Alex видел это как «есть, но сильно после первых секций». + ### 🔴 Перезапуск Lampa на ТВ обязателен `docker restart lampac` **НЕ сбрасывает** кэш `catalog.js` в браузере webOS — ТВ @@ -1724,10 +1798,12 @@ node --check plugin-cub.js `lampac-tmp` вне git, файлы там не переживут чистку. Коммит: `5e55a47`. Детерминирован: режет `[до startPlugin] + INJECT + [новый startPlugin] + '})();'`, -проверяет инварианты. **Текущий набор (12):** `INJECT=1`, `partNext def=1`, +проверяет инварианты. **Текущий набор (14):** `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`**. +`нет network.silent в INJECT=0`, `wlOnMain def=1`, `wlComp def=1`, +`wlComp читает component=1`, `wlOnMain guard=1`, `wlPrefetch def=1`, +`wlPrefetch call=3`. Инварианты `wlUrl*` **убраны** вместе с `wlUrl()` — +поле `url` оказалось не тем (см. раздел про `component`). 🔴 **Правило: новая функция в INJECT = новый инвариант в `checks{}`.** Иначе регресс (например, потерянный фильтр по экрану) пройдёт сборку молча и всплывёт