@code-yeongyu/senpi-tui 2026.11.3-2 → 2026.11.4-2

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/dist/index.d.ts CHANGED
@@ -34,6 +34,7 @@ export { parseTerminalColorSchemeReport, type RgbColor, type TerminalColorScheme
34
34
  export declare function calculateImageRows(imageDimensions: ImageDimensions, targetWidthCells: number, cellDimensions?: CellDimensions): number;
35
35
  export { allocateImageId, buildKittyPlaceholderRow, type CellDimensions, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getTerminalColorMode, getWebpDimensions, hyperlink, type ImageDimensions, type ImageProtocol, type ImageRenderOptions, imageFallback, KITTY_PLACEHOLDER_MAX, outerKittyGraphicsMode, renderImage, resetCapabilitiesCache, setCapabilities, setCapabilityOverrides, setCellDimensions, type TerminalCapabilities, type TmuxPassthroughState, wrapTmuxPassthrough, } from "./terminal-image.ts";
36
36
  export { sanitizeTerminalLabel, shortenImagePath } from "./terminal-text.ts";
37
+ export { encodeProgramStatus, type ProgramStatus, type ProgramStatusKind, type ProgramStatusState, } from "./program-status.ts";
37
38
  export { type Component, CompositeRevision, Container, CURSOR_MARKER, claimFrameRow, compositeTuiLine, currentRenderRevision, dispatchMouseEvent, type Focusable, type FrameLineBytesTotals, frameLineBytesTotals, frameMode, frameScrollbackRows, isFocusable, isViewportTUI, joinLineArrays, mainScreenHistoryLines, nextRenderRevision, type OverlayAnchor, type OverlayBounds, type OverlayHandle, type OverlayMargin, type OverlayOptions, type OverlayUnfocusOptions, renderAtFrameRow, resetMainScreenHistoryLines, type SizeValue, TUI, type TuiInputListener, type TuiInputListenerResult, type TuiMode, type TuiMouseButton, type TuiMouseDispatchResult, type TuiMouseEvent, type TuiMouseEventResult, type TuiMouseEventType, type TuiStopOptions, type ViewportTUI, } from "./tui.ts";
38
39
  export { TuiAltScreen, type TuiAltScreenOptions } from "./tui-alt-screen.ts";
39
40
  export { TuiMainScreen, type TuiMainScreenRenderState } from "./tui-main-screen.ts";
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ export function calculateImageRows(imageDimensions, targetWidthCells, cellDimens
49
49
  }
50
50
  export { allocateImageId, buildKittyPlaceholderRow, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getTerminalColorMode, getWebpDimensions, hyperlink, imageFallback, KITTY_PLACEHOLDER_MAX, outerKittyGraphicsMode, renderImage, resetCapabilitiesCache, setCapabilities, setCapabilityOverrides, setCellDimensions, wrapTmuxPassthrough, } from "./terminal-image.js";
51
51
  export { sanitizeTerminalLabel, shortenImagePath } from "./terminal-text.js";
52
+ export { encodeProgramStatus, } from "./program-status.js";
52
53
  export { CompositeRevision, Container, CURSOR_MARKER, claimFrameRow, compositeTuiLine, currentRenderRevision, dispatchMouseEvent, frameLineBytesTotals, frameMode, frameScrollbackRows, isFocusable, isViewportTUI, joinLineArrays, mainScreenHistoryLines, nextRenderRevision, renderAtFrameRow, resetMainScreenHistoryLines, TUI, } from "./tui.js";
53
54
  export { TuiAltScreen } from "./tui-alt-screen.js";
54
55
  export { TuiMainScreen } from "./tui-main-screen.js";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * OSC 7501 Program Status Protocol (rev 0.4): a program tells its terminal whether it is idle, working,
3
+ * blocked on the user, done or failed. https://control-codes.page/proposals/osc-7501/
4
+ */
5
+ export type ProgramStatusState = "idle" | "working" | "blocked" | "done" | "error";
6
+ export type ProgramStatusKind = "permission" | "question" | "auth";
7
+ export interface ProgramStatus {
8
+ state: ProgramStatusState;
9
+ /** How the user must act; only sent with `blocked`. */
10
+ kind?: ProgramStatusKind;
11
+ /** One line on what the program is doing or waiting for. */
12
+ message?: string;
13
+ }
14
+ /**
15
+ * Build the OSC 7501 report for `status`, or the `state=clear` report that removes every record when it is
16
+ * undefined. `app` is repeated in every report because each one replaces its record; an app name outside the
17
+ * protocol's charset or length is left out rather than sent for the terminal to discard.
18
+ */
19
+ export declare function encodeProgramStatus(status: ProgramStatus | undefined, app?: string): string;
20
+ //# sourceMappingURL=program-status.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * OSC 7501 Program Status Protocol (rev 0.4): a program tells its terminal whether it is idle, working,
3
+ * blocked on the user, done or failed. https://control-codes.page/proposals/osc-7501/
4
+ */
5
+ const MAX_MESSAGE_BYTES = 2048;
6
+ const APP_PATTERN = /^[A-Za-z0-9_.+-]{1,32}$/;
7
+ // C0, DEL and C1 controls: a decoded msg holding any of them makes the terminal drop the whole report.
8
+ const CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]+/g;
9
+ // Bidi overrides/isolates and invisible formatting, which a terminal may show outside the grid.
10
+ const INVISIBLE_FORMATTING_PATTERN = /[\u00ad\u061c\u180e\u200b\u200e\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/g;
11
+ function sanitizeMessage(message) {
12
+ return message.replace(INVISIBLE_FORMATTING_PATTERN, "").replace(CONTROL_PATTERN, " ").replace(/\s+/g, " ").trim();
13
+ }
14
+ function truncateUtf8(text, maxBytes) {
15
+ if (Buffer.byteLength(text, "utf8") <= maxBytes)
16
+ return text;
17
+ let bytes = 0;
18
+ let result = "";
19
+ for (const char of text) {
20
+ bytes += Buffer.byteLength(char, "utf8");
21
+ if (bytes > maxBytes)
22
+ break;
23
+ result += char;
24
+ }
25
+ return result;
26
+ }
27
+ /**
28
+ * Build the OSC 7501 report for `status`, or the `state=clear` report that removes every record when it is
29
+ * undefined. `app` is repeated in every report because each one replaces its record; an app name outside the
30
+ * protocol's charset or length is left out rather than sent for the terminal to discard.
31
+ */
32
+ export function encodeProgramStatus(status, app) {
33
+ if (status === undefined)
34
+ return "\x1b]7501;state=clear\x1b\\";
35
+ const pairs = [`state=${status.state}`];
36
+ if (status.state === "blocked" && status.kind !== undefined)
37
+ pairs.push(`kind=${status.kind}`);
38
+ if (app !== undefined && APP_PATTERN.test(app))
39
+ pairs.push(`app=${app}`);
40
+ const message = status.message === undefined ? "" : truncateUtf8(sanitizeMessage(status.message), MAX_MESSAGE_BYTES);
41
+ if (message.length > 0)
42
+ pairs.push(`msg=${Buffer.from(message, "utf8").toString("base64")}`);
43
+ return `\x1b]7501;${pairs.join(":")}\x1b\\`;
44
+ }
45
+ //# sourceMappingURL=program-status.js.map
@@ -22,6 +22,10 @@ export declare function isAppleTerminalSession(): boolean;
22
22
  */
23
23
  export declare function refreshTerminalDimensions(): void;
24
24
  export declare function normalizeNativeShiftEnterInput(data: string, shouldDetectNativeShiftEnter: boolean, isShiftPressed: boolean): string;
25
+ /**
26
+ * Terminal.app can deliver Shift+Return as ESC CR, which legacy key parsing reads as Alt+Enter.
27
+ * The pressed native Shift keeps it apart from an Option-as-Meta Return, which stays Alt+Enter.
28
+ */
25
29
  export declare function normalizeAppleTerminalInput(data: string, isAppleTerminal: boolean, isShiftPressed: boolean): string;
26
30
  export declare function isWarpWslSession(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, socketExists?: (socketPath: string) => boolean): boolean;
27
31
  export declare function normalizeWarpWslShiftEnterInput(data: string, env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, socketExists?: (socketPath: string) => boolean): string;
package/dist/terminal.js CHANGED
@@ -12,6 +12,7 @@ const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
12
12
  const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
13
13
  const TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1b]9;4;0\x07";
14
14
  const NATIVE_SHIFT_ENTER_SEQUENCE = "\x1b[13;2u";
15
+ const APPLE_TERMINAL_ESCAPED_RETURN_SEQUENCE = "\x1b\r";
15
16
  const DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS = 7;
16
17
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
17
18
  // Bun's macOS tty shim can report synchronous ioctl EIO as raw positive errno 5 without a string code.
@@ -75,7 +76,14 @@ export function normalizeNativeShiftEnterInput(data, shouldDetectNativeShiftEnte
75
76
  return NATIVE_SHIFT_ENTER_SEQUENCE;
76
77
  return data;
77
78
  }
79
+ /**
80
+ * Terminal.app can deliver Shift+Return as ESC CR, which legacy key parsing reads as Alt+Enter.
81
+ * The pressed native Shift keeps it apart from an Option-as-Meta Return, which stays Alt+Enter.
82
+ */
78
83
  export function normalizeAppleTerminalInput(data, isAppleTerminal, isShiftPressed) {
84
+ if (isAppleTerminal && data === APPLE_TERMINAL_ESCAPED_RETURN_SEQUENCE && isShiftPressed) {
85
+ return NATIVE_SHIFT_ENTER_SEQUENCE;
86
+ }
79
87
  return normalizeNativeShiftEnterInput(data, isAppleTerminal, isShiftPressed);
80
88
  }
81
89
  export function isWarpWslSession(env = process.env, platform = process.platform, socketExists = (socketPath) => {
@@ -328,6 +336,9 @@ export class ProcessTerminal {
328
336
  const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
329
337
  const encoding = typeof encodingOrCallback === "string" ? encodingOrCallback : undefined;
330
338
  const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(encoding);
339
+ if (/^\x1b]52;c;(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\x07(?![\s\S])/.test(text)) {
340
+ return rawWrite(text, cb);
341
+ }
331
342
  if (!handler || this.forwardingExternalWrite) {
332
343
  this.noteExternalWrite();
333
344
  rawWrite(text);
@@ -571,8 +582,13 @@ export class ProcessTerminal {
571
582
  forwardInputSequence(sequence) {
572
583
  if (!this.inputHandler)
573
584
  return;
574
- const shouldDetectNativeShiftEnter = sequence === "\r" && (isAppleTerminalSession() || process.platform === "win32");
575
- const input = normalizeNativeShiftEnterInput(sequence, shouldDetectNativeShiftEnter, shouldDetectNativeShiftEnter && isNativeModifierPressed("shift"));
585
+ const isReturn = sequence === "\r" || sequence === APPLE_TERMINAL_ESCAPED_RETURN_SEQUENCE;
586
+ const isAppleTerminal = isReturn && isAppleTerminalSession();
587
+ const shouldDetectNativeShiftEnter = isAppleTerminal || (sequence === "\r" && process.platform === "win32");
588
+ const isShiftPressed = shouldDetectNativeShiftEnter && isNativeModifierPressed("shift");
589
+ const input = isAppleTerminal
590
+ ? normalizeAppleTerminalInput(sequence, true, isShiftPressed)
591
+ : normalizeNativeShiftEnterInput(sequence, shouldDetectNativeShiftEnter, isShiftPressed);
576
592
  this.inputHandler(input);
577
593
  }
578
594
  enableModifyOtherKeys() {
package/dist/tui.js CHANGED
@@ -2774,9 +2774,12 @@ export class TuiBase extends Container {
2774
2774
  const isImage = isImageLine(line);
2775
2775
  const imageReservedRows = isImage ? this.getKittyImageReservedRows(newLines, i, renderEnd) : 1;
2776
2776
  if (imageReservedRows > 1) {
2777
+ // Reserving rows at or past the viewport bottom scrolls the terminal exactly like appended text, and the
2778
+ // relative move back up still lands on the image's first row; only an image that starts above the viewport
2779
+ // or is taller than it cannot be placed in place (senpi#4583).
2777
2780
  const imageStartScreenRow = i - viewportTop;
2778
- if (imageStartScreenRow < 0 || imageStartScreenRow + imageReservedRows > height) {
2779
- logRedraw(`kitty image pre-clear would scroll (${imageStartScreenRow} + ${imageReservedRows} > ${height})`);
2781
+ if (imageStartScreenRow < 0 || imageReservedRows > height) {
2782
+ logRedraw(`kitty image cannot be placed in place (start row ${imageStartScreenRow}, ${imageReservedRows} rows, height ${height})`);
2780
2783
  fullRender(true, !preserveMuxScrollback);
2781
2784
  return;
2782
2785
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-tui",
3
- "version": "2026.11.3-2",
3
+ "version": "2026.11.4-2",
4
4
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",