Skip to content

Architecture

Mike Strobel edited this page Jun 26, 2026 · 1 revision

Architecture & the layered stack

Cursorial is a stack, not a monolith. Each layer builds on the one below, and each is usable on its own — you reference only what you intend to use. This page is the map: what each layer adds, how the pieces fit, and how the Cursorial.UI namespaces are organized for anyone arriving from WPF or Avalonia.

The layered philosophy

The guiding rule is use as much as you need. Terminal development has a long tail of hard problems — capability detection, VT-vs-Win32 input, grapheme widths, wide-glyph alignment, raw-mode and signal-handler safety, per-terminal quirks. Cursorial absorbs that complexity once, at the bottom, and lets you stop climbing the stack wherever your needs are met:

  • A tool that just wants better input parsing takes Cursorial.Core and nothing else.
  • A dashboard that draws its own cells adds Cursorial.Rendering for the diffing renderer.
  • Something that wants charts, gradients, and a scene compositor adds Cursorial.Drawing.
  • A full declarative, data-bound application climbs all the way to Cursorial.UI and Cursorial.UI.Xaml.

Each layer up the stack depends on the ones below it but never the reverse — Cursorial.Core knows nothing about widgets, and Cursorial.Rendering knows nothing about layout. The boundaries are real assembly boundaries, so the dependency direction is enforced by the compiler.

Cursorial.Core                 input · output writers · capability negotiation · text utilities
   └─ Cursorial.Rendering      cell buffer · diffing renderer · rich text · images · blending
        ├─ Cursorial.Drawing   scenes & compositor · brushes/gradients · pen/junction engine · charts
        ├─ Cursorial.Animation time-free IAnimation<T> · easings · interpolators  (consumer owns the clock)
        └─ Cursorial.UI        property system · layout · controls · styling · binding · input/focus · windowing
             └─ Cursorial.UI.Xaml   runtime loader · source generator · compiled bindings · code-behind

Cursorial.Drawing and Cursorial.Animation are siblings above Cursorial.Rendering: Drawing is a retained scene/compositor model, Animation is a pure time-to-value engine with no ambient clock. Cursorial.UI is the layer that brings them together — it composites scenes and owns the clock that drives animations.

What each layer adds

Cursorial.Core is the foundation: input parsing and delivery (InputEvent records, pull and push device shapes), the byte-emitting output writers (SgrEncoder, CursorWriter, ScreenWriter, HyperlinkWriter, TextSizingWriter), capability negotiation (VtTerminalNegotiator — an XTVERSION + DA1 handshake returning realized capabilities), the orchestrated TerminalSession (raw mode, opt-in protocols, signal-safe restore), and grapheme-aware text utilities. This is the only layer published on NuGet today and its surface is approaching stable.

Cursorial.Rendering adds the cell buffer and the diffing frame renderer. You write into a CellBuffer and hand successive buffers to a stateful FrameRenderer that diffs them and emits the minimal byte stream to update the terminal. Writes are grapheme-aware, wide-cell consistent, and capability-quantized before diffing, so a stable frame produces an empty delta even on a 16-color terminal. On top of the buffer sit rich text, images (Kitty graphics → iTerm2 → Sixel with glyph fallback), sized text, and blending/alpha compositing.

Cursorial.Drawing adds a retained-scene drawing layer: the unit of work is a Scene (a cached raster) composited by a SceneCompositor that only repaints what changed. It brings brushes and gradients, a pen engine that forms box-drawing junctions automatically where strokes meet, charts (Cursorial.Drawing.Charts), and the UI drawing primitives — an intra-scene clip/translate stack, occluding fills, dither, titled panels, and drop/inner shadows.

Cursorial.Animation adds a time-free animation engine. IAnimation<T> maps elapsed time to a value, so the consumer owns the clock and the layer stays deterministic and testable. It provides easings (cubic/quad/elastic/ bounce/cubic-bezier(…)), a process-global interpolator registry (double, int, Color, Point/Size/Rect, Margins, brushes, pens), and combinators. Cursorial.UI drives these through a frame-aligned scheduler.

Cursorial.UI is the WPF/Avalonia-style framework: a dependency-property system with a priority-ordered value store, an element tree with Measure/Arrange layout, retained render zones, routed input and focus, CSS-like styling, data binding, resources and theming, windowing, storyboards and transitions, and a full control catalog. A headless test harness (Cursorial.UI.Testing) makes the whole framework unit-testable without a TTY.

Cursorial.UI.Xaml adds declarative markup — define your UI in XAML, loaded at runtime or compiled by a Roslyn source generator. The runtime loader handles {Binding}/{StaticResource}/ControlTemplates/resource dictionaries with line+column diagnostics; the generator emits typed x:Name fields, InitializeComponent() code-behind, compiled bindings, and an AOT-clean metadata provider.

Cross-platform stance

Cursorial targets Windows, macOS, and Linux terminals as equal peers. There is no privileged platform: a design choice that would bake in a single-platform assumption (VT sequences only, Win32 console only) needs an explicit story for the others before it lands. In practice that means the negotiator identifies terminal families and enables only what each honors; input decoding covers both VT/ANSI wire formats and Windows Input Mode; and TerminalSession abstracts platform stdio (POSIX stty raw mode vs. Windows console-mode P/Invoke) behind one factory. The suite has been exercised against kitty, Ghostty, iTerm2, WezTerm, Rio, Apple Terminal, Alacritty, GNOME Terminal, ConEmu/Cmder, and Windows Terminal / Console Host.

The target framework is net10.0 for the runtime layers; the two XAML pieces shared with the source generator (Cursorial.UI.Xaml.Frontend, Cursorial.UI.Xaml.Generator, Cursorial.Shared) target netstandard2.0 so the same parser runs inside a Roslyn analyzer.

The project map

Project Target Contents
Cursorial.Core net10.0 Input parsing & delivery, output writers, capability negotiation, terminal session, text utilities.
Cursorial.Rendering net10.0 Cell buffer, diffing frame renderer, rich text, images, blending & alpha compositing.
Cursorial.Drawing net10.0 Scenes & compositor, brushes/gradients, pen/junction engine, charts, UI drawing primitives.
Cursorial.Animation net10.0 Time-free IAnimation<T>, easings, interpolator registry.
Cursorial.UI net10.0 The framework: property system, layout, controls, styling, binding, input/focus, windowing, animation.
Cursorial.UI.Xaml.Frontend netstandard2.0 The shared XAML parser, the structure-of-arrays node model, markup-extension grammar, diagnostics, and type-system seams. No Cursorial.UI reference.
Cursorial.UI.Xaml net10.0 The runtime XAML loader: markup extensions, resource dictionaries, deferred content/templates.
Cursorial.UI.Xaml.Generator netstandard2.0 The Roslyn incremental source generator: symbol-backed parse, compiled bindings, code-behind, AOT-clean metadata provider.
Cursorial.UI.Themes net10.0 Bundled UI themes (the built-in control themes, also available as embedded XAML).
Cursorial.UI.Testing net10.0 The headless UITestHost + synthetic terminal — the integration substrate so no UI test needs a TTY.
Cursorial.Shared netstandard2.0 Markup attributes ([ContentProperty], [TypeConverter], …) shared by the loader and the generator.
Cursorial.Demo net10.0 An interactive REPL that drives every layer end-to-end.
Cursorial.Gallery net10.0 A standalone XAML-first MVVM control gallery — a full app, not a demo command.
*.Tests net10.0 xUnit suites per project.

Cursorial.UI.Xaml.Frontend is the keystone of the dual loader/generator design: it is a netstandard2.0 assembly with no reference to Cursorial.UI, holding the parser, node model, and type-system seams. The runtime loader and the build-time generator are two backends over the same frontend, and a drift gate asserts they produce byte-identical element trees.

A taste of each end of the stack

The bottom — Cursorial.Core alone, no rendering:

// Raw mode, capability handshake, opt-in protocols, signal-safe restore — one call.
await using var session = await TerminalSession.OpenAsync();

await foreach (var evt in session.Input.ReadAllAsync())
    if (evt is KeyEvent { Kind: KeyEventKind.Down, Text.Span: "q" }) break;

The top — the full framework, built imperatively:

var app = UIApplication.CreateBuilder().Build();

return await app.RunAsync(() =>
{
    var stack  = new StackPanel { Spacing = 1, Margin = new Margins(2, 1) };
    var button = new Button { Content = "_Click me" };   // _ marks the Alt access key
    button.Click += (_, _) => button.Content = "Clicked!";

    stack.Children.Add(new TextBlock { Text = "Hello, Cursorial.UI 👋" });
    stack.Children.Add(button);
    return stack;
});

…or the same UI declared in XAML and loaded at runtime:

var root = XamlLoader.Shared.Load<StackPanel>(xaml);
root.DataContext = viewModel;

The Cursorial.UI namespace scheme

The UI framework deliberately mirrors WPF's (and Avalonia's) namespace organization, so the mental model transfers directly. The acronym UI is fully capitalized in type namesUIElement, UIProperty, UIApplication — never UiElement.

  • Cursorial.UI — the core, analogous to WPF's System.Windows. The base objects (UIObject / UIElement), the dependency-property system, element-level layout enums (Visibility, the alignment enums), render integration, and hosting (UIApplication). UIElementCollection lives here too, beside UIElement — a deliberate deviation from WPF's placement.
  • Cursorial.UI.Controls — analogous to System.Windows.Controls. Panel and the panels (StackPanel, DockPanel, Grid with GridLength/definitions, Canvas, WrapPanel), the presenters, every control from Control down, and the panel-facing Orientation / Dock enums.
  • Cursorial.UI.Input — routed events, the dispatcher, focus, and gestures.

The folder layout mirrors the namespaces (Cursorial.UI/Controls/, Cursorial.UI/Input/). Brushes and pens live one layer down in Cursorial.Drawing.Media, with charts in Cursorial.Drawing.Charts.

One naming note worth knowing early: the UI styling type is Cursorial.UI.Style (the WPF Style analog), which is distinct from the SGR Cursorial.Output.Style value type used for cell colors and attributes. Framework source disambiguates with a using alias; in your own code, the Cursorial.UI.Style you reach for in styling and the Style you pass to CellBuffer.Write are different types from different layers.

See also

  • Getting started — install, the first running app, and where to climb the stack.
  • UI overview — the framework's object model, element tree, and frame loop in depth.
  • Core input — the input event model and device shapes at the bottom of the stack.
  • XAML — the runtime loader, the source generator, and how they share one parser.

Clone this wiki locally