# aiogram-ui-router -- LLM Agent Guide

> This documentation helps LLM coding agents understand and use the aiogram-ui-router library. Read this file first, then consult topic-specific files as needed.

## What is aiogram-ui-router

Declarative UI routing framework for Telegram bots built on aiogram 3. Bot interfaces are defined entirely as Pydantic schemas -- scenes, handlers, keyboards, and actions -- with no imperative handler code. The framework resolves dynamic content, manages navigation state, and executes action chains automatically.

## Core Concepts

**UIRouter -> Scene -> Handler -> ActionInstruction** -- the composition chain. `UIRouter` is the root schema containing scenes. Each `Scene` has handlers that respond to events (callback presses, messages, lifecycle hooks). Each `Handler` executes a list of `ActionInstruction` items -- navigate to a scene, send a message, save user input, set a variable, emit an event, and so on. Actions run sequentially in the order they are defined.

**Flag / FlagGetter** -- dynamic data injected into message templates at render time. A `Flag` declares a named value; its `FlagGetter` resolves that value from one of several sources: a static literal, a registered async function, the event context, or previously saved user input. Templates reference flags by name using `{flag_name}` syntax inside `ContentTemplate.template`.

**ExecutionContext** -- the runtime context object passed to ALL user-defined functions (getters, conditions, validators, custom actions). It provides access to `user_id`, `chat_id`, `bot`, the raw aiogram `event` (Message or CallbackQuery), `event_data` dict, resolved flags, and saved user input. Functions receive it as their first positional argument.

**MasterRegistry** -- central registry where user functions are registered by name. The schema references functions by string name; the registry resolves them at runtime. Functions are declared in `UIRouter.custom_functions` as `{"name": "module.path.function"}` and auto-loaded on executor init. Sub-registries: `getters`, `conditions`, `validators`, `actions`, `business_actions`.

**SharedServices** -- required infrastructure container passed to `UIRouterExecutor`. It groups the shared components that can be reused across multiple router instances: `navigation_storage` (persists user navigation state), `variable_repository` (stores bot/user/chat variables), `event_bus` (pub/sub for events), and `event_scheduler` (delayed/scheduled events).

## Minimal Working Example

A complete, self-contained bot with two scenes -- a home screen and a settings screen:

```python
import asyncio

from aiogram import Bot, Dispatcher, Router
from aiogram.filters import CommandStart
from aiogram.types import Message

from ui_router import (
    UIRouter, Scene, Handler, ActionInstruction, ActionType,
    EventType, TransitionType, MessageContent, Keyboard, Button,
    Flag, FlagGetter, DynamicContent, ContentTemplate,
    UIRouterExecutor, SharedServices,
    InMemoryNavigationStorage, InMemoryVariableRepository,
    EventBus, InMemoryEventScheduler,
)


async def get_user_name(context):
    user = context.get_from_event("event_from_user")
    return user.first_name if user else "User"


schema = UIRouter(
    name="demo_bot",
    initial_scene="home",
    custom_functions={
        "get_user_name": "__main__.get_user_name",
    },
    scenes=[
        Scene(
            id="home",
            name="Home",
            flags=[
                Flag(
                    name="username",
                    getter=FlagGetter(type="function", function="get_user_name"),
                ),
            ],
            default_content=MessageContent(
                text=DynamicContent(
                    type="template",
                    template=ContentTemplate(
                        template="Welcome, {username}!",
                        flags=["username"],
                    ),
                ),
            ),
            default_keyboard=Keyboard(
                buttons=[
                    [Button(text="Settings", callback_action="open_settings")],
                ],
            ),
            handlers=[
                Handler(
                    name="open_settings",
                    event_type=EventType.CALLBACK,
                    actions=[
                        ActionInstruction(
                            type=ActionType.GOTO_SCENE,
                            scene_id="settings",
                            transition=TransitionType.EDIT_SMART,
                        ),
                    ],
                ),
            ],
        ),
        Scene(
            id="settings",
            name="Settings",
            default_content=MessageContent(text="Settings"),
            default_keyboard=Keyboard(
                buttons=[
                    [Button(text="Back", callback_action="go_back")],
                ],
            ),
            handlers=[
                Handler(
                    name="go_back",
                    event_type=EventType.CALLBACK,
                    actions=[
                        ActionInstruction(
                            type=ActionType.BACK,
                            transition=TransitionType.EDIT_SMART,
                        ),
                    ],
                ),
            ],
        ),
    ],
)


shared = SharedServices(
    navigation_storage=InMemoryNavigationStorage(),
    variable_repository=InMemoryVariableRepository(),
    event_bus=EventBus(),
    event_scheduler=InMemoryEventScheduler(),
)
executor = UIRouterExecutor(schema=schema, shared=shared)

router = Router()


@router.message(CommandStart())
async def cmd_start(message: Message):
    await executor.start(
        user_id=message.from_user.id, message=message, bot=message.bot,
    )


async def main():
    bot = Bot(token="YOUR_TOKEN")
    dp = Dispatcher()
    dp.include_router(executor.get_router())
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())
```

## Setup Boilerplate

The essential imports and initialization pattern for any bot using aiogram-ui-router:

```python
from ui_router import (
    UIRouter, Scene, Handler, GlobalHandler, ActionInstruction, ActionType,
    EventType, TransitionType, MessageContent, Keyboard, Button, Filter,
    Flag, FlagGetter, DynamicContent, ContentTemplate,
    UIRouterExecutor, SharedServices,
    InMemoryNavigationStorage, InMemoryVariableRepository,
    EventBus, InMemoryEventScheduler,
)

# 1. Define the schema (UIRouter with scenes, handlers, actions)
schema = UIRouter(
    name="my_bot",
    initial_scene="home",
    custom_functions={
        "func_name": "myapp.module.func_name",
    },
    scenes=[...],
)

# 2. Create shared services (swap InMemory* for persistent implementations in production)
shared = SharedServices(
    navigation_storage=InMemoryNavigationStorage(),
    variable_repository=InMemoryVariableRepository(),
    event_bus=EventBus(),
    event_scheduler=InMemoryEventScheduler(),
)

# 3. Create the executor
executor = UIRouterExecutor(schema=schema, shared=shared)

# 4. Wire into aiogram
dp = Dispatcher()
dp.include_router(executor.get_router())  # registers callback/message handlers
dp.include_router(your_router)            # your /start command, etc.
```

## Module Index

| File | When to read |
|---|---|
| [`schema.md`](schema.md) | Defining scenes, handlers, actions, keyboards, filters, enums |
| [`functions.md`](functions.md) | Writing getter/condition/validator/action functions, ExecutionContext API |
| [`keyboards.md`](keyboards.md) | Building static or dynamic keyboards, using callback_params |
| [`variables.md`](variables.md) | Persistent variables (BotVariable), SET_VARIABLE, user input (SAVE_INPUT) |
| [`events.md`](events.md) | Event definitions, scheduled/webhook events, EMIT_EVENT/SCHEDULE_EVENT |
| [`patterns.md`](patterns.md) | Navigation flows, input handling, conditional content, troubleshooting |
