# 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`).
