The previous commit (f095209) shipped a DiscordWizardInteractionModule
whose MaybeOpenModalAsync helper was a documented no-op: the handler
called SendResponseAsync(DeferredMessage) early, then tried to swap
the deferred response for a Modal via ModifyResponseAsync, which
NetCord forbids (the response type is locked after the first call).
As a result, the wizard's button click that advances to a text-input
step (Title, Description, Cover, DateTime, Capacity, PoolSlot*…)
edited the draft embed but never popped the modal, leaving the user
stuck on a step that demanded popup input.
This commit restructures the dispatcher:
- Run the wizard FIRST (a separate REST call to edit the draft embed;
no interaction response is touched yet).
- Then send the interaction response as either
InteractionCallback.Modal(modalProperties) when the new step is in
the OpenModal set (Title, Description, Cover, DateTime, Capacity,
PoolSlotDateTime, PoolSlotCapacity, SystemFreeText,
DurationFreeText, PoolSystemDurationFreeText), or
InteractionCallback.DeferredMessage(MessageFlags.Ephemeral) otherwise.
- The Modal handler's draft.Step = wizardStep / originalStep restore
hack is removed: the wizard's mutation of draft.Step must persist
to the DB (the wizard already called _drafts.UpsertAsync before we
get control back), so restoring locally would only mask the truth
from the next interaction's GetActiveAsync.
- The Resume:continue path re-renders the current step via the
messenger and acks the click with a deferred ephemeral.
- The Create path delegates to DiscordWizardSubmitter.SubmitAsync and
acks the click with a deferred ephemeral.
- The constructor now assigns _messenger (was unassigned, caught by
nullable-flow analysis).
Also adds deliverable.md in the repo root describing the full Discord
adapter for issue #112.
Build green. 190/190 Discord+Wizard tests pass (2 pre-existing skipped).
dotnet format clean. The previous 12 source-level smoke tests still
pass — they assert on file shape, not runtime flow.
Moves the game-creation wizard state machine, view builder, and
platform-neutral contracts (callback data, step names, storage
exception, club option, step limits) from GmRelay.Bot to GmRelay.Shared.
Telegram continues to work through a new TelegramWizardMessenger
implementing IWizardMessenger and a WizardInteractionMapper that
converts Update → WizardInteraction. Wires the new platform column on
wizard_drafts (V032 migration) and switches chat/owner/thread/message
ids to TEXT so the same table can hold Discord snowflakes later.
- GameCreationWizard: now in Shared, takes IWizardMessenger +
IWizardDraftRepository, dispatches on WizardInteraction.
- New IWizardMessenger contract with Edit/Send/Answer/GetOwnerClubs
(returns string ids so Telegram longs and Discord snowflakes both
fit).
- New WizardStepViewBuilder in Shared returns
(text, IReadOnlyList<WizardAction>); TelegramWizardMessenger
renders actions into InlineKeyboardMarkup via a new Bot-side
ToInlineKeyboard helper.
- New WizardInteractionMapper in Bot (5-case test) converts Telegram
Update to WizardInteraction.
- WizardDraft gains a Platform column; ChatId/MessageThreadId/OwnerId/
DraftMessageId switched to string. V032 migrates existing rows and
rebuilds the owner lookup index on (platform, owner_id).
- All existing wizard / create-session tests updated to the new
contract (HandleInteractionAsync + WizardInteraction). Wizard
callback-data format preserved.
- dotnet build clean, dotnet format --verify-no-changes clean, all
101 wizard tests pass.