implement: slot modes (:stack, :replace, :single-winner)

Add :mode parameter to defslot with three behaviors:
- :stack (default) — accumulate all registrations, render in order
- :replace — each registration replaces previous entries
- :single-winner — first registration wins, rest ignored

Mode is set on first defslot call and frozen for subsequent calls
to prevent conflicting mode specifications from different plugins.

Store slot data as plist (:mode <keyword> :entries <list>) instead
of bare entries list.

Add 5 new tests covering mode-specific behavior. All 9 slot tests
pass. All 13 suites pass at 100%.
This commit is contained in:
Hermes Agent
2026-05-12 19:17:24 +00:00
parent a9670a5cd7
commit 6cd045ff59
3 changed files with 249 additions and 71 deletions

View File

@@ -1,24 +1,49 @@
(in-package :cl-tty.slot)
(defvar *slots* (make-hash-table :test #'equal)
"Hash table mapping slot name (string) -> list of (order . render-fn) pairs.")
(defvar *slots* (make-hash-table :test 'equal)
"Hash table mapping slot name (string) -> plist of slot data.
Each entry: (:mode <mode> :entries <(order . render-fn) list>).")
(defun defslot (name &key (order 0) render-fn)
(defun defslot (name &key (order 0) render-fn (mode :stack))
(let* ((key (string name))
(entries (gethash key *slots*)))
(if (null entries)
(setf (gethash key *slots*) (list (cons order render-fn)))
(slot (gethash key *slots*)))
(if (null slot)
;; First registration — set mode and create entry
(setf (gethash key *slots*)
(sort (cons (cons order render-fn) entries) #'< :key #'car))))
(list :mode mode
:entries (list (cons order render-fn))))
;; Existing slot — respect frozen mode
(let ((entries (getf slot :entries)))
(ecase (getf slot :mode)
(:stack
(setf (getf slot :entries)
(sort (cons (cons order render-fn) entries)
#'< :key #'car)))
(:replace
(setf (getf slot :entries)
(list (cons order render-fn))))
(:single-winner
;; First registration already present — no-op
(values))))))
render-fn)
(defun slot-render (slot-name &rest args)
(let ((entries (gethash (string slot-name) *slots*)))
(when entries
(mapcar (lambda (entry)
(let ((fn (cdr entry)))
(when fn (apply fn args))))
entries))))
(let ((slot (gethash (string slot-name) *slots*)))
(when slot
(let ((mode (getf slot :mode))
(entries (getf slot :entries)))
(ecase mode
(:stack
(mapcar (lambda (entry)
(let ((fn (cdr entry)))
(when fn (apply fn args))))
entries))
(:replace
(let ((fn (cdar (last entries))))
(when fn (apply fn args))))
(:single-winner
(let ((fn (cdar entries)))
(when fn (apply fn args)))))))))
(defun slot-p (slot-name)
(nth-value 1 (gethash (string slot-name) *slots*)))