API Reference

July 11, 2026 · View on GitHub

Complete reference for all public types, functions, and constants in maya.

Table of Contents


App Framework

run<P>()

template <Program P>
void run(RunConfig cfg = {});

The primary entry point for interactive apps. P must satisfy the Program concept (see below). The runtime owns the event loop, calling P::update on each message and re-rendering via P::view.

Program concept

template <typename P>
concept Program = requires {
    typename P::Model;
    typename P::Msg;
} && (HasFullInit<P> || HasSimpleInit<P>)
  && requires(P::Model m, P::Msg msg) {
    { P::update(std::move(m), std::move(msg)) } -> std::convertible_to<std::pair<P::Model, Cmd<P::Msg>>>;
} && requires(const P::Model& m) {
    { P::view(m) } -> std::convertible_to<Element>;
};

A Program is a struct with:

  • Model — the app state type (plain data)
  • Msg — a std::variant of all possible messages
  • init() — returns Model (simple) or std::pair<Model, Cmd<Msg>> (full)
  • update(Model, Msg) — returns std::pair<Model, Cmd<Msg>>
  • view(const Model&) — returns Element
  • subscribe(const Model&) (optional) — returns Sub<Msg>

Cmd<Msg>

template <typename Msg>
class Cmd {
    static Cmd none();
    static Cmd quit();
    static Cmd batch(std::vector<Cmd> cmds);      // also variadic
    static Cmd after(std::chrono::milliseconds delay, Msg msg);
    static Cmd task(std::function<void(std::function<void(Msg)>)> fn);
    static Cmd task_isolated(...);                // detached thread
    static Cmd set_title(std::string title);
    static Cmd write_clipboard(std::string text);
    static Cmd query_clipboard();

    // Interactive-child escape hatch (see below).
    template <std::invocable F> static Cmd suspend(F&& run);

    // Inline scrollback control (see docs/internals/witness-chain.md).
    static Cmd commit_scrollback(ScrollbackDebt debt);
    static Cmd commit_scrollback_overflow();
    static Cmd force_redraw();
    static Cmd reset_inline();
};

Commands represent side effects. Cmd<Msg>{} (or Cmd::none()) means no effect. Cmd::quit() exits the app. Cmd::batch() combines multiple commands. Cmd::after() sends a delayed message. Cmd::task() runs an async function that may produce a message.

Cmd::query_clipboard() / write_clipboard() — clipboard I/O over the escape channel

static Cmd write_clipboard(std::string text);   // put text on the clipboard
static Cmd query_clipboard();                    // read clipboard → PasteEvent

Both travel in-band over the terminal escape channel, so they work across SSH with no remote clipboard tool. write_clipboard() emits OSC 52. query_clipboard() asks the terminal to send its clipboard back; the reply arrives as a PasteEvent (matched by pasted() / Sub::on_paste).

Maya picks the read protocol from the host: OSC 52 (text-only) by default, or kitty's OSC 5522 multi-format read when a kitty host is detected (KITTY_WINDOW_ID set, or kitty-like TERM — both survive an sshd hop). Only OSC 5522 can carry image bytes, which is what enables screenshot paste over SSH: the decoded image is delivered as one PasteEvent whose content holds the raw bytes. See Events → Clipboard reads and image paste over SSH.

Cmd::suspend() — hand the real terminal to an interactive child

template <std::invocable F>
    requires std::convertible_to<std::invoke_result_t<F>, Msg>
static Cmd suspend(F&& run);

Suspends the TUI and hands the real terminal to run — the escape hatch for interactive children (sudo password prompts, $EDITOR, pagers). The runtime tears the TUI down to a clean, cooked (line-disciplined) tty, calls run() synchronously on the UI thread — the user is interacting with the child, so there is nothing else to do — then restores raw mode + the TUI escapes, re-anchors the renderer (inline: a fresh serialize below the child's output; fullscreen: a full repaint), and dispatches the Msg that run returned so update() can fold the child's result back into the model.

run returning a Msg (rather than void) is what closes the loop: the callable typically spawns the child with inherited stdio, tees its output to a buffer, and returns a completion Msg carrying the exit code + captured bytes.

return Cmd<Msg>::suspend([] {
    int rc = std::system("sudo -v");           // child owns the tty here
    return Msg{SudoDone{ .code = rc }};        // folded back after restore
});

See the Cmd::suspend demo in d726f07 and App::suspend in maya/app/app.hpp.

Sub<Msg>

template <typename Msg>
class Sub {
    static Sub none();
    static Sub batch(std::vector<Sub> subs);   // also variadic: batch(a, b, c)
    static Sub on_key(std::function<std::optional<Msg>(const KeyEvent&)> fn);
    static Sub on_mouse(std::function<std::optional<Msg>(const MouseEvent&)> fn);
    static Sub on_resize(std::function<Msg(Size)> fn);
    static Sub on_paste(std::function<Msg(std::string)> fn);
    static Sub every(std::chrono::milliseconds interval, Msg msg);
    static Sub on_animation_frame(Msg msg);    // sugar for every(16ms, msg)
};

Subscriptions declare which external events the app listens to. Returned from the optional subscribe(const Model&) method. Subscriptions are re-evaluated when the model changes, enabling conditional subscriptions (e.g. only tick while a timer is running).

on_animation_frame() is every(16ms, msg) under one shared timer engine — there is no separate animation pump. Drop the subscription from subscribe() and the ticks stop; the loop returns to idle wait with zero bytes per frame.

Animation-frame requests

// maya/app/app.hpp
void request_animation_frame() noexcept;

The redraw-only counterpart to Sub::on_animation_frame. A widget calls it from its build() each frame it wants to keep animating; the run loop folds these requests into the same wake schedule as Sub::Every timers. The distinction is intent, not mechanism: Every delivers a Msg (drives update → model), a frame request only asks for a repaint — pure visual layer (cursor blink, scramble caret, fade) that reads wall-clock in build() and mutates nothing. A widget that stops calling drops out of the next collection and the loop idles. Idempotent within a frame.

key_map<Msg>() and key predicates

using KeySpec = std::variant<char, SpecialKey>;

template <typename Msg>
Sub<Msg> key_map(std::initializer_list<std::pair<KeySpec, Msg>> entries);

// Pure predicates for use inside subscribe() / on_key filters:
bool key_is(const KeyEvent& k, char c) noexcept;      // also char32_t / SpecialKey
bool ctrl_is(const KeyEvent& k, char c) noexcept;     // Ctrl+c, not Ctrl+Alt+c
bool alt_is(const KeyEvent& k, char c) noexcept;      // Alt+c, not Ctrl+Alt+c

key_map() builds a Sub::on_key from a declarative key→message table:

return key_map<Msg>({
    {'q', Quit{}}, {'+', Increment{}},
    {SpecialKey::Up, Increment{}},
});

The *_is predicates are the building blocks for hand-written on_key filters where a table isn't expressive enough (modifiers, ranges).

RunConfig

struct RunConfig {
    std::string_view title      = "";
    int              fps        = 0;       // 0 = event-driven
    bool             mouse      = false;
    Mode             mode       = Mode::Fullscreen;
    Theme            theme      = theme::dark;
};

run() — Simple Convenience API

template <SimpleEventFn EventFn, SimpleRenderFn RenderFn>
void run(RunConfig cfg, EventFn&& event_fn, RenderFn&& render_fn);

template <SimpleEventFn EventFn, SimpleRenderFn RenderFn>
void run(EventFn&& event_fn, RenderFn&& render_fn); // default RunConfig

A closure-based entry point that requires no boilerplate. Suitable for most interactive apps that don't need the full Elm-architecture of run<P>().

Event function: (const Event&) -> bool (returning false quits) or (const Event&) -> void (call quit() to exit).

Render function: () -> Element or (const Ctx&) -> Element.

Global control — quit() / set_mouse()

// maya/app/quit.hpp
void quit() noexcept;            // request a clean exit from run() / live()
void set_mouse(bool on) noexcept; // toggle mouse capture at runtime

set_mouse() flips terminal mouse reporting on/off while the app runs — off hands the scroll wheel back to the terminal (native scrollback), on recaptures clicks/drag/wheel. The request is applied on the next loop iteration and keeps the runtime's mouse state in sync for a clean terminal restore on exit. Works from run() event functions and from a Program's update()/subscribe(). For Program apps Cmd<Msg>::quit() is preferred over quit(); there is no Cmd form of set_mouse() yet, so call the free function. See Events → Mouse capture vs. native terminal scroll.

Ctx

struct Ctx {
    Size  size;   // Current terminal dimensions
    Theme theme;  // Active color theme
};

The Ctx overload of the render function is useful for adaptive layouts that need to inspect the terminal size or current theme at render time.

live()

template <AnyLiveRenderFn RenderFn>
void live(LiveConfig cfg, RenderFn&& render_fn);

LiveConfig

struct LiveConfig {
    int   fps       = 30;
    int   max_width = 0;     // 0 = auto-detect
    bool  cursor    = false;
};

canvas_run()

Status canvas_run(
    CanvasConfig                                   cfg,
    std::function<void(StylePool&, int w, int h)>  on_resize,
    std::function<bool(const Event&)>              on_event,
    std::function<void(Canvas&, int w, int h)>     on_paint);

CanvasConfig

struct CanvasConfig {
    int         fps        = 60;
    bool        mouse      = false;
    Mode        mode       = Mode::Fullscreen;
    std::string title;
};

print()

void print(const Element& root);
void print(const Element& root, int width);

quit()

void quit() noexcept;  // Schedule clean exit after current frame

In Program apps, prefer Cmd<Msg>::quit() from update() instead of calling quit() directly. The free function is still available for live() and canvas_run() apps.


DSL Nodes

All nodes satisfy the Node concept:

template <typename T>
concept Node = requires(const T& n) {
    { n.build() } -> std::convertible_to<Element>;
};

template <typename T>
concept DslChild = Node<T> || ElementRange<T>;

DslChild is what v() and h() accept — either a Node (compile-time or runtime) or an ElementRange (e.g. std::vector<Element>).

TextNode

template <Str S, CTStyle Sty = CTStyle{}>
struct TextNode {
    Element build() const;
};

Created via t<"...">.

RuntimeTextNode

template <typename S>
struct RuntimeTextNode {
    S content;
    Style style{};
    TextWrap wrap{TextWrap::Wrap};

    operator Element() const;
    Element build() const;
};

Created via text(). Supports | Bold, | Fg<R,G,B>, etc.

BoxNode

template <FlexDirection Dir, BoxCfg Cfg, typename... Children>
struct BoxNode {
    std::tuple<Children...> children;
    Element build() const;
};

Created via v() and h(). Supports layout pipes.

DynNode

template <typename F>
struct DynNode {
    F fn;
    Element build() const;  // Calls fn()
};

Created via dyn().

MapNode

template <typename R, typename Proj>
struct MapNode {
    R range;
    Proj proj;
    Element build() const;  // Iterates range, applies proj
};

Created via map().

SpacerNode

struct SpacerNode {
    operator Element() const;
    Element build() const;  // BoxElement with grow=1
};

SepNode

struct SepNode {
    operator Element() const;
    Element build() const;  // Horizontal separator line
};

VSepNode

struct VSepNode {
    operator Element() const;
    Element build() const;  // Vertical separator line
};

BlankNode

struct BlankNode {
    operator Element() const;
    Element build() const;  // Empty TextElement
};

DSL Style Tags

inline constexpr StyTag<...> Bold;
inline constexpr StyTag<...> Dim;
inline constexpr StyTag<...> Italic;
inline constexpr StyTag<...> Underline;
inline constexpr StyTag<...> Strike;
inline constexpr StyTag<...> Inverse;

template <uint8_t R, uint8_t G, uint8_t B>
inline constexpr StyTag<...> Fg;

template <uint8_t R, uint8_t G, uint8_t B>
inline constexpr StyTag<...> Bg;

DSL Layout Tags

template <int T, int R = T, int B = T, int L = R>
inline constexpr PadTag<T,R,B,L> pad;

template <int G>
inline constexpr GapTag<G> gap_;

template <BorderStyle BS>
inline constexpr BorderTag<BS> border_;

template <uint8_t R, uint8_t G, uint8_t B>
inline constexpr BColTag<R,G,B> bcol;    // Requires border first

template <int G = 1>
inline constexpr GrowTag<G> grow_;

template <int W>
inline constexpr WidthTag<W> w_;        // fixed width; also wraps text nodes

template <int H>
inline constexpr HeightTag<H> h_;       // fixed height

DSL Runtime Pipes

Runtime pipe tags for dynamic values. Same | syntax as compile-time pipes.

Layout Pipes

FunctionTag TypeDescription
padding(int) / padding(int,int) / padding(int,int,int,int)RPadRuntime padding
gap(int)RGapGap between children
margin(int) / margin(int,int) / margin(int,int,int,int)RMarginOuter margin
grow(float g = 1.0f)RGrowFlex grow factor
width(int)RWidthFixed width
height(int)RHeightFixed height

Border Pipes

FunctionTag TypeDescription
border(BorderStyle)RBorderBorder style
bcolor(Color)RBColBorder color
btext(string, pos = Top, align = Start)RBTextBorder text label (pos/align default)

Style Pipes

FunctionTag TypeDescription
fgc(Color)RFgForeground color
bgc(Color)RBgBackground color

Alignment Pipes

FunctionTag TypeDescription
align(Align)RAlignCross-axis alignment
justify(Justify)RJustMain-axis distribution
overflow(Overflow)ROvfOverflow behavior

Scroll Pipes

Wrap content in a scroll viewport backed by a caller-owned ScrollState. The renderer applies the scroll offset and writes max_x/max_y back after layout so clamping is automatic.

FunctionDescription
scroll(ScrollState&)Scroll on both axes, viewport = allocated size
scroll(ScrollState&, int viewport_h)Vertical scroll, fixed viewport height
scroll(ScrollState&, int w, int h)Both axes, fixed viewport w×h
scrolly(ScrollState&, int h)Vertical-only scroll, fixed height
scrollx(ScrollState&, int w)Horizontal-only scroll, fixed width

Text-Wrap Tags

TagEffect on a text(...) node
clipTextWrap::TruncateEnd — hard-truncate at the box edge
nowrapTextWrap::NoWrap — overflow past the edge, never wrap

WrappedNode

template <Node Inner>
struct WrappedNode;

Created automatically when a runtime pipe is applied to any Node. Satisfies Node. Multiple runtime pipes chain onto the same WrappedNode without extra nesting.


DSL Factory Functions

// Compile-time text
template <Str S>
inline constexpr TextNode<S> t;

// Vertical stack — accepts Nodes and ElementRanges (e.g. vector<Element>)
template <DslChild... Cs>
constexpr auto v(Cs... cs) -> BoxNode<Column, BoxCfg{}, Cs...>;

// Horizontal stack — accepts Nodes and ElementRanges (e.g. vector<Element>)
template <DslChild... Cs>
constexpr auto h(Cs... cs) -> BoxNode<Row, BoxCfg{}, Cs...>;

// Runtime builders (promoted from detail namespace)
auto box() -> BoxBuilder;      // Base builder
auto vstack() -> BoxBuilder;   // Column direction
auto hstack() -> BoxBuilder;   // Row direction
auto center() -> BoxBuilder;   // Centered, grow=1

// Z-stack — layer children on top of one another
Element zstack(std::vector<Element> layers);

// Measure-aware component: receives the (width, height) it was allotted
Element component(std::function<Element(int w, int h)> fn);

// Responsive layout toolkit (see "Responsive layout" below)
Size measure_element(const Element& el, int max_w, int max_h = 1 << 20);
auto fill(std::function<Element(int w, int h)> fn, int min_w = 0, int min_h = 1);
auto adapt(std::function<Element(int w)> fn);
auto fit_row(std::vector<FitItem> items, int gap = 0);
auto fit_col(std::vector<FitItem> items, int gap = 0);
auto pick(std::vector<Element> alternatives);
auto clamp(Element el, int max_width, HAlign align = HAlign::Center);
auto responsive(std::vector<Bp> tiers);
Element place(Element child, HAlign h = HAlign::Center, VAlign v = VAlign::Middle);

// The grid — responsive layout with one number (see "Responsive layout" below)
auto row(std::vector<Element> cells, int min_width = 24) -> ComponentBuilder;
Element col(std::vector<Element> cells, int gap = 0);
auto grid(std::vector<Element> cells, int min_width) -> ComponentBuilder;
auto sidebar(Element rail, Element main, int width)  -> ComponentBuilder;

// Pretty text (see "Gradients" below)
Element gradient(std::string text, Color from, Color to, Style base = {});
Element gradient(std::string text, Gradient g, Style base = {});
Element rainbow(std::string text, Style base = {},
                float saturation = 0.85f, float lightness = 0.62f);
auto    gradient_rule(Color from, Color to, char32_t glyph = U'─');
auto    gradient_rule(Gradient g, char32_t glyph = U'─');

// Empty element (renders nothing, satisfies Node)
Element nothing();

// Zero-copy references (no element copy): render an externally-owned
// element vector / sealed ScrollbackLedger in place
Element list_ref(const std::vector<Element>* items);
Element ledger_ref(const ScrollbackLedger& ledger);

// Runtime text
template <typename S>
auto text(S&& content, Style s = {}) -> RuntimeTextNode<decay_t<S>>;

template <typename S>
auto text(S&& content, Style s, TextWrap w) -> RuntimeTextNode<decay_t<S>>;

// Dynamic node
template <typename F>
auto dyn(F&& fn) -> DynNode<decay_t<F>>;

// Map range
template <std::ranges::range R, typename Proj>
auto map(R&& range, Proj&& proj) -> MapNode<decay_t<R>, decay_t<Proj>>;

// each() — alias for map(), for discoverability
template <std::ranges::range R, typename Proj>
auto each(R&& range, Proj&& proj);

// Spacer/separator/blank (function forms)
constexpr auto spacer()    -> SpacerNode;
constexpr auto separator() -> SepNode;
constexpr auto blank()     -> BlankNode;

DSL Constants

inline constexpr SpacerNode space;
inline constexpr SepNode    sep;
inline constexpr VSepNode   vsep;
inline constexpr BlankNode  blank_;

inline constexpr BorderStyle Round  = BorderStyle::Round;
inline constexpr BorderStyle Single = BorderStyle::Single;
inline constexpr BorderStyle Thick  = BorderStyle::Bold;
inline constexpr BorderStyle Double = BorderStyle::Double;

Element Types

Element

struct Element {
    std::variant<BoxElement, TextElement, ElementList> inner;

    // Implicit constructors from each variant type
    Element(BoxElement);
    Element(TextElement);
    Element(ElementList);

    Element build() const;  // Returns *this (satisfies Node concept)
};

TextElement

struct TextElement {
    std::string content;
    Style       style{};
    TextWrap    wrap{TextWrap::Wrap};

    Size measure(int max_width) const;
    std::vector<std::string> format(int max_width) const;
};

BoxElement

struct BoxElement {
    FlexStyle             layout{};
    Style                 style{};
    BorderConfig          border{};
    Overflow              overflow{Overflow::Visible};
    std::vector<Element>  children;

    bool has_border() const;
    int  inner_horizontal() const;  // padding + border horizontal
    int  inner_vertical() const;    // padding + border vertical
};

ElementList

struct ElementList {
    std::vector<Element> items;
};

TextWrap

enum class TextWrap {
    Wrap,              // Word wrap at container width
    TruncateEnd,       // "Long te..."
    TruncateMiddle,    // "Lon...xt"
    TruncateStart,     // "...g text"
    NoWrap             // Overflow
};

Style System

Style

struct Style {
    std::optional<Color> fg, bg;
    bool bold = false, dim = false, italic = false;
    bool underline = false, strikethrough = false, inverse = false;

    Style with_fg(Color c) const;
    Style with_bg(Color c) const;
    Style with_bold(bool v = true) const;
    Style with_dim(bool v = true) const;
    Style with_italic(bool v = true) const;
    Style with_underline(bool v = true) const;
    Style with_strikethrough(bool v = true) const;
    Style with_inverse(bool v = true) const;

    Style merge(const Style& other) const;
    bool  empty() const;
    std::string to_sgr() const;
};

Style operator|(const Style& lhs, const Style& rhs);  // Merge

Color

class Color {
public:
    enum class Kind { Named, Indexed, Rgb };

    // Named colors (constexpr)
    static constexpr Color black();
    static constexpr Color red();
    static constexpr Color green();
    static constexpr Color yellow();
    static constexpr Color blue();
    static constexpr Color magenta();
    static constexpr Color cyan();
    static constexpr Color white();
    static constexpr Color bright_black();   // gray
    static constexpr Color bright_red();
    static constexpr Color bright_green();
    static constexpr Color bright_yellow();
    static constexpr Color bright_blue();
    static constexpr Color bright_magenta();
    static constexpr Color bright_cyan();
    static constexpr Color bright_white();
    static constexpr Color gray();

    // RGB (constexpr)
    static constexpr Color rgb(uint8_t r, uint8_t g, uint8_t b);

    // Hex (consteval — compile-time only)
    static consteval Color hex(uint32_t rgb);

    // HSL (constexpr)
    static constexpr Color hsl(float h, float s, float l);

    // Indexed (256-color)
    static Color indexed(uint8_t index);

    // Accessors
    Kind kind() const;
    uint8_t r() const, g() const, b() const;
    uint8_t index() const;

    // Adjustment (constexpr)
    Color lighten(float amount) const;
    Color darken(float amount) const;
    Color to_rgb() const;   // resolve Named/Indexed → true RGB channels

    // SGR sequences
    std::string fg_sgr() const;
    std::string bg_sgr() const;
};

Theme

struct Theme {
    Color primary, secondary, accent;
    Color success, error, warning, info;
    Color text, inverse_text, muted;
    Color surface, background, border;
    Color diff_added, diff_removed, diff_changed;
    Color highlight, selection, cursor, link;
    Color placeholder, shadow, overlay;

    static constexpr Theme derive(Theme base, auto&& patch);
};

namespace theme {
    inline constexpr Theme dark;
    inline constexpr Theme light;
    inline constexpr Theme dark_ansi;
    inline constexpr Theme light_ansi;
}

Border

BorderStyle

enum class BorderStyle {
    None, Single, Double, Round, Bold,
    SingleDouble, DoubleSingle, Classic, Arrow
};

BorderSides

struct BorderSides {
    bool top = false, right = false, bottom = false, left = false;

    static BorderSides all();
    static BorderSides none();
    static BorderSides horizontal();  // top + bottom
    static BorderSides vertical();    // left + right
};

BorderText

struct BorderText {
    std::string    content;
    BorderTextPos  position = BorderTextPos::Top;
    BorderTextAlign align   = BorderTextAlign::Start;
    int            offset   = 0;
};

BorderColors

struct BorderColors {
    std::optional<Color> top, right, bottom, left;
    static BorderColors uniform(Color c);
};

BorderConfig

struct BorderConfig {
    BorderStyle  style = BorderStyle::None;
    BorderSides  sides{};
    BorderColors colors{};
    std::optional<BorderText> text;

    bool empty() const;
};

Layout Types

FlexDirection

enum class FlexDirection { Row, Column, RowReverse, ColumnReverse };

FlexWrap

enum class FlexWrap { NoWrap, Wrap, WrapReverse };

Align

enum class Align { Start, Center, End, Stretch, Baseline, Auto };

Justify

enum class Justify { Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly };

Overflow

enum class Overflow { Visible, Hidden, Scroll };

Dimension

struct Dimension {
    enum class Kind { Auto, Fixed, Percent };
    Kind  kind;
    float value;

    static Dimension auto_();
    static Dimension fixed(int v);
    static Dimension percent(float v);

    bool is_auto() const;
    bool is_fixed() const;
    bool is_percent() const;
    int  resolve(int parent) const;
};

Dimension operator""_pct(unsigned long long v);  // 50_pct

FlexStyle

struct FlexStyle {
    FlexDirection direction = FlexDirection::Row;
    FlexWrap      wrap      = FlexWrap::NoWrap;
    Align         align_items = Align::Stretch;
    Align         align_self  = Align::Auto;
    Justify       justify     = Justify::Start;
    float         grow   = 0;
    float         shrink = 1;
    Dimension     basis  = Dimension::auto_();
    Dimension     width  = Dimension::auto_();
    Dimension     height = Dimension::auto_();
    Dimension     min_width  = Dimension::auto_();
    Dimension     min_height = Dimension::auto_();
    Dimension     max_width  = Dimension::auto_();
    Dimension     max_height = Dimension::auto_();
    int           gap = 0;
    Edges<int>    padding{};
    Edges<int>    margin{};
};

Edges<T>

template <typename T>
struct Edges {
    T top{}, right{}, bottom{}, left{};

    Edges() = default;
    Edges(T all);            // uniform
    Edges(T v, T h);         // vertical, horizontal
    Edges(T t, T r, T b, T l);

    T horizontal() const;    // left + right
    T vertical() const;      // top + bottom
};

Responsive Layout

Measure-driven primitives for layouts that restructure with the terminal size. Full treatment in Responsive Layouts. All are in maya::dsl (and maya::); solve_columns and its types live in <maya/layout/columns.hpp>.

row() / col() / grid() / GridOpts

// Cells side by side, sharing the width equally and EXACTLY (largest-
// remainder split — no ragged right edge) — wrapping, then stacking, by
// itself as the slot narrows: 4-across → 2×2 → one column. `min_width` is
// the one number: how wide one cell needs to be to look right.
auto row(std::vector<Element> cells, int min_width = 24) -> ComponentBuilder;

// Cells stacked top to bottom, each stretched to the full width (flex
// cross-stretch — the GTK "fill"). Pipe `| grow(1)` onto the child that
// should take the leftover height. Compose: col({ row({a, b}), table }).
Element col(std::vector<Element> cells, int gap = 0);

struct GridOpts {
    int  min       = 24;     // a cell's comfortable minimum width (columns)
    int  max_cols  = 0;      // cap cells-per-row; 0 = as many as fit
    int  gap_x     = 1;      // blank columns between cells
    int  gap_y     = 0;      // blank rows between rows
    bool grow_rows = false;  // rows share surplus height (definite slot)
};

// row's engine with the knobs exposed. Re-solved from the REAL slot width
// per frame (adapt() underneath), so nested grids collapse independently.
// A short last row keeps the same cell width so columns line up.
auto grid(std::vector<Element> cells, GridOpts opts = {}) -> ComponentBuilder;
auto grid(std::vector<Element> cells, int min_width)      -> ComponentBuilder;

//   row({cpu, mem, net, disk})        // side by side; stacks by itself
//   grid(cells, {.min = 24, .max_cols = 2})   // capped 2-across flow
struct SidebarOpts {
    int  width       = 32;    // the rail's fixed width (columns)
    int  stack_below = 0;     // stack when slot < this; 0 = auto (2×width)
    int  gap         = 1;     // blank columns between rail and main
    bool right       = false; // rail on the right instead of the left
};

// Fixed-width rail beside a main pane that takes the rest; the pair stacks
// vertically (reading order preserved) when the slot is too narrow. The
// default threshold (2×width) keeps them side-by-side only while the main
// pane gets at least as much as the rail.
auto sidebar(Element rail, Element main, SidebarOpts opts = {}) -> ComponentBuilder;
auto sidebar(Element rail, Element main, int width)             -> ComponentBuilder;

//   sidebar(row({cpu, mem, net, disk}), proc_table, 42)
//   // wide: 42-cell rail + table · medium: stats flow over the table
//   // narrow: one column — a whole dashboard in two lines.

measure_element()

// Runs a real layout pass over `el` and returns its natural size within the
// given bounds. The measurement uses the SAME engine the renderer uses, so it
// can never disagree with the eventual paint. Cheap enough to call per frame.
Size measure_element(const Element& el, int max_width, int max_height = 1 << 20);

Pass a large max_width (e.g. 1 << 14) for the natural one-line width; pass a real width to learn how many rows the fragment wraps to.

fill()

// A component that GROWS to fill the (w, h) its flex container allocates, then
// calls `fn` with the real allocated size at paint time. `min_w`/`min_h` set
// the measured minimum so grow has a finite basis. Sets grow(1) internally.
// The container must be DEFINITE on the fill axis for it to expand.
auto fill(std::function<Element(int w, int h)> fn, int min_w = 0, int min_h = 1)
    -> ComponentBuilder;

adapt()

// A component that BUILDS a different tree depending on the width it is given.
// `fn` receives the real allocated width at paint time; natural height is
// auto-measured from what `fn` returns (measure runs the callback).
auto adapt(std::function<Element(int w)> fn) -> ComponentBuilder;

fit_row() / FitItem

struct FitItem {
    Element el;
    int     keep = kKeepAlways;   // importance; lower ranks drop first
};

// A row that DROPS items when they don't fit: lowest-`keep` first (ties drop
// the rightmost) until the remainder fits. Widths come from measure_element
// over the real fragments. `kKeepAlways` items never drop. `gap` spaces the
// KEPT items. Grow spacers (dsl::space) measure 0 and always survive.
auto fit_row(std::vector<FitItem> items, int gap = 0) -> ComponentBuilder;

// The vertical fit_row: DROPS items when the slot is too SHORT, lowest-`keep`
// first, until what remains fits the rows actually given. Heights come from
// measure_element at the real slot width (wrapping accounted for). Natural
// size is ALL items — shedding only happens when a definite-height slot
// hands it fewer rows (flex shrink, on by default, delivers the budget).
auto fit_col(std::vector<FitItem> items, int gap = 0) -> ComponentBuilder;

pick()

// SwiftUI's ViewThatFits: the FIRST alternative whose real measured width
// fits the slot renders; richest first, the LAST is the always-rendered
// fallback. No breakpoints — the decision measures the actual fragments.
auto pick(std::vector<Element> alternatives) -> ComponentBuilder;

clamp()

// libadwaita's AdwClamp: content uses the full slot up to max_width, then
// stops growing and aligns (Center by default; Left/Right for corner-
// anchored content like toasts) — a web page's container column.
// Transparent below max_width. The "too wide" half of responsive design.
auto clamp(Element el, int max_width,
           HAlign align = HAlign::Center) -> ComponentBuilder;

responsive() / Bp

struct Bp {
    int min_width = 0;
    std::function<Element(int w)> build;
};

// Named width breakpoints over adapt(): the widest tier whose min_width the
// slot satisfies builds the view (the builder receives the real width). If
// the slot is narrower than every tier, the smallest tier is used.
auto responsive(std::vector<Bp> tiers) -> ComponentBuilder;

place() / HAlign / VAlign

enum class HAlign { Left, Center, Right };
enum class VAlign { Top, Middle, Bottom };

// Fill the slot the flex parent allocates and pin `child` at the given
// corner / edge / center. Tracks every resize with zero arithmetic.
Element place(Element child, HAlign h = HAlign::Center,
              VAlign v = VAlign::Middle);

!!! note ComponentBuilder (returned by component / fill / adapt / fit_row / responsive / gradient_rule) satisfies the Node concept — components go directly in v() / h() and accept runtime pipes (| grow(1), | width(40), | hit(id)).

solve_columns() / ColSpec / ColPlan

#include <maya/layout/columns.hpp>

inline constexpr int kKeepAlways = std::numeric_limits<int>::max();

struct ColSpec {
    int   min    = 1;             // minimum content width (cells)
    int   max    = 0;             // growth cap; 0 = unbounded (weight > 0 only)
    float weight = 0.0f;          // share of surplus space; 0 = fixed at min
    int   keep   = kKeepAlways;    // drop order: LOWER dropped first
};

struct ColPlan {
    std::vector<int> width;       // solved widths; 0 = dropped
    int gap = 0;

    bool has(std::size_t i) const;   // is column i visible?
    int  at(std::size_t i) const;    // solved width (0 when dropped/out of range)
    int  used() const;               // total cells: visible widths + inter-column gaps
};

// Solve one shared width plan for a table, so the header row and every body row
// read the same column widths. Drop phase (lowest keep first) then weighted
// surplus waterfill clamped at max. With an unbounded weighted column the plan
// fills `avail` exactly. Pure arithmetic — no Element types, no layout engine.
ColPlan solve_columns(std::span<const ColSpec> cols, int avail, int gap = 1);

Gradients

Multi-color "pretty" primitives. Full treatment in Styling › Gradients.

Gradient

#include <maya/maya.hpp>          // or <maya/style/gradient.hpp> standalone

struct Gradient {
    std::vector<Color> stops;

    Gradient(std::initializer_list<Color> stops);
    static Gradient two(Color from, Color to);

    // Sample at t ∈ [0,1] (clamped): linear RGB blend across the two nearest
    // stops. Named/Indexed stops resolve via Color::to_rgb() first.
    Color at(float t) const;
};

gradient() / rainbow() / gradient_rule()

// Horizontal gradient text — ONE TextElement with per-codepoint StyledRuns,
// so it wraps/truncates/measures exactly like plain text(). `base` carries
// non-color attributes (bold/italic) into every run.
Element gradient(std::string text, Color from, Color to, Style base = {});
Element gradient(std::string text, Gradient g, Style base = {});

// Full HSL hue sweep across the text width.
Element rainbow(std::string text, Style base = {},
                float saturation = 0.85f, float lightness = 0.62f);

// Full-width divider that re-tiles `glyph` to its real allocated width and
// sweeps the gradient across it. Responsive by construction.
auto gradient_rule(Color from, Color to, char32_t glyph = U'─') -> ComponentBuilder;
auto gradient_rule(Gradient g,           char32_t glyph = U'─') -> ComponentBuilder;

Event System

Event

using Event = std::variant<KeyEvent, MouseEvent, PasteEvent, FocusEvent, ResizeEvent>;

KeyEvent

using Key = std::variant<CharKey, SpecialKey>;

struct CharKey { char32_t codepoint; };

enum class SpecialKey {
    Up, Down, Left, Right, Home, End,
    PageUp, PageDown, Tab, BackTab,
    Backspace, Delete, Insert, Enter, Escape,
    F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12
};

struct Modifiers {
    bool ctrl = false, alt = false, shift = false, super_ = false;
    bool none() const;
};

struct KeyEvent {
    Key         key;
    Modifiers   mods;
    std::string raw_sequence;
};

MouseEvent

enum class MouseButton { Left, Right, Middle, ScrollUp, ScrollDown, None };
enum class MouseEventKind { Press, Release, Move };

struct MouseEvent {
    MouseButton    button;
    MouseEventKind kind;
    Columns        x;
    Rows           y;
    Modifiers      mods;
};

Other Events

struct PasteEvent  { std::string content; };
struct FocusEvent  { bool focused; };
struct ResizeEvent { Columns width; Rows height; };

Event Predicates

// Keyboard
bool key(const Event&, char c);
bool key(const Event&, char32_t cp);
bool key(const Event&, SpecialKey sk);
bool ctrl(const Event&, char c);
bool alt(const Event&, char c);
bool shift(const Event&, SpecialKey sk);
bool any_key(const Event&);
const KeyEvent* as_key(const Event&);

// Mouse
struct MousePos { int col, row; };
bool mouse_clicked(const Event&, MouseButton = MouseButton::Left);
bool mouse_released(const Event&, MouseButton = MouseButton::Left);
bool mouse_moved(const Event&);
bool scrolled_up(const Event&);
bool scrolled_down(const Event&);
std::optional<MousePos> mouse_pos(const Event&);
const MouseEvent* as_mouse(const Event&);

// Other
bool resized(const Event&, int* w = nullptr, int* h = nullptr);
bool pasted(const Event&, std::string* out = nullptr);
bool focused(const Event&);
bool unfocused(const Event&);

// Fire-and-forget
template <typename F> bool on(const Event&, char c, F&& fn);
template <typename F> bool on(const Event&, char c1, char c2, F&& fn);
template <typename F> bool on(const Event&, SpecialKey sk, F&& fn);

Signals

Signal<T>

template <typename T>
class Signal {
    Signal();                        // Default-construct T
    explicit Signal(U&& initial);    // Construct from value

    const T& get() const;           // Read (auto-tracks dependencies)
    const T& operator()() const;    // Shorthand for get()
    void set(const T& v);           // Write (notifies if changed)
    void set(T&& v);                // Write (move)
    void update(F&& fn);            // Mutate in-place (always notifies)
    auto map(F&& fn) const -> Computed<R>;  // Derive computed value
    uint64_t version() const;       // Change counter
};

Computed<T>

template <typename T>
class Computed {
    const T& get() const;           // Read (recomputes if dirty)
    const T& operator()() const;
};

template <std::invocable F>
auto computed(F&& fn) -> Computed<invoke_result_t<F>>;

Effect

class Effect {
    Effect();                        // Default (inactive)
    explicit Effect(F&& fn);        // Create and run immediately
    void dispose();                  // Unsubscribe from all deps
    bool active() const;
};

template <std::invocable F>
Effect effect(F&& fn);

Batch

class Batch {
    Batch();      // Begin batch
    ~Batch();     // End batch, flush notifications
};

template <std::invocable F>
decltype(auto) batch(F&& fn);  // Run fn inside a batch scope

Canvas

Canvas

class Canvas {
    Canvas(int width, int height, StylePool* pool);

    void set(int x, int y, char32_t ch, uint16_t style_id, uint8_t width = 0);
    void write_text(int x, int y, std::string_view text, uint16_t style_id);
    void fill(Rect region, char32_t ch, uint16_t style_id);
    void clear();
    void resize(int w, int h);

    Cell get(int x, int y) const;
    int  width() const;
    int  height() const;

    void push_clip(Rect clip);
    void pop_clip();
};

Cell

struct Cell {
    char32_t character;
    uint16_t style_id;
    uint16_t hyperlink_id;
    uint8_t  width;

    uint64_t pack() const;
    static Cell unpack(uint64_t v);
};

StylePool

class StylePool {
    uint16_t intern(const Style& s);
    const Style& get(uint16_t id) const;
    void clear();
    std::size_t size() const;
};

render_tree()

void render_tree(const Element& root, Canvas& canvas,
                 StylePool& pool, const Theme& theme);

Widgets

All widgets live in maya::widget. Full documentation in 13-widgets.md.

Input

WidgetHeaderDescription
Inputwidget/input.hppSingle-line text input with cursor and history
TextAreawidget/textarea.hppMulti-line text editor
Checkboxwidget/checkbox.hppToggle checkbox
ToggleSwitchwidget/checkbox.hppiOS-style toggle switch
Radiowidget/radio.hppRadio button group
Selectwidget/select.hppDropdown select menu
Sliderwidget/slider.hppNumeric slider
Buttonwidget/button.hppClickable button with variants
CommandPalettewidget/command_palette.hppFuzzy-search command launcher; clamped, rows shed detail when narrow

Data Display

WidgetHeaderDescription
Tablewidget/table.hppData table: rich cells (spans + width-adaptive builders), selection, height-aware windowing + scrollbar, host-owned scroll, sort indicators, flexible + shedding columns, hit rects, flow_rows() per-row Elements for host-owned flows
Treewidget/tree.hppExpandable tree view
Listwidget/list.hppSelectable item list
KeyHelpwidget/key_help.hppKeyboard shortcut legend; 2-col ↔ 1-col by real measurement (pick)
Badgewidget/badge.hppInline status badge
Calloutwidget/callout.hppInfo/success/warning/error callout box
Linkwidget/link.hppHyperlink element
ShortcutRowwidget/shortcut_row.hppWidth-adaptive keyboard hint row (Helix/k9s style)
ModelBadgewidget/model_badge.hppColor-coded active-model indicator
WidgetHeaderDescription
Tabswidget/tabs.hppTab bar navigation; falls back to ‹ active i/n › when narrow (pick)
Breadcrumbwidget/breadcrumb.hppBreadcrumb path navigation; collapses to first › … › last (pick)
Menuwidget/menu.hppMenu with selectable items; shortcuts shed when narrow
ActivityBarwidget/activity_bar.hppVertical icon sidebar
Scrollablewidget/scrollable.hppScrollable content region
Scrollbarwidget/scrollbar.hppVisual indicator for a ScrollState
Pickerwidget/picker.hppBordered modal picker with scrollable results
CommandPalettewidget/command_palette.hppFuzzy-search command launcher; clamped, rows shed detail when narrow

Display

WidgetHeaderDescription
StreamingMarkdownwidget/markdown.hppStreaming markdown renderer
Spinnerwidget/spinner.hppAnimated loading spinner
ProgressBarwidget/progress.hppProgress bar with percentage
Gaugewidget/gauge.hppGauge / meter display
Dividerwidget/divider.hppHorizontal or vertical divider
Imagewidget/image.hppTerminal image display
gradient()widget/gradient.hppColor gradient text builder
Disclosurewidget/disclosure.hppCollapsible disclosure section
Htmlwidget/html.hppRender a safe subset of HTML as styled Elements
Overlaywidget/overlay.hppBase layer + one anchored floating element

Overlay

WidgetHeaderDescription
Modalwidget/modal.hppModal dialog with buttons; clamped to dialog width
Popupwidget/popup.hppFloating popup
ToastManagerwidget/toast.hppToast notification manager; clamped right-anchored cards

Visualization

WidgetHeaderDescription
BarChartwidget/bar_chart.hppHorizontal/vertical bar chart
LineChartwidget/line_chart.hppLine chart with series
Sparklinewidget/sparkline.hppInline sparkline graph
Heatmapwidget/heatmap.hppGrid heatmap
Calendarwidget/calendar.hppCalendar date display
PixelCanvaswidget/canvas.hppPixel-level drawing canvas
FlameChartwidget/flame_chart.hppFlame graph for nested execution spans
Waterfallwidget/waterfall.hppTiming waterfall chart (devtools style)
Timelinewidget/timeline.hppVertical CI/pipeline event timeline
GitGraphwidget/git_graph.hppCommit graph with colored branch lines

Agent UI

Composable pieces of a Claude-Code / Zed-style agent conversation. Thread is the top-level viewport; the rest are the parts it (or a host) assembles.

WidgetHeaderDescription
Threadwidget/thread.hppTop-level viewport: empty → WelcomeScreen, else Conversation
WelcomeScreenwidget/welcome_screen.hppEmpty-thread brand splash + starters/hints
Conversationwidget/conversation.hppVertical list of turns with dividers + in-flight indicator
Turnwidget/turn.hppOne speaker turn (rail, role, checkpoint)
TurnDividerwidget/turn_divider.hppStyled rule between conversation turns
CheckpointDividerwidget/checkpoint_divider.hppFull-width "↺ Restore checkpoint" rule above a turn
Composerwidget/composer.hppState-driven bordered input box (idle/streaming/permission)
AgentTimelinewidget/agent_timeline.hppBordered Actions panel logging tool events for a turn
ToolBodyPreviewwidget/tool_body_preview.hppPer-ToolKind body detail under a timeline event
AppLayoutwidget/app_layout.hppTop-level chat-app frame (header/body/composer/overlay)
UserMessagewidget/message.hppChat user message bubble
AssistantMessagewidget/message.hppChat assistant message bubble
ThinkingBlockwidget/thinking.hppCollapsible AI thinking block
StreamingCursorwidget/streaming_cursor.hppPulsing "assistant is streaming" indicator
SystemBannerwidget/system_banner.hppSeverity-coloured system alert (ctx warning, rate limit)
TodoListwidget/todo_list.hppSession todo list card
PlanViewwidget/plan_view.hppTask plan with status tracking
Permissionwidget/permission.hppPermission approval prompt

Session / Diagnostics

WidgetHeaderDescription
DiffViewwidget/diff_view.hppSide-by-side or unified diff
InlineDiffwidget/inline_diff.hppTwo-line word-level LCS diff
FileChangeswidget/file_changes.hppSession file-change summary (created/modified/deleted + counts)
ChangesStripwidget/changes_strip.hppBordered "session has pending changes" banner
FileRefwidget/file_ref.hppFile reference with icon; dir collapses to …/ when narrow
LogViewerwidget/log_viewer.hppFilterable log viewer
SearchResultwidget/search_result.hppGrouped search results; paths keep their filename when narrow
ErrorBlockwidget/error_block.hppStructured error/exception card
GitStatuswidget/git_status.hppBranch + working-tree status
ContextWindowwidget/context_window.hppSegmented context-window usage meter
TokenStreamwidget/token_stream.hppLive token-rate sparkline + stats (compact/full)
CostTrackerwidget/cost_tracker.hppPer-turn + cumulative token/cost breakdown
ApiUsagewidget/api_usage.hppAPI rate-limit / request-count / latency display
ActivityIndicatorwidget/activity_indicator.hppSingle-row hex-dump activity tape

Status Bar

WidgetHeaderDescription
StatusBarwidget/status_bar.hppSingle-line streaming status bar (composes the pieces below)
TokenStreamSparklinewidget/token_stream_sparkline.hppLive tok/s rate + sparkline chip (adaptive width)
PhaseChipwidget/phase_chip.hppCurrent-phase verb + elapsed, breathing
TitleChipwidget/title_chip.hppBreadcrumb / title chip
ContextGaugewidget/context_gauge.hppContext-window usage gauge
PhaseAccentwidget/phase_accent.hppTop/bottom accent rail strip
StatusBannerwidget/status_banner.hppFull-width toast that takes over the activity row

Tool Widgets

WidgetHeaderDescription
ToolCallwidget/tool_call.hppGeneric tool invocation display
BashToolwidget/bash_tool.hppShell command tool call
ReadToolwidget/read_tool.hppFile read tool call
EditToolwidget/edit_tool.hppFile edit tool call
WriteToolwidget/write_tool.hppFile write tool call
FetchToolwidget/fetch_tool.hppHTTP fetch tool call
AgentToolwidget/agent_tool.hppSub-agent tool call
GitCommitToolwidget/git_commit_tool.hppGit commit operation card

Core Types

Strong<Tag, T>

template <typename Tag, typename T>
struct Strong {
    T value{};
    // Arithmetic: Strong + Strong, Strong - Strong, etc.
    // Comparison: ==, !=, <, >, <=, >=
};

Size / Position / Rect

using Columns = Strong<ColumnTag, int>;
using Rows    = Strong<RowTag, int>;

struct Size     { Columns width; Rows height; };
struct Position { Columns x; Rows y; };
struct Rect     { Position pos; Size size; };

Result / Status / Error

enum class ErrorKind {
    TerminalInit, Io, LayoutOverflow, InvalidStyle,
    InvalidUtf8, Unsupported, Signal, WouldBlock
};

struct Error {
    ErrorKind kind;
    std::string message;
    std::source_location location;

    static Error terminal(std::string msg);
    static Error io(std::string msg);
    static Error from_errno(std::string ctx);
};

template <typename T>
using Result = std::expected<T, Error>;
using Status = Result<void>;