@xzzpig/pi-starline 0.7.1 → 0.7.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/FORK-CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Fork release notes
2
2
 
3
+ ## 0.7.2 — pill text contrast on terminal-owned palettes
4
+
5
+ - Pick the pill text colour from the 256-colour cube (`38;5;16` black, `38;5;231` white) instead of the basic ANSI codes `30`/`97`. The basic codes belong to the terminal theme: herdr renders ANSI black as `#616161`, which left the automatic "dark" text at 1.76:1 on the muted pill background and 2.62:1 on the model pill. The cube's black and white are fixed by the standard, so contrast now holds in any palette (5.4–9.4:1 for the footer's default backgrounds).
6
+ - The change is two constants plus their comment in `extensions/starline/pill.ts`. It covers pill bodies, the thin divider drawn between same-background pills, and the bubble's selected row, all of which share `contrastTextSgr()`. Backgrounds, separators, caps, widths and click targets are unchanged, and an explicit `fg:` in `colors.*` still wins.
7
+ - Backgrounds that land on the terminal's own 16 colours are painted in reverse video (`\x1b[49m` + the palette colour as foreground + `\x1b[7m`): the palette colour stays the background and the text becomes the terminal's default background, a pair the palette is designed to keep readable. Only the terminal knows those RGBs — the standard table turned `48;5;3` into "dark yellow" when herdr paints it `#61AFEE`, which is why the contrast guess cannot be trusted there. Measured on the same pills: 2.62:1 → 8.61:1 (model/cwd) and 3.07:1 → 10.11:1 (cost); known-RGB backgrounds keep the cube black/white and land at 9.12:1. The predicate and SGR builders live in the fork-only `extensions/starline/fork-pill-legibility.ts`, with one seam each in `pill.ts` and `pill-bubble.ts`.
8
+ - Fork coverage lives in `test/fork-pill-contrast.test.ts`; `subtrees/pi-starline.json` records the sync-time re-apply. Drop the constants divergence once upstream stops deriving legibility from the terminal's basic palette, and the reverse-video seam once upstream handles terminal-owned backgrounds itself.
9
+
3
10
  ## 0.7.1 — Pi cursor compatibility
4
11
 
5
12
  - Restore `underline` and `terminal` editor cursor styles on Pi 1.2 and later while preserving the existing Pi 1.1 cursor path.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Fork-only legibility helpers for pill text.
3
+ *
4
+ * A pill's text colour is normally picked from the background's RGB, but the
5
+ * terminal owns the RGB of its own 16 colours: `38;5;3` is whatever the theme
6
+ * paints for palette slot 3 (herdr: `#61AFEE`), not the standard table's olive.
7
+ * Guessing from the standard table turned "light blue" into "dark yellow" and
8
+ * picked white text for a light pill.
9
+ *
10
+ * For those backgrounds the fork paints the text with reverse video instead:
11
+ * the palette colour becomes the background and the terminal's own default
12
+ * background becomes the text. A palette is designed to keep its colours
13
+ * readable against that default background, so the pair stays legible without
14
+ * the program knowing either RGB.
15
+ *
16
+ * See FORK-CHANGELOG.md; `test/fork-pill-contrast.test.ts` covers it.
17
+ */
18
+
19
+ import { toForegroundSgr } from "./style";
20
+
21
+ const DEFAULT_BG = "\x1b[49m";
22
+ const REVERSE = "\x1b[7m";
23
+
24
+ /**
25
+ * True when the background is one of the terminal's own 16 colours, whose RGB
26
+ * only the terminal knows. The 256-colour cube (`48;5;16`+) and truecolor are
27
+ * fixed by the standard, so those stay knowable.
28
+ */
29
+ export function isTerminalOwnedBackground(background: string): boolean {
30
+ if (/^\x1b\[(4[0-7]|10[0-7])m$/.test(background)) return true;
31
+ const indexed = /^\x1b\[48;5;(\d{1,3})m$/.exec(background);
32
+ return indexed ? Number(indexed[1]) < 16 : false;
33
+ }
34
+
35
+ /**
36
+ * True when the spec names a foreground colour itself (`fg:…`). Such a pill
37
+ * keeps the colour the user asked for: reverse video would replace it.
38
+ */
39
+ export function hasExplicitForeground(spec: string): boolean {
40
+ return /(^|\s)fg:/i.test(spec.trim());
41
+ }
42
+
43
+ /**
44
+ * The SGR that paints text on a terminal-owned background, or undefined when
45
+ * the background is knowable (the caller picks a contrast colour instead) or
46
+ * cannot be turned into a foreground colour.
47
+ */
48
+ export function reverseVideoSgr(background: string): string | undefined {
49
+ if (!isTerminalOwnedBackground(background)) return undefined;
50
+ const foreground = toForegroundSgr(background);
51
+ return foreground ? `${DEFAULT_BG}${foreground}${REVERSE}` : undefined;
52
+ }
@@ -36,6 +36,7 @@ import type {
36
36
  PillBubbleHandle,
37
37
  PillBubbleSource,
38
38
  } from "../../types/pill-api";
39
+ import { reverseVideoSgr } from "./fork-pill-legibility";
39
40
  import { requestPointerShape } from "./mouse/pointer-shape";
40
41
  import { contrastTextSgr } from "./pill";
41
42
  import { resolveBackgroundSgr, type ThemeLike, toForegroundSgr } from "./style";
@@ -211,7 +212,10 @@ function buildBubble(input: {
211
212
  const width = Math.max(8, Math.floor(input.width));
212
213
  const innerWidth = Math.max(1, width - 4);
213
214
  const background = input.background;
214
- const foreground = input.foreground || contrastTextSgr(background);
215
+ // A background the terminal owns has no knowable RGB, so its row is painted in
216
+ // reverse video: palette colour behind, terminal default background as text.
217
+ const reverse = input.foreground ? "" : (reverseVideoSgr(background) ?? "");
218
+ const foreground = input.foreground || (reverse ? "" : contrastTextSgr(background));
215
219
  // The outline is the pill's colour as a foreground on the terminal's own
216
220
  // background; nothing inside the box is filled.
217
221
  const line = `${DEFAULT_BG}${toForegroundSgr(background) ?? ""}`;
@@ -222,7 +226,7 @@ function buildBubble(input: {
222
226
  // underline covers the words and not the margin around them.
223
227
  `${side} ${style}${text}${STYLE_OFF}${" ".repeat(Math.max(0, innerWidth - visibleWidth(text)))} ${side}`;
224
228
  const selectedRow = (text: string) =>
225
- `${side}${background}${foreground} ${text}${" ".repeat(
229
+ `${side}${reverse || background}${foreground} ${text}${" ".repeat(
226
230
  Math.max(0, innerWidth - visibleWidth(text)),
227
231
  )} ${RESET}${side}`;
228
232
 
@@ -13,6 +13,7 @@ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
13
13
 
14
14
  import type { ColorSource, ColorSpec } from "./config";
15
15
  import type { ExtensionStatusSegment } from "./extension-status";
16
+ import { hasExplicitForeground, reverseVideoSgr } from "./fork-pill-legibility";
16
17
  import {
17
18
  EXTENSION_STATUS_PREFIX,
18
19
  EXTENSION_STATUS_SEGMENT,
@@ -25,9 +26,13 @@ const RESET = "\x1b[0m";
25
26
  const BOLD = "\x1b[1m";
26
27
  const DEFAULT_BG = "\x1b[49m";
27
28
 
28
- /** Text colours are the basic named codes, which every terminal renders correctly. */
29
- const TEXT_DARK = "\x1b[30m";
30
- const TEXT_LIGHT = "\x1b[97m";
29
+ /**
30
+ * Text colours are the 256-colour cube's black and white (16/231), whose values
31
+ * the standard fixes. The basic named codes are not fixed: a terminal theme may
32
+ * map ANSI black to a mid grey, which leaves mid-tone pills hard to read.
33
+ */
34
+ const TEXT_DARK = "\x1b[38;5;16m";
35
+ const TEXT_LIGHT = "\x1b[38;5;231m";
31
36
 
32
37
  /** Catppuccin surface1 — the neutral for segments whose spec names no colour. */
33
38
  const NEUTRAL_BG = "\x1b[48;2;69;71;90m";
@@ -164,6 +169,8 @@ type PreparedPill = {
164
169
  bg: string;
165
170
  fg: string;
166
171
  bold: boolean;
172
+ /** Body SGR for a terminal-owned background, which paints text in reverse. */
173
+ reverse: string;
167
174
  };
168
175
 
169
176
  const XTERM_CUBE_LEVELS = [0, 95, 135, 175, 215, 255];
@@ -250,19 +257,25 @@ function prepare(
250
257
  if (!text || visibleWidth(text) === 0) continue;
251
258
 
252
259
  if (input.keepStyling) {
253
- pills.push({ key: input.key, text, bg: neutral, fg: "", bold: false });
260
+ pills.push({ key: input.key, text, bg: neutral, fg: "", bold: false, reverse: "" });
254
261
  continue;
255
262
  }
256
263
 
257
264
  const resolved = resolveColorSpec(theme, source, input.spec);
258
265
  const bg = resolved.bg ?? resolveBackgroundSgr(theme, source, input.spec) ?? neutral;
259
- // An explicit fg: in the spec wins; otherwise pick for legibility.
266
+ // An explicit fg: in the spec wins. A background the terminal owns has no
267
+ // knowable RGB, so reverse video hands both sides to the palette instead of
268
+ // guessing from the standard table.
269
+ const explicitFg = hasExplicitForeground(input.spec);
270
+ const reverse = explicitFg ? "" : (reverseVideoSgr(bg) ?? "");
271
+ const specFg = explicitFg || (resolved.bg && resolved.fg) ? resolved.fg : undefined;
260
272
  pills.push({
261
273
  key: input.key,
262
274
  text,
263
275
  bg,
264
- fg: resolved.bg && resolved.fg ? resolved.fg : contrastTextSgr(bg),
276
+ fg: reverse ? "" : (specFg ?? contrastTextSgr(bg)),
265
277
  bold: config.bold || resolved.bold,
278
+ reverse,
266
279
  });
267
280
  }
268
281
 
@@ -319,7 +332,7 @@ export function renderPillBar(
319
332
  // The left cap opens the bar in the first pill's colour, so it belongs to it.
320
333
  const startColumn = index === 0 ? capStart : column;
321
334
  const bodyStartColumn = column;
322
- emit(`${pill.bg}${pill.fg}${pill.bold ? BOLD : ""} ${pill.text} ${RESET}`);
335
+ emit(`${pill.reverse || pill.bg}${pill.fg}${pill.bold ? BOLD : ""} ${pill.text} ${RESET}`);
323
336
  const bodyEndColumn = column;
324
337
 
325
338
  const next = pills[index + 1];
@@ -329,7 +342,8 @@ export function renderPillBar(
329
342
  // default to the same theme colour, which merged them into one blob.
330
343
  // Fall back to the thin divider in the text colour, which stays visible.
331
344
  if (separator && pill.bg === next.bg) {
332
- emit(`${pill.bg}${pill.fg || contrastTextSgr(pill.bg)}${THIN_SEPARATOR}${RESET}`);
345
+ const dividerFg = pill.reverse ? "" : pill.fg || contrastTextSgr(pill.bg);
346
+ emit(`${pill.reverse || pill.bg}${dividerFg}${THIN_SEPARATOR}${RESET}`);
333
347
  } else if (separator) {
334
348
  const arrowFg = toForegroundSgr(pill.bg);
335
349
  // Without a usable foreground the arrow would be invisible; letting the
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@xzzpig/pi-starline",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "A Starship-inspired statusline and Opencode-style TUI for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/Andy8647/pi-starline.git"
9
+ "url": "git+https://github.com/xzzpig/pi-extensions.git"
10
10
  },
11
11
  "homepage": "https://github.com/Andy8647/pi-starline#readme",
12
12
  "bugs": {