#+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 ../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