feat(social): asymmetric per-user block, in-game block control, admin lists
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s

Make a per-user block one-directional and non-destructive: the blocker stops
receiving everything from the blocked user (chat, nudge, friend requests,
invitations) and the matchmaker never pairs them, while the blocked user
notices nothing — their sends still persist by the normal rules but are never
delivered or surfaced (born-read). A block no longer deletes the friendship
(an unblock cleanly restores it) and instant-reads any unread the blocked user
had left for the blocker.

- backend: a directional blockExists guard across chat/nudge/friends/invitations
  (store-but-hide for the blocked->blocker direction, refuse blocker->blocked);
  the matchmaker excludes a block-related pair (both directions) from auto-match;
  user_blocked/user_unblocked notifications to the blocker only (in-app only).
- ui: the opponent score card gains a block ✖️ control mirroring add-friend
  (red "Block?" confirm, mutual-hide while confirming, struck name, hidden chat
  composer when blocked); optimistic apply + event confirm + rollback for both.
- admin: the user card gains cross-linked blocks / blocked-by / friends lists.
- docs: FUNCTIONAL(+ru), ARCHITECTURE §10 + decision record, UI_DESIGN, PRERELEASE.
This commit is contained in:
Ilia Denisov
2026-06-18 11:50:34 +02:00
parent 9074417762
commit 81b9e1529e
34 changed files with 1191 additions and 109 deletions
+21 -8
View File
@@ -183,14 +183,27 @@ nudge) приходят от бота **этой партии** — по язы
тому, с кем вы играли** — он принимает, игнорирует (заявка истекает через тридцать
дней, после чего её можно отправить снова) или отклоняет (отказ блокирует ваши
повторные заявки, пока он сам не передаст вам код). Отмена своей висящей заявки
снимает её; удаление расторгает дружбу. В партии карточка счёта каждого соперника несёт контрол
**в друзья 🤝** (при открытой истории ходов), отражающий живое отношение: он подтверждается
тапом по затухающей ✅ (карточка показывает *В друзья?* во время подтверждения), становится
**неактивным**, пока заявка висит или была отклонена, и **исчезает** после принятия —
обновляясь на месте в момент ответа соперника и оставаясь верным после перезагрузки. Глобальная блокировка — отключить входящие
чат и/или заявки —
и блокировка конкретного игрока (пер-юзер блок скрывает его чат и запрещает заявки
и приглашения в игру в обе стороны, а также расторгает уже имеющуюся дружбу). Чат
снимает её; удаление расторгает дружбу. В партии карточка счёта каждого соперника (при открытой
истории ходов) несёт два контрола: **в друзья 🤝** справа и **блокировка ✖️** слева. Каждый
подтверждается тапом по затухающей ✅ — на месте счёта карточка показывает *В друзья?* (или
*Блокируем?* красным) во время подтверждения, — и пока подтверждается один, второй скрыт, чтобы
они не пересекались. 🤝 становится **неактивным**, пока заявка висит или была отклонена, и
**исчезает** после принятия; **оба контрола исчезают, а имя соперника становится зачёркнутым**
после блокировки. Они обновляются на месте в момент изменения отношения и остаются верны после
перезагрузки. Блокировка (как и заявка в друзья) применяется немедленно и подтверждается живым
событием; при сбое транспорта контрол возвращается в прежнее состояние.
Глобальная блокировка — отключить входящие чат и/или заявки — либо блокировка **конкретного
игрока**. Пер-юзер блок **односторонний и незаметный**: вы перестаёте получать от этого человека
всё — чат, nudge, заявки в друзья и приглашения в игру, — и матчмейкер никогда не сводит вас с
ним, а он **ничего не замечает**. Его сообщения и nudge по-прежнему отправляются по обычным
правилам, но не доходят до вас (всё, что было бы для вас непрочитанным, сразу помечается
прочитанным), а присланные им заявка или приглашение сохраняются, но никогда не всплывают. Блок
**перекрывает, но не удаляет** имеющуюся дружбу (поэтому можно заблокировать друга, а он
продолжает видеть вас в друзьях); активные игры не прерываются — их можно доигрывать, при этом у
заблокированного соперника «подвал» чата скрыт (остаётся только лог). Блокировка с карточки в
партии повторяет блокировку в **Настройках → Друзья**; **разблокировка** и **удаление из друзей**
есть только там. Чат
партии — для быстрых реакций: сообщения короткие (до 60 символов) и не должны
содержать ссылок, email и телефонов, даже завуалированных. В свой ход можно отправить
**одно сообщение за ход**; после отправки поле сменяется короткой подписью до следующего