# Structuring a Bigger App
The handler in Getting Started is one `match` with four arms. That's perfect for
two pages. For ten pages with real functionality, we do it like this:
**give each page its own module, and make the center dumb routing.** Adding a
page becomes "write a file, add two lines" — you never touch the other pages.
## One module per page
Each page owns the two things the runtime asks about it: its `page_spec` (what's
on the page) and its `handle` (what its actions do). Nothing else knows about
Home except `home.rs`:
```rust
// pages/home.rs
use tui_pages::prelude::*;
use crate::app::{Action, View, State};
pub fn page_spec(state: &State) -> PageSpec {
PageSpec::new()
.focus(PageFocusBuilder::new().button(0).button(1))
.modes(vec![modes::GENERAL, modes::GLOBAL])
}
pub fn handle(action: Action, ctx: &ActionContext<View>, state: &mut State) -> ActionOutcome<View> {
match action {
Action::Select => match ctx.focus {
Some(FocusTarget::Button(0)) => ActionOutcome::effect(TuiEffect::Navigate(View::Settings)),
_ => ActionOutcome::none(),
},
_ => ActionOutcome::none(),
}
}
```
`settings.rs`, `editor.rs`, and the other seven look the same: their own focus
targets, their own logic. They're independent — you can write, read, or delete
one without opening the others.
## The center just routes
Now `app.rs` holds no page logic at all. The page function routes to the right
module:
```rust
// app.rs
fn page_spec(view: &View, state: &State, _focus: Option<&FocusTarget>) -> PageSpec {
match view {
View::Home => home::page_spec(state),
View::Settings => settings::page_spec(state),
View::Editor => editor::page_spec(state),
// ...one arm per page
}
}
```
and so does the handler:
```rust
impl TuiActionHandler<View, Action, State> for Handler {
type Error = std::convert::Infallible;
fn handle_action(&mut self, action: Action, ctx: ActionContext<View>, state: &mut State)
-> Result<ActionOutcome<View>, Self::Error>
{
Ok(match ctx.current_view {
View::Home => home::handle(action, &ctx, state),
View::Settings => settings::handle(action, &ctx, state),
View::Editor => editor::handle(action, &ctx, state),
})
}
}
```
> `match ctx.current_view` assumes `View` is `Copy` (it is in all the examples).
> If yours isn't, match on a reference instead — `match &ctx.current_view` — so
> the `&ctx` you pass to the page is still valid.
That's the backbone. Everything below is for keeping it tidy as the app grows.
## Handle global actions once
Some actions mean the same thing on every page — quit, move focus, open the
command palette. Don't repeat them in ten modules. Catch them in the router
*before* you delegate, and only fall through to the page for page-specific work:
```rust
fn handle_action(&mut self, action: Action, ctx: ActionContext<View>, state: &mut State)
-> Result<ActionOutcome<View>, Self::Error>
{
// Global: handled once, the same everywhere.
match action {
Action::Quit => return Ok(ActionOutcome::effect(TuiEffect::Quit)),
Action::FocusNext => return Ok(ActionOutcome::effect(TuiEffect::Focus(FocusIntent::Next))),
Action::FocusPrev => return Ok(ActionOutcome::effect(TuiEffect::Focus(FocusIntent::Prev))),
_ => {}
}
// Page-specific: route to wherever we are.
Ok(match ctx.current_view {
View::Home => home::handle(action, &ctx, state),
View::Settings => settings::handle(action, &ctx, state),
View::Editor => editor::handle(action, &ctx, state),
})
}
```
A page's `handle` now only deals with what's genuinely *its own*.
## When the `Action` enum gets big
Ten pages with lots of functionality means a lot of action variants. A single
flat enum still works, but you can group per page and let each module own its
slice. `A` is your type — nest it:
```rust
pub enum Action {
// shared across pages
FocusNext,
FocusPrev,
Quit,
// one group per page
Editor(EditorAction),
Settings(SettingsAction),
}
pub enum EditorAction { Save, Format, ToggleWrap, /* ... */ }
```
Then the router hands the inner enum straight to the page, which never sees
anything but its own actions:
```rust
match action {
Action::Editor(a) => editor::handle(a, &ctx, state), // a: EditorAction
Action::Settings(a) => settings::handle(a, &ctx, state),
other => global::handle(other, &ctx, state),
}
```
Do the same for `View` if the page list itself gets large
(`View::Editor(EditorView)`), so the top-level enums stay small.
## Where state lives
All your pages share one `State` (the `S` type), but each page usually cares
about its own slice. Give each one a field — or its own sub-struct — and pages
read and write only what's theirs:
```rust
pub struct State {
pub editor: EditorState,
pub settings: SettingsState,
pub user: User, // shared
}
```
`editor::handle` touches `state.editor`; `settings::handle` touches
`state.settings`. The runtime passes the whole `&mut State` through; the
discipline of "each page stays in its lane" is yours to keep.
## Keys: shared by default, per-page when needed
Bindings are registered once on the builder, keyed by *mode* — not per page. So
every page on `modes::GENERAL` shares `Tab`, `Enter`, and the rest for free, and
you bind them in one place. A key only needs special handling when it means
something different on one page, and you have two ways to do that:
- **Branch in that page's `handle`** on `ctx.focus` / `ctx.current_view` (what
the examples do) — good for "Enter does X here, Y there".
- **Give the page its own mode** when it has a whole different keymap (a text
editor, a modal tool):
```rust
const EDITOR: ModeId = ModeId::borrowed("editor");
.bind(EDITOR, "ctrl+s", Action::Editor(EditorAction::Save))
PageSpec::new().modes(vec![EDITOR, modes::GLOBAL])
```
## Adding a page, start to finish
That's the payoff. To add page number eleven:
1. Write `pages/foo.rs` with `page_spec` and `handle`.
2. Add one arm to the `page_spec` router and one to the handler router.
3. (Only if it needs them) bind its keys, or its own mode, on the builder.
You don't open any other page's file. That's the architecture the README
promised — a hundred pages, each minding its own business, with no shared god
object in the middle.