# Common Patterns

> Practical patterns for navigation, user input, conditional content, and common pitfalls.

## Navigation

**GOTO_SCENE** -- navigate to a scene by id. The `transition` field controls how the target scene is rendered:

- `TransitionType.SEND` -- always sends a new message.
- `TransitionType.EDIT_SMART` -- edits the current message when in a callback context, otherwise sends a new message. This is the most common choice.
- `TransitionType.ANSWER` -- answers the callback query only; does not render the scene.

**BACK** -- pops the history stack and returns to the previous scene. Requires `NavigationConfig.enable_history=True` (the default). If the history stack is empty, falls through to the scene's `parent_scene`.

**parent_scene** -- optional scene field that establishes hierarchical structure. Provides a fallback destination for BACK when the history stack is empty.

**allowed_scenes** -- whitelist of scene IDs that a scene can navigate to. An empty list (the default) means all scenes are reachable.

## User Input Flow

Step-by-step pattern for collecting free-text input:

1. Set `expect_input=True` on the scene.
2. Optionally set `input_validator="validate_func_name"` -- the validator receives text and returns `(bool, error_msg | None)`.
3. Add a MESSAGE handler with a SAVE_INPUT action: `ActionInstruction(type=ActionType.SAVE_INPUT, save_as="field_name")`.
4. Follow with GOTO_SCENE to advance to the next step.
5. Read the saved value later: `context.get_user_input("field_name")`.

```python
Scene(
    id="enter_email",
    name="Enter Email",
    expect_input=True,
    input_validator="validate_email",
    default_content=MessageContent(text="Please enter your email:"),
    handlers=[
        Handler(
            name="save_email",
            event_type=EventType.MESSAGE,
            actions=[
                ActionInstruction(type=ActionType.SAVE_INPUT, save_as="email"),
                ActionInstruction(type=ActionType.GOTO_SCENE, scene_id="confirm", transition=TransitionType.SEND),
            ],
        ),
    ],
)
```

## Passing Data via Buttons

For selecting items (products, options) without a dedicated handler per item:

1. Set `callback_params={"product_id": "123"}` on the Button.
2. When pressed, params are automatically saved as user input.
3. In the handler: `context.get_user_input("product_id")` returns `"123"`.

```python
Button(text="iPhone 15", callback_action="select", callback_params={"product_id": "iphone15"}),
Button(text="MacBook Pro", callback_action="select", callback_params={"product_id": "macbook"}),
```

All buttons point to the same `"select"` handler. The handler reads `callback_params` to determine which item was chosen.

## Conditional Content

Show different content based on variable values. Highest-priority matching `ConditionalContent` replaces `default_content`:

```python
Scene(
    id="profile",
    conditional_content=[
        ConditionalContent(
            conditions=[RuleCondition(variable="is_premium", operator=ConditionOperator.EQ, value=True)],
            content=MessageContent(text="Premium Profile"),
            priority=1,
        ),
    ],
    default_content=MessageContent(text="Profile"),
    ...
)
```

## Global Handlers

For commands available from any scene regardless of navigation state:

```python
GlobalHandler(
    name="cmd_help",
    event_type=EventType.MESSAGE,
    filters=[Filter(type="command", commands=["help"])],
    actions=[ActionInstruction(type=ActionType.SEND_MESSAGE, content=MessageContent(text="Help text..."))],
    interrupt_propagation=True,
    priority=10,
)
```

- `interrupt_propagation=True` -- prevents scene handlers from also processing the event.
- `priority` -- higher value means evaluated first.
- `require_state` defaults to `False` on GlobalHandler (works before any scene is entered).

## Schema Validation

Validate the schema before startup to catch configuration errors early:

```python
from ui_router import SchemaValidator, ValidationSeverity

report = SchemaValidator().validate(schema, registry=executor.registry)
if not report.is_valid:
    for issue in report.issues:
        if issue.severity == ValidationSeverity.ERROR:
            print(f"{issue.path}: {issue.message}")
```

The `validate` method accepts an optional `registry` argument to check that all referenced function names are registered.

## Multi-Router Setup

Run multiple independent bot UIs within a single Dispatcher:

```python
executor1 = UIRouterExecutor(schema=schema1, shared=shared, prefix="shop")
executor2 = UIRouterExecutor(schema=schema2, shared=shared, prefix="admin")
dp.include_router(executor1.get_router())
dp.include_router(executor2.get_router())
```

- `prefix` isolates navigation state so each router tracks its own scene stack independently.
- `outer_registry` allows sharing a single `MasterRegistry` across executors to avoid duplicate function registration.

## Hot Reload

Update the schema at runtime without restarting:

```python
await executor.reload_schema(new_schema)
```

This rebuilds internal services, re-registers functions, and syncs event subscriptions.

## Common Mistakes

- Forgetting `expect_input=True` when using SAVE_INPUT -- input will not be captured.
- `callback_action="name"` not matching any `Handler.name` in the scene -- button silently does nothing.
- Not registering functions before startup -- raises `RegistryError` at runtime.
- Using `require_state=True` on a /start command handler -- the user has no state yet, so the handler will not fire.
- Using EDIT_SMART transition on a MESSAGE handler -- there is no message to edit in a message context, so it falls back to SEND.
- Declaring GOTO_SCENE to a scene not listed in `allowed_scenes` -- navigation is silently blocked (when the list is non-empty).
- Defining `input_validator` without `expect_input=True` -- the validator is never called because the scene does not accept input.
- Placing GOTO_SCENE before SAVE_INPUT in the actions list -- actions execute sequentially, so navigation happens before the input is saved.
