| name | onboarding-system |
| description | Полная карта онбординга Movie Planner (miniapp + iOS/Android + бэкенд): seed, similar, жанры, watched/want picker, API, логи, кэш, типичные поломки и запреты. ОБЯЗАТЕЛЬНО читать перед любыми правками onboarding-flow.js, miniapp_routes.py onboarding endpoints, onboarding_seed.py, onboarding-picker, extended-onboarding. |
Онбординг Movie Planner — карта для агента
Последняя верификация: 2026-06-21 (series similar via genre search, seed cache-first).
Онбординг — критический прод-путь: регистрация, «волшебная палочка», импорт с KP. Ломается сразу на всех клиентах (miniapp, iOS, Android), потому что один бэкенд moviebot/web/miniapp_routes.py.
1. Где что лежит
| Слой | Путь | Роль |
|---|
| Seed-списки KP id | moviebot/web/onboarding_seed.py | Фиксированные 48+48 id, backup, anime, genre seed |
| API онбординга | moviebot/web/miniapp_routes.py | Все /api/miniapp/onboarding/*, enrich, similar |
| Mini App flow | moviebot/web/miniapp/onboarding-flow.js | Extended onboarding, picker, similar на клиенте |
| Mini App правило UI | .cursor/rules/onboarding-film-picker.mdc | Promise/ov, watched→want, scroll lock |
| Mobile API | movieplanner-mobile/src/api/onboarding.ts | fetch seed/similar/tail, timeout 70s |
| Mobile picker | movieplanner-mobile/app/onboarding-picker.tsx | Сетка фильмов/сeriалов, similar append |
| Mobile flow | movieplanner-mobile/app/extended-onboarding.tsx | Интересы → жанры → picker → премьеры → weekend |
| Mobile state | movieplanner-mobile/src/onboarding/state.ts | mp_onboard_v2_state (AsyncStorage) |
| KP import flow | moviebot/web/onboarding_kp_import_flow.py | После импорта — import-want picker |
| Тесты | moviebot/tests/test_onboarding_kp_import_flow.py | Только import flow, не picker |
Не путать с: site/onboarding, TMDB migration, series_hub (главная), premiere rails — это другие модули.
2. Пользовательский сценарий (как задумано)
2.1 Новый пользователь (фильмы + сериалы)
- Intro carousel (mobile:
onboarding.tsx → extended-onboarding)
- Интересы: movies / series / premieres / other
- Источник базы: KP import / MyShows / IMDb / none
- Жанры (если manual path, без успешного импорта): чипы →
st.genres, genresDone=true
- Picker watched: «Отметьте что смотрели» — сетка ~48 карточек
- При тапе на карточку → подгрузка похожих в конец списка
- Confirm watched → новый экран picker want (не in-place!)
- Confirm want → bulk-library POST → премьеры (если interest) → weekend carousel → dashboard
2.2 «Волшебная палочка» / random / wtw (mobile)
Обязательно идти через /extended-onboarding с патчем state (genresDone: false, importSkipped: true, …), не напрямую в /onboarding-picker.
Файлы: dashboard.tsx, random/index.tsx, wtw.tsx — navigation на extended-onboarding, иначе пропадает шаг жанров.
2.3 После импорта KP
import-want picker (mode=import-want), seed из /import-want-seed (оценки пользователя).
3. API endpoints (бэкенд)
Все под @app.route в register_miniapp_routes, логи через _onboard_log(event, **fields) → [ONBOARD] event ....
| Endpoint | Назначение |
|---|
GET /onboarding/status | onboarding_interest_done, empty_library, force_onboarding |
GET /onboarding/seed?type=film|series|any&genres=...&exclude_library=1 | Стартовая сетка ~48 карточек |
GET /onboarding/similar?kp_id=&type=&exclude= | Похожие при выборе карточки |
GET /onboarding/rated-tail?type=&exclude= | Хвост из оценок пользователя (после seed) |
GET /onboarding/import-want-seed | Seed после KP import |
GET /onboarding/library-seed?type= | Персональная сетка из библиотеки |
POST /onboarding/bulk-library | { watched, unwatched } → movies table |
POST /onboarding/interest | Сохранение интересов/жанров |
POST /onboarding/client-log | Клиентские события |
3.1 /seed — как работает (НЕ ЛОМАТЬ)
- Берёт id из
ONBOARDING_SEED_FILMS / ONBOARDING_SEED_SERIES + backup
- Если
genres= переданы → в начало списка fetch_onboarding_genre_seed_ids() (KP search или anime fallback)
fetch_missing=False — только кэш KP (batch_get_cached), без блокирующих внешних запросов в HTTP
_onboarding_row_presentable: кириллица в title (или whitelist 1+1) + poster_ok
- Если presentable < 48 → дозаполнение карточками
Фильм {id} / Сериал {id} с CDN poster (чтобы не было чёрного экрана)
- Фон:
_warm_onboarding_seed_cache_async(deduped) — прогрев кэша для фиксированных id (не блокирует ответ)
- Import
fetch_onboarding_genre_seed_ids только через getattr — прямой import ломал prod (ImportError → seed.fail → numbered titles)
Режим логов: seed.start → seed.ids → seed.enrich → seed.ok (items≈48, ms<200 типично).
3.2 /similar — как работает (НЕ ЛОМАТЬ)
Клиент передаёт exclude = все kp_id уже на экране (~48 из seed + ранее добавленные). Это критично.
Фильмы (type=film)
get_similars(kp_id) → KP /films/{id}/similars
- Фильтр: не в exclude, не сериал
_onboarding_enrich_kp_items(..., fetch_missing=True, max_fetch=12)
- Presentable filter
- Если < 6 → pad из
ONBOARDING_SEED_BACKUP_FILMS + ONBOARDING_SEED_ANIME_FILMS (не main seed — они в exclude!)
Сериалы (type=series) — отдельная ветка
KP /similars для сериалов возвращает в основном фильмы (is_series=False). Сериалы из ответа часто уже в seed → в exclude → matched=0.
Поэтому для series не используем /similars как primary:
_onboarding_series_similar_ids():
- sequels/spin-offs (
get_sequels)
- жанры anchor из
kp_film_cache → search_films_by_filters(genre, film_type=TV_SERIES)
- skip всё из
exclude
- enrich + presentable
- pad: backup/anime series (не main seed)
Логи: similar.start → similar.raw matched=N → similar.pad fill=N → similar.ok items=N.
Ожидание: matched=8..16, items=6..12 для series. matched=0 при raw=20 для film-path на series = баг (вернули film-path).
3.3 /rated-tail
Для пользователей с оценками: similars от top-rated anchors. На чистом онбординге anchors=0 → items=0 — норма.
3.4 Enrich pipeline (_onboarding_enrich_kp_items)
batch_get_cached(ordered) сначала
fetch_missing=True → get_or_fetch в ThreadPool, max_fetch лимит
- Перед slow IO:
release_http_db_pool_before_slow_io() — иначе pool exhausted
_onboarding_build_row: type filter, poster из API или film_kp_poster_url
- Seed:
fetch_missing=False (быстро)
- Similar:
fetch_missing=True (нужны названия/постеры новых id)
4. Seed-списки (onboarding_seed.py)
| Константа | Смысл |
|---|
ONBOARDING_SEED_FILMS | 48 фильмов, основная сетка |
ONBOARDING_SEED_SERIES | 48 сериалов, основная сетка |
ONBOARDING_SEED_BACKUP_* | Запас для pad / seed overflow |
ONBOARDING_SEED_ANIME_* | Жанр аниме + pad similar |
fetch_onboarding_genre_seed_ids(prefs, media_type) | KP top by genre; fallback _onboarding_genre_seed_fallback |
Правило: id в MAIN seed проверены на nameRu + posterUrl. Новый id — проверить через KP API перед добавлением.
type=series в seed: только id из ONBOARDING_SEED_SERIES + backup series. Не подмешивать film id в series endpoint.
5. Клиент: picker и similar
5.1 Mini App (onboarding-flow.js)
MAX_TILES=100, MAX_SIMILAR_LOADS=15
loadSimilar(kp): exclude = seenKp + watchedKpBlock (comma-separated)
- Append в конец
items.push — sortRecommendedFirst() пустая (не сортировать recommended наверх!)
isPickerItemOk: кириллица + poster_ok (как сервер)
- Два mount picker: watched confirm →
resolve({ phase: "want" }) → новый mountFilmPicker. In-place watched→want запрещён.
OB_FLOW_V — bump при правках + ?v= в index.html
5.2 Mobile (onboarding-picker.tsx)
- Те же лимиты: append similar в конец
[...prev, ...fresh]
fetchOnboardingSeed(type, genrePrefs, { excludeLibrary }) — жанры из readOnboardState().genres
- Timeout 70000 ms + retry 3x в
onboarding.ts
- Фазы watched/want: mobile использует один экран с
setPhase("want") после confirm watched (допустимо для RN, отличается от miniapp)
5.3 Mobile extended flow
Порядок шагов в extended-onboarding.tsx:
interest → (premieres only path) → db → import → genres → onboarding-picker → premieres → weekend
needsManual = hasMedia && (db none/other || importSkipped || importStarted) → жанры обязательны перед picker.
6. Логи и диагностика
Railway / prod
Искать [ONBOARD]:
| Паттерн | Значение |
|---|
seed.ok items=0 | Катастрофа — UI пустой |
seed.ok items=48 ms=... | Seed OK |
seed.fail / seed.error | Exception в seed; проверить ImportError в genre fn |
seed.enrich presentable=0 | Cold cache — ждать cache_warm или проверить kp_film_cache |
similar.raw matched=0 raw=20 type=series | Film-path на series (баг) или все в exclude |
similar.pad fill=0 | Pad pool исчерпан / всё в exclude |
similar.ok items=0 | Пользователь не видит рекомендаций |
pool exhausted | Не release pool перед KP fetch |
Client
Miniapp: POST /onboarding/client-log. Mobile: [MOBILE-CLIENT] boot events.
7. Частые поломки (история 2026-06)
| Симптом | Причина | Fix |
|---|
| Чёрный экран picker | ov вне Promise, нет unlockViewportScroll | .cursor/rules/onboarding-film-picker.mdc |
Фильм 12345 без названий | seed ImportError или cold cache + hard fallback | getattr import, cache warm |
| Нет шага жанров | Прямой route на picker | Через extended-onboarding |
| Similar в начале списка | sortRecommendedFirst сортировал наверх | Append only, пустая sort fn |
| Similar 0 для series | KP similars=фильмы + exclude=seed | Genre TV_SERIES path |
| Similar 0 pad не помог | Pad брал MAIN seed (уже в exclude) | Backup/anime/genre pad |
| Серии показывают фильмы | Неверные id в series seed/fallback | Только ONBOARDING_SEED_SERIES |
| 70s timeout | Блокирующий fetch в seed HTTP | seed cache-only; fetch в similar только |
| TMDB в онбординге | Лишние resolver | Онбординг = только Kinopoisk |
8. ЗАПРЕЩЕНО без явного согласования
- Блокирующие внешние API в
/seed HTTP (TMDB, heavy enrich, 50× get_or_fetch sync)
- Убирать hard fallback карточек при cold cache (лучше «Сериал id» чем пусто)
- Pad similar из MAIN seed (
ONBOARDING_SEED_FILMS/SERIES) — клиент их уже в exclude
- Для series полагаться на KP
/similars без genre fallback
- Pre-filter similar по
is_series из API без проверки known_series / cache
- In-place watched→want в miniapp (один mountFilmPicker)
- sortRecommendedFirst с реальной сортировкой наверх
- Прямой import
from onboarding_seed import fetch_onboarding_genre_seed_ids — только getattr + fallback
- Уменьшать timeout onboarding API ниже 65–70s без ускорения бэка
- Менять ONBOARDING_SEED_ без проверки* poster+title в KP
- Регистрировать onboarding routes асинхронно после startup (404 на deploy)
9. Чеклист перед merge (онбординг)
Бэкенд
Mini App
Mobile
10. Быстрые curl (prod)
curl -s "https://api.movie-planner.ru/api/miniapp/onboarding/seed?type=series" -H "Cookie: ..."
curl -s "https://api.movie-planner.ru/api/miniapp/onboarding/similar?kp_id=464963&type=series&exclude=464963,404900,77044"
11. Связанные правила
.cursor/rules/onboarding-film-picker.mdc — miniapp picker UI (Promise, scroll)
.cursor/rules/product-ui-user-facing.mdc — тексты в UI
.cursor/rules/db-connections-pool.mdc — pool + release_http_db_pool_before_slow_io
DATABASE_CONNECTIONS_GUIDE.md — фон vs HTTP pool
12. Ментальная модель (одной фразой)
Seed = быстрый фиксированный кэшированный каталог; Similar = «ещё карточки по выбору» (films→KP similars, series→жанры anchor); exclude на клиенте = всё уже показанное; жанры влияют на порядок/состав seed, не на similar для films.
При любой правке спроси себя: «Сломаю ли я append similar, genre path для series, или cache-first seed?» — если да, перечитай этот файл.