@oh-my-pi/pi-tui 13.3.7 → 13.3.8

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,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.3.8] - 2026-02-28
6
+ ### Breaking Changes
7
+
8
+ - Changed mermaid hash type from string to bigint in `getMermaidImage` callback and `extractMermaidBlocks` return type
9
+ - Removed `mime-types` and `@types/mime-types` from dependencies
10
+ - Removed `@xterm/xterm` from dependencies
11
+
12
+ ### Changed
13
+
14
+ - Updated mermaid hash computation to use `Bun.hash.xxHash64()` instead of `Bun.hash().toString(16)`
15
+
5
16
  ## [12.19.0] - 2026-02-22
6
17
 
7
18
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "13.3.7",
4
+ "version": "13.3.8",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -33,16 +33,13 @@
33
33
  "test": "bun test test/*.test.ts"
34
34
  },
35
35
  "dependencies": {
36
- "@oh-my-pi/pi-natives": "13.3.7",
37
- "@oh-my-pi/pi-utils": "13.3.7",
38
- "@types/mime-types": "^3.0",
39
- "chalk": "^5.6",
40
- "marked": "^17.0",
41
- "mime-types": "^3.0"
36
+ "@oh-my-pi/pi-natives": "13.3.8",
37
+ "@oh-my-pi/pi-utils": "13.3.8",
38
+ "marked": "^17.0"
42
39
  },
43
40
  "devDependencies": {
44
- "@xterm/headless": "^6.0",
45
- "@xterm/xterm": "^6.0"
41
+ "chalk": "^5.6",
42
+ "@xterm/headless": "^6.0"
46
43
  },
47
44
  "engines": {
48
45
  "bun": ">=1.3.7"
@@ -46,10 +46,10 @@ export interface MarkdownTheme {
46
46
  highlightCode?: (code: string, lang?: string) => string[];
47
47
  /**
48
48
  * Lookup a pre-rendered mermaid image by source hash.
49
- * Hash is computed as `Bun.hash(source.trim()).toString(16)`.
49
+ * Hash is computed as `Bun.hash.xxHash64(source.trim())`.
50
50
  * Return null to fall back to text rendering.
51
51
  */
52
- getMermaidImage?: (sourceHash: string) => MermaidImage | null;
52
+ getMermaidImage?: (sourceHash: bigint) => MermaidImage | null;
53
53
  symbols: SymbolTheme;
54
54
  }
55
55
 
@@ -322,7 +322,7 @@ export class Markdown implements Component {
322
322
  case "code": {
323
323
  // Handle mermaid diagrams with image rendering when available
324
324
  if (token.lang === "mermaid" && this.#theme.getMermaidImage) {
325
- const hash = Bun.hash(token.text.trim()).toString(16);
325
+ const hash = Bun.hash.xxHash64(token.text.trim());
326
326
  const image = this.#theme.getMermaidImage(hash);
327
327
 
328
328
  if (image && TERMINAL.imageProtocol) {
package/src/index.ts CHANGED
@@ -57,7 +57,7 @@ export {
57
57
  export { StdinBuffer, type StdinBufferEventMap, type StdinBufferOptions } from "./stdin-buffer";
58
58
  export type { BoxSymbols, SymbolTheme } from "./symbols";
59
59
  // Terminal interface and implementations
60
- export { emergencyTerminalRestore, ProcessTerminal, type Terminal } from "./terminal";
60
+ export { emergencyTerminalRestore, ProcessTerminal, type Terminal, type TerminalAppearance } from "./terminal";
61
61
  // Terminal image support
62
62
  export * from "./terminal-capabilities";
63
63
  // TTY ID
package/src/mermaid.ts CHANGED
@@ -99,13 +99,13 @@ function parsePngDimensions(buffer: Uint8Array): { width: number; height: number
99
99
  * Extract mermaid code blocks from markdown text.
100
100
  * Returns array of { source, startIndex, endIndex } for each block.
101
101
  */
102
- export function extractMermaidBlocks(markdown: string): { source: string; hash: string }[] {
103
- const blocks: { source: string; hash: string }[] = [];
102
+ export function extractMermaidBlocks(markdown: string): { source: string; hash: bigint }[] {
103
+ const blocks: { source: string; hash: bigint }[] = [];
104
104
  const regex = /```mermaid\s*\n([\s\S]*?)```/g;
105
105
 
106
106
  for (let match = regex.exec(markdown); match !== null; match = regex.exec(markdown)) {
107
107
  const source = match[1].trim();
108
- const hash = Bun.hash(source).toString(16);
108
+ const hash = Bun.hash.xxHash64(source);
109
109
  blocks.push({ source, hash });
110
110
  }
111
111
 
@@ -119,9 +119,9 @@ export function extractMermaidBlocks(markdown: string): { source: string; hash:
119
119
  export async function prerenderMermaidBlocks(
120
120
  markdown: string,
121
121
  options: MermaidRenderOptions = {},
122
- ): Promise<Map<string, MermaidImage>> {
122
+ ): Promise<Map<bigint, MermaidImage>> {
123
123
  const blocks = extractMermaidBlocks(markdown);
124
- const cache = new Map<string, MermaidImage>();
124
+ const cache = new Map<bigint, MermaidImage>();
125
125
 
126
126
  const results = await Promise.all(
127
127
  blocks.map(async ({ source, hash }) => {
package/src/terminal.ts CHANGED
@@ -30,6 +30,7 @@ export function emergencyTerminalRestore(): void {
30
30
  // This avoids writing escape sequences for non-TUI commands (grep, commit, etc.)
31
31
  process.stdout.write(
32
32
  "\x1b[?2004l" + // Disable bracketed paste
33
+ "\x1b[?2031l" + // Disable Mode 2031 appearance notifications
33
34
  "\x1b[<u" + // Pop kitty keyboard protocol
34
35
  "\x1b[?25h", // Show cursor
35
36
  );
@@ -41,6 +42,8 @@ export function emergencyTerminalRestore(): void {
41
42
  // Terminal may already be dead during crash cleanup - ignore errors
42
43
  }
43
44
  }
45
+ /** Terminal-reported appearance (dark/light mode). */
46
+ export type TerminalAppearance = "dark" | "light";
44
47
  export interface Terminal {
45
48
  // Start the terminal with input and resize handlers
46
49
  start(onInput: (data: string) => void, onResize: () => void): void;
@@ -80,6 +83,16 @@ export interface Terminal {
80
83
 
81
84
  // Title operations
82
85
  setTitle(title: string): void; // Set terminal window title
86
+
87
+ /**
88
+ * Register a callback for Mode 2031 dark/light appearance change notifications.
89
+ * Supported by Ghostty, Kitty, Contour, VTE (GNOME Terminal), and tmux 3.6+.
90
+ * The callback fires when the terminal reports a change; it does NOT fire for the initial query.
91
+ */
92
+ onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
93
+
94
+ /** The last appearance reported by the terminal, or undefined if not yet known. */
95
+ get appearance(): TerminalAppearance | undefined;
83
96
  }
84
97
 
85
98
  /**
@@ -95,11 +108,21 @@ export class ProcessTerminal implements Terminal {
95
108
  #dead = false;
96
109
  #writeLogPath = $env.PI_TUI_WRITE_LOG || "";
97
110
  #windowsVTInputRestore?: () => void;
111
+ #appearanceCallbacks: Array<(appearance: TerminalAppearance) => void> = [];
112
+ #appearance: TerminalAppearance | undefined;
98
113
 
99
114
  get kittyProtocolActive(): boolean {
100
115
  return this.#kittyProtocolActive;
101
116
  }
102
117
 
118
+ get appearance(): TerminalAppearance | undefined {
119
+ return this.#appearance;
120
+ }
121
+
122
+ onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void {
123
+ this.#appearanceCallbacks.push(callback);
124
+ }
125
+
103
126
  start(onInput: (data: string) => void, onResize: () => void): void {
104
127
  this.#inputHandler = onInput;
105
128
  this.#resizeHandler = onResize;
@@ -137,6 +160,13 @@ export class ProcessTerminal implements Terminal {
137
160
  // The query handler intercepts input temporarily, then installs the user's handler
138
161
  // See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
139
162
  this.#queryAndEnableKittyProtocol();
163
+
164
+ // Enable Mode 2031: terminal will send DSR notifications on dark/light appearance changes.
165
+ // Query current mode with CSI ? 996 n, then subscribe with CSI ? 2031 h.
166
+ // Supported by Ghostty, Kitty, Contour, VTE/GNOME Terminal, tmux 3.6+.
167
+ // Unsupported terminals silently ignore these sequences.
168
+ this.#safeWrite("\x1b[?996n"); // Query current appearance
169
+ this.#safeWrite("\x1b[?2031h"); // Subscribe to appearance changes
140
170
  }
141
171
 
142
172
  /**
@@ -203,6 +233,9 @@ export class ProcessTerminal implements Terminal {
203
233
  // Kitty protocol response pattern: \x1b[?<flags>u
204
234
  const kittyResponsePattern = /^\x1b\[\?(\d+)u$/;
205
235
 
236
+ // Mode 2031 DSR response pattern: \x1b[?997;{1=dark,2=light}n
237
+ const appearanceDsrPattern = /^\x1b\[\?997;([12])n$/;
238
+
206
239
  // Forward individual sequences to the input handler
207
240
  this.#stdinBuffer.on("data", (sequence: string) => {
208
241
  // Check for Kitty protocol response (only if not already enabled)
@@ -221,6 +254,23 @@ export class ProcessTerminal implements Terminal {
221
254
  }
222
255
  }
223
256
 
257
+ // Check for Mode 2031 appearance DSR response: CSI ? 997 ; {1,2} n
258
+ const appearanceMatch = sequence.match(appearanceDsrPattern);
259
+ if (appearanceMatch) {
260
+ const mode: TerminalAppearance = appearanceMatch[1] === "1" ? "dark" : "light";
261
+ const changed = mode !== this.#appearance;
262
+ this.#appearance = mode;
263
+ if (changed) {
264
+ for (const cb of this.#appearanceCallbacks) {
265
+ try {
266
+ cb(mode);
267
+ } catch {
268
+ /* ignore callback errors */
269
+ }
270
+ }
271
+ }
272
+ return; // Don't forward DSR to TUI
273
+ }
224
274
  if (this.#inputHandler) {
225
275
  this.#inputHandler(sequence);
226
276
  }
@@ -297,6 +347,10 @@ export class ProcessTerminal implements Terminal {
297
347
  // Disable bracketed paste mode
298
348
  this.#safeWrite("\x1b[?2004l");
299
349
 
350
+ // Disable Mode 2031 appearance change notifications
351
+ this.#safeWrite("\x1b[?2031l");
352
+ this.#appearanceCallbacks = [];
353
+
300
354
  // Disable Kitty keyboard protocol if not already done by drainInput()
301
355
  if (this.#kittyProtocolActive) {
302
356
  this.#safeWrite("\x1b[<u");
@@ -317,6 +371,7 @@ export class ProcessTerminal implements Terminal {
317
371
  this.#stdinDataHandler = undefined;
318
372
  }
319
373
  this.#inputHandler = undefined;
374
+ this.#appearance = undefined;
320
375
  if (this.#resizeHandler) {
321
376
  process.stdout.removeListener("resize", this.#resizeHandler);
322
377
  this.#resizeHandler = undefined;
@@ -391,7 +446,7 @@ export class ProcessTerminal implements Terminal {
391
446
  }
392
447
 
393
448
  clearScreen(): void {
394
- this.#safeWrite("\x1b[2J\x1b[H"); // Clear screen and move to home (1,1)
449
+ this.#safeWrite("\x1b[H\x1b[0J"); // Move to home (1,1) and clear from cursor to end
395
450
  }
396
451
 
397
452
  setTitle(title: string): void {
package/src/tui.ts CHANGED
@@ -886,7 +886,7 @@ export class TUI extends Container {
886
886
  const fullRender = (clear: boolean): void => {
887
887
  this.#fullRedrawCount += 1;
888
888
  let buffer = "\x1b[?2026h"; // Begin synchronized output
889
- if (clear) buffer += this.#clearScrollbackOnNextFullRender ? "\x1b[3J\x1b[2J\x1b[H" : "\x1b[2J\x1b[H"; // Clear viewport (and optionally scrollback), then home
889
+ if (clear) buffer += this.#clearScrollbackOnNextFullRender ? "\x1b[3J\x1b[H\x1b[0J" : "\x1b[H\x1b[0J"; // Home + clear from cursor (and optionally scrollback)
890
890
  for (let i = 0; i < newLines.length; i++) {
891
891
  if (i > 0) buffer += "\r\n";
892
892
  buffer += newLines[i];