fix(shared,bot,discordbot): make club-picker Dapper calls AOT-safe (v3.9.2)
Deploy Telegram Bot / build-and-push (push) Successful in 7m18s
Deploy Telegram Bot / scan-images (push) Failing after 17s
Deploy Telegram Bot / deploy (push) Has been skipped

The 3.9.1 hotfix only repaired WizardDraftRepository, the most common
Dapper call in the wizard. The same AOT-unsafe CommandDefinition pattern
remained in 4 other places that the user hit immediately after the
deploy: the 'Choose visibility' wizard step triggers GetOwnerClubsAsync
when the user picks 'Публичная в витрине клуба' or 'Только для членов
клуба'. The wizard swallowed PlatformNotSupportedException, the
callback ack replied with '⚠️ Ошибка', and the next step never rendered.
Privacy 'didn't stick' from the user's perspective.

Two changes to fix the Discord side as well:

1. Switched GetOwnerClubsAsync / LoadClubsAsync / LoadManagerUserIdsAsync
   to the direct (sql, params) overload across TelegramWizardMessenger,
   DiscordWizardMessenger, DiscordWizardInteractionModule, and
   DiscordPermissionLookup — same pattern as the 3.9.1 fix.

2. Added Dapper.AOT module attribute ([module: Dapper.DapperAot]) and
   InterceptorsPreviewNamespaces to the DiscordBot project. The
   DiscordBot assembly was previously skipped by the AOT source
   generator, so even the direct-overload fix wouldn't have produced
   interceptors for the Discord-specific Dapper call sites. With this
   addition, the generator emits 3 DiscordBot-specific interceptors
   (DiscordWizardMessenger, DiscordWizardInteractionModule,
   DiscordPermissionLookup) and the AssemblyLoad ships with the right
   GmRelay.DiscordBot.generated.cs.

Also expanded the AOT shape regression tests to cover all 4
CommandDefinition sites + added a 'containingClass' parameter to
ExtractMethodBody to disambiguate the duplicated LoadClubsAsync names
in DiscordWizardInteractionModule.

Bumps: 3.9.1 -> 3.9.2.
This commit is contained in:
2026-06-08 10:48:24 +03:00
parent 065e8011ee
commit 2c9016a383
200 changed files with 5856 additions and 27 deletions
@@ -0,0 +1,23 @@
<h2>Два подхода к архитектуре wizard-а</h2>
<p class="subtitle">Уже решено: черновик в БД с TTL 24ч, линейный wizard, пул = один batch_id. Выбираем, как организовать код.</p>
<div class="cards">
<div class="card" data-choice="centralized" onclick="toggleSelect(this)">
<div class="card-body">
<h3>A. Центральный Wizard-сервис</h3>
<p><b>GameCreationWizard</b> — один сервис, держит в БД таблицу <code>wizard_drafts</code> (chat_id, step, payload jsonb, updated_at). Маршрутизация в <code>UpdateRouter</code> проверяет наличие активного черновика при любом апдейте и делегирует ему.</p>
<p><b>Плюсы:</b> вся логика wizard-а в одном месте, легко тестировать, единая точка для TTL и таймаутов.</p>
<p><b>Минусы:</b> <code>UpdateRouter</code> разрастается, нужен чёткий контракт «черновик активен → текст идёт в wizard» vs. свободный режим.</p>
</div>
</div>
<div class="card" data-choice="per-feature" onclick="toggleSelect(this)">
<div class="card-body">
<h3>B. Per-feature диалоги</h3>
<p>В каждой feature (CreateSession, ShowCase, и т.д.) — свой маленький handler для wizard-а, использующий <b>ConversationStateService</b> (state machine + storage). <code>UpdateRouter</code> делегирует в активный диалог через <code>IDialogDispatcher</code>.</p>
<p><b>Плюсы:</b> масштабируется на будущие wizard-ы (для других фич), <code>UpdateRouter</code> остаётся тонким, модульные тесты на диалог.</p>
<p><b>Минусы:</b> больше абстракций, нужно продумать контракт <code>IDialog</code> и dispatcher; для одной фичи это может быть overkill.</p>
</div>
</div>
</div>
<p class="subtitle">Кликните, чтобы выбрать. По умолчанию рекомендую <b>A</b> — мы делаем только один wizard, лишний слой абстракции не оправдан.</p>
@@ -0,0 +1,88 @@
<h2>Шаги wizard-а: пул игр (v2)</h2>
<p class="subtitle">Система и длительность — общие на весь пул. Мини-wizard слота: только дата + лимит/waitlist.</p>
<div class="mockup">
<div class="mockup-header">Шаги 1–5 — общие параметры пула</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
1. Тип создания → [Одну / Пул]
2. Название пула → ввод
3. Описание (опц.) → ввод или «-»
4. Обложка (опц.) → фото/URL или «-»
5. Система + длительность → [D&amp;D 5e 4ч] [PF2e 4ч] [CoC 3ч] …
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
<p>На шаге 5 — пара кнопок «Система + длительность» (сначала выбираешь систему, потом длительность), или сразу готовые пресеты. Пресеты — самый быстрый путь.</p>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 6 — видимость (issue #110)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🔒 Видимость пула
[ 🌐 Публичная в общем showcase ]
[ 🏠 Публичная в витрине клуба ]
[ 🔐 Только для членов клуба ]
[ 🏷 Выбрать клуб… ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 7 — добавить слоты</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📅 Слоты пула «Сага о Багровом Караване»
Сейчас в пуле: 0 слотов.
Параметры пула: D&amp;D 5e, 4 часа.
[ ➕ Добавить слот ]
[ ✅ Готово, к превью ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Мини-wizard слота</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
Слот 1 — D&amp;D 5e, 4 часа
📅 Дата/время: ввод ДД.ММ.ГГГГ ЧЧ:ММ
👥 Мест + waitlist: ввод + кнопка
[ ✅ Готово, добавить слот ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
<p>После «Готово» возврат на шаг 7 с обновлённым счётчиком.</p>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 8 — превью и подтверждение</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
👀 Проверьте пул перед созданием
📝 Сага о Багровом Караване
📄 …
🎲 D&amp;D 5e, 4 часа
🖼 [превью обложки]
🔒 Публичная в общем showcase
Слоты (3):
• 15.06.2026 19:30 — мест 4, waitlist
• 22.06.2026 19:30 — мест 4, waitlist
• 29.06.2026 19:30 — мест 4, waitlist
[ ✅ Создать пул ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
@@ -0,0 +1,128 @@
<h2>Шаги wizard-а: пул игр</h2>
<p class="subtitle">Только общие параметры пула: название, описание, обложка, видимость/клуб. Даты и игровые параметры слотов — отдельный мини-wizard на следующем шаге.</p>
<div class="mockup">
<div class="mockup-header">Шаг 1 — тип создания</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🎲 Создание новой игровой сессии
[ 🎯 Одну игру ]
[ 📅 Пул игр (несколько) ]
[ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 2 — название пула</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📝 Название пула
Введите название одним сообщением. Например:
• «Сага о Багровом Караване — серия ваншотов»
• «D&amp;D 5e: Tyranny of Dragons, 4 вечера»
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 3 — описание пула (опционально)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📄 Описание пула
Общий сеттинг/правила серии. Можно пропустить — отправьте «-».
[ ⏭ Пропустить ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 4 — обложка пула (опционально)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🖼 Обложка
Пришлите картинку или URL. Можно пропустить.
[ ⏭ Пропустить ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 5 — видимость пула (issue #110)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🔒 Видимость пула
[ 🌐 Публичная в общем showcase ]
[ 🏠 Публичная в витрине клуба ]
[ 🔐 Только для членов клуба ]
[ 🏷 Выбрать клуб… ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 6 — добавить слоты (мини-wizard)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📅 Слоты пула «Сага о Багровом Караване»
Сейчас в пуле: 0 слотов.
Что дальше?
[ ➕ Добавить слот ] — войти в мини-wizard для слота
[ ✅ Готово, перейти к превью ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Мини-wizard слота (повторяется)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
Слот 1 из пула «Сага о Багровом Караване»
📅 Дата: ДД.ММ.ГГГГ ЧЧ:ММ → ввод
🎲 Система: [ кнопки систем ]
⏱ Длительность: [ кнопки часов ]
👥 Мест + waitlist: ввод
[ ✅ Готово, добавить слот ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
<p>После «Готово» возвращаемся на шаг 6 с обновлённым счётчиком. Можно добавить ещё или перейти к превью.</p>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 7 — превью и подтверждение пула</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
👀 Проверьте пул перед созданием
📝 Сага о Багровом Караване — серия ваншотов
📄 Сеттинг: магический караван идёт через пустоши…
🖼 [превью обложки]
🔒 Публичная в общем showcase
Слоты (3):
• 15.06.2026 19:30 — D&amp;D 5e, 4 часа, мест 4
• 22.06.2026 19:30 — D&amp;D 5e, 4 часа, мест 4
• 29.06.2026 19:30 — D&amp;D 5e, 4 часа, мест 4
[ ✅ Создать пул ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<p class="subtitle">После «Создать пул» всё идёт в БД одним batch_id — переиспользуем существующий <code>CreateSessionHandler</code>.</p>
@@ -0,0 +1,167 @@
<h2>Шаги wizard-а: одиночная игра</h2>
<p class="subtitle">Линейный поток. На каждом шаге inline-кнопки для выбора + текст для свободного ввода. Назад/Отмена на каждом экране.</p>
<div class="mockup">
<div class="mockup-header">Шаг 1 — тип создания</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🎲 Создание новой игровой сессии
Выберите, что вы хотите создать:
[ 🎯 Одну игру ]
[ 📅 Пул игр (несколько) ]
[ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 2 — название</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📝 Название игры
Введите название одним сообщением. Например:
• «Вечер ужасов»
• «D&amp;D 5e: Hoard of the Dragon Queen»
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 3 — описание (опционально)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📄 Описание
Кратко: сеттинг, ожидания, что взять с собой.
Можно пропустить — отправьте «-».
[ ⏭ Пропустить ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 4 — обложка (опционально)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🖼 Обложка
Пришлите картинку как вложение (фото) или отправьте URL.
Можно пропустить — нажмите кнопку.
[ ⏭ Пропустить ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 5 — система/формат</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🎲 Система / формат
[ D&amp;D 5e ] [ Pathfinder 2e ]
[ Call of Cth ] [ GURPS ]
[ Другое… ✏️ ] [ ⏭ Пропустить ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 6 — длительность</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
⏱ Длительность
[ 3 часа ] [ 4 часа ] [ 5 часов ]
[ 6 часов ] [ Другое ✏️ ] [ ⏭ Пропустить ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 7 — дата/время</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📅 Дата и время
Введите дату и время в формате <code>ДД.ММ.ГГГГ ЧЧ:ММ</code> (Москва).
Например: <code>15.06.2026 19:30</code>
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 8 — лимит мест и waitlist</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
👥 Лимит мест
Введите целое число больше 0. Например: <code>4</code>
Waitlist — лист ожидания сверх лимита?
[ ✅ Waitlist вкл ] [ ❌ Без waitlist ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 9 — клуб/видимость (issue #110)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🔒 Видимость
[ 🌐 Публичная в общем showcase ]
[ 🏠 Публичная в витрине клуба ]
[ 🔐 Только для членов клуба ]
[ 🏷 Выбрать клуб… ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 10 — публикация в витрине</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
✨ Публикация
Опубликовать в витрине сейчас?
[ ✅ Опубликовать ] [ 📝 Только в чате ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 11 — превью и подтверждение</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
👀 Проверьте перед созданием
🎲 Вечер ужасов
📄 Тёмная атмосфера, pre-made персонажи
🖼 [превью картинки]
🎲 Call of Cthulhu, 4 часа
📅 15.06.2026 19:30 (МСК)
👥 Мест: 4, waitlist вкл
🔒 Публичная в общем showcase
[ ✅ Создать ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
@@ -0,0 +1,152 @@
<h2>Шаги wizard-а: одиночная игра</h2>
<p class="subtitle">Линейный поток. На каждом шаге inline-кнопки для выбора + текст для свободного ввода. Назад/Отмена на каждом экране.</p>
<div class="mockup">
<div class="mockup-header">Шаг 1 — тип создания</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🎲 Создание новой игровой сессии
Выберите, что вы хотите создать:
[ 🎯 Одну игру ]
[ 📅 Пул игр (несколько) ]
[ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 2 — название</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📝 Название игры
Введите название одним сообщением. Например:
• «Вечер ужасов»
• «D&D 5e: Hoard of the Dragon Queen»
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 3 — описание (опционально)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📄 Описание
Кратко: сеттинг, ожидания, что взять с собой.
Можно пропустить — отправьте «-».
[ ⏭ Пропустить ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 4 — система/формат</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🎲 Система / формат
[ D&amp;D 5e ] [ Pathfinder 2e ]
[ Call of Cth ] [ GURPS ]
[ Другое… ✏️ ] [ ⏭ Пропустить ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 5 — длительность</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
⏱ Длительность
[ 3 часа ] [ 4 часа ] [ 5 часов ]
[ 6 часов ] [ Другое ✏️ ] [ ⏭ Пропустить ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 6 — дата/время</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
📅 Дата и время
Введите дату и время в формате <code>ДД.ММ.ГГГГ ЧЧ:ММ</code> (Москва).
Например: <code>15.06.2026 19:30</code>
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 7 — лимит мест и waitlist</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
👥 Лимит мест
Введите целое число больше 0. Например: <code>4</code>
Waitlist — лист ожидания сверх лимита?
[ ✅ Waitlist вкл ] [ ❌ Без waitlist ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 8 — клуб/видимость (issue #110)</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
🔒 Видимость
[ 🌐 Публичная в общем showcase ]
[ 🏠 Публичная в витрине клуба ]
[ 🔐 Только для членов клуба ]
[ 🏷 Выбрать клуб… ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 9 — публикация в витрине</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
✨ Публикация
Опубликовать в витрине сейчас?
[ ✅ Опубликовать ] [ 📝 Только в чате ]
[ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Шаг 10 — превью и подтверждение</div>
<div class="mockup-body">
<pre style="font-family: ui-monospace, monospace; white-space: pre-wrap;">
👀 Проверьте перед созданием
🎲 Вечер ужаров
📄 Тёмная атмосфера, pre-made персонажи
🎲 Call of Cthulhu, 4 часа
📅 15.06.2026 19:30 (МСК)
👥 Мест: 4, waitlist вкл
🔒 Публичная в общем showcase
[ ✅ Создать ] [ ⬅️ Назад ] [ ❌ Отмена ]
</pre>
</div>
</div>
@@ -0,0 +1 @@
{"reason":"idle timeout","timestamp":1780549154324}
@@ -0,0 +1 @@
1868
+3
View File
@@ -0,0 +1,3 @@
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
<p class="subtitle">Continuing in terminal...</p>
</div>