@pi-kaush/pi-tool-call-markers 0.2.11 → 0.3.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,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Color collapsed rows (tool calls, `+ Thought`, subagents) from the
6
+ theme's `syntaxComment` token — which ships with every theme — instead of
7
+ the louder muted/toolTitle/toolOutput split. The bolded tool name and the
8
+ call content share the single tone, so collapsed blocks read as one
9
+ muted unit under any theme without per-theme tuning. Failures stay
10
+ error-colored and live spinners keep their thinking-level tint.
11
+ - Add optional `collapsedToolCall` and `collapsedThinkingCall` theme color
12
+ tokens to override each collapsed kind independently; themes without
13
+ them are unchanged.
14
+ - Fix settled "+ Thought" labels keeping stale colors after a mid-session
15
+ theme switch: label styles now recompute whenever the theme object
16
+ changes instead of only on session start and thinking-level selects.
5
17
  - Add `/toggle-info`: hide tool calls and thinking entirely, or restore them
6
18
  collapsed. Execution rows (including user-run `!` blocks) are lifted out of
7
19
  the render pass via the shared chat-container hook; thinking blocks are
package/README.md CHANGED
@@ -100,6 +100,21 @@ pi
100
100
 
101
101
  `0`, `false`, `no`, and `off` disable parallel grouping. `1`, `true`, `yes`, and `on` enable it. The value is read when the extension loads.
102
102
 
103
+ ### Collapsed-row colors
104
+
105
+ Collapsed rows (tool calls, `+ Thought`, subagents) default to the theme's `syntaxComment` color — it ships with every Pi theme and reads as a muted tone — with the bolded tool name and the call content sharing it. Failures stay error-colored and live spinners keep their existing tints.
106
+
107
+ A theme can override each collapsed kind independently with optional color tokens:
108
+
109
+ ```json
110
+ {
111
+ "colors": {
112
+ "collapsedToolCall": "#6272a4",
113
+ "collapsedThinkingCall": "#6272a4"
114
+ }
115
+ }
116
+ ```
117
+
103
118
  ## Compatibility and fallback policy
104
119
 
105
120
  **Compatible Pi version:** `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` `>=0.80.6`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-kaush/pi-tool-call-markers",
3
- "version": "0.2.11",
3
+ "version": "0.3.0",
4
4
  "description": "Compact and group Pi tool calls, with a bundled extension for adjacent thinking blocks.",
5
5
  "license": "MIT",
6
6
  "author": "Kaushik Gopal",
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  } from "./bash-block.ts";
17
17
  import { runChatContainerHooks } from "./container-hooks.ts";
18
18
  import { installInfoVisibility } from "./info-visibility.ts";
19
+ import { fgCollapsed } from "./muted.ts";
19
20
 
20
21
  const OUTER_INSET = 2;
21
22
  const GROUP_MARKER = "%";
@@ -29,6 +30,8 @@ const COLLAPSE_PARALLEL_ENV = "PI_TOOL_CALL_MARKERS_COLLAPSE_PARALLEL";
29
30
  type ThemeLike = {
30
31
  bold(text: string): string;
31
32
  fg(color: string, text: string): string;
33
+ getFgAnsi?(color: string): string;
34
+ getColorMode?(): "truecolor" | "256color" | string;
32
35
  };
33
36
 
34
37
  type ComponentLike = {
@@ -252,7 +255,7 @@ function capFailureTail(tail: string, cap: number, theme: ThemeLike): string {
252
255
  }
253
256
 
254
257
  function renderedGenericOutcome(theme: ThemeLike): string {
255
- return theme.fg("toolOutput", "→ done");
258
+ return fgCollapsed(theme, "toolOutput", "→ done");
256
259
  }
257
260
 
258
261
  // The MCP adapter stashes its call's first line in renderer state before
@@ -637,7 +640,7 @@ function renderedEditDiffLines(
637
640
  for (const line of raw.slice(0, MAX_EDIT_DIFF_LINES)) {
638
641
  const clean = sanitizeInline(line).trimEnd();
639
642
  if (clean.trim() === "...") {
640
- lines.push(theme.fg("muted", " ..."));
643
+ lines.push(fgCollapsed(theme, "muted", " ..."));
641
644
  continue;
642
645
  }
643
646
  const match = EDIT_DIFF_LINE_RE.exec(clean);
@@ -652,7 +655,11 @@ function renderedEditDiffLines(
652
655
  }
653
656
  if (raw.length > MAX_EDIT_DIFF_LINES) {
654
657
  lines.push(
655
- theme.fg("muted", ` ... +${raw.length - MAX_EDIT_DIFF_LINES} more`),
658
+ fgCollapsed(
659
+ theme,
660
+ "muted",
661
+ ` ... +${raw.length - MAX_EDIT_DIFF_LINES} more`,
662
+ ),
656
663
  );
657
664
  }
658
665
  return lines;
@@ -706,7 +713,7 @@ function renderedOutcome(
706
713
  ): string | undefined {
707
714
  const summary = outcomeSummary(row);
708
715
  if (!summary) return undefined;
709
- return theme.fg("toolOutput", `→ ${summary}`);
716
+ return fgCollapsed(theme, "toolOutput", `→ ${summary}`);
710
717
  }
711
718
 
712
719
  function renderedGroupedOutcome(
@@ -835,10 +842,10 @@ function renderedCallSummary(
835
842
  .map(trimRenderedLine)
836
843
  .filter(hasVisibleContent);
837
844
  if (visibleLines.length > rendered.length)
838
- continuations.push(theme.fg("muted", "…"));
845
+ continuations.push(fgCollapsed(theme, "muted", "…"));
839
846
  const compact = [firstSummary, ...continuations]
840
847
  .filter(hasVisibleContent)
841
- .join(theme.fg("muted", " · "));
848
+ .join(fgCollapsed(theme, "muted", " · "));
842
849
  if (hasVisibleContent(compact)) return compact;
843
850
  }
844
851
  }
@@ -846,8 +853,8 @@ function renderedCallSummary(
846
853
 
847
854
  const fallback = compactArgs(row.args);
848
855
  return fallback
849
- ? theme.fg("toolOutput", fallback)
850
- : theme.fg("muted", "(no arguments)");
856
+ ? fgCollapsed(theme, "toolOutput", fallback)
857
+ : fgCollapsed(theme, "muted", "(no arguments)");
851
858
  }
852
859
 
853
860
  function styledCallLabel(
@@ -857,15 +864,14 @@ function styledCallLabel(
857
864
  ): string {
858
865
  const plain = sanitizeInline(stripAnsi(label)).trim();
859
866
  const match = /^(\S+)(.*)$/s.exec(plain);
860
- if (!match) return theme.fg(color, plain);
867
+ if (!match) return fgCollapsed(theme, color, plain);
861
868
  const rest = match[2] ?? "";
862
- // Failed rows stay uniformly error-colored; settled rows use Pi's native
863
- // split toolTitle for the name, toolOutput for the call content.
864
- const nameColor = color === "muted" ? "toolTitle" : color;
865
- const restColor = color === "muted" ? "toolOutput" : color;
869
+ // The colon joins the bolded name to its content. Failed rows stay
870
+ // uniformly error-colored; settled rows use the collapsed mute same
871
+ // color for the bolded name and the call content.
866
872
  return (
867
- theme.fg(nameColor, theme.bold(match[1] ?? "")) +
868
- (rest ? theme.fg(restColor, `:${rest}`) : "")
873
+ fgCollapsed(theme, color, match[1] ?? "", true) +
874
+ (rest ? fgCollapsed(theme, color, `:${rest}`) : "")
869
875
  );
870
876
  }
871
877
 
@@ -904,7 +910,7 @@ function collapsedOutcome(
904
910
  }
905
911
  if (row.getRenderShell?.() === "self") {
906
912
  const editStat = renderedEditDiffStat(row);
907
- if (editStat) return theme.fg("toolOutput", `→ ${editStat}`);
913
+ if (editStat) return fgCollapsed(theme, "toolOutput", `→ ${editStat}`);
908
914
  return renderedGenericOutcome(theme);
909
915
  }
910
916
  return renderedOutcome(row, theme);
@@ -917,18 +923,19 @@ function collapsedHeadline(
917
923
  ): string {
918
924
  // Failed rows render entirely in error so the row reads as the one that
919
925
  // failed, not just its outcome tail. Only the tool name is bold.
920
- const color = rowHasFailed(row) ? "error" : "muted";
921
- const marker = `${theme.fg(
922
- color,
926
+ const tone = rowHasFailed(row) ? "error" : "muted";
927
+ const marker = `${fgCollapsed(
928
+ theme,
929
+ tone,
923
930
  row.toolName === "subagent" ? SUBAGENT_MARKER : GROUP_MARKER,
924
931
  )} `;
925
932
  const budget = Math.max(1, width - visibleWidth(marker));
926
- const label = collapsedCallLabel(row, budget, theme, color);
933
+ const label = collapsedCallLabel(row, budget, theme, tone);
927
934
  const outcome = collapsedOutcome(row, budget, theme);
928
935
  // The truncation suffix inherits the row tone; pi-tui's truncation resets
929
936
  // around a plain suffix, which would render it in the terminal default
930
937
  // foreground instead of the row color.
931
- const suffix = theme.fg(color, "…");
938
+ const suffix = fgCollapsed(theme, tone, "…");
932
939
  return (
933
940
  marker +
934
941
  (outcome
@@ -1099,20 +1106,25 @@ function renderSubagentPlan(
1099
1106
  const failed = rowHasFailed(row);
1100
1107
  const color = failed ? "error" : "muted";
1101
1108
  const nameColor = failed ? "error" : "accent";
1102
- const suffix = theme.fg(color, "…");
1109
+ const suffix = fgCollapsed(theme, color, "…");
1103
1110
  const displayNames = scrapeSubagentDisplayNames(row);
1104
1111
  // Agent/profile/task values are model-supplied; sanitize them like every
1105
1112
  // other collapsed-row text so control bytes cannot reach the terminal.
1106
1113
  const displayOf = (agent: string) =>
1107
- theme.fg(nameColor, displayNames.get(agent) ?? sanitizeInline(agent));
1114
+ fgCollapsed(
1115
+ theme,
1116
+ nameColor,
1117
+ displayNames.get(agent) ?? sanitizeInline(agent),
1118
+ );
1108
1119
  const detailColor = failed ? "error" : "toolOutput";
1109
1120
  const detailOf = (step: SubagentStep) =>
1110
- theme.fg(
1121
+ fgCollapsed(
1122
+ theme,
1111
1123
  detailColor,
1112
1124
  `${step.profile ? ` [${sanitizeInline(step.profile)}]` : ""} ${subagentStepPreview(step.task)}`,
1113
1125
  );
1114
1126
 
1115
- const marker = `${theme.fg(color, theme.bold(SUBAGENT_MARKER))} `;
1127
+ const marker = `${fgCollapsed(theme, color, SUBAGENT_MARKER, true)} `;
1116
1128
  const budget = Math.max(1, width - visibleWidth(marker));
1117
1129
  const progress = subagentProgressText(row);
1118
1130
  const outcome = failed
@@ -1120,7 +1132,11 @@ function renderSubagentPlan(
1120
1132
  : isLiveRow(row)
1121
1133
  ? theme.fg("warning", progress ? `→ ${progress}` : "…")
1122
1134
  : row.result
1123
- ? theme.fg("toolOutput", progress ? `→ ${progress}` : "→ done")
1135
+ ? fgCollapsed(
1136
+ theme,
1137
+ "toolOutput",
1138
+ progress ? `→ ${progress}` : "→ done",
1139
+ )
1124
1140
  : theme.fg("warning", "…");
1125
1141
  const headline = (label: string) =>
1126
1142
  marker +
@@ -1132,7 +1148,7 @@ function renderSubagentPlan(
1132
1148
  const step = plan.steps[0]!;
1133
1149
  return [
1134
1150
  headline(
1135
- theme.fg(failed ? "error" : "toolTitle", theme.bold("subagent")) +
1151
+ fgCollapsed(theme, failed ? "error" : "toolTitle", "subagent", true) +
1136
1152
  " " +
1137
1153
  displayOf(step.agent) +
1138
1154
  detailOf(step),
@@ -1146,22 +1162,24 @@ function renderSubagentPlan(
1146
1162
  : `parallel (${plan.steps.length} tasks)`;
1147
1163
  const lines = [
1148
1164
  headline(
1149
- theme.fg(failed ? "error" : "toolTitle", theme.bold("subagent")) +
1165
+ fgCollapsed(theme, failed ? "error" : "toolTitle", "subagent", true) +
1150
1166
  " " +
1151
- theme.fg(failed ? "error" : "toolOutput", kindLabel) +
1152
- theme.fg(failed ? "error" : "toolOutput", ` [${plan.scope}]`),
1167
+ fgCollapsed(theme, failed ? "error" : "toolOutput", kindLabel) +
1168
+ fgCollapsed(theme, failed ? "error" : "toolOutput", ` [${plan.scope}]`),
1153
1169
  ),
1154
1170
  ];
1155
1171
  const shown = plan.steps.slice(0, 3);
1156
1172
  for (let index = 0; index < shown.length; index++) {
1157
1173
  const step = shown[index]!;
1158
1174
  const number =
1159
- plan.kind === "chain" ? `${theme.fg(color, `${index + 1}.`)} ` : "";
1175
+ plan.kind === "chain"
1176
+ ? `${fgCollapsed(theme, color, `${index + 1}.`)} `
1177
+ : "";
1160
1178
  lines.push(` ${number}${displayOf(step.agent)}${detailOf(step)}`);
1161
1179
  }
1162
1180
  if (plan.steps.length > shown.length) {
1163
1181
  lines.push(
1164
- ` ${theme.fg(color, `... +${plan.steps.length - shown.length} more`)}`,
1182
+ ` ${fgCollapsed(theme, color, `... +${plan.steps.length - shown.length} more`)}`,
1165
1183
  );
1166
1184
  }
1167
1185
  return lines;
@@ -1182,7 +1200,7 @@ function imageResultLines(
1182
1200
  .join("\n");
1183
1201
  lines.push(
1184
1202
  ...wrapTextWithAnsi(
1185
- theme.fg("toolOutput", sanitized),
1203
+ fgCollapsed(theme, "toolOutput", sanitized),
1186
1204
  Math.max(1, width),
1187
1205
  ),
1188
1206
  );
@@ -1238,7 +1256,7 @@ function groupedChildLabel(
1238
1256
  Math.max(0, visibleWidth(label) - prefixWidth),
1239
1257
  true,
1240
1258
  );
1241
- return child || theme.fg(color, "(no arguments)");
1259
+ return child || fgCollapsed(theme, color, "(no arguments)");
1242
1260
  }
1243
1261
 
1244
1262
  function renderGroupedCallLines(
@@ -1252,17 +1270,17 @@ function renderGroupedCallLines(
1252
1270
  const toolName = row.toolName ?? "tool";
1253
1271
  if (toolName !== previousToolName) {
1254
1272
  lines.push(
1255
- `${theme.fg("muted", GROUP_MARKER)} ${theme.fg("toolTitle", theme.bold(toolName))}`,
1273
+ `${fgCollapsed(theme, "muted", GROUP_MARKER)} ${fgCollapsed(theme, "toolTitle", toolName, true)}`,
1256
1274
  );
1257
1275
  previousToolName = toolName;
1258
1276
  }
1259
1277
 
1260
1278
  const color = rowHasFailed(row) ? "error" : "muted";
1261
- const prefix = ` ${theme.fg(color, "•")} `;
1279
+ const prefix = ` ${fgCollapsed(theme, color, "•")} `;
1262
1280
  const budget = Math.max(1, width - visibleWidth(prefix));
1263
1281
  const label = groupedChildLabel(row, budget, theme, color);
1264
1282
  const outcome = collapsedOutcome(row, budget, theme);
1265
- const suffix = theme.fg(color, "…");
1283
+ const suffix = fgCollapsed(theme, color, "…");
1266
1284
  lines.push(
1267
1285
  prefix +
1268
1286
  (outcome
@@ -1681,6 +1699,12 @@ export default function (pi: ExtensionAPI) {
1681
1699
  ctx.ui.setToolsExpanded(false);
1682
1700
  });
1683
1701
 
1702
+ pi.on("session_start", (_event, ctx) => {
1703
+ if (patch) patch.theme = ctx.ui.theme;
1704
+ if (bashPatch) bashPatch.theme = ctx.ui.theme;
1705
+ ctx.ui.setToolsExpanded(false);
1706
+ });
1707
+
1684
1708
  pi.on("session_shutdown", () => {
1685
1709
  if (released) return;
1686
1710
  released = true;
package/src/muted.ts ADDED
@@ -0,0 +1,80 @@
1
+ // Collapsed-row color policy.
2
+ //
3
+ // Collapsed blocks (tool calls, settled "+ Thought" labels, subagent rows)
4
+ // default to the theme's `syntaxComment` token — it ships with every Pi
5
+ // theme and reads as a muted tone — instead of the louder
6
+ // muted/toolTitle/toolOutput split. A theme can override each collapsed
7
+ // kind independently with a `collapsedToolCall` or `collapsedThinkingCall`
8
+ // color token.
9
+ //
10
+ // Failures always fall back to the historical tokens.
11
+
12
+ const TOOL_OVERRIDE_TOKEN = "collapsedToolCall";
13
+ const THINKING_OVERRIDE_TOKEN = "collapsedThinkingCall";
14
+ const DEFAULT_TOKEN = "syntaxComment";
15
+
16
+ type CollapsedTheme = {
17
+ fg(color: string, text: string): string;
18
+ bold?(text: string): string;
19
+ getFgAnsi?(color: string): string;
20
+ };
21
+
22
+ // Resolved per theme object (Pi's Theme throws on unknown tokens; themes
23
+ // without getFgAnsi have no overrides), alongside index.ts's other
24
+ // theme-identity-keyed caches.
25
+ const ansiCache = new WeakMap<object, Map<string, string | null>>();
26
+
27
+ function tokenAnsi(theme: CollapsedTheme, token: string): string | null {
28
+ let cache = ansiCache.get(theme);
29
+ if (!cache) {
30
+ cache = new Map();
31
+ ansiCache.set(theme, cache);
32
+ }
33
+ if (cache.has(token)) return cache.get(token)!;
34
+ let ansi: string | null = null;
35
+ try {
36
+ const resolved = theme.getFgAnsi?.(token);
37
+ if (typeof resolved === "string") ansi = resolved;
38
+ } catch {
39
+ ansi = null;
40
+ }
41
+ cache.set(token, ansi);
42
+ return ansi;
43
+ }
44
+
45
+ // Override token when defined, else the theme's syntaxComment color, else
46
+ // null (caller falls back to the historical tokens).
47
+ function collapsedAnsi(
48
+ theme: CollapsedTheme,
49
+ overrideToken: string,
50
+ ): string | null {
51
+ return tokenAnsi(theme, overrideToken) ?? tokenAnsi(theme, DEFAULT_TOKEN);
52
+ }
53
+
54
+ export function collapsedToolAnsi(theme: CollapsedTheme): string | null {
55
+ return collapsedAnsi(theme, TOOL_OVERRIDE_TOKEN);
56
+ }
57
+
58
+ export function collapsedThinkingAnsi(theme: CollapsedTheme): string | null {
59
+ return collapsedAnsi(theme, THINKING_OVERRIDE_TOKEN);
60
+ }
61
+
62
+ // One styling entry point for collapsed-row text. `color` is the historical
63
+ // token role ("muted", "toolOutput", "toolTitle", "accent", "error"); error
64
+ // keeps its semantic color in all paths.
65
+ export function fgCollapsed(
66
+ theme: CollapsedTheme,
67
+ color: string,
68
+ text: string,
69
+ bold = false,
70
+ ): string {
71
+ if (color !== "error") {
72
+ const ansi = collapsedToolAnsi(theme);
73
+ if (ansi) {
74
+ return bold
75
+ ? `${ansi}\x1b[1m${text}\x1b[22m\x1b[39m`
76
+ : `${ansi}${text}\x1b[39m`;
77
+ }
78
+ }
79
+ return theme.fg(color, bold && theme.bold ? theme.bold(text) : text);
80
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { AssistantMessageComponent } from "@earendil-works/pi-coding-agent";
3
3
  import { infoVisibilityHidden } from "./info-visibility-state.ts";
4
+ import { collapsedThinkingAnsi } from "./muted.ts";
4
5
 
5
6
  const THINKING_GROUPING_PATCHED = Symbol.for("kg.pi.thinkingGrouping.v2");
6
7
  const PI_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -44,15 +45,22 @@ type ThemeDetail = {
44
45
  getColorMode?(): "truecolor" | "256color" | string;
45
46
  };
46
47
 
47
- let activeTheme: ThemeDetail | undefined;
48
+ // Live reference into the UI context: reading .theme per check keeps label
49
+ // styles current across mid-session theme switches, which fire no event.
50
+ let themeProvider: (() => ThemeDetail | undefined) | undefined;
48
51
  let activeLevel: (() => string) | undefined;
52
+ // Theme object the current label styles were computed from.
53
+ let stylesTheme: ThemeDetail | undefined;
49
54
 
50
55
  type LabelStyle = { prefix: string; suffix: string };
51
56
  let liveLabelStyle: LabelStyle | undefined;
52
57
  let settledLabelStyle: LabelStyle | undefined;
53
58
 
54
- function styledWith(token: string): LabelStyle | undefined {
55
- const ansi = activeTheme?.getFgAnsi?.(token);
59
+ function styledWith(
60
+ token: string,
61
+ theme: ThemeDetail | undefined = themeProvider?.(),
62
+ ): LabelStyle | undefined {
63
+ const ansi = theme?.getFgAnsi?.(token);
56
64
  if (!ansi) return undefined;
57
65
  // The styled label replaces Pi's italicized Text node wholesale (see
58
66
  // restyleHiddenThinkingLabel). The leading italic-off still matters: the
@@ -61,19 +69,36 @@ function styledWith(token: string): LabelStyle | undefined {
61
69
  return { prefix: `\x1b[23m${ansi}`, suffix: "\x1b[39m" };
62
70
  }
63
71
 
72
+ // The theme's collapsedThinkingCall override when defined, else its comment
73
+ // color, else the muted token.
74
+ function settledMutedStyle(
75
+ theme: ThemeDetail | undefined,
76
+ ): LabelStyle | undefined {
77
+ const ansi = theme ? collapsedThinkingAnsi(theme) : null;
78
+ if (ansi) return { prefix: `\x1b[23m${ansi}`, suffix: "\x1b[39m" };
79
+ return styledWith("muted", theme);
80
+ }
64
81
  function updateThoughtLabelStyle(): void {
65
82
  liveLabelStyle = undefined;
66
83
  settledLabelStyle = undefined;
84
+ const theme = themeProvider?.();
85
+ stylesTheme = theme;
67
86
  const choice = thoughtLabelColorChoice();
68
- if (choice === "inherit" || !activeTheme) return;
87
+ if (choice === "inherit" || !theme) return;
69
88
 
70
89
  const token =
71
90
  choice === "mdheading"
72
91
  ? "mdHeading"
73
92
  : (LEVEL_TOKEN[activeLevel?.() ?? "off"] ?? "thinkingOff");
74
- liveLabelStyle = styledWith(token);
93
+ liveLabelStyle = styledWith(token, theme);
75
94
  settledLabelStyle =
76
- choice === "mdheading" ? liveLabelStyle : styledWith("muted");
95
+ choice === "mdheading" ? liveLabelStyle : settledMutedStyle(theme);
96
+ }
97
+
98
+ // Recompute label styles whenever the theme object has swapped (theme switch
99
+ // or /reload); those transitions fire no extension event.
100
+ function ensureLabelStyles(): void {
101
+ if (themeProvider?.() !== stylesTheme) updateThoughtLabelStyle();
77
102
  }
78
103
 
79
104
  // The settled label is the finalized "+ Thought" row; every other label is
@@ -83,6 +108,7 @@ function isSettledThoughtLabel(label: string): boolean {
83
108
  }
84
109
 
85
110
  export function visibleThoughtLabel(label: string): string {
111
+ ensureLabelStyles();
86
112
  const style = isSettledThoughtLabel(label)
87
113
  ? settledLabelStyle
88
114
  : liveLabelStyle;
@@ -418,7 +444,8 @@ export default function (pi: ExtensionAPI) {
418
444
  } catch {
419
445
  // Best effort; stale rows would only linger until the process exits.
420
446
  }
421
- activeTheme = ctx.ui.theme as unknown as ThemeDetail;
447
+ const uiCtx = ctx;
448
+ themeProvider = () => uiCtx.ui?.theme as unknown as ThemeDetail;
422
449
  activeLevel = () => {
423
450
  try {
424
451
  return pi.getThinkingLevel();
@@ -433,8 +460,9 @@ export default function (pi: ExtensionAPI) {
433
460
  if (released) return;
434
461
  released = true;
435
462
  if (patch && !uninstallThinkingGroupingPatch(patch)) return;
436
- activeTheme = undefined;
463
+ themeProvider = undefined;
437
464
  activeLevel = undefined;
465
+ stylesTheme = undefined;
438
466
  liveLabelStyle = undefined;
439
467
  settledLabelStyle = undefined;
440
468
  });