v0.12.0: Terminal capability detection, GPL 3.0 license, roadmap rewrite
LICENSE: - Added GNU General Public License v3.0 - Updated README.org to reflect GPL 3.0 ROADMAP: - Complete rewrite to reflect actual project state - Removed croatoan/ncurses/Yoga FFI references - Marked all 11 existing versions DONE - Added v0.12.0-0.14.0 for new features (detection, pipeline, mouse) DETECTION (v0.12.0): - detect-backend: auto-detect modern vs simple backend - detect-backend-by-env: check COLORTERM env var - detect-backend-by-tty: check interactive-stream-p - detect-backend-by-da1: query terminal via ESC[c (best-effort) - *detected-backend* cache for zero-cost subsequent calls - Added detection.lisp to ASDF and package exports - Added 2 new tests (360 total, all passing) - demo.lisp updated to use detect-backend ORG BACKPORT (pre-existing fixes synced): - dialog.org: render-dialog/render-toast fixes, class initforms - scrollbox-tabbar.org: background-element -> bright-black, remove duplicate render - select.org: remove duplicate render export - text-input.org: remove duplicate %split-string, undo overflow fix - layout-engine.org: quoted-literal -> list constructors, normalize-box rewrite - mouse.org: add missing exports, fix test
This commit is contained in:
207
docs/plans/2026-05-11-terminal-detection.md
Normal file
207
docs/plans/2026-05-11-terminal-detection.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Terminal Capability Detection — Implementation Plan
|
||||
|
||||
> **For Hermes:** Implement this plan task-by-task using subagent-driven-development.
|
||||
|
||||
**Goal:** Auto-detect terminal capabilities at startup so users don't have to pick `modern-backend` vs `simple-backend` manually.
|
||||
|
||||
**Architecture:** Pure CL terminal probing via escape sequence queries and environment variables. No external dependencies. Detection happens once at startup and returns a backend instance.
|
||||
|
||||
**Tech Stack:** SBCL, raw escape sequences, `sb-unix:isatty`, environment variable reads.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Create detection.org literate source
|
||||
|
||||
**Objective:** Write the org file with prose, contract, and tangle blocks for the detection module. No code generation yet — this is the design document.
|
||||
|
||||
**Files:**
|
||||
- Create: `org/detection.org`
|
||||
|
||||
**Content structure:**
|
||||
|
||||
```
|
||||
#+TITLE: Terminal Capability Detection (v0.12.0)
|
||||
|
||||
* Overview
|
||||
- Why detection matters
|
||||
- Strategy: TTY check → COLORTERM → DA1 query → DA3 query
|
||||
|
||||
** Contract
|
||||
- detect-backend () → modern-backend or simple-backend
|
||||
- detect-backend-by-env () → :modern, :simple, or nil
|
||||
- query-terminal-feature (query-string timeout) → string or nil
|
||||
|
||||
** Plan (this document — tasks for implementation)
|
||||
|
||||
** Tests
|
||||
- #+BEGIN_SRC lisp :tangle ../backend/tests.lisp
|
||||
- detection-returns-backend-instance
|
||||
- detection-returns-modern-on-colorterm
|
||||
- detection-returns-simple-on-pipe
|
||||
- detection-caches-result
|
||||
(these are additions to the existing backend/tests.lisp)
|
||||
|
||||
** Implementation
|
||||
- Package (adds to cl-tty.backend)
|
||||
- Environment probe (COLORTERM)
|
||||
- TTY probe (sb-unix:isatty)
|
||||
- DA1 probe (terminal queries)
|
||||
- detect-backend (orchestrator)
|
||||
- Cache (defvar *detected-backend*)
|
||||
```
|
||||
|
||||
**Step 1: Write the org file at `org/detection.org`** with the sections above, full prose, and empty code blocks.
|
||||
|
||||
**Step 2: Review** — verify structure matches existing .org files in the project.
|
||||
|
||||
**Step 3: Commit**
|
||||
```bash
|
||||
git add org/detection.org
|
||||
git commit -m "docs: add detection module design and plan"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Add detection functions to backend/classes.lisp
|
||||
|
||||
**Objective:** Implement the environment and TTY probe functions.
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/classes.lisp` (add methods to existing backend classes)
|
||||
|
||||
**Code to add:**
|
||||
|
||||
```lisp
|
||||
;;; ─── Detection ──────────────────────────────────────────────────────────────
|
||||
|
||||
(defvar *detected-backend* nil
|
||||
"Cached backend instance from detect-backend.")
|
||||
|
||||
(defun detect-backend-by-env ()
|
||||
"Check COLORTERM environment variable for modern terminal support."
|
||||
(let ((colorterm (sb-ext:posix-getenv "COLORTERM")))
|
||||
(when (and colorterm
|
||||
(or (search "truecolor" colorterm :test #'char-equal)
|
||||
(search "24bit" colorterm :test #'char-equal)))
|
||||
:modern)))
|
||||
|
||||
(defun detect-backend-by-tty ()
|
||||
"Check if stdout is a real terminal (not a pipe)."
|
||||
(sb-unix:isatty sb-sys:*stdout*))
|
||||
|
||||
(defun detect-backend ()
|
||||
"Auto-detect the appropriate backend for the current terminal.
|
||||
Returns a backend instance."
|
||||
(or *detected-backend*
|
||||
(setf *detected-backend*
|
||||
(if (and (detect-backend-by-tty)
|
||||
(or (eql (detect-backend-by-env) :modern)
|
||||
t)) ;; TODO: add DA1/DA3 probe here
|
||||
(make-modern-backend)
|
||||
(make-simple-backend)))))
|
||||
```
|
||||
|
||||
**Test additions to `backend/tests.lisp`:**
|
||||
|
||||
```lisp
|
||||
(def-test detection-returns-backend-instance ()
|
||||
(let ((be (cl-tty.backend:detect-backend)))
|
||||
(is-true (typep be 'cl-tty.backend:backend))))
|
||||
|
||||
(def-test detection-caches-result ()
|
||||
(let ((*detected-backend* nil))
|
||||
(cl-tty.backend:detect-backend)
|
||||
(is-true (not (null cl-tty.backend::*detected-backend*)))))
|
||||
```
|
||||
|
||||
**Follow TDD:**
|
||||
1. Write failing tests in `src/components/box-tests.lisp` (or wherever backend tests live — actually in `backend/tests.lisp`)
|
||||
2. Run tests to verify failure
|
||||
3. Write implementation code in `backend/classes.lisp`
|
||||
4. Run tests to verify pass
|
||||
5. Commit
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add DA1/DA3 terminal query probe
|
||||
|
||||
**Objective:** Send escape sequence queries to the terminal and parse responses to detect modern features (Kitty keyboard, DECICM sync).
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/classes.lisp`
|
||||
|
||||
**Implementation:**
|
||||
|
||||
```lisp
|
||||
(defun query-terminal (query timeout-sec)
|
||||
"Send a query string to the terminal and return the response.
|
||||
Returns nil if no response within TIMEOUT-SEC seconds."
|
||||
(let ((response (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t)))
|
||||
(format t "~A" query)
|
||||
(force-output)
|
||||
(sleep timeout-sec)
|
||||
(loop while (listen)
|
||||
do (vector-push-extend (read-char-no-hang) response))
|
||||
(when (plusp (length response))
|
||||
response)))
|
||||
|
||||
(defun detect-backend-by-da1 ()
|
||||
"Send DA1 (Device Attributes) query and parse response for modern features."
|
||||
(let ((response (query-terminal (format nil "~C[c" #\Esc) 0.1)))
|
||||
(when response
|
||||
;; Check for specific feature codes in response
|
||||
(search "?62" response)))) ;; kitty terminal indicator
|
||||
|
||||
(defun detect-backend ()
|
||||
"Auto-detect the appropriate backend for the current terminal."
|
||||
(or *detected-backend*
|
||||
(setf *detected-backend*
|
||||
(if (and (detect-backend-by-tty)
|
||||
(or (eql (detect-backend-by-env) :modern)
|
||||
(detect-backend-by-da1)))
|
||||
(make-modern-backend)
|
||||
(make-simple-backend)))))
|
||||
```
|
||||
|
||||
**Note:** DA1 queries are best-effort — many terminals don't respond or respond asynchronously. The env-var check is more reliable. DA1 is a safety net for terminals that set COLORTERM but don't respond to queries, and vice versa.
|
||||
|
||||
**Test for DA1 is hard to automate** (requires a real terminal). Add a manual test note.
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Wire into ASDF and run full test suite
|
||||
|
||||
**Files:**
|
||||
- Modify: `cl-tty.asd` (add detection.lisp if created as separate file, or verify existing)
|
||||
- Run: `run-all-tests.lisp`
|
||||
|
||||
**Steps:**
|
||||
1. Ensure `cl-tty.asd` includes the detection code (if in `backend/classes.lisp` it's already loaded)
|
||||
2. Run full test suite: `sbcl --script run-all-tests.lisp`
|
||||
3. Verify all 358+ tests pass (add 2 new detection tests → 360)
|
||||
4. Commit
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Update demo.lisp to use detection
|
||||
|
||||
**Objective:** Make `demo.lisp` use `detect-backend` instead of hardcoded `make-modern-backend`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `demo.lisp`
|
||||
|
||||
**Change:** Replace `(make-modern-backend)` with `(detect-backend)`.
|
||||
|
||||
**Verification:** `sbcl --script demo.lisp` should work in a terminal.
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Tangle org → lisp and verify no regressions
|
||||
|
||||
**Files:** All
|
||||
|
||||
**Steps:**
|
||||
1. Tangle all org files: `for f in org/*.org; do emacs --batch ...; done`
|
||||
2. Run full test suite
|
||||
3. Verify 0 regressions
|
||||
4. Commit final
|
||||
Reference in New Issue
Block a user