Files
cl-tty/org/container-package.org
Hermes Agent 29f99a576d 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%.
2026-05-12 18:55:07 +00:00

5.5 KiB

Container Package

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

(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))