Core Concepts

Charming is a Rails-inspired framework for terminal apps. Generated apps use routes, controllers, state objects, templates, layouts, components, themes, and a runtime that talks to a terminal backend.

Architecture

Generated apps follow this flow:

Application -> Router -> Controller -> Template/Layout -> Component -> UI
                         Runtime -> Renderer -> Terminal Backend

At runtime, the flow for a screen is:

Route -> Controller action -> Template -> Layout -> Renderer -> Terminal frame

Applications

An application owns the route table, session, themes, and task executor. Generated apps define the application class in lib/my_app/application.rb:

module MyApp
  class Application < Charming::Application
    root File.expand_path("../..", __dir__)

    Charming::UI::Theme.built_in_names.each do |theme_name|
      theme theme_name.to_sym, built_in: theme_name
    end

    default_theme :phosphor
  end
end

Routes are usually defined separately in config/routes.rb:

MyApp::Application.routes do
  root "home#show"
end

Controllers Are Ephemeral

Charming creates a fresh controller instance for each dispatch. A controller handles one action or event and returns a response.

Do not store durable state in controller instance variables. This is wrong for state that must survive multiple key presses:

def increment
  @count ||= 0
  @count += 1
  render "Count: #{@count}"
end

Store durable state in an application state object instead:

def increment
  counter.count += 1
  render "Count: #{counter.count}"
end

private

def counter
  state(:counter, CounterState)
end

Controller#state stores the state object in the application session and returns the same object on later dispatches.

Views And Layouts

Generated controllers render views by symbol:

def show
  render :show, home: home, palette: command_palette
end

For HomeController, render :show resolves:

app/views/home/show_view.rb

Layouts wrap rendered views. Generated apps use a Ruby layout class:

class ApplicationController < Charming::Controller
  layout Layouts::ApplicationLayout
end

That resolves:

app/views/layouts/application_layout.rb

ERB templates remain available as a fallback. See Controllers & Views and Layouts for details.

Runtime

Most apps start through:

Charming.run(MyApp::Application.new)

The runtime:

  1. Enters the terminal alternate screen and enables mouse tracking, bracketed paste, and focus reporting (and detects the terminal background for adaptive colors)
  2. Resolves the root route
  3. Dispatches controller actions and events
  4. Renders responses through a renderer
  5. Reads key, mouse, paste, resize, timer, and task events
  6. Restores terminal state on quit or error

Key dispatch priority

Key events are matched in order: command palette (when open) → printable characters to a focused text-capturing component → global key bindings → overlay focus scopes (modals) → sidebar keys → content key bindings → the focused component / Tab traversal. The practical upshot: typing into a field always types, shortcuts work everywhere else. Details in Controllers & Views.

Errors

An unhandled exception from a controller action does not crash the terminal. The runtime logs the full backtrace to the application logger and renders a centered error panel showing the exception class, message, and top backtrace lines. Any key dismisses it and re-renders the current route; q quits. Handle expected errors yourself with rescue_from before they reach the runtime.

Quitting

q-style bindings produce a quit Response. The runtime also traps SIGINT (and SIGTERM/SIGHUP) and treats an unbound ctrl+c keypress as quit, so apps always have an escape hatch — bind "ctrl+c" yourself to take it over. On the way out the runtime drains background tasks (with a grace period), persists the session when configured, and restores the terminal. An at_exit fallback restores the terminal even if the process dies without reaching the normal teardown.

Suspend and resume

Ctrl+Z works like it does in any well-behaved TUI: the runtime returns the terminal to its normal state and stops the process; on fg it re-enters raw mode and the alternate screen and repaints the current route. No app code is needed.

For tests, instantiate Charming::Runtime directly with MemoryBackend. See Testing.


This site uses Just the Docs, a documentation theme for Jekyll.