--- name: build-wizard-flow description: Creates multi-step wizard flows with validation and state persistence using @WizardHandler and WizardStep. Use when building forms, questionnaires, or multi-step conversations that need typed state and validation. --- # Build Wizard Flow ## Quick Start Annotate an object/class with `@WizardHandler`. Define nested steps extending `WizardStep`. KSP generates the `WizardActivity`; no manual Activity class needed. ```kotlin import eu.vendeli.tgbot.types.component.MessageUpdate @WizardHandler(trigger = ["/start"]) object RegistrationWizard { object NameStep : WizardStep(isInitial = true) { override suspend fun onEntry(ctx: WizardContext) { message("What's your name?").send(ctx.user, ctx.bot) } override suspend fun validate(ctx: WizardContext): Transition { val text = (ctx.update as? MessageUpdate)?.message?.text ?: return Transition.Retry() return if (text.isNotBlank()) Transition.Next else Transition.Retry("Invalid name") } override suspend fun store(ctx: WizardContext): String? = (ctx.update as? MessageUpdate)?.message?.text } object AgeStep : WizardStep() { override suspend fun onEntry(ctx: WizardContext) { message("How old are you?").send(ctx.user, ctx.bot) } override suspend fun validate(ctx: WizardContext): Transition { val age = (ctx.update as? MessageUpdate)?.message?.text?.toIntOrNull() return if (age != null && age in 1..150) Transition.Next else Transition.Retry() } override suspend fun store(ctx: WizardContext): Int? = (ctx.update as? MessageUpdate)?.message?.text?.toIntOrNull() } } ``` ## WizardStep Lifecycle - **onEntry(ctx)** — Called when entering the step. Send prompts, set keyboards. - **validate(ctx)** — Return `Transition` based on input. - **store(ctx)** — Return value to persist (or null). Type must match a state manager. - **onRetry(ctx, reason)** — Called when validation fails (optional). ## Transitions | Transition | Effect | |------------|--------| | `Transition.Next` | Move to next step in sequence | | `Transition.JumpTo(Step::class)` | Jump to specific step | | `Transition.Retry()` or `Transition.Retry("reason")` | Stay on step, call onRetry | | `Transition.Finish` | End wizard | ## State Managers Default: `MapStringStateManager`, `MapIntStateManager`, `MapLongStateManager`. KSP matches `store()` return type to the manager. Override per step: ```kotlin @WizardHandler.StateManager(CustomStateManager::class) object CustomStep : WizardStep { ... } ``` ## WizardContext - `ctx.user`, `ctx.update`, `ctx.bot` - `ctx.getState(Step::class)`, `ctx.setState(Step::class, value)`, `ctx.delState(Step::class)` — type-safe accessors generated by KSP for each step ## Trigger and Scope - `trigger = ["/start", "/register"]` — commands that start the wizard - `scope = [UpdateType.MESSAGE]` — default; use `UpdateType.CALLBACK_QUERY` for button-triggered wizards ## Related Skills - **add-wizard-handler** — Configure @WizardHandler (trigger, scope, state managers) - **add-wizard-step** — Implement WizardStep (onEntry, validate, store, transitions) ## Reference - [WizardHandler.kt](telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/annotations/WizardHandler.kt) - [WizardStep.kt](telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/chain/WizardStep.kt) - [TestWizard.kt](telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/TestWizard.kt)