Перейти к основному содержимому

UC-CTL-001. Лента проектов (главная страница, бесконечная лента 20–30 карточек)

Назначение

Гость или авторизованный пользователь открывает главную страницу / ленту проектов (ЖК, апарт-отели) и видит бесконечную ленту карточек с ключевыми параметрами: название, адрес, девелопер, диапазон цен, количество объектов, окупаемость. Лента читается из витрины каталога (CatalogProjectCard) и по умолчанию упорядочена как «Рекомендованные». Авторизованные видят точные цены и статусы, гость по реферальной ссылке — маскированные значения.

Metadata

ПолеЗначение
IDUC-CTL-001
Контекстcatalog (см. docs/06-services/catalog/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольГость (с реф-cookie) / Инвестор / Агент / Девелопер / МенеджерПИН
Permissions / scopeчтение ленты — permissions не требуются; гейт: JWT или валидная реферальная cookie referralUrl; авторизованные видят точные цены/статусы, гость — маскированные
Tenantне применяется — витрина platform-level (ADR-0052 п.3); TenantId актора не участвует в выборке
Record-levelне применяется — публичные данные; отбор только по Status = Published

Предусловие

  • Витрина CatalogProjectCard наполнена (есть хотя бы один проект в Status = Published); источник структуры — события pin.chessboard.projects.v1.
  • У актора есть JWT (pin:session) ИЛИ реферальная cookie referralUrl (срок 30 дней).

Триггер

ПолеЗначение
ИсточникUI navigation
Триггерящий элементОткрытие страницы /lots (лента объектов недвижимости — UC-CTL-002..004 тем же контрактом), /hotels (лента проектов-отелей) или главной страницы

Основной Поток

  1. Пользователь открывает главную страницу / /hotels.
  2. Интерфейс проверяет гейт: наличие JWT в pin:session ИЛИ валидной реф-cookie referralUrl; при отсутствии обоих — альтернатива A1 (auth-модалка).
  3. Интерфейс запрашивает ленту проектов с параметрами: тип проекта (kind, enum ProjectKind, опционально), курсор keyset-пагинации (опционально), размер страницы (1..30, по умолчанию 20), поисковый запрос и фильтры/сортировка (см. UC-CTL-002..004).
  4. Каталог проверяет доступ и формирует выдачу:
    • Проверка входа. limit в диапазоне 1..30; cursor — корректный keyset-токен (кодирует PromotionBoost|DeveloperPriority|PopularityScore|PublishedAt|Id последней карточки); kind — известное значение enum. При нарушении — ValidationError (400).
    • Гейт. Актор авторизован ИЛИ реф-cookie валидна; иначе — CATALOG_FEED_GATE_REQUIRED (401).
    • Выборка. Карточки из витрины по условию Status = Published (+ Kind = kind, если задан), порядок по умолчанию — «Рекомендованные»: PromotionBoost DESC → DeveloperPriority DESC → PopularityScore DESC → PublishedAt DESC → Id DESC (формула — ranking-rule.md).
    • Состав карточки. Только витринные поля: Id, название, адрес, девелопер (+лого), диапазон цен priceFrom/priceTo, количество доступных объектов, окупаемость payback {Value, Period}, звёзды, срок сдачи, готовность, рейтинг, обложка, короткое описание (для всплывающей подсказки).
    • Гостевое маскирование. Для гостя по реф-cookie без JWT поля priceFrom/priceTo/propertyObjectsAvailable = null, masked = true.
    • Результат. Список карточек, nextCursor (null, когда страница неполная) и признак hasMore.
  5. Интерфейс рендерит сетку 20–30 карточек и кеширует страницу.
  6. Пользователь скроллит ленту; при достижении конца интерфейс повторяет шаг 3 с cursor = nextCursor — бесконечный скролл, эквивалент «Загрузить ещё».
  7. Наведение курсора на карточку показывает подсказку из короткого описания без запроса к API.
  8. Клик по карточке — переход на страницу проекта: UC-CTL-005 (гость) / UC-CTL-006 (авторизованный).

Альтернативные Потоки

  1. Гейт не пройден (CATALOG_FEED_GATE_REQUIRED): лента блокируется, открывается auth-модалка (UC-IDN-001, СМС-вход).
  2. После входа возвращается к шагу 3 основного потока; гость, пришедший по реферальной ссылке, получает cookie и проходит гейт без входа (маскированные данные).

A2. Пустая лента

  1. Шаг 4 возвращает 0 карточек (нет Published-проектов выбранного kind).
  2. Ответ { items: [], nextCursor: null, hasMore: false }; интерфейс показывает empty-state «Проекты скоро появятся». Сценарий завершается.

A3. Устаревший/повреждённый cursor

  1. Шаг 4: cursor не декодируется → ValidationError (400).
  2. Интерфейс сбрасывает пагинацию и повторяет запрос без cursor (лента с начала). Возврат к шагу 3.

A4. Сессия истекла во время бесконечного скролла

  1. На подгрузке очередной страницы (шаг 6) JWT протух, а реф-cookie отсутствует → каталог отвечает CATALOG_FEED_GATE_REQUIRED (401).
  2. Интерфейс сохраняет позицию скролла, открывает auth-модалку (как A1); после входа повторяет запрос той же страницы с прежним cursor — уже загруженные карточки не перезапрашиваются, лента продолжается без потери позиции. Возврат к шагу 6.

Постусловие

  • Пользователь видит актуальную (eventual, лаг < 1 мин по ценам/статусам — README.md, раздел «Runtime») ленту Published-проектов в порядке «Рекомендованные».
  • Только что опубликованный в источнике проект попадает в ленту после доезда консюмера chessboard и пересчёта витрины (окно секунды–минуты); до этого он в выдаче отсутствует — это ожидаемое промежуточное состояние, не ошибка. Симметрично, скрытый/делистнутый проект исчезает из ленты в том же окне.
  • Состояние системы не изменилось: сущности не создавались и не мутировались; события не публиковались.

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
ValidationError400limit вне 1..30, неизвестный kind, битый cursorСброс параметров, повтор без cursor
CATALOG_FEED_GATE_REQUIRED401Нет JWT и нет валидной реф-cookieОткрыть auth-модалку (A1)

Acceptance Criteria

#КритерийПроверка
AC1В ленту попадают только карточки Status = Published; Hidden/Delisted исключеныинтеграционный тест: скрыть проект оверрайдом → карточка исчезает из ответа
AC2Порядок: PromotionBoost DESC → DeveloperPriority DESC → PopularityScore DESC → PublishedAt DESCтест с карточками разных boost/priority
AC3Keyset-пагинация: страницы не пересекаются и не теряют карточки при вставке новыхтест: 3 страницы по 20 при конкурентной публикации
AC4Гость (реф-cookie без JWT): priceFrom/priceTo/propertyObjectsAvailable = null, masked = true; авторизованный видит точные значениядва запроса с разными акторами
AC5Без JWT и без реф-cookie — 401 CATALOG_FEED_GATE_REQUIREDзапрос без credentials
AC6Ответ ленты собирается из одной витрины catalog.project_cards без джойнов в источникипроверка выборки
AC7Истечение JWT на подгрузке страницы → 401 CATALOG_FEED_GATE_REQUIRED; после входа лента продолжается с того же cursor без потери/дублей карточектест протухания токена в середине пагинации

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные Документы

  • API метод: ../api/public/get-projects.md (контракт ленты/поиска/фильтров/сортировки в одном endpoint-е).
  • Domain entity: ../domain/catalog-project-card.md (дом полей, ленточный индекс), ../domain/ranking-rule.md (формула «Рекомендованные»), ../consumers/promotion-boost.md (PromotionBoost — консюмер promotion).
  • Frontend: страницы /hotels, /lots (гейт, бесконечная лента).
  • Связанные UC: UC-CTL-002 (поиск), UC-CTL-003 (фильтры), UC-CTL-004 (сортировка), UC-CTL-005/006 (карточка проекта); вход по реферальной ссылке — docs/06-services/referral/.