v0.10.1: architectural cleanup — full-frame redraw, explicit bg everywhere, :bg-input fallback

- 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).
This commit is contained in:
2026-05-16 09:03:59 -04:00
parent 0a0478f502
commit 2189745f40
3 changed files with 112 additions and 114 deletions

View File

@@ -1016,17 +1016,8 @@ Returns T on success, nil on failure. Does NOT wait or retry."
;; Guard w and h before render (resize or other code may have set them to nil)
(setq w (or (and (numberp w) (> w 0) w) 80)
h (or (and (numberp h) (> h 0) h) 24))
(when (and (or (first (st :dirty)) (second (st :dirty)) (third (st :dirty)))
(null (st :dialog-stack)))
(cl-tty.backend:begin-sync be)
(cl-tty.backend:draw-rect be 0 0 w h :bg (theme-color :bg))
(view-status be w h)
(view-chat be w h)
(view-input be w h)
(when (sidebar-visible-p w)
(view-sidebar be w h))
(cl-tty.backend:end-sync be)
(setf (st :dirty) (list nil nil nil)))
(unless (st :dialog-stack)
(redraw be w h))
(let ((ds (st :dialog-stack)))
(when ds
(cl-tty.backend:begin-sync be)
@@ -1038,42 +1029,42 @@ Returns T on success, nil on failure. Does NOT wait or retry."
(cnt (length filtered))
(filter (cl-tty.select:select-filter sel))
(mh (min 15 (+ 1 cnt)))
(top (max 0 (- h 4 mh)))
(top (max 0 (- h 7 mh)))
(bg-p (theme-color :bg-panel))
(sep-c (theme-color :separator)))
;; Fill minibuffer area with panel bg
(dotimes (r (min (- h 3 top) h))
(cl-tty.backend:draw-rect be 0 (+ top r) chat-w 1 :bg bg-p))
;; Top separator
(cl-tty.backend:draw-text be 0 top
(make-string chat-w :initial-element #\─)
sep-c nil)
(cl-tty.backend:draw-text be 1 top
(cl-tty.dialog:dialog-title dlg)
(theme-color :accent) nil)
;; Options
(let ((y-off 1))
(dolist (item filtered)
(let* ((display-idx (first item))
(option (third item))
(title (getf option :title))
(cat (getf option :category))
(sel-p (eql display-idx sel-idx))
(text (if cat (format nil " ~a" title)
(format nil " ~a~a" (if sel-p "▸ " " ") title)))
(row (+ top y-off)))
(when (>= row (1- h)) (return))
(cl-tty.backend:draw-text be 1 row text
(cond (cat (theme-color :text-muted))
(sel-p (theme-color :accent))
(t (theme-color :agent-fg)))
nil :bold sel-p)
(incf y-off))))
;; Filter prompt
(cl-tty.backend:draw-rect be 0 (- h 3) chat-w 1 :bg bg-p)
(cl-tty.backend:draw-text be 0 (- h 3)
(format nil "> ~a" (or filter ""))
(theme-color :input-prompt) nil))
;; Top separator
(cl-tty.backend:draw-text be 0 top
(make-string chat-w :initial-element #\─)
sep-c bg-p)
(cl-tty.backend:draw-text be 1 top
(cl-tty.dialog:dialog-title dlg)
(theme-color :accent) bg-p)
;; Options
(let ((y-off 1))
(dolist (item filtered)
(let* ((display-idx (first item))
(option (third item))
(title (getf option :title))
(cat (getf option :category))
(sel-p (eql display-idx sel-idx))
(text (if cat (format nil " ~a" title)
(format nil " ~a~a" (if sel-p "▸ " " ") title)))
(row (+ top y-off)))
(when (>= row (1- h)) (return))
(cl-tty.backend:draw-text be 1 row text
(cond (cat (theme-color :text-muted))
(sel-p (theme-color :accent))
(t (theme-color :agent-fg)))
bg-p :bold sel-p)
(incf y-off))))
;; Filter prompt
(cl-tty.backend:draw-rect be 0 (- h 3) chat-w 1 :bg bg-p)
(cl-tty.backend:draw-text be 0 (- h 3)
(format nil "> ~a" (or filter ""))
(theme-color :input-prompt) bg-p))
(cl-tty.backend:end-sync be))
(sleep 0.1)))
(progn (disconnect-daemon)))))

View File

@@ -42,13 +42,14 @@ All state mutation flows through event handlers in the controller.
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:text-muted "#808080"
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
:bg-input "#2e2e2e"
:error "#e06c75"
:tool-running "#fab283" :tool-done "#7fd88f" :tool-error "#e06c75"
:separator "#3c3c3c" :accent "#fab283" :dim "#606060")
"Dark-neutral color theme with warm amber accent. Backgrounds are dark grays,
semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:text-muted, :user-fg/bg/border, :agent-header/fg, :system, :input-prompt/fg,
:hint, :status-bg/fg, :dot-connected/disconnected, :error, :tool-*,
:hint, :status-bg/fg, :bg-input, :dot-connected/disconnected, :error, :tool-*,
:separator, :accent, :dim.")
(defvar *tui-theme-presets*
@@ -60,6 +61,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:hint "#606060"
:status-bg "#141414" :status-fg "#e8e8e8"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
:error "#e06c75"
@@ -73,6 +75,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:hint "#606060"
:status-bg "#141414" :status-fg "#ffd700"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#7fd88f" :dot-disconnected "#e06c75"
:error "#e06c75"
@@ -86,6 +89,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:hint "#606060"
:status-bg "#141414" :status-fg "#d4956a"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#6cb85c" :dot-disconnected "#d94a3a"
:error "#d94a3a"
@@ -99,6 +103,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:hint "#606060"
:status-bg "#141414" :status-fg "#b89870"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#7aac5c" :dot-disconnected "#c84a3a"
:error "#c84a3a"
@@ -112,6 +117,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:hint "#606060"
:status-bg "#141414" :status-fg "#c8a080"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#7cb860" :dot-disconnected "#d06050"
:error "#d06050"
@@ -125,6 +131,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:hint "#606060"
:status-bg "#141414" :status-fg "#cc9966"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#7ab85c" :dot-disconnected "#d94a3a"
:error "#d94a3a"
@@ -138,6 +145,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:hint "#606060"
:status-bg "#141414" :status-fg "#c8a070"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1e1e1e"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#7ab85c" :dot-disconnected "#d94a3a"
:error "#d94a3a"
@@ -150,7 +158,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:input-prompt "#cc6600" :input-fg "#3a2a1a"
:hint "#a0a0a0"
:status-bg "#ebebeb" :status-fg "#3a2a1a"
:bg "#ffffff" :bg-panel "#f5f5f5" :bg-element "#ebebeb"
:bg "#ffffff" :bg-panel "#f5f5f5" :bg-element "#ebebeb"
:bg-input "#d4d4d4"
:text-muted "#808080"
:dot-connected "#2e8b57" :dot-disconnected "#cc3300"
:error "#cc3300"
@@ -163,7 +172,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:input-prompt "#fab387" :input-fg "#cdd6f4"
:hint "#6c7086"
:status-bg "#181825" :status-fg "#a6adc8"
:bg "#11111b" :bg-panel "#181825" :bg-element "#1e1e2e"
:bg "#11111b" :bg-panel "#181825" :bg-element "#1e1e2e"
:bg-input "#2e2e2e"
:text-muted "#6c7086"
:dot-connected "#a6e3a1" :dot-disconnected "#f38ba8"
:error "#f38ba8"
@@ -176,7 +186,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:input-prompt "#ff9e64" :input-fg "#a9b1d6"
:hint "#565f89"
:status-bg "#16161e" :status-fg "#9aa5ce"
:bg "#0f0f18" :bg-panel "#16161e" :bg-element "#1a1b26"
:bg "#0f0f18" :bg-panel "#16161e" :bg-element "#1a1b26"
:bg-input "#2e2e2e"
:text-muted "#565f89"
:dot-connected "#9ece6a" :dot-disconnected "#db4b4b"
:error "#db4b4b"
@@ -189,7 +200,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:input-prompt "#ff9580" :input-fg "#f8f8f2"
:hint "#6272a4"
:status-bg "#191a24" :status-fg "#e0e0e0"
:bg "#0f101a" :bg-panel "#191a24" :bg-element "#1e1f2b"
:bg "#0f101a" :bg-panel "#191a24" :bg-element "#1e1f2b"
:bg-input "#2e2e2e"
:text-muted "#6272a4"
:dot-connected "#50fa7b" :dot-disconnected "#ff5555"
:error "#ff5555"
@@ -202,7 +214,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:input-prompt "#87afff" :input-fg "#ffffff"
:hint "#606060"
:status-bg "#141414" :status-fg "#afafaf"
:bg "#000000" :bg-panel "#141414" :bg-element "#1a1a1a"
:bg "#000000" :bg-panel "#141414" :bg-element "#1a1a1a"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#d7ffd7" :dot-disconnected "#ff87af"
:error "#ff87af"
@@ -215,7 +228,8 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:input-prompt "#ffffff" :input-fg "#d0d0d0"
:hint "#606060"
:status-bg "#141414" :status-fg "#b0b0b0"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1a1a1a"
:bg "#0a0a0a" :bg-panel "#141414" :bg-element "#1a1a1a"
:bg-input "#2e2e2e"
:text-muted "#808080"
:dot-connected "#a0a0a0" :dot-disconnected "#808080"
:error "#808080"
@@ -223,7 +237,7 @@ semantic text colors for context. Keys: :bg (deepest), :bg-panel, :bg-element,
:separator "#303030" :accent "#ffffff" :dim "#505050"))
"13 theme presets (amber, gold, terracotta, sepia, nord-warm,
monokai-warm, gruvbox-warm, light-amber, catppuccin, tokyonight, dracula,
gemini, mono). Now with dark-neutral backgrounds and new :bg/:bg-panel/:bg-element/:text-muted slots.")
gemini, mono). Keys: :bg/:bg-panel/:bg-element/:bg-input/:text-muted.")
(defvar *tui-theme-current-name* :amber
"Name of the currently active theme preset.")
@@ -240,11 +254,19 @@ gemini, mono). Now with dark-neutral backgrounds and new :bg/:bg-panel/:bg-eleme
t))
(defun theme-load ()
"Load persisted theme from disk. Called at startup."
"Load persisted theme from disk. Called at startup.
Adds any missing keys with defaults to handle saved themes from older versions."
(let ((path (merge-pathnames ".cache/passepartout/theme.lisp"
(user-homedir-pathname))))
(when (uiop:file-exists-p path)
(ignore-errors (load path)))))
(ignore-errors (load path)))
;; Fill in any missing keys from the default preset
(let ((defaults (getf *tui-theme-presets* *tui-theme-current-name*)))
(when defaults
(dolist (key '(:bg-input :bg-element :text-muted))
(unless (getf *tui-theme* key)
(let ((val (getf defaults key)))
(when val (setf (getf *tui-theme* key) val)))))))))
(defun theme-switch (name)
"Switch to a named theme preset. Returns the preset name or nil if not found."
@@ -298,18 +320,6 @@ gemini, mono). Now with dark-neutral backgrounds and new :bg/:bg-panel/:bg-eleme
:dirty (list nil nil nil))))
#+END_SRC
** Sidebar panel definitions
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
(defvar *sidebar-panels*
'((:id :gate-trace :title "Gate Trace" :width 28)
(:id :focus :title "Focus" :width 28)
(:id :rules :title "Rules" :width 28)
(:id :context :title "Context" :width 28)
(:id :cost :title "Cost" :width 28)
(:id :files :title "Files" :width 28))
"Sidebar panel definitions for cl-tty slot registrations.")
#+END_SRC
** Helpers
#+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-state.lisp
(defun now ()

View File

@@ -8,21 +8,19 @@
** Contract
1. (view-status win): renders the status bar with connection info,
msg count, scroll offset, rule counter, focus map (v0.4.0), and
timestamp. Two lines: line 1 (status + rules), line 2 (focus + time).
2. (view-chat win h): renders the scrolled chat message list. Takes
window and available height. Messages are color-coded: green (user),
white (agent), yellow (system).
3. (view-input win): renders the input line with cursor and typing
indicator.
4. (redraw sw cw ch iw): dispatches redraws based on ~(st :dirty)~
flags (status, chat, input). Minimizes terminal writes.
5. (char-width ch): returns the terminal column width of character CH.
1. (view-status fb w h): no-op. Status bar is a clean black line.
2. (view-chat fb w h): renders scrolled chat messages. User messages
get amber left border (│), agent messages no border, streaming
agent gets grey left border. Gate traces/tool calls use ╎ prefix.
3. (view-input fb w h): renders light grey input box (h-7 to h-4),
prompt at h-6, right-aligned lowercase hint at h-2.
4. (redraw fb w h): wraps view-status/chat/input in begin-sync/end-sync,
dispatches per dirty flags, fills global :bg first.
5. (char-width ch): returns terminal column width of character CH.
ASCII < 128 = 1. CJK, fullwidth, emoji = 2. Combining marks = 0.
Tab = 8. Used by word-wrap for accurate line counting (v0.7.0).
6. (view-status win): v0.7.0 — timestamp right-aligned at (- chat-w 12)
on line 2, focus info at :x 1. No overlap.
6. (sidebar-visible-p w): returns T if sidebar should show given width W
and current :sidebar-mode (:auto >120, :visible always, :hidden never).
** Status Bar
@@ -92,11 +90,11 @@ Returns a list of strings, one per line."
(defun view-chat (fb w h)
(let* ((w (or (and (numberp w) (> w 0) w) 80))
(h (or (and (numberp h) (> h 0) h) 24))
(hpad 2)
(sidebar-w (if (sidebar-visible-p w) (or (st :sidebar-width) 42) 0))
(chat-w (- w sidebar-w))
(msgs (st :messages)) (total (length msgs))
(max-lines (- h 4)) (is-search (st :search-mode))
(hpad 2)
(sidebar-w (if (sidebar-visible-p w) (or (st :sidebar-width) 42) 0))
(chat-w (- w sidebar-w))
(msgs (st :messages)) (total (length msgs))
(max-lines (- h 7)) (is-search (st :search-mode))
(bordered-w (- chat-w (* 2 hpad) 2))
(unbordered-w (- chat-w (* 2 hpad)))
(y 0))
@@ -113,7 +111,7 @@ Returns a list of strings, one per line."
(content (getf msg :content))
(cs (if is-search (search-highlight content (st :search-query)) content))
(pairs nil)
(dim-bg (theme-color :dim))
(dim-fg (theme-color :dim))
(user-bdr (theme-color :user-border))
(user-fg (theme-color :user-fg))
(agent-fg (theme-color :agent-fg))
@@ -124,7 +122,7 @@ Returns a list of strings, one per line."
(push (list "│" user-bdr l user-fg) pairs)))
(:agent
(let* ((streaming (getf msg :streaming))
(bdr-color (if streaming dim-bg nil))
(bdr-color (if streaming dim-fg nil))
(bdr-str (if streaming "│" ""))
(wrap-w (if streaming bordered-w unbordered-w))
(nodes (cl-tty.markdown:parse-blocks cs))
@@ -138,18 +136,18 @@ Returns a list of strings, one per line."
(let ((gt (getf msg :gate-trace)))
(when (and gt (eq role :agent))
(if (member i (st :collapsed-gates))
(push (list "╎" dim-bg (format nil "Gate trace: ~a gates" (length gt)) dim-bg) pairs)
(push (list "╎" dim-fg (format nil "Gate trace: ~a gates" (length gt)) dim-fg) pairs)
(dolist (entry (passepartout::gate-trace-lines gt))
(let ((ec (theme-color (getf (cdr entry) :fgcolor))))
(dolist (l (cl-tty.box:word-wrap (car entry) bordered-w))
(push (list "╎" dim-bg l ec) pairs)))))))
(push (list "╎" dim-fg l ec) pairs)))))))
;; Tool calls
(let ((tc (getf msg :tool-calls)))
(when tc
(if (member i (st :collapsed-tools))
(let* ((n (or (getf (first tc) :name) "tool"))
(d (or (getf (first tc) :duration) 0.0)))
(push (list "╎" dim-bg (format nil "~a … ~,1fs" n d) (theme-color :tool-done)) pairs))
(push (list "╎" dim-fg (format nil "~a … ~,1fs" n d) (theme-color :tool-done)) pairs))
(dolist (call tc)
(let* ((name (or (getf call :name) "tool"))
(dur (or (getf call :duration) 0.0))
@@ -169,16 +167,16 @@ Returns a list of strings, one per line."
(let ((msg-count 0) (lines-remaining max-lines))
(loop for i from (1- total) downto 0
while (> lines-remaining 0)
do (let ((h (aref msg-heights i)))
(if (<= h lines-remaining)
(progn (decf lines-remaining h) (incf msg-count))
do (let ((mh (aref msg-heights i)))
(if (<= mh lines-remaining)
(progn (decf lines-remaining mh) (incf msg-count))
(setf lines-remaining 0))))
(let* ((scroll-skip (st :scroll-offset))
(start (max 0 (- total msg-count scroll-skip))))
(loop for i from start below total while (< y (- h 4))
do (let ((pairs (aref msg-lines i)))
(dolist (pair pairs)
(when (>= y (- h 4)) (return))
(loop for i from start below total while (< y (- h 7))
do (let ((pairs (aref msg-lines i)))
(dolist (pair pairs)
(when (>= y (- h 7)) (return))
(destructuring-bind (bstr bcolor tstr tcolor) pair
(let ((has-border (and bstr (> (length bstr) 0))))
(when has-border
@@ -201,19 +199,16 @@ Returns a list of strings, one per line."
(pos (or (st :cursor-pos) 0))
(display-start (max 0 (- pos (1- prompt-w))))
(visible (subseq text display-start (min (length text) (+ display-start prompt-w))))
(bg-p (theme-color :bg-panel))
(sep-c (theme-color :separator))
(input-fg (theme-color :input-fg))
(hint-fg (theme-color :hint)))
;; Fill the 2-line input area (separator + prompt) with panel bg, indented by hpad
(cl-tty.backend:draw-rect fb hpad (- h 4) inner-w 2 :bg bg-p)
;; Separator line within the panel
(cl-tty.backend:draw-text fb hpad (- h 4) (make-string inner-w :initial-element #\─) sep-c nil)
;; Input line
(cl-tty.backend:draw-text fb hpad (- h 3) (format nil"> ~a" visible) input-fg nil)
;; Hint line — right-aligned on black background at the very bottom
(let ((hint "Ctrl+P | /help"))
(cl-tty.backend:draw-text fb (- chat-w (length hint) 2) (- h 1) hint hint-fg (theme-color :bg)))))
(bg-i (theme-color :bg-input))
(input-fg (theme-color :input-fg))
(hint-fg (theme-color :hint)))
;; Light grey input panel: h-7 to h-4 (4 rows), indented by hpad
(cl-tty.backend:draw-rect fb hpad (- h 7) inner-w 4 :bg bg-i)
;; Prompt at h-6, second row at h-5 (placeholder for expansion)
(cl-tty.backend:draw-text fb hpad (- h 6) (format nil"> ~a" visible) input-fg nil)
;; Hint — lowercase, right-aligned at h-2
(let ((hint "ctrl+p | /help"))
(cl-tty.backend:draw-text fb (- chat-w (length hint) 2) (- h 2) hint hint-fg (theme-color :bg)))))
#+end_src
** Sidebar
@@ -275,14 +270,15 @@ Returns a list of strings, one per line."
(defun redraw (fb w h)
(setq w (or (and (numberp w) (> w 0) w) 80)
h (or (and (numberp h) (> h 0) h) 24))
(destructuring-bind (sd cd id) (st :dirty)
;; Fill global background
(when (or (first (st :dirty)) (second (st :dirty)) (third (st :dirty)))
(cl-tty.backend:begin-sync fb)
(cl-tty.backend:draw-rect fb 0 0 w h :bg (theme-color :bg))
(when sd (view-status fb w h))
(when cd (view-chat fb w h))
(when id (view-input fb w h))
(view-status fb w h)
(view-chat fb w h)
(view-input fb w h)
(when (sidebar-visible-p w)
(view-sidebar fb w h))
(cl-tty.backend:end-sync fb)
(setf (st :dirty) (list nil nil nil))))
#+END_SRC
@@ -380,7 +376,7 @@ dead code.
(cl-tty.backend:draw-text fb x y text
(cond (url (passepartout.channel-tui:theme-color :accent))
(t (passepartout.channel-tui:theme-color (or (getf attrs :role) :agent-fg))))
nil
(passepartout.channel-tui:theme-color :bg)
:bold bold)
(incf x (length text))))
y)
@@ -628,9 +624,10 @@ and current sidebar mode."
(is (getf passepartout.channel-tui::*tui-theme* :status-bg)))
(test test-new-theme-keys
"v0.10.0: theme has new :bg, :bg-panel, :bg-element, :text-muted keys."
"v0.10.0: theme has new :bg, :bg-panel, :bg-element, :bg-input, :text-muted keys."
(is (getf passepartout.channel-tui::*tui-theme* :bg))
(is (getf passepartout.channel-tui::*tui-theme* :bg-panel))
(is (getf passepartout.channel-tui::*tui-theme* :bg-element))
(is (getf passepartout.channel-tui::*tui-theme* :bg-input))
(is (getf passepartout.channel-tui::*tui-theme* :text-muted)))
#+END_SRC