Bug fixes:
- read-raw-byte now returns (values nil :eof) on stdin EOF
instead of just nil, so callers can distinguish EOF from
timeout. Previously, non-TTY stdin (pipes, /dev/null)
caused a busy-spin: sb-posix:read returned 0 immediately,
read-raw-byte returned nil, the demo loop treated nil as
'no event yet' and spun at 100% CPU producing 86MB of
repeated rendering frames.
- %read-escape-sequence now uses a 50ms timeout on the first
follow-up byte to resolve the classic Escape-key ambiguity:
a lone Escape press returned an :escape key-event instead of
blocking indefinitely on VMIN=1 VTIME=0. All callers
(SS3, CSI, Alt+char) propagate :eof instead of faking
:escape events when EOF occurs mid-sequence.
- parse-csi-params now uses multiple-value-bind on read-raw-byte
to preserve the :eof signal through CSI parsing.
- simple-backend draw-border now renders :title on the top
edge instead of declaring it (ignore). The title was
silently swallowed — the box rendered with the right border
frame but the title text was never written.
- demo.lisp: removed 'q' as quit key (conflicted with text
input). Only Esc and Ctrl+C quit. Widget event forwarding
scoped to tab 1 (Widgets tab). EOF handling in main loop.
- Stale help text (still said 'q/esc: quit') updated.
Verification infrastructure:
- PTY-based demo test (17 checks) spawns the demo in a real
pseudo-terminal, sends actual keystrokes, reads terminal
output back. Verifies: startup rendering, tab switching,
key dispatch, 'q' doesn't quit, Escape quits via timeout,
Ctrl+C quits, EOF clean exit, no busy-spin.
- API feature verification (29 checks) exercises every major
component through the actual exported API: Simple backend,
Box with title, Text attributes, draw-rect, TextInput
(insert/backspace/cursor/Ctrl-A/E), TextArea, key/mouse
events, Layout flex, Markdown, Theme presets (dark/light/
nord), Select filtering, Dialog stack, Mouse hit-test,
Framebuffer, Dirty tracking, Modern backend, draw-ellipsis/
draw-link, Render dispatch, Detection, Capabilities.
- Testing pattern saved as skill (tui-pty-testing) for reuse.
Unit tests: 392/392 passing. All 12 test suites green.
Raw terminal mode must be set by the parent process (the shell),
not from inside SBCL. sb-ext:run-program subprocesses cannot
reliably access the controlling terminal for stty operations.
./demo.sh sets raw mode via stty, runs sbcl --script demo.lisp,
and restores terminal state on exit (EXIT, INT, TERM).
demo.lisp no longer calls with-raw-terminal — it assumes the
calling shell has already set raw mode.
Added ./demo shell script that sets raw mode via stty before running
the Lisp demo and restores it on exit (including SIGINT/SIGTERM).
demo.lisp no longer attempts to set raw mode from inside SBCL —
terminal raw mode is the shell's responsibility. This avoids the
recurring problem of sb-ext:run-program + stty not being able to
access the controlling terminal from inside sbcl --script.
make-raw-termios (input.lisp:66-67): termios-cc accessor in SBCL 2.5.x
takes one arg (the struct) and returns the cc array. Use (aref ...)
to set individual control characters. Old code used 3-arg setf form
that no longer works and produced style warnings.
demo.lisp: Now exits with a clear error message when raw mode can't
be established, rather than running in broken pipe-safe mode where
escape sequences are echoed and input is line-buffered.
No Quicklisp needed at all. Works from a fresh git clone with
just SBCL installed. Registering the current directory in ASDF's
central-registry is enough to find cl-tty.asd.
The demo now guards the quickload with a (find-package :cl-tty.backend)
check first, tries ql:quickload inside ignore-errors, and falls through
to direct (load cl-tty.asd) + (asdf:load-system :cl-tty) if the
package still isn't loaded. Works in --disable-debugger mode where
Quicklisp's SYSTEM-NOT-FOUND continuable error kills the process.
demo.lisp now registers the current directory as a quicklisp project
source and falls back to direct asdf:load-system if quicklisp can't
find cl-tty. Lets the demo run from a fresh git clone without
symlinking into ~/quicklisp/local-projects/.
demo.lisp:
- Removed ignore-errors wrapper: run-demo now returns normally,
followed by (uiop:quit 0) at top level — fixes exit code always 1 bug
- Manual set-raw-mode/unwind-protect/restore-terminal-state instead of
with-raw-terminal macro (safer in edge cases)
- Graceful fallback when raw mode fails: continues in pipe-safe mode
so the demo renders frames even without terminal control
- Simplified tab rendering, fixed textarea-lines display
The demo runs correctly in both interactive and pipe-safe modes.
In a real terminal: raw mode, keyboard/mouse event loop.
In pipe-safe mode: spins rendering frames (read-event returns nil).
Verified running: frames render correctly with borders, tabs, content,
status bar, and event counter.
- Three tabs: Home, Components, Stats with different content
- Real keyboard input: arrow keys to switch tabs, q to quit
- CSI escape sequence parsing for arrow keys
- Footer bar shows current tab position
- Tab bar highlights active tab in bright blue