Skip to content

Large App Shells

Large tui-lipan apps are easiest to diagnose when the root Component stays thin and app behavior lives in named operation modules.

  • Keep the root app type, Message enum, Component implementation, and bootstrap code together.
  • Put the Message match in a dispatcher module when it grows beyond a short screen of code.
  • Put app-owned behavior in operation modules named after behavior: actions, key_routing, search, theme, focus, or domain-specific names.
  • Keep view code as the widget/callback boundary. It should build elements and emit messages, not mutate app policy directly.
  • Keep reusable chrome as helper functions or composite widgets. Use nested Components only when the child owns state, lifecycle, keyboard handling, or async work.

Message dispatch

The root component can delegate update logic without hiding the tui-lipan lifecycle:

rust
impl Component for App {
    type Message = Msg;
    type Properties = ();
    type State = State;

    fn update(&mut self, msg: Self::Message, ctx: &mut Context<Self>) -> Update {
        update::handle_msg(self, msg, ctx)
    }

    fn view(&self, ctx: &Context<Self>) -> Element {
        view::render(self, ctx)
    }
}

The dispatcher should route messages to narrow functions. Long behavior bodies belong in app modules, not in the root component.

Input routing

Apps with global shortcuts and focusable children should make the input sources explicit:

  • Component::on_key receives keys that bubble to the root.
  • Widget callbacks receive keys consumed by focused widgets.
  • Both paths can call the same app-owned routing function when they share policy.

Use ctx.request_focus(...) and stable element keys for focus handoff. Return KeyUpdate::handled(...) only when the app consumed the key.

Terminal apps

Terminal widgets expose low-level control. A terminal-heavy app should keep these concerns visible:

  • PTY readiness and output event handling.
  • Terminal input forwarding.
  • Terminal resize side effects.
  • Scrollback synchronization.
  • App theme to terminal palette mapping.

These policies are usually app-owned. Move them to focused app modules before considering a framework abstraction.

Diagnostics checklist

When debugging a large app, locate the bug by boundary:

SymptomFirst place to inspect
Message has no effectMessage dispatcher and operation module
Shortcut works only in some widgetsRoot on_key, widget callback, and focus bubbling
Focus jumps to the wrong elementStable keys and ctx.request_focus(...) call site
Widget renders correctly but app state is staleCallback-to-message wiring in the view boundary
Terminal receives wrong bytesApp terminal input forwarding
Terminal layout or resize is wrongApp geometry policy before framework layout primitives
Async result applies out of orderCommand key and TaskPolicy choice

Reference example

examples/window_manager.rs demonstrates a large root app with canvas composition, focus routing, drag/resize behavior, workspace switching, and terminal composition. Treat it as a reference for boundaries and event flow rather than as a generic framework abstraction.

MIT OR Apache-2.0