@gajae-code/tui 0.13.1 → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.13.3] - 2026-08-15
6
+
7
+ ### Added
8
+ - Added the Ouroboros pet component with frame data, selector integration, and animation lifecycle shared with the existing Gajae Pet (#4468).
9
+
10
+ ### Fixed
11
+ - 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).
12
+ - 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).
13
+
14
+ ## [0.13.2] - 2026-08-13
15
+
16
+ ### Fixed
17
+
18
+ - 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).
19
+ - 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).
20
+ - 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).
21
+ - 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).
22
+ - 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).
23
+
5
24
  ## [0.13.1] - 2026-08-11
6
25
 
7
26
  ### Added
@@ -1,11 +1,12 @@
1
+ import { type OuroborosFrameName } from "./ouroboros-pet";
1
2
  /**
2
3
  * ┌─ GAJAE PET SPRITE SPEC ────────────────────────────────────────────────┐
3
- * The pet is a 16×16 pixel sprite drawn beside the composer. Everything here is
4
- * data: no PNGs, no assets — each frame is 16 strings of 16 chars, encoded to a
5
- * sixel or kitty escape at runtime. Author a new frame by drawing a grid.
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
- * - Exactly 16 rows × 16 columns. Only PALETTE keys below are valid chars.
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,28 @@
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 entry to PET_SKINS below — { id, label, description,
38
- * palette, burst }. The id flows into PetSkinId/PetMode automatically, the settings
39
- * enum, `/pet` command and both selectors derive their options from PET_SKINS, and the
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
59
  export declare const PARA_PARA_STEPS: ReadonlyArray<readonly [GajaePixelFrameName, number]>;
60
+ export declare const GAJAE_IDLE_STEPS: ReadonlyArray<readonly [GajaePixelFrameName, number]>;
57
61
  /**
58
62
  * A skin's idle burst: a short intro sequence, then an optional looping tail. It drives
59
63
  * BOTH the random live show-off AND the selector's preview demo, so give every skin a
@@ -61,10 +65,10 @@ export declare const PARA_PARA_STEPS: ReadonlyArray<readonly [GajaePixelFrameNam
61
65
  */
62
66
  export interface PetBurst {
63
67
  /** Frames played once, in order, at the start of the burst. */
64
- intro: ReadonlyArray<readonly [GajaePixelFrameName, number]>;
68
+ intro: ReadonlyArray<readonly [PetFrameName, number]>;
65
69
  /** Frames cycled every `stepMs` for `ms` after the intro (a held or looping finish). */
66
70
  tail?: {
67
- frames: readonly GajaePixelFrameName[];
71
+ frames: readonly PetFrameName[];
68
72
  stepMs: number;
69
73
  ms: number;
70
74
  };
@@ -77,28 +81,38 @@ export interface PetSkin {
77
81
  /** One-line selector/settings description. */
78
82
  description: string;
79
83
  palette: Palette;
84
+ frames: Readonly<Record<string, string[]>>;
85
+ baseFrame: PetFrameName;
86
+ idle: ReadonlyArray<readonly [PetFrameName, number]>;
87
+ workEnter?: ReadonlyArray<readonly [PetFrameName, number]>;
88
+ work: ReadonlyArray<readonly [PetFrameName, number]>;
89
+ workExit?: ReadonlyArray<readonly [PetFrameName, number]>;
80
90
  /** Idle burst animation played between quiet idle loops. */
81
91
  burst: PetBurst;
92
+ /** Optional variants that interrupt and then resume the work loop. */
93
+ workBursts?: readonly PetBurst[];
82
94
  }
83
95
  /** Skin registry — the single source for palettes, behavior and selector/command copy. */
84
96
  export declare const PET_SKINS: Record<PetSkinId, PetSkin>;
85
97
  /** Total burst duration (intro beats plus the looping tail). */
86
98
  export declare function petBurstDurationMs(burst: PetBurst): number;
87
99
  /** 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): GajaePixelFrameName;
100
+ export declare function petBurstFrame(burst: PetBurst, elapsed: number, now: number): PetFrameName;
89
101
  /** Test-only access to logical art; production rendering still uses encoded frames. */
90
102
  export declare const __gajaePetTestHooks: {
91
- getPixelGrid(name: GajaePixelFrameName): string[];
103
+ getPixelGrid(name: PetFrameName, skin?: PetSkinId): string[];
92
104
  };
93
105
  /** Encode a grid as a transparent SIXEL image, optionally bottom-aligned by top padding. */
94
106
  export declare function encodeGridSixel(grid: string[], scale: number, topPaddingPx?: number, palette?: Palette): string;
107
+ /** Encode a grid as an iTerm2 inline PNG. */
108
+ export declare function encodeGridIterm2(grid: string[], scale: number, topPaddingPx?: number, bottomPaddingPx?: number, palette?: Palette): string;
95
109
  /** Encode a bottom-aligned grid as kitty raw RGBA at `scale`. */
96
110
  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
111
  export interface GajaePixelFrames {
98
112
  /** escape payload per logical frame (drawn at the current cursor cell) */
99
- frames: Record<GajaePixelFrameName, string>;
113
+ frames: Record<string, string>;
100
114
  /** protocol the frames were encoded for */
101
- protocol: "sixel" | "kitty";
115
+ protocol: "sixel" | "kitty" | "iterm2";
102
116
  widthPx: number;
103
117
  heightPx: number;
104
118
  columns: number;
@@ -108,11 +122,11 @@ export interface GajaePixelFrames {
108
122
  }
109
123
  /**
110
124
  * Build overlay pixel frames exactly `targetRows` terminal rows tall when the
111
- * terminal cells permit it. Nearest-neighbor sampling preserves the 16x16 art
112
- * while allowing fractional scale factors such as 36px / 16px.
125
+ * terminal cells permit it. Each skin owns its source resolution so future
126
+ * additions can opt into denser art without changing the terminal footprint.
113
127
  */
114
128
  export declare function buildGajaePixelFrames(options: {
115
- protocol: "sixel" | "kitty";
129
+ protocol: "sixel" | "kitty" | "iterm2";
116
130
  cellWidthPx: number;
117
131
  cellHeightPx: number;
118
132
  targetRows?: number;
@@ -121,6 +135,10 @@ export declare function buildGajaePixelFrames(options: {
121
135
  /** Native sub-cell `Y=` pixel offset that drops the kitty sprite within its first cell. */
122
136
  kittyCellYOffsetPx?: number;
123
137
  kittyImageId?: number;
138
+ /** Transparent iTerm2-only top padding for half-cell vertical alignment. */
139
+ iterm2TopPaddingPx?: number;
140
+ /** Transparent iTerm2-only bottom padding inside the two-row canvas. */
141
+ iterm2BottomPaddingPx?: number;
124
142
  /** Color skin for the sprite palette (default "red"). */
125
143
  skin?: PetSkinId;
126
144
  }): 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 theme;
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, theme: SelectListTheme, layout?: SelectListLayoutOptions);
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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/tui",
4
- "version": "0.13.1",
4
+ "version": "0.13.3",
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.13.1",
40
- "@gajae-code/utils": "0.13.1",
39
+ "@gajae-code/natives": "0.13.3",
40
+ "@gajae-code/utils": "0.13.3",
41
41
  "lru-cache": "11.3.6",
42
42
  "marked": "18.0.6"
43
43
  },