Skip to content
Updated View as Markdown

tuika 0.7.0

Released 2026-07-31. crates.io · docs.rs · GitHub release

Released alongside tuika-html 0.1.0, the new companion crate behind the block-HTML boundary, plus tuika-codeformatters 0.4.0 and tuika-mermaid 0.2.0. The existing companions adopt tuika 0.7’s breaking view-measurement API and update their tuika dependency requirement in the same release.

Highlights

Responsive application primitives. Docked panels, target-relative overlays, virtualized collections, semantic styles, and single-line input give hosts a coherent framework for complex terminal applications without retained UI state.

primitives demo

Rich HTML in Markdown. Presentational inline HTML now shares Markdown’s styles, while the new parser-free HtmlBlockRenderer boundary lets tuika-html render styled block HTML without adding a parser to tuika core.

markdown HTML demo

Added

  • Wrapped flex lines with independent row/column gaps, grow and shrink weights, min/max constraints, align_self, cross-line AlignContent, and exact boundary-based cell rounding. FlexItemStyle separates child properties from LayoutStyle; solve_layout also reports resolved line geometry.

  • Flow for intrinsic-width wrapping and a deliberately small equal-column row-major Grid component.

  • Extensible MeasureRequest / AvailableSpace measurement, with known axes and definite/min-content/max-content modes. The default adapter preserves existing View::measure implementations.

  • Runtime-neutral RunnerCore, configurable TerminalSessionConfig, and testing::TestHarness for state/signal/view application tests without a terminal or async runtime.

  • render_once / write_once for ANSI-styled ordinary output, and view! when(...) / for(... in ...) composition.

  • Clock and SystemClock provide one monotonic time boundary. SelectionState::handle_with_clock makes double-click gestures deterministic, and Runner::with_clock lets replayable synchronous hosts own tick time; existing handle and Runner::new behavior remains system-clock backed.

  • VirtualWindow provides overflow-safe clamped and selection-centered ranges; Scrollbar renders the same window vertically or horizontally with semantic styling and local glyph/style overrides. Scroll, item scroll, viewport, select, and table now share those primitives. SelectList::windowed and Table::windowed accept only the visible records while preserving absolute selection and full-collection scrollbar geometry.

  • DockState, DockSpec, and DockLayout provide a host-owned responsive lifecycle for one auxiliary panel: wide panels dock, narrow passive panels hide, and focused narrow panels resolve as overlay drawers without introducing a retained panel manager.

  • Target-relative overlays: TargetPlacement selects above/below/left/right, cross-axis alignment, gap, and optional edge-aware flipping; OverlaySpec::resolve_target resolves it directly and SceneOverlay::target follows a RectProbe from the scene root in the same frame. Screen-anchored placement remains unchanged.

  • StyleRole and StyleResolver form an open semantic styling boundary for hosts and companion crates. RenderCtx::style resolves built-in or namespaced application roles, resolver bundles partially overlay stylesheet defaults, and resolver revisions invalidate measurement caches. paint_with_context and testing::render_with_context install and test the complete policy.

  • ScopedElement<'view>, the boxed frame-borrowed counterpart to owned Element, for heterogeneous component subtrees that read host state without cloning it.

  • Inline HTML in markdown. The presentational inline tags render instead of being dropped: <b>/<strong>, <i>/<em>/<var>/<cite>/<dfn>, <code>/<kbd>/<samp>/<tt>, <s>/<del>/<strike>, <u>/<ins>, <mark>, <a href>, <img src alt>, <br>, and <sub>/<sup>. Each resolves the same StyleSheet role as the markdown construct it mirrors, so a host that restyles strong restyles <b> with it; <a> and <img> take the existing hyperlink and ImageResolver paths. No new dependency and no HTML parser: this is a fixed tag whitelist, so anything outside it — block-level HTML, <script>, unlisted attributes — is dropped as before, and never echoed as literal markup.

  • One structured markdown block boundary. MarkdownBlockRenderer receives a non-exhaustive MarkdownBlock descriptor (Fenced or Html) and a shared MarkdownBlockContext containing width, theme, and the active stylesheet. Markdown::block_renderer and MarkdownState::with_block_renderer append to an ordered renderer chain, so Mermaid, HTML, and host-defined block parsers compose without adding another trait or field for every syntax.

  • tuika-html bounds nesting on the source before parsing, so a fragment deep enough to overflow html5ever’s recursive tree building is refused rather than crashing the host.

  • markdown::Renderers builds the same ordered block-renderer chain for markdown::to_lines_with / to_linked_lines_with.

  • ProgressBar::label draws a centered, clipped caption over determinate and indeterminate bars.

  • Scroll::wrap(true) reflows owned styled lines at render width before applying the scroll window.

  • Table::selection_style and SelectList::selection_style allow per-instance selection foreground, background, and modifiers.

  • SelectNavigation policies for optional j/k, Ctrl+N/P, Tab/Shift+Tab, and numeric selection aliases; explicit mouse hit-testing on SelectState; and MultiSelectState for toggleable multiple-selection workflows.

  • KeyHints::from_keymap, priority-aware whole-hint fitting, and KeymapHelp so one labeled keymap declaration drives dispatch, responsive footer hints, and a complete help view.

  • SingleLineInputState for search/command fields, with newline normalization, Enter/Ctrl+J submission, and allocation-free borrowed text access.

  • tuika::ui re-exports Rect, Color, Style, Modifier, Line, and Span for custom views without a direct ratatui-core dependency.

  • testing::render_with_sheet renders consumer views under an explicit stylesheet in the same hermetic buffer harness as testing::render.

Changed

  • Breaking: FencedBlockRenderer and HtmlBlockRenderer are replaced by MarkdownBlockRenderer. Match on MarkdownBlock, read width/theme/sheet from MarkdownBlockContext, register every implementation through block_renderer, and build free-function chains with Renderers::new().renderer(&first).renderer(&second). HTML fences now receive the host’s active stylesheet instead of synthesizing theme defaults.

  • Breaking: LayoutStyle::gap is split into row_gap and column_gap (the .gap(...) builder still sets both), and Item::dimension is replaced by Item::style. Use Item::new for the compatible compact path or Item::styled(FlexItemStyle, ...) for independent flex properties.

  • Breaking: keymap character specs are now explicitly logical text. Write the character produced by the active layout (A, ?, ctrl+R) rather than shift+a or shift+/; Shift remains valid for non-character chords such as shift+enter. Ambiguous shift+character specs now return KeyParseError (or panic through static Layer::bind) instead of silently discarding Shift.

  • Breaking: AsyncRunner update closures now return UpdateResult instead of ControlFlow<()>, matching synchronous Runner; clean ticks and events no longer rebuild or repaint the view.

  • Breaking: interactive component handlers now return one root/prelude InputOutcome (Ignored, Consumed, Changed, Submitted, or Cancelled). Read the submitted value back from its host-owned state. Replace SelectOutcome, MultiSelectOutcome, FormOutcome, TabSelectOutcome, TextInputEvent, and direct EventFlow matches with InputOutcome; call .flow() when only propagation matters. SelectState, MultiSelectState, and ScrollState now make Default identical to new(); use SelectState::unselected() for an initially cursorless list.

  • Breaking: StyleSheet adds typed toast, diff, and key-hint fields. Toasts, diffs, KeyHints, and KeymapHelp now derive their defaults from those roles instead of renderer literals; explicit Diff::style colors still win for one instance. Exhaustive StyleSheet literals must add the new fields or use ..StyleSheet::from_theme(&theme). Construct RenderCtx through RenderCtx::new rather than a struct literal now that it carries the optional resolver.

  • Breaking: View::measure now takes &RenderCtx, and every composition container forwards the frame’s active theme, stylesheet, and focus state. Migrate fn measure(&self, available: Size) implementations to fn measure(&self, available: Size, ctx: &RenderCtx); callers likewise pass the context used for rendering. Flex::solve now takes that context, and ItemScroll::{measure_height, measure_views} take it as their final argument.

  • Markdown and the companion tuika-html::Html view now measure with the same theme and stylesheet they render with. Boxed implements stylesheet panel padding as real layout; an explicit .padding(...) wins over the stylesheet.

  • element, view!, and composition containers now preserve borrowed child views at any depth. Existing owned trees continue to use Element; borrowed trees use the same builders and are bounded to their frame lifetime.

  • Flex measures padded children against their actual inner box and reports fixed/percent child dimensions when the container itself is auto-sized, so nested measurement matches the rects assigned during rendering.

  • TextInputState preserves grapheme clusters during cursor motion and deletion. Text input wrapping, rendering, and terminal cursor placement now share terminal-cell width, fixing CJK and multi-scalar emoji alignment while keeping public cursor and span coordinates as char indices.

  • FocusRegistry immediately falls back to the first current registration when a focused id disappears from a dynamic frame, and commits that fallback at the next frame boundary instead of retaining or resurrecting a stale target.

  • Breaking: synchronous Runner::run and run_with_backend now mirror the state/view/update shape of AsyncRunner: pass &mut State, render through view(&State, frame), and handle Signal in update(&mut State, Signal). Updates return UpdateResult::{Clean, Dirty, Exit} instead of ControlFlow<()>; ticks no longer repaint unless the update is dirty or a RedrawHandle fires.

  • Breaking: SelectState::selected() now returns Option<usize>, and SelectState::select takes Option<usize>, so lists and tables can render without a selected row. Migrate state.select(index) to state.select(Some(index)); use state.select(None) or SelectState::default() for no selection. SelectState::new() still selects the first row.

  • Markdown output changes where the source contains inline HTML. Text inside the whitelisted tags is now styled, <br> starts a new line, <img> becomes an image or an alt-text placeholder, and <sub>/<sup> digits become Unicode (H<sub>2</sub>O → H₂O). Markdown without HTML renders identically.

  • Consecutive blank lines no longer appear in markdown output when a block renders nothing (a block-HTML run with no renderer attached), so a dropped block leaves no gap where it was.

  • New gallery demo for inline HTML (docs/demos/markdown_html.png), referenced from the component gallery, the markdown guide, and Markdown’s rustdoc.

  • tuika-html gains an example and a recording for the Html component (cargo run -p tuika-html --example html_view); the existing example covers the markdown boundary. <sub>/<sup> now transliterate there too, so one document cannot render H₂O through markdown and H2O through the crate, and <dd> hangs directly under its <dt> instead of a blank line below.

  • Table now windows rows to its assigned render height by default. Table::viewport(rows) remains an optional upper bound.

  • ratatui Line styles are composed underneath their Span styles in text, table cells, scrolling text, and box titles.

  • Boxed titles start directly after the corner and truncate at the opposite corner, matching ratatui Block title placement.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close