@gajae-code/tui 0.13.2 → 0.14.0
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.
- package/CHANGELOG.md +25 -1
- package/dist/types/components/gajae-pet.d.ts +121 -22
- package/dist/types/components/ouroboros-pet.d.ts +23 -0
- package/dist/types/components/select-list.d.ts +3 -2
- package/dist/types/terminal-capabilities.d.ts +22 -0
- package/dist/types/terminal.d.ts +9 -0
- package/dist/types/tui.d.ts +122 -4
- package/package.json +3 -3
- package/src/components/gajae-pet.ts +645 -49
- package/src/components/ouroboros-pet-frames.json +488 -0
- package/src/components/ouroboros-pet.ts +150 -0
- package/src/components/select-list.ts +17 -11
- package/src/terminal-capabilities.ts +137 -0
- package/src/terminal.ts +33 -0
- package/src/tui.ts +1026 -135
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [0.
|
|
5
|
+
## [0.14.0] - 2026-08-17
|
|
6
6
|
|
|
7
7
|
### Fixed
|
|
8
|
+
- iTerm2 pet capability probes now wait for pending input without disabling Kitty keyboard mode, disabling `modifyOtherKeys`, or detaching the live input handler.
|
|
9
|
+
- iTerm2 Gajae Pet frames now carry a dedicated filename, so dragging one retains iTerm's native drag behavior while the coding agent can discard only the temporary pet pathname pasted back into its composer.
|
|
10
|
+
- iTerm2 Gajae Pet now uses one animated GIF upload per meaningful state change and a raster lease that excludes its cell rectangle from generic TUI redraw erases, eliminating animation flicker while preserving geometry-derived art placement and leaving Kitty and Sixel encoding unchanged.
|
|
11
|
+
|
|
12
|
+
- Layout-only animation and selector frames can now reuse an unchanged revisioned transcript subtree instead of rebuilding every off-screen transcript component, anchor row, and Kitty placement on each tick. Ordinary render requests remain conservative, and transcript revision, width, identity, and global invalidation changes still force a full subtree render.
|
|
8
13
|
|
|
9
14
|
- A fast double-Esc (or triple-Esc) whose ESC bytes coalesce into one stdin chunk — which tmux always produces within its escape-time window, and SSH batching produces routinely — is now emitted as individual Escape key presses instead of a single `"\x1b\x1b"` sequence that parsed as the unbound `alt+escape` and silently swallowed both presses. This restores the double-Esc draft-clear and double-Esc selector gestures under tmux/SSH. Option-as-Meta sequences with a real continuation (e.g. Option+Up as `ESC ESC [ A`) remain atomic, and an ESC-cancelled incomplete sequence is still emitted whole.
|
|
10
15
|
- An ambiguous trailing run of Escape bytes now stays buffered until a continuation or the flush timeout resolves it, so `ESC ESC ESC` followed by `[A` in the next chunk still decodes as Escape then `alt+up` instead of two Escapes plus a plain Up that fired the destructive double-Escape gesture.
|
|
@@ -13,6 +18,25 @@
|
|
|
13
18
|
- A long run of Escape bytes followed by another key now decodes in linear time instead of rescanning the remaining input on every step, which blocked the event loop for over a second on a 50,000-byte run.
|
|
14
19
|
- Apple Terminal.app now retains its default keyboard mode when it does not support the Kitty keyboard protocol, avoiding the modifyOtherKeys fallback that breaks Korean/Hangul IME composition.
|
|
15
20
|
|
|
21
|
+
## [0.13.3] - 2026-08-15
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Added the Ouroboros pet component with frame data, selector integration, and animation lifecycle shared with the existing Gajae Pet (#4468).
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Non-finite overlay geometry can no longer turn frame padding into an infinite allocating loop on the main thread; margins, positions, offsets, and minimum widths now fall back to bounded terminal-relative values (#4481).
|
|
28
|
+
- Kitty inline images are no longer deleted when live output moves their anchor above the viewport, so terminal-native scrollback keeps previously rendered images visible (#4424).
|
|
29
|
+
|
|
30
|
+
## [0.13.2] - 2026-08-13
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Fast double-Esc and triple-Esc sequences coalesced into one stdin chunk by tmux or SSH are now emitted as individual Escape presses, restoring draft-clear and selector gestures while preserving atomic Option-as-Meta sequences (#4312 by @Yeachan-Heo).
|
|
35
|
+
- Ambiguous trailing Escape bytes now remain buffered until a continuation or flush timeout resolves them, preventing a split `ESC ESC ESC [A` sequence from firing the destructive double-Escape gesture (#4312 by @Yeachan-Heo).
|
|
36
|
+
- Escape presses immediately followed by bracketed paste are now emitted individually instead of being swallowed as an unbound `alt+escape` sequence (#4312 by @Yeachan-Heo).
|
|
37
|
+
- Long runs of Escape bytes now decode in linear time without repeatedly rescanning accumulated input; 50,000 byte-by-byte reads now complete in milliseconds instead of seconds (#4312 by @Yeachan-Heo).
|
|
38
|
+
- Apple Terminal.app now retains its default keyboard mode when it does not support the Kitty keyboard protocol, avoiding the modifyOtherKeys fallback that broke Korean/Hangul IME composition (#4297 by @Yeachan-Heo).
|
|
39
|
+
|
|
16
40
|
## [0.13.1] - 2026-08-11
|
|
17
41
|
|
|
18
42
|
### Added
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { type OuroborosFrameName } from "./ouroboros-pet";
|
|
1
2
|
/**
|
|
2
3
|
* ┌─ GAJAE PET SPRITE SPEC ────────────────────────────────────────────────┐
|
|
3
|
-
*
|
|
4
|
-
* data: no PNGs
|
|
5
|
-
*
|
|
4
|
+
* Pets are square pixel sprites drawn beside the composer. Everything here is
|
|
5
|
+
* data: no PNGs or binary assets. Current pet frames are 16×16 and render into
|
|
6
|
+
* the same terminal footprint.
|
|
6
7
|
*
|
|
7
8
|
* GRID RULES
|
|
8
|
-
* -
|
|
9
|
+
* - Every frame within a skin has the same square dimensions.
|
|
9
10
|
* - `.` = transparent. Keep the outer columns transparent so the sprite sits
|
|
10
11
|
* snug beside the input box (the widget reserves +1 column of slack).
|
|
11
12
|
*
|
|
@@ -26,7 +27,8 @@
|
|
|
26
27
|
* RENDERING: buildGajaePixelFrames({ protocol, cellWidthPx, cellHeightPx,
|
|
27
28
|
* targetRows: 2 }) scales the art to 2 terminal rows and encodes each frame
|
|
28
29
|
* once. Kitty uses a native `Y=` sub-cell drop (set by the widget) to sit on the
|
|
29
|
-
* composer border; sixel uses transparent top padding
|
|
30
|
+
* composer border; sixel uses transparent top padding; iTerm2 uses an inline PNG
|
|
31
|
+
* sized to the reserved cell block.
|
|
30
32
|
*
|
|
31
33
|
* BEHAVIOR (timing, positioning, on/off) lives in
|
|
32
34
|
* packages/coding-agent/src/modes/components/gajae-pet-widget.ts.
|
|
@@ -34,26 +36,29 @@
|
|
|
34
36
|
* ADD A FRAME: draw the grid → add its name to GajaePixelFrameName → register it in
|
|
35
37
|
* PIXEL_GRIDS → reference it from an idle/work loop or a skin burst.
|
|
36
38
|
*
|
|
37
|
-
* ADD A PET (skin): append one
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* widget reads `burst` to animate — no other file needs editing. Recolor with a palette
|
|
41
|
-
* spread (see BLUE_PALETTE); add frames only for poses the catalog lacks.
|
|
39
|
+
* ADD A PET (skin): append one PET_SKINS entry with its palette, frame registry,
|
|
40
|
+
* base/idle/work animations and burst. The id flows into PetSkinId/PetMode
|
|
41
|
+
* automatically; settings, `/pet`, and both selectors derive from PET_SKINS.
|
|
42
42
|
* └────────────────────────────────────────────────────────────────────────┘
|
|
43
43
|
*/
|
|
44
44
|
type Rgb = readonly [number, number, number];
|
|
45
45
|
export type Palette = Record<string, Rgb | null>;
|
|
46
|
-
export declare const PET_SKIN_IDS: readonly ["red", "blue"];
|
|
46
|
+
export declare const PET_SKIN_IDS: readonly ["red", "blue", "ouroboros"];
|
|
47
47
|
export type PetSkinId = (typeof PET_SKIN_IDS)[number];
|
|
48
48
|
/** Every pet mode: "off" plus each skin id, in menu order. */
|
|
49
|
-
export declare const PET_MODE_IDS: readonly ["off", "red", "blue"];
|
|
49
|
+
export declare const PET_MODE_IDS: readonly ["off", "red", "blue", "ouroboros"];
|
|
50
50
|
export type PetMode = (typeof PET_MODE_IDS)[number];
|
|
51
51
|
/** Narrow an arbitrary string to a PetMode. */
|
|
52
52
|
export declare function isPetMode(value: string): value is PetMode;
|
|
53
|
+
/** Resolve a persisted mode after a skin has been removed. Explicit "off" remains off. */
|
|
54
|
+
export declare function resolvePetMode(value: string): PetMode;
|
|
53
55
|
/** Logical pixel-pet frame names shared by the overlay state machine. */
|
|
54
56
|
export type GajaePixelFrameName = "base" | "gazeL" | "gazeR" | "flicker" | "flex" | "danceL" | "danceR" | "cry1" | "cry2" | "cry3";
|
|
57
|
+
export type PetFrameName = GajaePixelFrameName | OuroborosFrameName;
|
|
55
58
|
/** Para-para work dance beats: the working loop and each skin's burst "work-in" intro. */
|
|
56
|
-
export
|
|
59
|
+
export type GajaeGifFrameTuple = readonly [GajaePixelFrameName, number];
|
|
60
|
+
export declare const PARA_PARA_STEPS: readonly GajaeGifFrameTuple[];
|
|
61
|
+
export declare const GAJAE_IDLE_STEPS: ReadonlyArray<readonly [GajaePixelFrameName, number]>;
|
|
57
62
|
/**
|
|
58
63
|
* A skin's idle burst: a short intro sequence, then an optional looping tail. It drives
|
|
59
64
|
* BOTH the random live show-off AND the selector's preview demo, so give every skin a
|
|
@@ -61,10 +66,10 @@ export declare const PARA_PARA_STEPS: ReadonlyArray<readonly [GajaePixelFrameNam
|
|
|
61
66
|
*/
|
|
62
67
|
export interface PetBurst {
|
|
63
68
|
/** Frames played once, in order, at the start of the burst. */
|
|
64
|
-
intro: ReadonlyArray<readonly [
|
|
69
|
+
intro: ReadonlyArray<readonly [PetFrameName, number]>;
|
|
65
70
|
/** Frames cycled every `stepMs` for `ms` after the intro (a held or looping finish). */
|
|
66
71
|
tail?: {
|
|
67
|
-
frames: readonly
|
|
72
|
+
frames: readonly PetFrameName[];
|
|
68
73
|
stepMs: number;
|
|
69
74
|
ms: number;
|
|
70
75
|
};
|
|
@@ -77,29 +82,108 @@ export interface PetSkin {
|
|
|
77
82
|
/** One-line selector/settings description. */
|
|
78
83
|
description: string;
|
|
79
84
|
palette: Palette;
|
|
85
|
+
frames: Readonly<Record<string, string[]>>;
|
|
86
|
+
baseFrame: PetFrameName;
|
|
87
|
+
idle: ReadonlyArray<readonly [PetFrameName, number]>;
|
|
88
|
+
workEnter?: ReadonlyArray<readonly [PetFrameName, number]>;
|
|
89
|
+
work: ReadonlyArray<readonly [PetFrameName, number]>;
|
|
90
|
+
workExit?: ReadonlyArray<readonly [PetFrameName, number]>;
|
|
80
91
|
/** Idle burst animation played between quiet idle loops. */
|
|
81
92
|
burst: PetBurst;
|
|
93
|
+
/** Optional variants that interrupt and then resume the work loop. */
|
|
94
|
+
workBursts?: readonly PetBurst[];
|
|
82
95
|
}
|
|
83
96
|
/** Skin registry — the single source for palettes, behavior and selector/command copy. */
|
|
84
97
|
export declare const PET_SKINS: Record<PetSkinId, PetSkin>;
|
|
85
98
|
/** Total burst duration (intro beats plus the looping tail). */
|
|
86
99
|
export declare function petBurstDurationMs(burst: PetBurst): number;
|
|
87
100
|
/** The frame to show `elapsed` ms into a burst (`now` cycles the looping tail). */
|
|
88
|
-
export declare function petBurstFrame(burst: PetBurst, elapsed: number, now: number):
|
|
101
|
+
export declare function petBurstFrame(burst: PetBurst, elapsed: number, now: number): PetFrameName;
|
|
89
102
|
/** Test-only access to logical art; production rendering still uses encoded frames. */
|
|
90
103
|
export declare const __gajaePetTestHooks: {
|
|
91
|
-
getPixelGrid(name:
|
|
104
|
+
getPixelGrid(name: PetFrameName, skin?: PetSkinId): string[];
|
|
92
105
|
};
|
|
106
|
+
export interface GajaeGifFrame {
|
|
107
|
+
readonly name: PetFrameName;
|
|
108
|
+
readonly delayMs: number;
|
|
109
|
+
}
|
|
110
|
+
export type GajaeGifTimeline = readonly GajaeGifFrame[];
|
|
111
|
+
export interface GajaeGifRectangle {
|
|
112
|
+
readonly width?: number;
|
|
113
|
+
readonly height?: number;
|
|
114
|
+
}
|
|
115
|
+
export interface GajaeGifDisplaySize {
|
|
116
|
+
/** iTerm2 display width: bare numbers are terminal cells; strings may use px or auto. */
|
|
117
|
+
readonly width: number | string;
|
|
118
|
+
/** iTerm2 display height: bare numbers are terminal cells; strings may use px or auto. */
|
|
119
|
+
readonly height: number | string;
|
|
120
|
+
}
|
|
121
|
+
export interface GajaeGifContentInset {
|
|
122
|
+
/** Transparent top padding in source pixels. */
|
|
123
|
+
readonly topPx?: number;
|
|
124
|
+
/** Transparent bottom padding in source pixels. */
|
|
125
|
+
readonly bottomPx?: number;
|
|
126
|
+
}
|
|
127
|
+
export interface GajaePetGifArtifact {
|
|
128
|
+
readonly bytes: Uint8Array;
|
|
129
|
+
readonly base64: string;
|
|
130
|
+
readonly width: number;
|
|
131
|
+
readonly height: number;
|
|
132
|
+
readonly frames: readonly GajaeGifFrame[];
|
|
133
|
+
readonly skin: PetSkinId;
|
|
134
|
+
readonly multipart: readonly string[];
|
|
135
|
+
readonly tmuxDcs: readonly string[];
|
|
136
|
+
}
|
|
137
|
+
export interface GajaePetGifOptions {
|
|
138
|
+
readonly skin?: PetSkinId;
|
|
139
|
+
readonly timeline?: GajaeGifTimeline;
|
|
140
|
+
readonly cellWidthPx?: number;
|
|
141
|
+
readonly cellHeightPx?: number;
|
|
142
|
+
readonly targetRows?: number;
|
|
143
|
+
readonly rectangle?: GajaeGifRectangle;
|
|
144
|
+
readonly displaySize?: GajaeGifDisplaySize;
|
|
145
|
+
readonly contentInset?: GajaeGifContentInset;
|
|
146
|
+
}
|
|
147
|
+
export declare const idleTimeline: () => GajaeGifTimeline;
|
|
148
|
+
export declare const workingTimeline: () => GajaeGifTimeline;
|
|
149
|
+
export declare const burstTimeline: (skin?: PetSkinId) => GajaeGifTimeline;
|
|
150
|
+
export declare const previewTimeline: (skin?: PetSkinId) => GajaeGifTimeline;
|
|
151
|
+
export declare function encodeGajaePetGif(input?: GajaePetGifOptions | GajaeGifTimeline): GajaePetGifArtifact;
|
|
152
|
+
export declare function getGajaePetGifCached(input?: GajaePetGifOptions | GajaeGifTimeline): GajaePetGifArtifact;
|
|
153
|
+
export declare function getGajaePetGifCacheStats(): {
|
|
154
|
+
size: number;
|
|
155
|
+
bytes: number;
|
|
156
|
+
gifBytes: number;
|
|
157
|
+
base64Bytes: number;
|
|
158
|
+
multipartBytes: number;
|
|
159
|
+
tmuxDcsBytes: number;
|
|
160
|
+
evictions: number;
|
|
161
|
+
};
|
|
162
|
+
export declare function resetGajaePetGifCache(): void;
|
|
163
|
+
export declare const clearGajaePetGifCache: typeof resetGajaePetGifCache;
|
|
93
164
|
/** Encode a grid as a transparent SIXEL image, optionally bottom-aligned by top padding. */
|
|
94
165
|
export declare function encodeGridSixel(grid: string[], scale: number, topPaddingPx?: number, palette?: Palette): string;
|
|
166
|
+
/**
|
|
167
|
+
* Encode a grid as an iTerm2 inline PNG spanning a terminal cell block.
|
|
168
|
+
*
|
|
169
|
+
* The escape's `width`/`height` are the reserved cell-block footprint in
|
|
170
|
+
* character cells (unitless numbers per the iTerm2 inline-images protocol).
|
|
171
|
+
* iTerm2 resolves cells against its own live font metrics, so the sprite
|
|
172
|
+
* scales with the real terminal geometry — including Retina, where iTerm2
|
|
173
|
+
* divides `Npx` values by the backing-scale factor and would render a fixed
|
|
174
|
+
* pixel box at half size.
|
|
175
|
+
*/
|
|
176
|
+
export declare function encodeGridIterm2(grid: string[], scale: number, columns: number, rows: number, topPaddingPx?: number, bottomPaddingPx?: number, leftPaddingPx?: number, rightPaddingPx?: number, palette?: Palette): string;
|
|
95
177
|
/** Encode a bottom-aligned grid as kitty raw RGBA at `scale`. */
|
|
96
178
|
export declare function encodeGridKitty(grid: string[], scale: number, imageId: number, cols: number, rows: number, topPaddingPx?: number, cellYOffsetPx?: number, leftPaddingPx?: number, rightPaddingPx?: number, palette?: Palette): string;
|
|
97
179
|
export interface GajaePixelFrames {
|
|
98
180
|
/** escape payload per logical frame (drawn at the current cursor cell) */
|
|
99
|
-
frames: Record<
|
|
181
|
+
frames: Record<string, string>;
|
|
100
182
|
/** protocol the frames were encoded for */
|
|
101
|
-
protocol: "sixel" | "kitty";
|
|
183
|
+
protocol: "sixel" | "kitty" | "iterm2";
|
|
184
|
+
/** Scaled sprite width before transparent cell-block padding. */
|
|
102
185
|
widthPx: number;
|
|
186
|
+
/** Encoded raster height, including protocol-specific transparent padding. */
|
|
103
187
|
heightPx: number;
|
|
104
188
|
columns: number;
|
|
105
189
|
rows: number;
|
|
@@ -108,11 +192,22 @@ export interface GajaePixelFrames {
|
|
|
108
192
|
}
|
|
109
193
|
/**
|
|
110
194
|
* Build overlay pixel frames exactly `targetRows` terminal rows tall when the
|
|
111
|
-
* terminal cells permit it.
|
|
112
|
-
*
|
|
195
|
+
* terminal cells permit it. Each skin owns its source resolution so future
|
|
196
|
+
* additions can opt into denser art without changing the terminal footprint.
|
|
197
|
+
*
|
|
198
|
+
* Geometry contract:
|
|
199
|
+
* - `scale = max(1, targetRows * cellHeightPx / gridHeight)`
|
|
200
|
+
* - `columns = ceil(scaledSpriteWidthPx / cellWidthPx)`
|
|
201
|
+
* - `rows = ceil(scaledSpriteHeightPx / cellHeightPx)`
|
|
202
|
+
* - the square sprite is centered in a `columns * cellWidthPx` PNG canvas
|
|
203
|
+
*
|
|
204
|
+
* iTerm2 receives unitless `width=columns;height=rasterRows`, so it resolves the
|
|
205
|
+
* padded block with its live cell metrics. The PNG has that block's pixel aspect
|
|
206
|
+
* ratio, allowing `preserveAspectRatio=0` without stretching the authored square
|
|
207
|
+
* sprite. Kitty and Sixel retain their protocol-specific paths.
|
|
113
208
|
*/
|
|
114
209
|
export declare function buildGajaePixelFrames(options: {
|
|
115
|
-
protocol: "sixel" | "kitty";
|
|
210
|
+
protocol: "sixel" | "kitty" | "iterm2";
|
|
116
211
|
cellWidthPx: number;
|
|
117
212
|
cellHeightPx: number;
|
|
118
213
|
targetRows?: number;
|
|
@@ -121,6 +216,10 @@ export declare function buildGajaePixelFrames(options: {
|
|
|
121
216
|
/** Native sub-cell `Y=` pixel offset that drops the kitty sprite within its first cell. */
|
|
122
217
|
kittyCellYOffsetPx?: number;
|
|
123
218
|
kittyImageId?: number;
|
|
219
|
+
/** Additional transparent iTerm2-only top padding for sub-cell vertical alignment. */
|
|
220
|
+
iterm2TopPaddingPx?: number;
|
|
221
|
+
/** Transparent iTerm2-only bottom padding inside the canvas. */
|
|
222
|
+
iterm2BottomPaddingPx?: number;
|
|
124
223
|
/** Color skin for the sprite palette (default "red"). */
|
|
125
224
|
skin?: PetSkinId;
|
|
126
225
|
}): GajaePixelFrames;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const OUROBOROS_FRAME_NAMES: readonly ["idle", "tongue-1", "tongue-2", "blink", "cry-1", "cry-2", "cry-3", "heart-turn-0", "heart-turn-1", "heart-turn-3", "heart-turn-4", "heart-turn-5", "heart-turn-6", "heart-turn-10", "heart-turn-11", "heart", "heart-accent", "enter-1", "enter-2", "spin-1", "spin-2", "spin-3", "spin-4", "spin-5", "spin-6", "spin-7", "spin-8"];
|
|
2
|
+
export type OuroborosFrameName = (typeof OUROBOROS_FRAME_NAMES)[number];
|
|
3
|
+
export declare const OUROBOROS_PIXEL_GRIDS: Record<OuroborosFrameName, string[]>;
|
|
4
|
+
/** Quiet loop with frequent tongue flicks and an occasional three-drop sob. */
|
|
5
|
+
export declare const OUROBOROS_IDLE_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
6
|
+
/** One sob builds into three distinct tear drops: 1-2-3-2-3-2-3. */
|
|
7
|
+
export declare const OUROBOROS_CRY_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
8
|
+
/**
|
|
9
|
+
* Signature flex: the eye opens before the body rolls counterclockwise through
|
|
10
|
+
* 45°, 90°, 120°, and the final overlap with the heart pose. The return reuses
|
|
11
|
+
* the authored frames in reverse.
|
|
12
|
+
*/
|
|
13
|
+
export declare const OUROBOROS_HEART_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
14
|
+
/** Head leads left while the resting coil resolves into the infinity loop. */
|
|
15
|
+
export declare const OUROBOROS_WORK_ENTER_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
16
|
+
/** The exact inverse of work entry, so stopping visibly unwinds into idle. */
|
|
17
|
+
export declare const OUROBOROS_WORK_EXIT_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
18
|
+
/** Retouched eight-frame infinity loop with the full body following the head. */
|
|
19
|
+
export declare const OUROBOROS_WORK_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
20
|
+
/** Leave the infinity loop, perform the heart flourish, then resolve back into it. */
|
|
21
|
+
export declare const OUROBOROS_WORK_HEART_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
22
|
+
/** Leave the infinity loop for a three-drop sob, then resume at full size. */
|
|
23
|
+
export declare const OUROBOROS_WORK_CRY_STEPS: ReadonlyArray<readonly [OuroborosFrameName, number]>;
|
|
@@ -21,6 +21,7 @@ export interface SelectListTheme {
|
|
|
21
21
|
noMatch: (text: string) => string;
|
|
22
22
|
symbols: SymbolTheme;
|
|
23
23
|
}
|
|
24
|
+
export type SelectListThemeSource = SelectListTheme | (() => SelectListTheme);
|
|
24
25
|
export interface SelectListTruncatePrimaryContext {
|
|
25
26
|
text: string;
|
|
26
27
|
maxWidth: number;
|
|
@@ -37,12 +38,12 @@ export declare class SelectList implements Component {
|
|
|
37
38
|
#private;
|
|
38
39
|
private readonly items;
|
|
39
40
|
private readonly maxVisible;
|
|
40
|
-
private readonly
|
|
41
|
+
private readonly themeSource;
|
|
41
42
|
private readonly layout;
|
|
42
43
|
onSelect?: (item: SelectItem) => void;
|
|
43
44
|
onCancel?: () => void;
|
|
44
45
|
onSelectionChange?: (item: SelectItem) => void;
|
|
45
|
-
constructor(items: ReadonlyArray<SelectItem>, maxVisible: number,
|
|
46
|
+
constructor(items: ReadonlyArray<SelectItem>, maxVisible: number, themeSource: SelectListThemeSource, layout?: SelectListLayoutOptions);
|
|
46
47
|
setFilter(filter: string): void;
|
|
47
48
|
setSelectedIndex(index: number): void;
|
|
48
49
|
handleNavigation(action: "tui.select.up" | "tui.select.down" | "tui.select.pageUp" | "tui.select.pageDown"): void;
|
|
@@ -195,4 +195,26 @@ export interface RenderedImage {
|
|
|
195
195
|
}
|
|
196
196
|
export declare function renderImage(base64Data: string, imageDimensions: ImageDimensions, options?: ImageRenderOptions): RenderedImage | null;
|
|
197
197
|
export declare function imageFallback(mimeType: string, dimensions?: ImageDimensions, filename?: string): string;
|
|
198
|
+
export type Iterm2Capability = {
|
|
199
|
+
readonly key: string;
|
|
200
|
+
readonly value: string;
|
|
201
|
+
};
|
|
202
|
+
export type Iterm2CapabilityReply = "complete-f" | "missing-f" | "invalid-f" | undefined;
|
|
203
|
+
/**
|
|
204
|
+
* Classifies complete iTerm2 capability replies. An absent result means the
|
|
205
|
+
* input does not yet contain a complete capability frame.
|
|
206
|
+
*/
|
|
207
|
+
export declare function parseITerm2CapabilityReply(input: Uint8Array | string): Iterm2CapabilityReply;
|
|
208
|
+
export declare function encodeITerm2Multipart(base64Data: string, options?: {
|
|
209
|
+
width?: number | string;
|
|
210
|
+
height?: number | string;
|
|
211
|
+
}): string[];
|
|
212
|
+
export declare function wrapITerm2RecordForTmux(record: string): string;
|
|
213
|
+
export declare function wrapITerm2RecordsForTmux(records: readonly string[]): string[];
|
|
214
|
+
export declare function parseITerm2Capabilities(input: string): Iterm2Capability[];
|
|
215
|
+
export declare class Iterm2CapabilitiesParser {
|
|
216
|
+
#private;
|
|
217
|
+
push(input: Uint8Array | string): Iterm2Capability[];
|
|
218
|
+
reset(): void;
|
|
219
|
+
}
|
|
198
220
|
export {};
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -45,6 +45,14 @@ export interface Terminal {
|
|
|
45
45
|
* @param idleMs - Exit early if no input arrives within this time (default: 50ms)
|
|
46
46
|
*/
|
|
47
47
|
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Wait for pending stdin to go quiet without changing terminal protocols or
|
|
50
|
+
* the active input handler. Capability probes use this non-destructive drain;
|
|
51
|
+
* shutdown paths must continue using drainInput().
|
|
52
|
+
* @param maxMs - Maximum time to wait (default: 1000ms)
|
|
53
|
+
* @param idleMs - Exit early if no input arrives within this time (default: 50ms)
|
|
54
|
+
*/
|
|
55
|
+
drainPendingInput?(maxMs?: number, idleMs?: number): Promise<void>;
|
|
48
56
|
write(data: string): void;
|
|
49
57
|
get available(): boolean;
|
|
50
58
|
readonly isProcessTerminal?: boolean;
|
|
@@ -90,6 +98,7 @@ export declare class ProcessTerminal implements Terminal {
|
|
|
90
98
|
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
|
|
91
99
|
setMouseEnabled(enabled: boolean): void;
|
|
92
100
|
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
101
|
+
drainPendingInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
93
102
|
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
94
103
|
stop(): void;
|
|
95
104
|
write(data: string): void;
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
import type { Terminal } from "./terminal";
|
|
2
2
|
import { visibleWidth } from "./utils";
|
|
3
|
+
export type CellRect = Readonly<{
|
|
4
|
+
column: number;
|
|
5
|
+
row: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}>;
|
|
9
|
+
export type RasterLeaseToken = Readonly<{
|
|
10
|
+
ownerId: string;
|
|
11
|
+
generation: number;
|
|
12
|
+
rect: CellRect;
|
|
13
|
+
}>;
|
|
14
|
+
export type RasterLeaseInvalidatedNotification = Readonly<{
|
|
15
|
+
type: "raster-lease-invalidated";
|
|
16
|
+
queueId: number;
|
|
17
|
+
token: RasterLeaseToken;
|
|
18
|
+
cause: "intersecting-generic-output" | "full-redraw" | "resize" | "terminal-loss" | "capability-loss" | "mode-off" | "dispose" | "explicit" | "manual-viewport";
|
|
19
|
+
eraseAck: TerminalOutputAck;
|
|
20
|
+
}>;
|
|
21
|
+
export type RasterLeaseRequest = Readonly<{
|
|
22
|
+
ownerId: string;
|
|
23
|
+
rect: CellRect;
|
|
24
|
+
erase: Readonly<{
|
|
25
|
+
type: "raster-erase";
|
|
26
|
+
bytes: Uint8Array;
|
|
27
|
+
}>;
|
|
28
|
+
onInvalidated?: (notice: RasterLeaseInvalidatedNotification) => void;
|
|
29
|
+
}>;
|
|
30
|
+
export type TerminalOutputOperation = Readonly<{
|
|
31
|
+
type: "generic-render";
|
|
32
|
+
rect: CellRect;
|
|
33
|
+
bytes: Uint8Array;
|
|
34
|
+
}> | Readonly<{
|
|
35
|
+
type: "generic-full-redraw";
|
|
36
|
+
rect: CellRect;
|
|
37
|
+
bytes: Uint8Array;
|
|
38
|
+
}> | Readonly<{
|
|
39
|
+
type: "raster-multipart-batch";
|
|
40
|
+
records: readonly Uint8Array[];
|
|
41
|
+
prefix?: Uint8Array;
|
|
42
|
+
afterPrefix?: () => Promise<boolean>;
|
|
43
|
+
/** Synchronous freshness gate evaluated immediately before terminal output. */
|
|
44
|
+
shouldWrite?: () => boolean;
|
|
45
|
+
replayPrefix?: Uint8Array;
|
|
46
|
+
suffix?: Uint8Array;
|
|
47
|
+
abortSuffix?: Uint8Array;
|
|
48
|
+
restoreCursorVisibility?: boolean;
|
|
49
|
+
}> | Readonly<{
|
|
50
|
+
type: "raster-erase";
|
|
51
|
+
bytes: Uint8Array;
|
|
52
|
+
}> | Readonly<{
|
|
53
|
+
type: "raster-probe";
|
|
54
|
+
bytes: Uint8Array;
|
|
55
|
+
}> | Readonly<{
|
|
56
|
+
type: "queued-output";
|
|
57
|
+
bytes: Uint8Array;
|
|
58
|
+
shouldWrite?: () => boolean;
|
|
59
|
+
/** Runs synchronously at the terminal write boundary after a successful write. */
|
|
60
|
+
onWritten?: () => void;
|
|
61
|
+
}>;
|
|
62
|
+
export type TerminalOutputAck = Readonly<{
|
|
63
|
+
queueId: number;
|
|
64
|
+
operation: TerminalOutputOperation["type"];
|
|
65
|
+
status: "written" | "stale-token" | "revoked" | "failed";
|
|
66
|
+
token?: RasterLeaseToken;
|
|
67
|
+
}>;
|
|
68
|
+
export type LifecycleCleanupAck = Readonly<{
|
|
69
|
+
attempted: number;
|
|
70
|
+
written: number;
|
|
71
|
+
stillPending: number;
|
|
72
|
+
}>;
|
|
73
|
+
export type RasterLeaseAcquireResult = Readonly<{
|
|
74
|
+
status: "acquired";
|
|
75
|
+
token: RasterLeaseToken;
|
|
76
|
+
}> | Readonly<{
|
|
77
|
+
status: "rejected";
|
|
78
|
+
reason: "invalid-geometry" | "terminal-unavailable" | "owner-conflict" | "manual-viewport";
|
|
79
|
+
}>;
|
|
3
80
|
/** Discrete mouse-wheel notch size in terminal rows (xterm/less-style). */
|
|
4
81
|
export declare const DEFAULT_WHEEL_LINES = 3;
|
|
5
82
|
type InputListenerResult = {
|
|
@@ -7,6 +84,10 @@ type InputListenerResult = {
|
|
|
7
84
|
data?: string;
|
|
8
85
|
} | undefined;
|
|
9
86
|
type InputListener = (data: string) => InputListenerResult;
|
|
87
|
+
type PostRenderEmission = {
|
|
88
|
+
payload: string;
|
|
89
|
+
onWritten?: () => void;
|
|
90
|
+
};
|
|
10
91
|
/**
|
|
11
92
|
* Component interface - all components must implement this
|
|
12
93
|
*/
|
|
@@ -53,6 +134,12 @@ export interface Component {
|
|
|
53
134
|
* Default is false - release events are filtered out.
|
|
54
135
|
*/
|
|
55
136
|
wantsKeyRelease?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Optional monotonic revision for renderer-level subtree reuse. Components that
|
|
139
|
+
* expose this MUST advance it whenever render output can change without a
|
|
140
|
+
* width change. Unversioned components are always rendered normally.
|
|
141
|
+
*/
|
|
142
|
+
getRenderRevision?(): bigint;
|
|
56
143
|
/**
|
|
57
144
|
* Invalidate any cached rendering state.
|
|
58
145
|
* Called when theme changes or when component needs to re-render from scratch.
|
|
@@ -238,6 +325,7 @@ export interface OverlayHandle {
|
|
|
238
325
|
export declare class Container implements ViewportAnchorProvider {
|
|
239
326
|
#private;
|
|
240
327
|
children: Component[];
|
|
328
|
+
getRenderRevision(): bigint;
|
|
241
329
|
addChild(component: Component): void;
|
|
242
330
|
removeChild(component: Component): void;
|
|
243
331
|
/** Remove a child without disposing it (for detach-then-readd reuse). */
|
|
@@ -254,6 +342,8 @@ export declare class Container implements ViewportAnchorProvider {
|
|
|
254
342
|
clear(): void;
|
|
255
343
|
/** Remove all children without disposing them (for detach-then-readd reuse). */
|
|
256
344
|
detachAll(): void;
|
|
345
|
+
/** Replace direct children without disposing reusable components. */
|
|
346
|
+
replaceChildren(children: Component[]): void;
|
|
257
347
|
/** Registers a direct child as eligible for semantic viewport anchoring. */
|
|
258
348
|
setViewportAnchorSource(component: Component, source: ViewportAnchorSource | null): void;
|
|
259
349
|
dispose(): void;
|
|
@@ -344,6 +434,20 @@ export declare class TUI extends Container {
|
|
|
344
434
|
/** Check if there are any visible overlays */
|
|
345
435
|
hasOverlay(): boolean;
|
|
346
436
|
invalidate(): void;
|
|
437
|
+
acquireRasterLease(request: RasterLeaseRequest): Promise<RasterLeaseAcquireResult>;
|
|
438
|
+
submitTerminalOutput(request: Readonly<{
|
|
439
|
+
operation: TerminalOutputOperation;
|
|
440
|
+
token?: RasterLeaseToken;
|
|
441
|
+
}>): Promise<TerminalOutputAck>;
|
|
442
|
+
invalidateRasterLease(request: Readonly<{
|
|
443
|
+
token: RasterLeaseToken;
|
|
444
|
+
cause: RasterLeaseInvalidatedNotification["cause"];
|
|
445
|
+
}>): Promise<TerminalOutputAck>;
|
|
446
|
+
notifyTerminalLifecycle(event: {
|
|
447
|
+
kind: "availability-restored" | "explicit-cleanup";
|
|
448
|
+
source: "tui" | "interactive-mode" | "transport";
|
|
449
|
+
terminalGeneration: number;
|
|
450
|
+
}): Promise<LifecycleCleanupAck>;
|
|
347
451
|
start(): void;
|
|
348
452
|
/**
|
|
349
453
|
* Wait for a specific render request generation to be written successfully.
|
|
@@ -355,8 +459,15 @@ export declare class TUI extends Container {
|
|
|
355
459
|
*/
|
|
356
460
|
waitForRenderCommit(generation: number, timeoutMs?: number): Promise<boolean>;
|
|
357
461
|
get terminalAvailable(): boolean;
|
|
462
|
+
get isRunning(): boolean;
|
|
463
|
+
get terminalGeneration(): number;
|
|
464
|
+
get manualViewportActive(): boolean;
|
|
358
465
|
addInputListener(listener: InputListener): () => void;
|
|
466
|
+
drainInput(maxMs: number, quiescenceMs: number): Promise<void>;
|
|
467
|
+
drainPetProbeInput(maxMs: number, quiescenceMs: number): Promise<void>;
|
|
359
468
|
removeInputListener(listener: InputListener): void;
|
|
469
|
+
/** Refresh terminal cell metrics for a verified external image transport. */
|
|
470
|
+
refreshImageCellSize(): void;
|
|
360
471
|
stop(): void;
|
|
361
472
|
/**
|
|
362
473
|
* Viewport-repaint-aware resize render request.
|
|
@@ -372,6 +483,8 @@ export declare class TUI extends Container {
|
|
|
372
483
|
*/
|
|
373
484
|
requestResizeRender(): void;
|
|
374
485
|
requestRender(force?: boolean, source?: string): void;
|
|
486
|
+
/** Request a frame whose mutation is known to be outside the viewport-anchor subtree. */
|
|
487
|
+
requestLayoutRender(source?: string): void;
|
|
375
488
|
requestRenderWithGeneration(force?: boolean, source?: string): number;
|
|
376
489
|
getLineRenderCacheStats(): {
|
|
377
490
|
normalizationSize: number;
|
|
@@ -380,13 +493,18 @@ export declare class TUI extends Container {
|
|
|
380
493
|
truncationLimit: number;
|
|
381
494
|
};
|
|
382
495
|
/** Retain terminal cleanup until a write succeeds, even after its component is disposed. */
|
|
383
|
-
queueTerminalCleanup(payload: string, onDelivered?: () => void): void
|
|
384
|
-
/**
|
|
385
|
-
|
|
496
|
+
queueTerminalCleanup(payload: string, onDelivered?: () => void): Promise<void>;
|
|
497
|
+
/** Queue protocol-neutral output behind the same terminal ordering as renders. */
|
|
498
|
+
queueTerminalOutput(payload: string, options?: {
|
|
499
|
+
shouldWrite?: () => boolean;
|
|
500
|
+
onWritten?: () => void;
|
|
501
|
+
}): Promise<TerminalOutputAck>;
|
|
502
|
+
/** Retry retained cleanup after recovery or before shutdown. */
|
|
503
|
+
flushTerminalCleanup(restoreTerminalAvailability?: boolean): void;
|
|
386
504
|
/**
|
|
387
505
|
* Register an emitter whose payload is delivered after each shared render
|
|
388
506
|
* transaction. The emitter is an exempt physical overlay: its bytes are
|
|
389
507
|
* deliberately kept out of the shared transcript write.
|
|
390
508
|
*/
|
|
391
|
-
setPostRenderEmitter(emitter: (() => string | null) | undefined): void;
|
|
509
|
+
setPostRenderEmitter(emitter: (() => string | PostRenderEmission | null) | undefined): void;
|
|
392
510
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/tui",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"fmt": "biome format --write ."
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@gajae-code/natives": "0.
|
|
40
|
-
"@gajae-code/utils": "0.
|
|
39
|
+
"@gajae-code/natives": "0.14.0",
|
|
40
|
+
"@gajae-code/utils": "0.14.0",
|
|
41
41
|
"lru-cache": "11.3.6",
|
|
42
42
|
"marked": "18.0.6"
|
|
43
43
|
},
|