@mariozechner/pi-tui 0.5.48 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +166 -475
  2. package/dist/autocomplete.d.ts.map +1 -1
  3. package/dist/autocomplete.js +2 -0
  4. package/dist/autocomplete.js.map +1 -1
  5. package/dist/components/{text-editor.d.ts → editor.d.ts} +9 -5
  6. package/dist/components/editor.d.ts.map +1 -0
  7. package/dist/components/{text-editor.js → editor.js} +125 -70
  8. package/dist/components/editor.js.map +1 -0
  9. package/dist/components/input.d.ts +14 -0
  10. package/dist/components/input.d.ts.map +1 -0
  11. package/dist/components/input.js +120 -0
  12. package/dist/components/input.js.map +1 -0
  13. package/dist/components/{loading-animation.d.ts → loader.d.ts} +5 -5
  14. package/dist/components/loader.d.ts.map +1 -0
  15. package/dist/components/{loading-animation.js → loader.js} +13 -10
  16. package/dist/components/loader.js.map +1 -0
  17. package/dist/components/markdown.d.ts +46 -0
  18. package/dist/components/markdown.d.ts.map +1 -0
  19. package/dist/components/markdown.js +499 -0
  20. package/dist/components/markdown.js.map +1 -0
  21. package/dist/components/select-list.d.ts +3 -3
  22. package/dist/components/select-list.d.ts.map +1 -1
  23. package/dist/components/select-list.js +24 -16
  24. package/dist/components/select-list.js.map +1 -1
  25. package/dist/components/spacer.d.ts +11 -0
  26. package/dist/components/spacer.d.ts.map +1 -0
  27. package/dist/components/spacer.js +20 -0
  28. package/dist/components/spacer.js.map +1 -0
  29. package/dist/components/text.d.ts +26 -0
  30. package/dist/components/text.d.ts.map +1 -0
  31. package/dist/components/text.js +141 -0
  32. package/dist/components/text.js.map +1 -0
  33. package/dist/index.d.ts +8 -6
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +10 -12
  36. package/dist/index.js.map +1 -1
  37. package/dist/terminal.d.ts +12 -0
  38. package/dist/terminal.d.ts.map +1 -1
  39. package/dist/terminal.js +33 -3
  40. package/dist/terminal.js.map +1 -1
  41. package/dist/tui.d.ts +30 -52
  42. package/dist/tui.d.ts.map +1 -1
  43. package/dist/tui.js +131 -337
  44. package/dist/tui.js.map +1 -1
  45. package/dist/utils.d.ts +10 -0
  46. package/dist/utils.d.ts.map +1 -0
  47. package/dist/utils.js +15 -0
  48. package/dist/utils.js.map +1 -0
  49. package/package.json +6 -5
  50. package/dist/components/loading-animation.d.ts.map +0 -1
  51. package/dist/components/loading-animation.js.map +0 -1
  52. package/dist/components/markdown-component.d.ts +0 -15
  53. package/dist/components/markdown-component.d.ts.map +0 -1
  54. package/dist/components/markdown-component.js +0 -247
  55. package/dist/components/markdown-component.js.map +0 -1
  56. package/dist/components/text-component.d.ts +0 -14
  57. package/dist/components/text-component.d.ts.map +0 -1
  58. package/dist/components/text-component.js +0 -90
  59. package/dist/components/text-component.js.map +0 -1
  60. package/dist/components/text-editor.d.ts.map +0 -1
  61. package/dist/components/text-editor.js.map +0 -1
  62. package/dist/components/whitespace-component.d.ts +0 -13
  63. package/dist/components/whitespace-component.d.ts.map +0 -1
  64. package/dist/components/whitespace-component.js +0 -22
  65. package/dist/components/whitespace-component.js.map +0 -1
package/README.md CHANGED
@@ -1,559 +1,261 @@
1
1
  # @mariozechner/pi-tui
2
2
 
3
- Terminal UI framework with surgical differential rendering for building flicker-free interactive CLI applications.
3
+ Minimal terminal UI framework with differential rendering and synchronized output for flicker-free interactive CLI applications.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Surgical Differential Rendering**: Three-strategy system that minimizes redraws to 1-2 lines for typical updates
8
- - **Scrollback Buffer Preservation**: Correctly maintains terminal history when content exceeds viewport
9
- - **Zero Flicker**: Components like text editors remain perfectly still while other parts update
10
- - **Interactive Components**: Text editor with autocomplete, selection lists, markdown rendering
11
- - **Composable Architecture**: Container-based component system with automatic lifecycle management
7
+ - **Differential Rendering**: Three-strategy rendering system that only updates what changed
8
+ - **Synchronized Output**: Uses CSI 2026 for atomic screen updates (no flicker)
9
+ - **Bracketed Paste Mode**: Handles large pastes correctly with markers for >10 line pastes
10
+ - **Component-based**: Simple Component interface with render() method
11
+ - **Built-in Components**: Text, Input, Editor, Markdown, Loader, SelectList, Spacer
12
+ - **Autocomplete Support**: File paths and slash commands
12
13
 
13
14
  ## Quick Start
14
15
 
15
16
  ```typescript
16
- import { TUI, Container, TextComponent, TextEditor } from "@mariozechner/pi-tui";
17
-
18
- // Create TUI manager
19
- const ui = new TUI();
20
-
21
- // Create components
22
- const header = new TextComponent("🚀 My TUI App");
23
- const chatContainer = new Container();
24
- const editor = new TextEditor();
25
-
26
- // Add components to UI
27
- ui.addChild(header);
28
- ui.addChild(chatContainer);
29
- ui.addChild(editor);
30
-
31
- // Set focus to the editor
32
- ui.setFocus(editor);
33
-
34
- // Handle editor submissions
35
- editor.onSubmit = (text: string) => {
36
- if (text.trim()) {
37
- const message = new TextComponent(`💬 ${text}`);
38
- chatContainer.addChild(message);
39
- // Note: Container automatically calls requestRender when children change
40
- }
41
- };
17
+ import { TUI, Text, Editor, ProcessTerminal } from "@mariozechner/pi-tui";
42
18
 
43
- // Start the UI
44
- ui.start();
45
- ```
19
+ // Create terminal
20
+ const terminal = new ProcessTerminal();
46
21
 
47
- ## Core Components
22
+ // Create TUI
23
+ const tui = new TUI(terminal);
48
24
 
49
- ### TUI
25
+ // Add components
26
+ tui.addChild(new Text("Welcome to my app!"));
50
27
 
51
- Main TUI manager with surgical differential rendering that handles input and component lifecycle.
28
+ const editor = new Editor();
29
+ editor.onSubmit = (text) => {
30
+ console.log("Submitted:", text);
31
+ tui.addChild(new Text(`You said: ${text}`));
32
+ };
33
+ tui.addChild(editor);
52
34
 
53
- **Key Features:**
54
- - **Three rendering strategies**: Automatically selects optimal approach
55
- - Surgical: Updates only changed lines (1-2 lines typical)
56
- - Partial: Re-renders from first change when structure shifts
57
- - Full: Complete re-render when changes are above viewport
58
- - **Performance metrics**: Built-in tracking via `getLinesRedrawn()` and `getAverageLinesRedrawn()`
59
- - **Terminal abstraction**: Works with any Terminal interface implementation
35
+ // Start
36
+ tui.start();
37
+ ```
60
38
 
61
- **Methods:**
62
- - `addChild(component)` - Add a component
63
- - `removeChild(component)` - Remove a component
64
- - `setFocus(component)` - Set keyboard focus
65
- - `start()` / `stop()` - Lifecycle management
66
- - `requestRender()` - Queue re-render (automatically debounced)
67
- - `configureLogging(config)` - Enable debug logging
39
+ ## Core API
68
40
 
69
- ### Container
41
+ ### TUI
70
42
 
71
- Component that manages child components. Automatically triggers re-renders when children change.
43
+ Main container that manages components and rendering.
72
44
 
73
45
  ```typescript
74
- const container = new Container();
75
- container.addChild(new TextComponent("Child 1"));
76
- container.removeChild(component);
77
- container.clear();
46
+ const tui = new TUI(terminal);
47
+ tui.addChild(component);
48
+ tui.removeChild(component);
49
+ tui.start();
50
+ tui.stop();
51
+ tui.requestRender(); // Request a re-render
78
52
  ```
79
53
 
80
- ### TextEditor
54
+ ### Component Interface
81
55
 
82
- Interactive multiline text editor with autocomplete support.
56
+ All components implement:
83
57
 
84
58
  ```typescript
85
- const editor = new TextEditor();
86
- editor.setText("Initial text");
87
- editor.onSubmit = (text) => console.log("Submitted:", text);
88
- editor.setAutocompleteProvider(provider);
89
- ```
90
-
91
- **Key Bindings:**
92
- - `Enter` - Submit text
93
- - `Shift+Enter` - New line
94
- - `Tab` - Autocomplete
95
- - `Ctrl+K` - Delete line
96
- - `Ctrl+A/E` - Start/end of line
97
- - Arrow keys, Backspace, Delete work as expected
98
-
99
- ### TextComponent
100
-
101
- Simple text display with automatic word wrapping.
102
-
103
- ```typescript
104
- const text = new TextComponent("Hello World", { top: 1, bottom: 1 });
105
- text.setText("Updated text");
59
+ interface Component {
60
+ render(width: number): string[];
61
+ handleInput?(data: string): void;
62
+ }
106
63
  ```
107
64
 
108
- ### MarkdownComponent
65
+ ## Built-in Components
109
66
 
110
- Renders markdown content with syntax highlighting and proper formatting.
67
+ ### Container
111
68
 
112
- **Constructor:**
69
+ Groups child components.
113
70
 
114
71
  ```typescript
115
- new MarkdownComponent(text?: string)
72
+ const container = new Container();
73
+ container.addChild(component);
74
+ container.removeChild(component);
116
75
  ```
117
76
 
118
- **Methods:**
119
-
120
- - `setText(text)` - Update markdown content
121
- - `render(width)` - Render parsed markdown
122
-
123
- **Features:**
124
-
125
- - **Headings**: Styled with colors and formatting
126
- - **Code blocks**: Syntax highlighting with gray background
127
- - **Lists**: Bullet points (•) and numbered lists
128
- - **Emphasis**: **Bold** and _italic_ text
129
- - **Links**: Underlined with URL display
130
- - **Blockquotes**: Styled with left border
131
- - **Inline code**: Highlighted with background
132
- - **Horizontal rules**: Terminal-width separator lines
133
- - Differential rendering for performance
134
-
135
- ### SelectList
136
-
137
- Interactive selection component for choosing from options.
77
+ ### Text
138
78
 
139
- **Constructor:**
79
+ Displays multi-line text with word wrapping and padding.
140
80
 
141
81
  ```typescript
142
- new SelectList(items: SelectItem[], maxVisible?: number)
143
-
144
- interface SelectItem {
145
- value: string;
146
- label: string;
147
- description?: string;
148
- }
82
+ const text = new Text("Hello World", paddingX, paddingY); // defaults: 1, 1
83
+ text.setText("Updated text");
149
84
  ```
150
85
 
151
- **Properties:**
152
-
153
- - `onSelect?: (item: SelectItem) => void` - Called when item is selected
154
- - `onCancel?: () => void` - Called when selection is cancelled
155
-
156
- **Methods:**
157
-
158
- - `setFilter(filter)` - Filter items by value
159
- - `getSelectedItem()` - Get currently selected item
160
- - `handleInput(keyData)` - Handle keyboard navigation
161
- - `render(width)` - Render the selection list
162
-
163
- **Features:**
164
-
165
- - Keyboard navigation (arrow keys, Enter)
166
- - Search/filter functionality
167
- - Scrolling for long lists
168
- - Custom option rendering with descriptions
169
- - Visual selection indicator (→)
170
- - Scroll position indicator
171
-
172
- ### Autocomplete System
173
-
174
- Comprehensive autocomplete system supporting slash commands and file paths.
86
+ ### Input
175
87
 
176
- #### AutocompleteProvider Interface
88
+ Single-line text input with horizontal scrolling.
177
89
 
178
90
  ```typescript
179
- interface AutocompleteProvider {
180
- getSuggestions(
181
- lines: string[],
182
- cursorLine: number,
183
- cursorCol: number,
184
- ): {
185
- items: AutocompleteItem[];
186
- prefix: string;
187
- } | null;
188
-
189
- applyCompletion(
190
- lines: string[],
191
- cursorLine: number,
192
- cursorCol: number,
193
- item: AutocompleteItem,
194
- prefix: string,
195
- ): {
196
- lines: string[];
197
- cursorLine: number;
198
- cursorCol: number;
199
- };
200
- }
201
-
202
- interface AutocompleteItem {
203
- value: string;
204
- label: string;
205
- description?: string;
206
- }
91
+ const input = new Input();
92
+ input.onSubmit = (value) => console.log(value);
93
+ input.setValue("initial");
207
94
  ```
208
95
 
209
- #### CombinedAutocompleteProvider
210
-
211
- Built-in provider supporting slash commands and file completion.
96
+ ### Editor
212
97
 
213
- **Constructor:**
98
+ Multi-line text editor with autocomplete, file completion, and paste handling.
214
99
 
215
100
  ```typescript
216
- new CombinedAutocompleteProvider(
217
- commands: (SlashCommand | AutocompleteItem)[] = [],
218
- basePath: string = process.cwd()
219
- )
220
-
221
- interface SlashCommand {
222
- name: string;
223
- description?: string;
224
- getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
225
- }
101
+ const editor = new Editor();
102
+ editor.onSubmit = (text) => console.log(text);
103
+ editor.onChange = (text) => console.log("Changed:", text);
104
+ editor.disableSubmit = true; // Disable submit temporarily
105
+ editor.setAutocompleteProvider(provider);
226
106
  ```
227
107
 
228
108
  **Features:**
109
+ - Multi-line editing with word wrap
110
+ - Slash command autocomplete (type `/`)
111
+ - File path autocomplete (press `Tab`)
112
+ - Large paste handling (>10 lines creates `[paste #1 +50 lines]` marker)
113
+ - Horizontal lines above/below editor
114
+ - Fake cursor rendering (hidden real cursor)
229
115
 
230
- **Slash Commands:**
231
-
232
- - Type `/` to trigger command completion
233
- - Auto-completion for command names
234
- - Argument completion for commands that support it
235
- - Space after command name for argument input
236
-
237
- **File Completion:**
238
-
239
- - `Tab` key triggers file completion
240
- - `@` prefix for file attachments
241
- - Home directory expansion (`~/`)
242
- - Relative and absolute path support
243
- - Directory-first sorting
244
- - Filters to attachable files for `@` prefix
245
-
246
- **Path Patterns:**
247
-
248
- - `./` and `../` - Relative paths
249
- - `~/` - Home directory
250
- - `@path` - File attachment syntax
251
- - Tab completion from any context
252
-
253
- **Methods:**
254
-
255
- - `getSuggestions()` - Get completions for current context
256
- - `getForceFileSuggestions()` - Force file completion (Tab key)
257
- - `shouldTriggerFileCompletion()` - Check if file completion should trigger
258
- - `applyCompletion()` - Apply selected completion
259
-
260
- ## Surgical Differential Rendering
261
-
262
- The TUI uses a three-strategy rendering system that minimizes redraws to only what's necessary:
263
-
264
- ### Rendering Strategies
265
-
266
- 1. **Surgical Updates** (most common)
267
- - When: Only content changes, same line counts, all changes in viewport
268
- - Action: Updates only specific changed lines (typically 1-2 lines)
269
- - Example: Loading spinner animation, updating status text
270
-
271
- 2. **Partial Re-render**
272
- - When: Line count changes or structural changes within viewport
273
- - Action: Clears from first change to end of screen, re-renders tail
274
- - Example: Adding new messages to a chat, expanding text editor
275
-
276
- 3. **Full Re-render**
277
- - When: Changes occur above the viewport (in scrollback buffer)
278
- - Action: Clears scrollback and screen, renders everything fresh
279
- - Example: Content exceeds viewport and early components change
116
+ **Key Bindings:**
117
+ - `Enter` - Submit
118
+ - `Shift+Enter` or `Ctrl+Enter` - New line
119
+ - `Tab` - Autocomplete
120
+ - `Ctrl+K` - Delete line
121
+ - `Ctrl+A` / `Ctrl+E` - Line start/end
122
+ - Arrow keys, Backspace, Delete work as expected
280
123
 
281
- ### How Components Participate
124
+ ### Markdown
282
125
 
283
- Components implement the simple `Component` interface:
126
+ Renders markdown with syntax highlighting and optional background colors.
284
127
 
285
128
  ```typescript
286
- interface ComponentRenderResult {
287
- lines: string[]; // The lines to display
288
- changed: boolean; // Whether content changed since last render
289
- }
290
-
291
- interface Component {
292
- readonly id: number; // Unique ID for tracking
293
- render(width: number): ComponentRenderResult;
294
- handleInput?(keyData: string): void;
295
- }
129
+ const md = new Markdown(
130
+ "# Hello\n\nSome **bold** text",
131
+ bgColor, // optional: "bgRed", "bgBlue", etc.
132
+ fgColor, // optional: "white", "cyan", etc.
133
+ customBgRgb, // optional: { r: 52, g: 53, b: 65 }
134
+ paddingX, // optional: default 1
135
+ paddingY // optional: default 1
136
+ );
137
+ md.setText("Updated markdown");
296
138
  ```
297
139
 
298
- The TUI tracks component IDs and line positions to determine the optimal strategy automatically.
140
+ **Features:**
141
+ - Headings, bold, italic, code blocks, lists, links, blockquotes
142
+ - Syntax highlighting with chalk
143
+ - Optional background colors (including custom RGB)
144
+ - Padding support
145
+ - Render caching for performance
299
146
 
300
- ### Performance Metrics
147
+ ### Loader
301
148
 
302
- Monitor rendering efficiency:
149
+ Animated loading spinner.
303
150
 
304
151
  ```typescript
305
- const ui = new TUI();
306
- // After some rendering...
307
- console.log(`Total lines redrawn: ${ui.getLinesRedrawn()}`);
308
- console.log(`Average per render: ${ui.getAverageLinesRedrawn()}`);
152
+ const loader = new Loader(tui, "Loading...");
153
+ loader.start();
154
+ loader.stop();
309
155
  ```
310
156
 
311
- Typical performance: 1-2 lines redrawn for animations, 0 for static content.
312
-
313
- ## Examples
314
-
315
- Run the example applications in the `test/` directory:
316
-
317
- ```bash
318
- # Chat application with slash commands and autocomplete
319
- npx tsx test/chat-app.ts
320
-
321
- # File browser with navigation
322
- npx tsx test/file-browser.ts
157
+ ### SelectList
323
158
 
324
- # Multi-component layout demo
325
- npx tsx test/multi-layout.ts
159
+ Interactive selection list with keyboard navigation.
326
160
 
327
- # Performance benchmark with animation
328
- npx tsx test/bench.ts
161
+ ```typescript
162
+ const list = new SelectList([
163
+ { value: "opt1", label: "Option 1", description: "First option" },
164
+ { value: "opt2", label: "Option 2", description: "Second option" },
165
+ ], 5); // maxVisible
166
+
167
+ list.onSelect = (item) => console.log("Selected:", item);
168
+ list.onCancel = () => console.log("Cancelled");
169
+ list.setFilter("opt"); // Filter items
329
170
  ```
330
171
 
331
- ### Example Descriptions
172
+ **Controls:**
173
+ - Arrow keys: Navigate
174
+ - Enter or Tab: Select
175
+ - Escape: Cancel
332
176
 
333
- - **chat-app.ts** - Chat interface with slash commands (/clear, /help, /attach) and autocomplete
334
- - **file-browser.ts** - Interactive file browser with directory navigation
335
- - **multi-layout.ts** - Complex layout with header, sidebar, main content, and footer
336
- - **bench.ts** - Performance test with animation showing surgical rendering efficiency
177
+ ### Spacer
337
178
 
338
- ## Interfaces and Types
339
-
340
- ### Core Types
179
+ Empty lines for vertical spacing.
341
180
 
342
181
  ```typescript
343
- interface ComponentRenderResult {
344
- lines: string[];
345
- changed: boolean;
346
- }
347
-
348
- interface ContainerRenderResult extends ComponentRenderResult {
349
- keepLines: number;
350
- }
351
-
352
- interface Component {
353
- render(width: number): ComponentRenderResult;
354
- handleInput?(keyData: string): void;
355
- }
356
-
357
- interface Padding {
358
- top?: number;
359
- bottom?: number;
360
- left?: number;
361
- right?: number;
362
- }
182
+ const spacer = new Spacer(2); // 2 empty lines (default: 1)
363
183
  ```
364
184
 
365
- ### Autocomplete Types
366
-
367
- ```typescript
368
- interface AutocompleteItem {
369
- value: string;
370
- label: string;
371
- description?: string;
372
- }
185
+ ## Autocomplete
373
186
 
374
- interface SlashCommand {
375
- name: string;
376
- description?: string;
377
- getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
378
- }
187
+ ### CombinedAutocompleteProvider
379
188
 
380
- interface AutocompleteProvider {
381
- getSuggestions(
382
- lines: string[],
383
- cursorLine: number,
384
- cursorCol: number,
385
- ): {
386
- items: AutocompleteItem[];
387
- prefix: string;
388
- } | null;
389
-
390
- applyCompletion(
391
- lines: string[],
392
- cursorLine: number,
393
- cursorCol: number,
394
- item: AutocompleteItem,
395
- prefix: string,
396
- ): {
397
- lines: string[];
398
- cursorLine: number;
399
- cursorCol: number;
400
- };
401
- }
402
- ```
403
-
404
- ### Selection Types
189
+ Supports both slash commands and file paths.
405
190
 
406
191
  ```typescript
407
- interface SelectItem {
408
- value: string;
409
- label: string;
410
- description?: string;
411
- }
412
- ```
413
-
414
- ## Testing
415
-
416
- ### Running Tests
417
-
418
- ```bash
419
- # Run all tests
420
- npm test
192
+ import { CombinedAutocompleteProvider } from "@mariozechner/pi-tui";
421
193
 
422
- # Run specific test file
423
- npm test -- test/tui-rendering.test.ts
194
+ const provider = new CombinedAutocompleteProvider(
195
+ [
196
+ { name: "help", description: "Show help" },
197
+ { name: "clear", description: "Clear screen" },
198
+ { name: "delete", description: "Delete last message" },
199
+ ],
200
+ process.cwd() // base path for file completion
201
+ );
424
202
 
425
- # Run tests matching a pattern
426
- npm test -- --test-name-pattern="preserves existing"
203
+ editor.setAutocompleteProvider(provider);
427
204
  ```
428
205
 
429
- ### Test Infrastructure
206
+ **Features:**
207
+ - Type `/` to see slash commands
208
+ - Press `Tab` for file path completion
209
+ - Works with `~/`, `./`, `../`, and `@` prefix
210
+ - Filters to attachable files for `@` prefix
430
211
 
431
- The TUI uses a **VirtualTerminal** for testing that provides accurate terminal emulation via `@xterm/headless`:
212
+ ## Differential Rendering
432
213
 
433
- ```typescript
434
- import { VirtualTerminal } from "./test/virtual-terminal.js";
435
- import { TUI, TextComponent } from "../src/index.js";
214
+ The TUI uses three rendering strategies:
436
215
 
437
- test("my TUI test", async () => {
438
- const terminal = new VirtualTerminal(80, 24);
439
- const ui = new TUI(terminal);
440
- ui.start();
216
+ 1. **First Render**: Output all lines without clearing scrollback
217
+ 2. **Width Changed or Change Above Viewport**: Clear screen and full re-render
218
+ 3. **Normal Update**: Move cursor to first changed line, clear to end, render changed lines
441
219
 
442
- ui.addChild(new TextComponent("Hello"));
220
+ All updates are wrapped in **synchronized output** (`\x1b[?2026h` ... `\x1b[?2026l`) for atomic, flicker-free rendering.
443
221
 
444
- // Wait for render
445
- await new Promise(resolve => process.nextTick(resolve));
222
+ ## Terminal Interface
446
223
 
447
- // Get rendered output
448
- const viewport = await terminal.flushAndGetViewport();
449
- assert.strictEqual(viewport[0], "Hello");
224
+ The TUI works with any object implementing the `Terminal` interface:
450
225
 
451
- ui.stop();
452
- });
226
+ ```typescript
227
+ interface Terminal {
228
+ start(onInput: (data: string) => void, onResize: () => void): void;
229
+ stop(): void;
230
+ write(data: string): void;
231
+ get columns(): number;
232
+ get rows(): number;
233
+ moveBy(lines: number): void;
234
+ hideCursor(): void;
235
+ showCursor(): void;
236
+ clearLine(): void;
237
+ clearFromCursor(): void;
238
+ clearScreen(): void;
239
+ }
453
240
  ```
454
241
 
455
- ### Writing a New Test
456
-
457
- 1. **Create test file** in `test/` directory with `.test.ts` extension
458
- 2. **Use VirtualTerminal** for accurate terminal emulation
459
- 3. **Key testing patterns**:
242
+ **Built-in implementations:**
243
+ - `ProcessTerminal` - Uses `process.stdin/stdout`
244
+ - `VirtualTerminal` - For testing (uses `@xterm/headless`)
460
245
 
461
- ```typescript
462
- import { test, describe } from "node:test";
463
- import assert from "node:assert";
464
- import { VirtualTerminal } from "./virtual-terminal.js";
465
- import { TUI, Container, TextComponent } from "../src/index.js";
466
-
467
- describe("My Feature", () => {
468
- test("should handle dynamic content", async () => {
469
- const terminal = new VirtualTerminal(80, 24);
470
- const ui = new TUI(terminal);
471
- ui.start();
472
-
473
- // Setup components
474
- const container = new Container();
475
- ui.addChild(container);
476
-
477
- // Initial render
478
- await new Promise(resolve => process.nextTick(resolve));
479
- await terminal.flush();
480
-
481
- // Check viewport (visible content)
482
- let viewport = terminal.getViewport();
483
- assert.strictEqual(viewport.length, 24);
484
-
485
- // Check scrollback buffer (all content including history)
486
- let scrollBuffer = terminal.getScrollBuffer();
487
-
488
- // Simulate user input
489
- terminal.sendInput("Hello");
490
-
491
- // Wait for processing
492
- await new Promise(resolve => process.nextTick(resolve));
493
- await terminal.flush();
494
-
495
- // Verify changes
496
- viewport = terminal.getViewport();
497
- // ... assertions
498
-
499
- ui.stop();
500
- });
501
- });
502
- ```
246
+ ## Example
503
247
 
504
- ### VirtualTerminal API
505
-
506
- - `new VirtualTerminal(columns, rows)` - Create terminal with dimensions
507
- - `write(data)` - Write ANSI sequences to terminal
508
- - `sendInput(data)` - Simulate keyboard input
509
- - `flush()` - Wait for all writes to complete
510
- - `getViewport()` - Get visible lines (what user sees)
511
- - `getScrollBuffer()` - Get all lines including scrollback
512
- - `flushAndGetViewport()` - Convenience method
513
- - `getCursorPosition()` - Get cursor row/column
514
- - `resize(columns, rows)` - Resize terminal
515
-
516
- ### Testing Best Practices
517
-
518
- 1. **Always flush after renders**: Terminal writes are async
519
- ```typescript
520
- await new Promise(resolve => process.nextTick(resolve));
521
- await terminal.flush();
522
- ```
523
-
524
- 2. **Test both viewport and scrollback**: Ensure content preservation
525
- ```typescript
526
- const viewport = terminal.getViewport(); // Visible content
527
- const scrollBuffer = terminal.getScrollBuffer(); // All content
528
- ```
529
-
530
- 3. **Use exact string matching**: Don't trim() - whitespace matters
531
- ```typescript
532
- assert.strictEqual(viewport[0], "Expected text"); // Good
533
- assert.strictEqual(viewport[0].trim(), "Expected"); // Bad
534
- ```
535
-
536
- 4. **Test rendering strategies**: Verify surgical vs partial vs full
537
- ```typescript
538
- const beforeLines = ui.getLinesRedrawn();
539
- // Make change...
540
- const afterLines = ui.getLinesRedrawn();
541
- assert.strictEqual(afterLines - beforeLines, 1); // Only 1 line changed
542
- ```
543
-
544
- ### Performance Testing
545
-
546
- Use `test/bench.ts` as a template for performance testing:
248
+ See `test/chat-simple.ts` for a complete chat interface example with:
249
+ - Markdown messages with custom background colors
250
+ - Loading spinner during responses
251
+ - Editor with autocomplete and slash commands
252
+ - Spacers between messages
547
253
 
254
+ Run it:
548
255
  ```bash
549
- npx tsx test/bench.ts
256
+ npx tsx test/chat-simple.ts
550
257
  ```
551
258
 
552
- Monitor real-time performance metrics:
553
- - Render count and timing
554
- - Lines redrawn per render
555
- - Visual verification of flicker-free updates
556
-
557
259
  ## Development
558
260
 
559
261
  ```bash
@@ -563,17 +265,6 @@ npm install
563
265
  # Run type checking
564
266
  npm run check
565
267
 
566
- # Run tests
567
- npm test
568
- ```
569
-
570
- **Debugging:**
571
- Enable logging to see detailed component behavior:
572
-
573
- ```typescript
574
- ui.configureLogging({
575
- enabled: true,
576
- level: "debug", // "error" | "warn" | "info" | "debug"
577
- logFile: "tui-debug.log",
578
- });
268
+ # Run the demo
269
+ npx tsx test/chat-simple.ts
579
270
  ```