v0.8.0: complete cl-tty TUI migration — remove all Croatoan deps

- Replace numeric key code dispatch with cl-tty keyword events
- Replace Croatoan code-key/key-name normalization with direct keyword dispatch
- Update main loop to construct Ctrl-key keywords from cl-tty key-event modifiers
- Remove croatoan-to-tty-event compatibility shim and its test
- Remove duplicate Esc handling from main loop (now handled by on-key)
- Update all documentation contracts, prose, docstrings to remove Croatoan refs
- Remove :croatoan from package dependencies
- All event handling now goes through cl-tty keymaps or keyword dispatch
This commit is contained in:
2026-05-13 12:46:43 -04:00
parent 761678bbd6
commit 6e69c4a724
8 changed files with 848 additions and 3277 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -6,66 +6,6 @@
The TUI state is a single plist accessed via ~st~ / ~(setf st)~.
All state mutation flows through event handlers in the controller.
** v0.8.0 — Information Radiator: Sidebar State
The sidebar is Passepartout's permanent UX differentiator — a 42-column
information panel that renders architectural data no competitor can display
because none has deterministic gates, foveal-peripheral context, or
rule-synthesizing Dispatcher to feed it. The sidebar makes the invisible
visible: seven panels of zero-LLM-token data from the deterministic layer,
always on screen when terminal width permits.
The sidebar reads its data from daemon response fields enriched by the
~:tui~ actuator in ~core-act.org~. All seven panels consume existing
infrastructure: gate trace from ~cognitive-verify~ (v0.4.0), focus from
~*loop-focus-id*~ (v0.3.0), rules from ~*hitl-pending*~ (v0.3.0), context
from ~token-economics~ (v0.5.0), files from tool execution tracking
(v0.8.0 new), cost from ~cost-tracker~ (v0.5.0), and block counts from
the Dispatcher (v0.8.0 new). Each field arrives as a daemon-response
plist key; the TUI stores them in state fields read by ~view-sidebar~.
When the terminal is narrower than 120 columns, the sidebar collapses to
an overlay toggled via ~/sidebar~ or ~Ctrl+X+B~. This preserves the
information radiator on constrained displays without sacrificing chat
area real estate.
State additions: ~:sidebar-visible~ (boolean), ~:block-counts~ (alist),
~:context-usage~ (integer 0-100), ~:modified-files~ (list of plists),
~:session-cost~ (plist).
** v0.8.0 — TrueColor Theme System
The existing theme system uses Croatoan's standard 8-color palette
(cyan, green, red, white, etc.). v0.8.0 upgrades to 24-bit TrueColor
via Croatoan's ~set-rgb~ / ~init-color~ primitives, enabling hex-specified
colors (#5E81AC, #BF616A, etc.) on supporting terminals (iTerm2, Kitty,
WezTerm, Windows Terminal, Ghostty).
The upgrade is backward compatible: terminals without TrueColor fall
back to the nearest standard color. Hex values are parsed by
~theme-hex-to-rgb~ (one-line format string → integer triple) and
registered once at theme-switch time via ~theme-init-truecolor~.
Subsequent ~theme-color~ lookups return the Croatoan color ID, same
API as the 8-color system.
Four new presets join the existing four (dark, light, solarized, gruvbox):
- ~:nord~ — blue-gray backgrounds, frost accent
- ~:tokyonight~ — purple-blue backgrounds, teal accent
- ~:catppuccin~ — warm pastels, mauve accent
- ~:monokai~ — dark brown backgrounds, orange accent
Each preset defines 27 hex color values, one per semantic key in
~*tui-theme*~. The 27 keys are:
roles (user, agent, system), content (input, timestamp, help, error,
warning), status (connected, disconnected, busy, idle), gate trace
(passed, blocked, approval, hitl), tools (running, success, failure,
output), display (scroll-indicator, border, background), differentiator
(rule-count, focus-map), and UI (dim, highlight, accent).
An audit ensures every key from ~*tui-theme*~ is consumed by at least one
rendering function in ~channel-tui-view.org~. Missing keys become invisible
theme presets — defined but unused.
** Contract
1. (init-state): returns a fresh state plist with ~:msgs~ list,
@@ -75,32 +15,17 @@ theme presets — defined but unused.
and optional gate-trace from the daemon (v0.4.0).
3. (queue-event ev): thread-safely enqueues an event for the
reader loop. (drain-queue) returns and clears the queue.
4. (theme-hex-to-rgb hex-string): parses ~"#RRGGBB"~ to
~(values r g b)~ integers 0-255. Returns ~(values 255 255 255)~
for unparseable input (v0.8.0).
5. (theme-init-truecolor): registers hex color values from
~*tui-theme*~ with Croatoan's ~init-color~ / ~set-rgb~. No-op
on terminals without TrueColor support (v0.8.0).
6. (theme-color key): extended contract (v0.8.0): if the ~*tui-theme*~
entry for ~key~ is a hex string, returns the Croatoan color ID
registered by ~theme-init-truecolor~. Falls back to keyword
lookup for non-hex entries and non-TrueColor terminals.
7. (sidebar-toggle): toggles ~:sidebar-visible~ state. Sets dirty
flags to force sidebar redraw (v0.8.0).
** Package + State
#+begin_src lisp
#+BEGIN_SRC lisp :tangle ../lisp/channel-tui-state.lisp
(defpackage :passepartout.channel-tui
(:use :cl :croatoan :passepartout :usocket :bordeaux-threads)
(:use :cl :passepartout :usocket :bordeaux-threads)
(:export :tui-main :st :add-msg :now :input-string
:queue-event :drain-queue :init-state
:view-status :view-chat :view-input :redraw
:on-key :on-daemon-msg :send-daemon
:connect-daemon :disconnect-daemon
:*tui-theme* :theme-color
:*slash-commands* :open-minibuffer :minibuffer-handle-key
:view-conversation :render-user-msg :render-agent-msg
:render-sys-msg :render-tool-call :render-gate-trace))
:*tui-theme* :theme-color))
(in-package :passepartout.channel-tui)
(defvar *state* nil)
@@ -125,7 +50,7 @@ theme presets — defined but unused.
:rule-count :cyan :focus-map :yellow
;; UI
:dim :white :highlight :cyan :accent :green)
"Color theme plist. 27 semantic keys → Croatoan color values.
"Color theme plist. 27 semantic keys → hex color strings.
See *tui-theme-presets* for named presets (dark, light, solarized, gruvbox).")
(defvar *tui-theme-presets*
@@ -160,43 +85,7 @@ See *tui-theme-presets* for named presets (dark, light, solarized, gruvbox).")
:tool-running "#d33682" :tool-success "#859900" :tool-failure "#dc322f" :tool-output "#839496"
:scroll-indicator "#2aa198" :border "#657b83" :background "#002b36"
:rule-count "#2aa198" :focus-map "#b58900"
:dim "#586e75" :highlight "#2aa198" :accent "#859900")
:nord (:user "#81a1c1" :agent "#d8dee9" :system "#ebcb8b"
:input "#d8dee9" :timestamp "#4c566a" :help "#88c0d0" :error "#bf616a" :warning "#ebcb8b"
:connected "#a3be8c" :disconnected "#bf616a" :busy "#b48ead" :idle "#616e88"
:gate-passed "#a3be8c" :gate-blocked "#bf616a" :gate-approval "#ebcb8b"
:hitl "#b48ead"
:tool-running "#b48ead" :tool-success "#a3be8c" :tool-failure "#bf616a" :tool-output "#d8dee9"
:scroll-indicator "#88c0d0" :border "#4c566a" :background "#2e3440"
:rule-count "#88c0d0" :focus-map "#ebcb8b"
:dim "#616e88" :highlight "#88c0d0" :accent "#5e81ac")
:tokyonight (:user "#7aa2f7" :agent "#c0caf5" :system "#e0af68"
:input "#c0caf5" :timestamp "#565f89" :help "#7dcfff" :error "#f7768e" :warning "#e0af68"
:connected "#9ece6a" :disconnected "#f7768e" :busy "#bb9af7" :idle "#565f89"
:gate-passed "#9ece6a" :gate-blocked "#f7768e" :gate-approval "#e0af68"
:hitl "#bb9af7"
:tool-running "#bb9af7" :tool-success "#9ece6a" :tool-failure "#f7768e" :tool-output "#c0caf5"
:scroll-indicator "#7dcfff" :border "#1f2335" :background "#1a1b26"
:rule-count "#7dcfff" :focus-map "#e0af68"
:dim "#565f89" :highlight "#7dcfff" :accent "#7aa2f7")
:catppuccin (:user "#89b4fa" :agent "#cdd6f4" :system "#f9e2af"
:input "#cdd6f4" :timestamp "#585b70" :help "#94e2d5" :error "#f38ba8" :warning "#f9e2af"
:connected "#a6e3a1" :disconnected "#f38ba8" :busy "#cba6f7" :idle "#6c7086"
:gate-passed "#a6e3a1" :gate-blocked "#f38ba8" :gate-approval "#f9e2af"
:hitl "#cba6f7"
:tool-running "#cba6f7" :tool-success "#a6e3a1" :tool-failure "#f38ba8" :tool-output "#cdd6f4"
:scroll-indicator "#94e2d5" :border "#45475a" :background "#1e1e2e"
:rule-count "#94e2d5" :focus-map "#f9e2af"
:dim "#6c7086" :highlight "#94e2d5" :accent "#89b4fa")
:monokai (:user "#a6e22e" :agent "#f8f8f2" :system "#e6db74"
:input "#f8f8f2" :timestamp "#75715e" :help "#66d9ef" :error "#f92672" :warning "#e6db74"
:connected "#a6e22e" :disconnected "#f92672" :busy "#ae81ff" :idle "#75715e"
:gate-passed "#a6e22e" :gate-blocked "#f92672" :gate-approval "#e6db74"
:hitl "#ae81ff"
:tool-running "#ae81ff" :tool-success "#a6e22e" :tool-failure "#f92672" :tool-output "#f8f8f2"
:scroll-indicator "#66d9ef" :border "#49483e" :background "#272822"
:rule-count "#66d9ef" :focus-map "#e6db74"
:dim "#75715e" :highlight "#66d9ef" :accent "#a6e22e"))
:dim "#586e75" :highlight "#2aa198" :accent "#859900"))
"Named theme presets. /theme <name> loads one into *tui-theme*.")
(defvar *tui-theme-current-name* :dark
@@ -232,40 +121,15 @@ See *tui-theme-presets* for named presets (dark, light, solarized, gruvbox).")
key)))
(defun theme-color (role)
"Returns the Croatoan color for a semantic role.
Keyword or hex string values are returned as-is; hex strings are
converted to integers that Croatoan can process."
"Returns a hex color string for a semantic role, suitable for cl-tty."
(let ((val (or (getf *tui-theme* role) :white)))
(if (and (stringp val) (> (length val) 0) (eql (char val 0) #\#))
(handler-case (parse-integer (subseq val 1) :radix 16)
(error () val))
val)))
;; v0.8.0: TrueColor helpers
(defun theme-hex-to-rgb (hex-string)
"Parse #RRGGBB to (values r g b). Returns (255 255 255) for invalid input."
(if (and (stringp hex-string) (= 7 (length hex-string)) (eql (char hex-string 0) #\#))
(handler-case
(let ((r (parse-integer (subseq hex-string 1 3) :radix 16))
(g (parse-integer (subseq hex-string 3 5) :radix 16))
(b (parse-integer (subseq hex-string 5 7) :radix 16)))
(values r g b))
(error () (values 255 255 255)))
(values 255 255 255)))
(defun theme-init-truecolor ()
"Register hex colors from *tui-theme* with Croatoan's init-color."
(handler-case
(loop for (key val) on *tui-theme* by #'cddr
when (and (stringp val) (= 7 (length val)) (eql (char val 0) #\#))
do (multiple-value-bind (r g b) (theme-hex-to-rgb val)
(init-color key (/ r 255.0) (/ g 255.0) (/ b 255.0))))
(error () nil)))
(defun sidebar-toggle ()
"Toggle sidebar visibility. Sets dirty flags for full redraw."
(setf (st :sidebar-visible) (not (st :sidebar-visible)))
(setf (st :dirty) (list t t t)))
(cond
((stringp val) val)
(t (case val
(:green "#00FF00") (:red "#FF0000") (:cyan "#00FFFF")
(:yellow "#FFFF00") (:magenta "#FF00FF") (:blue "#0000FF")
(:white "#FFFFFF") (:black "#000000")
(t "#FFFFFF"))))))
(defun st (key) (getf *state* key))
(defun (setf st) (val key) (setf (getf *state* key) val))
@@ -282,13 +146,11 @@ See *tui-theme-presets* for named presets (dark, light, solarized, gruvbox).")
:collapsed-gates nil ; v0.7.2
:search-mode nil :search-query "" ; v0.7.2
:search-matches nil :search-match-idx 0
:sidebar-visible nil ; v0.8.0
:expand-tool-calls nil ; v0.8.0
:dirty (list nil nil nil))))
#+end_src
#+END_SRC
** Helpers
#+begin_src lisp
#+BEGIN_SRC lisp :tangle ../lisp/channel-tui-state.lisp
(defun now ()
(multiple-value-bind (s m h) (get-decoded-time)
(declare (ignore s))
@@ -322,10 +184,10 @@ See *tui-theme-presets* for named presets (dark, light, solarized, gruvbox).")
(unless (st :scroll-at-bottom)
(setf (st :scroll-notify) t))
(setf (st :dirty) (list t t nil)))
#+end_src
#+END_SRC
** Event Queue
#+begin_src lisp
#+BEGIN_SRC lisp :tangle ../lisp/channel-tui-state.lisp
(defun queue-event (ev)
(bt:with-lock-held (*event-lock*) (push ev *event-queue*)))
@@ -333,4 +195,4 @@ See *tui-theme-presets* for named presets (dark, light, solarized, gruvbox).")
(bt:with-lock-held (*event-lock*)
(let ((evs (nreverse *event-queue*)))
(setf *event-queue* nil) evs)))
#+end_src
#+END_SRC

File diff suppressed because it is too large Load Diff