# Передача Cursor — единый список «Мои игры» ## Цель изменения Перенести этот дизайн в существующий consumer /my-games. Успешный normal entry открывает все собственные отношения сразу, полученное приглашение можно принять/отклонить в карточке, прошедшие находятся ниже на той же странице. Owner дал направление единого списка, точные детали composition и label ниже выбраны дизайнером на основании текущих DTO. ## Проверенные точки реализации Все пути относительно `/home/agentagency-dev/owner-venue507-community-i1-on57-20261108`. | Путь | Назначение изменения | |---|---| | apps/web/src/components/consumer/pages/MyGamesPage.tsx | Загрузка обеих текущих relation-проекций и received invitations, page-local join по gameId, filter state, обе временные группы, page states, history anchor. Устранить взаимоисключающую загрузку по relation. | | apps/web/src/components/consumer/games/GameDetailComposition.tsx | PersonalGameCard получает независимые management/participation/invitation/action данные либо scoped children для My Games. Компактная персональная list-композиция, own result и reminder внутри одной gamecard. Existing use sites предварительно проверить. | | apps/web/src/components/consumer/games/PostgameReminders.tsx | Выделить допустимую компактную строку с supplied context/handlers либо переиспользовать её представление в MyGames. Результат для existing results screen сохранить. Получение reminders на MyGames выполняется один раз, с текущими privacy-generation guards. | | apps/web/src/components/consumer/games/GameRecommendationsSection.tsx | Сохранить загрузку/gates/mutation semantics и received/sent. Добавить размещение received по gameId в общей композиции и компактный secondary поток. Пустой default block скрывается только после полного разрешённого read. | | apps/web/src/components/consumer/messages/InvitationDetail.tsx | Reference существующих recipients/actions, без необходимой смены сообщения. При извлечении общей карточки сохранить её текущий сценарий. | | apps/web/src/components/consumer/messages/useSocialMutation.ts | Переиспользовать существующий механизм мутаций и pending identity. Shared hook не требует нового протокола. | | apps/web/src/lib/messaging-api.ts / messaging-types.ts | listInvitations, invitationPath, GameInvitation, AcceptInvitationResult уже доступны. | | apps/web/src/lib/api-client.ts | getMe, getGame, listAllMyManagedGames, listAllMyParticipations, listMyGameResults. Существующие list-all helpers переиспользуются. | | apps/web/src/lib/game-presentation.ts | isPastGame/isTerminalParticipation/managedGameRelationLabel/participationLabel. Для union учитывать активность всех отношений, поскольку один terminal participation не должен прятать активную management game. | | apps/web/src/lib/game-results.ts | findOwnResultForGame/ownResultEndsAtBucket/resultsPath/ownResultPendingNotice и текущая attendance presentation. Preserve contextId внутри объединения. | | apps/web/src/lib/game-reminders.ts | listPostgameReminders/laterPostgameReminder/reminderResultsHref. | | apps/web/src/styles/games.css | Scoped .gb-my-games layout и компактные personal-card modifiers. Удаление sidebar из My Games, responsive rules. | | apps/web/src/styles/postgame.css | Только при необходимости scoped My Games action-row modifier. | Legacy apps/web/src/components/account/MyGamesView.tsx использует fixtures и AccountShell. Он не является текущим consumer entry, используемым screenshot, и не должен стать точкой реализации. ## Последовательность реализации 1. Зафиксировать page state для каждого источника. После успешного getMe параллельно читать managed/participation/invitations/own results/reminders и capability-gated recommendations. Все новые private reads под текущую session generation. Восстановление каждого источника отдельное. Каждый getGame на уникальный gameId максимум один раз в цикле загрузки, запросы с ограниченной конкуренцией по текущему проектному приёму. 2. Внутри page scope агрегировать relationship facts по gameId. Авторитетная managed Game projection имеет разрешённые поля для текущего пользователя. Дополнять узкие own-result/reminder данные только имеющимся разрешённым Game. GameInvitation и recommendation никогда не становятся источником venue/roster. Один сетевой private 404 не должен затирать отдельно доступные собственные итоги. 3. Применить filter predicate к объединённым rows. Temporal bucket зависит от реального времени/status и активных отношений. Одна row видна ровно в одной группе. Старый terminal invitation остаётся вторичным фактом при confirmed participation или current management. 4. Изменить list/card composition с существующими Button/GameStatusBadge/date pattern. Приглашения отображают actionable controls прямо здесь. 5. Прикрепить compact postgame actions. Сохранить results routing и privacy of attendance. Upper attention link ведёт к исторической карточке, при необходимости меняет фильтр на all. 6. Перенести received recommendation в соответствующую карточку или secondary поток, sent — в раскрытие. Сохранить existing recommendation operation scope и recovery. 7. Добавить локальные regression checks только на конкретные риски union, invite in-place, partial read, privacy clear, routing и reminder placement. Обычная пагинация принадлежит каждому источнику. При cursor results/recommendations append повторно пересобирает тот же union, не дублируя игру. Пустой recommendation page с nextCursor требует продолжения. Глобальный total не вычисляется из текущего массива. При позднем arrival нового relation карточка обогащается, scroll/focus по gameId сохраняются. ## API и мутации | Операция | Существующий контракт | |---|---| | Полученные приглашения | GET /v1/me/invitations?limit=50&cursor=… через listInvitations. Источник уже actor-owned. Дочитать cursor, в том числе когда первая страница содержит только terminal invitations. | | Принятие | POST /v1/invitations/:id/accept, body `{expectedVersion, expectedGameVersion}`. Cookie mutation, Origin/CSRF, Idempotency-Key, current game.version. | | Отказ | POST /v1/invitations/:id/decline, body `{expectedVersion}`. Те же session/idempotency fences. | | Результат принятия | `{invitation, participation}`. Подтверждение только при валидном ответе и participation.status === confirmed для той же gameId/identity. После receipt перечитать связанные lists и capacity. | | Рекомендации | Существующие getSocialCapabilities, listGameRecommendations, hide/withdraw и useGameRecommendationMutation. | | Отложить reminder | POST /v1/me/postgame-reminders/:id, body `{command:'later', expectedVersion}` через existing helper. Применить server page. | | Посещение/уточнение | Переход к resultsPath(gameId) / resultsPath(gameId,'clarification'). Запись остаётся в существующем results flow. | Pending invitation не резервирует место. Accept повторно проверяет серверные capacity/version/ownership/social fences. Preview eligibility зависит от доступной published Game, времени/срока, свободных мест и состояния invitation. Backend остаётся источником окончательного результата. Frontend не конструирует participation на основании local click. Scope мутации приглашения согласовать с existing `${me.id}:invitation:${id}`. Нельзя иметь независимые operation identities для одного и того же unresolved intent при переходе между My Games и Messages. Network/5xx сохраняет exact key/body/path. Прочитанный список используется для контекста, его отсутствие не доказывает неуспех предыдущей записи. Существующий MutationNotice можно расположить внутри соответствующей карточки. 409 после изменения условий перечитывает invitation и Game. GAME_FULL отражается отдельным текстом и блокирует accept на обновлённой карточке. Decline остаётся доступным по actual invitation. 401 очищает все приватные данные. 403/404 показывают нейтральное отсутствие доступа и очищают соответствующую проекцию. Private 404 не обходится через новую серверную проекцию в этой работе. ## URL совместимость | Entry | Поведение нового экрана | |---|---| | /my-games | Фильтр all, предстоящие сверху, история ниже. | | /my-games?tab=created | Фильтр manage. Сохранить этот legacy entry или нормализовать replace без потери history state. | | /my-games?view=history | Та же страница, focus/scroll к history после готовности области. Не создавать отдельный мир истории. | | /my-games?tab=created&view=history | Manage filter + history anchor. | | /my-games?rec=sent | Открыть/сфокусировать sent recommendations disclosure. Recovery pending operation сохранён. | | Новые filter links | Можно использовать query `filter=all|manage|participate|invites`, с legacy aliases выше. Invalid значения дают all. | | Возврат из detail/results | Сохранить filter + history/scroll origin существующим backlink механизмом. Проверить test/my-games-detail-backlink.test.ts. | ## STYLE_GUIDE_PARITY Переиспользуются Onest, существующая palette/token scale, ConsumerShell, shared rail, Button, GameStatusBadge, PersonalGameCard family, ListRow/date-badge из OwnResultsHistoryCard, Empty/Error/Skeleton patterns. Desktop сохраняет max 1380, mobile inset 20, 44–48px targets, действующую навигацию и focus. OWNER_VISUAL_OVERRIDE — прямое указание текущего Owner заменяет нынешние три раздельные tabpages и пустые sidebar-панели на единый список с relationship-фильтрами. STYLE_GUIDE_GAP — компактное объединение нескольких собственных отношений и inline invitation/postgame actions внутри PersonalGameCard. Это локальная composition существующих primitives. Фото для короткого персонального списка уступает место date badge, чтобы время и действие были доступны на первом экране. Discovery feed/GameCover hero не затрагиваются. Специальные overlays, если понадобятся для existing actions, продолжают действующий Spacious40/ProfileDialog и mobile sheet. В прототипе новый overlay не вводится. Standalone CSS и demo JS служат design reference, продукт переносит паттерн в существующие styles/components. ## Инварианты существующей системы Capacity, auth/CSRF, idempotency, actor identity, social capability, private 404, accepted friend prerequisite, recommendation expiration, invitation expiration, participation statuses, postgame outcome/attendance/dispute policy, reminder limits, review privacy, chat membership и session-generation guards продолжают определять доступ и результат действий. Раздельные сущности сохраняются в данных. Визуальное объединение выполняется только по одной gameId. Все product/DB/API/backend/notification policy изменения требуют отдельного подтверждённого дефекта и точного scope. Текущая задача решается в UI с уже существующими endpoints. Для private invitation DTO отсутствует безопасный game.version, поэтому degraded state имеет ограниченные действия в рамках текущего API. ## Проверка результата через normal entry | Проверка | Ожидание | |---|---| | Один gameId в managed + participation + invitation + received recommendation | Одна gamecard, все доказанные отношения сохранены. | | Current user и organizer с одинаковым displayName | Роль определяется только IDs/management projection. | | Pending invitation на второй странице | Доступна в основном списке и фильтре invitations. | | Inline accept/decline | Действие без перехода в messages, mutation protocol и корректный receipt/reread. | | Two recipients race last seat | UI показывает результат существующего server fence, ложное confirmed исключено. | | Recommendation без Participation | Открытие обычной game page, заявка только через действующий flow. | | Ноль recommendation при success / недоступный capability | Default пустой sidebar отсутствует. No-data и failed reads различимы при прямом входе. | | Reminder и notice одной игры | Одна историческая gamecard с двумя compact actions. | | Короткий title «44» | Сохранён title, добавлен доступный human date/game context. | | Позже и reload | Поведение существующего reminder cap, никаких новых автоматических resets. | | Own-result orphan при public 404 | Только собственные authorized fields, results path доступен по серверным правам. | | Logout / смена account во время fetch | Поздний response не возвращает старые private data. | | 390 / 820 / 1280 / 1920 | Один поток, readable wrapping, action ≥44px, отсутствует horizontal overflow, bottom nav не закрывает последний action. | | Keyboard и back | aria-pressed, focus после mutation/anchor, сохранён list filter при возврате. | Адресные existing tests для расширения — my-games-results.test.tsx, my-games-recommendations.test.tsx, game-recommendations.test.tsx, invitation-eligibility.test.tsx, my-games-detail-backlink.test.ts и reminder tests, если они покрывают изменённое представление. Full test/CI execution назначает Root в обычном процессе. ## Точные неопределённости 1. Фактические API ответы и runtime flags alpha.58 не читались в этой роли. DESIGN опирается на source и предоставленную версионную привязку. Live UAT — NOT_TESTED. 2. Private invitation может читаться без Game detail. Source показывает отсутствие game.version в invitation DTO. При таком read frontend не может безопасно вызвать accept с текущим контрактом. Макет показывает недоступное принятие и действующий decline, без нового backend scope. 3. MeGameResultsPage не даёт aggregate manager dispute count. Макет не вводит счётчик чужих споров. Собственный dispute допускается только по фактическому attendance.dispute в доступной проекции. 4. Screenshot «44» не доказывает raw database ID. Source выводит gameTitle. Макет сохраняет это как возможное короткое название и даёт ему контекст. 5. Напоминания ограничены текущим list limit. UI может раскрыть загруженный хвост, но общее количество вне полученной страницы не известно. 6. Репозиторные #182/#372 handoff documents содержат исторические SOURCE DRAFT/NOT_TESTED формулировки. Они использованы для contract provenance. Текущая release readiness проверяется Root по receipts, не выводится из этих заголовков.