Controllers & Responses
Controllers are where your app’s behavior lives: they bind keys, run actions, kick off async tasks, and decide what happens next. Every action returns a response object — render, navigate, or quit — that the runtime then carries out. Durable data goes in ApplicationState objects, because controller instances themselves are thrown away between events. For a guided introduction, see Controllers & Views.
Charming::Controller
Inherit from Charming::Controller or your app’s ApplicationController.
Class APIs
Key bindings and commands — how users trigger actions:
key name, action, scope: :contentbinds a content-pane key to an action.key name, action, scope: :globalbinds an app-level shortcut.command label, action = nil, &blockadds a command palette item.
Timers and animation — periodic dispatch while the route is active:
timer name, every:, action:, autostart: truedispatches a periodic timer while the route is active (every:must be positive;autostart: falsewaits forstart_timer).animate name, fps:, action:declares a stopped animation timer ticking at fps frames per second (start it withstart_timer).
Async task callbacks:
on_task name, action:handles async task completion.on_task_progress name, action:handlesprogress.reportcalls from a running task.
Action hooks and error handling:
before_action method, only: nil, except: nilruns a hook before matching actions.after_action method, only: nil, except: nilruns a hook after matching actions.around_action method, only: nil, except: nilwraps matching actions (the hook mustyield).rescue_from ExceptionClass, with: :handlerhandles action exceptions (most-specific class wins).
Layout and focus:
layout layout_classwraps rendered output in a class-based layout view.layout "layouts/application"wraps rendered output in an ERB template layout fallback.layout falsedisables inherited layout wrapping.focus_ring *slotsdefines tab-traversable focus slots (the first slot starts focused).
Instance APIs
Dispatch — how the runtime invokes actions (app code rarely calls these directly):
dispatch(action)calls an action (through its hooks) and returns a response.dispatch_key,dispatch_timer,dispatch_task,dispatch_task_progress,dispatch_mouse, anddispatch_pastedispatch event-specific handlers.
Rendering and navigation — what actions return:
render(body = "", **assigns)produces a render response.render "literal"renders a literal string.render :show, **assignsrenders a conventional Ruby view class, falling back toapp/views/<controller>/show.tui.erbor.txt.erb.render view_objectrenders a class-based view or component object.render_template(name, **assigns)renders an explicit template path underapp/views.navigate_to(path)produces a navigation response.quitproduces a quit response.
State and session — where data survives between events:
sessionaccesses the application session.state(name, state_class, **attributes)stores or returns a session-backed state object.component_state(name, **defaults)stores or returns a JSON-safe primitive hash for widget state (cursor, selection, offsets) — seeded fromdefaultson first access; survivespersist_session.loggerreturns the application logger.
Tasks and timers at runtime:
run_task(name, timeout: nil) { ... }submits async work; blocks accepting an argument receive aTasks::Progressreporter.cancel_task(name)cancels an in-flight task (raisesTasks::Cancelledinside it).start_timer(name),stop_timer(name), andtimer_running?(name)control declared timers at runtime (unknown names raiseArgumentError; both mutators are idempotent).
Request context — what the current event looks like:
paramsexposes current route params.eventexposes the current key, timer, task, progress, resize, mouse, or paste event.screenexposes terminal dimensions.themereturns the current theme.use_theme(name)switches themes.
Palettes:
open_command_palette,close_command_palette, andcommand_palettemanage the command palette.open_theme_paletteopens the theme picker.command_palette_open?returns whether a command or theme palette is open.
Focus:
focusreturns the controller’s focus object (focus.push_scope,focus.pop_scope,focus.cycle,focus.focus(slot),focus.current,focus.ring,focus.overlay?).focused?(slot)asks whether a slot is currently focused.focus_sidebar,focus_content,sidebar_focused?, andcontent_focused?support generated layouts (focus_contenttargets:contentor the first non-sidebar slot in the ring).sidebar_routesreturns the routes listed in the sidebar — override to filter.
Controller instances are ephemeral. Store durable state in ApplicationState objects through state(...). Focus-slot component methods are invoked on key-dispatch paths where before_action has not run — build components from session/params, not hook-set instance variables.
Charming::ApplicationState
Typed, session-backed state objects — the durable home for data that outlives a single controller instance. Inherit from Charming::ApplicationState:
class CounterState < Charming::ApplicationState
attribute :count, :integer, default: 0
end
It includes ActiveModel model and attributes support, so typed attributes and validations are available.
Common attribute types include :string, :integer, :float, :boolean, :date, :datetime, and :time.
Responses
Every controller action returns a response object telling the runtime what to do next. Controllers return response objects through helper methods:
render(...)creates a render response.navigate_to(path)creates a navigation response.quitcreates a quit response.
Response factories:
Charming::Response.render(body)Charming::Response.navigate(path)Charming::Response.quit
Response predicates:
response.navigate?response.quit?
Response attributes:
kindbodypath
The runtime follows navigation responses, renders render responses, and exits on quit responses.