Ansiq
Ansiq is a runtime-first TUI framework for long-running terminal applications.
It is designed around one core idea:
Streams, tasks, and input should be coordinated by a single terminal runtime.
Ansiq is not a widget-heavy component kit. It is a Rust-only runtime for streaming-first terminal UX, aimed at AI agents, developer tools, dashboards, and similar interactive systems.
Positioning
Ansiq focuses on:
- async-native terminal applications
- retained UI tree rendering
- incremental framebuffer diffing
- focus-aware input routing
- background task integration through message passing
- streaming output as a first-class UI behavior
- a signal-first Rust API with function components and a
view!macro
Architecture
The render path is:
retained tree -> layout -> framebuffer -> diff -> terminal
The runtime path is:
input / async message -> update -> rebuild tree -> layout -> diff render
Key runtime rules:
- UI changes are coordinated by one runtime loop
- async tasks never mutate the UI directly
- background work sends messages back to the runtime through channels
- the renderer flushes only changed cells instead of clearing and repainting the whole screen
- terminal session policy lives in
ansiq-surface, so the runtime does not invent its own terminal occupancy rules - reactive handles are thread-affine and intentionally non-
Send - terminal input intake uses async event streams instead of blocking a Tokio worker with synchronous polling
Runtime Boundary
Ansiq runtime is responsible for:
- app lifecycle and the single UI loop
- reactive flush scheduling
- dirty scope collection
- subtree rerender and replacement
- focus management and input routing
- partial relayout
- invalidated region tracking
- terminal session and viewport management
- framebuffer diff and terminal patch emission
Ansiq runtime is not responsible for:
- business domain modeling
- network protocol implementations
- higher-level AI agent orchestration
- complex rich-text parsing or markdown semantics
- persistence and external storage policy
The detailed version of this boundary is documented in
docs/runtime-boundary.md.
The release process and publish order are documented in
docs/release-checklist.md.
Monorepo Layout
This repository is a Cargo workspace monorepo. Framework crates live under packages/, and runnable demos live under examples/.
packages/ansiq-coreCore element model, geometry, style types, signal-first reactivity primitives, and function-component helpers.packages/ansiq-macrosview!proc macro for JSX-like declarative trees that compile to the same retainedElementmodel.packages/ansiq-runtimeMain UI loop, focus handling, input routing, async message integration, and tree rendering orchestration.packages/ansiq-surfaceTerminal session lifecycle, viewport policy, raw mode, cursor handling, and event intake viacrossterm.packages/ansiq-renderCell buffer, diff generation, and ANSI patch emission.packages/ansiq-layoutMinimal row/column layout with fixed/fill sizing and pane inset handling.packages/ansiq-widgetsBuilder-style primitives:Box,Block,Text,Paragraph,List,Table,Tabs,Gauge,LineGauge,Sparkline,Scrollbar,Clear,Pane,ScrollView,StreamingText,Input,StatusBar.examplesRunnable scenario demos and example applications.
Each package keeps its tests in a dedicated tests/ directory instead of inline #[cfg(test)] modules.
Runtime Implementation Status
The current runtime is no longer just an idea sketch. It already has working implementations for the main kernel responsibilities, but the maturity level is not uniform across every subsystem.
Implemented
- terminal session setup and teardown without taking over the whole terminal by default
- surface-side viewport planning for initial reserve, resize, reanchor, and fit operations
- history reanchor based on counted rendered rows instead of terminal cursor-position queries
ReservePreferred(n)now returns tonafter history commit reanchor instead of treating temporary growth as the new steady-state shell heightHistoryEntry::Textnow normalizes through the same commit-timeHistoryBlockwrapping path as structured history blocks- terminal session teardown clamps the final exit row to the visible terminal height before restoring the cursor
- framebuffer-based diff rendering
- retained
Elementtree - signal-first component API via
Cx:cx.signal,cx.computed, andcx.effect - standalone signal-first reactivity core in
ansiq-core(signal,computed,effect, explicitflush)
effect is not React's useEffect: there is no dependency array. Dependencies are tracked automatically from reactive reads performed while the effect runs.
Signal::set_if_changed(...)for non-breaking same-value propagation suppression whenT: PartialEq- focusable widgets and tab traversal
- optional focus trapping to a continuity-keyed subtree
- focused input routing for the
Inputwidget - app-level
on_unhandled_key(...)for keys not consumed by widgets or runtime focus traversal - async app messages with
tokio - dirty component scope collection
- subtree rerender and replacement
- explicit continuity keys for preserving focus and local widget state across subtree and root rerenders
- component subtree replacement refreshing wrapper-node measured height before ancestor relayout
- partial relayout along dirty ancestor chains
- invalidated region tracking
- overlapping dirty paths normalized before relayout and damage collection
- layout-only containers avoiding full-rect self-invalidation when only descendants shift
- separation between rerendering the tree and redrawing in-place interactive widget state
- core-owned layout contracts consumed by
ansiq-layout - debug-time hardening of strict layout primitives such as three-slot
Shell - core-owned widget key routing contracts consumed by
ansiq-runtime - core-owned render math consumed by
ansiq-runtime::draw - core-owned text shaping helpers consumed by
ansiq-runtime::draw - runtime-side drawing and cursor lookup split into focused internal modules instead of one monolithic
draw.rs - function component API via
Cx view!macro for declarative UI trees
Implemented But Still Maturing
- partial redraw planning and damage tracking for complex shells
- viewport growth, scrollback flush, and inline session behavior across varied terminal environments
- more advanced viewport pinning and detached scrollback semantics for long-running shells
- committed scrollback history currently wraps at commit-time width and does not reflow after later terminal resizes
- widget parity with
ratatui - Unicode/grapheme correctness beyond common terminal cases
- higher-level session/shell composition patterns
Not Yet Complete
- keyed reconciliation
- mouse support
- a fully mature shell/layout model for complex workspaces
- virtualization for very large scroll regions
- complete
ratatuiwidget parity
Engineering Bar
Ansiq started from an MVP direction, but ongoing implementation work should not aim for “good enough for a demo”.
The current expectation is:
- prefer stable, reusable behavior over scenario-specific patches
- prefer library-level abstractions over example-local workarounds
- prefer explicit runtime boundaries over convenient coupling
- treat partially correct behavior as unfinished, not “close enough”
Current Limitations
The following areas are still intentionally minimal or incomplete:
- no keyed reconciliation
- no mouse support
- no rich text or multiline editing
- no advanced layout engine or full flexbox behavior
- no virtualization for very large scroll regions
- Unicode handling is conservative and optimized for common terminal text, not every grapheme edge case
ScrollViewis optimized for text-like children in this MVP- the
view!macro is intentionally smaller than JSX and does not yet support inline control flow
Example
The examples crate now provides multiple runnable demos:
activity_monitor: macOS-style process monitor with live CPU, memory, energy, disk, and network tabslist_navigation: interactive list selectionscroll_sync: shared scroll state betweenScrollViewandScrollbartable_interaction: keyboard-driven table selection
Run:
Controls:
Tab/Shift-Tab: move focusLeft/Right: switch tabs whenTabsis focusedUp/Down: move selection in tables and listsj/k: move focus when the focused widget does not consume the keyCtrl+C: exit
The example entrypoints opt into scenario-appropriate viewport policies.
The framework default remains a conservative inline mode that preserves visible terminal content above the app.
Future Work
Logical next steps after the MVP:
- keyed tree reconciliation
- richer layout and sizing policies
- better Unicode and grapheme support
- mouse input
- additional widgets
- higher-level reactive helpers built on top of the signal core
- smarter damage tracking for large scrolling regions
- richer
view!syntax, including controlled support for conditional and repeated children