opentui_rust 0.2.1

High-performance terminal UI rendering engine with alpha blending and diffed buffers
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# demo_showcase Resilience & Degradation Rules

> **Version:** 1.0
> **Status:** Draft
> **Bead:** bd-1i7

This document defines how `demo_showcase` gracefully handles constrained
environments while remaining impressive in ideal terminals.

---

## 1. Design Principles

1. **Never embarrassing** — Demo should look good even in worst-case terminals
2. **Fail gracefully** — Clear error messages, clean exit on fatal conditions
3. **Progressive enhancement** — Best experience on capable terminals, functional on limited ones
4. **Always usable** — Core functionality accessible regardless of capabilities

---

## 2. Capability Detection

### 2.1 Detected Capabilities

```rust
pub struct TerminalCapabilities {
    // Display
    pub size: Option<(u16, u16)>,      // Terminal dimensions
    pub is_tty: bool,                   // Connected to a TTY
    pub color_support: ColorSupport,    // Color depth

    // Features
    pub has_mouse: bool,                // Mouse reporting available
    pub has_synchronized_output: bool,  // DE/CSI 2026 support
    pub has_bracketed_paste: bool,      // Paste mode support
    pub has_focus_events: bool,         // Focus tracking support

    // Text attributes
    pub has_italic: bool,               // Italic text support
    pub has_strikethrough: bool,        // Strikethrough support
    pub has_hyperlinks: bool,           // OSC 8 hyperlink support
}
```

### 2.2 Color Support Levels

```rust
pub enum ColorSupport {
    TrueColor,   // 24-bit (16M colors)
    Extended,    // 256-color palette
    Basic,       // 16-color ANSI
    None,        // Monochrome
}
```

### 2.3 Detection Methods

| Capability | Detection Method |
|------------|------------------|
| TTY | `libc::isatty(stdout)` |
| Size | `ioctl TIOCGWINSZ` or `$COLUMNS`/`$LINES` |
| TrueColor | `$COLORTERM == "truecolor"` or `"24bit"` |
| 256-color | `$TERM` contains "256" |
| Mouse | Assume yes unless `--no-mouse` flag |
| Sync output | Query via CSI, timeout fallback |

---

## 3. Layout Degradation

### 3.1 Size Thresholds

| Layout Mode | Min Width | Min Height |
|-------------|-----------|------------|
| Full | 80 | 24 |
| Compact | 60 | 16 |
| Minimal | 40 | 12 |
| Too Small | < 40 | < 12 |

### 3.2 Full Layout (80+ x 24+)

Standard layout as defined in DEMO_SHOWCASE_SPEC.md:
- TopBar + Sidebar + Editor + Preview + StatusBar

### 3.3 Compact Layout (60-79 x 16-23)

```
┌────────────────────────────────────────────────────────────┐
│  OpenTUI            Mode: Normal               22:05       │
├──────┬─────────────────────────────────────────────────────┤
│ [O]  │  1 │ fn main() {                                    │
│ [E]  │  2 │     println!("hello");                         │
│ [P]  │  3 │ }                                              │
│ [L]  │  4 │                                                │
│ [U]  │  5 │                                                │
│ [F]  │  6 │                                                │
├──────┴─────────────────────────────────────────────────────┤
│  F1 Help  Ctrl+Q Quit                          FPS:60      │
└────────────────────────────────────────────────────────────┘
```

Changes from full layout:
- Sidebar collapses to icons only (3 columns)
- Preview panel hidden (merged into editor area)
- TopBar simplified (no project name)
- StatusBar abbreviated

### 3.4 Minimal Layout (40-59 x 12-15)

```
┌──────────────────────────────────────┐
│ OpenTUI                  Mode:Normal │
├──────────────────────────────────────┤
│  fn main() {                         │
│      println!("hello");              │
│  }                                   │
│                                      │
├──────────────────────────────────────┤
│ F1 Help  Ctrl+Q Quit                 │
└──────────────────────────────────────┘
```

Changes from compact layout:
- Sidebar completely hidden
- Single panel fills screen
- Tab to switch panels (section shown in title)
- Minimal chrome

### 3.5 Too Small (< 40 x 12)

Display error message and exit:

```
┌─────────────────────────┐
│ Terminal too small!     │
│ Need at least 40x12     │
│ Current: 30x10          │
│                         │
│ Press any key to exit   │
└─────────────────────────┘
```

### 3.6 Layout Transition Rules

```rust
fn compute_layout_mode(width: u16, height: u16) -> LayoutMode {
    if width < 40 || height < 12 {
        LayoutMode::TooSmall
    } else if width < 60 || height < 16 {
        LayoutMode::Minimal
    } else if width < 80 || height < 24 {
        LayoutMode::Compact
    } else {
        LayoutMode::Full
    }
}
```

### 3.7 Resize Handling

When terminal resizes:
1. Detect new size via SIGWINCH or resize event
2. Recompute layout mode
3. If mode changes, trigger full redraw
4. Preserve scroll positions and selection where possible

---

## 4. Color Degradation

### 4.1 Strategy by Color Support

| Support | Strategy |
|---------|----------|
| TrueColor | Use exact theme colors |
| Extended (256) | Map to nearest 256-color |
| Basic (16) | Use named ANSI colors |
| None | Use bold/dim/inverse only |

### 4.2 256-Color Mapping

```rust
fn map_to_256(rgba: Rgba) -> u8 {
    let r = (rgba.r * 5.0).round() as u8;
    let g = (rgba.g * 5.0).round() as u8;
    let b = (rgba.b * 5.0).round() as u8;
    16 + 36 * r + 6 * g + b
}
```

### 4.3 16-Color Mapping

Map theme colors to closest ANSI equivalents:

| Theme Token | ANSI Fallback |
|-------------|---------------|
| `bg0/bg1` | Black (30/40) |
| `bg2` | Bright Black (90) |
| `fg0` | White (97) |
| `fg1` | White (37) |
| `fg2` | Bright Black (90) |
| `accent_primary` | Cyan (36) |
| `accent_secondary` | Magenta (35) |
| `accent_success` | Green (32) |
| `accent_warning` | Yellow (33) |
| `accent_error` | Red (31) |

### 4.4 Monochrome Fallback

When no colors available:
- Use **bold** for emphasis (headings, focus)
- Use **dim** for de-emphasis (secondary text)
- Use **inverse** for selection/focus
- Use **underline** for links

### 4.5 Auto Theme Selection

```rust
fn default_theme_for_capability(color: ColorSupport) -> ThemeId {
    match color {
        ColorSupport::TrueColor | ColorSupport::Extended => ThemeId::Synthwave,
        ColorSupport::Basic | ColorSupport::None => ThemeId::HighContrast,
    }
}
```

---

## 5. Text Attribute Degradation

### 5.1 Attribute Availability Matrix

| Attribute | Wide Support | Fallback |
|-----------|-------------|----------|
| Bold | Yes | Always use |
| Dim | Mostly | Use normal |
| Underline | Yes | Always use |
| Italic | Partial | Use normal |
| Strikethrough | Rare | Use dim or normal |
| Blink | Rare | Ignore |
| Inverse | Yes | Always use |

### 5.2 Feature Check at Runtime

```rust
fn apply_text_attr(attr: TextAttribute, caps: &TerminalCapabilities) -> TextAttributes {
    match attr {
        TextAttribute::Bold => TextAttributes::BOLD,
        TextAttribute::Italic => {
            if caps.has_italic {
                TextAttributes::ITALIC
            } else {
                TextAttributes::empty()
            }
        }
        TextAttribute::Strikethrough => {
            if caps.has_strikethrough {
                TextAttributes::STRIKETHROUGH
            } else {
                TextAttributes::DIM
            }
        }
        // ... etc
    }
}
```

---

## 6. Input Degradation

### 6.1 Mouse Disabled Fallback

When mouse is unavailable or disabled (`--no-mouse`):

| Mouse Action | Keyboard Equivalent |
|--------------|---------------------|
| Click panel | Tab to panel |
| Click sidebar item | Arrow keys + Enter |
| Scroll | PageUp/PageDown, Arrow keys |
| Hover preview | Not available |
| Click button | Tab + Enter |

### 6.2 Keyboard-Only Navigation

All functionality must be accessible via keyboard:

| Action | Key |
|--------|-----|
| Cycle focus | Tab / Shift+Tab |
| Navigate in panel | Arrow keys |
| Activate selection | Enter / Space |
| Open command palette | Ctrl+P |
| Open help | F1 |
| Switch section | 1-6 number keys |
| Quit | Ctrl+Q / Esc |

### 6.3 Focus Visibility

When mouse is disabled, focus visibility becomes critical:
- Always show clear focus indicator
- Use contrasting colors for focused element
- Show keyboard hints in status bar

---

## 7. Output Degradation

### 7.1 Synchronized Output

If synchronized output (`CSI ? 2026 h/l`) is unsupported:
- Fall back to buffered writes
- May experience slight flicker on complex updates
- Consider reducing update frequency

### 7.2 Alternate Screen

If alternate screen is unsupported:
- Continue in main screen
- Clear screen on exit
- May interfere with scrollback

### 7.3 Cursor Control

If cursor hiding fails:
- Continue with visible cursor
- Position cursor at a sensible location (end of status bar)

---

## 8. Non-TTY Behavior

### 8.1 Detection

```rust
fn is_tty() -> bool {
    unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
}
```

### 8.2 Interactive Mode Failure

When stdout is not a TTY and no headless mode:

```
Error: stdout is not a terminal

demo_showcase requires an interactive terminal to run.
For non-interactive use, try: demo_showcase --headless-smoke

Exit code: 1
```

### 8.3 Headless Smoke Mode (`--headless-smoke`)

A special mode for CI testing:

1. Create renderer in headless mode (no terminal I/O)
2. Run deterministic render steps to internal buffer
3. Verify no panics, proper cleanup
4. Exit with code 0 if successful

```rust
fn run_headless_smoke() -> io::Result<()> {
    let mut buffer = OptimizedBuffer::new(80, 24);

    // Run through all sections
    for section in Section::all() {
        draw_section(&mut buffer, section);
    }

    // Verify buffer is valid
    assert!(buffer.width() == 80);
    assert!(buffer.height() == 24);

    eprintln!("Headless smoke test passed");
    Ok(())
}
```

---

## 9. Error Handling

### 9.1 Fatal Errors

| Condition | Behavior |
|-----------|----------|
| No TTY (without headless) | Print error, exit 1 |
| Terminal too small | Show message, exit 1 |
| Renderer init failure | Print error, exit 1 |
| Raw mode failure | Print error, exit 1 |

### 9.2 Recoverable Errors

| Condition | Behavior |
|-----------|----------|
| Resize to too-small | Show "too small" message, wait for resize |
| Input parse error | Ignore and continue |
| Single frame render failure | Log and continue |
| Color query timeout | Assume TrueColor |

### 9.3 Panic Recovery

Install panic hook to restore terminal:

```rust
fn install_panic_hook() {
    let original = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        // Restore terminal first
        let _ = disable_raw_mode();
        let _ = execute!(stdout(), LeaveAlternateScreen, Show);

        // Then call original hook
        original(info);
    }));
}
```

---

## 10. CLI Flags for Degradation Control

| Flag | Effect |
|------|--------|
| `--no-mouse` | Disable mouse input |
| `--no-color` | Force monochrome mode |
| `--16-color` | Force 16-color mode |
| `--256-color` | Force 256-color mode |
| `--no-sync-output` | Disable synchronized output |
| `--headless-smoke` | Run headless smoke test |
| `--compact` | Force compact layout |
| `--minimal` | Force minimal layout |

---

## 11. Acceptance Criteria Checklist

- [x] **Compact layout rules defined** — Section 3 defines Full/Compact/Minimal/TooSmall modes
- [x] **Color fallback rules defined** — Section 4 defines degradation for 256/16/mono
- [x] **Mouse-disabled fallback rules defined** — Section 6 defines keyboard-only navigation
- [x] **Non-TTY behavior defined** — Section 8 defines error message and headless mode

---

## 12. Related Beads

| Bead | Dependency | Description |
|------|------------|-------------|
| bd-1i7 | This bead | Resilience + degradation rules |
| bd-1ob | Blocked by this | Resilience implementation |
| bd-3ii | Blocked by this | Layout helpers implementation |
| bd-2iv | Blocked by this | CLI args + Config |