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.
The position-cursor function now uses cursor-line/cursor-col stored by
view-input instead of recomputing from scratch, guaranteeing alignment
with the rendered text. The boundary check uses (< pos (+ accum len))
to avoid falsely matching the first character of the next wrapped line.
Removed the speculative reset-recovery code that sent cursor to end
when pos was 0, since that broke legitimate navigation to the
beginning of the input.
(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.
Removed dependency on (st :cursor-line) and (st :cursor-col) state.
position-cursor now does its own word-wrap and accum tracking to
determine which wrapped line and column the cursor is on. This makes
it independent of view-input's rendering state.
view-input now stores cursor-line and cursor-col in state after
word-wrapping. position-cursor uses these to place the cursor on
the correct wrapped line, instead of the hardcoded row (- h 6)
which always put the cursor on the first line.
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.
The generated temp script manually compiled and loaded TUI files from
/lisp/, silently swallowing compile errors and
leaving the user with a blank screen. Replaced with a direct ASDF load
that gives proper error messages and lets ASDF handle compilation.
Phase 1 of cl-tty abstraction: remove 5 dead functions (word-wrap,
char-width, parse-markdown-spans, parse-markdown-blocks, render-styled,
syntax-highlight) and their tests. Switch 2 remaining word-wrap calls
to cl-tty.box:word-wrap.
- 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 reverse-video cursor with a solid █ block in :input-prompt
color (amber). Drawn at the insertion point every frame from redraw
and main loop. finish-output ensures the cursor escape reaches the
terminal immediately.
Replaced the software blinking █ cursor with a reverse-video cursor
that swaps foreground and background colors at the insertion point.
Solid at all times — no blink logic, no state tracking, no flicker.
- Removed duplicate cursor-visible-p functions
- Removed software cursor draw from view-input
- Removed terminal cursor style/show from initialize-backend
- position-cursor draws character at cursor with :bg fg + :input-fg bg
position-cursor now called at end of redraw so the cursor appears
on the very first frame and after every keypress without a 100ms
delay. Also still called from main loop between sleep for blinking.
position-cursor runs every frame from main loop (after sleep 0.1),
drawing █ when cursor-visible-p returns T, space when NIL.
This creates a true 2Hz blink that toggles independently of keypresses.
Terminal cursor also set to blinking block as fallback.
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.
Clean implementation: spacer inserted in the rendering loop as an
(incf y) between message blocks, tracked in scroll-fitting loop
via spacer variable. No data structure changes.
Also: fixed premature let close in spacer binding, fixed view-input
closing paren count, and re-applied speaker alignment fixes lost in
revert.
- 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.
set -e on line 2 causes the bash script to exit immediately when sbcl
returns non-zero, before the stty icanon echo ixon restore runs.
Add trap cleanup EXIT to guarantee terminal restore on any exit path.
- 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)
passepartout script:
- Add (uiop:quit 0) after tui-main so SBCL exits on Ctrl+Q
- Remove exec to allow stty restore after sbcl subprocess
- Restore icanon echo ixon after TUI exits (terminal stuck raw)
channel-tui-view.org:
- Remove unused fb/h vars from test-sidebar-not-shown-narrow
- Add (declare (ignore w)) to render-styled
- Qualify theme-color as passepartout.channel-tui:theme-color
(render-styled is in :passepartout package)
- Remove dead :url clause from pick in parse-markdown-spans
(URLs handled by dedicated branch, not via pick)
- Update literate prose for all changes
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 -ixon to stty so Ctrl+Q (XON byte) isn't swallowed by the
terminal driver and reaches the TUI as :CTRL-Q
- view-input now truncates the prompt (> prefix + visible text) to
chat-w - 2 characters, and the hint to chat-w characters, so
neither extends into the sidebar area