@xynogen/pix-optimizer 1.1.8 → 1.1.9

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/README.md CHANGED
@@ -25,8 +25,31 @@ State persists to `~/.pi/agent/optimizer.json` (caveman/rtk/toon/ponytail).
25
25
 
26
26
  ## Status bar
27
27
 
28
- A single cell always shows the enabled icons in a fixed order (`󰜐 󰓥 󰗀 󰆐`), color-
29
- coded by state: **accent** when the tool is enabled, **dim** when disabled.
28
+ A single cell always shows all four tool icons in a fixed order, color-coded by
29
+ state: **accent** when the tool is enabled, **dim** when disabled.
30
+
31
+ ### Icon style (Nerd Font, Unicode, or ASCII)
32
+
33
+ The default glyphs (`󰜐 󰓥 󰗀 󰆐`) are **Nerd Font** symbols and require a patched
34
+ font (e.g. MesloLGS NF). Terminals without one render them as missing-glyph
35
+ “tofu” boxes. Two font-independent fallbacks are available:
36
+
37
+ | Mode | Glyphs | Needs Nerd Font? |
38
+ |---|---|---|
39
+ | `nerd` (default) | `󰜐 󰓥 󰗀 󰆐` | yes |
40
+ | `unicode` | `♤ ♡ ♢ ♧` (outline card suits) | no |
41
+ | `ascii` | `Cv Rk Tn Pt` | no |
42
+
43
+ Switch the style live from the **`/optimizer` overlay** — a fifth `icons` row
44
+ at the bottom cycles `nerd → unicode → ascii` with `←→`; the choice persists to
45
+ `~/.pi/agent/optimizer.json`.
46
+
47
+ Or set it via env var (a persisted menu choice takes precedence):
48
+
49
+ ```bash
50
+ export OPTIMIZER_ICONS=unicode # nerd | unicode | ascii
51
+ export PRETTY_ICONS=none # stack-wide: also maps the cell to ASCII
52
+ ```
30
53
 
31
54
  ## Features
32
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-optimizer",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + jq/TOON JSON compression + ponytail lazy-dev mode",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -7,18 +7,21 @@
7
7
  * - toon: jq + TOON guidance for dense JSON (+ bundled skill)
8
8
  * - ponytail: lazy-senior-dev system prompt (minimal code, YAGNI)
9
9
  *
10
- * They share ONE status-bar cell (󰜐 󰓥 󰗀 󰆐, all four always shown — dimmed when off, accented when on) and ONE
11
- * command (/optimizer — an interactive overlay). index.ts wires lifecycle hooks
12
- * via each module, then registers the overlay command from their handles.
10
+ * They share ONE status-bar cell (all four icons always shown — dimmed when
11
+ * off, accented when on; icon style is nerd/unicode/ascii, set via /optimizer)
12
+ * and ONE command (/optimizer — an interactive overlay). index.ts wires
13
+ * lifecycle hooks via each module, then registers the overlay command.
13
14
  */
14
15
 
15
16
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
16
17
  import { caveman } from "./caveman.ts";
17
18
  import { json } from "./json.ts";
18
19
  import { registerOptCommand } from "./opt.ts";
20
+ import { loadIconMode } from "./persist.ts";
19
21
  import { ponytail } from "./ponytail.ts";
20
22
  import { rtk } from "./rtk.ts";
21
23
  import {
24
+ envIconMode,
22
25
  type OptimizerHandle,
23
26
  OptimizerStatus,
24
27
  type OptimizerTool,
@@ -26,7 +29,8 @@ import {
26
29
  import { filterModelWarnings } from "./tool-result-filter.ts";
27
30
 
28
31
  export default function optimizer(pi: ExtensionAPI) {
29
- const status = new OptimizerStatus();
32
+ // Persisted menu choice wins; otherwise fall back to the env default.
33
+ const status = new OptimizerStatus(loadIconMode() ?? envIconMode());
30
34
 
31
35
  // Each module registers its own lifecycle hooks and returns a handle the
32
36
  // /optimizer overlay renders + drives.
package/src/opt.ts CHANGED
@@ -16,13 +16,23 @@
16
16
 
17
17
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
18
  import { frameLines } from "@xynogen/pix-pretty/modal-frame";
19
+ import { saveIconMode } from "./persist.ts";
19
20
  import {
21
+ getIcons,
22
+ ICON_MODES,
23
+ type IconMode,
20
24
  type OptimizerHandle,
21
25
  type OptimizerStatus,
22
26
  type OptimizerTool,
23
- TOOL_ICONS,
24
27
  } from "./status.ts";
25
28
 
29
+ /** Glyph for the icon-mode row itself (uses the active mode's diamond/Tn). */
30
+ const ICON_ROW_GLYPH: Record<IconMode, string> = {
31
+ nerd: "󰆐", // gear-ish; distinct from the four tool glyphs
32
+ unicode: "\u25C8\uFE0E", // ◈ diamond-in-diamond
33
+ ascii: "Ic",
34
+ };
35
+
26
36
  /** Min/max content width for the overlay box (excludes 4 cols of chrome). */
27
37
  const MIN_CONTENT = 28;
28
38
  const MAX_CONTENT = 60;
@@ -76,7 +86,7 @@ export function buildOptHelp(
76
86
  export function registerOptCommand(
77
87
  pi: ExtensionAPI,
78
88
  handles: Record<OptimizerTool, OptimizerHandle>,
79
- _status: OptimizerStatus,
89
+ status: OptimizerStatus,
80
90
  ): void {
81
91
  pi.registerCommand("optimizer", {
82
92
  description: "pix-optimizer: caveman / rtk / toon / ponytail tools",
@@ -129,9 +139,26 @@ export function registerOptCommand(
129
139
  _kb: unknown,
130
140
  done: (v: null) => void,
131
141
  ) => {
142
+ // Rows = 4 tools + 1 icon-mode row at index TOOL_ORDER.length.
143
+ const ICON_ROW = TOOL_ORDER.length;
144
+ const ROW_COUNT = TOOL_ORDER.length + 1;
145
+ const ICON_HELP = "status-cell icon style (nerd needs a Nerd Font)";
132
146
  let selected = 0;
133
147
 
148
+ const cycleIconMode = (direction: -1 | 1) => {
149
+ const cur = ICON_MODES.indexOf(status.mode);
150
+ const next =
151
+ (cur + direction + ICON_MODES.length) % ICON_MODES.length;
152
+ const mode = ICON_MODES[next]!;
153
+ status.setMode(mode, ctx);
154
+ saveIconMode(mode);
155
+ };
156
+
134
157
  const cycle = (direction: -1 | 1) => {
158
+ if (selected === ICON_ROW) {
159
+ cycleIconMode(direction);
160
+ return;
161
+ }
135
162
  const tool = TOOL_ORDER[selected]!;
136
163
  const values = handles[tool].values;
137
164
  const cur = values.indexOf(handles[tool].current());
@@ -140,17 +167,17 @@ export function registerOptCommand(
140
167
  };
141
168
 
142
169
  const move = (direction: -1 | 1) => {
143
- selected =
144
- (selected + direction + TOOL_ORDER.length) % TOOL_ORDER.length;
170
+ selected = (selected + direction + ROW_COUNT) % ROW_COUNT;
145
171
  };
146
172
 
147
173
  return {
148
174
  render: () => {
175
+ const icons = getIcons(status.mode);
149
176
  const rows = TOOL_ORDER.map((tool, i) => {
150
177
  const on = handles[tool].current() !== "off";
151
178
  const sel = i === selected;
152
179
  const cursor = sel ? theme.fg("accent", "→") : " ";
153
- const icon = theme.fg(on ? "accent" : "dim", TOOL_ICONS[tool]);
180
+ const icon = theme.fg(on ? "accent" : "dim", icons[tool]);
154
181
  const name = theme.fg(
155
182
  sel ? "accent" : on ? "text" : "muted",
156
183
  tool.padEnd(nameWidth),
@@ -166,15 +193,29 @@ export function registerOptCommand(
166
193
  );
167
194
  return `${cursor} ${icon} ${name} ${value} ${bar}`;
168
195
  });
196
+ // Icon-mode row: glyph + "icons" + current mode name.
197
+ const iconSel = selected === ICON_ROW;
198
+ const iconCursor = iconSel ? theme.fg("accent", "→") : " ";
199
+ const iconGlyph = theme.fg("accent", ICON_ROW_GLYPH[status.mode]);
200
+ const iconName = theme.fg(
201
+ iconSel ? "accent" : "text",
202
+ "icons".padEnd(nameWidth),
203
+ );
204
+ const iconValue = theme.fg(
205
+ "success",
206
+ status.mode.padEnd(valueWidth),
207
+ );
208
+ const iconRow = `${iconCursor} ${iconGlyph} ${iconName} ${iconValue}`;
209
+ const helpText = iconSel
210
+ ? ICON_HELP
211
+ : helpSummary(handles[TOOL_ORDER[selected]!].help);
169
212
  const lines = [
170
213
  theme.fg("accent", theme.bold("󱎫 Optimizer")),
171
214
  "",
172
215
  ...rows,
216
+ iconRow,
173
217
  "",
174
- theme.fg(
175
- "dim",
176
- helpSummary(handles[TOOL_ORDER[selected]!].help),
177
- ),
218
+ theme.fg("dim", helpText),
178
219
  "",
179
220
  theme.fg("dim", "←→ change · ↑↓ move · esc close"),
180
221
  ];
@@ -9,7 +9,12 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
9
9
  import { mkdtempSync, rmSync } from "node:fs";
10
10
  import { tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
- import { loadOptValue, saveOptValue } from "./persist.ts";
12
+ import {
13
+ loadIconMode,
14
+ loadOptValue,
15
+ saveIconMode,
16
+ saveOptValue,
17
+ } from "./persist.ts";
13
18
 
14
19
  let tmpAgentDir: string;
15
20
 
@@ -53,4 +58,21 @@ describe("optimizer persistence", () => {
53
58
  expect(loadOptValue("caveman")).toBe("ultra");
54
59
  expect(loadOptValue("ponytail")).toBe("full");
55
60
  });
61
+
62
+ test("icon mode round-trips and is independent of tool values", () => {
63
+ expect(loadIconMode()).toBeUndefined();
64
+ saveIconMode("unicode");
65
+ expect(loadIconMode()).toBe("unicode");
66
+ // Did not disturb existing tool state…
67
+ expect(loadOptValue("caveman")).toBe("ultra");
68
+ // …and a later tool write does not disturb the icon mode.
69
+ saveOptValue("rtk", "on");
70
+ expect(loadIconMode()).toBe("unicode");
71
+ });
72
+
73
+ test("rejects an invalid persisted icon mode", () => {
74
+ saveOptValue("caveman", "lite"); // unrelated write
75
+ saveIconMode("ascii");
76
+ expect(loadIconMode()).toBe("ascii");
77
+ });
56
78
  });
package/src/persist.ts CHANGED
@@ -7,14 +7,21 @@
7
7
  * restart. Each tool reads its value on session_start and writes on run().
8
8
  *
9
9
  * ~/.pi/agent/optimizer.json → { "caveman": "lite", "rtk": "on", ... }
10
+ *
11
+ * A reserved "iconMode" key (not a tool) stores the status-cell icon style.
10
12
  */
11
13
 
12
14
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
15
  import { dirname, join } from "node:path";
14
16
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
15
- import type { OptimizerTool } from "./status.ts";
17
+ import { ICON_MODES, type IconMode, type OptimizerTool } from "./status.ts";
18
+
19
+ /** Reserved (non-tool) key holding the icon presentation mode. */
20
+ const ICON_MODE_KEY = "iconMode";
16
21
 
17
- type OptimizerConfig = Partial<Record<OptimizerTool, string>>;
22
+ type OptimizerConfig = Partial<Record<OptimizerTool, string>> & {
23
+ [ICON_MODE_KEY]?: string;
24
+ };
18
25
 
19
26
  function getStatePath(): string {
20
27
  return join(getAgentDir(), "optimizer.json");
@@ -48,3 +55,21 @@ export function saveOptValue(tool: OptimizerTool, value: string): void {
48
55
  console.warn(`optimizer: persist ${tool} failed:`, err);
49
56
  }
50
57
  }
58
+
59
+ /** Read the persisted icon mode, or undefined if unset/invalid. */
60
+ export function loadIconMode(): IconMode | undefined {
61
+ const raw = readAll()[ICON_MODE_KEY];
62
+ return ICON_MODES.includes(raw as IconMode) ? (raw as IconMode) : undefined;
63
+ }
64
+
65
+ /** Persist the icon mode, merging into the shared config file. */
66
+ export function saveIconMode(mode: IconMode): void {
67
+ try {
68
+ const sp = getStatePath();
69
+ mkdirSync(dirname(sp), { recursive: true });
70
+ const next = { ...readAll(), [ICON_MODE_KEY]: mode };
71
+ writeFileSync(sp, JSON.stringify(next, null, 2), "utf-8");
72
+ } catch (err) {
73
+ console.warn("optimizer: persist iconMode failed:", err);
74
+ }
75
+ }
@@ -1,5 +1,8 @@
1
- import { describe, expect, it } from "bun:test";
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
2
  import {
3
+ envIconMode,
4
+ getIcons,
5
+ ICON_MODES,
3
6
  OptimizerStatus,
4
7
  renderStatus,
5
8
  STATUS_KEY,
@@ -45,6 +48,66 @@ describe("renderStatus", () => {
45
48
  });
46
49
  });
47
50
 
51
+ describe("icon modes", () => {
52
+ it("nerd is the default mode used by renderStatus", () => {
53
+ expect(renderStatus({ rtk: true }, tag)).toContain(TOOL_ICONS.rtk);
54
+ });
55
+
56
+ it("renders the unicode (outline-suit) set when mode=unicode", () => {
57
+ const icons = getIcons("unicode");
58
+ const out = renderStatus({ caveman: true }, tag, "unicode");
59
+ expect(out).toContain(icons.caveman);
60
+ // outline white spade + text variation selector
61
+ expect(icons.caveman).toBe("\u2664\uFE0E");
62
+ });
63
+
64
+ it("renders the ascii set when mode=ascii", () => {
65
+ const out = renderStatus(
66
+ { caveman: true, rtk: false, toon: true, ponytail: false },
67
+ tag,
68
+ "ascii",
69
+ );
70
+ expect(out).toBe(
71
+ `<accent>Cv</accent> <dim>Rk</dim> <accent>Tn</accent> <dim>Pt</dim> `,
72
+ );
73
+ });
74
+
75
+ it("falls back to nerd for an unknown mode", () => {
76
+ // @ts-expect-error exercising the runtime guard in getIcons
77
+ expect(getIcons("bogus")).toBe(getIcons("nerd"));
78
+ });
79
+
80
+ it("exposes exactly nerd/unicode/ascii in cycle order", () => {
81
+ expect([...ICON_MODES]).toEqual(["nerd", "unicode", "ascii"]);
82
+ });
83
+ });
84
+
85
+ describe("envIconMode", () => {
86
+ const saved = { ...process.env };
87
+ afterEach(() => {
88
+ process.env.OPTIMIZER_ICONS = saved.OPTIMIZER_ICONS;
89
+ process.env.PRETTY_ICONS = saved.PRETTY_ICONS;
90
+ });
91
+
92
+ it("defaults to nerd when nothing set", () => {
93
+ process.env.OPTIMIZER_ICONS = undefined;
94
+ process.env.PRETTY_ICONS = undefined;
95
+ expect(envIconMode()).toBe("nerd");
96
+ });
97
+
98
+ it("OPTIMIZER_ICONS wins over PRETTY_ICONS", () => {
99
+ process.env.OPTIMIZER_ICONS = "unicode";
100
+ process.env.PRETTY_ICONS = "none";
101
+ expect(envIconMode()).toBe("unicode");
102
+ });
103
+
104
+ it("PRETTY_ICONS=none maps to ascii", () => {
105
+ process.env.OPTIMIZER_ICONS = undefined;
106
+ process.env.PRETTY_ICONS = "none";
107
+ expect(envIconMode()).toBe("ascii");
108
+ });
109
+ });
110
+
48
111
  describe("OptimizerStatus", () => {
49
112
  /** Minimal ui stub capturing setStatus calls. */
50
113
  function fakeCtx() {
@@ -92,4 +155,24 @@ describe("OptimizerStatus", () => {
92
155
  `<dim>${TOOL_ICONS.caveman}</dim> <dim>${TOOL_ICONS.rtk}</dim> <dim>${TOOL_ICONS.toon}</dim> <dim>${TOOL_ICONS.ponytail}</dim> `,
93
156
  );
94
157
  });
158
+
159
+ it("seeds icon mode from the constructor", () => {
160
+ const status = new OptimizerStatus("ascii");
161
+ expect(status.mode).toBe("ascii");
162
+ const ctx = fakeCtx();
163
+ status.set("caveman", true, ctx as never);
164
+ expect(ctx.calls.at(-1)!.text).toContain("Cv");
165
+ });
166
+
167
+ it("setMode switches the rendered glyph set and repaints", () => {
168
+ const status = new OptimizerStatus("nerd");
169
+ const ctx = fakeCtx();
170
+ status.set("rtk", true, ctx as never);
171
+ expect(ctx.calls.at(-1)!.text).toContain(TOOL_ICONS.rtk);
172
+ status.setMode("ascii", ctx as never);
173
+ const last = ctx.calls.at(-1)!;
174
+ expect(last.key).toBe(STATUS_KEY);
175
+ expect(last.text).toContain("Rk");
176
+ expect(last.text).not.toContain(TOOL_ICONS.rtk);
177
+ });
95
178
  });
package/src/status.ts CHANGED
@@ -42,13 +42,72 @@ export const STATUS_KEY = "pix-optimizer";
42
42
  /** Tools that participate in the shared indicator, in render order. */
43
43
  export type OptimizerTool = "caveman" | "rtk" | "toon" | "ponytail";
44
44
 
45
- export const TOOL_ICONS: Record<OptimizerTool, string> = {
46
- caveman: "󰜐",
47
- rtk: "󰓥",
48
- toon: "󰗀",
49
- ponytail: "󰆐",
45
+ /**
46
+ * Icon presentation modes. The optimizer cell defaults to Nerd Font glyphs,
47
+ * which require a patched font (e.g. MesloLGS NF). Terminals without one show
48
+ * missing-glyph tofu, so two font-independent fallbacks are offered:
49
+ * - `unicode`: outline playing-card suits (standard BMP, ships with virtually
50
+ * every monospace font) + U+FE0E to force text (non-emoji) presentation.
51
+ * - `ascii`: two-letter labels, renders on literally any terminal.
52
+ * Mode is chosen via the /optimizer menu (persisted) or the OPTIMIZER_ICONS /
53
+ * PRETTY_ICONS env vars.
54
+ */
55
+ export type IconMode = "nerd" | "unicode" | "ascii";
56
+
57
+ /** All selectable icon modes, in /optimizer cycle order. */
58
+ export const ICON_MODES: readonly IconMode[] = ["nerd", "unicode", "ascii"];
59
+
60
+ /** Force text (non-emoji) presentation for symbols that default to emoji. */
61
+ const VS_TEXT = "\uFE0E";
62
+
63
+ /** Per-mode glyph table for each tool. */
64
+ const ICON_SETS: Record<IconMode, Record<OptimizerTool, string>> = {
65
+ nerd: {
66
+ caveman: "󰜐",
67
+ rtk: "󰓥",
68
+ toon: "󰗀",
69
+ ponytail: "󰆐",
70
+ },
71
+ unicode: {
72
+ caveman: `\u2664${VS_TEXT}`, // ♤ white spade
73
+ rtk: `\u2661${VS_TEXT}`, // ♡ white heart
74
+ toon: `\u2662${VS_TEXT}`, // ♢ white diamond
75
+ ponytail: `\u2667${VS_TEXT}`, // ♧ white club
76
+ },
77
+ ascii: {
78
+ caveman: "Cv",
79
+ rtk: "Rk",
80
+ toon: "Tn",
81
+ ponytail: "Pt",
82
+ },
50
83
  };
51
84
 
85
+ /** Resolve the glyph table for a mode. */
86
+ export function getIcons(mode: IconMode): Record<OptimizerTool, string> {
87
+ return ICON_SETS[mode] ?? ICON_SETS.nerd;
88
+ }
89
+
90
+ /**
91
+ * Default icon mode from the environment. OPTIMIZER_ICONS takes precedence;
92
+ * PRETTY_ICONS=none|off maps to ascii so one stack-wide var disables glyphs.
93
+ * Anything unrecognized (or unset) falls back to `nerd`. A persisted menu
94
+ * choice overrides this at load time (see persist.loadIconMode).
95
+ */
96
+ export function envIconMode(): IconMode {
97
+ const raw = (process.env.OPTIMIZER_ICONS ?? "").toLowerCase();
98
+ if (raw === "nerd" || raw === "unicode" || raw === "ascii") return raw;
99
+ const pretty = (process.env.PRETTY_ICONS ?? "").toLowerCase();
100
+ if (pretty === "none" || pretty === "off") return "ascii";
101
+ return "nerd";
102
+ }
103
+
104
+ /**
105
+ * Default Nerd Font glyph table. Kept as a named export for back-compat with
106
+ * callers/tests that reference the nerd set directly; equivalent to
107
+ * getIcons("nerd").
108
+ */
109
+ export const TOOL_ICONS: Record<OptimizerTool, string> = ICON_SETS.nerd;
110
+
52
111
  /** Fixed left-to-right order of icons in the cell. */
53
112
  const TOOL_ORDER: readonly OptimizerTool[] = [
54
113
  "caveman",
@@ -69,6 +128,7 @@ export type Colorize = (color: ThemeColor, text: string) => string;
69
128
  * Build the colored status string for a set of tool states. ALL tool icons are
70
129
  * always shown, in TOOL_ORDER; each is accent-colored when its tool is enabled
71
130
  * and dim when disabled. `color` applies the theme color (e.g. theme.fg).
131
+ * `mode` selects the glyph table (nerd / unicode / ascii); defaults to nerd.
72
132
  *
73
133
  * Pure + exported for tests (pass a tagging colorizer to assert per-icon color).
74
134
  * A trailing space separates the cell from the next status segment.
@@ -76,9 +136,11 @@ export type Colorize = (color: ThemeColor, text: string) => string;
76
136
  export function renderStatus(
77
137
  states: Partial<Record<OptimizerTool, boolean>>,
78
138
  color: Colorize,
139
+ mode: IconMode = "nerd",
79
140
  ): string {
141
+ const icons = getIcons(mode);
80
142
  return `${TOOL_ORDER.map((t) =>
81
- color(states[t] === true ? ENABLED_COLOR : DISABLED_COLOR, TOOL_ICONS[t]),
143
+ color(states[t] === true ? ENABLED_COLOR : DISABLED_COLOR, icons[t]),
82
144
  ).join(" ")} `;
83
145
  }
84
146
 
@@ -89,12 +151,29 @@ export function renderStatus(
89
151
  */
90
152
  export class OptimizerStatus {
91
153
  private states: Partial<Record<OptimizerTool, boolean>> = {};
154
+ private iconMode: IconMode;
155
+
156
+ /** `mode` seeds the icon set (persisted choice or env default). */
157
+ constructor(mode: IconMode = "nerd") {
158
+ this.iconMode = mode;
159
+ }
92
160
 
93
161
  /** Current enabled state for one tool (undefined until first set). */
94
162
  get(tool: OptimizerTool): boolean | undefined {
95
163
  return this.states[tool];
96
164
  }
97
165
 
166
+ /** Current icon presentation mode. */
167
+ get mode(): IconMode {
168
+ return this.iconMode;
169
+ }
170
+
171
+ /** Switch icon mode and repaint the shared cell. */
172
+ setMode(mode: IconMode, ctx: Pick<ExtensionContext, "ui">): void {
173
+ this.iconMode = mode;
174
+ this.paint(ctx);
175
+ }
176
+
98
177
  /** Update one tool's enabled state and repaint the shared cell. */
99
178
  set(
100
179
  tool: OptimizerTool,
@@ -107,7 +186,11 @@ export class OptimizerStatus {
107
186
 
108
187
  /** Repaint the shared cell from current states. */
109
188
  paint(ctx: Pick<ExtensionContext, "ui">): void {
110
- const text = renderStatus(this.states, (c, t) => ctx.ui.theme.fg(c, t));
189
+ const text = renderStatus(
190
+ this.states,
191
+ (c, t) => ctx.ui.theme.fg(c, t),
192
+ this.iconMode,
193
+ );
111
194
  ctx.ui.setStatus(STATUS_KEY, text);
112
195
  }
113
196
  }