# v0.2.0: Renderables — Box and Text > Implementation plan for the first two renderable component types. **Goal:** Create Box (border+background+title) and Text (styled wrapping text) renderables that render through the backend protocol. **Architecture:** Each renderable is a CLOS class with a `layout-node` slot for positioning. The `render` method dispatches through the backend protocol (draw-text, draw-border, draw-rect). Tests capture backend output via string streams. **Files created:** - `org/box-renderable.org` — Box class, render method (literate source) - `org/text-renderable.org` — Text class, render method, inline spans (literate source) - `org/dirty-tracking.org` — Dirty flag system (literate source) - `src/components/box.lisp` — tangled - `src/components/text.lisp` — tangled - `src/components/dirty.lisp` — tangled **Files modified:** - `cl-tty.asd` — add component modules - `docs/ROADMAP.org` — mark v0.2.0 tasks DONE ## Task 1: Box renderable **Objective:** Box class that draws borders, fills backgrounds, and renders titles. **Files:** - Create: `org/box-renderable.org` - Create: `src/components/box.lisp` (extracted) - Modify: `cl-tty.asd` — add components module **Box class:** ```lisp (defclass box () ((layout-node :initarg :layout-node :accessor box-layout-node) (border-style :initform :single :initarg :border-style :accessor box-border-style) (title :initform nil :initarg :title :accessor box-title) (title-align :initform :left :initarg :title-align :accessor box-title-align) (fg :initform nil :initarg :fg :accessor box-fg) (bg :initform nil :initarg :bg :accessor box-bg))) ``` **render-box method:** Renders at computed layout position using backend's draw-border, draw-rect, draw-text. Delegates to the backend — no escape sequences directly. **Tests:** - Create box with border, verify draw-border was called with correct params - Create box with title, verify title positioning - Create box with background fill - Edge cases: box with 0 width/height, no border style, very long title ## Task 2: Text renderable **Objective:** Text class that renders strings at layout position with word-wrap. **Files:** - Create: `org/text-renderable.org` - Create: `src/components/text.lisp` (extracted) **Text class:** ```lisp (defclass text () ((layout-node :initarg :layout-node :accessor text-layout-node) (content :initarg :content :accessor text-content) (fg :initform nil :initarg :fg :accessor text-fg) (bg :initform nil :initarg :bg :accessor text-bg) (wrap-mode :initform :word :initarg :wrap-mode :accessor text-wrap-mode) (spans :initform nil :initarg :spans :accessor text-spans))) ``` **render-text method:** 1. Get layout position (x, y, width, height) 2. If wrap-mode is :none, truncate to width 3. If wrap-mode is :word, word-wrap (break on whitespace) 4. Draw each line via backend's draw-text 5. Apply span attributes (bold, italic, etc.) per segment **Inline spans:** ```lisp (defclass span () ((text :initarg :text :accessor span-text) (bold :initform nil :initarg :bold :accessor span-bold) (italic :initform nil :initarg :italic :accessor span-italic) (underline :initform nil :initarg :underline :accessor span-underline))) ``` **Tests:** - Text renders string at correct position - Word-wrap breaks at word boundaries - Truncation mode clips at width - Spans apply style attributes per segment - Empty string rendering - Single character - String shorter than width (no wrapping needed) ## Task 3: Dirty tracking **Objective:** Lightweight dirty-flag system for incremental rendering. **Files:** - Create: `org/dirty-tracking.org` - Create: `src/components/dirty.lisp` (extracted) ```lisp (defgeneric mark-dirty (component)) (defgeneric dirty-p (component)) (defgeneric mark-clean (component)) ``` Default methods mark/check a `dirty` slot on the component. When implemented: - `mark-dirty` — sets dirty flag, propagates to parent - `dirty-p` — returns T if component needs re-render - `mark-clean` — clears dirty flag after render **Tests:** - New component is dirty (default) - mark-clean clears dirty flag - dirty-p returns nil after mark-clean - mark-dirty sets dirty flag again ## Task 4: Wire into ASDF + update roadmap **Files:** - Modify: `cl-tty.asd` — add `:module "components"` to both main and test systems - Modify: `docs/ROADMAP.org` — mark v0.2.0 tasks DONE **Run full test suite:** All 72 existing tests + new component tests: 100% GREEN.