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.

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.

Added
-
Wrapped flex lines with independent row/column gaps, grow and shrink weights, min/max constraints,
align_self, cross-lineAlignContent, and exact boundary-based cell rounding.FlexItemStyleseparates child properties fromLayoutStyle;solve_layoutalso reports resolved line geometry. -
Flowfor intrinsic-width wrapping and a deliberately small equal-column row-majorGridcomponent. -
Extensible
MeasureRequest/AvailableSpacemeasurement, with known axes and definite/min-content/max-content modes. The default adapter preserves existingView::measureimplementations. -
Runtime-neutral
RunnerCore, configurableTerminalSessionConfig, andtesting::TestHarnessfor state/signal/view application tests without a terminal or async runtime. -
render_once/write_oncefor ANSI-styled ordinary output, andview!when(...)/for(... in ...)composition. -
ClockandSystemClockprovide one monotonic time boundary.SelectionState::handle_with_clockmakes double-click gestures deterministic, andRunner::with_clocklets replayable synchronous hosts own tick time; existinghandleandRunner::newbehavior remains system-clock backed. -
VirtualWindowprovides overflow-safe clamped and selection-centered ranges;Scrollbarrenders 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::windowedandTable::windowedaccept only the visible records while preserving absolute selection and full-collection scrollbar geometry. -
DockState,DockSpec, andDockLayoutprovide 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:
TargetPlacementselects above/below/left/right, cross-axis alignment, gap, and optional edge-aware flipping;OverlaySpec::resolve_targetresolves it directly andSceneOverlay::targetfollows aRectProbefrom the scene root in the same frame. Screen-anchored placement remains unchanged. -
StyleRoleandStyleResolverform an open semantic styling boundary for hosts and companion crates.RenderCtx::styleresolves built-in or namespaced application roles, resolver bundles partially overlay stylesheet defaults, and resolver revisions invalidate measurement caches.paint_with_contextandtesting::render_with_contextinstall and test the complete policy. -
ScopedElement<'view>, the boxed frame-borrowed counterpart to ownedElement, 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 sameStyleSheetrole as the markdown construct it mirrors, so a host that restylesstrongrestyles<b>with it;<a>and<img>take the existing hyperlink andImageResolverpaths. 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.
MarkdownBlockRendererreceives a non-exhaustiveMarkdownBlockdescriptor (FencedorHtml) and a sharedMarkdownBlockContextcontaining width, theme, and the active stylesheet.Markdown::block_rendererandMarkdownState::with_block_rendererappend to an ordered renderer chain, so Mermaid, HTML, and host-defined block parsers compose without adding another trait or field for every syntax. -
tuika-htmlbounds 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::Renderersbuilds the same ordered block-renderer chain formarkdown::to_lines_with/to_linked_lines_with. -
ProgressBar::labeldraws 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_styleandSelectList::selection_styleallow per-instance selection foreground, background, and modifiers. -
SelectNavigationpolicies for optional j/k, Ctrl+N/P, Tab/Shift+Tab, and numeric selection aliases; explicit mouse hit-testing onSelectState; andMultiSelectStatefor toggleable multiple-selection workflows. -
KeyHints::from_keymap, priority-aware whole-hint fitting, andKeymapHelpso one labeled keymap declaration drives dispatch, responsive footer hints, and a complete help view. -
SingleLineInputStatefor search/command fields, with newline normalization, Enter/Ctrl+J submission, and allocation-free borrowed text access. -
tuika::uire-exportsRect,Color,Style,Modifier,Line, andSpanfor custom views without a directratatui-coredependency. -
testing::render_with_sheetrenders consumer views under an explicit stylesheet in the same hermetic buffer harness astesting::render.
Changed
-
Breaking:
FencedBlockRendererandHtmlBlockRendererare replaced byMarkdownBlockRenderer. Match onMarkdownBlock, read width/theme/sheet fromMarkdownBlockContext, register every implementation throughblock_renderer, and build free-function chains withRenderers::new().renderer(&first).renderer(&second). HTML fences now receive the host’s active stylesheet instead of synthesizing theme defaults. -
Breaking:
LayoutStyle::gapis split intorow_gapandcolumn_gap(the.gap(...)builder still sets both), andItem::dimensionis replaced byItem::style. UseItem::newfor the compatible compact path orItem::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 thanshift+aorshift+/;Shiftremains valid for non-character chords such asshift+enter. Ambiguousshift+characterspecs now returnKeyParseError(or panic through staticLayer::bind) instead of silently discarding Shift. -
Breaking:
AsyncRunnerupdate closures now returnUpdateResultinstead ofControlFlow<()>, matching synchronousRunner; 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, orCancelled). Read the submitted value back from its host-owned state. ReplaceSelectOutcome,MultiSelectOutcome,FormOutcome,TabSelectOutcome,TextInputEvent, and directEventFlowmatches withInputOutcome; call.flow()when only propagation matters.SelectState,MultiSelectState, andScrollStatenow makeDefaultidentical tonew(); useSelectState::unselected()for an initially cursorless list. -
Breaking:
StyleSheetadds typed toast, diff, and key-hint fields. Toasts, diffs,KeyHints, andKeymapHelpnow derive their defaults from those roles instead of renderer literals; explicitDiff::stylecolors still win for one instance. ExhaustiveStyleSheetliterals must add the new fields or use..StyleSheet::from_theme(&theme). ConstructRenderCtxthroughRenderCtx::newrather than a struct literal now that it carries the optional resolver. -
Breaking:
View::measurenow takes&RenderCtx, and every composition container forwards the frame’s active theme, stylesheet, and focus state. Migratefn measure(&self, available: Size)implementations tofn measure(&self, available: Size, ctx: &RenderCtx); callers likewise pass the context used for rendering.Flex::solvenow takes that context, andItemScroll::{measure_height, measure_views}take it as their final argument. -
Markdownand the companiontuika-html::Htmlview now measure with the same theme and stylesheet they render with.Boxedimplements 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 useElement; borrowed trees use the same builders and are bounded to their frame lifetime. -
Flexmeasures 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. -
TextInputStatepreserves 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. -
FocusRegistryimmediately 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::runandrun_with_backendnow mirror the state/view/update shape ofAsyncRunner: pass&mut State, render throughview(&State, frame), and handleSignalinupdate(&mut State, Signal). Updates returnUpdateResult::{Clean, Dirty, Exit}instead ofControlFlow<()>; ticks no longer repaint unless the update is dirty or aRedrawHandlefires. -
Breaking:
SelectState::selected()now returnsOption<usize>, andSelectState::selecttakesOption<usize>, so lists and tables can render without a selected row. Migratestate.select(index)tostate.select(Some(index)); usestate.select(None)orSelectState::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, andMarkdown’s rustdoc. -
tuika-htmlgains an example and a recording for theHtmlcomponent (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 renderH₂Othrough markdown andH2Othrough the crate, and<dd>hangs directly under its<dt>instead of a blank line below. -
Tablenow windows rows to its assigned render height by default.Table::viewport(rows)remains an optional upper bound. -
ratatui
Linestyles are composed underneath theirSpanstyles in text, table cells, scrolling text, and box titles. -
Boxedtitles start directly after the corner and truncate at the opposite corner, matching ratatuiBlocktitle placement.