Jumbee.Console 0.2.0

dotnet add package Jumbee.Console --version 0.2.0
                    
NuGet\Install-Package Jumbee.Console -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Jumbee.Console" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Jumbee.Console" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Jumbee.Console" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Jumbee.Console --version 0.2.0
                    
#r "nuget: Jumbee.Console, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Jumbee.Console@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Jumbee.Console&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Jumbee.Console&version=0.2.0
                    
Install as a Cake Tool

Jumbee.Console

alternate text is missing from this package README image

About

Jumbee.Console is a .NET library for advanced TUIs that focuses on performance and usability. Inspired by libs like ratatui and Textual, it tries to provide a high-performance retained-mode library that is easy-to-use with idiomatic .NET GUI and Task patterns, while flexible enough to create different types of TUI applications from news readers to animated dashboards to IDEs to agent harnesses to graphics apps.

Features

  • 100% managed AOT-compatible code.
  • Retained-mode GUI framework with an API designed to be easy to use and extend.
  • Sub-ms frame rendering times and minimal CPU consumption even with complex displays like multi-tab document editing and syntax highlighting.
  • Uses modern terminal features: ANSI/VT control sequences, 24-bit colour, SGR-encoded mouse with motion tracking, bracketed paste, focus reporting, and the alternate screen buffer.
  • Also support legacy non-ANSI terminal emulators like the classic Windows console.
  • Uses Spectre.Console-compatible markup, styles, text rendering, and widgets in a retained-mode rendering pipeline.
  • Supports both fixed-width layouts like Grid and flexible, resizable layouts like DockPanel, HorizontalStack, VerticalStack, resizable SplitPanel.
  • Large set of common GUI controls: menus, buttons, trees, text inputs with autocomplete, modal dialog windows, etc...., supports easy composition of controls.
  • Control frames support adornments like titles, borders, margins, and scrollbars.
  • Cross-platform 100% managed code terminal-emulator.
  • Multi-tab editor that supports C#, JavaScript, C++, Markdown + a dozen other languages.
  • Split-pane interactive editors with preview for Markdown, AsciiDoc, Mermaid documents, Mermaid embedded in Markdown.
  • Visualization and graphics: Many different types of plots and graphs like candlestick & heatmap plots & bar/run charts, world maps, sub-cell drawing canvas and shapes, 3D texture rendering, and support for animation.
  • Flexible themes that support styling both colors and glyphs independently.
  • Headless snapshot testing: render any control or layout to text or PNG without a real terminal.

Getting Started

dotnet add package Jumbee.Console

A first app — a label and a button that increments a counter:

using Jumbee.Console;

using static Jumbee.Console.Color;   // import the static color names

var count = 0;

var label = new TextLabel(TextLabelOrientation.Horizontal, "Count: 0", Cyan1);
var button = new Button("Increment");

// Change the label text when the button is clicked or pressed
button.Activated += (_, _) => label.Text = $"Count: {++count}";

// One column, two rows: the label above the button.
var root = new Grid(
    columnWidths: [30],
    rowHeights: [1, 3],
    controls:
    [
        [label],
        [button],
    ]);

UI.RegisterHotKey(UI.HotKeys.Escape, UI.Stop);   // Esc quits (Ctrl+Q also quits by default)
UI.SetFocus(button);                             // focus it so Enter/Space activates

// Start the UI. Mouse/hover need a VtInputSource or omit for keyboard only.
// Returns a task.
var t = UI.Start(root, width: 34, height: 6, input: new VtInputSource(anyMotion: true));

// Wait for the UI to stop.
t.Wait();

See GETTING-STARTED.md and the documentation. To find the right control for a job, start with the control guides — a decision table over the whole library, then a guide per category.

For LLMs and coding agents

llms.txt (what this is) is a curated index of every doc and API page in this project, as absolute links to raw Markdown. Fetch it first to find the page you need, then fetch that page directly.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Jumbee.Console:

Package Downloads
Jumbee.Console.Documents

Document viewers and editors for Jumbee.Console: Markdown, AsciiDoc, and Mermaid diagram rendering and interactive editing controls for TUIs.

Jumbee.Console.Snapshot

Headless snapshot testing for Jumbee.Console TUIs: render controls to text or PNG without a real terminal, for unit tests and visual verification.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 163 8/18/2026
0.1.11 102 8/8/2026
0.1.10 124 8/2/2026
0.1.9 127 8/1/2026
0.1.8 128 7/31/2026
0.1.7 119 7/30/2026
0.1.6 118 7/29/2026
0.1.5 140 7/23/2026
0.1.4 131 7/21/2026
0.1.3 128 7/20/2026
0.1.2 129 7/20/2026
0.1.1 136 7/18/2026

0.2.0
-----
ADDED

 * UI.RestoreBuiltInHotKeys() - drops every hotkey an app registered with UI.RegisterHotKey, keeping the
   library's own (Ctrl+Q, the Ctrl focus tier, F1). UI.Stop now calls it, so hotkeys are scoped to the
   session that registered them, alongside the control list Stop already cleared.

 * IScrollable - the opt-in that makes a framed control scroll. A ControlFrame owns the viewport, the
   scrollbar and the offset; the control's side of the bargain is one honest answer, "how tall is my content
   at this width", which is now IScrollable.MeasureHeight. Implemented by ListBox, Tree, TextEditor,
   TextPanel, CodeEditor, MarkdownViewer, AsciiDocViewer and MermaidViewer.

 * IScrollable.FocusRowChanged, with the RowSpan struct it carries. A control with a moving point of
   interest - a selected item, a caret - raises it when that point moves, and the enclosing frame scrolls the
   rows into view. It has a do-nothing default implementation, so a viewer with nothing to report simply
   omits it. Declared as a plain field-like event, the compiler reports CS0067 if it is never raised, which
   catches the mistake worth catching: a control that claims a moving selection and then lets it scroll off
   screen.

 * ControlFrame.ScrollIntoView(start, height) and the protected Control.ScrollIntoView, replacing three
   private copies of the same clamp in ListBox, Tree and CodeEditor. Scrolls the minimum needed, does nothing
   when the rows are already visible, and aligns a span taller than the viewport to its top.

 * A frame now reveals a newly focused descendant automatically. Tab into a tall sidebar and the frame
   scrolls the focused control into view with no code in the composite: the row is recovered by walking the
   drawing-context chain, so it reads the layout's real positions and cannot drift from them the way
   arithmetic in a composite would.

 * Terminal-write telemetry. The console write runs off the render loop, so frame time never included it and
   nothing reported it at all. UI.AverageWriteTime, AverageWriteWaitTime, WriteQueueDepth/Peak,
   AverageFrameLatency and PeakFrameLatency now do, fed by ConsoleManager's Emit and paired back to their own
   frame by ordinal. PerfHud gained write, wait and latency rows.

 * ProcessMetrics.RenderTimeMsDrawnAvg, FrameLatencyMsAvg/Peak, LockContentionsPerSecond, RecordWrite and
   ConsoleManager.ResetOutputTelemetry.

CHANGED

 * BREAKING: Control.FillsFrameViewport is removed, and frame scrolling is now opt-in rather than opt-out.
   Previously a frame gave EVERY child an unbounded height unless it set FillsFrameViewport, and a control
   that overrode neither that nor MeasureHeight resolved to the 1000-row size clamp - a scrollbar with a
   thumb too small to draw over ~1000 rows of blank space, with nothing thrown, nothing logged, the content
   drawn correctly at the top, and a snapshot of the visible area passing. The wrong answer was the one you
   got by doing nothing.

   To migrate:

       Was                                          Now
       --------------------------------------------------------------------------------
       override FillsFrameViewport => true          delete the override (it is the default)
       override MeasureHeight, wanted scrolling     implement IScrollable
       neither, but relied on frame scrolling       implement IScrollable

   A control that implemented neither and did not want scrolling needs no change. A third-party control that
   relied on the old implicit default stops scrolling rather than scrolling wrongly, which is the better
   direction to fail, but it is a real break.

 * BREAKING: Control.MeasureHeight moved from a protected virtual to IScrollable.MeasureHeight, and is public
   on the implementing control. It is deliberately not an explicit interface implementation: those are not
   virtual, so the first subclass wanting its own measurement would have had to re-declare the interface.

 * Control.OnMouseWheel's default now scrolls the nearest ENCLOSING scrolling frame, not just Frame. The
   control under the pointer is almost never the one being scrolled - a button inside a panel of framed
   sections has no frame of its own - so a wheel notch over it used to be dropped entirely.

 * Slider takes the wheel only when it has focus, or when nothing enclosing it can scroll. A standalone
   slider behaves as before; one inside a scrolling panel now passes the notch through instead of making the
   panel nearly unscrollable and changing a value the pointer was only passing over.

 * PerfHud: the frame row is renamed render and now averages the frames that actually drew (the old figure
   was diluted by cheap idle frames, so it did not add up with the write/wait rows); locks is a contention
   RATE over the rolling window rather than the lifetime total, so it returns to zero once contention stops;
   the timing rows are grouped together; and the panel derives its height from the rows it emits rather than
   a hand-kept constant that silently clipped the last metric when it fell behind.

FIXED

 * Tree reported no content height, so a framed Tree scrolled over the 1000-row clamp rather than its own
   rows - a sliver of a thumb, and a scroll range with nothing in most of it. It had been scrolling by
   accident for as long as it has existed, which is why nothing caught it; the test asserting that navigation
   scrolls passed for the wrong reason. Tree now reports its real row count.

 * A framed DataTable drew a dead scroll track and lost its own bottom border. It owns its viewport and draws
   its own scrollbar, but declared neither of the old opt-outs, so the frame gave it the unbounded height,
   took a column for a scrollbar whose thumb was too small to render, and clipped the table's last row out of
   reach - the wheel is consumed by the table, so nothing could scroll it back. Framing a table for a border
   and a title is the common case and now works.

 * Tree's scroll-into-view lacked the guard ListBox had for a node taller than the viewport, so navigating to
   one scrolled past its own first row and pushed the just-selected node off the top. Both now share one
   implementation.

 * Control.Dispose leaked the frame's UI.FocusChanged subscription. The Frame setter attached the frame to
   both UI.ThemeChanged and UI.FocusChanged; Dispose detached only the first, so every framed control ever
   created kept handling focus moves for the life of the process.

 * ConsoleManager no longer commands the terminal in response to a terminal resize. AdjustBufferSize called
   Resize, whose StandardConsole setter drives SetBufferSize/SetWindowSize - and those can fail or clamp
   silently, so the size read back differed again next frame and the whole UI re-laid-out every frame. That
   feedback loop is why AnsiTerminalConsole existed to route around it. Observing a resize now adopts it; the
   console still syncs its own scroll buffer, so a full-screen UI keeps a host scrollbar off the app.

 * The legacy console no longer collapses to a single cell. StandardConsole's Size setter shrank the window
   to 1x1 as an intermediate step before resizing the buffer, and SafeConsole swallows failures - so if
   either following call failed, the window was simply left at 1x1 and the app looked like it had vanished
   while the process kept running. It now shrinks only to the target size.

 * ChatPrompt is one row tall in a plain layout cell, and one column wider inside a frame. It reported its
   single row as an IScrollable content height, which is honoured only under an unbounded parent, so unframed
   it filled its cell; and being scrollable cost it a column of every frame's interior for a scrollbar that
   could never appear. It states the row as an intrinsic height instead.

EXAMPLES

 * The 3D sandbox model viewer's sidebar scrolls, so it stays usable on a short terminal. It also gained a
   "Scale All" slider that drives the three axis sliders together, a Reset in each of the Scale and Shear
   sections (each undoing only its own section - the whole-transform reset is still Model > Reset transform
   and the 0 key), and the mouse-wheel zoom direction is corrected: wheel up now zooms in.

 * The agent harness demo's task list stopped round-tripping every style through markup text. It built a
   markup STRING with Style.ToMarkup()/Color.ToMarkup() and let Markup parse it straight back through the
   tokenizer, style parser and hex-colour parser - 57% of the whole demo's allocations. Emitting styled
   Segments directly, and computing the pane height arithmetically rather than rendering it to count lines,
   cut the demo's total allocations from ~482k to ~123k and its per-frame allocation by roughly half.

DOCS

 * New "Scrolling" section in docs/controls/Control Model.md - the canonical recipe, in the guide someone
   framing a control actually opens rather than filed under composites. It names the failure mode outright
   ("a control that doesn't implement IScrollable is sized to the viewport and never scrolled") and covers
   the two things a frame will not do for you: focus within a control, and the column the scrollbar costs.

 * The PerfHud counter table in docs/controls/Live Data.md is rewritten for the new metrics, with a section
   on why latency is not a frame budget: render and write overlap, so throughput is bounded by whichever is
   slower while latency is their sum.

 * New internal design note at docs/internal/Scrolling.md recording why the old default was the broken one,
   why the focus row is an event rather than a polled property, and what CS0067 does and does not enforce.