@oh-my-pi/pi-tui 17.0.9 → 17.1.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 CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.1.0] - 2026-07-24
6
+
7
+ ### Added
8
+
9
+ - Added Text.setStyleFn() to apply foreground stylers at render time, allowing components to dynamically re-resolve colors after invalidation instead of baking in the palette active at construction.
10
+
11
+ ### Fixed
12
+
13
+ - Fixed an issue where exiting or tearing down the TUI left the terminal in cursor-key/keypad application mode (DECCKM), which broke arrow keys in the parent shell. Both stop() and emergencyTerminalRestore() now correctly emit standard rmkx resets.
14
+
5
15
  ## [17.0.9] - 2026-07-23
6
16
 
7
17
  ### Added
@@ -1,6 +1,12 @@
1
1
  import type { Component } from "../tui.js";
2
2
  /**
3
- * Text component - displays multi-line text with word wrapping
3
+ * Text component - displays multi-line text with word wrapping.
4
+ *
5
+ * Foreground colors may be supplied lazily via {@link setStyleFn} instead of
6
+ * baked into `text`: the styler runs at render time, so a caller that
7
+ * invalidates the component on a theme change (see the coding-agent's
8
+ * `onThemeChange` handler) re-resolves the color against the now-active theme
9
+ * rather than replaying the palette active when the component was constructed.
4
10
  */
5
11
  export declare class Text implements Component {
6
12
  #private;
@@ -9,6 +15,13 @@ export declare class Text implements Component {
9
15
  getText(): string;
10
16
  setText(text: string): boolean;
11
17
  setCustomBgFn(customBgFn?: (text: string) => string): void;
18
+ /**
19
+ * Supply a foreground styler applied to the text at render time (e.g. a
20
+ * theme color resolver). Unlike baking the color into `text`, the styler
21
+ * re-runs on every render, so invalidating the component after a theme
22
+ * change re-resolves the color against the active theme.
23
+ */
24
+ setStyleFn(styleFn?: (text: string) => string): this;
12
25
  invalidate(): void;
13
26
  render(width: number): readonly string[];
14
27
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "17.0.9",
4
+ "version": "17.1.0",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -37,8 +37,8 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@oh-my-pi/pi-natives": "17.0.9",
41
- "@oh-my-pi/pi-utils": "17.0.9",
40
+ "@oh-my-pi/pi-natives": "17.1.0",
41
+ "@oh-my-pi/pi-utils": "17.1.0",
42
42
  "lru-cache": "11.5.2",
43
43
  "marked": "^18.0.6"
44
44
  },
@@ -11,13 +11,20 @@ import {
11
11
  } from "../utils";
12
12
 
13
13
  /**
14
- * Text component - displays multi-line text with word wrapping
14
+ * Text component - displays multi-line text with word wrapping.
15
+ *
16
+ * Foreground colors may be supplied lazily via {@link setStyleFn} instead of
17
+ * baked into `text`: the styler runs at render time, so a caller that
18
+ * invalidates the component on a theme change (see the coding-agent's
19
+ * `onThemeChange` handler) re-resolves the color against the now-active theme
20
+ * rather than replaying the palette active when the component was constructed.
15
21
  */
16
22
  export class Text implements Component {
17
23
  #text: string;
18
24
  #paddingX: number; // Left/right padding
19
25
  #paddingY: number; // Top/bottom padding
20
26
  #customBgFn?: (text: string) => string;
27
+ #styleFn?: (text: string) => string;
21
28
 
22
29
  #ignoreTight = false;
23
30
 
@@ -64,6 +71,21 @@ export class Text implements Component {
64
71
  this.#cachedLines = undefined;
65
72
  }
66
73
 
74
+ /**
75
+ * Supply a foreground styler applied to the text at render time (e.g. a
76
+ * theme color resolver). Unlike baking the color into `text`, the styler
77
+ * re-runs on every render, so invalidating the component after a theme
78
+ * change re-resolves the color against the active theme.
79
+ */
80
+ setStyleFn(styleFn?: (text: string) => string): this {
81
+ this.#styleFn = styleFn;
82
+ this.#cachedText = undefined;
83
+ this.#cachedWidth = undefined;
84
+ this.#cachedWidthEpoch = undefined;
85
+ this.#cachedLines = undefined;
86
+ return this;
87
+ }
88
+
67
89
  invalidate(): void {
68
90
  this.#cachedText = undefined;
69
91
  this.#cachedWidth = undefined;
@@ -93,7 +115,7 @@ export class Text implements Component {
93
115
  }
94
116
 
95
117
  // Replace tabs with 3 spaces
96
- const normalizedText = replaceTabs(this.#text);
118
+ const normalizedText = replaceTabs(this.#styleFn ? this.#styleFn(this.#text) : this.#text);
97
119
 
98
120
  // Calculate content width (subtract left/right margins)
99
121
  const paddingX = this.#ignoreTight ? this.#paddingX : getPaddingX(this.#paddingX);
package/src/terminal.ts CHANGED
@@ -312,6 +312,7 @@ export function emergencyTerminalRestore(): void {
312
312
  process.stdout.write(
313
313
  "\x1b[?2026l" + // End synchronized output
314
314
  "\x1b[?7h" + // Restore autowrap
315
+ "\x1b[?1l\x1b>" + // Restore normal cursor-key + keypad mode (rmkx, #6374)
315
316
  "\x1b[?2004l" + // Disable bracketed paste
316
317
  "\x1b[?2031l" + // Disable Mode 2031 appearance notifications
317
318
  "\x1b[?2048l" + // Disable in-band resize notifications
@@ -625,6 +626,15 @@ export class ProcessTerminal implements Terminal {
625
626
  // Enable bracketed paste mode - terminal will wrap pastes in \x1b[200~ ... \x1b[201~
626
627
  this.#safeWrite("\x1b[?2004h");
627
628
 
629
+ // Force normal cursor-key (DECCKM) and numeric-keypad mode (terminfo
630
+ // `rmkx` = "\x1b[?1l\x1b>"). omp decodes both CSI ("\x1b[A") and SS3
631
+ // ("\x1bOA") arrow encodings, so it never enables application mode
632
+ // itself — but a prior program that left the TTY in application-cursor-
633
+ // keys mode makes arrows arrive as SS3. Normalizing on entry keeps input
634
+ // in the predictable default state; stop() restores the same on exit.
635
+ // See #6374.
636
+ this.#safeWrite("\x1b[?1l\x1b>");
637
+
628
638
  // Set up resize handler immediately. The OS refreshes process.stdout
629
639
  // dimensions before firing `resize`, so it is authoritative for geometry:
630
640
  // reconcile any stale cached DEC 2048 report before notifying the renderer.
@@ -1376,6 +1386,13 @@ export class ProcessTerminal implements Terminal {
1376
1386
  // begin/end halves of a frame. Safe no-ops on terminals that ignored them.
1377
1387
  this.#safeWrite("\x1b[?2026l\x1b[?7h");
1378
1388
 
1389
+ // Restore normal cursor-key (DECCKM) and numeric-keypad mode (terminfo
1390
+ // `rmkx`). Symmetric with the normalize in start(): a TTY-sharing child
1391
+ // can leave the terminal in application-cursor-keys mode, and without
1392
+ // this reset the parent shell inherits SS3 arrows so Up/Down history
1393
+ // navigation stays broken after omp exits (#6374).
1394
+ this.#safeWrite("\x1b[?1l\x1b>");
1395
+
1379
1396
  // Disable bracketed paste mode
1380
1397
  this.#safeWrite("\x1b[?2004l");
1381
1398
  this.#safeWrite("\x1b[?5522l");