class

Phosphor::App(S, M)

Inherits Reference < Object

Adds theme access to App. Theme itself lives in theme.cr.

Widget#sync calls app.theme to resolve semantic color names into concrete Style values for MockWidget::Props. Exposed as a settable property so tests (and apps) can inject a custom theme.

Constants

Log = ::Log.for("phosphor.app")

Constructors

Instance methods

add_port(port : Port, screen : Screen(S, M)) : Void

Registers port under screen, starts its fiber, and returns.

The port is appended to the registry entry for screen, creating the entry on the first call for that screen. When screen unmounts, the framework calls stop_ports_for(screen) to stop and deregister all ports registered here — screens do not need to manage this themselves.

Source
add_port(port : Port) : Void

Registers port scoped to the currently mounting screen.

Convenience overload for Screen#on_mount where the screen reference is implicit — the framework sets the active screen before calling on_mount and clears it after. Raises if called outside of that lifecycle; use add_port(port, screen) from any other context.

Source
color_profile

The terminal's color rendering capability.

Stubbed as TrueColor until Phase 15 adds $COLORTERM / $TERM / $NO_COLOR detection. buf.set_string uses this to downsample colors before writing ANSI.

Source
dark_background?

Whether the terminal uses a dark background.

Stubbed as true until Phase 15 adds OSC 11 detection. Widgets and themes use this to choose appropriate Style variants in Widget#sync.

Source
event_channel

The shared channel every Port (including ones registered via #add_port) writes events onto. Exposed read-only so specs driving a Port directly (e.g. FakeTickPort#tick!) can confirm what does or doesn't arrive, without needing their own separate channel.

Source
inline_dismiss

Erases the active Inline block entirely, in response to Cmd::InlineDismiss — unlike normal exit, no trace of the block is left in scrollback. Only clears the block; the dispatch loop that calls this still sets the quit flag itself afterward, since a plain method can't reach #run's local quit variable — that keeps the existing quit path (and its ensure-guaranteed Terminal#exit) responsible for actually stopping the loop, per Cmd::InlineDismiss's contract of not calling Terminal#exit directly.

Inline mode only — raises RuntimeError in Fullscreen, same as #print.

See DESIGN.md § "Inline block transitions".

Source
inline_resize(height : Int32) : Void

Changes the active Inline block's height, in response to Cmd::InlineResize. Clears the old block, re-reserves the terminal rows at the new height, and forces a full redraw. Content is not preserved across the resize.

Inline mode only — raises RuntimeError in Fullscreen, same as #print.

See DESIGN.md § "Inline block transitions".

Source
ports_for(screen : Screen(S, M)) : Array(Port)

Returns the ports currently registered for screen, or an empty array if it has none (including after #stop_ports_for removed its entry). Read-only counterpart to #add_port/#stop_ports_for, for callers and specs that need to confirm registration state rather than change it.

Source
renderer

Returns the running session's Renderer — used by Screen#transition_widget to drive View#transition_widget's renderer.invalidate call once playback completes.

Raises if called before #run, the same @renderer || raise pattern #push_screen/#pop_screen/#replace_screen/#print already use for this ivar — there's no Renderer to return outside a running session.

Source
router

The Router::Definition passed to #run, or nil if the app didn't pass one. HelpScreen reads this to render app.router.generate_help — see DESIGN.md § "Router: your routes.rb". Settable (like #theme) so specs can inject one directly without going through #run, which isn't safely callable outside a real terminal session.

Source
router=(router : Router::Definition | Nil)

The Router::Definition passed to #run, or nil if the app didn't pass one. HelpScreen reads this to render app.router.generate_help — see DESIGN.md § "Router: your routes.rb". Settable (like #theme) so specs can inject one directly without going through #run, which isn't safely callable outside a real terminal session.

Source
run(initial_state : S, initial_screen : Screen(S, M).class, mode : RenderMode = RenderMode::Fullscreen, height : Int32 | Terminal::AutoHeight | Nil = nil, theme : Theme = Theme.new, mouse : Bool = false, log_path : String | Nil = nil, log_max_size : Int32 = (10 * 1024) * 1024, log_keep : Int32 = 5, debug : Bool = false, debug_hotkey : Key = Key::F12, debug_scroll_down_hotkey : Key = Key::F11, debug_scroll_up_hotkey : Key = Key::ShiftF11, router : Router::Definition | Nil = nil, stats : Bool = false, max_fps : Int32 = 60, transition : Transition(S, M) | Nil = nil, backend : Backend | Nil = nil) : Void

Boots the application and runs the main event loop until the process exits.

Creates a fresh Terminal, Renderer, event channel, and the initial screen, then enters a loop that drains events, dispatches messages, handles state transitions, fires async Cmds, and renders each frame.

terminal.enter is called before the loop with the shared event channel so SIGWINCH pushes a ResizeEvent into the loop automatically. terminal.exit is guaranteed by an ensure block so raw mode and the alternate screen are always restored.

initial_state is the first S value passed to initial_screen's on_mount. initial_screen is the Screen(S, M) subclass the app starts on.

mode selects RenderMode::Fullscreen (the alternate screen buffer, the default) or RenderMode::Inline (a fixed-height block in normal terminal flow, below the shell prompt). height is required when mode is Inline; unused otherwise. Pass Terminal::AUTO_HEIGHT instead of an Int32 to grow the block to fit the initial screen's mounted widgets' declared content (MockWidget#preferred_height) instead of using a fixed row count — re-measured on every ResizeEvent, since some widgets (e.g. ParagraphMock) wrap differently at a new width. Falls back to Terminal::DEFAULT_AUTO_HEIGHT when no mounted widget declares a preference.

theme is the Theme widgets resolve semantic color names against via app.theme — see Widget#sync. Defaults to Theme.new.

Set mouse to true to enable mouse tracking (clicks, drags, wheel scroll), delivered as MouseEvents. Off by default.

log_path, log_max_size, and log_keep configure Logger.setup: where the JSON-lines log file is written (default ${XDG_STATE_HOME:-$HOME/.local/state}/phosphor/<program_name>.log), its size-based rotation threshold (default 10 MB), and how many rotated files to keep (default 5).

Set debug to true to enable the debug overlay (the app's own state, View focus/subscription info), toggled on and off with debug_hotkey (default Key::F12). debug defaults to false so a production app can never accidentally expose internal state just because a user happens to press F12 — the hotkey does nothing unless explicitly opted into.

debug_scroll_down_hotkey (default Key::F11) and debug_scroll_up_hotkey (default Key::ShiftF11) scroll the overlay's own content without moving focus away from the active screen — the main app keeps receiving every other key normally while the overlay is open. Like debug_hotkey, both do nothing unless debug is true; unlike debug_hotkey, they're swallowed (never reach the screen) whenever debug is true, even while the overlay itself is closed — see #dispatch_event. Scroll position resets to the top each time the overlay closes, so it never reopens mid-scroll.

Pass router (the Router::Definition returned by Router.define) to make it available as app.router — the built-in HelpScreen reads it to render a keybinding reference. Omit it (nil) if the app doesn't use Router; HelpScreen falls back to empty content rather than raising.

Set stats to true to have the debug overlay display a performance section — FPS plus Renderer#stats (cells changed, bytes written, average draw time) — above the State:/View: info that's always there whenever the overlay is visible. With debug alone (no stats), that whole section is omitted, not just the Renderer#stats part of it. Only takes effect together with debug and the debug hotkey, same as the rest of the overlay. Renderer always collects these numbers regardless of stats; this only controls whether they're shown.

max_fps caps how often the main loop actually draws — Renderer#draw skips (returns without rendering) any call arriving less than 1.second / max_fps after the previous completed draw. Defaults to 60. This bounds wasted work when events arrive faster than the terminal can usefully redraw (e.g. a burst of resolved Cmds, or a widget with a very short animation_interval); it does not slow down an app that already renders less often than the cap.

transition is the Transition(S, M) used to animate between screens, or nil (the default) for the existing instant swap, via NoTransition. Not yet consumed by Cmd::Push/Cmd::Pop/ Cmd::Replace — see @transition's own doc comment.

backend is the Backend the session's Terminal delegates to — nil (the default) leaves Terminal.new to build its own AnsiBackend, i.e. a real terminal, with no change in behavior. Pass a TestBackend here to drive #run in a spec without a real terminal:

# Default — real terminal, no change required at existing call sites
App.new.run(state, MyScreen)

# Test — inject TestBackend
backend = TestBackend(MyState, MyMsg).new
App.new.run(state, MyScreen, backend: backend)
Source
stats?

Whether App.run(stats: true) was passed — gates only whether the debug overlay displays Renderer#stats, not whether Renderer collects it (that's unconditional; see Renderer#draw's doc comment).

Source
stop_ports_for(screen : Screen(S, M)) : Void

Stops every port registered for screen and removes it from the registry.

Called by the screen navigation machinery (Phase 19) when a screen pauses or unmounts. Each port's fiber exits cleanly on its next iteration. Does nothing if screen has no registered ports.

Source
terminal

Returns the running session's Terminal — used by Screen#transition_widget to drive View#transition_widget's renderer.draw_frame calls, which need a Terminal to flush each transition step to as it plays.

Raises if called before #run, same @terminal || raise pattern #renderer above already uses for its own ivar.

Source
theme
Source
theme=(theme : Theme)
Source