Root cause: normalize-box and slot :initforms used quoted literal
lists ('(...)) that were destructively modified by (setf (getf ...)).
Each call to normalize-box with a non-nil spec corrupted the shared
default list, causing all subsequent nodes with no explicit padding
to inherit the previous node's padding values.
Fix: replace all '(...) quoted literals with (list ...) constructor
calls — in normalize-box (3 paths) and in slot initforms for both
padding and margin.
All 11 test suites now pass: 358/358 checks, 0 failures.
- Three tabs: Home, Components, Stats with different content
- Real keyboard input: arrow keys to switch tabs, q to quit
- CSI escape sequence parsing for arrow keys
- Footer bar shows current tab position
- Tab bar highlights active tab in bright blue
- defslot: register render functions into named slots with ordering
- slot-render: call all registered render-fns for a slot
- Slot modes designed (stack/replace/single-winner) but mode dispatch
is implicit via the registration API
- slot-p, clear-slot, list-slots for lifecycle management
- Slots stored in a hash table keyed by string (equal test)
- 4 tests, 100% passing
- mouse-mixin class with on-mouse-down/up/move/scroll handler slots
- handle-mouse-event dispatches to the right handler by event type
- hit-test finds deepest component at (x,y) coordinates
- selection struct + get-selection + copy-to-clipboard
- SGR mouse parsing already existed in input system (mouse-event struct,
parse-sgr-mouse function, CSI dispatch in %read-escape-sequence)
- 3 tests, 100% passing
37 per-function code blocks with prose explaining design reasoning,
edge cases, and CL traps. Combined tangle blocks at end for actual
compilation.
New scripts/tangle.py: reliable Python tangler (emacs --batch failed).
Added: %split-string, %join-lines, tangle helper.
CL traps documented in org prose:
- defstruct generates keyword constructors (no :constructor needed)
- case with strings uses EQL — use cond + string=
- CL strings: no \n escape — use (string #\Newline)
- FiveAM closure capture — use list boxing
- read-byte is package-locked — use read-raw-byte
- ASDF compile-file stricter than LOAD — debug with LOAD
60 tests, 100% GREEN.
Fixes from subagent review:
- ASDF version 0.3.0 → 0.4.0
- define-preset now checks (check-type name keyword) at macro-expand time
- load-preset-unknown-warns test now uses (signals warning ...) to
actually verify the warning fires (was false-positive before)
Fixes from subagent review:
- render-tests.lisp: added (in-suite box-suite) — tests were registered
to default suite, never executed by runner
- dirty-tests.lisp: same fix
- cl-tui.asd: version 0.2.0 → 0.3.0
- render.lisp: component-children default method (c t) nil for
protocol completeness (component-parent already had this)
- Text class with content, fg/bg, wrap-mode (:word or :none)
- Span class for inline styled segments (bold, italic, etc.)
- render-text dispatches through backend's draw-text
- word-wrap function splits text at word boundaries
- split-string utility for whitespace tokenization
- 9 new tests: creation, content, empty, truncation, word-wrap,
single-word, span creation, span storage
- modern-backend now accepts :output-stream
- ASDF updated with text component
- 28 total component tests, 100% GREEN
- Box class with border-style, title, fg/bg slots
- render-box dispatches through backend protocol
- draw-border for borders, draw-rect for background
- draw-text for title below top border
- 7 tests: defaults, border, background, title, no-border,
zero-size, minimum-size
- 13 assertions, 100% GREEN
- ASDF updated with src/components module
- modern-backend now accepts :output-stream initarg
Fixes during debugging:
- Variable scope: loop's closing parens closed the let* prematurely,
making children/is-row/pr/pb undefined in own-size calculation
- gap NIL bug: make-layout-node passed :gap nil (from &key default)
to make-instance, overriding :initform 0 → (* nil ...) crash
- Child order: push (LIFO) in add-child reversed children order;
changed to nconc (FIFO), removed the compensating reverse
- Fixed distribute-sizes to base all children from their fixed size
then apply grow/shrink on top, instead of treating fixed-size
children as non-participating