# ADR 002: Platform-Neutral Batch Rendering ## Status **Accepted** — implemented in v1.10.0 (PR #42). ## Context `SessionBatchRenderer` жил в `GmRelay.Shared` и напрямую зависел от `Telegram.Bot` (`InlineKeyboardMarkup`, `ParseMode.Html`). Это создавало проблемы: 1. **Shared не был platform-neutral.** Любой платформенный проект (Discord, Slack, WebSocket) тащил Telegram-зависимость. 2. **Дублирование логики.** `GmRelay.Web` использовал тот же рендерер через прямую зависимость от `Shared`, но Web — это не Telegram-клиент. 3. **Невозможно написать unit-тесты без Telegram-объектов.** Smoke-тесты создавали InlineKeyboardMarkup даже для проверки чисто доменной логики. ## Decision Разделить рендеринг на две стадии: 1. **View Builder (platform-neutral)** — собирает view model из доменных DTO. 2. **Platform Renderer (platform-specific)** — превращает view model в платформенное представление. ``` Domain DTOs │ ▼ SessionBatchViewBuilder (Shared) │ ▼ SessionBatchViewModel (platform-neutral) │ ├──► TelegramSessionBatchRenderer ──► HTML + InlineKeyboardMarkup │ └──► DiscordSessionBatchRenderer ──► Discord embeds + buttons ``` ### Изменённые компоненты | Компонент | Было | Стало | |---|---|---| | `SessionBatchRenderer` | `GmRelay.Shared.Rendering` | Удалён | | `SessionBatchViewBuilder` | — | `GmRelay.Shared.Rendering` | | `SessionBatchViewModel` | — | `GmRelay.Shared.Rendering` | | `TelegramSessionBatchRenderer` | — | `GmRelay.Bot` + `GmRelay.Web` | | `DiscordSessionBatchRenderer` | — | `GmRelay.DiscordBot.Rendering` | | `BatchMessageEditor` | `GmRelay.Shared.Rendering` | `GmRelay.Bot` + `GmRelay.Web` | ## Consequences ### Positive - `GmRelay.Shared` больше не зависит от `Telegram.Bot`. Чистый platform-agnostic проект. - Discord renderer lives in `GmRelay.DiscordBot`, so NetCord stays out of `Shared`. - Unit-тесты ViewBuilder не создают `InlineKeyboardMarkup`. - Логика подсчёта игроков, сортировки сессий и генерации действий — в одном месте (ViewBuilder). ### Negative - **Временное дублирование.** `TelegramSessionBatchRenderer` и `BatchMessageEditor` скопированы в `Bot` и `Web`. Планируется вынести в `GmRelay.Shared.Telegram` при появлении третьего Telegram-потребителя. - **Дополнительная стадия.** Теперь два вызова вместо одного: `Build` + `Render`. Этоtrade-off за чистоту абстракции. ## Related - Issue #22 — этот рефакторинг. - Issue #26 — Discord Bot MVP (потребитель новой архитектуры). - Issue #30 — Discord reschedule voting использует `IPlatformMessenger`. - Issue #31 — scheduler notifications и reschedule deadline updates через `IPlatformMessenger`. - Issue #32 — compose wiring для Discord bot (healthcheck :8082). - Issue #33 — регрессионные тесты platform rendering (Telegram + Discord). - ADR 001 — vertical slice, native AOT, Aspire (`docs/adr/0001-use-vertical-slice-native-aot-and-aspire.md`).