Files
cl-tty/docs/ROADMAP.org
Hermes 811d51a4f2 Rename cl-tui -> cl-tty, v0.9.0: Dialog System + Toast
Rename: cl-tty avoids naming collision with Quicklisp's cl-tui (naryl/cl-tui,
a cl-charms-based ncurses library). Our project is pure escape-sequence CL.

v0.9.0 adds:
- Dialog base class: modal overlay with backdrop, centered panel, size
  variants (:small/:medium/:large), stack-based management
- Dialog subclasses: alert, confirm, select-dialog, prompt-dialog
- Toast notifications: transient, top-right corner, auto-dismiss,
  colored variants (info/success/warning/error)
- 78 tests total, 100% passing

ASDF: read-time package references (+fiveam:+) replaced with
find-symbol so .asd loads without FiveAM pre-loaded
2026-05-11 19:55:37 +00:00

22 KiB

cl-tty Roadmap

The Roadmap

Each phase is one minor release. Phases ship in dependency order — each depends on the components from prior phases. The backend protocol ships first because everything else builds on it.

v0.0.1: Foundation — Backend Protocol

The abstraction layer that makes everything portable. Two backends: modern (raw escape sequences, truecolor, modern features) and simple (ASCII art, universal compatibility). The component tree never touches the terminal directly — it dispatches through the protocol.

TODO Backend protocol definition

  • Define backend abstract class with generic functions:

    • initialize-backend, shutdown-backend, suspend-backend, resume-backend
    • backend-size, backend-write, backend-clear
    • begin-sync, end-sync — DECICM synchronized updates
    • draw-rect, draw-text, draw-border, draw-ellipsis, draw-link
    • cursor-move, cursor-hide, cursor-show, cursor-style
    • read-event, enable-mouse, enable-bracketed-paste, set-keyboard-mode
    • capable-p — query feature support
  • Style plist structure: (:fg :error :bg :background-panel :bold t :italic nil ...)
  • ~100 lines

TODO Simple backend

  • simple-backend class — inherits backend
  • Borders: ASCII (+-|), no rounded corners
  • No color, no bold/italic — plain characters only
  • No OSC 8 links, no mouse, no synchronized updates
  • Works on any terminal, any SSH connection, piped output
  • ~100 lines

TODO Modern backend

  • modern-backend class — inherits backend
  • Truecolor 24-bit foreground/background
  • Rounded, single, double border styles via Unicode box-drawing
  • OSC 8 hyperlinks (clickable URLs)
  • DECICM synchronized updates (flicker-free)
  • SGR mouse tracking + kitty keyboard protocol
  • Bracketed paste detection
  • Bold, italic, underline, dim, blink, reverse, strikethrough
  • Cursor style: :bar, :block, :underline, with blink option
  • ~250 lines

TODO Terminal capability detection

  • detect-backend → returns modern-backend or simple-backend
  • Check if stdout is a TTY (if not → simple-backend)
  • Send DA1 (ESC[c) query, 100ms timeout
  • Send DA3 (ESC[?c) for kitty/wezterm identification
  • Query DECRPM (ESC[?2026$p) for DECICM sync support
  • Query truecolor support via COLORTERM env var + DA response
  • Cache detection result so subsequent calls are instant
  • ~100 lines

~550 lines total. Dependencies: None (pure CL, no FFI, no external libs).

v0.0.2: Layout Engine

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-tty.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.

DONE Box renderable

  • State \"DONE\" from \"TODO\" [2026-05-11 Mon]
  • (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

DONE Text renderable

  • State \"DONE\" from \"TODO\" [2026-05-11 Mon]
  • (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

DONE Inline text styles

  • State \"DONE\" from \"TODO\" [2026-05-11 Mon]
  • (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

DONE Dirty tracking

  • State \"DONE\" from \"TODO\" [2026-05-11 Mon]
  • (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-tty/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