20 KiB
cl-tui Roadmap
- The Roadmap
- File Update Checklist
- v0.1.0: Layout Engine
- v0.2.0: Renderables — Box and Text
- v0.3.0: Rendering Engine
- v0.4.0: Theme Engine
- v0.5.0: Text Input + Keybinding System
- v0.6.0: ScrollBox + TabBar
- v0.7.0: Select — Dropdown + Fuzzy Filter
- v0.8.0: Markdown + Code + Diff Rendering
- v0.9.0: Dialog System + Toast
- v0.10.0: Mouse Support
- v0.11.0: Plugin / Slot System
- v1.0.0: Complete Framework
- Neurosymbolic Phase Reference
The Roadmap
Each phase is one minor release. Phases ship in dependency order — each depends on the components from prior phases. The layout engine ships first because everything else builds on it.
Feature releases increment the minor version (v0.X.0). Bugfix releases increment the patch version (v0.X.Y).
File Update Checklist
When a version ships:
ROADMAP.org— mark item DONE, update LOGBOOK timestampREADME.org— update Status linecl-tui.asd— update version string
v0.1.0: Layout Engine
Yoga Flexbox backend wrapped in a Common Lisp API. This is the foundation — every component after v0.1.0 uses the layout engine for positioning.
DONE Yoga FFI binding
- State "DONE" from "TODO" [2026-05-11 Mon]
- Load the Yoga shared library via CFFI
- Define foreign types for
YGNodeRef,YGSize,YGValue,YGDirection,YGFlexDirection,YGAlign,YGJustify,YGWrap,YGPositionType,YGOverflow,YGDisplay,YGEdge - Bind core functions:
node-new,node-free,node-style-set-*,node-layout-get-*,calculate-layout - ~100 lines CFFI
TODO Layout primitives
(make-layout-node)— wraps aYGNodeRefin a CLOS object(layout-node-set-dimension node width height)— sets width/height in points(layout-node-set-flex node &key grow shrink basis)— flex properties(layout-node-set-direction node :row | :column | :row-reverse | :column-reverse)(layout-node-set-wrap node :nowrap | :wrap | :wrap-reverse)(layout-node-set-align node :flex-start | :center | :flex-end | :stretch | :baseline)(layout-node-set-justify node :flex-start | :center | :flex-end | :space-between | :space-around | :space-evenly)(layout-node-set-padding node &key top right bottom left x y)(layout-node-set-margin node &key top right bottom left x y)(layout-node-set-gap node &key row column)(layout-node-set-position node :relative | :absolute &key top right bottom left)(layout-node-set-border node width)(layout-node-add-child parent child)— builds the tree(layout-calculate root width height)— runs Yoga's calculateLayout, populates each node's computed x/y/w/h- ~200 lines CL
TODO Layout composable API
Convenience macros to build layout trees from CL function calls:
(vbox &key ... children ...)→ column-direction container with children(hbox &key ... children ...)→ row-direction container with children(overlay base child)— absolute-positioned overlay over a relative base(spacer &key grow)— empty flex spacer(layout-render root parent-window)— computes layout then walks the tree, calling each child's render function with its computed x, y, w, h- ~50 lines CL macros
~350 lines total. Dependencies: Yoga shared library, CFFI, croatoan.
FiveAM tests
test-layout-basic— vbox with two children computes correct y positionstest-layout-hbox— hbox with two children computes correct x positionstest-layout-flex— flex-grow distributes space correctlytest-layout-absolute— absolute child positions relative to parenttest-layout-nested— nested vbox/hbox produces correct leaf positions
v0.2.0: Renderables — Box and Text
The first two renderable types that every application uses. A Box draws borders and backgrounds. A Text renders strings with color and style. Together they cover 80% of terminal UI.
TODO Box renderable
(defclass box ...)— renderable with background color, border, title(render-box box window)— draws border (single/double/rounded), fills background, renders title- Border styles:
:single,:double,:rounded - Title alignment:
:left,:center,:right :focusableproperty — renders focused border color when focused- ~100 lines
TODO Text renderable
(defclass text ...)— renderable with content, fg/bg color, wrap mode(render-text text window)— renders text at the layout position, wraps at width- Word-wrap:
:none(truncate) or:word(break at word boundaries) - CJK/emoji character-width aware wrapping
- ~100 lines
TODO Inline text styles
(defclass span ...)— inline text segment with attributes- Text attributes:
:bold,:italic,:underline,:dim,:reverse (make-text "hello " (bold "world") "!")— builds styled text from spans and strings- ~60 lines
TODO Dirty tracking
(mark-dirty component)— flags component and all ancestors(dirty-p component)— returns T if the component needs re-rendering(mark-clean component)— clears dirty flag after render- ~40 lines
~300 lines total. Dependencies: Phase 1 (layout engine).
v0.3.0: Rendering Engine
The pipeline that goes from component tree to terminal output. Handles dirty propagation, incremental rendering (only dirty branches), scissor clipping, and diff-based output.
TODO Component tree → render commands
(render-screen root screen)— entry point: computes layout, walks dirty branches, collects render commands- Render commands are lists:
(:box x y w h bg border title),(:text x y str fg bg attrs) - Each component's
renderfunction returns a list of render commands - ~100 lines
TODO Scissor clipping
(with-scissor (window x y w h) &body body)— clips all render operations to a rectangle- Pushes/pops scissor state so nested containers clip correctly
- ~50 lines
TODO Incremental diff output
*framebuffer*— a 2D array of (char, fg-color, bg-color, attrs) tuples(flush-framebuffer screen)— compares framebuffer to previous frame, writes only changed cells via croatoan(clear-dirty screen)— clears all dirty flags after a successful flush- Croatoan compatibility: uses
add-stringfor unchanged text,clear+add-stringfor changed regions - ~150 lines
~300 lines total. Dependencies: Phase 2 (renderables + dirty tracking).
v0.4.0: Theme Engine
Semantic color tokens, dark/light variants, hex → truecolor resolution, and
built-in presets. Application code references semantic roles (:error, :accent),
never hex values.
TODO Semantic color tokens
(defclass theme ...)— holds a mapping from semantic roles to hex colors- 30+ semantic roles:
:primary,:secondary,:accent,:error,:warning,:success,:info,:text,:text-muted,:background,:background-panel,:background-element,:border,:border-active,:diff-added,:diff-removed,:diff-context,:markdown-heading,:markdown-code,:markdown-link,:markdown-quote,:syntax-keyword,:syntax-function,:syntax-string,:syntax-number,:syntax-comment,:syntax-type - ~120 lines
TODO theme-color
(theme-color theme role)→ returns the croatoan color pair number for the role(themed-add-string window x y str :color :error)— renders text with a theme semantic role- Color pair caching: resolve hex → croatoan
init-coloronce per (fg, bg) pair, reuse - ~40 lines
TODO Built-in presets
8 presets: default (gold), professional, minimal, nord, tokyonight, catppuccin, monokai, gruvbox
- Each preset is a plist:
(:primary "#FFD700" :error "#BF616A" ...) (theme-load :nord)— activates a preset, re-renders dirty- Load from
/.config/cl-tui/themes/<name>.lispfor custom themes - ~80 lines
TODO Dark/light variants
- Each preset defines both
:darkand:lightvariants (theme-set-mode :dark | :light)— switches variant- Auto-detect: read terminal background color (croatoan's background), pick closest variant
- ~50 lines
290 lines total. Dependencies: Phase 2 (renderables), Croatoan's ~init-color~/~color-pair.
v0.5.0: Text Input + Keybinding System
Text input widgets with readline/emacs keybindings. A layered keybinding system that routes keystrokes through global → local → input layers.
TODO TextInput — single-line input
(defclass text-input ...)— single-line input with value, cursor, placeholder(render-text-input input window)— renders text left-aligned, placeholder when empty, blinking cursor- Cursor movement: left/right, home, end
- Insert/delete at cursor position
:on-submitcallback — fires on Enter:max-lengthproperty — prevents input exceeding limit- ~150 lines
TODO Textarea — multi-line input
(defclass textarea ...)— multi-line input with value, cursor (row, column), selection(render-textarea area window)— renders visible lines, cursor, selection highlight- Cursor: up/down, left/right, word-forward/backward, line/home/end, buffer/home/end
- Selection: Shift + navigation extends selection
- Undo/redo stack (configurable depth, default 100)
:on-submitcallback — fires on Enter- ~200 lines
TODO Keybinding system
- Layered keymaps:
:global→:local→:input(input layer takes priority when text input is focused) (defkeymap :global '((:ctrl+p . command-palette) (:ctrl+c,ctrl+d . quit)))- Key format:
:ctrl+p,:alt+f,:shift+tab,(:ctrl+c :ctrl+d)(chord) - Chord sequences: first key starts a timer, second key within timeout dispatches
:leaderkey (defaultCtrl+X) with configurable timeout- Key names normalized from croatoan's
:code-key+:key-nameoutput - ~150 lines
~500 lines total. Dependencies: Phase 3 (rendering engine), Phase 4 (theme).
v0.6.0: ScrollBox + TabBar
Container components. ScrollBox handles content larger than the viewport. TabBar handles horizontal tab navigation.
TODO ScrollBox
(defclass scroll-box ...)— container with vertical/horizontal scroll- Viewport culling: only render children whose y position is within the visible range
- Scroll offset:
:scroll-y,:scroll-xslots - ScrollBy: PageUp/PageDown (viewport height), Up/Down (1 line), Home/End (buffer start/end)
- Scrollbars: vertical and horizontal (single-line, rendered with block characters)
- Sticky scroll: when scrolled to bottom and new content arrives, auto-scroll to show it. When user scrolls up, stop auto-scrolling until they scroll back down.
- ~200 lines
TODO TabBar
(defclass tab-bar ...)— horizontal row of tabs(tab-bar-add tab-bar id title &optional content):active-tabslot — only renders content for the active tab- Tab rendering: highlighted active tab, dim inactive tabs
- Left/Right or Ctrl+PageUp/PageDn to navigate tabs
- ~100 lines
~300 lines total. Dependencies: Phase 3 (rendering engine), Phase 4 (theme).
v0.7.0: Select — Dropdown + Fuzzy Filter
A selection list component — the building block for command palettes, theme pickers, agent selectors, file pickers.
TODO Select
(defclass select ...)— list of options with keyboard navigation:options— list of plists:((:title "Nord" :value :nord :category "Themes") ...)- Categories: options can be grouped. Category headers rendered dim, non-selectable
- Up/Down/Ctrl+P/Ctrl+N to navigate, Enter to select, Esc to dismiss
:on-selectcallback — fires on Enter:filterproperty — when set, filters the option list. Options whose title contains the filter (case-insensitive) are shown.- Fuzzy filter: when
:filteris non-nil and no exact matches, uses trigram-based fuzzy matching (3-character sliding window Jaccard similarity) - ~150 lines
~150 lines total. Dependencies: Phase 5 (keybindings), Phase 4 (theme).
v0.8.0: Markdown + Code + Diff Rendering
Content rendering components. Markdown for agent responses. Code for syntax highlighting. Diff for file changes.
TODO Markdown
(defclass markdown ...)— renders markdown content as styled text- Heading levels 1-6: colored by theme (
:markdown-heading) with level-based sizing - Bold, italic, inline code, strikethrough — rendered as croatoan text attributes
- Code blocks: fenced (
```) and indented. Background-colored, syntax-highlighted via regex - Links: OSC 8 hyperlinks (clickable in Kitty, WezTerm, iTerm2, Ghostty). Format:
\x1b]8;;url\x1b\\...link text...\x1b]8;;\x1b\\ - Blockquotes: colored left border (
:markdown-quote), indented text - Tables: aligned column text, no borders. Column alignment from header separators
- Lists: ordered and unordered, with indentation
- All features degrade gracefully to plain text on terminals without attribute support
- ~200 lines
TODO Code
(defclass code ...)— renders syntax-highlighted code:content— the code string:language— language identifier for syntax rules- Line numbers (optional, via
:line-numbers t) -
Regex-based highlighting (no Tree-sitter dependency):
- Keywords: language-specific keyword lists
- Strings: single and double quoted
- Comments: line (
;//,#) and block (/* */) - Numbers: integer and float literals
- Functions: word followed by
(
- Colors from theme:
:syntax-keyword,:syntax-function,:syntax-string,:syntax-number,:syntax-comment,:syntax-type - ~150 lines
TODO Diff
(defclass diff ...)— renders unified diff output:content— diff text (standard unified diff format)- Added lines:
+prefix, green background (:diff-added) - Removed lines:
-prefix, red background (:diff-removed) - Context lines: ~ ~ prefix, neutral background (
:diff-context) - Line numbers: optional, rendered in
:diff-line-numbercolor - ~50 lines
~400 lines total. Dependencies: Phase 4 (theme), Phase 2 (renderables).
v0.9.0: Dialog System + Toast
Modal overlays and transient notifications.
TODO Dialog base
(defclass dialog ...)— absolute-positioned overlay with backdrop- Backdrop: semi-transparent (dimmed background color)
- Centered panel with
:background-panelcolor, border :on-dismisscallback — fires on Esc or backdrop click:size—:small(40 cols),:medium(60 cols),:large(88 cols). Height computed from content.- Stack-based: dialogs push/pop on a
*dialog-stack* - Esc dismisses top dialog. Ctrl+C clears stack.
- ~100 lines
TODO Dialog sub-classes
alert-dialog— title + message + OK buttonconfirm-dialog— title + message + Yes/No/Cancel buttonsselect-dialog— wraps a Select component in a modal. Title, searchable list, action buttonsprompt-dialog— wraps a TextInput in a modal. Title, input, OK/Cancel buttons- ~60 lines
TODO Toast notifications
(toast title &key variant duration)— shows a transient notification- Variants:
:info(blue),:success(green),:warning(yellow),:error(red) — colored left border :duration— auto-dismiss after N milliseconds (default 5000)- Position: top-right corner, max 60 cols wide
- Multiple toasts stack vertically
- ~60 lines
~220 lines total. Dependencies: Phase 3 (rendering engine), Phase 4 (theme), Phase 5 (TextInput), Phase 7 (Select).
v0.10.0: Mouse Support
Mouse event propagation through the component tree.
TODO Mouse events
- Enable croatoan mouse mode:
(setf (mouse-enabled-p window) t) - Parse ncurses mouse codes: button (left/right/middle), state (press/release/drag), x, y
- Ctrl/Shift/Meta modifiers from mouse event
:on-mouse-down,:on-mouse-up,:on-mouse-move,:on-mouse-scrollcallbacks on components- Hit-testing: walk the component tree from root, find the deepest component whose rect contains (x, y)
- Event propagation: component consumes event by returning T from callback; otherwise bubbles to parent
- Scroll wheel: mapped to PageUp/PageDown in ScrollBox
- Click on OSC 8 link: extract URL, open via
xdg-open - ~100 lines
TODO Text selection + copy
- Mouse drag: highlight text between drag start and current position
(get-selection)— returns the selected text as a string- Copy: pipe selection to
xclip/wl-copy/pbcopy - ~50 lines
~150 lines total. Dependencies: Phase 3 (rendering engine).
v0.11.0: Plugin / Slot System
Extensible named slots. Applications and plugins register content into named slots. The component tree renders whatever is registered.
TODO Slot system
(defslot :sidebar-title &key order render-fn)— registers a rendering function for a slot(slot-render slot-name ...)— calls all registered render-fns for the slot in priority-ordered sequence- Slot modes:
:stack(render all, default),:replace(last registered wins),:single-winner(first matching wins) :orderinteger — sorting key for:stackmode (lower = renders first)- Built-in slot naming convention: component name, then sub-slot:
sidebar-title,sidebar-content,home-logo,home-prompt - ~100 lines
~100 lines total. Dependencies: Phase 2 (renderables + layout).
v1.0.0: Complete Framework
All 11 phases integrated and tested. Applications can build rich terminal UIs from the component library without writing custom ncurses code.
Neurosymbolic Phase Reference
| Phase | Component | Lines | Release |
|---|---|---|---|
| 1 | Layout engine (Yoga FFI + API) | ~350 | v0.1.0 |
| 2 | Renderables (Box, Text) + dirty | ~300 | v0.2.0 |
| 3 | Rendering engine (diff, scissor) | ~300 | v0.3.0 |
| 4 | Theme engine (tokens, presets) | ~290 | v0.4.0 |
| 5 | TextInput + Textarea + keybindings | ~500 | v0.5.0 |
| 6 | ScrollBox + TabBar | ~300 | v0.6.0 |
| 7 | Select (dropdown + fuzzy filter) | ~150 | v0.7.0 |
| 8 | Markdown + Code + Diff | ~400 | v0.8.0 |
| 9 | Dialog system + Toast | ~220 | v0.9.0 |
| 10 | Mouse support + selection | ~150 | v0.10.0 |
| 11 | Plugin / slot system | ~100 | v0.11.0 |
| Total | ~3060 |