@code-yeongyu/senpi-tui 2026.8.1 → 2026.8.3

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 (72) hide show
  1. package/README.md +77 -14
  2. package/dist/TuiAltScreen.d.ts +83 -0
  3. package/dist/TuiAltScreen.d.ts.map +1 -0
  4. package/dist/TuiAltScreen.js +687 -0
  5. package/dist/TuiAltScreen.js.map +1 -0
  6. package/dist/TuiMainScreen.d.ts +5 -0
  7. package/dist/TuiMainScreen.d.ts.map +1 -0
  8. package/dist/TuiMainScreen.js +5 -0
  9. package/dist/TuiMainScreen.js.map +1 -0
  10. package/dist/components/alt-screen-flash.d.ts +13 -0
  11. package/dist/components/alt-screen-flash.d.ts.map +1 -0
  12. package/dist/components/alt-screen-flash.js +36 -0
  13. package/dist/components/alt-screen-flash.js.map +1 -0
  14. package/dist/components/h-stack.d.ts +7 -0
  15. package/dist/components/h-stack.d.ts.map +1 -0
  16. package/dist/components/h-stack.js +43 -0
  17. package/dist/components/h-stack.js.map +1 -0
  18. package/dist/components/image.d.ts.map +1 -1
  19. package/dist/components/image.js +4 -4
  20. package/dist/components/image.js.map +1 -1
  21. package/dist/components/markdown.d.ts +2 -0
  22. package/dist/components/markdown.d.ts.map +1 -1
  23. package/dist/components/markdown.js +3 -2
  24. package/dist/components/markdown.js.map +1 -1
  25. package/dist/components/scroll-view.d.ts +51 -0
  26. package/dist/components/scroll-view.d.ts.map +1 -0
  27. package/dist/components/scroll-view.js +163 -0
  28. package/dist/components/scroll-view.js.map +1 -0
  29. package/dist/components/settings-list.d.ts.map +1 -1
  30. package/dist/components/settings-list.js +3 -6
  31. package/dist/components/settings-list.js.map +1 -1
  32. package/dist/components/stack.d.ts +32 -0
  33. package/dist/components/stack.d.ts.map +1 -0
  34. package/dist/components/stack.js +109 -0
  35. package/dist/components/stack.js.map +1 -0
  36. package/dist/components/v-stack.d.ts +8 -0
  37. package/dist/components/v-stack.d.ts.map +1 -0
  38. package/dist/components/v-stack.js +21 -0
  39. package/dist/components/v-stack.js.map +1 -0
  40. package/dist/index.d.ts +10 -4
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +10 -4
  43. package/dist/index.js.map +1 -1
  44. package/dist/keybindings.d.ts +30 -0
  45. package/dist/keybindings.d.ts.map +1 -1
  46. package/dist/keybindings.js +18 -0
  47. package/dist/keybindings.js.map +1 -1
  48. package/dist/layout-node.d.ts +40 -0
  49. package/dist/layout-node.d.ts.map +1 -0
  50. package/dist/layout-node.js +6 -0
  51. package/dist/layout-node.js.map +1 -0
  52. package/dist/layout.d.ts +40 -0
  53. package/dist/layout.d.ts.map +1 -0
  54. package/dist/layout.js +315 -0
  55. package/dist/layout.js.map +1 -0
  56. package/dist/terminal-image.d.ts +10 -0
  57. package/dist/terminal-image.d.ts.map +1 -1
  58. package/dist/terminal-image.js +61 -3
  59. package/dist/terminal-image.js.map +1 -1
  60. package/dist/terminal-text.d.ts +1 -0
  61. package/dist/terminal-text.d.ts.map +1 -1
  62. package/dist/terminal-text.js +12 -2
  63. package/dist/terminal-text.js.map +1 -1
  64. package/dist/tui.d.ts +36 -16
  65. package/dist/tui.d.ts.map +1 -1
  66. package/dist/tui.js +72 -18
  67. package/dist/tui.js.map +1 -1
  68. package/dist/utils.d.ts +10 -0
  69. package/dist/utils.d.ts.map +1 -1
  70. package/dist/utils.js +95 -4
  71. package/dist/utils.js.map +1 -1
  72. package/package.json +2 -2
package/README.md CHANGED
@@ -4,25 +4,27 @@ Minimal terminal UI framework with differential rendering and synchronized outpu
4
4
 
5
5
  ## Features
6
6
 
7
- - **Differential Rendering**: Three-strategy rendering system that only updates what changed
7
+ - **Interchangeable Renderers**: Shared `TUI` interface with main-screen and alternate-screen implementations
8
+ - **Differential Rendering**: Updates only changed lines or viewport rows
9
+ - **Application-owned Scrolling**: Alternate-screen viewport supports mouse, trackpad, and keyboard navigation
8
10
  - **Synchronized Output**: Uses CSI 2026 for atomic screen updates (no flicker)
9
11
  - **Bracketed Paste Mode**: Handles large pastes correctly with markers for >10 line pastes
10
12
  - **Component-based**: Simple Component interface with render() method
11
13
  - **Theme Support**: Components accept theme interfaces for customizable styling
12
- - **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container
14
+ - **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container, VStack, HStack, ScrollView
13
15
  - **Inline Images**: Renders images in terminals that support Kitty or iTerm2 graphics protocols
14
16
  - **Autocomplete Support**: File paths and slash commands
15
17
 
16
18
  ## Quick Start
17
19
 
18
20
  ```typescript
19
- import { TUI, Text, Editor, ProcessTerminal, matchesKey } from "@earendil-works/pi-tui";
21
+ import { type TUI, Text, Editor, ProcessTerminal, TuiMainScreen, matchesKey } from "@earendil-works/pi-tui";
20
22
 
21
23
  // Create terminal
22
24
  const terminal = new ProcessTerminal();
23
25
 
24
- // Create TUI
25
- const tui = new TUI(terminal);
26
+ // Create the default main-screen renderer through the shared TUI interface
27
+ const tui: TUI = new TuiMainScreen(terminal);
26
28
 
27
29
  // Add components
28
30
  tui.addChild(new Text("Welcome to my app!"));
@@ -52,12 +54,20 @@ tui.start();
52
54
 
53
55
  ## Core API
54
56
 
55
- ### TUI
57
+ ### TUI interface and renderers
56
58
 
57
- Main container that manages components and rendering.
59
+ `TUI` is the shared interface for component management, focus, overlays, input, lifecycle, terminal queries, and rendering. Choose a concrete renderer only when constructing the application:
60
+
61
+ - `TuiMainScreen` renders into the main terminal buffer and preserves terminal scrollback.
62
+ - `TuiAltScreen` renders a fixed-height viewport in the alternate terminal buffer with application-owned scrolling. When stopped, it restores the main buffer and prints the complete final document.
58
63
 
59
64
  ```typescript
60
- const tui = new TUI(terminal);
65
+ import { type TUI, TuiAltScreen, TuiMainScreen } from "@earendil-works/pi-tui";
66
+
67
+ const tui: TUI = new TuiMainScreen(terminal);
68
+ // To use an application-owned viewport in the alternate terminal buffer instead:
69
+ // const tui: TUI = new TuiAltScreen(terminal);
70
+
61
71
  tui.addChild(component);
62
72
  tui.removeChild(component);
63
73
  tui.start();
@@ -68,6 +78,53 @@ tui.requestRender(); // Request a re-render
68
78
  tui.onDebug = () => console.log("Debug triggered");
69
79
  ```
70
80
 
81
+ ### Alternate-screen viewport layouts
82
+
83
+ `TuiAltScreen` can render an explicit terminal-height layout. `VStack` and `HStack` allocate constrained regions, while `ScrollView` owns scrolling for one region. These semantics are intentionally unavailable on `TuiMainScreen`, where the terminal owns scrollback.
84
+
85
+ ```typescript
86
+ import {
87
+ Container,
88
+ isViewportTUI,
89
+ ScrollView,
90
+ Text,
91
+ VStack,
92
+ } from "@earendil-works/pi-tui";
93
+
94
+ const transcript = new Container();
95
+ transcript.addChild(new Text("History"));
96
+
97
+ const editorAndFooter = new VStack([
98
+ editor,
99
+ new Text("status"),
100
+ ]);
101
+
102
+ if (isViewportTUI(tui)) {
103
+ tui.setLayoutRoot(new VStack([
104
+ {
105
+ component: new ScrollView(transcript, {
106
+ follow: "end",
107
+ primary: true,
108
+ overscroll: "chain",
109
+ }),
110
+ basis: 0,
111
+ grow: 1,
112
+ minSize: 1,
113
+ },
114
+ {
115
+ component: editorAndFooter,
116
+ basis: "auto",
117
+ shrink: 1,
118
+ minSize: 1,
119
+ },
120
+ ]));
121
+ }
122
+ ```
123
+
124
+ Stack entries support `basis`, `grow`, `shrink`, `minSize`, `maxSize`, and responsive `visible` callbacks. Mouse-wheel input targets the scroll view under the pointer and unused delta chains to outer scroll views by default. The primary scroll view receives the alternate-screen keyboard navigation actions and wheel input over non-scrollable regions. It can also jump between OSC 133 semantic prompt markers, matching common terminal prompt-navigation shortcuts.
125
+
126
+ Layout geometry is rebuilt for each requested frame. Stateful components are retained, and their existing rendered-line caches remain effective. Calling `render(width)` directly on these layout components produces an unbounded document, which is also used when alt mode restores the main screen.
127
+
71
128
  ### Overlays
72
129
 
73
130
  Overlays render components on top of existing content without replacing it. Useful for dialogs, menus, and modal UI.
@@ -190,7 +247,7 @@ When a `Focusable` component has focus, TUI:
190
247
  3. Positions the hardware terminal cursor at that location
191
248
  4. Shows the hardware cursor only when `showHardwareCursor` is enabled
192
249
 
193
- The cursor remains hidden by default. This keeps the fake cursor rendering, while still positioning the hardware cursor for terminals that track IME candidate windows with hidden cursors. Some terminals require a visible hardware cursor for IME positioning; enable it with the `TUI` constructor option, `setShowHardwareCursor(true)`, or `PI_HARDWARE_CURSOR=1`. The `Editor` and `Input` built-in components already implement this interface.
250
+ The cursor remains hidden by default. This keeps the fake cursor rendering, while still positioning the hardware cursor for terminals that track IME candidate windows with hidden cursors. Some terminals require a visible hardware cursor for IME positioning; enable it with the renderer constructor's `showHardwareCursor` argument, `setShowHardwareCursor(true)`, or `PI_HARDWARE_CURSOR=1`. The `Editor` and `Input` built-in components already implement this interface.
194
251
 
195
252
  **Container components with embedded inputs:** When a container component (dialog, selector, etc.) contains an `Input` or `Editor` child, the container must implement `Focusable` and propagate the focus state to the child:
196
253
 
@@ -535,6 +592,10 @@ tui.addChild(image);
535
592
 
536
593
  Supported formats: PNG, JPEG, GIF, WebP. Dimensions are parsed from the image headers automatically.
537
594
 
595
+ #### Alternate-screen image compatibility
596
+
597
+ `TuiAltScreen` supports inline images and partial viewport cropping in terminals that implement the Kitty graphics protocol, including Kitty and Ghostty. iTerm2's inline-image protocol does not provide operations to delete an existing placement or crop its source while scrolling. To prevent stale images from remaining over repainted content, `TuiAltScreen` renders image components as text placeholders in iTerm2. `TuiMainScreen` continues to render iTerm2 inline images normally.
598
+
538
599
  ## Autocomplete
539
600
 
540
601
  ### CombinedAutocompleteProvider
@@ -588,15 +649,17 @@ if (matchesKey(data, Key.enter)) {
588
649
  - With modifiers: `Key.ctrl("c")`, `Key.shift("tab")`, `Key.alt("left")`, `Key.ctrlShift("p")`
589
650
  - String format also works: `"enter"`, `"ctrl+c"`, `"shift+tab"`, `"ctrl+shift+p"`
590
651
 
591
- ## Differential Rendering
652
+ ## Rendering modes
592
653
 
593
- The TUI uses three rendering strategies:
654
+ `TuiMainScreen` uses three rendering strategies:
594
655
 
595
656
  1. **First Render**: Output all lines without clearing scrollback
596
- 2. **Width Changed or Change Above Viewport**: Clear screen and full re-render
597
- 3. **Normal Update**: Move cursor to first changed line, clear to end, render changed lines
657
+ 2. **Width Changed or Change Above Viewport**: Clear screen and fully re-render
658
+ 3. **Normal Update**: Move the cursor to the first changed line, clear to the end, and render changed lines
659
+
660
+ `TuiAltScreen` owns a terminal-height viewport. Without an explicit layout root it preserves the legacy single-document scrolling behavior. With `setLayoutRoot()`, `VStack`, `HStack`, and nested `ScrollView` components can reserve fixed regions and independently scroll constrained regions. It updates changed viewport rows in place, follows streaming output while at the bottom, and preserves a manually selected scroll position while content grows. Mouse-wheel and configurable keyboard navigation scroll without modifying terminal scrollback, including jumps between OSC 133 semantic prompt markers. Clicking an OSC 8 hyperlink opens it with the configured URL handler. Dragging with the primary mouse button selects text and copies it to the clipboard with OSC 52; holding the drag at a scroll view's top or bottom edge auto-scrolls and extends the selection into off-screen content. Kitty images support vertical viewport cropping; iTerm2 inline images fall back to text because the iTerm2 protocol cannot delete or crop placements during viewport repainting.
598
661
 
599
- All updates are wrapped in **synchronized output** (`\x1b[?2026h` ... `\x1b[?2026l`) for atomic, flicker-free rendering.
662
+ Both renderers wrap updates in **synchronized output** (`\x1b[?2026h` ... `\x1b[?2026l`) for atomic, flicker-free rendering.
600
663
 
601
664
  ## Terminal Interface
602
665
 
@@ -0,0 +1,83 @@
1
+ import type { Terminal } from "./terminal.ts";
2
+ import { type Component, TuiBase, VIEWPORT_TUI, type ViewportTUI } from "./tui.ts";
3
+ export interface TuiAltScreenOptions {
4
+ /** Number of logical lines moved for each mouse-wheel event. */
5
+ wheelScrollLines?: number;
6
+ /** Capture mouse events for viewport scrolling and application-owned text selection. */
7
+ mouse?: boolean;
8
+ /** Open an OSC 8 hyperlink activated with a primary-button click. */
9
+ openUrl?: (url: string) => void;
10
+ }
11
+ /** Alternate-screen TUI with a scrollable, application-owned viewport. */
12
+ export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
13
+ readonly [VIEWPORT_TUI]: true;
14
+ private previousScreen;
15
+ private lastDocument;
16
+ private previousScreenWidth;
17
+ private previousScreenHeight;
18
+ private layoutRoot;
19
+ private currentLayout;
20
+ private readonly implicitDocument;
21
+ private readonly implicitScrollView;
22
+ private readonly flashes;
23
+ private altScreenActive;
24
+ private imageProtocol;
25
+ private savedCapabilities?;
26
+ private selectionAnchor?;
27
+ private selectionFocus?;
28
+ private selectionDragPointer?;
29
+ private selectionAutoScrollDirection;
30
+ private selectionAutoScrollTimer?;
31
+ private selectionPressActive;
32
+ private scrollbarDrag?;
33
+ private scrollbarHover?;
34
+ private pressedUrl?;
35
+ private selectionDragged;
36
+ private readonly wheelScrollLines;
37
+ private readonly mouseEnabled;
38
+ private readonly openUrl?;
39
+ constructor(terminal: Terminal, showHardwareCursor?: boolean, logDirectory?: string, options?: TuiAltScreenOptions);
40
+ get viewportTop(): number;
41
+ get isFollowingOutput(): boolean;
42
+ setLayoutRoot(component: Component | undefined): void;
43
+ render(width: number): string[];
44
+ invalidate(): void;
45
+ protected getMountedRoots(): readonly Component[];
46
+ private getPrimaryScrollView;
47
+ protected beforeTerminalStart(): void;
48
+ protected beforeTerminalStop(): void;
49
+ protected afterTerminalStop(): void;
50
+ private deleteAltScreenKittyImages;
51
+ protected resetRenderState(): void;
52
+ scrollBy(lines: number): void;
53
+ scrollToTop(): void;
54
+ scrollToBottom(): void;
55
+ private scrollToPrompt;
56
+ /** Show a transient message in the alternate-screen flash stack. */
57
+ flash(message: string, durationMs?: number): void;
58
+ private handleViewportInput;
59
+ private parseWheelEvent;
60
+ private routeWheel;
61
+ private parseSgrMouseEvent;
62
+ private getScrollbarTargetAt;
63
+ private setScrollbarHover;
64
+ private updateScrollbarHover;
65
+ private stopScrollbarHover;
66
+ private handleScrollbarMouseEvent;
67
+ private stopScrollbarDrag;
68
+ private getScrollSelectionPoint;
69
+ private getSelectionPoint;
70
+ private updateSelectionAutoScroll;
71
+ private autoScrollSelection;
72
+ private stopSelectionAutoScroll;
73
+ private handleSelectionMouseEvent;
74
+ private getSelectionBounds;
75
+ private getSelectionColumns;
76
+ private copySelectionToClipboard;
77
+ private applySelectionHighlight;
78
+ private applySelection;
79
+ private isMouseSequence;
80
+ private compositeFlashes;
81
+ protected doRender(): void;
82
+ }
83
+ //# sourceMappingURL=TuiAltScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TuiAltScreen.d.ts","sourceRoot":"","sources":["../src/TuiAltScreen.ts"],"names":[],"mappings":"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"}