Files
cl-tty/docs/ROADMAP.org
Amr Gharbeia 3e0268c982 Initial commit: cl-tui — Reusable Common Lisp Terminal UI Framework
Layout engine (Yoga FFI), renderables (Box, Text), rendering engine,
theme engine, TextInput/Textarea, ScrollBox/TabBar, Select, Markdown/Code/Diff,
dialog system, mouse support, plugin/slot system.
2026-05-11 06:48:20 -04:00

20 KiB

cl-tui Roadmap

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:

  1. ROADMAP.org — mark item DONE, update LOGBOOK timestamp
  2. README.org — update Status line
  3. cl-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.

TODO Yoga FFI binding

  • 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 a YGNodeRef in 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 positions
  • test-layout-hbox — hbox with two children computes correct x positions
  • test-layout-flex — flex-grow distributes space correctly
  • test-layout-absolute — absolute child positions relative to parent
  • test-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
  • :focusable property — 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 render function 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-string for unchanged text, clear + add-string for 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-color once 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>.lisp for custom themes
  • ~80 lines

TODO Dark/light variants

  • Each preset defines both :dark and :light variants
  • (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-submit callback — fires on Enter
  • :max-length property — 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-submit callback — 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
  • :leader key (default Ctrl+X) with configurable timeout
  • Key names normalized from croatoan's :code-key + :key-name output
  • ~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-x slots
  • 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-tab slot — 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-select callback — fires on Enter
  • :filter property — when set, filters the option list. Options whose title contains the filter (case-insensitive) are shown.
  • Fuzzy filter: when :filter is 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-number color
  • ~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-panel color, border
  • :on-dismiss callback — 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 button
  • confirm-dialog — title + message + Yes/No/Cancel buttons
  • select-dialog — wraps a Select component in a modal. Title, searchable list, action buttons
  • prompt-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-scroll callbacks 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)
  • :order integer — sorting key for :stack mode (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