@xynogen/pix-pretty 1.7.22 → 1.7.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-pretty",
3
- "version": "1.7.22",
3
+ "version": "1.7.25",
4
4
  "description": "Enhanced tool output rendering with syntax highlighting, file icons, tree views, diff rendering, and FFF search",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -58,7 +58,9 @@
58
58
  "access": "public"
59
59
  },
60
60
  "dependencies": {
61
- "@xynogen/pix-data": "^0.3.4",
61
+ "@xynogen/pix-data": "^0.4.0",
62
+ "@xynogen/pix-runtime": "^0.2.0",
63
+ "chalk": "^4.1.2",
62
64
  "cli-highlight": "^2.1.11",
63
65
  "@ff-labs/fff-node": "^0.5.2",
64
66
  "diff": "^8.0.3"
package/src/ansi.test.ts CHANGED
@@ -2,24 +2,9 @@ import { describe, expect, test } from "bun:test";
2
2
  import * as ansi from "./ansi.ts";
3
3
  import { resolveBaseBackground } from "./ansi.ts";
4
4
 
5
- const bg = (r: number, g: number, b: number) => `\x1b[48;2;${r};${g};${b}m`;
6
-
7
- describe("themed tool surfaces", () => {
8
- test("uses semantic success and error backgrounds from the active theme", () => {
9
- resolveBaseBackground({
10
- getBgAnsi: (key) => {
11
- if (key === "toolSuccessBg") return bg(10, 20, 30);
12
- if (key === "toolErrorBg") return bg(40, 50, 60);
13
- return "";
14
- },
15
- });
16
- expect(ansi.BG_BASE).toBe(bg(10, 20, 30));
17
- expect(ansi.BG_ERROR).toBe(bg(40, 50, 60));
18
- expect(ansi.RST).toContain(bg(10, 20, 30));
19
- });
20
-
21
- test("resets stale theme backgrounds when no raw theme accessor exists", () => {
22
- resolveBaseBackground(undefined);
5
+ describe("tool surfaces", () => {
6
+ test("always preserves terminal background", () => {
7
+ resolveBaseBackground({ getBgAnsi: () => "\x1b[48;2;10;20;30m" });
23
8
  expect(ansi.BG_BASE).toBe("\x1b[49m");
24
9
  expect(ansi.BG_ERROR).toBe("\x1b[49m");
25
10
  expect(ansi.RST).toBe("\x1b[0m");
package/src/ansi.ts CHANGED
@@ -1,5 +1,3 @@
1
- import type { BgTheme } from "./types.js";
2
-
3
1
  export let RST = "\x1b[0m";
4
2
  export const BOLD = "\x1b[1m";
5
3
 
@@ -13,47 +11,17 @@ export const FG_BLUE = "\x1b[38;2;100;140;220m";
13
11
  const FG_MUTED = "\x1b[38;2;139;148;158m";
14
12
 
15
13
  const BG_DEFAULT = "\x1b[49m";
16
- export let BG_BASE = BG_DEFAULT; // tool box success/base bg — updated from theme's toolSuccessBg
17
- export let BG_ERROR = BG_DEFAULT; // tool box error bg — updated from theme's toolErrorBg
18
-
19
- /** Parse an ANSI 24-bit color escape into { r, g, b }. Handles both fg (38;2) and bg (48;2). */
20
- function parseAnsiRgb(ansi: string): { r: number; g: number; b: number } | null {
21
- const m = ansi.match(new RegExp(`${ESC_RE}\\[(?:38|48);2;(\\d+);(\\d+);(\\d+)m`));
22
- return m ? { r: +(m[1] ?? 0), g: +(m[2] ?? 0), b: +(m[3] ?? 0) } : null;
23
- }
24
-
25
- function getThemeBgAnsi(theme: BgTheme, key: string): string | null {
26
- try {
27
- const bgAnsi = theme.getBgAnsi?.(key);
28
- return bgAnsi && parseAnsiRgb(bgAnsi) ? bgAnsi : null;
29
- } catch {
30
- return null;
31
- }
14
+ export let BG_BASE = BG_DEFAULT;
15
+ export let BG_ERROR = BG_DEFAULT;
16
+
17
+ /** Tool and diff renderers always preserve the terminal background. */
18
+ export function resolveBaseBackground(_theme: unknown): void {
19
+ BG_BASE = BG_DEFAULT;
20
+ BG_ERROR = BG_DEFAULT;
21
+ RST = "\x1b[0m";
32
22
  }
33
23
 
34
- /** Read themed tool backgrounds and update BG_BASE / BG_ERROR + RST.
35
- * Recompute on each render so runtime theme changes are respected. */
36
- export function resolveBaseBackground(theme: BgTheme | null | undefined): void {
37
- if (!theme?.getBgAnsi) {
38
- BG_BASE = BG_DEFAULT;
39
- BG_ERROR = BG_DEFAULT;
40
- RST = "\x1b[0m";
41
- return;
42
- }
43
-
44
- BG_BASE =
45
- getThemeBgAnsi(theme, "toolSuccessBg") ??
46
- getThemeBgAnsi(theme, "toolPendingBg") ??
47
- getThemeBgAnsi(theme, "toolBg") ??
48
- getThemeBgAnsi(theme, "background") ??
49
- BG_DEFAULT;
50
- BG_ERROR = getThemeBgAnsi(theme, "toolErrorBg") ?? BG_BASE;
51
- RST = `\x1b[0m${BG_BASE}`;
52
- }
53
-
54
- const ESC_RE = "\u001b";
55
-
56
- export const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([0-9;]*)m`, "g");
24
+ export const ANSI_CAPTURE_RE = /\x1b\[([0-9;]*)m/g;
57
25
 
58
26
  // ---------------------------------------------------------------------------
59
27
  // Low-contrast fix (same as pi-diff)
package/src/config.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { pixConfig } from "@xynogen/pix-data/pix-config";
1
+ import { config } from "@xynogen/pix-runtime/config";
2
+ import { prettySection } from "@xynogen/pix-runtime/sections";
2
3
 
3
4
  export function envInt(name: string, fallback: number): number {
4
5
  const v = Number.parseInt(process.env[name] ?? "", 10);
@@ -15,7 +16,7 @@ function pixOrEnvInt(envName: string, pixValue: number, fallback: number): numbe
15
16
  return pixValue !== fallback ? pixValue : fallback;
16
17
  }
17
18
 
18
- const pc = pixConfig().pretty;
19
+ const pc = config(prettySection);
19
20
 
20
21
  export const MAX_HL_CHARS = pixOrEnvInt("PRETTY_MAX_HL_CHARS", pc.maxHighlightChars, 80_000);
21
22
 
@@ -3,14 +3,13 @@
3
3
  // vendored pretty extension's primitives (cli-highlight `hlBlock`, shared
4
4
  // theme-aware `RST`/`BG_BASE` from ansi.ts).
5
5
  //
6
- // Engine note: pi-diff used Shiki's codeToANSI (fg-only output). pretty's
7
- // hlBlock (cli-highlight) likewise emits only fg codes, so the bg-injection
8
- // technique below works unchanged — diff backgrounds layer underneath and
9
- // persist through fg switches.
6
+ // Engine note: pi-diff used Shiki's codeToANSI. pretty uses cli-highlight,
7
+ // shared with other pix-pretty renderers, and paints foreground tokens only.
10
8
 
11
- import { pixConfig } from "@xynogen/pix-data/pix-config";
9
+ import { config } from "@xynogen/pix-runtime/config";
10
+ import { prettySection } from "@xynogen/pix-runtime/sections";
12
11
  import * as Diff from "diff";
13
- import { BG_BASE, BOLD, FG_DIM, FG_LNUM, FG_RULE, RST } from "./ansi.js";
12
+ import { BG_BASE, BOLD, FG_DIM, FG_GREEN, FG_LNUM, FG_RED, FG_RULE, RST } from "./ansi.js";
14
13
  import { MAX_HL_CHARS, MAX_RENDER_LINES, WORD_DIFF_MIN_SIM } from "./config.js";
15
14
  import type { DiffLine, ParsedDiff } from "./diff.js";
16
15
  import { hlBlock } from "./highlight.js";
@@ -32,16 +31,11 @@ function envInt(name: string, fallback: number): number {
32
31
  // ---------------------------------------------------------------------------
33
32
 
34
33
  const DIM = "\x1b[2m";
35
- const dc = pixConfig().pretty.diff;
36
-
37
- const FALLBACK_BG_ADD = "\x1b[48;2;22;38;32m";
38
- const FALLBACK_BG_DEL = "\x1b[48;2;45;25;25m";
39
- const FALLBACK_BG_ADD_W = "\x1b[48;2;35;75;50m";
40
- const FALLBACK_BG_DEL_W = "\x1b[48;2;80;35;35m";
41
- const FALLBACK_BG_GUTTER_ADD = "\x1b[48;2;18;32;26m";
42
- const FALLBACK_BG_GUTTER_DEL = "\x1b[48;2;38;22;22m";
43
- const FG_ADD = "\x1b[38;2;100;180;120m";
44
- const FG_DEL = "\x1b[38;2;200;100;100m";
34
+ const dc = config(prettySection).diff;
35
+
36
+ // Diff add/remove share the canonical green/red from ansi.ts (single source).
37
+ const FG_ADD = FG_GREEN;
38
+ const FG_DEL = FG_RED;
45
39
  const FG_STRIPE = "\x1b[38;2;40;40;40m"; // diagonal stripes on filler cells
46
40
 
47
41
  const BORDER_BAR = "▌";
@@ -90,68 +84,17 @@ export const DEFAULT_DIFF_COLORS: DiffColors = {
90
84
  fgAdd: FG_ADD,
91
85
  fgDel: FG_DEL,
92
86
  fgCtx: FG_DIM,
93
- bgAdd: FALLBACK_BG_ADD,
94
- bgDel: FALLBACK_BG_DEL,
95
- bgAddHighlight: FALLBACK_BG_ADD_W,
96
- bgDelHighlight: FALLBACK_BG_DEL_W,
97
- bgGutterAdd: FALLBACK_BG_GUTTER_ADD,
98
- bgGutterDel: FALLBACK_BG_GUTTER_DEL,
87
+ bgAdd: BG_BASE,
88
+ bgDel: BG_BASE,
89
+ bgAddHighlight: BG_BASE,
90
+ bgDelHighlight: BG_BASE,
91
+ bgGutterAdd: BG_BASE,
92
+ bgGutterDel: BG_BASE,
99
93
  };
100
94
 
101
- // --- contrast helpers -------------------------------------------------------
102
- // The gutter (line number + sign) paints the diff fg over a dark gutter bg.
103
- // A theme whose diff fg is itself dark renders the number/sign as black-on-
104
- // black. We keep the theme's hue but lift its luminance until it clears a
105
- // minimum contrast ratio against the gutter background it sits on.
106
-
107
- type Rgb = [number, number, number];
108
-
109
- function parseAnsiRgb(seq: string, kind: "38" | "48"): Rgb | null {
110
- const m = seq.match(new RegExp(`\\x1b\\[${kind};2;(\\d+);(\\d+);(\\d+)m`));
111
- if (!m) return null;
112
- return [Number(m[1]), Number(m[2]), Number(m[3])];
113
- }
114
-
115
- function relLuminance([r, g, b]: Rgb): number {
116
- const f = (c: number) => {
117
- const s = c / 255;
118
- return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
119
- };
120
- return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
121
- }
122
-
123
- function contrastRatio(a: Rgb, b: Rgb): number {
124
- const la = relLuminance(a);
125
- const lb = relLuminance(b);
126
- const [hi, lo] = la > lb ? [la, lb] : [lb, la];
127
- return (hi + 0.05) / (lo + 0.05);
128
- }
129
-
130
- /** Keep hue, raise lightness toward white until contrast >= min (or capped). */
131
- function ensureContrast(fg: string, bgSeq: string, min = 3): string {
132
- const rgb = parseAnsiRgb(fg, "38");
133
- const bg = parseAnsiRgb(bgSeq, "48");
134
- if (!rgb || !bg) return fg; // can't reason about it — leave theme value
135
- if (contrastRatio(rgb, bg) >= min) return fg; // already legible
136
- let [r, g, b] = rgb;
137
- for (let i = 0; i < 12 && contrastRatio([r, g, b], bg) < min; i++) {
138
- r = Math.round(r + (255 - r) * 0.25);
139
- g = Math.round(g + (255 - g) * 0.25);
140
- b = Math.round(b + (255 - b) * 0.25);
141
- }
142
- return `\x1b[38;2;${r};${g};${b}m`;
143
- }
144
-
145
- /** Resolve diff fg colors from pi's theme (if it exposes getFgAnsi), falling
146
- * back to hardcoded ANSI. BG_BASE is already kept in sync by ansi.ts's
147
- * resolveBaseBackground (called from the tool renderers).
148
- *
149
- * Theme hue is preserved, but each add/del fg is contrast-checked against the
150
- * gutter bg it is painted on and lifted if it would render too dark to read. */
151
95
  type DiffTheme = {
152
96
  fg?: FgTheme["fg"];
153
97
  getFgAnsi?: (key: string) => string;
154
- getBgAnsi?: (key: string) => string;
155
98
  };
156
99
 
157
100
  function themeFg(theme: DiffTheme, key: string, fallback: string): string {
@@ -162,32 +105,19 @@ function themeFg(theme: DiffTheme, key: string, fallback: string): string {
162
105
  }
163
106
  }
164
107
 
165
- function tintedBackground(fg: string, strength: number, fallback: string): string {
166
- const rgb = parseAnsiRgb(fg, "38");
167
- if (!rgb) return fallback;
168
- const [r, g, b] = rgb.map((channel) => Math.round(channel * strength)) as Rgb;
169
- return `\x1b[48;2;${r};${g};${b}m`;
170
- }
171
-
172
108
  export function resolveDiffColors(theme?: DiffTheme): DiffColors {
173
109
  if (!theme) return DEFAULT_DIFF_COLORS;
174
- const rawFgAdd = themeFg(theme, "toolDiffAdded", FG_ADD);
175
- const rawFgDel = themeFg(theme, "toolDiffRemoved", FG_DEL);
176
- const bgAdd = tintedBackground(rawFgAdd, 0.2, FALLBACK_BG_ADD);
177
- const bgDel = tintedBackground(rawFgDel, 0.2, FALLBACK_BG_DEL);
178
- const bgGutterAdd = tintedBackground(rawFgAdd, 0.15, FALLBACK_BG_GUTTER_ADD);
179
- const bgGutterDel = tintedBackground(rawFgDel, 0.15, FALLBACK_BG_GUTTER_DEL);
180
110
  return {
181
- fgAdd: ensureContrast(rawFgAdd, bgGutterAdd),
182
- fgDel: ensureContrast(rawFgDel, bgGutterDel),
111
+ fgAdd: themeFg(theme, "toolDiffAdded", FG_ADD),
112
+ fgDel: themeFg(theme, "toolDiffRemoved", FG_DEL),
183
113
  fgCtx: themeFg(theme, "toolDiffContext", FG_DIM),
184
114
  theme: typeof theme.fg === "function" ? (theme as FgTheme) : undefined,
185
- bgAdd,
186
- bgDel,
187
- bgAddHighlight: tintedBackground(rawFgAdd, 0.35, FALLBACK_BG_ADD_W),
188
- bgDelHighlight: tintedBackground(rawFgDel, 0.35, FALLBACK_BG_DEL_W),
189
- bgGutterAdd,
190
- bgGutterDel,
115
+ bgAdd: BG_BASE,
116
+ bgDel: BG_BASE,
117
+ bgAddHighlight: BG_BASE,
118
+ bgDelHighlight: BG_BASE,
119
+ bgGutterAdd: BG_BASE,
120
+ bgGutterDel: BG_BASE,
191
121
  };
192
122
  }
193
123
 
package/src/diff.test.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import { parseDiff } from "./diff.js";
3
- import { diffThemeCacheKey, renderDiffSummary, resolveDiffColors } from "./diff-render.js";
3
+ import {
4
+ diffThemeCacheKey,
5
+ renderDiffSummary,
6
+ renderUnified,
7
+ resolveDiffColors,
8
+ } from "./diff-render.js";
4
9
 
5
10
  const OLD = "line1\nline2\nline3";
6
11
  const NEW = "line1\nCHANGED\nline3";
12
+ const ANSI_RE = /\x1b\[[0-9;]*m|<\/?syntax\w+>/g;
7
13
 
8
14
  describe("theme-derived diff rendering", () => {
9
15
  const theme = {
@@ -16,13 +22,21 @@ describe("theme-derived diff rendering", () => {
16
22
  },
17
23
  };
18
24
 
19
- it("derives foregrounds and tint backgrounds from semantic theme tokens", () => {
25
+ it("uses semantic foregrounds without tint backgrounds", () => {
20
26
  const colors = resolveDiffColors(theme);
21
27
  expect(colors.fgAdd).toBe("\x1b[38;2;120;210;150m");
22
28
  expect(colors.fgDel).toBe("\x1b[38;2;230;120;130m");
23
29
  expect(colors.fgCtx).toBe("\x1b[38;2;130;140;150m");
24
- expect(colors.bgAdd).toBe("\x1b[48;2;24;42;30m");
25
- expect(colors.bgDel).toBe("\x1b[48;2;46;24;26m");
30
+ for (const key of [
31
+ "bgAdd",
32
+ "bgDel",
33
+ "bgAddHighlight",
34
+ "bgDelHighlight",
35
+ "bgGutterAdd",
36
+ "bgGutterDel",
37
+ ] as const) {
38
+ expect(colors[key]).toBe("\x1b[49m");
39
+ }
26
40
  });
27
41
 
28
42
  it("includes semantic theme colors in cache identity", () => {
@@ -38,6 +52,25 @@ describe("theme-derived diff rendering", () => {
38
52
  "<toolDiffContext>no changes</toolDiffContext>",
39
53
  );
40
54
  });
55
+
56
+ it("keeps the foreground-only diff style", async () => {
57
+ const rendered = await renderUnified(
58
+ parseDiff("const oldValue = 1;", "const newValue = 2;"),
59
+ "typescript",
60
+ 80,
61
+ resolveDiffColors({ ...theme, fg: (_key, text) => text }),
62
+ );
63
+ const lines = rendered.replace(ANSI_RE, "").split("\n");
64
+
65
+ expect(lines).toHaveLength(4);
66
+ expect(lines[0]).toMatch(/^─+$/);
67
+ expect(lines[1]).toMatch(/^▌\s+1- │ const oldValue = 1;\s*$/);
68
+ expect(lines[2]).toMatch(/^▌\s+1\+ │ const newValue = 2;\s*$/);
69
+ expect(lines[3]).toMatch(/^─+$/);
70
+ expect(rendered).toContain(theme.getFgAnsi("toolDiffRemoved"));
71
+ expect(rendered).toContain(theme.getFgAnsi("toolDiffAdded"));
72
+ expect(rendered).not.toMatch(/\x1b\[48(?:;[^m]*)?m/);
73
+ });
41
74
  });
42
75
 
43
76
  describe("parseDiff baseLine", () => {
@@ -2,26 +2,30 @@ import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test";
2
2
  import { mkdtempSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { reloadPixConfig } from "@xynogen/pix-data/pix-config";
5
+ import { reloadConfig } from "@xynogen/pix-runtime/config";
6
6
  import { getIconMode, setIconMode } from "./icon-catalog.ts";
7
7
  import { initIconMode, loadIconMode, saveIconMode } from "./icon-persist.ts";
8
8
 
9
9
  let tmpAgentDir: string;
10
10
  let origHome: string | undefined;
11
11
 
12
- beforeAll(() => {
12
+ beforeAll(async () => {
13
13
  tmpAgentDir = mkdtempSync(join(tmpdir(), "pretty-persist-test-"));
14
14
  origHome = process.env.HOME;
15
- // Point HOME at the temp dir so pixConfig() reads from there, not the real ~/.pi/agent/pix.json
15
+ // Point HOME at the temp dir so the runtime reads from there, not the real ~/.pi/agent/pix.json
16
16
  process.env.HOME = tmpAgentDir;
17
17
  process.env.PI_CODING_AGENT_DIR = tmpAgentDir;
18
- // Force pix-config to re-read from the temp HOME (clears cached real config).
19
- reloadPixConfig();
18
+ // Drop any singleton created by earlier test files (it is bound to the old
19
+ // agent dir); the next accessor call lazily recreates it under the temp HOME.
20
+ delete (globalThis as Record<symbol, unknown>)[Symbol.for("@xynogen/pix-runtime")];
21
+ await reloadConfig();
20
22
  });
21
23
 
22
24
  afterAll(() => {
23
25
  process.env.HOME = origHome;
24
26
  delete process.env.PI_CODING_AGENT_DIR;
27
+ // Drop the temp-HOME-bound singleton so later test files get a fresh one.
28
+ delete (globalThis as Record<symbol, unknown>)[Symbol.for("@xynogen/pix-runtime")];
25
29
  try {
26
30
  rmSync(tmpAgentDir, { recursive: true });
27
31
  } catch {
@@ -36,18 +40,18 @@ describe("icon-persist", () => {
36
40
  expect(loadIconMode()).toBe("nerd");
37
41
  });
38
42
 
39
- it("round-trips a mode across save/load (new-session sim)", () => {
40
- saveIconMode("unicode");
43
+ it("round-trips a mode across save/load (new-session sim)", async () => {
44
+ await saveIconMode("unicode");
41
45
  expect(loadIconMode()).toBe("unicode");
42
46
  });
43
47
 
44
- it("rejects an invalid persisted mode", () => {
45
- saveIconMode("ascii");
48
+ it("rejects an invalid persisted mode", async () => {
49
+ await saveIconMode("ascii");
46
50
  expect(loadIconMode()).toBe("ascii");
47
51
  });
48
52
 
49
- it("initIconMode applies the persisted choice to the catalog", () => {
50
- saveIconMode("ascii");
53
+ it("initIconMode applies the persisted choice to the catalog", async () => {
54
+ await saveIconMode("ascii");
51
55
  setIconMode("nerd"); // pretend env default
52
56
  initIconMode();
53
57
  expect(getIconMode()).toBe("ascii");
@@ -9,7 +9,8 @@
9
9
  * Precedence: env PRETTY_ICONS → pix.json pretty.icons → default ("nerd")
10
10
  */
11
11
 
12
- import { onPixConfigChange, pixConfig, savePixConfig } from "@xynogen/pix-data/pix-config";
12
+ import { config, onConfigChange, updateConfig } from "@xynogen/pix-runtime/config";
13
+ import { prettySection } from "@xynogen/pix-runtime/sections";
13
14
  import { ICON_MODES, type IconMode, setIconMode } from "./icon-catalog.js";
14
15
 
15
16
  function isIconMode(m: string): m is IconMode {
@@ -19,7 +20,7 @@ function isIconMode(m: string): m is IconMode {
19
20
  /** Read the persisted icon mode from pix.json, or undefined if unset/invalid. */
20
21
  export function loadIconMode(): IconMode | undefined {
21
22
  try {
22
- const mode = pixConfig().pretty.icons;
23
+ const mode = config(prettySection).icons;
23
24
  if (mode == null) return undefined;
24
25
  return isIconMode(mode) ? mode : undefined;
25
26
  } catch {
@@ -28,12 +29,13 @@ export function loadIconMode(): IconMode | undefined {
28
29
  }
29
30
 
30
31
  /** Persist the icon mode to pix.json (`pretty.icons`). */
31
- export function saveIconMode(mode: IconMode): void {
32
- try {
33
- savePixConfig({ pretty: { icons: mode } });
34
- } catch (err) {
35
- console.warn("pix-pretty: persist icon mode failed:", err);
36
- }
32
+ export function saveIconMode(mode: IconMode): Promise<void> {
33
+ return updateConfig(prettySection, { icons: mode }).then(
34
+ () => undefined,
35
+ (err) => {
36
+ console.error("pix-pretty: persist icon mode failed:", err);
37
+ },
38
+ );
37
39
  }
38
40
 
39
41
  /**
@@ -43,12 +45,15 @@ export function saveIconMode(mode: IconMode): void {
43
45
  * Precedence: env PRETTY_ICONS → pix.json pretty.icons → default ("nerd")
44
46
  */
45
47
  export function initIconMode(): void {
46
- const pixIcons = pixConfig().pretty.icons;
48
+ const pixIcons = config(prettySection).icons;
47
49
  if (pixIcons && isIconMode(pixIcons)) setIconMode(pixIcons);
48
50
 
49
51
  // Keep the in-memory icon mode in sync when /pix changes pretty.icons.
50
- onPixConfigChange((cfg) => {
51
- const mode = cfg.pretty.icons;
52
- if (mode && isIconMode(mode)) setIconMode(mode);
53
- });
52
+ onConfigChange(
53
+ (change) => {
54
+ const mode = change.current.get(prettySection).icons;
55
+ if (mode && isIconMode(mode)) setIconMode(mode);
56
+ },
57
+ { paths: ["pretty.icons"] },
58
+ );
54
59
  }
package/src/renderers.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
- import { getLsStyle } from "@xynogen/pix-data/pix-config";
2
+ import { config } from "@xynogen/pix-runtime/config";
3
+ import { prettySection } from "@xynogen/pix-runtime/sections";
3
4
 
4
5
  import { FG_DIM, FG_RULE, RST } from "./ansi.js";
5
6
  import { MAX_PREVIEW_LINES } from "./config.js";
@@ -75,7 +76,7 @@ export function renderBashOutput(
75
76
 
76
77
  /** Render ls output using the configured style (grid or tree). */
77
78
  export function renderTree(text: string, basePath: string, theme?: FgTheme): string {
78
- return getLsStyle() === "tree"
79
+ return config(prettySection).lsStyle === "tree"
79
80
  ? renderLsTree(text, basePath, theme)
80
81
  : renderLsGrid(text, basePath, theme);
81
82
  }
package/src/types.ts CHANGED
@@ -28,8 +28,7 @@ export type BgTheme = { getBgAnsi?: (key: string) => string };
28
28
 
29
29
  export type FgTheme = {
30
30
  fg: (key: string, text: string) => string;
31
- // Optional raw-ANSI accessors exposed by Pi's theme. Diff foregrounds and
32
- // generated tint backgrounds derive from these semantic theme colors.
31
+ // Optional raw-ANSI accessor exposed by Pi's theme for semantic foregrounds.
33
32
  getFgAnsi?: (key: string) => string;
34
33
  };
35
34