@oh-my-pi/pi-tui 13.3.13 → 13.4.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,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.4.0] - 2026-03-01
6
+
7
+ ### Added
8
+
9
+ - Added `PI_TUI_RESIZE_CLEAR_STRATEGY` environment variable to control terminal behavior on resize: `viewport` (default) clears/redraws the viewport while preserving scrollback, or `scrollback` clears all history
10
+
11
+ ### Changed
12
+
13
+ - Changed resize redraw behavior to use configurable clear semantics (`viewport` vs `scrollback`) while keeping full content rendering for scrollback navigation
14
+
15
+ ### Fixed
16
+
17
+ - Fixed loader component rendering lines wider than terminal width, preventing text overflow and display artifacts
18
+
5
19
  ## [13.3.11] - 2026-02-28
6
20
 
7
21
  ### Fixed
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.13",
4
+ "version": "13.4.0",
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,8 +33,8 @@
33
33
  "test": "bun test test/*.test.ts"
34
34
  },
35
35
  "dependencies": {
36
- "@oh-my-pi/pi-natives": "13.3.13",
37
- "@oh-my-pi/pi-utils": "13.3.13",
36
+ "@oh-my-pi/pi-natives": "13.4.0",
37
+ "@oh-my-pi/pi-utils": "13.4.0",
38
38
  "marked": "^17.0"
39
39
  },
40
40
  "devDependencies": {
@@ -1,4 +1,5 @@
1
1
  import type { TUI } from "../tui";
2
+ import { sliceByColumn, visibleWidth } from "../utils";
2
3
  import { Text } from "./text";
3
4
 
4
5
  /**
@@ -26,7 +27,14 @@ export class Loader extends Text {
26
27
  }
27
28
 
28
29
  render(width: number): string[] {
29
- return ["", ...super.render(width)];
30
+ const lines = ["", ...super.render(width)];
31
+ for (let i = 0; i < lines.length; i++) {
32
+ const line = lines[i];
33
+ if (visibleWidth(line) > width) {
34
+ lines[i] = sliceByColumn(line, 0, width, true);
35
+ }
36
+ }
37
+ return lines;
30
38
  }
31
39
 
32
40
  start() {
package/src/index.ts CHANGED
@@ -1,67 +1,40 @@
1
1
  // Core TUI interfaces and classes
2
2
 
3
3
  // Autocomplete support
4
- export {
5
- type AutocompleteItem,
6
- type AutocompleteProvider,
7
- CombinedAutocompleteProvider,
8
- type SlashCommand,
9
- } from "./autocomplete";
4
+ export * from "./autocomplete";
10
5
  // Components
11
- export { Box } from "./components/box";
12
- export { CancellableLoader } from "./components/cancellable-loader";
13
- export { Editor, type EditorTheme, type EditorTopBorder } from "./components/editor";
14
- export { Image, type ImageOptions, type ImageTheme } from "./components/image";
15
- export { Input } from "./components/input";
16
- export { Loader } from "./components/loader";
17
- export { type DefaultTextStyle, Markdown, type MarkdownTheme } from "./components/markdown";
18
- export { type SelectItem, SelectList, type SelectListTheme } from "./components/select-list";
19
- export { type SettingItem, SettingsList, type SettingsListTheme } from "./components/settings-list";
20
- export { Spacer } from "./components/spacer";
21
- export { type Tab, TabBar, type TabBarTheme } from "./components/tab-bar";
22
- export { Text } from "./components/text";
23
- export { TruncatedText } from "./components/truncated-text";
6
+ export * from "./components/box";
7
+ export * from "./components/cancellable-loader";
8
+ export * from "./components/editor";
9
+ export * from "./components/image";
10
+ export * from "./components/input";
11
+ export * from "./components/loader";
12
+ export * from "./components/markdown";
13
+ export * from "./components/select-list";
14
+ export * from "./components/settings-list";
15
+ export * from "./components/spacer";
16
+ export * from "./components/tab-bar";
17
+ export * from "./components/text";
18
+ export * from "./components/truncated-text";
24
19
  // Editor component interface (for custom editors)
25
- export type { EditorComponent } from "./editor-component";
20
+ export type * from "./editor-component";
26
21
  // Fuzzy matching
27
- export { type FuzzyMatch, fuzzyFilter, fuzzyMatch } from "./fuzzy";
22
+ export * from "./fuzzy";
28
23
  // Keybindings
29
- export {
30
- DEFAULT_EDITOR_KEYBINDINGS,
31
- type EditorAction,
32
- type EditorKeybindingsConfig,
33
- EditorKeybindingsManager,
34
- getEditorKeybindings,
35
- setEditorKeybindings,
36
- } from "./keybindings";
24
+ export * from "./keybindings";
37
25
  // Kitty keyboard protocol helpers
38
- export {
39
- isKeyRelease,
40
- isKeyRepeat,
41
- isKittyProtocolActive,
42
- type KeyId,
43
- matchesKey,
44
- parseKey,
45
- parseKittySequence,
46
- setKittyProtocolActive,
47
- } from "./keys";
26
+ export * from "./keys";
48
27
  // Mermaid diagram support
49
- export {
50
- extractMermaidBlocks,
51
- type MermaidImage,
52
- type MermaidRenderOptions,
53
- prerenderMermaidBlocks,
54
- renderMermaidToPng,
55
- } from "./mermaid";
28
+ export * from "./mermaid";
56
29
  // Input buffering for batch splitting
57
- export { StdinBuffer, type StdinBufferEventMap, type StdinBufferOptions } from "./stdin-buffer";
58
- export type { BoxSymbols, SymbolTheme } from "./symbols";
30
+ export * from "./stdin-buffer";
31
+ export type * from "./symbols";
59
32
  // Terminal interface and implementations
60
- export { emergencyTerminalRestore, ProcessTerminal, type Terminal, type TerminalAppearance } from "./terminal";
33
+ export * from "./terminal";
61
34
  // Terminal image support
62
35
  export * from "./terminal-capabilities";
63
36
  // TTY ID
64
- export { getTerminalId, getTtyPath } from "./ttyid";
65
- export { type Component, Container, type OverlayHandle, type SizeValue, TUI } from "./tui";
37
+ export * from "./ttyid";
38
+ export * from "./tui";
66
39
  // Utilities
67
- export { Ellipsis, padding, replaceTabs, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils";
40
+ export * from "./utils";
package/src/tui.ts CHANGED
@@ -11,6 +11,11 @@ import { extractSegments, sliceByColumn, sliceWithWidth, visibleWidth } from "./
11
11
 
12
12
  const SEGMENT_RESET = "\x1b[0m";
13
13
 
14
+ type ResizeClearStrategy = "viewport" | "scrollback";
15
+
16
+ function resolveResizeClearStrategy(value: string | undefined): ResizeClearStrategy {
17
+ return value?.toLowerCase() === "scrollback" ? "scrollback" : "viewport";
18
+ }
14
19
  type InputListenerResult = { consume?: boolean; data?: string } | undefined;
15
20
  type InputListener = (data: string) => InputListenerResult;
16
21
 
@@ -217,6 +222,7 @@ export class TUI extends Container {
217
222
  #cellSizeQueryPending = false;
218
223
  #showHardwareCursor = process.env.PI_HARDWARE_CURSOR === "1";
219
224
  #clearOnShrink = process.env.PI_CLEAR_ON_SHRINK === "1"; // Clear empty rows when content shrinks (default: off)
225
+ #resizeClearStrategy = resolveResizeClearStrategy(process.env.PI_TUI_RESIZE_CLEAR_STRATEGY); // Resize full-redraw strategy: viewport(default) or scrollback
220
226
  #maxLinesRendered = 0; // Track terminal's working area (max lines ever rendered)
221
227
  #previousViewportTop = 0; // Track previous viewport top for resize-aware cursor moves
222
228
  #fullRedrawCount = 0;
@@ -885,19 +891,34 @@ export class TUI extends Container {
885
891
  // Helper to clear scrollback and viewport and render all new lines
886
892
  const fullRender = (clear: boolean): void => {
887
893
  this.#fullRedrawCount += 1;
894
+ const isResizeRedraw = clear && (widthChanged || heightChanged);
895
+ const shouldRenderViewportOnly =
896
+ isResizeRedraw && this.#resizeClearStrategy === "viewport" && newLines.length <= this.#previousLines.length;
897
+ const renderFrom = shouldRenderViewportOnly ? Math.max(0, newLines.length - height) : 0;
898
+ const renderedLines = renderFrom > 0 ? newLines.slice(renderFrom) : newLines;
888
899
  let buffer = "\x1b[?2026h"; // Begin synchronized output
889
- if (clear) buffer += this.#clearScrollbackOnNextFullRender ? "\x1b[3J\x1b[H\x1b[0J" : "\x1b[H\x1b[0J"; // Home + clear from cursor (and optionally scrollback)
890
- for (let i = 0; i < newLines.length; i++) {
900
+ if (clear) {
901
+ // Keep default home+erase-below semantics for non-resize redraws.
902
+ // On resize, choose behavior via PI_TUI_RESIZE_CLEAR_STRATEGY.
903
+ if (this.#clearScrollbackOnNextFullRender) buffer += "\x1b[3J\x1b[2J\x1b[H";
904
+ else if (isResizeRedraw && this.#resizeClearStrategy === "scrollback") buffer += "\x1b[3J\x1b[2J\x1b[H";
905
+ else if (isResizeRedraw) buffer += "\x1b[2J\x1b[H";
906
+ else buffer += "\x1b[H\x1b[0J";
907
+ }
908
+ for (let i = 0; i < renderedLines.length; i++) {
891
909
  if (i > 0) buffer += "\r\n";
892
- buffer += newLines[i];
910
+ buffer += renderedLines[i];
893
911
  }
894
- const renderCursorRow = Math.max(0, newLines.length - 1);
895
- const cursorUpdate = this.#buildHardwareCursorSequence(cursorPos, newLines.length, renderCursorRow);
912
+ const renderCursorRow = Math.max(0, renderedLines.length - 1);
913
+ const renderCursorPos = cursorPos
914
+ ? { row: Math.max(0, cursorPos.row - renderFrom), col: cursorPos.col }
915
+ : null;
916
+ const cursorUpdate = this.#buildHardwareCursorSequence(renderCursorPos, renderedLines.length, renderCursorRow);
896
917
  buffer += cursorUpdate.sequence;
897
918
  buffer += "\x1b[?2026l"; // End synchronized output
898
919
  this.terminal.write(buffer);
899
- this.#cursorRow = renderCursorRow;
900
- this.#hardwareCursorRow = cursorUpdate.row;
920
+ this.#cursorRow = Math.max(0, newLines.length - 1);
921
+ this.#hardwareCursorRow = cursorUpdate.row + renderFrom;
901
922
  // Reset max lines when clearing, otherwise track growth
902
923
  if (clear) {
903
924
  this.#maxLinesRendered = newLines.length;