# #372 · Участники / Итоги — design handoff v1 Статус: **DESIGN_PROPOSED · OWNER_REVIEW_PENDING**. Это дизайн перед PG-1b → PG-2 → PG-3 → PG-4, не продуктовая реализация, не release authorization и не закрытие #372. Owner UAT: **NOT_TESTED**. Независимая QA: **NOT_PERFORMED**. Открыть [index.html](index.html) локально. HTML самодостаточен: CSS, JavaScript и точная копия существующего SVG-логотипа встроены; сеть, backend, библиотеки и сборка не нужны. Внешние ссылки только на соседние handoff-файлы. Все имена, площадка, даты, отметки и квитанции вымышлены. Действия меняют память демосессии; переключение PG/роли/сценария её сбрасывает. Галерейный JavaScript не переносить в приложение. ## Зафиксированные источники - Прямое Owner assignment для senior UI designer Astra High перед реализацией #372; short rules SHA256 `0d529c0d50118f6473d8126df8202aa2201322eebe8e66ed00fe572e06c36b30` передан Owner. Независимая сверка этого digest с внешней системой не выполнялась. - Принятая продуктовая база: `8733578a2f8a6cfd860371e8a2c2aa39f1b885bb`. - Локальный read-only checkout: `a6ae601a2c24de57296e94d8693703230748fee4`, изолированный Swipe #438 candidate. Он не принят этим дизайном и не менялся. `git diff HEAD` показал отличия Swipe/release-history; использованные ниже post-game seams и оба Guide совпадают с принятой базой. Файловые hashes — в [coverage.json](coverage.json). - Прочитаны frozen inputs: `/home/agentagency-dev/train-execution-evidence-20261027/postgame372-design-input/POST_GAME_CONTRACT_V001.md` и `OWNER_DECISION_POST_GAME_MODEL_V001.md`. - Прочитаны действующие `docs/product/SITEWIDE_UI_STYLE_GUIDE.md` (Owner-approved v3) и `docs/product/DESKTOP_OVERLAY_STYLE_GUIDE.md` («Просторный40»). - Дополнительно прочитаны узкие UI/API seams из таблицы ниже, `docs/handoffs/postgame-pg1a-outcome-api.md`, `apps/web/src/styles/consumer-tokens.css`, `apps/web/src/app/layout.tsx`, `nav-items.ts`, `Logo.tsx`, `CloseButton.tsx`. Из локального style-guide прочитаны только сведения о шрифте; удалённый font URL не открывался. - Канонический полный текст Issue через сеть не запрашивался: scope и критерии взяты из exact trusted assignment и двух frozen inputs. Старые architecture/canons не использовались как новые инструкции. PG-1a outcome API уже merged PR391 по Owner input. В исходнике есть GET results и PUT outcome; default writes **OFF**. Ни feature flag, ни runtime, ни DB здесь не менялись. Наличие остальных endpoint-ов в frozen design inventory **не означает их реализацию**. ## Композиция Одна страница игры «Участники / Итоги» внутри существующего ConsumerShell. Сверху Back на общей левой линии с логотипом и H1, затем небольшой контекст игры: дата, название, время с timezone, площадка. Нет повторного GameHero, публичной стены или нового dashboard. Три раздела одного контекста: **Посещение**, **Уточнение**, **Обратная связь**. Селекторы PG снаружи — только инструменты Owner-галереи, не продуктовая навигация. PG-4 открывает существующую «Историю», её карточка приводит в тот же экран. На desktop основная область и спокойная поясняющая колонка. Информационный rail не повторяет все действия. На mobile — один поток, нижняя навигация «Игры / Игроки / Мои игры / Сообщения», один reserve 76px + safe area. Отдельная основная кнопка исправления уже в уведомлении, чтобы её можно было найти до длинной истории. Состав — разделённые линиями строки, не карточки внутри карточек. На 390px имя занимает строку, ниже отметка и «Изменить». Страница владеет прокруткой; у списка нет собственного overflow. В dialog прокручивается только тело, header/X/footer неподвижны. В сценарии «Длинный состав · 16» используется тот же компонент строки и точного подтверждения. Основные токены взяты без изменения из consumer-tokens: paper `#f5f6f2`, ink `#13221a`, lime `#a7f447`, purple `#7945de`, border `#dfe4dc`. Основное действие lime; purple — спокойный акцент уточнения, не наказание за посещение. Шрифт продукта — **Onest**. В предоставленных доступных каталогах локального бинарного Onest нет: приложение использует next/font, style-guide — удалённый URL. В галерее установлен `Onest, system-ui, sans-serif`, без загрузки из сети. На устройстве без Onest будет fallback; это ограничение точности, не новый выбор шрифта. В реализации использовать существующий `--font-onest`, не добавлять внешний font request. Логотип встроен byte-for-byte из `apps/web/public/brand/go-badminton.svg`. ## Роль и видимость | Актор | Видит и делает | Граница | |---|---|---| | Текущий организатор / GameManager | Итог встречи, bounded roster, явные отметки, точное bulk-подтверждение, ответ на возражение, допустимое исправление | Не получает согласие за участника; не закрывает спор своим ответом; не читает чужие peer reviews и private notes | | Участник | Своя отметка, автор/источник/время/версия, durable notice, собственное согласие/возражение/отзыв возражения, история и Support | Нет чужого attendance roster или чужих споров; молчание не меняет данные | | Автор заметки | Своя заметка на допустимом контексте | Frozen contract v001 разрешает создание текущему manager; после отзыва роли — чтение/редактирование уже собственной заметки без доступа к roster. Галерея не расширяет создание заметок всем участникам | | Автор отзыва | Одна запись peer/org/training в своём игровом контексте, редактирование той же записи | Нет self-review, повторного голоса, публичной ленты, skill update, fake verification | | Адресат | Разрешённый полученный отзыв, один редактируемый ответ, контекстная жалоба | Не удаляет отрицательный отзыв; отсутствие/блокировка не лишает автора права обратиться | | Назначенный независимый reviewer | Только материалы назначенного attendance case, обоснованное scoped решение | Отдельный admin audience, capability, step-up, audit; не роль организатора и не общее чтение заметок | `allowedActions` — только UI hints. Все чтения, исполнения и повторы требуют серверной авторизации текущего актора. Private 404/known capability 403 не раскрывают чужие данные. При потере capability очистить чувствительную проекцию; не оставлять roster под полупрозрачным оверлеем. В галерее forbidden заменяет контент целиком. ## PG-1b · Итог и фактическое посещение 1. Outcome задаётся явным `held` / `not_held`. «Позже», закрытие X, время окончания и статус completed не являются командой. Причина обязательна для `not_held` и исправления — по уже существующему PG-1a. Сохранение outcome не делает всех absent. Приехавший на несостоявшуюся встречу может иметь present. 2. Attendance сохраняет `unknown / present / absent`; `late` — только необязательное уточнение present. У отсутствующей записи version 0, нет автора/источника. Самоотметка играющего manager имеет source=self_attested, не independently_verified. Creator без реального места не становится игроком; confirmed participation не доказательство посещения. Отказавшиеся/снятые до начала, rejected/pending исключены из no-show. Eligibility/снимок состава должен определять сервер, не фильтр статусов браузера. 3. «Пришли все»: сначала выбор конкретных людей, по умолчанию ничего не выбрано; затем точные имена/число/версии и отдельное подтверждение. Пакет — immutable `items: [{contextId, expectedVersion}]`, `confirmedExactSelection: true`, all-or-nothing. Никакого «оставшиеся», расширения на новый roster или автоматического выбора организатора. При pagination допускается выбрать только реально просмотренный набор; UI обязан назвать границу загруженного набора. 4. Сохранение отметки публикует участнику durable in-app notice, доступный из «Моих игр» независимо от чата. В проекции видны автор, источник, локализованное время с timezone и версия. Новая версия требует нового согласия; прежнее не переносится. 5. «Всё верно» — явное подтверждение конкретной версии. «Запросить исправление» — отдельное возражение с пояснением. Ни молчание, ни просмотр уведомления не считаются согласием. **Gate:** нельзя включить absent для реальных пользователей отдельно от durable notice, исправления/возражения и ограничений последствий. Если PG-2 ещё не имеет безопасного минимального recourse, соответствующие PG-1b writes остаются OFF; разрешено ранее собрать интерфейс и read projection под выключенным доступом. Дизайн не снимает этот gate. ## PG-2 · Уточнение, история, scoped resolution В открытом уточнении рядом находятся оспариваемая версия и текущая отметка. Организатор может ответить, исправить на present/unknown или повторно заявить absent. **Любой такой ответ сохраняет «На уточнении»**. Исправление не удаляет прежнюю запись и не закрывает возражение. Участник отдельно соглашается с текущей известной отметкой и явно завершает собственное уточнение, либо отзывает возражение с причиной. Withdrawal не переписывает attendance и не означает согласие. Для unknown нет ложного согласия с фактом присутствия/неявки. Неразрешённое дело остаётся открытым и доступным Support. «Помощь по уточнению» явно связывает дело с существующим Support/Safety; само возражение не создаёт report. Квитанция и прогресс — private own projection. Ни срок, ни гарантия решения/санкции не придуманы. Блокировка, social opt-out, потеря чата, отмена/отказ заявки не убирают собственное право обращения. В галерее роль «Проверяющий · одно дело» демонстрирует отдельный служебный экран: ограниченные материалы, основание решения, возможность оставить открытым при недостатке данных. Демо-чекбокс step-up является лишь указателем внешнего серверного gate; в продукте не заменяет авторизацию. Организатор не получает эту кнопку. Результат reviewer — отдельное обоснованное решение, не присвоение actor-ом флага independently_verified. Требует уточнения в существующем контракте перед coding: consumer command ответа организатора/предложения исправления отсутствует в frozen route inventory; exact isolated admin path, case assignment, dual expected versions, step-up/audit, допустимые resolution enum, safe history projection, reviewer-to-party notice. В рамках #372, без нового workflow/framework. ## PG-3 · Обратная связь - Peer: «Было здорово сыграть» → `play_again`, «Всё спокойно» → `neutral`, «Были сложности» → `difficulties`; optional comment. Это опыт конкретной встречи, без уровня/рейтинга игрока. Optional tags пока не добавлены в макет — отсутствие тегов допустимо. - Organization: необязательная 1–5 и комментарий; хотя бы одно поле заполнено. Получатели: автор и ответственный организатор этой встречи. - Training: отдельная вкладка только при **реальном trainer-led контексте**; свободное описание опыта, без skill score. Наличие TrainerProfile само по себе вкладку не открывает. Recipient/visibility фиксируются серверным training context. - При спорной/absent отметке право на отзыв не отнимается: форма просит собственное описание опыта/расхождения. Никакого «проверено» за слова автора или менеджера. - Private note: замок + явное «Только для вас». Сохранение/удаление заметки не изменяет отзыв/обращение. Case staff не получает общей видимости заметок. - Полученный отзыв: один ответ адресата. Изменение обновляет singleton с edited/version marker; нет ветки форума. Жалоба на отзыв открывает существующий Safety flow в контексте review/game. - «Сыграть снова» — отдельное явное действие после/рядом с отзывом, переход в существующий chooser приглашения с его capability/friend gates. Сам выбор `play_again` не отправляет ничего, не создаёт friendship и не резервирует место. В галерее показана граница перехода, chooser не переизобретён. До coding заморозить bounded text/tag limits, rate budgets, creation uniqueness, editing/history rules, recipient permissions, typed report union/backward compatibility. Не копировать демонстрационные textarea без продуктовой validation. Приватные формы не persist в localStorage; черновики привязать к actor/resource и очищать при logout/lost capability. ## PG-4 · Мои игры и общие входы «Участвую / Управляю / История» сохраняются. Существующий disclaimer о заявке/посещении/оплате остаётся. Карточка истории получает вход «Участники / Итоги» с небольшим статусом, не новый публичный game card redesign. Семантика summary: **ждёт ответа**, **не отмечено**, **на уточнении**, **согласовано/решено** — отдельно от held/not_held, Participation и Game lifecycle. Это не итоговый рейтинг. У участника статус только свой. Агрегаты roster доступны только manager; pending/unknown/disputed не превращаются в settled из-за времени. На основной manager-панели «Отмечены» и «На уточнении» могут пересекаться, это разные измерения, не сумма в 100%. История и notices строятся на разрешённой серверной own projection с cursor/limit, не на наличии chat membership, directory opt-in или списка активных participations. Нельзя выдавать первые загруженные записи за полный roster/полную историю. После возврата сохраняются существующие history/popstate/scroll restoration и выбранный контекст. ## Точные source seams для реализации Пути относительны одному CLEAN-репозиторию. Это места интеграции после Owner approval; новые исходники данным заданием не создавались. | Источник | Существующая ответственность → узкое расширение | |---|---| | `apps/web/src/components/consumer/pages/MyGamesPage.tsx` | `Relation`, `View`, `returnPath`, `listAllMyManagedGames`, `listAllMyParticipations`, history disclaimer → разрешённые own results/notices, status/action entry; сохранить фильтры и очистку private data при session expiry | | `apps/web/src/components/consumer/games/GameDetailComposition.tsx` · `PersonalGameCard` | Уже направляет manager в `/my-games/{id}`, participant в `/games/{id}` → добавить contextual results action для этих entry points, без изменения discovery feed | | `apps/web/src/components/consumer/pages/OwnedGameSummaryPage.tsx` | `getMyManagedGame`, `listAllGameParticipations`, pre-start `mutable`, `ManagerApplicants` → единая композиция Participants/Results после игры; не превращать старые participation commands в attendance | | `apps/web/src/components/consumer/games/ManagerApplicants.tsx` | pending/confirmed/history и confirm/reject/remove → сохранить pre-game поведение; post-game roster использует отдельную attendance проекцию | | `apps/web/src/components/consumer/pages/GameDetailPage.tsx` | Обычный путь participant к игре → вход в ту же Results композицию с own audience, сохранить GameActionBar/chat capability | | `apps/web/src/components/consumer/pages/ParticipationReviewPage.tsx` | `intent=withdraw/refuse`, версия заявки и `withdrawParticipation` → **не использовать как отзыв о встрече или отзыв attendance dispute**; это существующая работа с заявкой | | `apps/web/src/components/consumer/ConsumerShell.tsx`, `RootNav.tsx`, `nav-items.ts` | Один content rail, local heading/Back, mobile root navigation и reserve | | `apps/web/src/components/consumer/players/ProfileComposition.tsx` | `SectionHeader`, `Avatar`, `ProfileDialog`: существующая композиция, native dialog/focus/Escape; не создавать новый modal provider | | `apps/web/src/components/consumer/CloseButton.tsx`, `apps/web/src/styles/desktop-overlays.css` | Существующий shared X и desktop skin. CSS-файл указан действующим Overlay Guide; самостоятельно не изменялся | | `apps/web/src/components/consumer/messages/ConversationSafety.tsx`, `apps/web/src/lib/api-client.ts` · `createAbuseReport` | Существующий `/v1/reports`, player payload/UNKNOWN handling → additive typed target/context, сохранить old clients и player no-self policy | | `apps/web/src/app/help/page.tsx` | Существующий Help/EditorialPage entry → Support entry с собственным контекстом. Это не доказательство уже существующих typed case commands | | `apps/api/src/games/game-outcomes.controller.ts` | `GET games/:gameId/results`, `PUT games/:gameId/outcome`, no-store, cookie mutation, idempotency → сохранить PG-1a; расширять DTO только вместе с контрактом | | `docs/handoffs/postgame-pg1a-outcome-api.md` | Реальная текущая граница PG-1a, default OFF, lifecycle/QA restrictions; не объявлять attendance/notices готовыми | | `packages/contracts/openapi.json` + generated clients | Требуемая будущая синхронная правка при реализации; текущий файл целиком не читался и в данном задании не менялся | Новая каноническая URL-форма результатов намеренно не выдумана: общий body доступен из существующих game/managed entries; точную route/query привязку согласовать при admitted implementation внутри #372. Ни `/games/{id}/review` (withdraw/refuse), ни API `/results` не являются автоматически готовой UI-страницей итогов. ## Ошибки и восстановление | Состояние | Поведение | |---|---| | Loading | Нейтральный skeleton, нет фиктивных нулей/успеха и доступной мутации | | Empty / unknown | «Нет отметок», source null/version 0 при отсутствующей записи; не absent | | Error read | Явный retry чтения; не заменять ошибку пустым составом | | Forbidden / private 404 | Закрыть чувствительную проекцию; собственный разрешённый recourse отдельно. Foreign/missing не различать | | Version conflict | Сохранить actor-scoped черновик, показать старую/текущую версии, reread; новое решение пользователя перед командой | | UNKNOWN mutation | Не показывать success; блокировать новый intent той же операции; сохранять key/body, переавторизовывать replay. Проверка демо намеренно остаётся UNKNOWN | | Busy | В реализации single-flight соответствующей операции, disabled submit и guard X/back согласно существующей семье; не терять UNKNOWN receipt при закрытии | В галерее нет настоящего асинхронного транспорта: spinner/busy timing, reload/replay, durable outbox, pagination, session revocation, backend ACL и storage не реализованы. Native dialog обеспечивает focus trap/Escape; UI demo ограниченно восстанавливает trigger. Browser Back/popstate и mobile gestures должны остаться существующими в продукте; gallery не симулирует их движок. ## Порядок передачи автору 1. **До реализации:** Owner смотрит галерею и утверждает или возвращает конкретную композицию/копирайт. [STYLE_GUIDE_POSTGAME.md](STYLE_GUIDE_POSTGAME.md) остаётся draft. Действующие Guide не редактировать до этого решения. 2. **PG-1b:** общий shell/контекст, отдельный attendance контракт, exact bulk, provenance, durable notice и безопасный минимальный recourse. Сохранить PG-1a writes OFF до применимых gates. Не выпускать standalone absent. 3. **PG-2:** freeze response/resolution contracts; расширить коррекции, неизменяемую историю, scoped Support review. Не выдавать manager capability за reviewer. 4. **PG-3:** own notes, peer/org/training, singleton reply, typed reports; добавить server ACL/limits и сохранить legacy Safety compatibility. 5. **PG-4:** все существующие history/action entry points ведут в общий body; server own projection, attention summaries/notices, mobile/desktop integration и scroll restoration. Фазы — части того же #372, не четыре mini-app, не новые Issues по умолчанию. Один source writer; применимые deterministic checks → независимая QA → обязательные Product CI/security → trusted DEV release/smoke → Owner UAT NOT_TESTED. Выпуск и история версии сопровождаются существующим release process; данный дизайн не назначает app version, release_id, accepted date или source receipt. ## Что проверено здесь Два inline script прошли `node --check`. Выполнены девять адресных isolated JS checks на точный bulk, неизменность открытого спора при повторном absent, explicit acknowledgement, withdrawal/history, scoped unresolved, own-note presentation/single reply, UNKNOWN, history→unknown и сохранение текущего статуса при возврате из истории. Они проверяют логику демо с минимальными DOM stubs, **не браузерную раскладку, доступность, ACL сервера или продуктовую QA**. Подробности: [checks.json](checks.json). Браузерный исполняемый файл не найден в проверенном окружении; screenshots и фактический render на 390/1024/1440 не выполнены. В галерее это настоящие CSS viewport размеры iframe, со scale-to-fit и режимом 100%, а не растянутые скриншоты. Onest fallback и отсутствие browser review остаются ограничениями до Owner просмотра. Нет network/model/subagent вызовов, новых зависимостей, публикации, app/source/runtime/DB/git edits. Артефакты записаны только в этот evidence-каталог. Работа не заявлена как продуктовый DONE/READY/ACTIVE.