literate: restructure all 19 org files with per-function blocks and prose

Every function, defclass, defstruct, defgeneric, defmethod, defmacro,
defvar, and defparameter in every org file now has its own #+BEGIN_SRC
block with literate prose above it explaining the design reasoning.

Block counts before → after:
  package.org:           1 → 7
  container-package.org: 1 → 1 (prose expanded)
  dirty.org:             4 → 6
  render.org:           10 → 25
  theme.org:             6 → 19
  box-renderable.org:    9 → 29
  scrollbox.org:         8 → 26
  tabbar.org:            5 → 10
  backend-protocol.org:  8 → 66
  modern-backend.org:   17 → 53
  detection.org:         4 → 6
  layout-engine.org:     9 → 36
  framebuffer.org:       8 → 37
  markdown-renderer.org:13 → 38
  dialog.org:           17 → 23 (merged dual structure)
  mouse.org:             4 → 25
  select.org:           12 → 30
  slot.org:              4 → 12
  text-input.org:       11 → 53

Total: ~153 blocks → ~502 blocks

Bugs fixed during restructuring:
- render.org: stray π character typo (backenπd → backend)
- modern-backend.org: sgr-attr missing closing paren + #+END_SRC
- detection.org: invalid #\Esc character reference
- select.org: extra closing paren in select-visible-options

All 13 test suites pass at 100%.
This commit is contained in:
Hermes Agent
2026-05-12 18:55:07 +00:00
parent 927f786716
commit 29f99a576d
42 changed files with 4730 additions and 1745 deletions

View File

@@ -45,32 +45,75 @@ and the backend's ~*theme-colors*~ for SGR resolution.
* Tests
** Test header
Package declaration and test suite registration.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(in-package :cl-tty-box-test)
(in-suite box-suite)
#+END_SRC
** Test: theme-create-default
Verifies basic construction of a theme with default ~:dark~ mode. The
~make-theme~ constructor should return an instance of the ~theme~
class with ~:dark~ as the initial mode.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test theme-create-default
"A theme can be created with default mode"
(let ((th (make-theme)))
(is (typep th 'theme))
(is (eql (theme-mode th) :dark))))
#+END_SRC
** Test: theme-create-light
Verifies explicit ~:light~ mode works. Both modes must produce themes
ready to accept color role assignments.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test theme-create-light
"A theme can be created in light mode"
(let ((th (make-theme :mode :light)))
(is (eql (theme-mode th) :light))))
#+END_SRC
** Test: theme-color-set-and-get
Confirms ~setf~ on ~theme-color~ stores a value and that reading it
back returns the same string. This is the core read/write contract
for the theme's role map.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test theme-color-set-and-get
"theme-color setf/get works"
(let ((th (make-theme)))
(setf (theme-color th :primary) "#FFD700")
(is (string= (theme-color th :primary) "#FFD700"))))
#+END_SRC
** Test: theme-color-unknown-returns-nil
Unassigned roles must return ~nil~ rather than signaling an error.
This allows components to degrade gracefully when a theme doesn't
define every possible role.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test theme-color-unknown-returns-nil
"Unknown roles return nil"
(let ((th (make-theme)))
(is (null (theme-color th :nonexistent)))))
#+END_SRC
** Test: load-default-dark-preset
Loading the ~:default~ preset in ~:dark~ mode must populate a set of
expected roles with their documented hex values. We spot-check
~:primary~, ~:background~, and ~:error~.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test load-default-dark-preset
"Loading the default dark preset populates roles"
(let ((th (make-theme :mode :dark)))
@@ -78,27 +121,59 @@ and the backend's ~*theme-colors*~ for SGR resolution.
(is (string= (theme-color th :primary) "#FFD700"))
(is (string= (theme-color th :background) "#1A1A2E"))
(is (string= (theme-color th :error) "#FF4444"))))
#+END_SRC
** Test: load-default-light-preset
The light variant of ~:default~ must produce different values (warm
tones on near-white). This validates the mode dispatch inside
~load-preset~.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test load-default-light-preset
"Light variant has different colors"
(let ((th (make-theme :mode :light)))
(load-preset th :default)
(is (string= (theme-color th :primary) "#B8860B"))
(is (string= (theme-color th :background) "#F8F9FA"))))
#+END_SRC
** Test: load-nord-preset
The ~:nord~ preset must produce a distinct cool-blue palette,
different from the ~:default~ gold scheme. This validates independent
preset data.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test load-nord-preset
"Nord preset has different colors than default"
(let ((th (make-theme :mode :dark)))
(load-preset th :nord)
(is (string= (theme-color th :primary) "#88C0D0"))
(is (string= (theme-color th :background) "#2E3440"))))
#+END_SRC
** Test: load-preset-unknown-warns
An unknown preset name must signal a ~warning~ (not an ~error~) and
leave the theme's roles unpopulated. This ensures graceful degradation
when a preset is missing.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test load-preset-unknown-warns
"Unknown preset warns but doesn't error"
(let ((th (make-theme)))
(signals warning (load-preset th :nonexistent))
(is (null (theme-color th :primary)))))
#+END_SRC
** Test: preset-switch-mode
Switching the mode at runtime and re-loading the same preset must
produce the other variant's colors. This validates that ~load-preset~
reads the current ~theme-mode~ each time, not a cached value.
#+BEGIN_SRC lisp :tangle ../src/components/theme-tests.lisp
(test preset-switch-mode
"Switching mode and reloading changes colors"
(let ((th (make-theme :mode :dark)))
@@ -117,47 +192,84 @@ The ~theme~ class holds a mode flag (~:dark~/~:light~) and a hash
table of role→hex mappings. The hash table gives O(1) lookups for
~theme-color~ and clean iteration for ~load-preset~.
*** defclass theme
The class has two slots: ~mode~ (defaulting to ~:dark~, with an
~:initarg~ and ~accessor~ for reads and writes) and ~roles~ (a hash
table storing role→hex mappings, lazily initialized to an empty
hash table). Using ~make-hash-table~ as the ~:initform~ ensures each
instance gets its own table instead of sharing one.
#+BEGIN_SRC lisp :tangle ../src/components/theme.lisp
(in-package :cl-tty.box)
;; ── Theme Engine ──────────────────────────────────────────────
(defclass theme ()
((mode :initform :dark :initarg :mode :accessor theme-mode)
(roles :initform (make-hash-table) :accessor theme-roles)))
#+END_SRC
*** defun make-theme
A convenience constructor that delegates to ~make-instance~. Wrapping
this in a function lets us change the constructor signature without
breaking callers. Mode defaults to ~:dark~, suitable for dark-background
terminals; callers pass ~:mode :light~ for light backgrounds.
#+BEGIN_SRC lisp :tangle ../src/components/theme.lisp
(defun make-theme (&key (mode :dark))
(make-instance 'theme :mode mode))
#+END_SRC
The mode defaults to ~:dark~. Applications can initialize with
~:light~ for terminals with light backgrounds. The mode controls
which variant ~load-preset~ selects.
** Color resolution
*** defun theme-color
Reads a semantic role from the theme's roles hash table. Uses
~gethash~ which returns ~nil~ for unknown roles — so missing roles
degrade gracefully rather than crashing. The backend treats ~nil~ as
"use default."
#+BEGIN_SRC lisp :tangle ../src/components/theme.lisp
(defun theme-color (theme role)
"Resolve a semantic ROLE to a hex color string in THEME."
(gethash role (theme-roles theme)))
#+END_SRC
*** defun (setf theme-color)
The setter companion to ~theme-color~. Storing via ~setf~ writes
directly into the roles hash table. Uses ~setf~ on ~gethash~ which
creates the entry if it doesn't exist.
#+BEGIN_SRC lisp :tangle ../src/components/theme.lisp
(defun (setf theme-color) (hex theme role)
"Set the hex color for a semantic ROLE in THEME."
(setf (gethash role (theme-roles theme)) hex))
#+END_SRC
Uses ~gethash~ for both getter and setter. Unknown roles return ~nil~,
which the backend treats as "use default" — so missing roles degrade
gracefully rather than crashing.
** Global preset registry
** Preset system
A hash table (keyed by ~eq~-comparable keywords) stores all registered
presets. Using ~#\\'~ (quoted list) instead of an alist or nested hash
table keeps preset data inline and readable.
Presets are stored in a global hash table keyed by keyword name. The
~define-preset~ macro registers a preset at macro-expansion time.
*** defparameter *presets*
Global storage for preset definitions. The ~eq~ test matches keyword
identity, which is the fastest hash test for keywords.
#+BEGIN_SRC lisp :tangle ../src/components/theme.lisp
(defparameter *presets* (make-hash-table :test #'eq))
#+END_SRC
*** defmacro define-preset
Registers a preset by name (~keyword~) at macro-expansion time. The
~check-type~ enforces that names are keywords. The macro expands to a
~setf~ of ~gethash~, storing a plist of ~:dark~ and ~:light~ variants.
Using a quoted list (not an alist or hash) keeps the data compact.
#+BEGIN_SRC lisp :tangle ../src/components/theme.lisp
(defmacro define-preset (name &key dark light)
"Define a theme preset with DARK and LIGHT variants.
NAME should be a keyword (e.g., :default, :nord)."
@@ -165,9 +277,20 @@ NAME should be a keyword (e.g., :default, :nord)."
`(setf (gethash ,name *presets*) '(:dark ,dark :light ,light)))
#+END_SRC
Using ~#\'~ (quoted list) instead of an alist or hash table keeps the
preset data inline and easy to read. The ~eq~ hash table test matches
keyword identity.
** Loading presets
*** defun load-preset
The central function that applies a named preset to a theme. Does
double duty: populates the theme's role map and the backend's
~*theme-colors*~. This second step is what makes semantic colors work
at the SGR level — when the backend renders ~:accent~, it looks up
~*theme-colors*~ to get the hex, then generates the escape sequence.
The ~loop for (role hex) on colors by #'cddr~ iterates the plist in
pairs, setting both the theme entry and the backend entry. If the
preset doesn't exist, ~warn~ is called instead of ~error~ — a missing
preset shouldn't crash the application.
#+BEGIN_SRC lisp :tangle ../src/components/theme.lisp
(defun load-preset (theme preset-name)
@@ -188,18 +311,6 @@ color roles resolve to hex at SGR generation time."
(warn "Unknown preset: ~S" preset-name))))
#+END_SRC
~load-preset~ does double duty: it populates the theme's role map and
the backend's ~*theme-colors*~. This second step is what makes
semantic colors work at the SGR level — when the backend renders
~:accent~, it looks up ~*theme-colors*~ to get the hex, then
generates the escape sequence.
The ~loop for (role hex) on colors by #'cddr~ iterates the plist in
pairs, setting both the theme entry and the backend entry.
If the preset doesn't exist, ~warn~ is called instead of ~error~ — a
missing preset shouldn't crash the application.
** Built-in presets
Two presets are built in: