{
  "markdown": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/everruns/tuika/v0.11.1/logo.svg\" width=\"144\" alt=\"tuika logo: two offset rounded interface panels intersect at a gold anchor point\">\n</p>\n\n<h1 align=\"center\">tuika</h1>\n\n<div align=\"center\">\n\n[![crates.io](https://img.shields.io/crates/v/tuika.svg)](https://crates.io/crates/tuika)\n[![docs.rs](https://img.shields.io/docsrs/tuika)](https://docs.rs/tuika)\n[![downloads](https://img.shields.io/crates/d/tuika.svg)](https://crates.io/crates/tuika)\n[![license](https://img.shields.io/crates/l/tuika.svg)](https://github.com/everruns/tuika/blob/main/LICENSE)\n![msrv](https://img.shields.io/badge/rust-1.88%2B-blue.svg) \\\n[Website](https://tuika.dev) · [Rust API](https://docs.rs/tuika) · [Getting started](https://github.com/everruns/tuika/blob/v0.11.1/docs/getting-started.md) · [Components](https://github.com/everruns/tuika/blob/v0.11.1/docs/components.md) · [Layout](https://github.com/everruns/tuika/blob/v0.11.1/docs/layout.md) ·\n[Markdown](https://github.com/everruns/tuika/blob/v0.11.1/docs/markdown.md) · [Charts](https://github.com/everruns/tuika/blob/v0.11.1/docs/charts.md) ·\n[Terminal features](https://github.com/everruns/tuika/blob/v0.11.1/docs/features.md) ·\n[Keymap](https://github.com/everruns/tuika/blob/v0.11.1/docs/keymap.md) · [Input routing](https://github.com/everruns/tuika/blob/v0.11.1/docs/routing.md) ·\n[Styling](https://github.com/everruns/tuika/blob/v0.11.1/docs/styling.md) ·\n[Themes](https://github.com/everruns/tuika/blob/v0.11.1/docs/themes.md) \\\n[Showcases](https://github.com/everruns/tuika/blob/v0.11.1/docs/showcases.md) · [Examples](#runnable-examples) ·\n[Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) ·\n[Report a bug](https://github.com/everruns/tuika/issues)\n\n</div>\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/everruns/tuika/main/docs/hero.gif\" width=\"880\" alt=\"Animated tuika gallery: a terminal window with tabs, an activity panel of spinners, progress bars and a loader, a command palette, a commit-message input, and a status bar — all animating.\">\n</p>\n\n<div align=\"center\">\n\n### tuika's goal is to become the default TUI [application](https://github.com/everruns/tuika/blob/v0.11.1/docs/showcases.md) framework for Rust\n\n**Build the app, not the render loop.**\n\n</div>\n\n<details>\n<summary>Table of contents</summary>\n\n- [Install](#install)\n- [Model](#model)\n- [Crate layout](#crate-layout)\n- [Components](#components)\n- [Example](#example)\n- [Owned scenes, dialogs, and forms](#owned-scenes-dialogs-and-forms)\n- [Markdown and syntax highlighting](#markdown-and-syntax-highlighting)\n- [Theming](#theming)\n- [Runnable examples](#runnable-examples)\n- [Declarative DSL (`view!`)](#declarative-dsl-view)\n- [Ratatui interoperability](#ratatui-interoperability)\n- [Screen modes, lifecycle, and runner](#screen-modes-lifecycle-and-runner)\n- [Images](#images)\n- [Mouse, selection, and clipboard](#mouse-selection-and-clipboard)\n- [Testing your UI](#testing-your-ui)\n- [Used in](#used-in)\n- [Compatibility](#compatibility)\n- [Extending](#extending)\n- [License](#license)\n\n</details>\n\nRust has excellent terminal *rendering*. What it has mostly left to each\napplication is everything above that — layout, overlays, focus, input, the\nterminal lifecycle. tuika is that missing layer, and wants to be the standing\nanswer to \"what do I build a Rust TUI *application* on?\": start with\n`cargo add tuika`, describe your screen, and get a real app instead of a render\nloop.\n\nYou write views; tuika owns the rest:\n\n- **A whole app, not a widget set** — [flexbox layout](#model), anchored\n  [overlays](#owned-scenes-dialogs-and-forms), focus, a declarative\n  [keymap](https://github.com/everruns/tuika/blob/v0.11.1/docs/keymap.md), [themes and stylesheets](#theming), and a\n  [runner](#screen-modes-lifecycle-and-runner) that owns raw mode, the alternate\n  screen (or a [split footer](#screen-modes-lifecycle-and-runner) over live\n  scrollback), and event translation.\n- **Batteries the terminal era expects** — [30+ components](#components)\n  including streaming [Markdown](https://github.com/everruns/tuika/blob/v0.11.1/docs/markdown.md) with pluggable syntax\n  highlighting, [images](#images) over Kitty/iTerm2/Sixel, adaptive\n  [charts](https://github.com/everruns/tuika/blob/v0.11.1/docs/charts.md), mermaid diagrams,\n  [mouse selection and clipboard](#mouse-selection-and-clipboard), and\n  [native OSC 9;4 progress](#native-terminal-progress).\n- **No lock-in** — already have ratatui widgets? Turn on the `ratatui` feature,\n  wrap any of them in [`RatatuiView`](#ratatui-interoperability), and they\n  compose like built-ins. Your own types implement the same `View` trait the\n  built-ins do (see [Extending](#extending)).\n- **Boring where it counts** — no reconciler, no retained tree, no runtime, no\n  macro DSL you are forced into. Views are rebuilt each frame and tuika diffs the\n  cell buffer. Rendering is deterministic, so\n  [UI is unit-tested](#testing-your-ui) against an in-memory buffer with no\n  terminal at all.\n- **Small enough to adopt without a second thought** — a self-contained crate\n  depending only on `crossterm`, `unicode-segmentation`, `unicode-width`, and\n  `pulldown-cmark`: 32 crates in the default graph, of which `crossterm` is 27.\n  Anything heavy — grammars, diagram layout, image decoding — lives behind a\n  trait in a companion crate or your host.\n\nIt is host-agnostic: it knows nothing about the application embedding it, and no\ntype, feature, or default exists to serve one host. tuika owns its stack down to\nthe escape sequences — cell grid, backend, and terminal loop included — and has\nno runtime dependency on ratatui. That is a statement about dependencies, not a\nrivalry: ratatui is why a Rust TUI ecosystem exists, and every widget written for\nit still composes here through the optional `ratatui` feature (see\n[Compatibility](#compatibility)). (The optional `async` feature adds Tokio for\n[`AsyncRunner`](#screen-modes-lifecycle-and-runner); it is off by default.)\n\nSee what that buys in practice: the [showcases](https://github.com/everruns/tuika/blob/v0.11.1/docs/showcases.md) are\nrecordings of real applications running on tuika (also listed under\n[Used in](#used-in)), and the [`codex` example](examples/codex) is a whole\ncoding-agent UI built with nothing else.\n\n## Install\n\n```bash\ncargo add tuika\n```\n\nThat is the whole install for most applications — `Rect`, `Color`, `Style`,\n`Line`, `Span`, and the rest come from `tuika::ui` (or the prelude).\n\nTo render existing **ratatui widgets** inside tuika, turn the feature on and add\n`ratatui` to your own crate:\n\n```toml\ntuika = { version = \"0.12\", features = [\"ratatui\"] }\nratatui = \"0.30\"\n```\n\nSee [Compatibility](#compatibility). `crossterm` remains part of tuika's public\nsurface for terminal events either way.\n\n## Model\n\n- **Views** (`view::View`) are rebuilt from application state every frame. This\n  is cheap because tuika diffs the resulting cell buffer against the last one, so\n  there is no reconciler.\n- **State** that must survive across frames — scroll offset, selection index,\n  focus, dock visibility — lives in host-persisted `*State` structs (the\n  `StatefulWidget` idiom), not in the view tree.\n- **Live data** (`Live` / `LiveView`) is shared application state read at render\n  time. Updates request a redraw from the runner; Tuika does not spawn data\n  sources or reconcile a retained widget tree.\n- **Layout** is an [integer-native flexbox subset](https://github.com/everruns/tuika/blob/v0.11.1/docs/layout.md) (`layout`): wrapped flex lines,\n  independent basis/grow/shrink/min/max child styles, cross-line alignment, and\n  exact boundary rounding over one direction-agnostic solver. `Flow` packages\n  intrinsic wrapping; `Grid` is the smaller equal-column, row-major alternative\n  to adopting CSS Grid.\n- **Overlays** (`overlay`) anchor a view over the base tree; the **host**\n  (`host`) owns the alternate screen, translates crossterm input, and\n  composites the frame.\n- **Keymap** ([`keymap`](https://github.com/everruns/tuika/blob/v0.11.1/docs/keymap.md)) resolves declarative key bindings to\n  named commands: chords (`ctrl+r`) and multi-stroke sequences (`g g`) grouped\n  into prioritized, mode-gated `Layer`s, dispatched from a translated `Key` and\n  queryable for help/`KeyHints` surfaces. Character chords are exact logical\n  text (`A`, `?`, `ж`), so the active keyboard layout is applied before\n  matching; Shift stays explicit for non-character keys such as `Shift+Enter`.\n  Host-agnostic, so it unit-tests without a terminal. See the\n  [keymap guide](https://github.com/everruns/tuika/blob/v0.11.1/docs/keymap.md).\n- **Input routing** ([`routing`](https://github.com/everruns/tuika/blob/v0.11.1/docs/routing.md)) delivers an event to the\n  surface that owns input this frame — every event kind through one\n  registration, so a paste cannot take a different path from a key. `Router`\n  reads the focus registry an overlay-bearing `Scene` already synchronized, and\n  reports through `Delivery` which surface received what, including the case\n  where nothing did. See the\n  [routing guide](https://github.com/everruns/tuika/blob/v0.11.1/docs/routing.md).\n- **Motion** (`anim`, `components::{Spinner, ProgressBar, Loader}`,\n  `term::progress::TerminalProgress`) animates from a host-supplied frame counter and\n  can drive the terminal's own OSC 9;4 progress indicator. `anim::Timeline` adds\n  a scheduler-free keyframe track (values eased over frame offsets, with\n  looping/ping-pong) sampled purely from that counter.\n- **Pixels** (`framebuffer`) — a mutable RGBA `FrameBuffer` the host draws into\n  (`set`/`blend`/`fill_rect`/`blit`, a per-pixel `shade` shader post-pass, and\n  `Sprite` spritesheet frames). `FrameBufferView` paints it into cells with\n  half-blocks on any terminal, or hand `to_image_data()` to the crisp graphics\n  protocols.\n\n## Crate layout\n\nFour places, so you can guess where something is:\n\n| Path | Holds |\n| --- | --- |\n| `tuika::` | the framework spine — `View`, `view_fn`, `Element`, `ScopedElement`, `RenderCtx`, layout, events, `Theme`, `Surface`, the host boundary |\n| `tuika::components` | every widget: `Flex`, `Boxed`, `Text`, `Scroll`, `Markdown`, `Table`, … |\n| `tuika::term` | everything out-of-band: `clipboard` (OSC 52), `hyperlink` (OSC 8), `progress` (OSC 9;4), `pointer` (OSC 22), `image`, `capabilities`, `palette` (the terminal's own colors) |\n| `tuika::prelude` | the spine and the components in one glob import |\n\nApplication code usually wants the prelude:\n\n```rust\nuse tuika::prelude::*;\n```\n\nEverything else stays behind its module path on purpose — `themes::by_name`,\n`probe::RectProbe`, `width::str_cols`, `term::clipboard::write` — so a short\npath always means \"you will use this constantly\".\n\n## Components\n\nSee the [component gallery](https://github.com/everruns/tuika/blob/v0.11.1/docs/components.md) for an animated demo of each\ncomponent. Linked names below jump straight to their demo.\n\n| Component | Purpose |\n| --- | --- |\n| [`Text`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/text.md#text) / `Paragraph` | Literal styled lines / word-wrapped prose with bare web links |\n| `Wrap` | Word-wraps pre-styled lines, preserving per-span styles |\n| [`Markdown`](https://github.com/everruns/tuika/blob/v0.11.1/docs/markdown.md) (+ `MarkdownState`) | CommonMark → styled lines; `MarkdownState` streams incrementally — see the [markdown guide](https://github.com/everruns/tuika/blob/v0.11.1/docs/markdown.md) |\n| [`CodeBlock`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/markdown-code.md#codeblock) | Themed, framed code block with a pluggable `Highlighter` and optional line-number gutter |\n| [`Html`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/markdown-code.md#html) | HTML fragment → styled lines (companion crate [`tuika-html`](crates/tuika-html/)) |\n| `Diff` | Line diff (LCS), unified or side-by-side, with `+`/`-` gutters and line numbers |\n| `AsciiFont` | Large \"figlet-style\" block-letter banner text |\n| `QrCode` (+ `QrEcc`) | QR code (byte-mode v1–4 encoder) rendered with half-blocks |\n| [`Rule`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/text.md#rule) | Horizontal separator: optional title + fill glyph to width |\n| [`Flex`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/layout.md#flex) | Flexbox container (the composition primitive) |\n| [`Flow`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/layout.md#flow) | Intrinsic-width items wrapped into flex lines |\n| [`Grid`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/layout.md#grid) | Small equal-column, row-major terminal grid |\n| `Responsive` / `Constrained` | Breakpoint selection and min/max measurement |\n| [`Boxed`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/layout.md#boxed) | Border + padding + title, focus-aware |\n| `Scene` / `ScopedScene` / `Dialog` | Owned or frame-borrowed root + anchored overlays |\n| [`ConfirmDialog`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#dialog-presets) / `ChoiceDialog` / `MultiChoiceDialog` / `InputDialog` | Stateful presets for common modal flows |\n| `Spacer` | Flexible filler |\n| [`Scroll`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#scroll--scrollstate) (+ `ScrollState`) | Vertical scroll viewport + scrollbar over lines |\n| [`ItemScroll`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#itemscroll) | The same viewport over laid-out items (panels, tables, nested layouts) |\n| `Viewport` | Two-dimensional clipping/panning over any child view |\n| [`Scrollbar`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/layout.md#scrollbar--virtualwindow) / `VirtualWindow` | Reusable bars and clamped ranges for virtualized collections |\n| `Form` / `FormField` (+ `FormState`) | Responsive labeled controls and validation |\n| `DrawView` / `CanvasView` | Closure-based custom cell drawing |\n| [`SelectList`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#selectlist--selectstate) (+ `SelectState`) | Selectable list, including host-windowed collections |\n| [`TreeList`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#treelist--treestate) (+ `TreeState`) | Stable-id expandable tree over host-provided rows |\n| [`SelectionScreen`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/layout.md#selectionscreen) | Responsive full-screen action/agent/permission pickers |\n| [`KeyedTable`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#keyedtable--keyedselectstate) (+ keyed single/multi-selection) | Borrowed, virtualized slice or projected rows whose selection follows stable application keys |\n| [`CompletionPalette`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#completionpalette--completionstate) (+ `CompletionState`, `CompletionItem`) | Filter-ranked command and token completion |\n| `Slider` (+ `SliderState`) | One-row value picker over a numeric range |\n| [`TextInput`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#textinput--textinputstate) (+ `TextInputState`) | Multi-line composer: soft-wrap, placeholder, highlighted ranges, `@`/`/` tokens |\n| [`StatusBar`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/layout.md#statusbar) | One-row left/right status segments |\n| [`Tabs`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/interactive.md#tabs--tabsstate) / `KeyHints` | Host-state tab navigation and command hints |\n| `TabSelect` (+ `TabSelectState`) | Value-selecting segmented control |\n| `Toasts` / `ToastList` | Transient notification stack with frame-driven expiry |\n| `Console` (+ `ConsoleLog`) | Captured stdout/log ring buffer + tailing overlay view |\n| [`Spinner`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/motion.md#spinner) | Frame-cycled activity glyph |\n| [`ProgressBar`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/motion.md#progressbar) | Determinate (sub-cell) / indeterminate bar |\n| [`ActivityList`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/motion.md#activitylist) | Multi-step lifecycle status with optional per-step progress |\n| [`Loader`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/motion.md#loader) | Spinner + message + hint row |\n\n## Example\n\nLayout reads top-down with the [`view!`](#declarative-dsl-view) DSL:\n\n```rust\nuse tuika::prelude::*;\n\nlet theme = Theme::default();\nlet root = view! {\n    col(gap = 1) {\n        fixed(1) { node(Spinner::new(frame)) }\n        fixed(1) { node(ProgressBar::determinate(0.6).percent(true)) }\n        grow(1) { text(\"body\") }\n    }\n};\n\n// In a `terminal.draw(|f| ...)` closure:\npaint(f.buffer_mut(), f.area(), &theme, root.as_ref(), &[]);\n```\n\n## Owned and scoped scenes, dialogs, and forms\n\n`Scene` owns a root `Element` and ordered `SceneOverlay`s. Each layer retains\nits `OverlaySpec`, so it resolves against the current terminal size inside\nrendering; callers do not retain pre-resolved `Rect`s.\n`Dialog` composes `Boxed`, `Flex`, and optional `KeyHints` into a centered modal\nwith size clamps, clear/dim behavior, and an optional focus-owner id:\n\n```rust\nuse tuika::prelude::*;\n\nlet scene = Scene::new(element(base)).dialog(\n    Dialog::new(\"Confirm\", element(Text::raw(\"Delete this item?\")))\n        .min_size(30, 7)\n        .max_size(70, 20)\n        .key_hints([(\"enter\", \"delete\"), (\"esc\", \"cancel\")])\n        .dim_backdrop(true)\n        .focus_owner(\"confirm\"),\n);\nscene.sync_focus(&mut focus);\npaint_scene(buffer, area, &theme, &scene);\n```\n\nFor popovers, menus, and tooltips, wrap the trigger with a\n`probe::RectProbe` and attach the probe to a `SceneOverlay`. The root renders\nfirst, so placement uses the trigger's current rect in the same frame. It can\nalign on any side, keep a gap, flip when the preferred side runs out of room,\nand clamp to the screen margin:\n\n```rust\nuse tuika::overlay::Extent;\nuse tuika::prelude::*;\nuse tuika::probe::RectProbe;\n\nlet trigger = RectProbe::new();\nlet root = element(Flex::column().fixed(\n    1,\n    trigger.wrap(Text::raw(\"Open actions\")),\n));\nlet menu_size = OverlaySpec {\n    width: Extent::Cells(28),\n    height: Extent::Cells(7),\n    ..OverlaySpec::centered(0, 0).margin(1)\n};\nlet scene = Scene::new(root).overlay(\n    SceneOverlay::new(element(Text::raw(\"Run action\")), menu_size).target(\n        &trigger,\n        TargetPlacement::below().align(TargetAlign::Start).gap(1),\n    ),\n);\n```\n\nCustom views import `Rect`, `Color`, `Style`, `Modifier`, `Line`, and `Span` from `tuika::ui` or the prelude — these are tuika's own types, so a view takes no rendering dependency beyond tuika itself.\n\n`Element` is an owned, boxed view. `ScopedElement<'_>` is its frame-borrowed\ncounterpart: `element(view)` chooses the lifetime from `view`, and containers\naccept it at any depth. `ScopedScene` borrows the resulting root for one paint\nwhile continuing to own ordinary `SceneOverlay`s and `Dialog`s:\n\n```rust\nuse tuika::prelude::*;\n\nstruct Dashboard<'a> {\n    messages: &'a [String],\n}\n\nimpl View for Dashboard<'_> {\n    fn measure(&self, available: Size, _ctx: &RenderCtx) -> Size {\n        available\n    }\n\n    fn render(&self, area: Rect, surface: &mut Surface, ctx: &RenderCtx) {\n        for (row, message) in self.messages.iter().take(area.height as usize).enumerate() {\n            surface.set_string(area.x, area.y + row as u16, message, ctx.theme.text_style());\n        }\n    }\n}\n\nlet dashboard = Dashboard { messages: &app.messages };\nlet scene = ScopedScene::new(&dashboard).dialog(\n    Dialog::new(\"Confirm\", element(Text::raw(\"Delete this item?\")))\n        .dim_backdrop(true)\n        .focus_owner(\"confirm\"),\n);\nscene.sync_focus(&mut focus);\npaint(buffer, area, &theme, &scene, &[]);\n```\n\nThe borrow lasts only as long as the scoped scene, matching Tuika's\nframe-by-frame view model. No transcript clone, leaked allocation, custom\nwrapper view, or application compositor is needed. The `view!` macro preserves\nthe same lifetime through nested `Flex` and `Boxed` containers.\n\nMeasurement receives the same `RenderCtx` as rendering. A custom view whose\ngeometry depends on the active theme or stylesheet resolves it there, and every\ncontainer passes that context to the children it measures.\n\nFor a bespoke region, `view_fn` takes those two methods as closures and returns\na normal `View`. The closures can borrow the same application state as the\nsurrounding frame; they are `Fn`, so repeated measurement or rendering observes\nthat state without cloning it or requiring `Rc<RefCell<_>>`:\n\n```rust\nuse tuika::prelude::*;\n\nstruct App {\n    query: String,\n    match_label: String,\n    results: Vec<String>,\n}\n\nlet app = App {\n    query: \"view\".into(),\n    match_label: \"3 matches\".into(),\n    results: vec![\"src/view.rs\".into(), \"src/components/app_shell.rs\".into()],\n};\nlet search_header = view_fn(\n    |available, _ctx| Size::new(available.width, available.height.min(2)),\n    |area, surface, ctx| {\n        let row = area.bottom().saturating_sub(1);\n        surface.set_string(area.x, row, &app.query, ctx.theme.text_style());\n        surface.set_string(\n            area.right().saturating_sub(10),\n            row,\n            &app.match_label,\n            ctx.theme.muted_style(),\n        );\n    },\n);\nlet screen = AppShell::new(view_fn(\n    |available, _ctx| available, // growing body\n    |area, surface, ctx| {\n        for (row, result) in app.results.iter().take(area.height as usize).enumerate() {\n            surface.set_string(area.x, area.y + row as u16, result, ctx.theme.text_style());\n        }\n    },\n))\n.header(search_header);\nlet _ = screen;\n```\n\nIn the AGF search-header port that motivated this adapter, the named wrapper's\n`struct` plus `View` scaffold is 9 nonblank lines around the render logic; the\nequivalent `view_fn` scaffold is 5. The render body is unchanged, and the call\nsite no longer needs a named type.\n\n`Form` lays out arbitrary control `Element`s beside responsive labels, stacking\non narrow terminals. Help and validation rows are built in; `FormState` owns\nonly focus traversal, while values and cursor state stay in existing host-owned\n`TextInputState`, `SelectState`, or application models. Their `handle` methods\nshare `InputOutcome`: ignored events bubble, recognized no-ops are consumed,\nstate changes are distinct from submit/cancel intent, and submitted values are\nread from the state instead of duplicated in the outcome.\n\n## Arbitrary-child viewports and drawing\n\n`Viewport` clips and pans any child view in both axes. The host supplies the\nfull content `Size` and mirrors offsets through the same `ScrollState` used by\nline-oriented `Scroll`. It renders only the visible source window, so a large\nlogical canvas does not allocate a full off-screen buffer.\n\n`DrawView` (also named `CanvasView`) turns a render-only closure receiving `(Rect,\n&mut Surface, &RenderCtx)` into a normal view. The surface is already clipped,\nmaking it suitable for terminal grids, charts, emulators, and incremental\nmigrations. It reports either all available space or a fixed intrinsic size;\nuse `view_fn` when measurement itself is custom. Import `DrawView` explicitly\nfrom `tuika::view`; custom canvases stay outside the application prelude.\n\nRun `cargo run --example primitives` for one composition using `Scene`,\n`Dialog`, `Form`, `Viewport`, and `DrawView`.\n\n### Builder syntax (alternative)\n\n`view!` expands to plain builder calls, so the same tree can be written without\nthe macro:\n\n```rust\nuse tuika::prelude::*;\n\nlet root = Flex::column()\n    .gap(1)\n    .fixed(1, element(Spinner::new(frame)))\n    .fixed(1, element(ProgressBar::determinate(0.6).percent(true)))\n    .grow(1, element(Text::raw(\"body\")));\n```\n\n## Markdown and syntax highlighting\n\n`Markdown` renders CommonMark to styled lines, word-wrapping prose while drawing\ncode and tables verbatim. `MarkdownState` is its streaming form: fed deltas as a\nmessage arrives, it re-parses only the in-flight tail and caches everything\nbefore the last stable block boundary, so long transcripts don't re-tokenize and\nsettled code blocks aren't re-highlighted every frame. Its `links()` metadata\nkeeps OSC 8 targets aligned with the cached lines through streaming and resize.\n\nHighlighting is a boundary, not a dependency: `tuika` owns the *presentation* of code\n(framing, background, language label, wrapping) via `CodeBlock`, and takes token\ncolors from any `Highlighter` you supply — keeping the toolkit free of grammar\ncrates. The companion crate\n[`tuika-codeformatters`](https://crates.io/crates/tuika-codeformatters) ships a\nready-made tree-sitter `Highlighter`.\n\nStructured blocks can replace their source with a different, width-aware\npresentation through `MarkdownBlockRenderer`. The\n[`tuika-mermaid`](crates/tuika-mermaid/) companion uses that boundary with mmdflux:\na `mermaid` fence becomes a Unicode cell diagram inside the surrounding\nMarkdown, with no browser, SVG, or image protocol. Unsupported or invalid input\nfalls back to the ordinary code block.\n\n```rust\nuse tuika::prelude::*;\nuse tuika_mermaid::MermaidRenderer;\n\nlet mermaid = MermaidRenderer::new();\nlet document = Markdown::new(\n    \"```mermaid\\nflowchart LR\\n  Parse --> Layout --> Paint\\n```\",\n)\n.block_renderer(&mermaid);\n# let _ = document;\n```\n\nRun the complete integration demo with\n`cargo run -p tuika-mermaid --example mermaid_markdown`.\n\n<img src=\"https://raw.githubusercontent.com/everruns/tuika/main/crates/tuika-mermaid/examples/mermaid_markdown/mermaid.gif\" width=\"880\" alt=\"Mermaid diagram rendered as Unicode cells inside tuika Markdown\">\n\nImages use the same host-extension pattern: supply an `ImageResolver` and\n`![alt](url)` renders as real pixels (see [Images](#images)).\n\nMarkdown in the wild carries HTML. The presentational inline tags — `<b>`,\n`<em>`, `<code>`, `<kbd>`, `<mark>`, `<a>`, `<br>`, `<sub>`/`<sup>` — render in\ntuika itself, each through the same `StyleSheet` role as the markdown it\nmirrors. Block-level HTML is a boundary, for the same reason highlighting is: an\nHTML parser is a dependency tuika will not carry. Attach a\n`MarkdownBlockRenderer` and `<details>`, `<table>`, and `<div>` lay out too. The\nsame ordered renderer chain handles fenced diagrams and block HTML with one\ncontext, including the active stylesheet.\n[`tuika-html`](crates/tuika-html/) is the ready-made one, and it also supplies\nthe [`Html`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/markdown-code.md#html) component for markup that is not inside\nmarkdown at all. See the markdown guide for\n[inline HTML](https://github.com/everruns/tuika/blob/v0.11.1/docs/markdown.md#inline-html) and\n[block HTML](https://github.com/everruns/tuika/blob/v0.11.1/docs/markdown.md#block-html).\n\n## Theming\n\nEvery component styles itself from a `Theme` passed through the render context —\nno color is hard-coded, so swapping the theme handed to `paint` restyles the\nwhole tree at once. A `Theme` is a plain `Copy` struct of colors, and tuika\nbundles a few standard palettes as full `const Theme` structures in the\n[`themes`](https://docs.rs/tuika/latest/tuika/themes/index.html) module —\nreachable directly, by constructor, or by name:\n\n```rust\nuse tuika::themes;\n\nlet a = themes::GRUVBOX_DARK;                          // the struct\nlet b = tuika::Theme::gruvbox_dark();                  // named constructor\nlet c = tuika::themes::by_name(\"gruvbox-dark\").unwrap(); // config / --theme\n```\n\nSee the [theme gallery](https://github.com/everruns/tuika/blob/v0.11.1/docs/themes.md) for a screenshot of each bundled\npalette, or `themes::PRESETS` to enumerate them for a picker.\n\nAn app can also inherit the palette the user already configured in their\nterminal, rather than bringing its own — either implicitly with `themes::TERMINAL`\n(ANSI slots, no I/O) or by asking the terminal for its actual colors and deriving\na full theme from the reply. It is opt-in: tuika never probes unless a host asks\nit to. The query lives with the other out-of-band escapes, in `term::palette`. See\n[inheriting the terminal's colors](https://github.com/everruns/tuika/blob/v0.11.1/docs/features.md#inheriting-the-terminals-colors).\n\nWhere a `Theme` is the color *tokens*, a `StyleSheet` is the *rules* — a mapping\nfrom a semantic role (heading, link, inline code, a panel's border and fill, …)\nonto the style it draws with. Override a role in one place and every element with\nthat role restyles at once; markdown, toast severities, diff rows, and key hints\nare role-driven too. Companion crates and applications can define namespaced\n`StyleRole`s and install a `StyleResolver` without expanding tuika's closed data\nmodel. See the [styling guide](https://github.com/everruns/tuika/blob/v0.11.1/docs/styling.md). `StyleBundle::padding` is layout, not a\npaint-only hint: `Boxed` resolves panel padding during both measurement and\nrendering; an explicit `Boxed::padding` remains the per-instance override.\n\n## Runnable examples\n\nEach takes over the terminal — the alternate screen, or a pinned footer for\n[`split_footer`](examples/split_footer.rs); press `q` (or `esc`) to quit.\nPass `--theme <name>` after Cargo's `--` to run any example with a bundled\npalette, for example `cargo run --example gallery -- --theme gruvbox-dark`.\n\n| Example    | Command                                   | Shows                                              |\n| ---------- | ----------------------------------------- | -------------------------------------------------- |\n| [`gallery`](examples/gallery.rs)  | `cargo run --example gallery`    | motion components + native OSC 9;4 progress and OSC 8 links |\n| [`markdown`](examples/markdown.rs) | `cargo run --example markdown`   | streaming `MarkdownState` + highlighted `CodeBlock`, native OSC 8 links, following the stream until you scroll back |\n| [`select`](examples/select.rs)   | `cargo run --example select`     | interactive multi-select with aliases, numbers, and mouse hit-testing |\n| [`keyed_table`](examples/keyed_table.rs) | `cargo run --example keyed_table` | dynamic borrowed rows, stable keyed selection, filtering, and reordering |\n| [`tree_list`](examples/tree_list.rs) | `cargo run --example tree_list` | expandable stable-id tree, refresh, mouse selection, and persistent scrolling |\n| [`overlay`](examples/overlay.rs)  | `cargo run --example overlay`    | Target-following popover + input routing           |\n| [`primitives`](examples/primitives.rs) | `cargo run --example primitives` | owned dialog scene + form + arbitrary-child viewport |\n| [`app_shell`](examples/app_shell.rs) | `cargo run --example app_shell` | responsive header/body/status/footer shell with host-owned selection |\n| [`ratatui_dashboard`](examples/ratatui_dashboard.rs) | `cargo run --example ratatui_dashboard` | mixed Ratatui widgets + responsive live data |\n| [`workbench_demo`](examples/workbench_demo) | `cargo run --example workbench_demo` | native tuika editor/dashboard shell with keyboard and mouse navigation |\n| [`async_dashboard`](examples/async_dashboard.rs) | `cargo run --example async_dashboard --features async` | typed background messages waking `AsyncRunner`, no shared mutable state |\n| [`mouse`](examples/mouse.rs)     | `cargo run --example mouse`      | drag-to-select + highlight + OSC 52 copy, clickable buttons |\n| [`image`](examples/image.rs)     | `cargo run --example image`      | `Image` over reserved cells (Kitty/iTerm2/Sixel), alt-text fallback |\n| [`inherit`](examples/inherit.rs) | `cargo run --example inherit`    | adopting the terminal's own palette — probe, derive, and the no-I/O fallback |\n| [`split_footer`](examples/split_footer.rs) | `cargo run --example split_footer` | a pinned footer over live terminal scrollback, published through `Scrollback` |\n| [`codex`](examples/codex)        | `cargo run --example codex`      | a scripted Codex CLI interface replica: streaming transcript, composer, `@`/`/` pickers, approval prompt |\n| [`codex --split-footer`](examples/codex) | `cargo run --example codex -- --split-footer` | the same agent UI with its transcript published into the terminal's own scrollback |\n\nEvery example above except [`codex`](examples/codex) quits on `q`/`esc`. Those\nkeys are composer text in the Codex replica, so it quits with `⌃C`.\n\n## Declarative DSL (`view!`)\n\n`view!` is optional sugar over the builders — it expands to the exact same\n`Flex`/`Boxed`/`element(...)` calls, so there is no runtime cost and nothing new\nin the model. It just makes nested layout read top-down:\n\n```rust\nlet root = crate::view! {\n    col(gap = 1, padding = tuika::Padding::all(1)) {\n        boxed(title = \" body \") { text(\"hello\") }\n        grow(1) { spacer() }\n        node(status_bar)          // any expression that is `impl View`\n    }\n};\n```\n\nGrammar (each keyword consumes exactly one node):\n\n- `col(attrs) { … }` / `row(attrs) { … }` — flex containers. Attrs (all\n  optional): `gap`, `row_gap`, `column_gap`, `padding`, `align`, `justify`,\n  `wrap`, `align_content`, `background`.\n- `boxed(attrs) { child }` — bordered container. Attrs: `title`, `border`,\n  `padding`, `background`.\n- `text(expr)`, `spacer()` — leaves.\n- `grow(n) { node }` / `fixed(n) { node }` — set a child's main-axis size\n  (default auto).\n- `when(condition) { node }` / `for(pattern in iterable) { node }` — conditional\n  and repeated children, still expanding to ordinary builder calls.\n- **`node(expr)`** — splice any `impl View`. This is the escape hatch, and how\n  a component **from another crate** participates in the DSL:\n\n  ```rust\n  use other_crate::CustomView;\n  crate::view! { col { node(CustomView::new(&data)) } };\n  ```\n\n`node(...)` accepts any type that already implements Tuika's `View`; it does\nnot make a Ratatui `Widget` implement `View`. A node may borrow frame data; the\nmacro returns `ScopedElement<'_>` in that case and naturally coerces an\nall-owned tree to `Element`. Use `RatatuiView` for Ratatui widgets. The\n`tuika-gallery` demo is built entirely with `view!`.\n\n## Ratatui interoperability\n\nTuika deliberately does not duplicate Ratatui's widget catalog. Enable the\n`ratatui` feature and wrap existing widgets in `RatatuiView`; they render into an\nisolated buffer and only the assigned clip is composited into the frame:\n\n```toml\ntuika = { version = \"0.12\", features = [\"ratatui\"] }\nratatui = \"0.30\"\n```\n\n```rust\nuse ratatui::widgets::{Sparkline, Widget};\nuse tuika::prelude::*;\n\nlet values = vec![1, 4, 2, 8];\nlet chart = RatatuiView::sized(Size::new(20, 4), move |area, buffer| {\n    Sparkline::default().data(&values).render(area, buffer);\n});\n```\n\nThe closure form supports widgets that borrow captured data. Stateful widgets\ncan capture host-owned synchronized state and call `StatefulWidget::render`\ninside the same closure. `Surface::render_ratatui` is the lower-level escape\nhatch for custom views that need several widgets. Neither API exposes the\nframe's mutable buffer.\n\nBecause Tuika owns its own cell type, the boundary is a conversion over the\nrendered area rather than a shared buffer — so Tuika and Ratatui version\nindependently, and a widget costs one cell copy in and out of the area it\nactually draws. A view that only needs a private scratch buffer, with no Ratatui\ninvolved, should use `Surface::render_scratch`, which needs no feature.\n\n## Responsive and live views\n\n`Responsive` chooses complete compact/wide view trees from the current width;\nthis supports row-to-column reflow and intentionally omitted secondary\ncontent. `Constrained` supplies min/max intrinsic measurements to flex layout.\n\n`DockState` is the small host-owned lifecycle for an auxiliary panel. A visible\npanel docks beside the main view on wide frames; below its breakpoint it stays\npassive and hidden until focused, then resolves as an overlay drawer. It returns\nrectangles only—the host keeps the panel view, focus id, keymap, and state.\n\n```rust\nuse tuika::prelude::*;\n\nlet mut activity = DockState::new();\nactivity.show_passive();\nlet layout = activity.resolve(Rect::new(0, 0, 120, 30), DockSpec::right(90, 40));\n// Paint the main view into `layout.main` and, when present, the panel into\n// `layout.panel`.\n```\n\n`Live<T>` is shared application data with a narrow read/update API. `LiveView`\nderives a fresh view from its current value each frame. Connect it to\n`Runner::redraw_handle()` — or `AsyncRunner::redraw_handle()`, which wakes a\nparked `select!` rather than waiting for its next tick — when background\nproducers should invalidate the screen. Producers retain ownership of their\nthreads, tasks, retries, and lifecycle.\n\n## Screen modes, lifecycle, and runner\n\n`ScreenMode` picks which part of the terminal a frame owns:\n\n- `ScreenMode::Alternate` (the default) takes the whole window on the alternate\n  buffer and restores the user's screen and scrollback on exit. It leaves mouse\n  handling to the terminal, so native OSC 8 links, selection, and scrolling work.\n- `ScreenMode::split_footer(rows)` reserves those rows at the bottom of the\n  *main* screen. Everything above stays the terminal's own scrollback: the shell\n  prompt that launched the app, the wheel, and mouse selection all keep working,\n  and the output the app publishes is still there after it exits. This is the\n  shape for a long-running tool with a live composer, status line, or progress\n  panel over output the user wants to keep.\n\nThe [screen-modes guide](https://github.com/everruns/tuika/blob/v0.11.1/docs/features.md#screen-modes-alternate-screen--split-footer)\nshows the mode in motion and covers its terminal contract in detail.\n\nIn split-footer mode a host must not `println!` — the footer owns the cursor.\n`Runner::scrollback()` (and `AsyncRunner::scrollback()`) returns a `Scrollback`\nhandle instead: a cheap, cloneable, `Send + Sync` queue of *views*, which the\nrunner renders and commits above the footer, one whole block at a time. A host\ndriving its own loop can skip the queue with `screen::publish_block`, which\ncommits one view immediately and takes no `Send` bound — so a block may own\nframe state that could never cross a thread. Blocks are painted without a\nbackground fill, so they blend into the surrounding shell session rather than\nlooking like a pasted panel.\n\n```rust,ignore\nuse tuika::prelude::*;\n\nlet runner = Runner::new(RunnerConfig {\n    tick_rate: Duration::from_millis(80),\n    screen_mode: ScreenMode::split_footer(5),\n});\nlet scrollback = runner.scrollback();\n\n// From any thread; committed above the footer on the next loop iteration.\nscrollback.write(|_width| element(Text::raw(\"build finished in 12 ms\")));\n```\n\nThe footer's height is fixed for the life of the terminal, so a host whose\nfooter grows (a composer, a completion popup) reserves the tallest state it\nneeds. There is a `scrolling-regions` feature, but it is a compatibility mirror\nof ratatui's, not an optimization to reach for: rows scrolled out of a DECSTBM\nregion are discarded by the terminal instead of entering its scrollback, which\nis the one thing this mode exists to provide.\n\n[`split_footer`](examples/split_footer.rs) is the runnable version of all of\nthis, and [`codex`](examples/codex) runs its whole coding-agent UI this way with\n`--split-footer`: each finished transcript entry is handed to the terminal, and\nthe composer keeps the bottom rows. Hosts driving their own loop reserve and\nrelease the footer's rows with `screen::pin_footer` and `screen::close_footer`.\n\n`TerminalSession` is the complete RAII guard for either mode: it owns raw mode,\nenhanced keyboard reporting, the alternate screen, optional mouse capture, and cursor visibility,\nincluding rollback after partial initialization, and restores exactly what it\ntook. Enhanced reporting preserves\nnon-character modifiers, so `Shift+Enter` reaches `TextInputState` as a\ndifferent chord from `Enter`; iTerm2 and tmux get their required protocol\nvariants, while Windows uses the modifier state already carried by its native\nconsole events. Character codes carry the logical text produced by the active\nkeyboard layout, while modifiers remain separate for non-character chords. It\npreserves raw mode and any keyboard-reporting stack entries the caller had\nalready enabled. `AltScreen` remains available for hosts that intentionally own\nraw mode, keyboard modes, and cursor visibility themselves.\n`TerminalSession::enter_config(TerminalSessionConfig)` keeps the same rollback\nguarantees while independently configuring raw mode, enhanced keyboard\nreporting, mouse capture, and cursor visibility. `Runner::with_session_config`\nuses that policy without replacing the runner loop.\n\n`Runner` is an optional synchronous event loop for dashboards and small tools.\nIt owns `TerminalSession`, frame scheduling, Crossterm event translation, and\nstate-driven redraws. An `Application` keeps state and update policy together;\nits pure `view(&self)` may return a `ScopedElement<'_>` that borrows that state\nfor exactly one frame. The initial frame is painted once; a tick or input only\nrepaints when update returns `UpdateResult::Dirty`, while resize always repaints\nand a `RedrawHandle` can wake the loop from another thread:\n\n```rust,ignore\nuse std::time::Duration;\nuse tuika::prelude::*;\n\nlet runner = Runner::new(RunnerConfig {\n    tick_rate: Duration::from_secs(2),\n    ..RunnerConfig::default()\n});\nimpl Application for Stats {\n    fn update(&mut self, signal: Signal) -> UpdateResult {\n        match signal {\n            Signal::Tick if self.refresh() => UpdateResult::Dirty,\n            Signal::Event(Event::Key(k))\n                if k.plain() && k.code == KeyCode::Char('q') => UpdateResult::Exit,\n            _ => UpdateResult::Clean,\n        }\n    }\n\n    fn view(&self, _frame: u64) -> ScopedElement<'_> {\n        element(Text::raw(self.summary()))\n    }\n}\n\nlet mut app = Stats::default();\nrunner.run(&Theme::default(), &mut app)?;\n```\n\n`UpdateResult::Clean` leaves an input available to runner defaults such as text\nselection. Return `Consumed` when the application handled it without changing\nthe frame, `Dirty` when handling changed the frame, or `Exit` to stop.\n\nEvery run method takes a `FrameSource`, and there are exactly two: `&mut app`\nfor an `Application`, as above, or `from_fn(&mut state, view, update)` for the\nclosure form over an owned `Element` tree. The\n[`borrowed_app`](examples/borrowed_app.rs) example implements a custom `View`\nthat directly borrows a `String` from its application.\n\n`Runner::with_clock` replaces the default `SystemClock` when a replayable host\nor deterministic test owns monotonic time. The same `Clock` boundary drives\n`SelectionState::handle_with_clock`, so double-click timing never has to depend\non wall-clock sleeps. Frame animation, keymap timeouts, and toast expiry remain\nexplicitly host-driven and therefore need no internal clock.\n\n`RunnerCore` is the runtime-neutral state machine underneath both runners. It\nturns dirty/clean/exit results and external invalidations into\n`RunnerAction::{Wait, Render(frame), Exit}` without knowing about Crossterm,\nTokio, clocks, or terminal backends.\n\n`AsyncRunner` (behind `features = [\"async\"]`) uses the same state/view/signal\nmodel for applications that already have a Tokio runtime — anything doing\nnetwork or disk I/O — and lets its update closure `.await`. It ties\n`TerminalSession`, `paint`, and `translate_event` to crossterm's async\n`EventStream` and a tick timer in one `tokio::select!`, so the host keeps a\nsingle event loop. Its update closure returns the same\n`UpdateResult::{Clean, Consumed, Dirty, Exit}` as `Runner`, so awaited work only\nrebuilds and repaints when it actually changes visible state. `AsyncApplication`\nis its `Application` twin — the same borrowed-view boundary, with an awaiting\n`update` — and `&mut app` is a `FrameSource` for it just as it is for `Runner`.\n\n`run_with_messages` adds a typed host stream beside terminal events and ticks.\nMessages arrive as `Signal::Message`, so a background producer can wake and\nupdate the UI immediately without `Arc<Mutex<_>>`, fabricated keys, or a short\npolling interval — and the frame source is the same one `run` takes, at\n`Signal<M>` instead of the default. The lower-level `run_driven_by` accepts a\ncaller-owned terminal and both streams, which also makes completion and error\nbehavior deterministic under `TestBackend`.\n\n`Signal<M>` is that one signal type: `M` defaults to the uninhabited\n`Infallible`, so a loop with no message stream has no `Message` variant to\nhandle and a two-arm `match` stays exhaustive. What is left in the method names\nis only where the terminal and the input come from — `run`, `run_with_backend`,\n`run_with_messages`, `run_driven_by`. The runtime is the runner type; everything\nelse is `RunnerConfig` or a builder method.\n\n`run_driven_by` is the loop with nothing around it — no session, no terminal\nconstruction, no stdout-facing work — and both runners build their other entry\npoints on it. It is also how you test an application end to end: give it a\n`TestBackend` and, on the synchronous side, `scripted_events([...])`, and the\nwhole loop runs with no tty, no raw mode, and no real clock. A host that already\nowns its terminal and input can use it the same way; implement `EventSource` for\na custom input.\n\nEnabling `async` adds Tokio (timer + `select!`) and crossterm's `event-stream`\nfeature; it stays off by default so sync-only hosts pull in no runtime. The\n[`async_dashboard`](examples/async_dashboard.rs) example demonstrates that\nvariant with no shared state at all.\n\n## Native terminal progress\n\n`term::progress::TerminalProgress` emits the OSC 9;4 sequence, which drives the\nterminal's own progress indicator — a bar across the top of the window in\nGhostty, the taskbar in Windows Terminal / ConEmu, and similar in\nWezTerm / Konsole / mintty. It is out-of-band (no cursor movement, no cells),\nso it works in both the inline and full-screen renderers; terminals that don't\nunderstand it ignore the sequence. A host typically shows it (indeterminate)\nwhile long work runs and clears it when idle.\n\n## Images\n\n`Image` paints real pixels — an avatar, a chart, a rendered diagram — over the\ncells it reserves, using whichever terminal graphics protocol\n`ImageSupport::detect()` finds: **Kitty** (Kitty, Ghostty, WezTerm, Konsole),\n**iTerm2**, or **Sixel** (foot, xterm +sixel, mlterm, contour). Terminals with\nnone show the alt text, so the same view tree renders everywhere.\n\n<img src=\"https://raw.githubusercontent.com/everruns/tuika/main/docs/demos/image.svg\" width=\"880\" alt=\"Two terminal windows side by side: on a Kitty/Ghostty/WezTerm/Konsole terminal an Image view renders a red/green gradient in place; on every other terminal the same view shows a dimmed italic '[image: a red/green gradient]' placeholder.\">\n\nDecoding stays in the host — a heavy dependency, kept out like the highlighter\nboundary — so you hand in raw RGBA via `ImageData::from_rgba` and `tuika` owns the\nprotocol encoding (base64, PNG, and Sixel encoders are inline, so no image-codec\ndependency). `Runner` detects the terminal protocol, collects placements, and\nemits pixels after each cell frame.\n\n```rust\nuse tuika::prelude::*;\nuse tuika::term::image::ImageData;\n\nlet data = ImageData::from_rgba(2, 2, vec![0u8; 2 * 2 * 4]).unwrap();\nlet _image = Image::new(data, 20, 10)      // 20×10 cells on screen\n    .alt(\"a 2×2 swatch\");                  // shown where graphics aren't supported\n```\n\nCustom hosts that call `paint_with_context` directly can install\n`ImageSupport` and an `ImageLayer` with `RenderCtx::with_image_graphics`, then\nemit and clear the layer after flushing the cell frame.\n\nMarkdown `![alt](url)` renders too, in both the one-shot `Markdown` view and the\nstreaming `MarkdownState`: attach a host `ImageResolver` (URL → `ImageData`, the\nsame boundary as the highlighter) and resolved images become real pixels — a\nlink-styled placeholder for the rest, never a dropped URL.\n\nTo check support across every terminal feature in one place — `graphics`,\n`hyperlinks`, `clipboard`, `progress`, `truecolor` — use `Capabilities`:\n`Capabilities::from_env()` is an instant advisory guess, and\n`Capabilities::query(timeout)` adds a Device Attributes probe that confirms Sixel\n(the one protocol the environment can't reliably reveal).\n\n## Mouse, selection, and clipboard\n\nMouse handling stays with the terminal by default, including in alternate-screen\nmode. That preserves native OSC 8 link activation, click-drag selection, and\nterminal scrolling. An app that needs pointer or wheel events opts into capture\nwith `ScreenMode::Alternate.with_mouse_capture()`,\n`ScreenMode::split_footer(rows).with_mouse_capture()`, an enabled\n`TerminalSessionConfig::mouse_capture`, or\n`AltScreen::enter_with_mouse_capture()`.\n\nCapture is a deliberate trade: the terminal stops activating OSC 8 links and\nperforming its own selection/scrolling because it hands those mouse events to\nthe app instead. `Runner` and `AsyncRunner` then restore selection over the\nfinal rendered grid: a plain left drag highlights text, a same-cell double\nclick selects a word, and releasing copies through OSC 52. Wheel events reach\napplication scrolling. An\napplication claims a mouse gesture by returning `UpdateResult::Consumed` (no\nrepaint) or `UpdateResult::Dirty` (repaint), and can disable the default\nentirely with `with_text_selection(false)`.\n\nHosts with their own loop use the `mouse` module to build the same affordances:\n\n- **Text selection.** `SelectionState` turns a left-button `Down → Drag → Up`\n  gesture into a `SelectionRange` (a plain click selects nothing; a new press\n  clears the old selection). `selected_text(buffer, area, range)` reads the text\n  back out of the rendered `Buffer` — linear/stream selection like a\n  terminal's own, wide glyphs intact — and `mouse::paint_selection(buffer, area, range,\n  style)` paints it in. A same-cell double click selects a word;\n  `handle_with_clock` accepts a virtual monotonic `Clock`, while `handle` uses\n  `SystemClock`.\n- **Application link fallback.** `ctrl_click_url` resolves an OSC 8 target or\n  bare URL under a captured Ctrl-click. The host must open it itself. This is\n  opt-in fallback behavior for an app that chose capture, not a replacement for\n  native terminal activation.\n- **Clicks and regions.** `HitMap<T>` maps screen rects to values (a button, a\n  link, a row); the last-pushed match wins, so children/overlays registered\n  after their parents take precedence. `ClickTracker` turns a same-cell\n  `Down`/`Up` into a `Click` and lets an intervening drag cancel it.\n- **Clipboard.** `clipboard::write(out, text)` copies via **OSC 52**\n  (`clipboard::osc52` is the pure encoder) — no platform clipboard library,\n  works over SSH. Same tmux caveat as OSC 8: needs `allow-passthrough on`.\n\nThe enriched event model carries what selection and clicks need: `MouseKind` is\n`Down/Up/Drag(MouseButton)`, `Moved`, and `ScrollUp/Down/Left/Right`, and every\n`Mouse` reports `shift/ctrl/alt`. **Shift-drag** is deliberately left to the\nterminal — most emulators use it to bypass app mouse capture for a native\nselection — so a host should act on `plain()` left-drags.\n\n**Touch** arrives as mouse events: terminal emulators translate a tap to a\n`Down`+`Up` and a swipe to scroll or a drag, so touch flows through this same\npath — there is no separate touch event to handle.\n\n> See the [terminal features guide](https://github.com/everruns/tuika/blob/v0.11.1/docs/features.md) for these\n> terminal-integration capabilities — OSC 8 hyperlinks, mouse selection and\n> clicks, OSC 52 clipboard, OSC 9;4 progress, and Kitty/iTerm2/Sixel images —\n> plus `Capabilities` detection, with demos and runnable examples.\n\n## Testing your UI\n\nRendering is deterministic, so UI built on tuika can be tested without a real\nterminal or `TestBackend` setup. The [`testing`](https://docs.rs/tuika/latest/tuika/testing/index.html)\nmodule draws a `View` into an in-memory `Buffer` and reads it back:\n\n- `render(view, width, height, &theme) -> Buffer` — draw once at a fixed size.\n- `render_with_sheet(view, width, height, &theme, sheet) -> Buffer` — the same\n  harness with an explicit stylesheet.\n- `grid(&buffer) -> String` — the buffer as a plain glyph grid, ready for a\n  snapshot assertion.\n- `render_sizes(view, sizes, &theme) -> Vec<Buffer>` — the same view across a set\n  of sizes, for resize and degenerate-size sweeps.\n- `TestHarness<State>` — drive `Signal`s through state/update/view functions,\n  resize deterministically, and receive a buffer only for dirty updates.\n  `render_app` / `step_app` do the same for an `Application`, including scoped\n  views and mandatory resize redraws.\n\n```rust\nuse tuika::testing::{grid, render};\nuse tuika::Theme;\n\nlet buffer = render(my_view.as_ref(), 20, 3, &Theme::default());\nassert!(grid(&buffer).contains(\"expected text\"));\n```\n\nFor static command output that should remain in scrollback, `render_once` and\n`write_once` measure and render a view as ordinary ANSI-styled UTF-8. They do\nnot enter raw mode, capture input, hide the cursor, or own a screen.\n\n## Used in\n\n- [**yolop**](https://github.com/everruns/yolop) — a terminal coding agent whose\n  experimental full-screen renderer is built on tuika.\n- [**LLMSim**](https://github.com/chaliy/llmsim) — an LLM traffic simulator whose\n  live stats dashboard is a tuika screen.\n\nSee the [showcases](https://github.com/everruns/tuika/blob/v0.11.1/docs/showcases.md) for a recording of each. Building\nsomething on tuika? Open a PR adding it here.\n\n## Compatibility\n\n- Minimum supported Rust version: **1.88**, declared as `rust-version` and\n  checked in CI.\n- Tuika 0.x follows Cargo semver: minor releases may make deliberate breaking\n  API changes; patch releases do not.\n- Crossterm is part of Tuika's public surface, for terminal events.\n- Ratatui is **not** a dependency of Tuika. Its widgets remain usable through the\n  optional `ratatui` feature: enable it, add `ratatui` to your own crate, and\n  wrap widgets in\n  [`RatatuiView`](https://docs.rs/tuika/latest/tuika/interop/struct.RatatuiView.html)\n  or\n  [`Surface::render_ratatui`](https://docs.rs/tuika/latest/tuika/surface/struct.Surface.html#method.render_ratatui).\n  The boundary is a cell-by-cell conversion over the rendered area rather than a\n  shared buffer, so the two crates' versions are independent — a `ratatui` major\n  bump is no longer a Tuika breaking change.\n\n## Extending\n\ntuika is extended from your own crate — no fork, no registration step, no trait\nthe built-ins get that yours don't:\n\n- **Custom components.** Implement [`View`](https://docs.rs/tuika/latest/tuika/view/trait.View.html)\n  on your own type and splice it anywhere with `node(your_view)`, or hand it to\n  any container — they accept any `impl View`. The built-in components are on\n  equal footing with yours; nothing special-cases them.\n- **Existing Ratatui widgets.** Enable the `ratatui` feature and wrap one in\n  `RatatuiView` rather than reimplementing it — see\n  [Ratatui interoperability](#ratatui-interoperability).\n\nThe [`view!`](#declarative-dsl-view) DSL reaches your components through the same\n`node(...)` escape hatch, so they compose exactly like the built-ins.\n\n## Contributing\n\nIssues and pull requests are welcome at\n[everruns/tuika](https://github.com/everruns/tuika). See\n[CONTRIBUTING.md](CONTRIBUTING.md) for the local checks (`cargo fmt --check`,\n`cargo clippy --all-targets --all-features -- -D warnings`,\n`cargo test --all-features`) and the commit and review conventions.\n\nThe separately published companion crates live in this repository:\n\n- [`tuika-charts`](crates/tuika-charts/) renders one line/bar/area/scatter/step grammar as\n  smooth terminal graphics or a portable Unicode cell plot.\n- [`tuika-codeformatters`](crates/tuika-codeformatters/) supplies the\n  tree-sitter `Highlighter`.\n- [`tuika-mermaid`](crates/tuika-mermaid/) renders Mermaid fences as Unicode\n  terminal diagrams through mmdflux.\n- [`tuika-html`](crates/tuika-html/) lays out block-level HTML with html5ever —\n  inside Markdown through the `MarkdownBlockRenderer` boundary, or standalone\n  through its own [`Html`](https://github.com/everruns/tuika/blob/v0.11.1/docs/components/markdown-code.md#html) component.\n\nAll four keep specialized rendering and heavier parsers or grammars out of\ntuika core.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n",
  "bytes": 56453,
  "sha": "755d931b57549a80183c71d733e3e0f7ed9928d62285ef897d498537a97d661c",
  "repo_slug": "everruns/tuika",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_everruns_tuika_knowledge_index_md_1fe9be30/readme"
}