Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[2.0.0] - 2026-11-03
MiniTui 2.0: Buffer is renamed CellBuffer, so samples compile in a default console project, plus fixes found by building four apps from the docs alone. See Migrating from 1.x to 2.0.
Added
TextFieldState.Changed, raised after the text changes (typing, deleting, pasting, undo and redo, or settingText), but not for cursor or selection moves.ConfirmAsync(message, yes, no, yesFirst)andConfirmDialog(..., yesFirst): withyesFirst: false, No is selected when the dialog opens, for destructive actions. The example's Services demo asks that way before deleting.- On a pushed screen, the status bar hints
esc Back(after the screen's own hints) unless something in scope binds Esc.
Changed
- Breaking:
Bufferis nowCellBuffer, so it no longer clashes withSystem.Bufferin projects with implicit usings (everyRender(Buffer buf, ...)override failed to compile without an alias). EveryRender,Drawand render callback takes aCellBuffer. Migrating from 1.x to 2.0 has a one-linesed. - Breaking (binary):
ConfirmAsyncandConfirmDialog's constructor have the newyesFirstparameter; source compiles unchanged. - A tree's type-ahead handles a key only when it matches a row, so
?and other app keys work while a tree is focused. - Every click on a tree's expander toggles it; two quick clicks used to open it and leave it open.
- A gauge's label over the empty part is drawn in the theme's text color, not the terminal's default, so it reads with
Theme.Lighton a dark terminal. - Text fields, text areas and the command line leave Alt with the arrows, Home and End unhandled, so a split's Alt+Shift+arrows work while one is focused.
- The command palette lists an action once when a key binding and a menu item have the same delegate.
- Toasts start below the menu bar instead of covering it.
- The status bar gives the key hints room first, and the breadcrumb what's left.
- Overlay titles are drawn with a space on each side whether or not the title has them.
- Every open overlay counts as waiting for the user, so
ComponentHarnessdoesn't time out while a task awaits a custom dialog.
[1.15.1] - 2026-11-03
MiniTui 1.15.1: the documentation moves to minitui.org, and the repository to github.com/minitui-org/minitui. No code changes.
Changed
- The documentation moved to minitui.org: guides, an API reference and the changelog. The README is now a summary that links to it, the package's page on nuget.org is a shorter one of its own, and the package's project URL is the site.
- The repository moved to github.com/minitui-org/minitui; the old address redirects. New versions publish to GitHub Packages under
minitui-org.
[1.15.0] - 2026-11-03
MiniTui 1.15: reading the system clipboard through the terminal, the Insert keys, and selecting paragraph text with the mouse.
Added
App.WithClipboardRead(): on Linux and macOS, Ctrl+V (and Shift+Insert) in text fields, text areas and command lines asks the terminal for its clipboard with an OSC 52 query and pastes the reply when it arrives. A refusal (an empty reply) pastes the in-app text; no reply within 500 ms does too, and the app stops asking for the rest of the run. The decoder reads OSC 52 replies of any length, never as keys.Clipboard.GetTextAsync()reads the same way from code (the Win32 clipboard on Windows).- Ctrl+Insert copies the selection (and never quits), Shift+Insert pastes, and Shift+Delete cuts (or deletes, without a selection) in text fields, text areas and command lines.
- Selecting paragraph text:
ScrollState.Selectablelets the mouse select the text of the paragraph drawn with the state (press and drag, scrolling past the edges; a double-click selects a word, a triple-click a row), drawn inTheme.Selection(orSelectionUnfocusedwhen the paragraph's block isn't focused).SelectedTextis the rows as shown, a line break between them.HandleKeycopies with Ctrl+C or Ctrl+Insert, selects all with Ctrl+A and clears with Esc;SelectAll,ClearSelectionandCopydo the same from code. When the text changes, the selection stays on its rows, and is cleared when they're gone. Component.HasSelection: while it's true, Ctrl+C goes to the focused component'sHandleKeyinstead of quitting, as it does for one that captures text.- The message and help dialogs' text can be selected and copied.
- Example: the example app reads the clipboard through the terminal, the Form demo's footer lists the Insert keys, and the Dashboard's log can be selected and copied (and follows new lines while scrolled to the end).
docs/SPEC-1.x.md§20: the clipboard and selection extras spec.
Changed
- Ctrl+Insert and Shift+Insert used to be unhandled in the text states, and Shift+Delete deleted a cluster even with a selection.
- Esc clears a selection in text fields, text areas, command lines and the command line overlay; without one it goes on as before (closing or going back, or clearing a command line). It used to go on with the selection left in place.
- The message and help dialogs handle a press and drag (selecting) where they ignored them, and Esc clears a selection before it closes them.
[1.14.0] - 2026-11-03
MiniTui 1.14: grouped and stacked bar charts, time and duration axes, tick marks and grid lines, and area charts.
Added
- Grouped and stacked bar charts:
BarChartBuilder.Groups(new BarGroup(label, bars), ...)draws each group's bars side by side, withGroupGapbetween groups and the group's label under them, or, withStacked(), as one bar of segments from the bottom up (left to right), whole cells between segments and eighths at the top, with the group's total in its top cell. Bar i of each group is series i, colored fromTheme.ChartSeriesunless it has its own style.Maxdefaults to the largest bar, or the largest total when stacked. BarChartBuilder.Legend(position): a legend of the groups' series (the first group's bar labels), in a corner asChart's.Axis.LabelCount(default 3) andAxis.Formatter, which formats the default labels instead ofLabelFormat.- Time axes:
Axis.Time(format)labels values as local times (seconds since the Unix epoch) andAxis.Duration(format)as durations in seconds, with-before negatives.ChartTime.ToValueandFromValueconvert times and durations to and from chart values. Axis.Ticks:┬on the X axis and┤on the Y axis at each label.Axis.Grid: dotted lines (┈ ┊ ┼) across the plot at each label, behind the data, except beside the other axis.GraphType.Area: a line with the dots under it filled down to the bottom of the plot.- Example:
gin the Charts demo cycles the requests chart between single, grouped and stacked bars (GET, POST and DELETE), the line chart's X axis is the last 10 s as a duration axis with ticks and a Y grid, andatoggles areas. docs/SPEC-1.x.md§19: the chart extras spec.
[1.13.0] - 2026-11-03
MiniTui 1.13: trees you can filter, reveal a node in, check nodes in, and load in the background.
Added
TreeState.Reveal(path): expands the nodes on a path from a root (raisingExpandedfor each one that opens), selects the last and scrolls it into view. Returns false, changing nothing, when the path doesn't match the tree or the filter hides its node.TreeState.Filter: shows only the nodes a predicate matches and their ancestors, drawn open, searching collapsed nodes too. ← and the expander still close and open rows while filtering, without changing the expanded nodes; clearing the filter selects the nearest node still shown. Type-ahead, the keys and the title's count work on the filtered rows.TreeState.Checkboxes: a[✓],[ ]or[-]box on each row, toggled with Space or a click on it. Checking a node checks everything under it; a parent is checked when all its children are and partly checked when some are.IsChecked,SetChecked,CheckedNodes(checked leaves and fully checked parents, in tree order) andCheckedChanged.TreeState.Async(loadChildren, text, comparer): children loaded by aTaskwhen a node first expands, with "Loading…" after its label meanwhile, cached untilReload(node). A failed load shows its message on the row and collapses the node; expanding again retries. Loads run throughApp.RunTask.RevealAsync(path)loads the nodes on a path, then reveals it.- Example: the Tree demo has a filter field above the tree, reveals
src/MiniTui/App/App.cswith Ctrl+G, loads folders in the background, and shows check boxes with Alt+X. docs/SPEC-1.x.md§18: the tree extras spec.
[1.12.0] - 2026-11-03
MiniTui 1.12: double- and triple-click, and hover for rows, buttons, tabs and menus.
Added
- Double- and triple-click: the app counts quick presses of the same button in the same cell and sets
MouseEvent.ClickCount(1, 2 or 3) on each press, beforeApp.OnMouseand the components see it.App.WithDoubleClickTime(interval)sets the time between presses (500 ms by default, 100 ms to 2 s). Windows' ownDOUBLE_CLICKrecords are counted the same way.ComponentHarness.DoubleClick(x, y). - A double-click selects the word under the pointer in text fields, text areas and command lines (a run of letters, digits and
_, of other symbols, or of spaces), and a triple-click selects the line (all of a text field). SelectionState.Activated: raised when a list or table row is double-clicked. A double-click on a tree row does what Enter does: a leaf raisesTreeState.Activated, a parent expands or collapses. A double-click on a tree's expander toggles it once.- Example: double-clicking a log row in the Panes demo moves focus to its details.
- Hover:
App.WithHover()turns on all-motion mouse reports (?1003; conhost's moves without a button) and delivers them asMouseAction.Move, routed toApp.OnEvent,App.OnMouseand the component under the pointer without focusing it. Queued moves are coalesced, and a move draws a frame only when the hovered component changes, something handled it, or the hovered components drew with the pointer.App.PointerPosition. RenderContext.Hover: the pointer, for the component under it and its ancestors (null under an overlay or without hover).Hover(position)on the list, table, tree, button, checkbox and tab bar builders patches the newTheme.Hoverover the row, widget or tab under it (never over the selection).Theme.Hoveris a subtle background in the dark and light themes and an underline in the default and monochrome themes.- With hover, popup menus, the command palette and the filterable select's list select the item under the pointer, and while a menu bar menu is open, moving over another title switches to it; with none open, the title under the pointer shows hover.
- Example: hover is on, in the demo list, the menus and the demos' lists, tables, trees, buttons, checkboxes and tabs.
docs/SPEC-1.x.md§17: the double-click and hover spec.
Changed
- The SGR and X10 decoders and the Windows input translator report motion without a button as
MouseAction.Moveinstead of dropping it; the app drops moves unlessWithHoveris on. - The second press of a double-click in a text field or text area selects a word instead of placing the cursor again.
[1.11.0] - 2026-11-03
MiniTui 1.11: the text area as an editor, with Tab to indent, auto-indent, line numbers, a scrollbar and find.
Added
- Text area editor features:
TextAreaState.TabIndents(Tab inserts spaces to the next indent stop, or indents every selected line; Shift+Tab outdents; Backspace in the indentation deletes back to the previous stop; Tab no longer moves focus out of the text area),AutoIndent(Enter starts the new line with the current line's indentation) andIndentSize(the indent stop width, 1–8, also used to expand tabs; 4 by default). Each indent or outdent is one undo step. TextAreaBuilder.LineNumbers(): a gutter of line numbers, the cursor's line highlighted. Clicking in it moves the cursor to the start of that row.- Find in text areas: Ctrl+F opens a find bar on the text area's last row, starting with the selection (within a line) or the last find text. Matches are found as you type, from where the search started; Enter, ↓ and F3 go to the next, Shift+Enter, ↑ and Shift+F3 to the previous, wrapping; Esc closes the bar and keeps the match selected. Smart case: a find text with an uppercase letter matches exactly, otherwise case is ignored. With the bar closed, F3 and Shift+F3 find again.
TextAreaState.FindEnabled,IsFinding,FindText,MatchCount,MatchIndex,OpenFind,CloseFind,FindNextandFindPrevious. Theme.SearchMatch: the other find matches, highlighted while the bar is open; set in every built-in theme.- Example: an Editor demo, a YAML file in a text area with indentation, auto-indent, line numbers, a scrollbar and find.
docs/SPEC-1.x.md§16: the text area as an editor spec.
Changed
- Ctrl+F and F3 (with or without Shift) in a focused text area open find and find again, instead of reaching the bindings. Set
FindEnabled = falseto keep them for the app. - A text area in a block with a right border shows a scrollbar on that border when its text doesn't fit. Dragging the thumb or pressing the track scrolls without moving the cursor.
[1.10.0] - 2026-11-02
MiniTui 1.10: a menu bar, and menus with mnemonics, check and radio items, and submenus.
Added
- Richer menu items in
PopupMenu(and so in context menus): mnemonics (&before a letter inMenuItem.Label, drawn underlined; typing it, or Alt+it, chooses the item, and items sharing one are cycled through), check and radio marks (Checked,Radio), submenus (Submenu, opened beside the item with → Enter or a click, to the left when there's no room, closed with ← or Esc; Tab or a click outside every menu closes them all), andRun, called after every open menu has closed.MenuItem.MnemonicandPopupMenu.Chosen(the chosen item at any depth;Resultis the top-level index). MenuBar,MenuandApp.WithMenuBar(bar)(App.MenuBar): a row of menus on the top line, above the screens and overlays. F10 opens the first menu (anf10"Menu" entry inApp.Keys, listed in help), Alt and a title's mnemonic opens that one after the focus chain andApp.Keys, and a click on a title opens, switches or closes. In an open menu, ← → move between menus (→ on an item without a submenu too), Alt and another title switches, and F10 or Esc closes. AMenutakes a fixed list or a factory run at each opening.MenuBar.Open(index)andIsOpen.- The command palette lists the menu bar's items that have a
Run, with their paths (File › Export) as the detail. - Example: the Command Registry demo's right-click menu has mnemonics and a Sort submenu of radio items, and the example app has a menu bar: File (open any demo, Quit), View (the theme as radio items, the debug overlay as a check item) and Help.
docs/SPEC-1.x.md§15: the menu bar and richer menus spec.
Changed
- A
&in aMenuItemlabel now marks a mnemonic and isn't drawn; write&&for a literal&(a&before a space or at the end stays as it is). - A click outside a menu that was opened from another menu closes both, unless it lands on the other menu.
[1.9.0] - 2026-11-02
MiniTui 1.9: split panes with draggable dividers, draggable scrollbars, and tables sorted by clicking their headers.
Added
SplitState,SplitPane,SplitBuilderandWidget.Split(state): two panes side by side or stacked, with a one-cell divider the user drags with the mouse or moves with Alt+Shift+arrows (HandleKey,MoveBy). Sized byRatio, or with one pane at a fixed size in cells (SplitState.Fixed,FixedSize,FixedPane) for sidebars;MinFirstandMinSecondlimit how small the user can make each pane;Changedfires when they move it. Splits nest.- Scrollbar dragging: in lists, tables, trees and paragraphs, dragging the scrollbar's thumb scrolls, and a press on its track pages towards it. The selection stays where it is.
- Sortable tables:
TableBuilder.Sortable(columns)makes a header click callSelectionState.ToggleSort(column)(ascending on a new column, then flipping), andSelectionState.SortChangedtells the app to re-sort its rows. - Example: a Panes demo with a fixed sidebar of log sources, a sortable log table and a details pane in nested splits.
docs/SPEC-1.x.md§14: the split panes, scrollbar dragging and sortable tables spec.
Changed
- A mouse release goes to the component the press hit, as drags do, instead of the one under the pointer.
- A press on a list, table, tree or paragraph scrollbar scrolls instead of doing nothing.
- The wheel over a list, table or tree scrolls the view by a row instead of moving the selection, and scrolling (the wheel or the scrollbar) keeps the selection. Lists, tables and trees scroll the selection back into view when it moves: a key, a click, or setting
Selected.
[1.8.0] - 2026-11-02
MiniTui 1.8: a command palette that finds and runs any key binding or command, fuzzy matching, and a select you can type into.
Added
CommandPaletteandOverlayStack.ShowPalette(commands): a searchable list of everything the app can do. By default (CommandPalette.ItemsFor) it lists the key bindings in scope, as help does but including hidden ones, with their groups and keys, then the commands and subcommands of an optionalCommandRegistry. Typing filters them with fuzzy matching and bolds the matched letters; ↑ ↓ PgUp PgDn Ctrl+Home Ctrl+End move; Enter or a click runs the item after the palette closes; Esc or a click outside closes it. A command with a usage opens the command line with its name typed. The binding that opened the palette isn't listed.PaletteItem(Label,Run,Detail,Hint) for lists of your own. Not bound by default.Fuzzy.Match(pattern, text)andFuzzyMatch: the palette's matcher, ignoring case and spaces in the pattern, favouring prefixes, word starts and runs, with the matched positions for highlighting.SelectState.Filterable: the dropdown gets a filter line that narrows the options as you type, and a letter typed on the closed field opens it with that letter in the filter.- Example: Ctrl+P opens the palette everywhere (on the Command Registry screen with its commands too), and the Form demo has a filterable Country select.
docs/SPEC-1.x.md§13: the command palette and filterable select spec.
[1.7.0] - 2026-11-02
MiniTui 1.7: the app asks the terminal about itself, picks a light or dark theme to match, and turns on keyboard protocols for keys like Ctrl+Enter.
Added
- Terminal queries: at startup and after a resume, the app asks the terminal for its default colors (OSC 10 and 11), its name (XTVERSION), its keyboard protocol support and its Primary Device Attributes. The input decoder reads the replies and never turns them into keys.
App.TerminalInfo(Answered,Background,Foreground,IsDark,Keyboard,Name) holds what the terminal said, andTerminalInfoEventreports each change.WithTerminalQueries(false)orMINITUI_QUERIES=0turns them off. - Keyboard protocols: where the terminal supports one, the app turns on the kitty keyboard protocol, else xterm's modifyOtherKeys (each only after the terminal's reply says it's supported), or win32-input-mode on Windows (Windows Terminal). Ctrl+Enter, Shift+Enter, Ctrl+Tab, Ctrl+I, Ctrl+M, Ctrl+[ and Ctrl with digits and punctuation then arrive as themselves, and bindings such as
ctrl+entermatch them; every key the classic encoding can express decodes exactly as before, and Esc no longer waits for a timeout under the kitty protocol. The protocol is turned off on every path that restores the terminal.KeyboardProtocolandTerminalInfo.Keyboardsay which is in use;App.WithKeyboardProtocol(false)orMINITUI_KEYBOARD=legacyturns them off. - Example: the Form demo also submits with Ctrl+Enter, and the Input demo suggests keys to try.
Theme.DarkandTheme.Light: themes with their own RGB colors, the same in every terminal whatever its palette. They set the text and background colors, and the app fills the screen with the background (Theme.Defaultstill uses the terminal's palette and background).App.WithTheme(dark, light)picks one from the terminal's background (orCOLORFGBG); the first frame waits up to 150 ms for the reply.- The debug overlay shows light or dark and the terminal's name. The Input demo shows what the terminal answered, the example app uses
Theme.DarkorTheme.Lightby the terminal's background, and the Widgets demo'stcycles auto, default, dark, light and monochrome. docs/SPEC-1.x.md§12: the terminal queries and keyboard protocols spec.
Changed
- In a terminal with a keyboard protocol, Ctrl+Enter, Alt+Enter, Ctrl+Tab and Ctrl+I arrive as themselves, so
enterandtabbindings no longer match them. The command line, dialogs, menus and trees don't treat Ctrl+Enter or Alt+Enter as Enter, and Ctrl+Tab and Alt+Tab don't move focus. Actrl+iorctrl+mbinding no longer matches the Tab or Enter key. - Widgets that clear an area before drawing (dialogs, toasts, buttons, the status bar) clear it with the theme's text style, so a theme with a background keeps it there. With
Theme.Defaultnothing changes. - In a real terminal, the app now writes its queries at startup and after a resume. Apps with their own
OnEventalso seeTerminalInfoEvent.
[1.6.0] - 2026-11-01
MiniTui 1.6: a tree view over your own nodes, with lazily read children.
Added
Widget.Tree(state),TreeBuilder<T>,TreeState<T>,TreeRow<T>andTreeNodeEventArgs<T>: a scrollable tree over the app's own nodes. The state asks for children only for expanded nodes and recomputes the visible rows at each frame;HasChildrenshows an expander before children are loaded, andExpandedis where lazy loaders start. Keys: ↑ ↓ PgUp PgDn Home End, → expands or moves to the first child, ← collapses or moves to the parent, Enter toggles a parent or raisesActivatedon a leaf, and letters jump by name (type-ahead). The mouse selects rows, toggles on the ▸/▾ expander and scrolls.Expand,Collapse,Toggle,ExpandAll,CollapseAll,IsExpanded,Rows,SelectionChangedandCollapsed; cycles are shown as leaves.Guides()draws │ ├─ └─ lines; in conhost the expanders are+and-.- Example: a Tree demo browsing the file system from the current directory, a directory at a time.
docs/SPEC-1.x.md§11: the tree view spec.
[1.5.0] - 2026-11-01
MiniTui 1.5: text selection, copy and paste, undo and redo, and selecting with the mouse.
Added
- Text selection in
TextFieldStateandTextAreaState(so inTextField,TextAreaandCommandLine): Shift with the movement keys, Ctrl+A to select all; typing, pasting and the delete keys replace the selection, and ← / → collapse it.SelectionAnchor,SelectionStart,SelectionEnd,HasSelection,SelectedText,Select,SelectAllandClearSelectionfor code. Selected text is drawn withTheme.Selection, orTheme.SelectionUnfocusedwhen the widget isn't focused. - Copy, cut and paste: Ctrl+C (with a selection), Ctrl+X and Ctrl+V, and
Copy()/Cut()on both states.Clipboard.SetTextandClipboard.GetText: the system clipboard on Windows, OSC 52 on Unix terminals, and an in-app clipboard that always works. - Undo and redo: Ctrl+Z and Ctrl+Y,
Undo(),Redo(),CanUndoandCanRedoon both states. Typing undoes a word at a time; up to 100 steps are kept. TextPositioncompares (IComparable<TextPosition>,<,>,<=,>=).MouseAction.Drag: withWithMouse, the terminal also reports the pointer moving while a button is held (?1002instead of?1000;MOUSE_MOVEDrecords on Windows). Drags go to the component the press hit, even outside it, and are clamped to the screen.TextFieldState.HandleMouse: a click places the cursor. In both text states, Shift+click extends the selection and dragging selects, scrolling past the edges.- Example: the Form demo lists the selection and clipboard keys, and its fields select with the mouse.
docs/SPEC-1.x.md§10: the text selection and clipboard spec.
Changed
- Ctrl+C copies when the focused component captures text and its field has a selection; otherwise it still quits.
- In text fields and text areas, Ctrl+A, Ctrl+X, Ctrl+V, Ctrl+Z and Ctrl+Y are handled by the field and no longer reach key bindings. With
WithSuspendKey, Ctrl+Z suspends only while focus isn't on a text field or area. TextFieldState.InsertandTextAreaState.Insertreplace the selection, if there is one.- With
WithMouse,App.OnMouseandOnEventalso see drag events.
[1.4.0] - 2026-10-31
MiniTui 1.4: canvas drawing, line and bar charts, and a debug overlay.
Added
Widget.Canvas(),CanvasPainterandMarker: draw points, lines, rectangles, circles and text in app coordinates (y grows upward) at up to 2×4 dots per cell with braille, or with half blocks, blocks or dots. Lines are clipped at the edges; where two shapes share a cell, the later one's style wins.Widget.Chart(),Dataset,Axis,GraphTypeandLegendPosition: line and scatter plots of one or more datasets, with axes, labels, axis titles and a legend in a corner. Axis bounds come from the data unless set, and small areas drop the titles, then the labels, then the axes.Widget.BarChart()andBar: labelled bars in eighths of a cell, vertical or horizontal, with each bar's value in or after it.Theme.ChartSeries: the styles of datasets without their own (cyan, yellow, magenta, green, blue, red; one plain style inTheme.Monochrome).- In conhost, canvases and charts draw braille as half blocks.
- Example: a Charts demo with a live line chart (
mcycles the marker,stoggles scatter), a bar chart (dtoggles the direction) and an animated canvas. docs/SPEC-1.x.md§8: the Canvas and charts spec.- A debug overlay:
App.WithDebugKey()(F12 by default, off unless asked for),App.ShowDebug, andMINITUI_DEBUG=1to turn both on without code changes. It outlines each component's area (the focus chain and the focused component in their own colors) and shows a panel with the frame time, cells and bytes per frame, the terminal's size and capabilities, the focus chain and the component under the pointer. App.LastFrameandFrameStats: the last frame's number, render and flush times, cells changed, bytes written and whether it was a full redraw.- Example: F12 toggles the debug overlay.
docs/SPEC-1.x.md§9: the debug overlay spec.
[1.3.0] - 2026-10-30
MiniTui 1.3: a multi-line text area, popup menus and dropdown selects.
Added
Widget.TextArea()andTextAreaState: multi-line text input. The text field's editing keys work across lines, plus Enter (new line), ↑ ↓ by wrapped row with a remembered column, PgUp/PgDn, Home/End by row then by line, and Ctrl+Home/End. Lines wrap at words by default (Wrap(WrapMode.Char)orWrapMode.Noneto scroll sideways), and the view scrolls to keep the cursor visible.Pastekeeps line breaks, tabs become spaces, andHandleMouseplaces the cursor on a click and scrolls with the wheel.TextPositionis the cursor type.PopupMenuandMenuItem: a menu overlay placed below an anchor (or above it, when there's more room), with right-aligned hints, separators and disabled items. ↑ ↓ (wrapping), PgUp/PgDn, Home/End and type-ahead move the selection; Enter or a click chooses; Esc, Tab or a click outside dismisses.app.Overlays.ShowMenuAsync(items, anchor)answers the chosen index, or null.Widget.Select()andSelectState: a dropdown field that opens aPopupMenuof its options below the field, as wide as it. Enter, Space, ↑ ↓, Alt+↓, F4 or a click opens it;Changedfires when the user picks a different option.Overlay.Framed(false: the overlay draws its own border) andOverlay.CloseOnClickOutside(true: a press outside closes it and goes nowhere else). Both default to the old behavior.- Example: the Form demo has a Notes text area and a Plan select; the Command Registry demo has a context menu on its notes (right-click or Shift+F10).
docs/SPEC-1.x.md§6 and §7: the TextArea, PopupMenu and Select specs.
[1.2.0] - 2026-10-30
MiniTui 1.2: Windows reads input the way Linux and macOS do (with bracketed paste), opt-in mouse support everywhere, and suspend and resume.
Added
- Windows reads the console in VT input mode and decodes keys with MiniTui's own decoder, as Linux and macOS do since 1.1. Waiting for input on Windows no longer wakes every 5 ms, and a window resize is noticed at once.
- Bracketed paste on Windows: in Windows Terminal a paste arrives as one
PasteEvent. Conhost has no bracketed paste, so there Ctrl+V pastes the clipboard's text as aPasteEvent. - Mouse support, opt-in with
App.WithMouse(): SGR (and X10) mouse reports on Linux and macOS and in Windows Terminal, and console mouse records in conhost. AMouseEventgoes toApp.OnMouse, then toComponent.HandleMouseon the component under the pointer and its ancestors; a press focuses that component first.RenderContext.Render(child, buf, rect)renders a child and records itsComponent.Areafor hit testing. SelectionState.HandleMouse(lists and tables: the wheel moves the selection, a click selects a row),ScrollState.HandleMouse(the wheel scrolls text) andTabBarState.HandleMouse(a click selects a tab).ConfirmDialoganswers clicks on its buttons;MessageDialogandHelpOverlayscroll with the wheel.ScriptedInputSource.EnqueueMouse, andComponentHarness.Click,ScrollandMouse, for tests.- Example: the mouse is on; clicks focus fields, toggle the checkbox, select rows and tabs and open menu items, and the Input demo shows mouse events.
docs/SPEC-1.x.md§4: the mouse and Windows input spec.App.Suspend(action)gives the terminal back whileactionruns, for running$EDITORor another program in it: input stops, mouse reporting and bracketed paste turn off, the terminal mode is restored and the alternate screen left, then all of it comes back and the app redraws. Ctrl+C during the action stops the child program, not the app. Works on every platform.App.SuspendProcess()stops the process and returns to the shell with the terminal restored (Linux and macOS);fgresumes the app, which redraws.App.WithSuspendKey()makes an unhandled Ctrl+Z do that; it's off by default.ResumeEvent, sent toApp.OnEventwhenever the app takes the terminal back.- Example: Ctrl+Z suspends the demos, and Ctrl+E in the Input demo edits a file in
$VISUALor$EDITOR. docs/SPEC-1.x.md§5: the suspend and resume spec.
Changed
- On Windows, keys decode by the same rules as on Linux and macOS: Esc is recognized after up to 50 ms, and Ctrl+Enter, Ctrl+Tab and Ctrl+digits arrive without the Ctrl.
MINITUI_INPUT=consolerestores the 1.0 input path on Windows too. - On Windows, pasted text no longer arrives as keys in Windows Terminal, and Ctrl+V in conhost pastes instead of being a key. A component that edits text needs
HandlePasteto accept pastes, as on Linux and macOS since 1.1. - On Linux and macOS, a
SIGTSTPsent to a running app from outside (kill -TSTP) restores the terminal before the process stops, and the app redraws when it's continued. Before, the process stopped with the terminal still in raw mode on the alternate screen.
[1.1.0] - 2026-10-30
MiniTui 1.1: raw terminal input and bracketed paste on Linux and macOS. Windows input is unchanged.
Added
TerminalInputSource, the new default input source. On Linux and macOS it reads the terminal in raw mode and decodes keys with MiniTui's own decoder: Esc, Alt and Alt+Shift letters, Ctrl+letters, Shift/Ctrl/Alt arrows, Home/End/PgUp/PgDn/Insert/Delete, Shift+Tab and F1–F12 decode the same way in every terminal. On Windows it reads keys as before.- Bracketed paste on Linux and macOS: a paste arrives as one
PasteEventinstead of a key per character.Component.HandlePasteandApp.OnPastereceive it,TextFieldState.Pasteinserts it as one line, andCommandLineOverlayhandles it.ScriptedInputSource.EnqueuePasteandComponentHarness.Pastescript pastes in tests. - The
MINITUI_INPUT=consoleenvironment variable selects the 1.0 input path (ConsoleInputSource) without recompiling. - Example: an Input demo that shows how each key and paste was decoded, and the Form screen accepts pastes.
docs/SPEC-1.x.md: the post-1.0 roadmap and the raw input and bracketed paste specs.
Changed
- On Linux and macOS, Esc is recognized after up to 50 ms (
TerminalInputSource.EscapeTimeout), and Ctrl+Z, Ctrl+, Ctrl+S, Ctrl+Q, Ctrl+V and Ctrl+O reach the app as keys instead of being handled by the terminal. - Waiting for input no longer wakes every 5 ms on Linux and macOS.
- On Linux and macOS, pasted text no longer arrives as keys, so a pasted line break doesn't submit and pasted letters don't fire key bindings. A component that edits text needs
HandlePaste(for example=> State.Paste(text)) to accept pastes; a paste nothing handles is dropped. On Windows and withMINITUI_INPUT=console, pastes still arrive as keys.
Fixed
- A fast burst of keys (about 1,000 arrow keys at once) could decode as a stray Esc and leave the current screen. Key sequences split across reads are now reassembled.
[1.0.0] - 2026-10-29
MiniTui 1.0: an event loop with async support, constraint layout, a themed widget set, and screens and components for larger apps. It removes the 0.7 APIs that 0.8 and 0.9 marked obsolete (they were never released separately); docs/MIGRATION-0.7-to-1.0.md maps each one to its replacement.
Added
docs/MIGRATION-0.7-to-1.0.md.- The public API is tracked with
Microsoft.CodeAnalysis.PublicApiAnalyzers(PublicAPI.Shipped.txt); changing it fails the build until the file is updated. - CI requires at least 85% line coverage of
src/MiniTui. - The publish workflow also pushes to nuget.org with trusted publishing (no stored API key) when the repository has a
NUGET_USERvariable. - Components (§9):
Component(keys,HandleKey,CapturesText, children,Lifetime,Load, mount/activate hooks,Component.From),RenderContextandFocusScope. ScreenandApp.Screens(ScreenStack:Push,Pop,Replace,PopTo). With screens, the app draws the top screen and a status bar with key hints and a breadcrumb (WithChrome), and quits when the last screen is popped (QuitWhenEmpty).OverlayandApp.Overlays(OverlayStack:Show,Close,ConfirmAsync,ShowMessageAsync,ShowHelp), with the built-inConfirmDialog,MessageDialog,HelpOverlayandCommandLineOverlay. Overlays are modal and work in render-callback apps too.- Key routing (§9.4): the
OnKeyhook, then the top overlay, or the focus chain innermost first, thenApp.Keys(with?for help andctrl+cbuilt in), then Esc pops the screen. Text-capturing components keep single-character bindings from firing while you type. App.Toasts, drawn by the app over everything else.ComponentHarnessfor testing screens:Press,Type,RunUntilIdle,Lines,CellAt.Theme(Default,Monochrome,Current) andApp.WithTheme. Every widget draws with the theme; backgrounds are left to the terminal except for bars, buttons and selections. Apps useMonochromeautomatically when the terminal has no colors (NO_COLOR).- Widget builders:
Widget.Block(Borders),Paragraph(WrapMode,Paragraph.MeasureLines),Table(Row, column constraints, sort markers, only visible rows formatted),Scrollbar,Gauge,Sparkline,Spinner(SpinnerFrames),Toasts(ToastQueue,ToastLevel),TextField,CommandLine,TabBar,ButtonandCheckbox. Listbuilder:Items(list, format)(only visible items are formatted),State(SelectionState),HighlightSymbol,Block,StyleandHighlightStyle.- State classes that handle their own keys:
SelectionState,ScrollState,TextFieldState,CommandLineStateandTabBarState. - Text editing (§8.4): any non-control character including surrogate pairs, cursor movement and deletion by grapheme cluster, Ctrl+←/→ by word, Ctrl+Backspace/Ctrl+W, Ctrl+U and Ctrl+K, and horizontal scrolling by display width. The text field shows the terminal cursor.
KeyMap(Add,Handle,Hints,HelpLines) andKeyBinding(Parse,Matches).- Spinners and visible toasts make the app tick while they're drawn, even with
WithTickRate(null). - In conhost (the classic Windows console, whose fonts have no fallback), widgets draw only glyphs its fonts have: gauges and sparklines use half and full blocks, the braille spinner becomes the line spinner, and rounded borders become single. Windows Terminal and other terminals keep the full glyphs.
- Example: Services (the §3.1 async program against a fake HTTP API) and Widgets (every widget, theme toggle, Unicode panel) demos.
Constraint(Length,Percentage,Ratio,Min,Max,Fill(weight)),FlexandDirectionfor layouts.Layout.Add(name, Constraint),Spacing,Margin,FlexandLayout.Split(area, direction, constraints).- Event loop (§6): keys, ticks, resizes, posted actions and redraw requests go through one queue, handled on the thread that called
Run(). Keys are read on a background thread. App.OnKey,OnEvent(KeyEvent,ResizeEvent,TickEvent),OnError(ErrorAction.Continue/Quit/Rethrow) andOnSetup(Action<App>).App.Post,RequestRedrawandQuitare thread-safe.App.RunTaskstarts async work whose continuations resume on the UI thread (aSynchronizationContextis installed while running); its exceptions go toOnError.App.RunAsync,Stopping(cancelled on quit),SizeandLog.App.WithTickRate(default 250 ms;nullredraws only on events),WithTimeProvider,WithBackend,WithInput,WithColorModeandCaptureConsoleOutput.Loadable<T>andLoadStatus: async load state with stale values, last-load-wins, cancellation andMatch/Switch.LogBufferandLogEntry. While an app runs,Console.OutandConsole.Errorgo intoapp.Log; error lines are written to stderr on exit.InputRouter.Global,InputRouter.RefreshandAdd(name, handler, capturesText).ITerminalBackendandCellUpdate.Terminal(ITerminalBackend)draws through any backend.AnsiBackend, the new default: one buffered UTF-8 write per frame, synchronized output (?2026), cursor and SGR tracking so only changes are emitted, colors downsampled to the detectedColorMode. An unchanged frame writes zero bytes and allocates nothing.LegacyConsoleBackend, used automatically on Windows consoles where VT can't be enabled (16 colors, no alternate screen). Line wrap is turned off while it runs, so the bottom-right cell doesn't scroll the screen.TestBackend, an in-memory screen for testing apps (Lines,CellAt,CursorPosition,CursorVisible).IInputSource,ConsoleInputSourceandScriptedInputSource.TerminalNotSupportedException, thrown when stdin or stdout is redirected.TerminalLease: terminals share a process-wide, ref-counted session.tests/MiniTui.TestsxUnit project with smoke tests, included insrc/MiniTui.sln.- CI workflow (
.github/workflows/ci.yml): build with-warnaserror, test, and pack dry run on Ubuntu, Windows and macOS; code coverage collected on Ubuntu. - Symbol packages (
.snupkg), SourceLink repository metadata and embedded untracked sources. Color(reset, 16 named, 256-indexed and RGB,FromHex), with implicit conversion fromConsoleColor.ColorModeandColorCapability.Detect(), which readsNO_COLOR,COLORTERM,WT_SESSION,TERM_PROGRAMandTERM. Colors are downsampled to the detected mode at encode time.StyleandModifiers. A style is a patch: unset colors keep the cell's existing ones.Span,Line,TextandAlignmentfor styled text.UnicodeWidth(Of,OfGrapheme,Graphemes,Truncate), with East Asian Width and emoji tables generated from Unicode 16.0 bytools/GenWidthTable.cs.Buffer.SetString,SetLine,SetStyle,SetCursor/CursorRequestandToLines.Rect.IsEmpty,Intersect,Clamp,Inner(Margin),Rows()andColumns(), and theMargintype.
Changed
- Version 1.0.0.
- The app draws a frame only when something happened (input, tick, posted action, resize, redraw request) instead of every 16 ms.
OnKeysees every key first; keys it doesn't handle are routed to overlays, screens andApp.Keys(§9.4).- An app started from inside another app receives the keys the outer app hadn't handled yet, and returns the ones it didn't handle when it exits.
OnSetupruns on the UI thread after the terminal is entered, so it canPostand start loads.App.RunTaskruns its work under the app's synchronization context even when called from another context, so its awaits resume on the UI thread.- On Windows, VT processing is enabled and the output encoding is set to UTF-8 while an app runs; both are restored afterwards.
Layoutuses a deterministic constraint algorithm (§7.2). The indexer builds the layout if needed, and an unknown region name throwsKeyNotFoundExceptionnaming it. Adding two regions with the same name throwsArgumentException.Cellis nowCell(string Symbol, Color Fg, Color Bg, Modifiers Modifiers): one grapheme cluster per cell, and wide characters are followed by a continuation cell.Cell.Fg/BgareColorinstead ofConsoleColor.Cell.Emptyuses the terminal's default colors instead of gray on black (D10).Buffer.DrawHLineandDrawVLinetake aStyleinstead of aConsoleColor, and keep the existing background.Buffer.SetandFillRectstore a symbol that starts with a zero-width character (a lone combining mark, ZWJ, VS16) as a space, andSetStringskips such graphemes, so they can't merge with the neighbouring cell on screen.- Widgets draw with the theme, measure text by display width, end truncated text with
…, and never throw on small or off-screen areas. - An unfocused list or table shows its selection with the
SelectionUnfocusedstyle instead of hiding it (D11). StatusBardraws on the area's row when the area is one row high, and truncates the left text when both sides don't fit.Rect.SplitHorizontal/SplitVerticalclamp the split offset, so neither part has a negative size.- The Example is one app with a stack of screens, with no nested
App.Run, and adds the Services and Widgets demos. MiniTui.csprojbuilds withTreatWarningsAsErrors,AnalysisLevel=latest-recommended, missing XML docs (CS1591) as errors,IsAotCompatible, and deterministic/CI builds.- The publish workflow runs tests before packing and takes the package version from the tag (
v1.2.3→1.2.3). Terminal.Dispose()callsGC.SuppressFinalize.- Source files moved into
App/,Core/,Text/,Layout/,Terminal/,Components/,Input/andWidgets/(no API change).
Removed
See docs/MIGRATION-0.7-to-1.0.md for the replacement of each.
App.RunWhile,App.WithFrameDelay,App.UseRouter, and the staticInputclass (Poll,Read,IsKey,IsChar,IsQuit).InputRouter,FocusManagerandInputHelpers.ListState,TextInputState,CommandInputState,CommandInputResult,TabsStateandConfirmState.- The static
Widgetsclass (now internal),Widget.Modal,Widget.Confirm,Widget.CommandInput,Widget.Tabs,ModalBuilder,ConfirmBuilder,CommandInputBuilder,TabsBuilderandListBuilder.State(ListState). Buffer.DrawText,DrawTextCentered,DrawBox,DrawBoxWithTitleandDrawProgressBar.Layout.Add(name, int)andLayout.AddFill(name).Cell(char, ConsoleColor, ConsoleColor),Cell.CharandCell.From(char).Command, which had no public members, is internal.- Stale duplicate solution file
src/MiniTui/MiniTui.sln(D19).src/MiniTui.slnis the canonical solution.
Fixed
LICENSEcontained the AGPL-3.0 text while the package and README declared MIT; it is now the MIT license (copyright Discover Edge LLC), and the package lists Discover Edge LLC as its author.- Ctrl+C is delivered as input (
TreatControlCAsInput) and quitsApp.Run, instead of killing the process with the terminal left in TUI mode; Ctrl+C sent as\x03is recognised too (D1). - Apps run on the alternate screen and never call
Console.Clear(), so the user's screen and scrollback are preserved (D2). - A frame is one write instead of thousands of console calls (D5).
- The terminal is restored on SIGTERM, SIGHUP, SIGQUIT, process exit and unhandled exceptions.
- Nested apps share the terminal session: the inner app doesn't clear the screen, and the outer app redraws fully when it exits, without
app.Invalidate()(D17). Layoutnever gives a region a negative size when fixed sizes exceed the space (they shrink proportionally), and the integer-division remainder is distributed instead of dropped: three fills in 10 rows are 4/3/3 (D7).- All pending keys are handled before the next frame (up to 256 per frame), so pasted text and key repeat are no longer throttled to one key per frame (D3).
- Keys are no longer dropped: each key goes through one path (
OnKey, then routing) exactly once (D4). - A dialog opened from code (for example by an HTTP error) is modal and drawn at the next frame, without a key press (D12).
- An idle app no longer redraws every 16 ms (D16).
- README license section now states MIT, matching
LICENSEand the package metadata (D18). - README box-style examples show the actual box-drawing glyphs (D18).
- Drawing never throws for any coordinates or
Rect, andRect.Shrinknever returns a negative size; an over-shrunk rect collapses to its center (D6). No widget throws on narrow areas. - Wide characters and surrogate pairs are no longer split. Text, truncation and centering use display width, including in the existing widgets (D9).
- Control characters (including ESC) in drawn text are replaced with U+FFFD, so data can't inject escape sequences (D14).
- With
NO_COLOR, an explicitWithTheme(Theme.Default)also becomesTheme.Monochrome, so the selection stays visible. - Text input accepts any non-control character, including accented letters, CJK and emoji (D8).
- Widgets no longer paint a black background over light or custom terminal themes (D10).
- Command history is an immutable list that copies its seed, so copies of a state never share or change each other's history (D13).
- Tables size columns with constraints instead of equal widths, show rows beyond the area by scrolling, and keep the selected row visible (D15).
[0.7.0]
Added
- Tabs widget for multi-panel UIs.