Окно — вариант E из макетов: заголовок в плашке-шапке, текст, точки очереди
и «Понятно», крестика нет. Очередь идёт от старого к новому; закрытое сразу
уходит из кэша, следующее открывается без ожидания сети. У повторно
показанного объявления под заголовком — красная пометка «обновлено».
AppShell показывает окно на любой странице и только когда пароль задан:
обязательное окно пароля всегда первое. Новые и изменённые объявления
приходят SSE-событием announcements, отложенное начало показа — перезапросом
раз в 5 минут.
Админка — вкладка «Объявления» с редактором R1: contenteditable и панель
(жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления),
вставка и перетаскивание только простым текстом, счётчик символов, период
показа по МСК, переключатели «показывать новым игрокам» и «показать заново».
Предпросмотр — тем же окном, что у игрока. Список со статусом, счётчиком
«закрыли N из M» и действиями: изменить, снять с показа, дублировать,
удалить. Выделение текста в форме — полупрозрачный оранжевый вместо синего.
schema.d.ts пересобран из OpenAPI. #84
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GET /users/{id}/matches отдаёт у каждой партии rating_delta — изменение
общего рейтинга владельца истории за неё (один знак после запятой), в обоих
режимах, включая «лучшую партию»; история и рейтинг считаются одним
проигрыванием. В списке партий группы поле пустое: там непонятно, чья это
дельта.
Фронт: компактная карточка истории — второй строкой «Рейтинг: +12.3»,
подробная (MatchListView) — последней строкой, только когда дельта есть.
Рост — зелёным, падение — красным, минус типографский (formatRatingDelta).
schema.d.ts перегенерирован.
Тест: дуэль новичков ±32 в обеих историях, реванш с обратными знаками,
сумма изменений сходится с рейтингом, в списке группы дельты нет, режим
«лучшая» её отдаёт. #77
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Форма завершения и правка результатов (MatchDetailPage): у каждого игрока
в PlaceEditor поля «цели» и «миры» на конец партии (у выбывшего миры
заблокированы нулём), общий блок «Итог партии» — причина победы и раунд
окончания из max_rounds партии. Всё новое уходит в общий черновик.
Причина «последний выживший» ставится сама при одном невыбывшем, выбор
заблокирован; вернули второго — причина сбрасывается и её нужно выбрать.
То же в админской правке партии.
Предупреждения (domain/finishWarnings.ts, отправку не блокируют): тип победы
и цели/миры лидеров не согласуются, досрочный конец без N целей у победителя,
у соперника целей больше, чем у победителя. Настройки группы: переключатель
«9 раундов при 5–6 игроках» через PATCH /groups/{id}. schema.d.ts
перегенерирован. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Многие тосты повторяли то, что пользователь и так видит сразу после действия:
новую фракцию или аватар в профиле, переход на страницу партии, исчезнувшую
карточку приглашения, закрывшийся редактор. Такой шум приучает не читать
всплывашки, в том числе ошибки. Убраны 19 таких тостов в профиле, группах,
партиях и админке.
Остались ошибки, предупреждения (лимит фото, повторы фракций, конфликт правок,
«вы не в группе») и сообщения о действиях без видимого следа: приглашение
отправлено, дополнения сохранены, вступление в группу, пароль задан. Сохранение
текстового поля, где заметна только погасшая кнопка, тоже осталось.
По просьбе владельца оставшиеся тосты показываются не нижней «таблеткой», а
всплывашкой сверху, как входящие уведомления. ToastProvider держит одну стопку
на всё приложение: show — обычное сообщение, error — ошибка с красной полосой
(держится 5 с), notify — уведомление с переходом по клику. Одинаковое сообщение
не копится, а заменяет прежнее; клик по карточке закрывает её.
NotificationToaster больше не рисует свою стопку, а отдаёт новые уведомления в
общую, поэтому они не наезжают на сообщения. Все вызовы с ApiError и отказы
(лимит фото, партия изменилась на другом устройстве) переведены на toast.error.
Нижний класс .toast удалён.
#41
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
В DuckDuckGo шапка держала отступ от адресной строки — тот же safe-area,
что раньше отодвигал подвал снизу. Insets браузер отдаёт именно из-за
viewport-fit=cover: он передаёт управление безопасной зоной странице.
После отказа от отступов сверху и снизу компенсировать нечего, поэтому
cover снят — вьюпорт снова размещает браузер, а env() везде даёт ноль.
Следом убран --safe-top и три его использования (.top-bar, .side-menu,
.toast-top-wrap): без cover переменная всегда нулевая. Токенов safe-area
в проекте больше нет.
#4
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk
Подвал держал внутренний отступ в safe-area снизу, а купол FAB был
дополнительно поднят на 8px — у нижней кромки оставалась пустая полоса.
Отступ снят: высота панели равна --bottombar-height, база купола на
нулевой отметке. Токен --safe-bottom больше нигде не используется и
удалён, отступ сверху (--safe-top) остаётся у шапки.
#4
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk
Подвал был position: fixed, шапка — position: sticky при html/body с
height: 100%. В мобильных браузерах со скрывающейся адресной строкой
layout viewport меняет высоту при прокрутке: fixed-подвал за ним
дёргается, а у sticky-шапки нет однозначного прокручиваемого предка.
Каркас переведён на 100dvh с единственной прокручиваемой областью
(.app-main) — шапка и подвал стали обычными элементами флекс-колонки и
дёргаться им больше нечем. Safe-area ограничена потолком 24px: при
viewport-fit=cover часть браузеров (DuckDuckGo, Firefox Focus) отдаёт
inset поверх уже смещённого вьюпорта, из-за чего шапка раздувалась вдвое,
а надпись и колокольчик съезжали вниз. Заодно шапка защищена от
распирания длинным названием на узких экранах.
#4
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk
Вместо select-ов мест — PlaceEditor: вертикальный список блоков игроков
(верхний — 1-е место), бросок на другого игрока сливает в ничью с общей
рамкой и местом (✂ выносит обратно), выбывшие — перетаскиванием в
пунктирную зону (сервер сам ставит им последнее место). Комментарий об
игроке — строка в каждом блоке. Причина победы — PickerSelect (дженерик
расширен на строковые id), общий комментарий и медиа — карточкой,
кнопка завершения отдельно. Места считаются по блокам и всегда дают
валидный competition ranking (1,2,2,4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PlayerSelect обобщён в PickerSelect (рендер опций задаёт родитель) — им
оформлены оба поля создания партии. Фракции, выбранные в других строках,
не предлагаются (сервер при рандоме и так их исключал); набор фракций
по-прежнему приходит из available_factions группы с учётом дополнений.
Выбранное значение ищется в полном списке, а не в отфильтрованном:
после «Рандом всем» с повторами фракция строки может быть занята соседней.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Из списков убраны варианты-плейсхолдеры («— игрок —» в выборе игрока,
«— фракция —» в выборе фракции — disabled+hidden, в закрытом виде текст
остаётся). Фон .suggest-list — surface-2, как у input/select; hover строк —
--border. Скорость: дебаунс поиска 250→100 мс, в инвайте показанный список
дофильтровывается по вводу на клиенте, не дожидаясь ответа сервера;
touch-action: manipulation на строках и триггере.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Вместо нативного select — кнопка-триггер (аватар + ник) с выпадающим
списком всех игроков группы в стилистике подсказок инвайта; игроки,
уже выбранные в других строках, не предлагаются (заодно исключены
дубли). Для аватаров в списке MemberRead теперь отдаёт avatar_url.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
При вводе в поле приглашения («Список игроков») под инпутом всплывают
до 3 подходящих игроков — аватар + ник, в стилистике строк топов.
Клик подставляет ник в поле (приглашение отправляется кнопкой/Enter),
уже состоящие в группе не предлагаются.
Детали: компонент PlayerSuggestions (выбор по mousedown — до blur
инпута), хук useUserSearch с дебаунсом 250 мс и keepPreviousData,
классы .suggest-* в духе .notif-panel/.lb-row. В поиске общего списка
подсказки не нужны — таблица уже фильтруется вживую при вводе.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .btn-block в global.css заменяет 9 повторов style={{width:100%}};
- .badge-live — единый бейдж «идёт» (3 копии inline-стиля);
- незавершённые партии на главной используют стандартный .card,
как список партий (была единственная карточка на самодельных стилях);
- в списке партий добавлен бейдж 🎲 случайной фракции — как на странице
партии (раньше в списке он терялся);
- переключатель групп: кнопка «+» подписана «+ Создать группу»
(по образцу «+ Добавить игрока»), убран пустой span.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Новая страница /help: формула League Points, нормировка по числу игроков, ничьи,
выбывшие = последнее место, порог 3 партии для топа, прочие показатели. Текст
соответствует services/scoring.py. Задел под будущие правила и подсказки.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Новая система уведомлений поверх готовой SSE-шины: события пишутся в БД,
живут 72 часа и чистятся (при чтении списка + фоновой задачей), всплывают
сверху экрана в момент прихода, доступны через колокольчик в правом верхнем
углу (бейдж непрочитанных + панель).
Типы: приглашение в группу (→ /group), старт/финиш партии участникам кроме
инициатора (→ /match/{id}). Титулы — готовый хелпер-задел (не подключён, т.к.
выдача титулов игрокам ещё не реализована).
Бэкенд: модель Notification + миграция 0008 (идемпотентная), notification_service,
notify.notifications_changed, роутер /api/notifications (GET + /read), триггеры
в groups/matches, фоновая чистка в lifespan, защита hub.publish от закрытого loop.
Фронт: useNotifications/useMarkNotificationsRead, NotificationBell/Panel/Toaster,
перекомпоновка top-bar, стили; useServerEvents знает тип notifications.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>