When / is typed on an empty input with no dialog open, open the
command palette with "/" pre-filled in the filter instead of
inserting / into the text buffer. Ctrl+P still opens the palette
without a pre-filled filter.
Replace the 400-line on-key function with cl-tty text-input callbacks.
Add on-cancel, on-tab, on-history slots to cl-tty's text-input widget.
Remove defkeymap :local up/down/escape handlers.
Remove (member k '(:enter :tab :escape :up :down)) from process-key-event.
PageUp/PageDown stay in keymap, routed to handle-ppage/handle-npage.
Fix XDG cl-tty.asd to remove stale select-package/select references
and add missing markdown-package/markdown entries.
Fix #\) character literal (not valid in all contexts).
Fix several missing closing parentheses in handle-tab and command-dispatch.
Dialog: use consistent cl-tty.dialog: prefix for all select accessors.
The :cl-tty.select and :cl-tty.dialog packages each define their own
SELECT class with separate accessor generic functions. Mixing prefixes
caused "no applicable method" errors. Now all 14 references use
cl-tty.dialog: (make-select, select-filter, select-next, etc.)
Sidebar: fix sidebar-lines append arguments. Each item must be a
proper list of cons cells, not a bare cons. Replaced all quoted
'("x" . :y) with (list (cons "x" :y)). Also fixed the quoted
cons call that was never evaluated.
Bash script: add --disable-debugger and --eval '(uiop:quit 0)' to
the tui sbcl invocation. Prevents the debugger from entering raw
terminal mode on error and ensures clean exit.
cl-tty: delete stale select-package.lisp and select.lisp orphan files
(not tangled by any current org file).
Phases 1-3 of library/application boundary cleanup:
Phase 1: Remove dead code (150 lines)
- Delete local word-wrap (all callers already used cl-tty.box:word-wrap)
- Delete parse-markdown-spans, render-styled, parse-markdown-blocks,
syntax-highlight (all unused — view uses cl-tty.markdown directly)
- Replace tests with cl-tty.markdown equivalents
Phase 2: Migrate theme to cl-tty.theme (250 lines removed)
- Replace *tui-theme*/*tui-theme-presets* with *theme* + define-preset
- theme-switch/theme-save/theme-load delegate to cl-tty.theme
- theme-color is now a 3-line wrapper
- Added save-theme/load-theme to cl-tty.theme (38 lines added there)
Phase 3: Fix dialog arrow navigation with select-handle-key
- Replace broken manual key dispatch with cl-tty.dialog:select-handle-key
- The old code had a dead (and ch (graphic-char-p ch)) — the and result
was discarded, so every unhandled key ran (code-char key-code) against
the filter unconditionally, inserting garbage on arrow keys
Remove the add-msg :system calls that printed * Swank <port> * and
* <backend> backend WxH * — purely informational/debug messages
that cluttered the first lines of the chat window.
Replace queue-based key dispatch with process-key-event (inline in reader,
zero latency between keypress and render).
Add with-frame to cl-tty.backend (error-safe begin-sync/end-sync wrapper).
Use with-frame in redraw instead of manual begin-sync/end-sync.
Add initial render before main loop (UI appears before first read-event).
Remove position-cursor (replaced by inline block cursor in view-input).
Remove input-string/input-insert-char/input-delete-char wrappers.
Remove :input-buffer/:cursor-pos from state (managed by text-input widget).
passepartout script: set *debugger-hook* nil and failure-behaviour :warn
before quickload to survive compile warnings; remove cache-clear line.
The old inline reader only handled basic CSI sequences (up/down/left/right/
home/end) and treated everything else as Escape. cl-tty.input:read-event
handles CSI sequences with modifiers, SS3 function keys, kitty keyboard
protocol (disambiguate escape codes), UTF-8 input, and terminal resize.
Key-event structs are converted back to the keyword/integer format that
the existing case ch dispatch and on-key expect:
- Ctrl+letter → :CTRL-X keyword
- :page-up/:page-down → :ppage/:npage
- Single-char keywords → printable character via code-char
- Everything else → pass keyword through
Removed the separate resize check block since read-event handles it.
The typecase guard, explicit navigation keyword dispatch, and word-wrap
cursor calculation changes introduced two regressions:
1. Cursor shows letter before instead of on (off-by-one)
2. Right arrow sometimes moves backward
Reverting to e04b12c which had working arrow keys and single-line cursor.
The unconditional position-cursor fix will be re-applied separately.
Removed (position-cursor fb w h) from inside redraw (which is gated by dirty
flags), and from inside the dialog-guarded (unless dialog* ...) block in the
main loop. Added unconditional (position-cursor be w h) at loop body level so
it runs every single iteration regardless of dirty state or dialog activity.
This ensures the cursor highlight always tracks cursor-pos correctly.
(characterp 97) is NIL so (when (characterp ch) ...) broke all printable
input. The typecase ((integer 32 126) ...) converts printable integer
bytes to characters while rejecting keywords like :CTRL-A.
Maps known navigation keywords (:up :down :left :right :enter :backspace :tab
:escape :home :end :ppage :npage) explicitly in the case to bypass the guard,
so only non-keyword, non-navigation values are filtered. This prevents :CTRL-A
(byte 1/SOH) from ever reaching on-key or dispatch-key-event and resetting
cursor-pos to 0 via the :ctrl+a keymap binding.
Add explicit (redraw be w h) call before the main loop so the TUI
renders immediately on startup, without waiting for the first
100ms input poll cycle to complete.
- ROADMAP: consolidate all TUI work under v0.8.0 (removed premature
v0.9.0/v0.10.x labels), restored original v0.9.0 eval harness plan
- channel-tui-view.org: Emacs-style reverse-video cursor (swap fg/bg
instead of drawing █), hint bar now shows F:focus/MCP:count on left
and token gauge + keybindings on right, sidebar reorganized to show
GATE TRACE, RULES + BLOCK COUNT, COST, FILES panels
- channel-tui-main.org: command palette selection now uses reverse-video
highlight (bg-input fg on input-fg bg, matching cursor style), fixed
cond order so sel-p is checked before cat (all items had :category
making sel-p unreachable), added session-cost extraction from daemon
- passepartout: export COLORTERM=truecolor for modern backend detection
Replaced software cursor (draw-text █ every frame) with native terminal
cursor (position-cursor using cursor-move + cursor-style). Terminal handles
blinking natively at 500ms — no redraw needed for cursor updates.
- position-cursor: computed input insertion point from state, calls
cursor-move + cursor-style (:block :blink t) + cursor-show.
- Called from main loop every frame after (sleep 0.1), outside
redraw's begin-sync/end-sync. No flicker.
view-input word-wraps input at prompt-w, expanding the grey panel
upward as needed. Uses software cursor (█) in :input-fg blinking
at 2Hz via get-internal-real-time.
view-chat max-lines adapts to variable panel height via input-panel-top.
Removed terminal cursor (position-cursor, cursor-show, cursor-style).
Dialog minibuffer top now computed from input-panel-top.
- Theme: added :agent-border, :thinking-bg, :symbolic-border to all 13
presets with theme-load fallback for saved themes.
- Agent output now draws │ with :agent-border color (muted tan).
- Neuro-thinking (streaming): draw-rect at column 0 with :thinking-bg
(dark grey block) instead of a grey │ character. No border text.
- Gate traces: │ with :symbolic-border (was ╎ with :dim).
- Tool calls: │ with tool status color (was ╎).
- Removed > prompt prefix from input line.
- Added position-cursor function: blinking block cursor at insertion
point, called every frame from the main loop after sleep.
- redraw: always draws all three views (status/chat/input) when any
dirty flag is set. Dirty flags only gate frame rendering, not
which parts render. Fixes disappearing input/history.
- Added :bg-input to all 13 presets with #2e2e2e (dark) / #d4d4d4
(light-amber). theme-load fills missing keys from current preset
defaults for backward compatibility.
- Removed unused *sidebar-panels* defvar and obsolete contract docs.
- Renamed dim-bg → dim-fg (foreground color, not background).
- All draw-text calls in sidebar and dialog minibuffer now pass
explicit bg-panel, preventing background leaks.
- render-styled (markdown renderer) passes explicit (theme-color :bg).
- Fix h shadowing in view-chat scroll loop (h → mh).
- Theme: near-black (#0a0a0a) backgrounds, dark-grey panels (#141414),
warm amber (#fab283) accent only. New keys: :bg, :bg-panel, :bg-element,
:text-muted. All 13 presets updated.
- Messages: No background fills (sit on global black). User messages get
amber left border (│). Agent response has no border (invisible).
Streaming agent messages get grey left border. Gate traces and tool
calls use grey ╎ prefix. No label lines, no time separators.
- Sidebar: :sidebar-mode with :auto/:visible/:hidden. Auto-shows at >120
cols (opencode-style). Width 42 with version + connection dot footer.
- Input: 2-char hpad on each side. Grey panel (2 rows: separator +
prompt). Hint right-aligned at bottom on black.
- Status bar: empty (clean black line).
- cl-tty backend: draw-text, draw-rect, draw-link, draw-border now use
\e[22;23;24;25;27m (style-only reset) instead of \e[0m (full reset),
preserving foreground/background across draw calls.
- Fix: all sidebar text draws pass explicit bg-panel background.
- Fix: hint at h-1 passes explicit (theme-color :bg).
- Fix: sidebar bottom row uses draw-text (no \n) to prevent scroll at h-1.
- Remove 'code' variable binding (redundant with b). esc-seq now
starts with (and b (= b 27) ...) so when b is nil (timeout), the
and short-circuits before (= b 27) can error with 'NIL is not
of the type NUMBER'.
- Swank prints to *standard-output*, not *error-output*. Bind both
to string output streams to prevent ';; Swank started' leak.
- (= b2 91) errors when b2 is nil (read-raw-byte timeout). Add
(and b2 (= b2 91)) to guard against nil.
- Swank writes ';; Swark started at port:...' to *standard-output*,
not *error-output*. Bind *standard-output* to string stream too.
cl-tty.input:read-event has bugs (CSI parser timeout causes :escape
to be returned for arrow keys). Replace with direct read-raw-byte
calls that are proven to work for CSI sequences. The inline detection:
- Read first byte with 100ms timeout
- If ESC (27), read two more bytes with 150ms timeout each
- Map 65→:up, 66→:down, 67→:right, 68→:left, etc.
- Other bytes converted via the same cond chain as before
Also re-add resize check (was handled by read-event).
Use handler-case around the reader to prevent any reader errors
from crashing the TUI. Re-add Swank *error-output* redirect.
Replace the inline raw byte reader + CSI detection with
cl-tty.input:read-event which uses read-raw-byte (direct fd reads)
and properly parses CSI escape sequences, UTF-8, mouse events, etc.
Also fix: remove extra ) in (t nil) clause that was prematurely
closing the let* binding, causing the if form to receive 4 args.
- Swank: bind *error-output* to string stream to prevent 'Swank started
at port: 4006.' from leaking to terminal on exit
- CSI detection: wrap inner dotimes in (progn ... t) so the and form
doesn't short-circuit (dotimes returns nil, breaking the chain)
- Add debug add-msg for CSI detection results
The main loop's closing paren was missing — (sleep 0.1)) only closed
sleep and the minibuffer let, but NOT the loop itself. The next form
(progn (disconnect-daemon)) was INSIDE the loop body, called on every
iteration. On first call it added '* Disconnected *' and cleared the
daemon stream, making the TUI permanently disconnected.
Fix: add ) to close the loop. Also:
- Connect-daemon runs synchronously BEFORE with-terminal (3 ports, 6s
max). If daemon is already running, the TUI starts connected.
- If sync connect fails, background thread retries every 5 seconds.
- start-daemon in background (no blocking wait for daemon startup),
so TUI appears immediately.
- Remove handler-case + sb-ext:with-timeout 0.1 pattern entirely.
sb-ext:timeout is a condition class, not a recognized type in the
compilation environment, causing runtime 'undefined function' crash.
- Replace with dotimes 10 * (listen *standard-input*) + sleep 0.01
polling loop. Same 0.1s timeout, no condition type dependencies.
- Also fix handler-bind → handler-case in tui-load.lisp so the stack
unwinds properly (running with-terminal's shutdown-backend cleanup)
before the crash handler runs, restoring terminal to normal state.
- Fix paren imbalance (off by 1) in the new listen-based reader code.
Major bug: defun disconnect-daemon in channel-tui-main.org was missing
its closing paren. Every form after disconnect-daemon (tui-main, tests,
etc.) was inside the unclosed defun, causing 'end of file' compile errors.
Adding the missing ) fixed all compilation errors.
Also revert handler-case change: keep sb-ext:timeout condition type.
- start-daemon: handle ADDRESS-IN-USE-ERROR by trying ports 9105-9115
instead of crashing. Logs which port is used.
- Add *daemon-port* defvar to track actual listening port
- main: wrap start-daemon in handler-case so the daemon doesn't
crash if all ports are in use
- connect-daemon (TUI): try ports 9105-9115 with 2s timeout each
instead of retrying the same port 3 times
- Add debug messages for connection success and disconnection timestamp
- Add CSI escape sequence detection: when ESC (27) is received, poll
for up to 20ms for the next bytes to detect arrow/home/end keys
- Use listen+read-char polling (not nested with-timeout) to reliably
collect multi-byte sequences while keeping standalone ESC responsive
- Fix paren balance in main code block (2 extra opens from nested
esc-seq forms needed matching closes)
- Revert async connect-daemon thread (bt:make-thread unreliable — errors
in the thread cause silent failure, no connection, no error message)
- Restore blocking connect-daemon before with-terminal (original pattern
that was working)
- Revert /reconnect to synchronous call
- Remove stale async thread code and error messages
Cat subprocess (uiop:launch-program '("cat") :input :interactive) was
unreliable — the process would exit immediately in some environments,
breaking ALL keyboard input. Root cause: uiop's :input :interactive mode
opened /dev/tty which failed under specific process-group configurations.
Replace with direct read-char-no-hang on *standard-input*. The bash script
sets stty -icanon -echo -ixon before launching sbcl, so SBCL's stdin is
already in raw mode. No subprocess needed.
Also fixed pre-existing paren imbalance in tui-main (2 extra opens).
- Wrap cat with stdbuf -o0 so keystrokes aren't stuck in cat's 4096-byte
pipe buffer — text input was invisible until buffer filled
- Dialog filter: (characterp ch) rejects integer char codes from raw event
dispatch. Accept integerp in range 32-126 and convert via code-char
- Remove initial render (backend-clear + view calls) before main loop.
Dirty flags already trigger a full sync-wrapped redraw in the first
iteration, eliminating the pre-loop clear flash
Issue 1 — flickering during typing/updating:
- Wrap every frame render in DECICM sync (begin-sync/end-sync) so the
terminal defers rendering until the entire frame is written
- Replace backend-clear (ESC[2J full clear) with draw-rect background
fill — eliminates visible blank frame between redraws
- These two changes together eliminate all visible tearing/flicker
Issue 2 — bottom-anchored minibuffer (Emacs-style):
- Replace centered overlay dialog with bottom-anchored minibuffer
that expands upward from the input line
- Unified command menu: Ctrl+P and / both open the same menu with
all 35+ commands (slash + daemon), dispatch by value type
- Filter prompt at h-3 (same position as normal input),
options listed above, grows up to 15 lines
- No full-screen dim backdrop — just clear the minibuffer area
Issue 3 — color schemes:
- Add 5 new presets: catppuccin, tokyonight, dracula, gemini, mono
- Total: 13 presets (up from 8)
- Update /theme completion list and help text
Also fixed: pre-existing unbalanced paren in tui-main (missing close)
Replaced (let* ((cat-proc ...) (tty-in ...)) ...) with global
special variables *cat-proc* and *tty-in* with defvar declarations.
The let* caused 'unbound variable' errors on Ctrl+Q because the
lexical scope didn't extend to terminate-process. Global vars have
indefinite scope and work reliably regardless of paren nesting.
Added chat-w = w - sidebar_width calculation in view-status and
view-input, matching view-chat's existing approach. Also added
chat-w for the separator line drawing in tui-main. This prevents
the prompt, separator, hint, and status bar from extending into
the sidebar area when it's visible.
uiop:run-program inside SBCL can't access the terminal, so stty
calls from within Lisp fail silently. By running 'stty -icanon -echo'
in the bash script before exec sbcl, the terminal is already in
character-at-a-time mode when the TUI starts, and Ctrl+P/B keys
arrive as individual bytes through the cat pipe.
- Sidebar threshold lowered from 120 to 60 so it works on 83-col
terminals
- Agent response text is now word-wrapped through cl-tty.box:word-wrap
after markdown rendering, preventing text from bleeding past the
terminal edge
- Added chat-w = w - sidebar-width in view-chat for all width
calculations (word-wrap, padding, borders) so text doesn't
bleed into the sidebar area
- Changed Ctrl+B dirty flags from (list t t nil) to (list t t t)
so input view also redraws, fixing the toggle-not-turning-off issue
The key event dispatch was lost during git restores, causing all
:key events (Ctrl+P, Ctrl+B, Enter, etc.) to fall through to
on-key which only handles :enter, :escape, and character insertion.
Added back the case ch with :CTRL-Q/:CTRL-P/:CTRL-B/:CTRL-L
branches and full dialog key routing.
Without -icanon, the terminal driver buffers all input until Enter,
so Ctrl+P/B never arrive as individual key events. With -icanon,
cat reads bytes immediately and pipes them to SBCL. SBCL reads from
the pipe, not fd 0, so there's no fd 0 read block issue.