Files
rf4-spotter/docs/ROADMAP.md
T
2026-09-20 20:21:39 +07:00

69 KiB
Raw Blame History

План работ RF4 Spotter

Этот файл — единственный актуальный список задач. Актуальный технический срез и границы доказательств зафиксированы в PROJECT_AUDIT_2026-09-20.md. Завершённые аудиты сохранены как история в PROJECT_AUDIT_2026-09-08.md, REGRESSION_AUDIT_2026-09-09.md и RECOVERY_PLAN_2026-09-10.md; их старые чекбоксы не являются текущей очередью.

Последняя сверка: 20 сентября 2026.

Подтверждено:

  • пакет восстановления A01–A13 завершён; итог и доказательства собраны в RECOVERY_FIXES_REPORT.md;
  • полный Python suite: 196 passed, 1 skipped; skip относится к интеграционной проверке PostgreSQL и требует отдельного Docker-прогона;
  • Astro check: 62 файла, 0 errors / 0 warnings / 0 hints; production build проходит;
  • API contract suite: apps/api/tests/test_api.py содержит 20 passed; live /health и /ready после миграции требуют отдельного Docker-прогона;
  • граф Alembic линеен и имеет единственную голову 0020; CI применяет её на чистой PostgreSQL, полный production bootstrap запускается отдельным еженедельным drill;
  • локальный dev Compose gate 20.09: PostgreSQL, MinIO, API и web healthy; /health//ready успешны, migration current — 0020 (head), public /, /waterbodies/ и /records/ отвечают 200 из контейнерной сети;
  • изолированный production bootstrap повторно прошёл 20.09: чистые PostgreSQL/MinIO volumes, MinIO init с отдельными app credentials, Alembic 0020, Caddy adapt, scheduler validation и оба Playwright-сценария; drill не обращается к внешним источникам;
  • Astro + FastAPI + PostgreSQL остаются целевым стеком; Next.js и Vinext не используются.

Ближайший пакет — без сервера

Пункты выполняются сверху вниз, небольшими связанными коммитами.

Брендинг и визуальная идентичность

  • B01 · Иерархия имени. RF4 Spotter — единое имя продукта в UI, metadata, manifest и документации; «Ни хвоста, ни чешуи» — поддерживающий слоган. Обновлены wordmark в header/footer и подпись выпуска.
  • B02 · Дизайн-токены. Добавлены семантические роли поверхностей, текста, границ, фокуса, success/warning/danger и всех источников. На токены переведены паспорта данных, status-карточки, skeleton и focus; контрастные пары зафиксированы в brand system.
  • B03 · Графическая грамматика. Мотивы закреплены за функциями: крючок — бренд, поплавок — активность/ожидание, леска — время, радар — координаты, силуэт — сущность рыбы. Убраны ложная рябь неполных сигналов и дублирующие радар круги карточки лидера; неполнота теперь подчёркнута спокойной полевой меткой.
  • B04 · Компонентная подпись. SectionHeading, PageHero и StatePanel унифицируют заголовки, hero и состояния; семантическая иерархия data-action согласует CTA публичных и admin-страниц.
  • B05 · Motion-система. Смысловые микроанимации пульса, поплавка, загрузки и интерактивного отклика используют общие duration/easing-токены; декоративное движение empty-state удалено, reduced motion покрывает элементы и псевдоэлементы.
  • B06 · Brand QA. Desktop/mobile-проверка усилила каталоги atlas-hero, счётчиками, береговыми контурами и выразительными карточками. Favicon/PWA PNG перегенерированы из SVG-мастера без размытия; any и полнофоновые maskable-иконки разделены, manifest и Caddy cache обновлены, OG подтверждён как 1200×630.
  • B07 · Аудит официальных изображений. Текущий records-parser получает названия рыб/водоёмов и title приманки, но не image URL; официальные gallery/userguide содержат тематические медиа без устойчивого полного соответствия каноническим сущностям. Изображения не хотлинкать и не считать разрешение на данные автоматическим разрешением на медиапубликацию.
  • B08 · Семейства силуэтов рыб. Три случайных hash-варианта заменены классификатором и отдельными формами pike, salmonid, cyprinid, perch, catfish, eel, flatfish, marine, плюс честный generic. Компонент допускает ручное переопределение family; название остаётся главным идентификатором.
  • B09 · Глифы снастей и приманок. Добавлены SVG-глифы spinner, wobbler, soft, boilie, worm, rig, unknown и стабильная палитра по normalized name. Классификация срабатывает только по явным словам; глиф сопровождает текст в activity, лидере, уловах, рекордах и списке лучших приманок, не выдавая категорию за точную модель.
  • B10 · Визуальные отпечатки водоёмов. Для каждого slug воспроизводимо выбираются один из восьми береговых контуров, число волн, положение точки и двухсимвольный индекс. Знак используется в каталоге и detail-hero; это явно абстрактный отпечаток, а не карта или игровая география.
  • B11 · Разрешённый media pipeline и публичная медиатека. Версионированный baseline содержит 19 водоёмов и 252 канонические рыбы; полное число снастей остаётся null, а не подменяется числом приманок. Актуальный manifest содержит 704 записи: 466 approved, 226 superseded, 1 provenance-only duplicate и 11 invalid; опубликованы 253 fish-файла, 149 снастей/приманок и 64 справочных материала. Публичные /media и /api/v1/media/* отдают Git-копии по SHA-256 без hotlink; у каждой карточки есть плашка и прямая ссылка на источник. Audit не находит ошибок и бесхозных файлов. Batch-контур по-прежнему соблюдает единый 30-минутный cooldown домена и не одобряет будущие загрузки автоматически.
  • B12 · Эмблема сочетания. Страница «водоём + рыба» получила составной атласный seal: собственный отпечаток водоёма пересекается со смысловым силуэтом рыбы. Так визуальная идентичность сопровождает всю иерархию каталога и не требует внешних изображений.
  • B13 · Навигационная леска атласа. Разрозненные ссылки назад на detail-страницах заменены доступной breadcrumb-цепочкой с мотивом лески и узлов. Страница точки связывает главную, водоём и координаты; сочетание — каталог, водоём и рыбу. Текущий узел всегда подписан текстом и отмечен aria-current.
  • B14 · Атласные переходы сущностей. Боковые списки рыб и водоёмов на detail-страницах получили компактные силуэты и отпечатки рядом с полным текстовым названием. Знаки продолжают систему каталога в рабочей навигации, а стрелка явно показывает переход к странице сочетания.
  • B15 · Целостность медиакаталога. Локальный audit сводит статусы очереди и проверяет наличие файлов, SHA-256, фактические dimensions/MIME, каноническое соответствие approved-записей и бесхозные файлы. Проверка не обращается в сеть и может использоваться как pre-publication gate.
  • B16 · Самодостаточное хранение media dataset. Все управляемые оригиналы из data/media/files/ версионируются в Git вместе с manifest; локальный клон содержит полный исследовательский набор. Пользовательские скриншоты остаются в MinIO/S3, а наличие файла в Git не означает разрешение на публикацию без статуса approved.
  • B17 · Полный каталог рыб RF4DB — финальная загрузка. Разрешённый RF4DB дал все 252 подписанных кандидата; прямые альтернативы обработаны с общим 30-минутным cooldown, а утверждённые версии опубликованы только после отдельного review. Очередь fish-кандидатов пуста: 226 старых версий сохранены как superseded, один provenance-only duplicate не связан с низкоразрешённым published-файлом. Один 48×48 fallback (Ёрш) сохранён честно из-за отсутствия проверенной альтернативы.
  • B18 · Каталог водоёмов RF4DB. После cooldown проиндексировать 19 русских карточек водоёмов, проверить полноразмерные карты и поставить только уникальные изображения в media queue.
  • B19 · Каталог снастей RF4DB. Подготовительный media-контур; полный справочник, crosswalk, связи с уловами и публичные страницы выполняются по пакету G01G09 ниже. Не считать каталог полным до получения проверяемого общего счётчика по категориям.
  • B20 · Аудит качества рыбных изображений. Offline media_cli --quality-report проверяет фактические dimensions опубликованных файлов, отдельно считает неизбежный upscale в карточках и известные альтернативы. После quality-upgrade: 253 опубликованных fish-файла, 252 имеют 1024×1024 WebP, один fallback (Ёрш) остаётся 48×48 и увеличивается до 180 px; новых известных альтернатив для него нет. Совпадение сущности больше нельзя считать достаточным основанием пропустить потенциально более качественный файл.
  • B21 · Очередь quality-upgrade. Текущие approved-файлы не менялись до готовности замены. Очередь из 226 прямых RF4DB-альтернатив обработана с сохранением duplicate_of: все 226 кандидатов прошли upgrade_stored и затем были атомарно продвинуты в approved, старые версии сохранены как superseded. Один provenance-only duplicate, не связанный с низкоразрешённым published-файлом, намеренно не переводился. Ошибка останавливает домен по существующим правилам; публикация старой версии при этом не прерывается.
  • B22 · Сравнение вариантов разных источников. Offline-команда --compare-quality-upgrades сопоставила сохранённые кандидаты с опубликованными fallback по duplicate_of и проверила dimensions, размер файла, MIME, прозрачность и aspect ratio. Все 226 кандидатов прошли технические пороги и offline contact-sheet review; источник RF4DB/RF4MAP/официальный RF4 не получал автоматического приоритета. После публикации в manifest не осталось upgrade_stored, поэтому текущий повторный запуск команды корректно сообщает compared: 0.
  • B23 · Безопасное продвижение и provenance. Связи supersedes/replaced_by, отдельное решение review, атомарное переключение публичного entity_key и явный CLI rollback сохраняют обе исходные ссылки и возможность отката. Публичная карточка показывает источник выбранного изображения, а manifest сохраняет проверенные варианты. Старый Git-файл не удаляется при включении нового.
  • B24 · Производные размеры. После выбора оригиналов генерируются детерминированные WebP/AVIF thumbnails для каталога и отдельный крупный вариант для detail, хэши производных фиксируются в manifest и отдаются через каталог. Маленькие карточки больше не обязаны загружать 1024×1024 оригинал.
  • B25 · Визуальная приёмка media. Contact sheets для всех 226 замен собраны и просмотрены offline: явных ложных соответствий, обрезки и искажённых пропорций не обнаружено; одно отличие подписи (Ерш-носарь/Ёрш-носарь) орфографическое. In-app Chromium проверил 12 комбинаций /media (320/390/768/1280 px × light/dark/system): горизонтального overflow нет, у 253 изображений есть alt, broken loaded images — 0, переключение темы корректно меняет data-theme; automated media acceptance теперь отдельно проверяет loaded assets и разделы waterbody/tackle/reference, keyboard/focus и print-regression проходят, standalone axe — 9/9 маршрутов. Launcher scripts/capture-visual-matrix.mjs успешно создал полную матрицу 156 PNG и manifest.json во внешней директории; остаются ручной review и решение о сохранении reference-артефактов. Gate: нет битых файлов, искажённых пропорций, ложных соответствий, обрезанного объекта и изображений ниже 256 px без явной пометки «низкое разрешение».

Каталог водоёмов, карты и координаты

  • W01 · Canonical-каталог RF4DB. 16.09 через браузерный контекст получен и проверен индекс /ru/maps: 19/19 карточек, source slug/ID, русское название, уровень открытия, число видов рыб и URL изображения сохранены в data/waterbodies/rf4db-catalog-2026-09-16.json. Добавлена команда python -m app.cli import-waterbody-catalog --input ... для применения снимка в БД. Изображения остаются кандидатами без автоматической роли map или cover; detail-данные выполняются отдельно по W02.
  • W02 · Карточки водоёмов RF4DB. Строгий fixture-based parser rf4db-waterbody расширен под реальный DOM /fishes/ и /positions/, regression-тесты, безопасный update_waterbody_detail и атомарный batch update_waterbody_details/import-waterbody-details готовы. Snapshots level_000_home, level_001_lake и level_002_kirzah_river сохранены. Добавлены guarded launcher scripts/fetch-waterbodies-chromium.mjs для обычной Chromium-сессии и offline-режим scripts/fetch-waterbodies.py --html; оба сохраняют snapshots атомарно, не импортируют их автоматически и используют общий cooldown, а Chromium-launcher сначала делает отдельную reserve-only операцию. Повторный разрешённый запрос level_002_kirzah_river 20.09 вернул совпадающие с canonical-каталогом имя, 29/29 видов и два URL изображений; snapshot импортирован в Compose PostgreSQL, /health и /ready успешны, публичная /waterbodies/р-вьюнок отвечает 200. Описание и позиции источник не вернул, поэтому они оставлены null/пустыми без домыслов; изображения остаются только provenance-кандидатами и не публиковались. Три разрешённых запроса level_019_american_pond 20.09 завершились 403 Forbidden, а текущий Chromium показывает защитную страницу Cloudflare: это блокировка HTTP-клиента источником, не ошибка атомарного сохранения launcher-а; snapshot и импорт не создавались, прежние подтверждённые данные и media не менялись. Одно промежуточное разрешённое окно 20.09 было зарезервировано CLI, но завершилось локальной DNS-ошибкой до ответа источника; повтор с сетевым доступом был остановлен общим cooldown до запроса. Следующий сетевой запуск допускается только после общего cooldown; полнота ответа остаётся неподтверждённой. CAPTCHA/challenge не автоматизируются. Осталось последовательно разобрать 16 detail-страниц.
  • W03 · Модель и provenance. В модель Waterbody, API-каталог и миграции 0017/0019/0020 добавлены nullable-поля provenance, счётчик видов и detail-факты; идемпотентный upsert применён к canonical snapshot: 19/19, без missing/duplicate/provenance issues. Осталось применить подтверждённые detail snapshots и отдельно разделить игровые и редакционные тексты при подключении detail-данных.
  • W04 · Классификация изображений. Добавлены допустимые роли waterbody_cover, waterbody_map, waterbody_depth_map, waterbody_screenshot и проверка их назначения только через review для canonical waterbody. Кандидаты по-прежнему не получают роль автоматически. Осталось наполнить очередь detail-изображениями и провести contact-sheet review с проверкой dimensions, MIME, SHA-256, соответствия названию и источника.
  • W05 · Crosswalk источников. Добавлен offline-конструктор консервативных предложений: нормализуются только точные имена/алиасы, неоднозначные и unmatched строки не получают canonical key; отсутствие ID выдаётся лишь диагностикой и не считается удалением. RF4MAP/RF4-STAT directory parsers теперь преобразуются в crosswalk identities без переноса метрик или автоматической привязки. Осталось подать полный набор реальных RF4DB/RF4MAP/RF4 Posts identities и вручную подтвердить результаты, включая три ранее отмеченных отсутствующих RF4MAP объекта.
  • W06 · Координаты и точность. В ExternalObservation, staging, provenance опубликованного улова и публичных activity/spot-ответах добавлены coordinate_raw, coordinate_precision = exact | approximate | area | missing и список источников; RF4DB/RF4-STAT/RF4MAP/RF4 Posts parsers теперь протягивают исходную строку, включая строки без доступных числовых координат. Исправлена spot detail: точность (точные/приблизительные/район/не указаны) теперь видна рядом с координатами и покрыта smoke regression. In-app Chromium и focused smoke подтвердили /spots/vyunok-321x654 на desktop/mobile без overflow; добавлен browser regression для отсутствующей точки без ложных координат. Осталась полная browser QA матрица для всех precision/error-состояний.
  • W07 · Публичный API и страницы. API и detail-страница теперь выводят подтверждённые detail-факты водоёма: описание, уровень, количество видов, алиасы, число ссылок на точки и отдельный счётчик изображений-кандидатов; источники и непроверенные media не смешиваются. Осталось подключить только проверенные waterbody media roles и завершить browser QA, включая различение карты, заставки и абстрактного отпечатка.
  • W08 · Приёмка и эксплуатация. Fixture-based parser tests, offline catalog/media audit и повторный idempotent import подтверждены: created=0 updated=19, в PostgreSQL ровно 19 RF4DB waterbodies плюс 2 legacy-записи, media audit сообщает issues=[] и orphaned_files=[]. In-app Chromium и focused smoke покрывают desktop/mobile основные страницы; 20.09 targeted waterbody E2E подтвердил detail с пустой активностью, not-found waterbody и not-found waterbody/fish pair, а spot regression — unavailable state без ложной координаты; полный web E2E прошёл 27 passed, 1 skipped, visual-matrix также прошёл. Остаются полная browser QA error/empty matrix и закрытие внешних detail-данных. Сетевые тесты не выполнять; регулярный импорт оставить opt-in и под общим cooldown/backoff.

Каталог снастей, наживок и прочей оснастки

Этот пакет повторяет жизненный цикл W01–W08, но не смешивает разные уровни описания. Наживка/приманка — предмет, который указан в улове; снасть — компонент комплекта (удилище, катушка, леска, крючок и т. п.); оснастка — собранная схема или монтаж (например, донная, поплавочная, method). Число найденных изображений приманок не считается размером полного каталога.

  • G01 · Canonical-каталог RF4DB. Offline-контракт и строгий parser готовы для категорий bait, lure, rod, reel, line, hook, rig, float, sinker, other: сохраняются исходный slug/ID, русское название, категория, подкатегория, бренд, семейство, игровой уровень и source URL; duplicate ID и неизвестная категория отклоняются. Общий count остаётся unknown, пока не получен разрешённый реальный индекс; fixture не считается каталогом.
  • G02 · Карточки предметов и оснасток. Fixture-based parser списка и detail-страницы готов: характеристики, варианты, совместимость, изображения и связанные типы монтажа сохраняются; атрибуты различают фактический 0, not_applicable и missing. Parser fail-closed при неполном/изменившемся DOM; реальные detail-запросы выполнять только для выбранных карточек и с общим 30-минутным cooldown домена.
  • G03 · Модель и provenance. Добавлены отдельные tackle_item, rig и rig_component с миграцией 0021; legacy bait и catch_report.bait_id не изменялись. tackle_item хранит category, subcategory, brand, family, unlock_level, source identity, timestamp и raw_payload; rig_component хранит роль, порядок, исходное значение и optional canonical item. Связи с catch-потоком добавлены в G05; безопасный backfill остаётся только после реального crosswalk.
  • G04 · Crosswalk и нормализация. Добавлен offline gear_crosswalk: нормализация регистра/пробелов/е, точное имя или alias плюс совместимая категория, консервативная проверка brand/family. Неоднозначные, несовместимые, брендовые и unmatched-строки получают review-статус без canonical key; исходное значение сохраняется. Остаётся подать реальные RF4DB/RF4MAP/RF4 Posts identities и вручную подтвердить результаты.
  • G05 · Связи с уловами и источниками. Добавлены catch_tackle_component и offline gear_components: можно сохранять несколько unresolved/canonical компонентов с ролью, порядком, исходным значением, source identity и raw_payload; legacy bait_id не меняется. Parser сохраняет порядок оборудования из detail и разделяет bait/rig в catch-полях. Запись компонентов подключена к community import, официальному импорту и пользовательской форме; повторная обработка идемпотентна. Canonical-привязка и безопасный backfill остаются только после подтверждённого crosswalk.
  • G06 · API и публичный каталог. Добавлены пагинированный /api/v1/tackle/items с фильтрами по категории, бренду, семейству и уровню, detail endpoints для предмета и монтажа, а также ordered tackle_components в ответе уловов точки. Ответы показывают только канонические характеристики, provenance, timestamp проверки и missing_fields; рейтинг эффективности не добавляется. Остаётся подключить публичные Astro-карточки и ссылки из всех нужных представлений улова.
  • G07 · Аналитика сочетаний и рекомендации. Добавлен /api/v1/analytics/tackle: approved-наблюдения группируются по роли и исходному компоненту с фильтрами водоёма, рыбы, метода и окна; дубликаты одного улова не увеличивают счётчик, а минимум наблюдений и независимых игроков отделяет факт использования от рекомендации. Пустая или малая выборка получает insufficient_data; decay по свежести и отдельные UI-состояния остаются следующим шагом.
  • G08 · Медиа и качество. Добавлены отдельные reviewed-роли tackle_card, tackle_detail, rig_diagram, tackle_screenshot для tackle, а также CLI-параметр --media-role; offline audit отклоняет неизвестную роль и несовпадение роли с entity type. Существующие dimensions, MIME, SHA-256, прозрачность, aspect ratio, provenance и атомарное продвижение сохраняются. Остаётся провести реальный contact-sheet review для будущих tackle-кандидатов без автоматической публикации.
  • G09 · Приёмка и эксплуатация. Добавлены fixture/regression tests для crosswalk, media roles, идемпотентных компонентов, пустых результатов и многокомпонентных наблюдений; каталог /tackle включён в visual-matrix, narrow smoke и accessibility routes. Offline catalog/media audits и сохранение старых данных при сбое импорта проходят. TEMP-only query-plan gate для фильтра каталога и группировки сочетаний использует индексы и укладывается в 250 мс. Chromium подтвердил empty-state публичного каталога и отсутствие overflow на 320 px; обычный E2E пропускает bootstrap без явных переменных, отдельный bootstrap с токеном проходит. Остаются HTTP/browser acceptance для неоднозначных и многокомпонентных комплектов, ручной visual review и проверяемый счётчик по категориям либо явный unknown. Сетевые тесты не выполнять; импорт оставить opt-in, последовательным и под общим cooldown/backoff.

Тёмная тема

Реализовывать последовательно: сначала семантическая палитра и системный режим, затем ручное управление и полировка компонентов. Тёмная тема должна сохранять полевую эстетику RF4 Spotter, а не быть механической инверсией светлой.

  • D01 · Семантическая палитра и color-scheme. Жёсткие цвета и конфликтующая роль исторического --deep проинвентаризированы; добавлены независимые canvas/surface/elevated/control, text, border, shadow и контрастные status-роли для light/dark. Базовые public/admin поверхности получили первый dark-layer, нативные controls используют color-scheme: light dark; стратегия миграции зафиксирована в dark-theme.md.
  • D02 · Системный режим без вспышки. Первый визит следует prefers-color-scheme полностью через CSS: серверный HTML сразу совместим с системной темой, состояние не хранится, inline bootstrap не добавлен и CSP не ослаблена. Явное переопределение появится только вместе с D03–D04.
  • D03 · Переключатель темы. В header добавлен доступный трёхпозиционный выбор «Системная / Светлая / Тёмная»; на узких экранах он сохраняет три понятные иконки и доступные названия. Кнопки работают с клавиатуры, имеют видимый focus и синхронизируют aria-pressed.
  • D04 · Сохранение и SSR-согласование. Выбор хранится год в allowlist-cookie rf4-theme с SameSite=Lax и Secure на HTTPS; Astro SSR выставляет data-theme до отрисовки, а системный режим оставляет выбор браузеру. Собранный Astro client script переключает тему без inline-кода, localStorage и ослабления CSP.
  • D05 · Темизация компонентов и графики. На семантические light/dark-токены переведены базовые и admin-поверхности, header, каталог, breadcrumb-леска, ссылки сущностей, таблицы, формы, moderation-карточки, provenance/quality/status badges, паспорта данных, pagination, empty/loading-состояния, радар, timeline, силуэты и глифы; добавлен forced-colors layer. In-app Chromium подтвердил переключение light/dark/system на медиатеке во всех четырёх ширинах; automated media acceptance отдельно подтверждает light/dark на 320 px, standalone axe проходит 9/9 маршрутов плюс 4 доменных маршрута, forced-colors и print regression подтверждены. Полная screenshot-матрица 156 PNG захватывается launcher-ом без document overflow; остаётся вручную проверить тени/градиенты и выбрать сохраняемый reference-набор. Источники и статусы сохраняют текстовые подписи и не различаются только цветом.
  • D06 · Метаданные браузера и CSP. Добавлены парные theme-color для системной light/dark схемы; явный cookie-выбор согласуется с SSR и мгновенно переключает активный meta-тег. PWA manifest использует устойчивый тёмный brand chrome и splash background. Production build сохраняет inlinedScripts: []; новые inline script/style/attributes не появились, ослабление CSP не потребовалось.
  • D07 · Визуальная и accessibility-приёмка. In-app Chromium подтвердил отсутствие горизонтального scroll на публичных /, /waterbodies, /records, /report, /status, /media, /rules, waterbody detail /waterbodies/р-вьюнок и fish detail /fish/pike, а также admin /admin, /admin/moderation, /admin/external-sources, /admin/media в 320/390/768/1280 px; исправлены tablet header и media-grid overflow. Автоматизированный visual-matrix regression и scripts/capture-visual-matrix.mjs подтверждают 13 маршрутов × 4 ширины × 3 режима (156 комбинаций): document overflow отсутствует, везде есть main/h1, SSR-тема совпадает; полная screenshot-матрица захвачена во внешнюю директорию. Для media и двух detail-страниц light/dark/system корректно переключаются, broken images — 0; axe 9/9, отдельная domain accessibility-проверка добавлена для /waterbodies, waterbody detail, spot detail и /media, keyboard/focus, forced-colors и print-regression проходят. Остались ручная проверка shadows/gradients и выбор сохраняемого reference-набора. Для обеих тем обеспечить WCAG AA, отсутствие горизонтального scroll и CLS, reduced motion и переключение без потери введённых данных; сохранить эталонные screenshots и краткий отчёт.

UX/UI и конкурентное преимущество

Результаты сравнительного аудита и ссылки сохранены в ux-competitive-audit.md. Целевой критерий проекта: RF4 Spotter должен быстрее и честнее отвечать на вопрос «куда идти, на что ловить и почему этому можно доверять», чем каталог или лента community-постов.

  • U01 · UX-контракт и scorecard. Контракт маршрутов, смысловой порядок ответа, словарь статусов и целевые метрики зафиксированы в ux-contract.md; для home, spot, waterbody, plan и tackle описаны первый ответ и обязательное объяснение. Осталось провести ручной task-based review и собрать completion rate на пилоте.

  • U02 · Главный сценарий «рыба → водоём → точка → снасть». Главная уже сохраняет рыбу, водоём, период и сортировку в shareable URL и после поиска явно показывает контекст запроса, включая мобильный режим; empty-state предлагает вернуться к полному набору данных. Осталось показать 3–5 вариантов с понятным CTA и добавить режим map. Acceptance: первый полезный вариант виден без регистрации, back/refresh сохраняют контекст, mobile не теряет фильтры.

  • U03 · Evidence/trust card. Общий evidence-контракт используется на activity-карточках, detail точки, водоёма и карточках снастей: freshness с текстовым Свежо/Устарело, completeness, confidence/статус, source badges и доступные доменные поля; targeted E2E и unit-тест проверяют публичные пути и 48-часовой порог. Осталось добавить период выборки, конфликт источников и правило минимальной выборки для любых рекомендаций. Нельзя показывать «лучшую точку» или AI-like рекомендацию без объяснения и минимальной выборки.

  • U04 · List/map и progressive disclosure. List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Следующий шаг — единый list/map-контракт после подтверждения геометрии; на mobile карта должна открываться отдельным действием. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу.

  • U05 · Mobile-first и сохранённый план рыбалки. /plan поддерживает список до 5 локальных вариантов, удаление, очистку, переход к точке, print/PDF и восстановление из shareable URL; кнопка «Поделиться планом» использует native share или clipboard fallback. Detail-кнопка сохраняет данные с aria-pressed и восстанавливается после reload. Осталось сделать отдельную проверку print на 320/390 px и расширить сравнение полями метода/риска.

  • U06 · Контентная и визуальная иерархия. Для detail точки действие «Что взять» выделено отдельным заголовком, а provenance и качество собраны в общем паспорте данных; статусы дополнительно передаются текстом, не только цветом. Осталось сократить конкурирующие цифры на карточках и провести ручной review на 5 ключевых маршрутах.

  • U07 · UX-приёмка и измерения. Playwright-контракты уже покрывают query journey, evidence states, saved plan, share/print, accessibility и visual matrix; unit-контракт добавил детерминированный stale-порог. Их критерии собраны в ux-contract.md. Осталось добавить отдельные blocked/insufficient-data fixtures, сохранить reference screenshots и получить production Lighthouse/CLS/INP/time-to-first-useful-answer после пилота.

  • Q01 · Документы источников — реестр готов, нужны первичные подтверждения. Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.

  • Q02 · Управляемое удаление источника. Изменение ранее опубликованной записи возвращает её в staging, сбрасывает сопоставление и переводит связанный улов в pending с новой версией решения. Результат проверки хранится как available, missing, temporary_error или blocked: только подтверждённый missing отзывает публикацию, временная ошибка и блокировка остаются диагностикой. Повторное появление требует ручного подтверждения. Withdrawn-записи исключены из публичной активности, а статус и время проверки доступны в admin provenance и журнале решений. Отдельного сетевого обхода нет: Q03 подключит эту реакцию к разрешённому плановому запросу.

  • Q03 · Целостность ссылок. Результат уже разрешённого scheduler-запроса классифицируется без retry и дополнительного HTTP: 404/410missing, 401/403/429blocked, остальные сетевые/парсерные сбои — temporary_error. Результат применяется только к наблюдениям с точным совпадением source system и запрошенного URL. Отсутствие записи в агрегатном списке намеренно не считается удалением; появившиеся в успешном ответе записи отмечаются available обычным staging-проходом. Все попытки по-прежнему резервируются до запроса и расходуют общий cooldown домена.

  • Q04 · Состояния ожидания. Асинхронные admin-очереди получили каркасные карточки, aria-busy, очистку при ошибке и поддержку prefers-reduced-motion. Публичные страницы остаются SSR и не показывают искусственный skeleton; форма уже блокирует повторную отправку и сообщает «Отправка…».

  • Q05 · Базовая визуальная матрица. Главная проверена в браузере на 320/390/768/1280 px, ключевые public-маршруты — на 320 px; удалён корневой min-width, создававший горизонтальный scroll. Добавлен E2E-контракт для /, records, report, waterbodies, status и видимого skip-link. Ширина 320 px также покрывает reflow, эквивалентный 200% zoom для окна 640 px. Расширенная матрица наполненных/длинных/error-состояний остаётся постоянной частью приёмки UI, а не отдельным блокером.

  • Q06 · Performance baseline. На локальной production-сборке после оптимизации hero: performance 100, LCP 1,66 с, FCP 1,15 с, CLS 0,023, TBT 9 мс. Устранены найденные Lighthouse проблемы контраста и accessible name; методика и бюджеты записаны в performance-baseline.md. Полевой INP измеряется только после запуска.

  • Q07 · Нагрузочная методика. Добавлен read-only runner для activity, records, staging и moderation с warm-up, p50/p95/max, распределением HTTP-кодов и ограниченной concurrency. Методика фиксирует контекст запуска, ступени нагрузки и бюджеты, но не объявляет результатов до трёх прогонов на целевом сервере.

  • Q08 · Политика MinIO. Production bootstrap создаёт bucket и отдельную policy только с bucket location/list и get/put/delete его объектов. App credentials проверяются через bucket stat и отрицательную проверку глобального list; API больше не требует ListAllMyBuckets и не пытается создавать bucket. Root credentials остаются только у init-задачи.

  • Q09 · Release-процедура. Миграции вынесены из API runtime в одноразовый migrate service; API запускается только после успешного Alembic upgrade. Документированы backup, rollout и два варианта отката. Изолированный drill поднимает предыдущую ревизию схемы, добавляет контрольные данные, обновляет до head и проверяет их сохранность и новые колонки.

  • Q10 · Документальная ревизия. README и активный ROADMAP сверены 13.09.2026 с тестами, Alembic head, CSP и локальными media_cli --audit/--coverage; устаревшие числа исправлены, исторические аудиты не возвращены в backlog. Дальнейшее обновление обоих файлов остаётся обязательным правилом каждого пакета.

Дополнения после ревизии PROJECT_AUDIT_2026-09-10

Аудит выполнен на старой базе 13e04e6; рекомендации ниже повторно проверены по текущей ветке. Уже реализованные или неприменимые предложения не возвращаются в backlog.

  • Q11 · Декомпозиция API. Catalog, activity/spots, records/community/status/import-history, submissions и весь admin API вынесены в отдельные APIRouter. main.py оставляет composition root, middleware, health/readiness и временные совместимые экспорты rate-limit для тестового контракта; URL и OpenAPI сохранены.
  • Q12 · Query-plan gate. Воспроизводимый TEMP-only fixture создаёт 100 000 уловов, 5 000 точек, 252 рыбы и 19 водоёмов без изменения рабочей БД; gate снимает EXPLAIN (ANALYZE, BUFFERS) для activity, records count/page, spot detail и public spot pages, требует index scan у селективных путей и бюджет 250 мс. Повторный прогон: 1,58 / 13,66 / 0,55 / 0,14 / 29,61 мс. Планы подтвердили существующие индексы; новый fish_id-индекс, SQL-агрегация и materialized view не добавлялись без оснований. Production p95 остаётся задачей после сервера.
  • Q13 · Production bootstrap в CI. Отдельный workflow запускает deploy/test-production-bootstrap.sh вручную или раз в неделю, а не на каждом push. Вывод bootstrap всегда сохраняется 14 дней; при падении добавляются Compose status и Playwright diagnostics.
  • Q14 · Полная CSP. Каждый Astro SSR-ответ получает отдельный nonce для динамического JSON-LD и собственный строгий CSP; FILES_DOMAIN валидируется как hostname. Production запрещает inline handlers, unsafe-inline, eval, wildcard и HTTP; page scripts/styles остаются same-origin _astro-ассетами, style/script attributes запрещены. Caddy сохраняет upstream policy и использует строгий fallback для API. Bootstrap проверяет совпадение nonce на главной, report и admin и доступность OG; реальный signed screenshot проверяется после наполнения production MinIO.
  • Q15 · Частичная деградация главной. SSR независимо получает activity, community signals и оба справочника через settled-результаты. Отказ секции показывает собственный StatePanel, сохраняет остальные данные и HTTP 200 с X-RF4-Partial/Cache-Control: no-store; только отказ всех четырёх частей возвращает 503, Retry-After и noindex. Client-side loading и optimistic UI не добавлялись.
  • Q16 · Контракт OpenAPI. apps/api/openapi.json перегенерирован из текущего FastAPI-контракта; apps/api/export_openapi.py --check проходит (36 paths). В artifact добавлены media decision/rollback schemas и quality-upgrade routes; ручное редактирование не использовалось.
  • Q17 · Эксплуатационные документы. Зафиксированы ADR по Astro/FastAPI/PostgreSQL, локальному cache, scheduler/cooldown и разделению PostgreSQL/MinIO. Incident runbook покрывает заполнение диска, отказ PostgreSQL/MinIO, зависшие импорты, ошибки миграций, компрометацию секретов и критерии закрытия без опасных reset/recreate операций.
  • Q18 · Минимальная observability. Зафиксированы дешёвые SLI, стартовые пороги и источники для request count/latency/5xx, moderation/staging depth, возраста импортов и инфраструктуры. Начальный контур использует JSON-логи, readiness, diagnostics и host monitor; exporter выбирается после покупки сервера, tracing и Prometheus не вводятся без измеренной потребности. Перед альфой остаётся подключить реальный канал и проверить critical/recovery alert.

Административная панель

  • M01 · Усиление административного входа. Сохранён двойной барьер Caddy Basic Auth + API Bearer; внешние ссылки ограничены http/https, UI завершает сессию после 15 минут бездействия, предоставляет явный выход и возвращает вход после 401. Неуспешная API-авторизация ограничена постоянным счётчиком по HMAC-идентификатору клиента с учётом доверенного proxy; успешный вход очищает ошибки клиента. Bearer-токен не сохраняется в URL, cookie или browser storage.
  • M02 · Единый dashboard. /admin показывает счётчики pending-уловов и staging-наблюдений, число активных источников, их безопасные публичные статусы, последние импорты и быстрые переходы в очереди. Dashboard использует тот же memory-only токен и 15-минутную сессию, не выводит секреты, внутренние URL и полные тексты исключений.
  • M03 · Эффективность очередей. Очередь внешних наблюдений получила серверные фильтры по источнику и полноте, безопасный поиск по рыбе/водоёму и сортировку по свежести или риску; проблемный порядок поднимает неполные и несопоставленные записи, а параметры работают до пагинации. Обе очереди блокируют всю карточку на время решения, сохраняют введённую причину при ошибке, явно подтверждают успех и переводят фокус к следующей записи. Безопасные горячие клавиши работают только внутри карточки с фокусом и отключены в полях ввода; отклонение и удаление намеренно оставлены только на кнопках.
  • M04 · Полный provenance и история решений. Admin API отдаёт время первого/последнего обнаружения и проверки, явный список missing fields и allowlist безопасных скалярных полей исходной записи; карточка показывает их перед публикацией. Единый read-only журнал объединяет решения по пользовательским и внешним записям без ников, URL и исходных payload и отображается на dashboard. Отдельный JSON-экспорт исключает также UUID сущностей, оператора и свободный текст причины; токен остаётся только в памяти вкладки.
  • M05 · Защита от параллельных решений. Обе очереди отдают moderation_version; mapping/publish/reject/approve/delete требуют увиденную версию и повторно сверяют её под row lock. Успешное решение атомарно увеличивает version, а устаревшая вкладка получает понятный 409 и автоматически перезагружает очередь. Схема обновляется линейной миграцией 0015.
  • M06 · Персональные роли — после пилота. Если модераторов станет больше одного, заменить общий токен индивидуальными аккаунтами, короткими сессиями, отзывом доступа и ролями; писать идентификатор оператора в аудит. Для одного владельца альфы не добавлять отдельный auth-сервис заранее.

План доведения административной панели

Этот план фиксирует следующий рабочий контур поверх уже закрытых M01–M05. Текущий MVP функционален для одного владельца альфы, но ниже перечислены эксплуатационные пробелы, найденные аудитом, и критерии их закрытия.

  • A01 · Защита маршрутов и границы сессии. Покрыть точный /admin и /admin/* единым Caddy Basic Auth, выставлять noindex и no-store для всех административных ответов, проверить отсутствие обхода через API и корректные 401/429. Критерий: автоматический proxy-smoke для /admin, страниц и /api/v1/admin/* с отсутствующим, неверным и валидным доступом.
  • A02 · Единая auth/error UX. Привести dashboard, moderation и external sources к одинаковому поведению при 401, 409, 429, 5xx, loading/empty-состояниях: понятное сообщение, блокировка повторной отправки, возврат к входу только при истёкшей авторизации. Критерий: regression-тесты на каждый ответ и сохранение введённой причины.
  • A03 · Полный single-owner workflow. Добавить пагинацию очереди уловов, кнопку запуска официального импорта и отображение результата/истории, безопасные статусы источников с возрастом данных, cooldown/backoff и ручное обновление очередей. Критерий: владелец может пройти путь «импорт → проверка → решение → история» без API/CLI; старые данные сохраняются при сбое импорта.
  • A04 · Контур медиа-проверки. Admin-экран показывает approved/upgrade_queued/upgrade_stored медиа, источник, размеры, производные и provenance. Защищённые действия требуют непустую причину: publish атомарно продвигает только проверенные upgrade_stored и сохраняет fallback, rollback принимает только связанную пару approved/superseded; небезопасные состояния возвращают 409. API-тесты проверяют auth, валидацию причины и вызов безопасных операций; browser acceptance остаётся в A06.
  • A05 · Многопользовательский доступ. После пилота заменить общий Bearer-токен персональными аккаунтами и короткими серверными сессиями с отзывом, ролями read-only/moderator/importer/owner, operator ID в аудите и журналом входов. Критерий: минимальные права реально ограничивают действия, logout/revoke инвалидируют сессию на сервере.
  • A06 · Browser/accessibility acceptance. In-app Chromium подтвердил /admin, moderation, external sources и media в 320/390/768/1280 px без горизонтального overflow; четыре admin-маршрута имеют landmarks, подписанные поля, именованные кнопки и alt у изображений. Visual-matrix regression покрывает 13 маршрутов × 4 ширины × 3 режима (156 комбинаций) без document overflow и с совпадающей SSR-темой. Standalone axe проходит 9/9 маршрутов без serious/critical violations, keyboard/focus regression не оставляет фокус на BODY, forced-colors сохраняет видимую границу theme controls, print-regression сохраняет светлую схему и убирает фильтр hero image. Остаются screenshots и ручная визуальная проверка shadows/gradients; headless gate больше не блокируется sandbox при escalated запуске.
  • A07 · Production gate. Выполнить preflight с реальными секретами, проверить Caddy Basic + API Bearer, закрытые внутренние порты, backup/restore PostgreSQL и MinIO, readiness, no-store и внешний smoke после деплоя. Локальный чистый-volume bootstrap и оба Playwright-сценария подтверждены; критерий A07 остаётся открытым до acceptance-runbook с реальными секретами, backup/restore, rollback и процедурой отзыва доступа.

Готовность открытой альфы — требуется сервер или внешний сервис

  • Купить/подготовить Linux-сервер и подтвердить его публичный IPv4/IPv6.
  • Настроить DNS rf4spotter.ru и files.rf4spotter.ru, открыть только необходимые внешние порты и получить корректный TLS через Caddy.
  • Создать .env.production, заменить все демонстрационные секреты и выполнить deploy/preflight.sh.
  • Создать публичные контакты privacy/abuse и подключить их к сайту и alpha-баннеру.
  • Настроить внешний backup, выполнить восстановление с сервера и подключить реальный канал уведомлений.
  • Наполнить альфу небольшим разрешённым набором данных и провести финальную приёмку по open-alpha-acceptance.md.
  • После 24 часов стабильной работы пригласить первых игроков, собрать обратную связь и закрыть блокирующие проблемы пилота.
  • Подключить Search Console/Яндекс Вебмастер и проверить реальные canonical, sitemap, robots, OG и JSON-LD.
  • Снять серверные p95 и Lighthouse; скорректировать индексы, кэш и изображения только по измерениям.

После пилота

  • Решить по обратной связи, нужны ли OCR, Telegram, профили и уведомления о клёве.
  • Рассмотреть cursor pagination при росте объёмов; текущая offset pagination достаточна для альфы.
  • Рассмотреть общую инвалидацию кэша при нескольких API-процессах; сейчас действует TTL и локальная инвалидация.
  • Рассмотреть Redis только при переходе к нескольким API-процессам или после измеренного дефицита локального cache; не добавлять отдельный stateful-сервис заранее.
  • Подготовить тематические обложки и индивидуальные OG для рыб/водоёмов, если страницы подтверждают поисковую ценность.

Правила выполнения

  • Сетевой парсинг одной площадки — не чаще одного раза за 30 минут, включая ошибки и разные endpoint.
  • Каждая публичная запись обязана показывать источник; неполные данные — отдельную пометку и список отсутствующих полей.
  • Реальные источники не используются в тестах: только fixtures и изолированные Docker-сценарии.
  • Docker запускается одним общим прогоном для инфраструктурного пакета, а не после каждой правки.
  • Пункт закрывается только после адресной проверки; команда и результат фиксируются в коммите или отчёте.
  • Не менять целевой стек Astro + FastAPI + PostgreSQL.