pi-claude-code-ui 1.0.88 → 1.0.90
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 +19 -0
- package/extensions/index.ts +141 -13
- package/extensions/spinner.ts +4 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
> [!IMPORTANT]
|
|
4
4
|
> **1.0.69 — package rename (permanent).** Canonical npm name is now [`pi-claude-code-ui`](https://www.npmjs.com/package/pi-claude-code-ui). `pi-claude-style-tools` is legacy and will not receive further releases. Install with `pi install npm:pi-claude-code-ui` or `npm i pi-claude-code-ui`.
|
|
5
5
|
|
|
6
|
+
## 1.0.90 — 2026-10-19
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Split diffs wrap long lines again** - with `"diffLayout": "split"` long lines were cut off with `›` after one row (two or three on wide terminals) instead of wrapping, because split columns kept the wrap limit meant for the auto layout. Forced split now wraps each line in full, up to 8 rows. Thanks @nabsiddiqui ([#34](https://github.com/FammasMaz/pi-cc-tools/issues/34)).
|
|
11
|
+
|
|
12
|
+
## 1.0.89 — 2026-10-19
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Direct MCP tools show what they did** - `mcp__<server>__<tool>` rows showed only `MCP status` until expanded. They now show `server:tool` and the main argument (for example `MCP subagents:Agent Debug NaN`), and grouped headers list the tool names (`Agent, Ctx Reduce`) instead of `Mcp Subagents Agent`.
|
|
17
|
+
- **Running tool rows no longer draw their header twice** - the first pending blink invalidated the row while Pi was still building it, which left the call line in the row twice until the next redraw (until the result arrived on expanded rows). Affected every tool, not only MCP.
|
|
18
|
+
- **Theme switches repaint everything** - after `/theme` or a light/dark switch, diff previews kept the old diff palette, rows inside tool groups kept the old colors, and the spinner verb/status kept the old theme until restart. All three now follow the new theme.
|
|
19
|
+
- **MCP output uses the same dim color as other tools** - MCP running previews and expanded output were drawn in `toolOutput`, brighter than read, bash, grep and other tools.
|
|
20
|
+
|
|
21
|
+
### Performance
|
|
22
|
+
|
|
23
|
+
- **Each diff preview renders once** - since 1.0.80 edit, write and apply_patch rows started their Shiki render at a guessed width and rendered again once the real row width was known. On resume every diff row paid twice. Rows now wait for their width: half the diff renders, and the first frame of a 60-row tool history dropped from about 37 ms to 8 ms in the benchmark.
|
|
24
|
+
|
|
6
25
|
## 1.0.88 — 2026-10-19
|
|
7
26
|
|
|
8
27
|
### Added
|
package/extensions/index.ts
CHANGED
|
@@ -89,6 +89,8 @@ const PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-container-render");
|
|
|
89
89
|
const TOOL_RENDER_CACHE = Symbol.for("pi-claude-style-tools:tool-render-cache");
|
|
90
90
|
const COMPONENT_PARENT = Symbol.for("pi-claude-style-tools:component-parent");
|
|
91
91
|
const PARENT_TRACKING_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-parent-tracking");
|
|
92
|
+
const TOOL_UPDATE_DISPLAY_ACTIVE = Symbol("pi-claude-style-tools:update-display-active");
|
|
93
|
+
const TOOL_UPDATE_DISPLAY_RERUN = Symbol("pi-claude-style-tools:update-display-rerun");
|
|
92
94
|
const TOOL_CACHE_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-tool-cache-invalidation");
|
|
93
95
|
const TOOL_MOUSE_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-tool-fullscreen-mouse");
|
|
94
96
|
const FULLSCREEN_HOVER_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-fullscreen-hover");
|
|
@@ -615,7 +617,7 @@ function getGroupedToolName(tools: any[]): string | undefined {
|
|
|
615
617
|
|
|
616
618
|
function getToolGroupLabel(tools: any[]): string {
|
|
617
619
|
const sameName = getGroupedToolName(tools);
|
|
618
|
-
return sameName ?
|
|
620
|
+
return sameName ? toolDisplayName(sameName) : "Multiple Tools";
|
|
619
621
|
}
|
|
620
622
|
|
|
621
623
|
function getToolGroupOverallStatus(tools: any[]): ToolStatus {
|
|
@@ -683,7 +685,7 @@ function formatToolNameList(tools: any[]): string {
|
|
|
683
685
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
684
686
|
}
|
|
685
687
|
return [...counts.entries()]
|
|
686
|
-
.map(([name, count]) => `${
|
|
688
|
+
.map(([name, count]) => `${toolDisplayName(name)}${count > 1 ? `×${count}` : ""}`)
|
|
687
689
|
.join(", ");
|
|
688
690
|
}
|
|
689
691
|
|
|
@@ -763,6 +765,7 @@ function getToolArgSummary(tool: any): string {
|
|
|
763
765
|
if (name === "grep") return `"${summarizeText(args.pattern ?? "", 40)}"${args.path ? ` in ${args.path}` : ""}`;
|
|
764
766
|
if (name === "find") return `"${summarizeText(args.pattern ?? "", 40)}"${args.path ? ` in ${args.path}` : ""}`;
|
|
765
767
|
if (name === "ls") return shortPath(process.cwd(), args.path ?? ".");
|
|
768
|
+
if (parseNamespacedMcpToolName(name)) return summarizeDirectMcpArgs(args);
|
|
766
769
|
return summarizeText(getStringArg(args, "path", "file_path", "url", "query", "name", "subject", "tool", "description", "prompt") || name, 72);
|
|
767
770
|
}
|
|
768
771
|
|
|
@@ -773,7 +776,7 @@ function getToolCallLine(tool: any): string {
|
|
|
773
776
|
return line.replaceAll(WRAP_MARK, "").replaceAll(CLIP_MARK, "");
|
|
774
777
|
}
|
|
775
778
|
const summary = getToolArgSummary(tool);
|
|
776
|
-
const label =
|
|
779
|
+
const label = toolDisplayName(getToolName(tool));
|
|
777
780
|
return `${label}${summary ? ` ${summary}` : ""}`;
|
|
778
781
|
}
|
|
779
782
|
|
|
@@ -917,6 +920,7 @@ class ToolGroupComponent extends Container {
|
|
|
917
920
|
private cachedStatusKey?: string;
|
|
918
921
|
private cachedToolCount?: number;
|
|
919
922
|
private cachedLines?: string[];
|
|
923
|
+
private paletteFingerprint?: string;
|
|
920
924
|
|
|
921
925
|
private clearRenderCache(): void {
|
|
922
926
|
this.dirty = true;
|
|
@@ -986,6 +990,15 @@ class ToolGroupComponent extends Container {
|
|
|
986
990
|
// only; children keep their own ToolText/TOOL_RENDER_CACHE entries and
|
|
987
991
|
// recompute only when their content actually changes.
|
|
988
992
|
this.clearRenderCache();
|
|
993
|
+
// Exception: a theme switch (Pi calls ui.invalidate()) changes colors the
|
|
994
|
+
// child renderers baked into their text, so those must re-run once.
|
|
995
|
+
// (Child invalidation re-enters here via clearToolRenderCache; the palette is
|
|
996
|
+
// recorded first so that re-entry is a no-op.)
|
|
997
|
+
const palette = currentThemePaletteFingerprint();
|
|
998
|
+
if (palette === undefined || palette === this.paletteFingerprint) return;
|
|
999
|
+
const changed = this.paletteFingerprint !== undefined;
|
|
1000
|
+
this.paletteFingerprint = palette;
|
|
1001
|
+
if (changed) for (const tool of this.tools) tool?.invalidate?.();
|
|
989
1002
|
}
|
|
990
1003
|
|
|
991
1004
|
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
@@ -1011,6 +1024,8 @@ class ToolGroupComponent extends Container {
|
|
|
1011
1024
|
}
|
|
1012
1025
|
const safeWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
|
|
1013
1026
|
// Fast path: settled groups with a valid memo skip ALL child walks.
|
|
1027
|
+
// Remember the palette this group was drawn with (see invalidate()).
|
|
1028
|
+
if (this.paletteFingerprint === undefined) this.paletteFingerprint = currentThemePaletteFingerprint();
|
|
1014
1029
|
// Child mutations mark dirty via clearToolRenderCache → invalidate().
|
|
1015
1030
|
if (
|
|
1016
1031
|
!this.dirty
|
|
@@ -2994,7 +3009,7 @@ function patchToolRenderCacheInvalidation(): void {
|
|
|
2994
3009
|
for (const method of methods) {
|
|
2995
3010
|
const original = proto[method];
|
|
2996
3011
|
if (typeof original !== "function") continue;
|
|
2997
|
-
|
|
3012
|
+
const run = function patchedToolMutation(this: any, ...args: any[]) {
|
|
2998
3013
|
clearToolRenderCache(this);
|
|
2999
3014
|
if (method === "updateDisplay" || method === "updateResult" || method === "invalidate") {
|
|
3000
3015
|
syncLiveToolRenderState(this);
|
|
@@ -3003,6 +3018,28 @@ function patchToolRenderCacheInvalidation(): void {
|
|
|
3003
3018
|
clearToolRenderCache(this);
|
|
3004
3019
|
return result;
|
|
3005
3020
|
};
|
|
3021
|
+
proto[method] = method !== "updateDisplay" ? run : function patchedToolUpdateDisplay(this: any, ...args: any[]) {
|
|
3022
|
+
// A renderer can invalidate its own row while updateDisplay is building it
|
|
3023
|
+
// (the first pending blink registration does). Pi's updateDisplay clears the
|
|
3024
|
+
// content box and re-adds regions, so a nested pass leaves the call/result
|
|
3025
|
+
// regions twice in the box until the next redraw. Defer it to one rerun.
|
|
3026
|
+
if (this[TOOL_UPDATE_DISPLAY_ACTIVE]) {
|
|
3027
|
+
this[TOOL_UPDATE_DISPLAY_RERUN] = true;
|
|
3028
|
+
return undefined;
|
|
3029
|
+
}
|
|
3030
|
+
this[TOOL_UPDATE_DISPLAY_ACTIVE] = true;
|
|
3031
|
+
try {
|
|
3032
|
+
let result = run.apply(this, args);
|
|
3033
|
+
if (this[TOOL_UPDATE_DISPLAY_RERUN]) {
|
|
3034
|
+
this[TOOL_UPDATE_DISPLAY_RERUN] = false;
|
|
3035
|
+
result = run.apply(this, args);
|
|
3036
|
+
}
|
|
3037
|
+
return result;
|
|
3038
|
+
} finally {
|
|
3039
|
+
this[TOOL_UPDATE_DISPLAY_ACTIVE] = false;
|
|
3040
|
+
this[TOOL_UPDATE_DISPLAY_RERUN] = false;
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3006
3043
|
}
|
|
3007
3044
|
|
|
3008
3045
|
proto[TOOL_CACHE_PATCH_FLAG] = true;
|
|
@@ -3788,6 +3825,9 @@ class ToolText extends Text {
|
|
|
3788
3825
|
private toolCachedLines?: string[];
|
|
3789
3826
|
private observedWidth?: number;
|
|
3790
3827
|
private pendingObservedWidth?: number;
|
|
3828
|
+
getObservedWidth(): number | undefined {
|
|
3829
|
+
return this.observedWidth;
|
|
3830
|
+
}
|
|
3791
3831
|
private widthObserver?: (width: number) => void;
|
|
3792
3832
|
private widthObserverScheduled = false;
|
|
3793
3833
|
|
|
@@ -4153,6 +4193,13 @@ function themeBgRgb(theme: any, key: string): Rgb | null {
|
|
|
4153
4193
|
let _themePaletteCacheTheme: unknown = null;
|
|
4154
4194
|
let _themePaletteCacheName: string | null = null;
|
|
4155
4195
|
let _themePaletteCacheFingerprint: string | null = null;
|
|
4196
|
+
// Pi passes the same global theme Proxy everywhere, so keeping the last one lets
|
|
4197
|
+
// non-render code (group invalidation) read the current palette.
|
|
4198
|
+
let _lastSeenTheme: unknown = null;
|
|
4199
|
+
|
|
4200
|
+
function currentThemePaletteFingerprint(): string | undefined {
|
|
4201
|
+
return _lastSeenTheme ? themePaletteFingerprint(_lastSeenTheme) : undefined;
|
|
4202
|
+
}
|
|
4156
4203
|
|
|
4157
4204
|
/** Resolved-color fingerprint so palette re-derives when the active theme file changes under the same name/object. */
|
|
4158
4205
|
function themePaletteFingerprint(theme: any): string {
|
|
@@ -4191,6 +4238,7 @@ const WORD_DIFF_MIN_SIM = 0.15;
|
|
|
4191
4238
|
const MAX_WRAP_ROWS_WIDE = 3;
|
|
4192
4239
|
const MAX_WRAP_ROWS_MED = 2;
|
|
4193
4240
|
const MAX_WRAP_ROWS_NARROW = 1;
|
|
4241
|
+
const FORCED_SPLIT_WRAP_ROWS = 8;
|
|
4194
4242
|
|
|
4195
4243
|
let D_RST = "\x1b[0m";
|
|
4196
4244
|
const D_BOLD = "\x1b[1m";
|
|
@@ -4567,6 +4615,7 @@ function resetThemePalette(): void {
|
|
|
4567
4615
|
|
|
4568
4616
|
function applyThemePaletteIfNeeded(theme: any): void {
|
|
4569
4617
|
if (!theme) return;
|
|
4618
|
+
_lastSeenTheme = theme;
|
|
4570
4619
|
if (!themeAdaptiveEnabled()) {
|
|
4571
4620
|
applyToolBranchColor(theme);
|
|
4572
4621
|
syncOutlineChromeFromBranch(theme);
|
|
@@ -4757,10 +4806,26 @@ function branchDiffWidth(componentWidth?: number, chromeWidth = 2): number {
|
|
|
4757
4806
|
return Math.max(20, Math.min(width - chromeWidth, MAX_TERM_WIDTH));
|
|
4758
4807
|
}
|
|
4759
4808
|
|
|
4809
|
+
// Diff preview cache keys also carry _toolBranchVisualEpoch, which applyThemePaletteIfNeeded
|
|
4810
|
+
// bumps when the theme palette changes: otherwise rows rendered before a /theme or light/dark
|
|
4811
|
+
// switch kept the old diff backgrounds and stat colors while new rows used the new ones.
|
|
4760
4812
|
function contextDiffWidth(ctx: any, chromeWidth = 2): number {
|
|
4761
4813
|
return branchDiffWidth(ctx.state?._diffComponentWidth, chromeWidth);
|
|
4762
4814
|
}
|
|
4763
4815
|
|
|
4816
|
+
// Diff previews are laid out for the row's real width, which is only known after the
|
|
4817
|
+
// row's text component has rendered once. Starting the async Shiki render before that
|
|
4818
|
+
// used the termW() guess and rendered every diff a second time once the real width
|
|
4819
|
+
// arrived (on resume: every edit/write row, twice). Wait for the width instead; the
|
|
4820
|
+
// width observer invalidates the row as soon as it is known.
|
|
4821
|
+
function diffWidthReady(ctx: any): boolean {
|
|
4822
|
+
if (typeof ctx.state?._diffComponentWidth === "number") return true;
|
|
4823
|
+
const seen = ctx.lastComponent instanceof ToolText ? ctx.lastComponent.getObservedWidth() : undefined;
|
|
4824
|
+
if (seen === undefined || !ctx.state) return false;
|
|
4825
|
+
ctx.state._diffComponentWidth = seen;
|
|
4826
|
+
return true;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4764
4829
|
function adaptiveWrapRows(tw?: number): number {
|
|
4765
4830
|
const width = tw ?? termW();
|
|
4766
4831
|
if (width >= 180) return MAX_WRAP_ROWS_WIDE;
|
|
@@ -5352,6 +5417,13 @@ async function renderSplit(
|
|
|
5352
5417
|
const nw = Math.max(2, String(maxLineNumber(diff.lines)).length);
|
|
5353
5418
|
const gw = nw + 5;
|
|
5354
5419
|
const cw = Math.max(12, half - gw);
|
|
5420
|
+
// Auto only picks split when lines fit, so the usual wrap cap rarely bites. A forced
|
|
5421
|
+
// split ("diffLayout": "split") halves the code width for every diff, and that cap
|
|
5422
|
+
// (one row below 120 columns) cut long lines off instead of wrapping them. Wrap
|
|
5423
|
+
// forced-split lines in full, with a cap only against minified one-liners.
|
|
5424
|
+
const splitWrapRows = diffLayout() === "split"
|
|
5425
|
+
? Math.max(adaptiveWrapRows(), FORCED_SPLIT_WRAP_ROWS)
|
|
5426
|
+
: adaptiveWrapRows();
|
|
5355
5427
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length * 2 <= MAX_RENDER_LINES * 2;
|
|
5356
5428
|
|
|
5357
5429
|
const leftSrc: string[] = [];
|
|
@@ -5401,7 +5473,7 @@ async function renderSplit(
|
|
|
5401
5473
|
else body = `${BG_BASE}${D_DIM}${hl}`;
|
|
5402
5474
|
const gutter = `${border}${gBg}${lnum(num, nw, numFg)}${sFg}${D_BOLD}${sign} ${D_RST}${FG_RULE}│${D_RST} `;
|
|
5403
5475
|
const contGutter = `${border}${gBg}${" ".repeat(nw + 2)}${D_RST}${FG_RULE}│${D_RST} `;
|
|
5404
|
-
return { gutter, contGutter, bodyRows: wrapAnsi(tabs(body), cw,
|
|
5476
|
+
return { gutter, contGutter, bodyRows: wrapAnsi(tabs(body), cw, splitWrapRows, cBg) };
|
|
5405
5477
|
}
|
|
5406
5478
|
|
|
5407
5479
|
const out: string[] = [];
|
|
@@ -6013,6 +6085,18 @@ function isMcpToolName(name: string): boolean {
|
|
|
6013
6085
|
return name === "mcp" || /^mcp[_:-]/i.test(name) || /[_:-]mcp[_:-]/i.test(name);
|
|
6014
6086
|
}
|
|
6015
6087
|
|
|
6088
|
+
// Direct MCP tools are registered as `mcp__<server>__<tool>`; their args are the
|
|
6089
|
+
// tool's own args, not the gateway's `{ tool, server, ... }`.
|
|
6090
|
+
function parseNamespacedMcpToolName(name: string): { server: string; tool: string } | undefined {
|
|
6091
|
+
const match = /^mcp__(.+?)__(.+)$/.exec(name);
|
|
6092
|
+
return match ? { server: match[1], tool: match[2] } : undefined;
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6095
|
+
function toolDisplayName(name: string): string {
|
|
6096
|
+
const direct = parseNamespacedMcpToolName(name);
|
|
6097
|
+
return humanizeToolName(direct ? direct.tool : name);
|
|
6098
|
+
}
|
|
6099
|
+
|
|
6016
6100
|
function shouldUseGenericToolRenderer(name: unknown): boolean {
|
|
6017
6101
|
return typeof name === "string" && name.length > 0 && !CORE_TOOL_OVERRIDES.has(name);
|
|
6018
6102
|
}
|
|
@@ -6519,8 +6603,11 @@ function renderApplyPatchCall(args: any, theme: Theme, ctx: any, sp: (path: stri
|
|
|
6519
6603
|
}
|
|
6520
6604
|
ctx.state._openAiPatchFiles = preview.changes.map((change) => change.displayPath);
|
|
6521
6605
|
|
|
6606
|
+
if (!diffWidthReady(ctx)) {
|
|
6607
|
+
return makeResponsiveDiffText(ctx, ctx.lastComponent, `${hdr}\n${withBranch(theme.fg("muted", "(rendering…)"), theme, false, true)}`);
|
|
6608
|
+
}
|
|
6522
6609
|
const diffWidth = contextDiffWidth(ctx);
|
|
6523
|
-
const key = `apply-preview:${ctx.state._applyPatchMetaKey ?? hashText(patchText)}:${diffWidth}:${ctx.expanded ? 1 : 0}`;
|
|
6610
|
+
const key = `apply-preview:${ctx.state._applyPatchMetaKey ?? hashText(patchText)}:${diffWidth}:${ctx.expanded ? 1 : 0}:${_toolBranchVisualEpoch}`;
|
|
6524
6611
|
if (ctx.state._applyPatchPreviewKey !== key) {
|
|
6525
6612
|
ctx.state._applyPatchPreviewKey = key;
|
|
6526
6613
|
ctx.state._applyPatchPreviewBody = theme.fg("muted", "(rendering…)");
|
|
@@ -6603,7 +6690,37 @@ function renderApplyPatchResult(result: any, isPartial: boolean, theme: Theme, c
|
|
|
6603
6690
|
return makeText(ctx.lastComponent, withBranch(`${theme.fg("success", "Applied")} ${meta.changeCount} files ${summary}${meta.totalLines ? ` ${theme.fg("muted", `(${meta.totalLines} diff lines)`)}` : ""}`, theme));
|
|
6604
6691
|
}
|
|
6605
6692
|
|
|
6606
|
-
|
|
6693
|
+
const DIRECT_MCP_SUMMARY_KEYS = [
|
|
6694
|
+
"description", "subject", "title", "query", "path", "file_path", "url", "command", "name",
|
|
6695
|
+
"agent_id", "agentId", "taskId", "task_id", "threadId", "drop", "target", "message", "prompt", "task",
|
|
6696
|
+
] as const;
|
|
6697
|
+
|
|
6698
|
+
function summarizeArgValue(value: unknown): string {
|
|
6699
|
+
if (typeof value === "string") return value.trim();
|
|
6700
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
6701
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string" || typeof item === "number")) return value.join(", ");
|
|
6702
|
+
return "";
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6705
|
+
function summarizeDirectMcpArgs(args: any): string {
|
|
6706
|
+
if (!args || typeof args !== "object") return "";
|
|
6707
|
+
for (const key of DIRECT_MCP_SUMMARY_KEYS) {
|
|
6708
|
+
const value = summarizeArgValue(args[key]);
|
|
6709
|
+
if (value) return summarizeText(value, 72);
|
|
6710
|
+
}
|
|
6711
|
+
for (const [key, raw] of Object.entries(args)) {
|
|
6712
|
+
const value = summarizeArgValue(raw);
|
|
6713
|
+
if (value) return summarizeText(`${key}: ${value}`, 72);
|
|
6714
|
+
}
|
|
6715
|
+
return "";
|
|
6716
|
+
}
|
|
6717
|
+
|
|
6718
|
+
function summarizeMcpToolCall(name: string, args: any, theme: Theme): string {
|
|
6719
|
+
const direct = parseNamespacedMcpToolName(name);
|
|
6720
|
+
if (direct) {
|
|
6721
|
+
const detail = summarizeDirectMcpArgs(args);
|
|
6722
|
+
return `${direct.server}:${direct.tool}${detail ? ` ${theme.fg("muted", detail)}` : ""}`;
|
|
6723
|
+
}
|
|
6607
6724
|
const tool = getStringArg(args, "tool");
|
|
6608
6725
|
if (tool) return args?.server ? `${args.server}:${tool}` : tool;
|
|
6609
6726
|
const connect = getStringArg(args, "connect");
|
|
@@ -6614,14 +6731,14 @@ function summarizeMcpToolCall(args: any, theme: Theme): string {
|
|
|
6614
6731
|
}
|
|
6615
6732
|
|
|
6616
6733
|
function summarizeGenericToolCall(name: string, args: any, theme: Theme, sp: (path: string) => string): string {
|
|
6617
|
-
if (isMcpToolName(name)) return summarizeMcpToolCall(args, theme);
|
|
6734
|
+
if (isMcpToolName(name)) return summarizeMcpToolCall(name, args, theme);
|
|
6618
6735
|
return summarizeOpenAiToolCall(name, args, theme, sp);
|
|
6619
6736
|
}
|
|
6620
6737
|
|
|
6621
6738
|
function renderMcpToolResult(result: any, expanded: boolean, isPartial: boolean, theme: Theme, ctx: any): Text {
|
|
6622
6739
|
if (isPartial) {
|
|
6623
6740
|
return makeText(ctx.lastComponent, runningPreviewBlock(result, theme.fg("dim", "MCP running..."), expanded, theme, ctx, {
|
|
6624
|
-
styleLine: (line) => theme.fg("
|
|
6741
|
+
styleLine: (line) => theme.fg("dim", line || " "),
|
|
6625
6742
|
}));
|
|
6626
6743
|
}
|
|
6627
6744
|
clearBlinkTimer(ctx);
|
|
@@ -6645,7 +6762,7 @@ function renderMcpToolResult(result: any, expanded: boolean, isPartial: boolean,
|
|
|
6645
6762
|
theme,
|
|
6646
6763
|
previewLimit(),
|
|
6647
6764
|
lines.length,
|
|
6648
|
-
(line) => theme.fg(ctx.isError ? "error" : "
|
|
6765
|
+
(line) => theme.fg(ctx.isError ? "error" : "dim", line || " "),
|
|
6649
6766
|
);
|
|
6650
6767
|
return makeText(ctx.lastComponent, withBranch(`${statusText}\n${preview}`, theme));
|
|
6651
6768
|
}
|
|
@@ -7705,10 +7822,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
7705
7822
|
if (d?._type === "diff") {
|
|
7706
7823
|
const previewLines = ctx.expanded ? MAX_RENDER_LINES : diffCollapsedLimit();
|
|
7707
7824
|
const hunks = d.diff?.lines?.filter((l: any) => l.type === "sep").length + (d.diff?.lines?.length ? 1 : 0);
|
|
7825
|
+
if (!diffWidthReady(ctx)) {
|
|
7826
|
+
const pendingSummary = diffSummaryWithMeta(d.diff.added, d.diff.removed, hunks, "");
|
|
7827
|
+
return makeResponsiveDiffText(ctx, ctx.lastComponent, withFinalBranchBlock(`${pendingSummary}\n${theme.fg("muted", "rendering diff…")}`, theme));
|
|
7828
|
+
}
|
|
7708
7829
|
const diffWidth = contextDiffWidth(ctx);
|
|
7709
7830
|
const mode = shouldUseSplit(d.diff, diffWidth, previewLines) ? "split" : "unified";
|
|
7710
7831
|
const richSummary = diffSummaryWithMeta(d.diff.added, d.diff.removed, hunks, mode);
|
|
7711
|
-
const key = `wd:${diffWidth}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}:${ctx.expanded ? 1 : 0}`;
|
|
7832
|
+
const key = `wd:${diffWidth}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}:${ctx.expanded ? 1 : 0}:${_toolBranchVisualEpoch}`;
|
|
7712
7833
|
if (ctx.state._wdk !== key) {
|
|
7713
7834
|
ctx.state._wdk = key;
|
|
7714
7835
|
ctx.state._wdt = withFinalBranchBlock(`${richSummary}\n${theme.fg("muted", "rendering diff…")}`, theme);
|
|
@@ -7735,8 +7856,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
7735
7856
|
const syntheticDiff = getCachedParsedDiff(ctx, `nf-diff:${d.filePath}:${contentHash}`, "", content);
|
|
7736
7857
|
const richSummary = diffSummaryWithMeta(syntheticDiff.added, 0, 1, "new file");
|
|
7737
7858
|
const previewLines = ctx.expanded ? MAX_RENDER_LINES : diffCollapsedLimit();
|
|
7859
|
+
if (!diffWidthReady(ctx)) {
|
|
7860
|
+
return makeResponsiveDiffText(ctx, ctx.lastComponent, withFinalBranchBlock(`${richSummary}\n${theme.fg("muted", "rendering diff…")}`, theme));
|
|
7861
|
+
}
|
|
7738
7862
|
const diffWidth = contextDiffWidth(ctx);
|
|
7739
|
-
const pk = `nf:${d.filePath}:${contentHash}:${diffWidth}:${ctx.expanded ? 1 : 0}`;
|
|
7863
|
+
const pk = `nf:${d.filePath}:${contentHash}:${diffWidth}:${ctx.expanded ? 1 : 0}:${_toolBranchVisualEpoch}`;
|
|
7740
7864
|
if (ctx.state._nfk !== pk) {
|
|
7741
7865
|
ctx.state._nfk = pk;
|
|
7742
7866
|
ctx.state._nft = withFinalBranchBlock(`${richSummary}\n${theme.fg("muted", "rendering diff…")}`, theme);
|
|
@@ -7824,8 +7948,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
7824
7948
|
// History replay never sets argsComplete; a finished edit is drawn from its result instead.
|
|
7825
7949
|
const finished = ctx.state._editResult as Record<string, unknown> | undefined;
|
|
7826
7950
|
if (!((ctx.argsComplete || finished) && operations.length > 0)) return makeText(ctx.lastComponent, hdr);
|
|
7951
|
+
if (!diffWidthReady(ctx)) {
|
|
7952
|
+
const pending = liveBranchDisplay(ctx.state, theme) ?? indentBranchBlock(withBranch(theme.fg("muted", "(rendering…)"), theme, false, true));
|
|
7953
|
+
return makeResponsiveDiffText(ctx, ctx.lastComponent, `${hdr}\n${pending}`);
|
|
7954
|
+
}
|
|
7827
7955
|
const diffWidth = contextDiffWidth(ctx, 3);
|
|
7828
|
-
const key = `edit:${fp}:${hashText(operations.map((edit) => `${edit.oldText}\u0000${edit.newText}`).join("\u0001"))}:${diffWidth}:${ctx.expanded ? 1 : 0}`;
|
|
7956
|
+
const key = `edit:${fp}:${hashText(operations.map((edit) => `${edit.oldText}\u0000${edit.newText}`).join("\u0001"))}:${diffWidth}:${ctx.expanded ? 1 : 0}:${_toolBranchVisualEpoch}`;
|
|
7829
7957
|
const { diffs: fallbackDiffs, summary: editSummary } = getCachedEditOperationSummary(ctx, key, operations);
|
|
7830
7958
|
if (ctx.state._pk !== key) {
|
|
7831
7959
|
ctx.state._pk = key;
|
package/extensions/spinner.ts
CHANGED
|
@@ -73,7 +73,6 @@ function themeAdaptiveEnabled(): boolean {
|
|
|
73
73
|
const _DEFAULT_CLAUDE_ORANGE = "\x1b[38;2;215;119;87m";
|
|
74
74
|
const _DEFAULT_STATUS_DIM = "\x1b[38;2;153;153;153m";
|
|
75
75
|
|
|
76
|
-
let _themeColorsCacheTheme: unknown = null;
|
|
77
76
|
let _themeColorsLastAdaptive: boolean | null = null;
|
|
78
77
|
let _themeColorsLastVerbKey: string | null = null;
|
|
79
78
|
let _themeColorsLastStatusKey: string | null = null;
|
|
@@ -105,7 +104,6 @@ function applyThemeColors(theme: any): void {
|
|
|
105
104
|
_themeColorsLastAdaptive = adaptive;
|
|
106
105
|
_themeColorsLastVerbKey = verbColor;
|
|
107
106
|
_themeColorsLastStatusKey = statusColor;
|
|
108
|
-
_themeColorsCacheTheme = null;
|
|
109
107
|
if (!adaptive) {
|
|
110
108
|
CLAUDE_ORANGE = _DEFAULT_CLAUDE_ORANGE;
|
|
111
109
|
STATUS_DIM = _DEFAULT_STATUS_DIM;
|
|
@@ -113,9 +111,9 @@ function applyThemeColors(theme: any): void {
|
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
if (!theme || !adaptive) return;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
// ctx.ui.theme is Pi's global theme Proxy: the same object before and after
|
|
115
|
+
// /theme or a light/dark switch, so an identity cache never refreshed the
|
|
116
|
+
// spinner colors. Resolving is two map lookups per tick; just do it.
|
|
119
117
|
const verb = resolveThemeColor(theme, verbColor, "accent");
|
|
120
118
|
if (verb) CLAUDE_ORANGE = verb;
|
|
121
119
|
const status = resolveThemeColor(theme, statusColor, "muted");
|
|
@@ -792,8 +790,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
792
790
|
}
|
|
793
791
|
// Re-derive colors on every tick so /cc-spinner verb/status changes
|
|
794
792
|
// take effect within ~250 ms without waiting for the next pi event.
|
|
795
|
-
// applyThemeColors
|
|
796
|
-
// this is cheap when nothing changed.
|
|
793
|
+
// applyThemeColors only re-resolves two theme colors, so this is cheap.
|
|
797
794
|
applyThemeColors(activeCtx.ui?.theme);
|
|
798
795
|
const nextMessage = buildWorkingMessage();
|
|
799
796
|
if (!force && nextMessage === lastWorkingMessage) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-claude-code-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.90",
|
|
4
4
|
"description": "Claude Code-style grouped tool rows for pi with Ctrl+O/Ctrl+Shift+O previews, math rendering, and consistent built-in, MCP, and custom tool rendering",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|