# Variables & User Input

> Persistent variables and transient user input for stateful bot behavior.

## BotVariable — Persistent Typed Variables

Declared in `UIRouter.variables` list:

- `name: str` — variable name
- `scope: VariableScope` — USER (per-user), BOT (global), CHAT (per-chat)
- `type: VariableType` — INT, FLOAT, BOOL, STR, DATETIME, JSON
- `default: Any = None` — default value
- `description: str | None = None`

```python
UIRouter(
    variables=[
        BotVariable(name="points", scope=VariableScope.USER, type=VariableType.INT, default=0),
        BotVariable(name="is_premium", scope=VariableScope.USER, type=VariableType.BOOL, default=False),
        BotVariable(name="announcement", scope=VariableScope.BOT, type=VariableType.STR, default=""),
    ],
    ...
)
```

## SET_VARIABLE Action

Modify a variable from a handler:

- `variable_name: str` — must match a declared BotVariable name
- `value: Any` — new value
- `operation: str = "set"` — "set" (replace), "increment" (add to number), "append" (add to list)
- `scope: VariableScope = USER`

```python
ActionInstruction(type=ActionType.SET_VARIABLE, variable_name="points", value=10, operation="increment")
```

## Conditional Flags from Variables

Use `ConditionalFlag` with `Rule` and `RuleCondition` to derive flag values from variables at render time:

```python
UIRouter(
    variables=[BotVariable(name="points", scope=VariableScope.USER, type=VariableType.INT, default=0)],
    conditional_flags=[
        ConditionalFlag(
            name="user_tier",
            rules=[
                Rule(conditions=[RuleCondition(variable="points", operator=ConditionOperator.GTE, value=100)], result="Gold", priority=1),
                Rule(conditions=[RuleCondition(variable="points", operator=ConditionOperator.GTE, value=50)], result="Silver"),
            ],
            default="Bronze",
        ),
    ],
    ...
)
```

Then use `{user_tier}` in templates.

## User Input — Transient Session Data

Stored per-user in NavigationState, not in VariableRepository.

**SAVE_INPUT action** saves the current message text:

```python
# Scene must have expect_input=True
ActionInstruction(type=ActionType.SAVE_INPUT, save_as="email")
```

**callback_params** — button params auto-saved to user input on click:

```python
Button(text="Option A", callback_action="select", callback_params={"choice": "a"})
# In handler: context.get_user_input("choice") -> "a"
```

**Reading:**

```python
context.get_user_input("email")  # returns saved value or None
```

**Clearing:**

```python
ActionInstruction(type=ActionType.CLEAR_DATA, data_keys=["email"])  # specific keys
ActionInstruction(type=ActionType.CLEAR_DATA)  # clear all
```

## VariableRepository Protocol

For custom storage backends (Redis, database):

```python
class VariableRepository(Protocol):
    async def get(self, bot_id, name, scope, user_id=None, chat_id=None) -> Any: ...
    async def set(self, bot_id, name, value, scope, user_id=None, chat_id=None) -> None: ...
    async def delete(self, bot_id, name, scope, user_id=None, chat_id=None) -> None: ...
```

Built-in: `InMemoryVariableRepository()` (development only, no persistence).
