v1.0.0: merge container (scrollbox + tabbar) into cl-tty.box

Eliminates the cl-tty.container package by merging scrollbox and tabbar
components directly into cl-tty.box, where the component system lives.

Changes:
- added scrollbox/tabbar exports to cl-tty.box defpackage in package.org
- changed scrollbox.org in-package from cl-tty.container to cl-tty.box
- changed tabbar.org in-package from cl-tty.container to cl-tty.box
- tabbar's key-event-key references are qualified with cl-tty.input:
  (avoids circular :use dependency with cl-tty.input which :uses cl-tty.box)
- deleted container-package.org
- updated test packages, integration tests, scripts, ASDF
- all 14 test suites pass at 100%
This commit is contained in:
2026-05-18 16:45:50 -04:00
parent 108abd054f
commit ef613927e6
8 changed files with 20 additions and 141 deletions

View File

@@ -33,10 +33,9 @@
(:file "text-input" :depends-on ("input-package" "input" "box"))
(:file "textarea" :depends-on ("input-package" "input" "box"))
(:file "keybindings" :depends-on ("input-package" "input"))
;; Container components (v0.6.0)
(:file "container-package" :depends-on ("package" "input-package"))
(:file "scrollbox" :depends-on ("container-package" "dirty" "box"))
(:file "tabbar" :depends-on ("container-package" "dirty" "box"))
;; Container components merged into box (v0.6.0)
(:file "scrollbox" :depends-on ("package" "dirty" "box"))
(:file "tabbar" :depends-on ("package" "dirty" "box"))
;; Markdown + Code + Diff rendering (v0.8.0)
(:file "markdown-package" :depends-on ("package"))
(:file "markdown" :depends-on ("markdown-package"))

View File

@@ -1,127 +0,0 @@
#+TITLE: Container Package
#+STARTUP: content
#+FILETAGS: :cl-tty:container:
* Overview
The ~cl-tty.container~ package defines the container component types:
ScrollBox and TabBar. It uses ~cl-tty.backend~, ~cl-tty.box~,
~cl-tty.layout~, and ~cl-tty.input~.
The package exports both ScrollBox and TabBar classes, constructors,
accessors, and navigation functions.
* Why a Separate Package?
The base ~cl-tty.box~ package was designed for the fundamental
renderable types — box, text, spans, dirty-tracking, the render
pipeline, and the theme engine. These are the building blocks that
virtually every component depends on. Container components —
ScrollBox and TabBar — are higher-level composite widgets with
specific behavioral contracts (viewport scrolling, tab navigation,
keyboard dispatch) that are not needed by every component user.
Separating them into ~cl-tty.container~ achieves two things:
1. It keeps ~cl-tty.box~ lean. Users who only need basic
renderables (boxes, text) do not pull in scroll-logic or
tab-navigation code. This is especially important for the
test suite — container tests have their own setup, backend
capture, and assertion patterns that are unrelated to the
base component tests.
2. It establishes a clean dependency boundary. ~cl-tty.box~
depends only on ~cl-tty.backend~ and ~cl-tty.layout~.
Container components additionally depend on ~cl-tty.input~,
because TabBar handles key events. By putting container
code in its own package, we avoid creating a circular or
incidental dependency between the input system and the
base component layer.
* What the Container Package Provides
The package exports two full component families:
- **ScrollBox**: A viewport-based container that holds a list of
child components and provides vertical/horizontal scrolling with
viewport culling (only visible children are rendered), scrollbar
display, sticky-scroll (auto-scroll to bottom on new content),
and scroll-offset clamping. ScrollBox inherits ~dirty-mixin~,
implements the component protocol (~render~, ~component-children~,
~component-layout-node~), and integrates with the layout engine.
Its constructor ~make-scroll-box~ accepts ~:children~,
~:scroll-y~, ~:scroll-x~, and ~:sticky-scroll-p~ keyword args.
- **TabBar**: A horizontal tab-navigation widget that manages a
list of named tabs, tracks the active tab, and dispatches
keyboard events (Left/Right for prev/next). TabBar also inherits
~dirty-mixin~ and implements ~render~ and ~component-layout-node~.
It provides ~tab-bar-add~ for dynamic tab creation, ~tab-bar-next~
/ ~tab-bar-prev~ for cycling, ~tab-bar-select~ for direct
activation, and ~tab-bar-handle-key~ for keyboard integration.
Both components export the generic ~render~ method, allowing the
rendering pipeline to dispatch ~(render instance backend)~ uniformly.
* Design Decisions: ScrollBox and TabBar in One Package
ScrollBox and TabBar are very different widgets — one manages a
scrollable viewport, the other renders a row of selectable labels.
They are kept in the same package rather than split into
~cl-tty.scroll-box~ and ~cl-tty.tab-bar~ for several reasons:
1. **Shared dependencies**: Both components :use the same four
packages (~cl-tty.backend~, ~cl-tty.box~, ~cl-tty.layout~,
~cl-tty.input~). They both inherit from ~dirty-mixin~ and
implement the component protocol. A shared package avoids
duplicating the ~:use~ and ~:export~ boilerplate.
2. **Co-located tests**: The test suite
(~tests/scrollbox-tabbar-tests.lisp~) tests both components
in one file and one FiveAM suite. They share test helpers,
backend-capture patterns, and the same package dependency.
Keeping them in one source package means the test defpackage
only needs one ~:use~ clause for the container, and symbols
from both components are visible together.
3. **Common contract**: Both components are "containers" in the
architectural sense — they manage a collection of sub-items
(children or tabs) and provide navigation over them. A
TabBar is conceptually a horizontal container of selectable
entries; a ScrollBox is a vertical container with scroll.
Placing them under the same ~:cl-tty.container~ namespace
signals to users that these are the composite widget types,
as opposed to the atomic renderables in ~:cl-tty.box~.
4. **Practical usage patterns**: In typical TUI applications, a
TabBar switches between views and a ScrollBox displays the
content of each view. They are often used together in the
same composition. Having them in one package eliminates
cross-package qualification or redundant ~:import-from~
declarations when building combined layouts.
If either component grows substantial internal logic in the future
(say, ScrollBox develops virtual scrolling, infinite loading, or
its own input model), it could be split into its own package at
that point. The current scope favors simplicity and co-location.
* Package Definition
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/container-package.lisp
(defpackage :cl-tty.container
(:use :cl :cl-tty.backend :cl-tty.box :cl-tty.layout :cl-tty.input)
(:export
;; ScrollBox
#:scroll-box #:make-scroll-box
#:scroll-box-scroll-y #:scroll-box-scroll-x
#:scroll-box-children
#:scroll-by #:sticky-scroll-p
#:clamp-scroll
;; TabBar
#:tab-bar #:make-tab-bar
#:tab-bar-active #:tab-bar-tabs
#:tab-bar-add #:tab-bar-next #:tab-bar-prev
#:tab-bar-select #:tab-bar-handle-key
;; Rendering
#:render))
#+END_SRC

View File

@@ -50,7 +50,7 @@ package, so the symbol must be interned and accessible.
(defpackage :cl-tty-integration-test
(:use :cl :fiveam
:cl-tty.backend :cl-tty.box :cl-tty.layout
:cl-tty.input :cl-tty.container
:cl-tty.input
:cl-tty.rendering :cl-tty.dialog))
(in-package :cl-tty-integration-test)

View File

@@ -175,6 +175,15 @@ means themes can be swapped without touching component instances.
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/package.lisp
;; Theme engine
#:theme #:make-theme #:theme-mode
#:theme-color #:load-preset #:define-preset))
(in-package :cl-tty.box)
#:theme-color #:load-preset #:define-preset
;; Container components (merged from cl-tty.container)
#:scroll-box #:make-scroll-box
#:scroll-box-scroll-y #:scroll-box-scroll-x
#:scroll-box-children
#:scroll-by #:sticky-scroll-p
#:clamp-scroll
#:tab-bar #:make-tab-bar
#:tab-bar-active #:tab-bar-tabs
#:tab-bar-add #:tab-bar-next #:tab-bar-prev
#:tab-bar-select #:tab-bar-handle-key))
#+END_SRC

View File

@@ -46,7 +46,7 @@ the CLOS-based component protocol — ~render~ dispatches on the class,
and dirty-mixin provides the marking machinery used by the refresh loop.
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/scrollbox.lisp
(in-package #:cl-tty.container)
(in-package :cl-tty.box)
(defclass scroll-box (dirty-mixin)
((children :initform nil :initarg :children
@@ -344,7 +344,7 @@ unconditionally; it runs the ~scrollbox-suite~ and prints results via
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/tests/scrollbox-tabbar-tests.lisp
(defpackage :cl-tty-scrollbox-test
(:use :cl :fiveam :cl-tty.backend :cl-tty.box :cl-tty.layout :cl-tty.input :cl-tty.container)
(:use :cl :fiveam :cl-tty.backend :cl-tty.box :cl-tty.layout :cl-tty.input)
(:export #:run-tests))
(in-package #:cl-tty-scrollbox-test)

View File

@@ -33,7 +33,7 @@ the symbol namespace clean and avoids accidental collisions with
user-level code.
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/tabbar.lisp
(in-package #:cl-tty.container)
(in-package :cl-tty.box)
#+END_SRC
** TabBar class
@@ -168,7 +168,7 @@ bar lives alongside other focusable elements.
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/tabbar.lisp
(defun tab-bar-handle-key (tb event)
"Handle a key-event on a TabBar. Returns T if handled."
(case (key-event-key event)
(case (cl-tty.input:key-event-key event)
(:left (tab-bar-prev tb) t)
(:right (tab-bar-next tb) t)
(t nil)))

View File

@@ -29,7 +29,6 @@
'("src/backend/classes.lisp" "src/backend/package.lisp"
"src/backend/detection.lisp" "src/backend/simple.lisp" "src/backend/modern.lisp"
"src/layout/layout.lisp"
"src/components/container-package.lisp"
"src/components/dialog-package.lisp" "src/components/dialog.lisp"
"src/components/dirty.lisp"
"src/components/input-package.lisp" "src/components/input.lisp"

View File

@@ -28,7 +28,6 @@
'("src/backend/classes.lisp" "src/backend/package.lisp"
"src/backend/detection.lisp" "src/backend/simple.lisp" "src/backend/modern.lisp"
"src/layout/layout.lisp"
"src/components/container-package.lisp"
"src/components/dialog-package.lisp" "src/components/dialog.lisp"
"src/components/dirty.lisp"
"src/components/input-package.lisp" "src/components/input.lisp"