更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)

* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files)

纯格式化:移除分号、React Compiler import、import 多行展开。
修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-2): 格式化 commands (79 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-5): 格式化 components其余 + hooks + tools (232 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md

- README.md: 大幅重写,更详细版本历史和配置示例
- Run.ps1: 新增 Windows 启动脚本
- TODO.md: 新增包完成清单
- V6.md: 删除(架构重构规划已不适用)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复以前的问题

* fix: 修复 login 面板的问题

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-04 23:24:27 +08:00
committed by GitHub
parent 02694918b5
commit 5b1a52b8e0
559 changed files with 103807 additions and 101817 deletions

View File

@@ -1,60 +1,39 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { Text, useTheme } from '../../ink.js';
import { getTheme, type Theme } from '../../utils/theme.js';
import { interpolateColor, parseRGB, toRGBColor } from './utils.js';
import * as React from 'react'
import { Text, useTheme } from '../../ink.js'
import { getTheme, type Theme } from '../../utils/theme.js'
import { interpolateColor, parseRGB, toRGBColor } from './utils.js'
type Props = {
char: string;
flashOpacity: number;
messageColor: keyof Theme;
shimmerColor: keyof Theme;
};
export function FlashingChar(t0) {
const $ = _c(9);
const {
char,
flashOpacity,
messageColor,
shimmerColor
} = t0;
const [themeName] = useTheme();
let t1;
if ($[0] !== char || $[1] !== flashOpacity || $[2] !== messageColor || $[3] !== shimmerColor || $[4] !== themeName) {
t1 = Symbol.for("react.early_return_sentinel");
bb0: {
const theme = getTheme(themeName);
const baseColorStr = theme[messageColor];
const shimmerColorStr = theme[shimmerColor];
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null;
const shimmerRGB = shimmerColorStr ? parseRGB(shimmerColorStr) : null;
if (baseRGB && shimmerRGB) {
const interpolated = interpolateColor(baseRGB, shimmerRGB, flashOpacity);
t1 = <Text color={toRGBColor(interpolated)}>{char}</Text>;
break bb0;
}
}
$[0] = char;
$[1] = flashOpacity;
$[2] = messageColor;
$[3] = shimmerColor;
$[4] = themeName;
$[5] = t1;
} else {
t1 = $[5];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
const shouldUseShimmer = flashOpacity > 0.5;
const t2 = shouldUseShimmer ? shimmerColor : messageColor;
let t3;
if ($[6] !== char || $[7] !== t2) {
t3 = <Text color={t2}>{char}</Text>;
$[6] = char;
$[7] = t2;
$[8] = t3;
} else {
t3 = $[8];
}
return t3;
char: string
flashOpacity: number
messageColor: keyof Theme
shimmerColor: keyof Theme
}
export function FlashingChar({
char,
flashOpacity,
messageColor,
shimmerColor,
}: Props): React.ReactNode {
const [themeName] = useTheme()
const theme = getTheme(themeName)
const baseColorStr = theme[messageColor]
const shimmerColorStr = theme[shimmerColor]
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null
const shimmerRGB = shimmerColorStr ? parseRGB(shimmerColorStr) : null
if (baseRGB && shimmerRGB) {
// Smooth interpolation between colors
const interpolated = interpolateColor(baseRGB, shimmerRGB, flashOpacity)
return <Text color={toRGBColor(interpolated)}>{char}</Text>
}
// Fallback for ANSI themes: binary switch
const shouldUseShimmer = flashOpacity > 0.5
return (
<Text color={shouldUseShimmer ? shimmerColor : messageColor}>{char}</Text>
)
}

View File

@@ -1,327 +1,142 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { stringWidth } from '../../ink/stringWidth.js';
import { Text, useTheme } from '../../ink.js';
import { getGraphemeSegmenter } from '../../utils/intl.js';
import { getTheme, type Theme } from '../../utils/theme.js';
import type { SpinnerMode } from './types.js';
import { interpolateColor, parseRGB, toRGBColor } from './utils.js';
import * as React from 'react'
import { stringWidth } from '../../ink/stringWidth.js'
import { Text, useTheme } from '../../ink.js'
import { getGraphemeSegmenter } from '../../utils/intl.js'
import { getTheme, type Theme } from '../../utils/theme.js'
import type { SpinnerMode } from './types.js'
import { interpolateColor, parseRGB, toRGBColor } from './utils.js'
type Props = {
message: string;
mode: SpinnerMode;
messageColor: keyof Theme;
glimmerIndex: number;
flashOpacity: number;
shimmerColor: keyof Theme;
stalledIntensity?: number;
};
const ERROR_RED = {
r: 171,
g: 43,
b: 63
};
export function GlimmerMessage(t0) {
const $ = _c(75);
const {
message,
mode,
messageColor,
glimmerIndex,
flashOpacity,
shimmerColor,
stalledIntensity: t1
} = t0;
const stalledIntensity = t1 === undefined ? 0 : t1;
const [themeName] = useTheme();
let messageWidth;
let segments;
let t2;
if ($[0] !== flashOpacity || $[1] !== message || $[2] !== messageColor || $[3] !== mode || $[4] !== shimmerColor || $[5] !== stalledIntensity || $[6] !== themeName) {
t2 = Symbol.for("react.early_return_sentinel");
bb0: {
const theme = getTheme(themeName);
let segs;
if ($[10] !== message) {
segs = [];
for (const {
segment
} of getGraphemeSegmenter().segment(message)) {
segs.push({
segment,
width: stringWidth(segment)
});
}
$[10] = message;
$[11] = segs;
} else {
segs = $[11];
}
let t3;
if ($[12] !== message) {
t3 = stringWidth(message);
$[12] = message;
$[13] = t3;
} else {
t3 = $[13];
}
let t4;
if ($[14] !== segs || $[15] !== t3) {
t4 = {
segments: segs,
messageWidth: t3
};
$[14] = segs;
$[15] = t3;
$[16] = t4;
} else {
t4 = $[16];
}
({
segments,
messageWidth
} = t4);
if (!message) {
t2 = null;
break bb0;
}
if (stalledIntensity > 0) {
const baseColorStr = theme[messageColor];
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null;
if (baseRGB) {
const interpolated = interpolateColor(baseRGB, ERROR_RED, stalledIntensity);
const color = toRGBColor(interpolated);
let t5;
if ($[17] !== color) {
t5 = <Text color={color}> </Text>;
$[17] = color;
$[18] = t5;
} else {
t5 = $[18];
}
t2 = <><Text color={color}>{message}</Text>{t5}</>;
break bb0;
}
const color_0 = stalledIntensity > 0.5 ? "error" : messageColor;
let t5;
if ($[19] !== color_0 || $[20] !== message) {
t5 = <Text color={color_0}>{message}</Text>;
$[19] = color_0;
$[20] = message;
$[21] = t5;
} else {
t5 = $[21];
}
let t6;
if ($[22] !== color_0) {
t6 = <Text color={color_0}> </Text>;
$[22] = color_0;
$[23] = t6;
} else {
t6 = $[23];
}
let t7;
if ($[24] !== t5 || $[25] !== t6) {
t7 = <>{t5}{t6}</>;
$[24] = t5;
$[25] = t6;
$[26] = t7;
} else {
t7 = $[26];
}
t2 = t7;
break bb0;
}
if (mode === "tool-use") {
const baseColorStr_0 = theme[messageColor];
const shimmerColorStr = theme[shimmerColor];
const baseRGB_0 = baseColorStr_0 ? parseRGB(baseColorStr_0) : null;
const shimmerRGB = shimmerColorStr ? parseRGB(shimmerColorStr) : null;
if (baseRGB_0 && shimmerRGB) {
const interpolated_0 = interpolateColor(baseRGB_0, shimmerRGB, flashOpacity);
const t5 = <Text color={toRGBColor(interpolated_0)}>{message}</Text>;
let t6;
if ($[27] !== messageColor) {
t6 = <Text color={messageColor}> </Text>;
$[27] = messageColor;
$[28] = t6;
} else {
t6 = $[28];
}
let t7;
if ($[29] !== t5 || $[30] !== t6) {
t7 = <>{t5}{t6}</>;
$[29] = t5;
$[30] = t6;
$[31] = t7;
} else {
t7 = $[31];
}
t2 = t7;
break bb0;
}
const color_1 = flashOpacity > 0.5 ? shimmerColor : messageColor;
let t5;
if ($[32] !== color_1 || $[33] !== message) {
t5 = <Text color={color_1}>{message}</Text>;
$[32] = color_1;
$[33] = message;
$[34] = t5;
} else {
t5 = $[34];
}
let t6;
if ($[35] !== messageColor) {
t6 = <Text color={messageColor}> </Text>;
$[35] = messageColor;
$[36] = t6;
} else {
t6 = $[36];
}
let t7;
if ($[37] !== t5 || $[38] !== t6) {
t7 = <>{t5}{t6}</>;
$[37] = t5;
$[38] = t6;
$[39] = t7;
} else {
t7 = $[39];
}
t2 = t7;
break bb0;
}
}
$[0] = flashOpacity;
$[1] = message;
$[2] = messageColor;
$[3] = mode;
$[4] = shimmerColor;
$[5] = stalledIntensity;
$[6] = themeName;
$[7] = messageWidth;
$[8] = segments;
$[9] = t2;
} else {
messageWidth = $[7];
segments = $[8];
t2 = $[9];
}
if (t2 !== Symbol.for("react.early_return_sentinel")) {
return t2;
}
const shimmerStart = glimmerIndex - 1;
const shimmerEnd = glimmerIndex + 1;
if (shimmerStart >= messageWidth || shimmerEnd < 0) {
let t3;
if ($[40] !== message || $[41] !== messageColor) {
t3 = <Text color={messageColor}>{message}</Text>;
$[40] = message;
$[41] = messageColor;
$[42] = t3;
} else {
t3 = $[42];
}
let t4;
if ($[43] !== messageColor) {
t4 = <Text color={messageColor}> </Text>;
$[43] = messageColor;
$[44] = t4;
} else {
t4 = $[44];
}
let t5;
if ($[45] !== t3 || $[46] !== t4) {
t5 = <>{t3}{t4}</>;
$[45] = t3;
$[46] = t4;
$[47] = t5;
} else {
t5 = $[47];
}
return t5;
}
const clampedStart = Math.max(0, shimmerStart);
let colPos = 0;
let before = "";
let shim = "";
let after = "";
if ($[48] !== after || $[49] !== before || $[50] !== clampedStart || $[51] !== colPos || $[52] !== segments || $[53] !== shim || $[54] !== shimmerEnd) {
for (const {
segment: segment_0,
width
} of segments) {
if (colPos + width <= clampedStart) {
before = before + segment_0;
} else {
if (colPos > shimmerEnd) {
after = after + segment_0;
} else {
shim = shim + segment_0;
}
}
colPos = colPos + width;
}
$[48] = after;
$[49] = before;
$[50] = clampedStart;
$[51] = colPos;
$[52] = segments;
$[53] = shim;
$[54] = shimmerEnd;
$[55] = before;
$[56] = after;
$[57] = shim;
$[58] = colPos;
} else {
before = $[55] as string;
after = $[56] as string;
shim = $[57] as string;
colPos = $[58] as number;
}
let t3;
if ($[59] !== before || $[60] !== messageColor) {
t3 = before && <Text color={messageColor}>{before}</Text>;
$[59] = before;
$[60] = messageColor;
$[61] = t3;
} else {
t3 = $[61];
}
let t4;
if ($[62] !== shim || $[63] !== shimmerColor) {
t4 = <Text color={shimmerColor}>{shim}</Text>;
$[62] = shim;
$[63] = shimmerColor;
$[64] = t4;
} else {
t4 = $[64];
}
let t5;
if ($[65] !== after || $[66] !== messageColor) {
t5 = after && <Text color={messageColor}>{after}</Text>;
$[65] = after;
$[66] = messageColor;
$[67] = t5;
} else {
t5 = $[67];
}
let t6;
if ($[68] !== messageColor) {
t6 = <Text color={messageColor}> </Text>;
$[68] = messageColor;
$[69] = t6;
} else {
t6 = $[69];
}
let t7;
if ($[70] !== t3 || $[71] !== t4 || $[72] !== t5 || $[73] !== t6) {
t7 = <>{t3}{t4}{t5}{t6}</>;
$[70] = t3;
$[71] = t4;
$[72] = t5;
$[73] = t6;
$[74] = t7;
} else {
t7 = $[74];
}
return t7;
message: string
mode: SpinnerMode
messageColor: keyof Theme
glimmerIndex: number
flashOpacity: number
shimmerColor: keyof Theme
stalledIntensity?: number
}
const ERROR_RED = { r: 171, g: 43, b: 63 }
export function GlimmerMessage({
message,
mode,
messageColor,
glimmerIndex,
flashOpacity,
shimmerColor,
stalledIntensity = 0,
}: Props): React.ReactNode {
const [themeName] = useTheme()
const theme = getTheme(themeName)
// This component re-renders at 20fps (glimmerIndex changes every 50ms) but
// message is stable within a turn. Precompute grapheme segmentation + widths
// once per message instead of per frame. Measured -81% on the shimmer path.
const { segments, messageWidth } = React.useMemo(() => {
const segs: { segment: string; width: number }[] = []
for (const { segment } of getGraphemeSegmenter().segment(message)) {
segs.push({ segment, width: stringWidth(segment) })
}
return { segments: segs, messageWidth: stringWidth(message) }
}, [message])
if (!message) return null
// When stalled, show text that smoothly transitions to red
if (stalledIntensity > 0) {
const baseColorStr = theme[messageColor]
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null
if (baseRGB) {
const interpolated = interpolateColor(
baseRGB,
ERROR_RED,
stalledIntensity,
)
const color = toRGBColor(interpolated)
return (
<>
<Text color={color}>{message}</Text>
<Text color={color}> </Text>
</>
)
}
// Fallback for ANSI themes: use messageColor until fully stalled, then error
const color = stalledIntensity > 0.5 ? 'error' : messageColor
return (
<>
<Text color={color}>{message}</Text>
<Text color={color}> </Text>
</>
)
}
// tool-use mode: all chars flash with the same opacity, so render as a
// single <Text> instead of N individual FlashingChar components.
if (mode === 'tool-use') {
const baseColorStr = theme[messageColor]
const shimmerColorStr = theme[shimmerColor]
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null
const shimmerRGB = shimmerColorStr ? parseRGB(shimmerColorStr) : null
if (baseRGB && shimmerRGB) {
const interpolated = interpolateColor(baseRGB, shimmerRGB, flashOpacity)
return (
<>
<Text color={toRGBColor(interpolated)}>{message}</Text>
<Text color={messageColor}> </Text>
</>
)
}
const color = flashOpacity > 0.5 ? shimmerColor : messageColor
return (
<>
<Text color={color}>{message}</Text>
<Text color={messageColor}> </Text>
</>
)
}
// Shimmer mode: only chars within ±1 of glimmerIndex need the shimmer
// color. When glimmer is offscreen, render as a single <Text>.
const shimmerStart = glimmerIndex - 1
const shimmerEnd = glimmerIndex + 1
if (shimmerStart >= messageWidth || shimmerEnd < 0) {
return (
<>
<Text color={messageColor}>{message}</Text>
<Text color={messageColor}> </Text>
</>
)
}
// Split into at most 3 segments by visual column position
const clampedStart = Math.max(0, shimmerStart)
let colPos = 0
let before = ''
let shim = ''
let after = ''
for (const { segment, width } of segments) {
if (colPos + width <= clampedStart) {
before += segment
} else if (colPos > shimmerEnd) {
after += segment
} else {
shim += segment
}
colPos += width
}
return (
<>
{before && <Text color={messageColor}>{before}</Text>}
<Text color={shimmerColor}>{shim}</Text>
{after && <Text color={messageColor}>{after}</Text>}
<Text color={messageColor}> </Text>
</>
)
}

View File

@@ -1,35 +1,27 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { Text } from '../../ink.js';
import type { Theme } from '../../utils/theme.js';
import * as React from 'react'
import { Text } from '../../ink.js'
import type { Theme } from '../../utils/theme.js'
type Props = {
char: string;
index: number;
glimmerIndex: number;
messageColor: keyof Theme;
shimmerColor: keyof Theme;
};
export function ShimmerChar(t0) {
const $ = _c(3);
const {
char,
index,
glimmerIndex,
messageColor,
shimmerColor
} = t0;
const isHighlighted = index === glimmerIndex;
const isNearHighlight = Math.abs(index - glimmerIndex) === 1;
const shouldUseShimmer = isHighlighted || isNearHighlight;
const t1 = shouldUseShimmer ? shimmerColor : messageColor;
let t2;
if ($[0] !== char || $[1] !== t1) {
t2 = <Text color={t1}>{char}</Text>;
$[0] = char;
$[1] = t1;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
char: string
index: number
glimmerIndex: number
messageColor: keyof Theme
shimmerColor: keyof Theme
}
export function ShimmerChar({
char,
index,
glimmerIndex,
messageColor,
shimmerColor,
}: Props): React.ReactNode {
const isHighlighted = index === glimmerIndex
const isNearHighlight = Math.abs(index - glimmerIndex) === 1
const shouldUseShimmer = isHighlighted || isNearHighlight
return (
<Text color={shouldUseShimmer ? shimmerColor : messageColor}>{char}</Text>
)
}

View File

@@ -1,72 +1,66 @@
import { c as _c } from "react/compiler-runtime";
import figures from 'figures';
import * as React from 'react';
import { useMemo, useRef } from 'react';
import { stringWidth } from '../../ink/stringWidth.js';
import { Box, Text, useAnimationFrame } from '../../ink.js';
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js';
import { formatDuration, formatNumber } from '../../utils/format.js';
import { toInkColor } from '../../utils/ink.js';
import type { Theme } from '../../utils/theme.js';
import { Byline } from '../design-system/Byline.js';
import { GlimmerMessage } from './GlimmerMessage.js';
import { SpinnerGlyph } from './SpinnerGlyph.js';
import type { SpinnerMode } from './types.js';
import { useStalledAnimation } from './useStalledAnimation.js';
import { interpolateColor, toRGBColor } from './utils.js';
const SEP_WIDTH = stringWidth(' · ');
const THINKING_BARE_WIDTH = stringWidth('thinking');
const SHOW_TOKENS_AFTER_MS = 30_000;
import figures from 'figures'
import * as React from 'react'
import { useMemo, useRef } from 'react'
import { stringWidth } from '../../ink/stringWidth.js'
import { Box, Text, useAnimationFrame } from '../../ink.js'
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js'
import { formatDuration, formatNumber } from '../../utils/format.js'
import { toInkColor } from '../../utils/ink.js'
import type { Theme } from '../../utils/theme.js'
import { Byline } from '../design-system/Byline.js'
import { GlimmerMessage } from './GlimmerMessage.js'
import { SpinnerGlyph } from './SpinnerGlyph.js'
import type { SpinnerMode } from './types.js'
import { useStalledAnimation } from './useStalledAnimation.js'
import { interpolateColor, toRGBColor } from './utils.js'
const SEP_WIDTH = stringWidth(' · ')
const THINKING_BARE_WIDTH = stringWidth('thinking')
const SHOW_TOKENS_AFTER_MS = 30_000
// Thinking shimmer constants. Previously lived in a separate ThinkingShimmerText
// component with its own useAnimationFrame(50) — inlined here to reuse our
// existing 50ms clock and eliminate the redundant subscriber.
const THINKING_INACTIVE = {
r: 153,
g: 153,
b: 153
};
const THINKING_INACTIVE_SHIMMER = {
r: 185,
g: 185,
b: 185
};
const THINKING_DELAY_MS = 3000;
const THINKING_GLOW_PERIOD_S = 2;
const THINKING_INACTIVE = { r: 153, g: 153, b: 153 }
const THINKING_INACTIVE_SHIMMER = { r: 185, g: 185, b: 185 }
const THINKING_DELAY_MS = 3000
const THINKING_GLOW_PERIOD_S = 2
export type SpinnerAnimationRowProps = {
// Animation inputs
mode: SpinnerMode;
reducedMotion: boolean;
hasActiveTools: boolean;
responseLengthRef: React.RefObject<number>;
mode: SpinnerMode
reducedMotion: boolean
hasActiveTools: boolean
responseLengthRef: React.RefObject<number>
// Message (stable within a turn)
message: string;
messageColor: keyof Theme;
shimmerColor: keyof Theme;
overrideColor?: keyof Theme | null;
message: string
messageColor: keyof Theme
shimmerColor: keyof Theme
overrideColor?: keyof Theme | null
// Timer refs (stable references)
loadingStartTimeRef: React.RefObject<number>;
totalPausedMsRef: React.RefObject<number>;
pauseStartTimeRef: React.RefObject<number | null>;
loadingStartTimeRef: React.RefObject<number>
totalPausedMsRef: React.RefObject<number>
pauseStartTimeRef: React.RefObject<number | null>
// Display flags
spinnerSuffix?: string | null;
verbose: boolean;
columns: number;
spinnerSuffix?: string | null
verbose: boolean
columns: number
// Teammate-derived (computed by parent from tasks)
hasRunningTeammates: boolean;
teammateTokens: number;
foregroundedTeammate: InProcessTeammateTaskState | undefined;
hasRunningTeammates: boolean
teammateTokens: number
foregroundedTeammate: InProcessTeammateTaskState | undefined
/** Leader's turn has completed. Suppresses stall-red since responseLengthRef/hasActiveTools track leader state only. */
leaderIsIdle?: boolean;
leaderIsIdle?: boolean
// Thinking (state owned by parent, mode-dependent)
thinkingStatus: 'thinking' | number | null;
effortSuffix: string;
};
thinkingStatus: 'thinking' | number | null
effortSuffix: string
}
/**
* The 50ms-animated portion of SpinnerWithVerb. Owns useAnimationFrame(50)
@@ -98,167 +92,282 @@ export function SpinnerAnimationRow({
foregroundedTeammate,
leaderIsIdle = false,
thinkingStatus,
effortSuffix
effortSuffix,
}: SpinnerAnimationRowProps): React.ReactNode {
const [viewportRef, time] = useAnimationFrame(reducedMotion ? null : 50);
const [viewportRef, time] = useAnimationFrame(reducedMotion ? null : 50)
// === Elapsed time (wall-clock, derived from refs each frame) ===
const now = Date.now();
const elapsedTimeMs = pauseStartTimeRef.current !== null ? pauseStartTimeRef.current - loadingStartTimeRef.current - totalPausedMsRef.current : now - loadingStartTimeRef.current - totalPausedMsRef.current;
const now = Date.now()
const elapsedTimeMs =
pauseStartTimeRef.current !== null
? pauseStartTimeRef.current -
loadingStartTimeRef.current -
totalPausedMsRef.current
: now - loadingStartTimeRef.current - totalPausedMsRef.current
// Track wall-clock turn start for teammates. While a swarm is running the
// leader's elapsedTimeMs may jump around (new API calls reset
// loadingStartTimeRef; pauses freeze it), so we anchor to the earliest
// derived start seen so far. When no teammates are running this just tracks
// derivedStart every frame, effectively resetting for the next swarm.
const derivedStart = now - elapsedTimeMs;
const turnStartRef = useRef(derivedStart);
const derivedStart = now - elapsedTimeMs
const turnStartRef = useRef(derivedStart)
if (!hasRunningTeammates || derivedStart < turnStartRef.current) {
turnStartRef.current = derivedStart;
turnStartRef.current = derivedStart
}
// === Animation derivations from `time` ===
const currentResponseLength = responseLengthRef.current;
const currentResponseLength = responseLengthRef.current
// Suppress stall detection when leader is idle — responseLengthRef and
// hasActiveTools both track leader state. When viewing an active teammate
// while leader is idle, they'd otherwise flag a false stall after 3s.
// Treating leaderIsIdle like hasActiveTools resets the stall timer.
const {
isStalled,
stalledIntensity
} = useStalledAnimation(time, currentResponseLength, hasActiveTools || leaderIsIdle, reducedMotion);
const frame = reducedMotion ? 0 : Math.floor(time / 120);
const glimmerSpeed = mode === 'requesting' ? 50 : 200;
const { isStalled, stalledIntensity } = useStalledAnimation(
time,
currentResponseLength,
hasActiveTools || leaderIsIdle,
reducedMotion,
)
const frame = reducedMotion ? 0 : Math.floor(time / 120)
const glimmerSpeed = mode === 'requesting' ? 50 : 200
// message is stable within a turn; stringWidth is expensive enough (Bun native
// call per code point) to memoize explicitly across the 50ms loop.
const glimmerMessageWidth = useMemo(() => stringWidth(message), [message]);
const cycleLength = glimmerMessageWidth + 20;
const cyclePosition = Math.floor(time / glimmerSpeed);
const glimmerIndex = reducedMotion ? -100 : isStalled ? -100 : mode === 'requesting' ? cyclePosition % cycleLength - 10 : glimmerMessageWidth + 10 - cyclePosition % cycleLength;
const flashOpacity = reducedMotion ? 0 : mode === 'tool-use' ? (Math.sin(time / 1000 * Math.PI) + 1) / 2 : 0;
const glimmerMessageWidth = useMemo(() => stringWidth(message), [message])
const cycleLength = glimmerMessageWidth + 20
const cyclePosition = Math.floor(time / glimmerSpeed)
const glimmerIndex = reducedMotion
? -100
: isStalled
? -100
: mode === 'requesting'
? (cyclePosition % cycleLength) - 10
: glimmerMessageWidth + 10 - (cyclePosition % cycleLength)
const flashOpacity = reducedMotion
? 0
: mode === 'tool-use'
? (Math.sin((time / 1000) * Math.PI) + 1) / 2
: 0
// === Token counter animation (smooth increment, driven by 50ms clock) ===
const tokenCounterRef = useRef(currentResponseLength);
const tokenCounterRef = useRef(currentResponseLength)
if (reducedMotion) {
tokenCounterRef.current = currentResponseLength;
tokenCounterRef.current = currentResponseLength
} else {
const gap = currentResponseLength - tokenCounterRef.current;
const gap = currentResponseLength - tokenCounterRef.current
if (gap > 0) {
let increment;
let increment
if (gap < 70) {
increment = 3;
increment = 3
} else if (gap < 200) {
increment = Math.max(8, Math.ceil(gap * 0.15));
increment = Math.max(8, Math.ceil(gap * 0.15))
} else {
increment = 50;
increment = 50
}
tokenCounterRef.current = Math.min(tokenCounterRef.current + increment, currentResponseLength);
tokenCounterRef.current = Math.min(
tokenCounterRef.current + increment,
currentResponseLength,
)
}
}
const displayedResponseLength = tokenCounterRef.current;
const leaderTokens = Math.round(displayedResponseLength / 4);
const effectiveElapsedMs = hasRunningTeammates ? Math.max(elapsedTimeMs, now - turnStartRef.current) : elapsedTimeMs;
const timerText = formatDuration(effectiveElapsedMs);
const timerWidth = stringWidth(timerText);
const displayedResponseLength = tokenCounterRef.current
const leaderTokens = Math.round(displayedResponseLength / 4)
const effectiveElapsedMs = hasRunningTeammates
? Math.max(elapsedTimeMs, now - turnStartRef.current)
: elapsedTimeMs
const timerText = formatDuration(effectiveElapsedMs)
const timerWidth = stringWidth(timerText)
// === Token count (leader + teammates, or foregrounded teammate) ===
const totalTokens = foregroundedTeammate && !foregroundedTeammate.isIdle ? foregroundedTeammate.progress?.tokenCount ?? 0 : leaderTokens + teammateTokens;
const tokenCount = formatNumber(totalTokens);
const tokensText = hasRunningTeammates ? `${tokenCount} tokens` : `${figures.arrowDown} ${tokenCount} tokens`;
const tokensWidth = stringWidth(tokensText);
const totalTokens =
foregroundedTeammate && !foregroundedTeammate.isIdle
? (foregroundedTeammate.progress?.tokenCount ?? 0)
: leaderTokens + teammateTokens
const tokenCount = formatNumber(totalTokens)
const tokensText = hasRunningTeammates
? `${tokenCount} tokens`
: `${figures.arrowDown} ${tokenCount} tokens`
const tokensWidth = stringWidth(tokensText)
// === Thinking text (may shrink to fit) ===
let thinkingText = thinkingStatus === 'thinking' ? `thinking${effortSuffix}` : typeof thinkingStatus === 'number' ? `thought for ${Math.max(1, Math.round(thinkingStatus / 1000))}s` : null;
let thinkingWidthValue = thinkingText ? stringWidth(thinkingText) : 0;
let thinkingText =
thinkingStatus === 'thinking'
? `thinking${effortSuffix}`
: typeof thinkingStatus === 'number'
? `thought for ${Math.max(1, Math.round(thinkingStatus / 1000))}s`
: null
let thinkingWidthValue = thinkingText ? stringWidth(thinkingText) : 0
// === Progressive width gating ===
const messageWidth = glimmerMessageWidth + 2;
const sep = SEP_WIDTH;
const wantsThinking = thinkingStatus !== null;
const wantsTimerAndTokens = verbose || hasRunningTeammates || effectiveElapsedMs > SHOW_TOKENS_AFTER_MS;
const availableSpace = columns - messageWidth - 5;
let showThinking = wantsThinking && availableSpace > thinkingWidthValue;
if (!showThinking && wantsThinking && thinkingStatus === 'thinking' && effortSuffix) {
const messageWidth = glimmerMessageWidth + 2
const sep = SEP_WIDTH
const wantsThinking = thinkingStatus !== null
const wantsTimerAndTokens =
verbose || hasRunningTeammates || effectiveElapsedMs > SHOW_TOKENS_AFTER_MS
const availableSpace = columns - messageWidth - 5
let showThinking = wantsThinking && availableSpace > thinkingWidthValue
if (
!showThinking &&
wantsThinking &&
thinkingStatus === 'thinking' &&
effortSuffix
) {
if (availableSpace > THINKING_BARE_WIDTH) {
thinkingText = 'thinking';
thinkingWidthValue = THINKING_BARE_WIDTH;
showThinking = true;
thinkingText = 'thinking'
thinkingWidthValue = THINKING_BARE_WIDTH
showThinking = true
}
}
const usedAfterThinking = showThinking ? thinkingWidthValue + sep : 0;
const showTimer = wantsTimerAndTokens && availableSpace > usedAfterThinking + timerWidth;
const usedAfterTimer = usedAfterThinking + (showTimer ? timerWidth + sep : 0);
const showTokens = wantsTimerAndTokens && totalTokens > 0 && availableSpace > usedAfterTimer + tokensWidth;
const thinkingOnly = showThinking && thinkingStatus === 'thinking' && !spinnerSuffix && !showTimer && !showTokens && true;
const usedAfterThinking = showThinking ? thinkingWidthValue + sep : 0
const showTimer =
wantsTimerAndTokens && availableSpace > usedAfterThinking + timerWidth
const usedAfterTimer = usedAfterThinking + (showTimer ? timerWidth + sep : 0)
const showTokens =
wantsTimerAndTokens &&
totalTokens > 0 &&
availableSpace > usedAfterTimer + tokensWidth
const thinkingOnly =
showThinking &&
thinkingStatus === 'thinking' &&
!spinnerSuffix &&
!showTimer &&
!showTokens &&
true
// === Thinking shimmer color (formerly ThinkingShimmerText's own timer) ===
// Same sine-wave opacity, but derived from our shared `time` instead of a
// second useAnimationFrame(50) subscription.
const thinkingElapsedSec = (time - THINKING_DELAY_MS) / 1000;
const thinkingOpacity = time < THINKING_DELAY_MS ? 0 : (Math.sin(thinkingElapsedSec * Math.PI * 2 / THINKING_GLOW_PERIOD_S) + 1) / 2;
const thinkingShimmerColor = toRGBColor(interpolateColor(THINKING_INACTIVE, THINKING_INACTIVE_SHIMMER, thinkingOpacity));
const thinkingElapsedSec = (time - THINKING_DELAY_MS) / 1000
const thinkingOpacity =
time < THINKING_DELAY_MS
? 0
: (Math.sin((thinkingElapsedSec * Math.PI * 2) / THINKING_GLOW_PERIOD_S) +
1) /
2
const thinkingShimmerColor = toRGBColor(
interpolateColor(
THINKING_INACTIVE,
THINKING_INACTIVE_SHIMMER,
thinkingOpacity,
),
)
// === Build status parts ===
const parts = [...(spinnerSuffix ? [<Text dimColor key="suffix">
const parts = [
...(spinnerSuffix
? [
<Text dimColor key="suffix">
{spinnerSuffix}
</Text>] : []), ...(showTimer ? [<Text dimColor key="elapsedTime">
</Text>,
]
: []),
...(showTimer
? [
<Text dimColor key="elapsedTime">
{timerText}
</Text>] : []), ...(showTokens ? [<Box flexDirection="row" key="tokens">
</Text>,
]
: []),
...(showTokens
? [
<Box flexDirection="row" key="tokens">
{!hasRunningTeammates && <SpinnerModeGlyph mode={mode} />}
<Text dimColor>{tokenCount} tokens</Text>
</Box>] : []), ...(showThinking && thinkingText ? [thinkingStatus === 'thinking' && !reducedMotion ? <Text key="thinking" color={thinkingShimmerColor}>
</Box>,
]
: []),
...(showThinking && thinkingText
? [
thinkingStatus === 'thinking' && !reducedMotion ? (
<Text key="thinking" color={thinkingShimmerColor}>
{thinkingOnly ? `(${thinkingText})` : thinkingText}
</Text> : <Text dimColor key="thinking">
</Text>
) : (
<Text dimColor key="thinking">
{thinkingText}
</Text>] : [])];
const status = foregroundedTeammate && !foregroundedTeammate.isIdle ? <>
</Text>
),
]
: []),
]
const status =
foregroundedTeammate && !foregroundedTeammate.isIdle ? (
<>
<Text dimColor>(esc to interrupt </Text>
<Text color={toInkColor(foregroundedTeammate.identity.color)}>
{foregroundedTeammate.identity.agentName}
</Text>
<Text dimColor>)</Text>
</> : !foregroundedTeammate && parts.length > 0 ? thinkingOnly ? <Byline>{parts}</Byline> : <>
</>
) : !foregroundedTeammate && parts.length > 0 ? (
thinkingOnly ? (
<Byline>{parts}</Byline>
) : (
<>
<Text dimColor>(</Text>
<Byline>{parts}</Byline>
<Text dimColor>)</Text>
</> : null;
return <Box ref={viewportRef} flexDirection="row" flexWrap="wrap" marginTop={1} width="100%">
<SpinnerGlyph frame={frame} messageColor={messageColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} reducedMotion={reducedMotion} time={time} />
<GlimmerMessage message={message} mode={mode} messageColor={messageColor} glimmerIndex={glimmerIndex} flashOpacity={flashOpacity} shimmerColor={shimmerColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} />
</>
)
) : null
return (
<Box
ref={viewportRef}
flexDirection="row"
flexWrap="wrap"
marginTop={1}
width="100%"
>
<SpinnerGlyph
frame={frame}
messageColor={messageColor}
stalledIntensity={overrideColor ? 0 : stalledIntensity}
reducedMotion={reducedMotion}
time={time}
/>
<GlimmerMessage
message={message}
mode={mode}
messageColor={messageColor}
glimmerIndex={glimmerIndex}
flashOpacity={flashOpacity}
shimmerColor={shimmerColor}
stalledIntensity={overrideColor ? 0 : stalledIntensity}
/>
{status}
</Box>;
</Box>
)
}
function SpinnerModeGlyph(t0) {
const $ = _c(2);
const {
mode
} = t0;
function SpinnerModeGlyph({ mode }: { mode: SpinnerMode }): React.ReactNode {
switch (mode) {
case "tool-input":
case "tool-use":
case "responding":
case "thinking":
{
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Box width={2}><Text dimColor={true}>{figures.arrowDown}</Text></Box>;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
case "requesting":
{
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Box width={2}><Text dimColor={true}>{figures.arrowUp}</Text></Box>;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
case 'tool-input':
case 'tool-use':
case 'responding':
case 'thinking':
return (
<Box width={2}>
<Text dimColor>{figures.arrowDown}</Text>
</Box>
)
case 'requesting':
return (
<Box width={2}>
<Text dimColor>{figures.arrowUp}</Text>
</Box>
)
}
}

View File

@@ -1,79 +1,86 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { Box, Text, useTheme } from '../../ink.js';
import { getTheme, type Theme } from '../../utils/theme.js';
import { getDefaultCharacters, interpolateColor, parseRGB, toRGBColor } from './utils.js';
const DEFAULT_CHARACTERS = getDefaultCharacters();
const SPINNER_FRAMES = [...DEFAULT_CHARACTERS, ...[...DEFAULT_CHARACTERS].reverse()];
const REDUCED_MOTION_DOT = '●';
const REDUCED_MOTION_CYCLE_MS = 2000; // 2-second cycle: 1s visible, 1s dim
const ERROR_RED = {
r: 171,
g: 43,
b: 63
};
import * as React from 'react'
import { Box, Text, useTheme } from '../../ink.js'
import { getTheme, type Theme } from '../../utils/theme.js'
import {
getDefaultCharacters,
interpolateColor,
parseRGB,
toRGBColor,
} from './utils.js'
const DEFAULT_CHARACTERS = getDefaultCharacters()
const SPINNER_FRAMES = [
...DEFAULT_CHARACTERS,
...[...DEFAULT_CHARACTERS].reverse(),
]
const REDUCED_MOTION_DOT = '●'
const REDUCED_MOTION_CYCLE_MS = 2000 // 2-second cycle: 1s visible, 1s dim
const ERROR_RED = { r: 171, g: 43, b: 63 }
type Props = {
frame: number;
messageColor: keyof Theme;
stalledIntensity?: number;
reducedMotion?: boolean;
time?: number;
};
export function SpinnerGlyph(t0) {
const $ = _c(9);
const {
frame,
messageColor,
stalledIntensity: t1,
reducedMotion: t2,
time: t3
} = t0;
const stalledIntensity = t1 === undefined ? 0 : t1;
const reducedMotion = t2 === undefined ? false : t2;
const time = t3 === undefined ? 0 : t3;
const [themeName] = useTheme();
const theme = getTheme(themeName);
if (reducedMotion) {
const isDim = Math.floor(time / (REDUCED_MOTION_CYCLE_MS / 2)) % 2 === 1;
let t4;
if ($[0] !== isDim || $[1] !== messageColor) {
t4 = <Box flexWrap="wrap" height={1} width={2}><Text color={messageColor} dimColor={isDim}>{REDUCED_MOTION_DOT}</Text></Box>;
$[0] = isDim;
$[1] = messageColor;
$[2] = t4;
} else {
t4 = $[2];
}
return t4;
}
const spinnerChar = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
if (stalledIntensity > 0) {
const baseColorStr = theme[messageColor];
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null;
if (baseRGB) {
const interpolated = interpolateColor(baseRGB, ERROR_RED, stalledIntensity);
return <Box flexWrap="wrap" height={1} width={2}><Text color={toRGBColor(interpolated)}>{spinnerChar}</Text></Box>;
}
const color = stalledIntensity > 0.5 ? "error" : messageColor;
let t4;
if ($[3] !== color || $[4] !== spinnerChar) {
t4 = <Box flexWrap="wrap" height={1} width={2}><Text color={color}>{spinnerChar}</Text></Box>;
$[3] = color;
$[4] = spinnerChar;
$[5] = t4;
} else {
t4 = $[5];
}
return t4;
}
let t4;
if ($[6] !== messageColor || $[7] !== spinnerChar) {
t4 = <Box flexWrap="wrap" height={1} width={2}><Text color={messageColor}>{spinnerChar}</Text></Box>;
$[6] = messageColor;
$[7] = spinnerChar;
$[8] = t4;
} else {
t4 = $[8];
}
return t4;
frame: number
messageColor: keyof Theme
stalledIntensity?: number
reducedMotion?: boolean
time?: number
}
export function SpinnerGlyph({
frame,
messageColor,
stalledIntensity = 0,
reducedMotion = false,
time = 0,
}: Props): React.ReactNode {
const [themeName] = useTheme()
const theme = getTheme(themeName)
// Reduced motion: slowly flashing orange dot
if (reducedMotion) {
const isDim = Math.floor(time / (REDUCED_MOTION_CYCLE_MS / 2)) % 2 === 1
return (
<Box flexWrap="wrap" height={1} width={2}>
<Text color={messageColor} dimColor={isDim}>
{REDUCED_MOTION_DOT}
</Text>
</Box>
)
}
const spinnerChar = SPINNER_FRAMES[frame % SPINNER_FRAMES.length]
// Smoothly interpolate from current color to red when stalled
if (stalledIntensity > 0) {
const baseColorStr = theme[messageColor]
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null
if (baseRGB) {
const interpolated = interpolateColor(
baseRGB,
ERROR_RED,
stalledIntensity,
)
return (
<Box flexWrap="wrap" height={1} width={2}>
<Text color={toRGBColor(interpolated)}>{spinnerChar}</Text>
</Box>
)
}
// Fallback for ANSI themes
const color = stalledIntensity > 0.5 ? 'error' : messageColor
return (
<Box flexWrap="wrap" height={1} width={2}>
<Text color={color}>{spinnerChar}</Text>
</Box>
)
}
return (
<Box flexWrap="wrap" height={1} width={2}>
<Text color={messageColor}>{spinnerChar}</Text>
</Box>
)
}

View File

@@ -1,205 +1,265 @@
import figures from 'figures';
import sample from 'lodash-es/sample.js';
import * as React from 'react';
import { useRef, useState } from 'react';
import { getSpinnerVerbs } from '../../constants/spinnerVerbs.js';
import { TURN_COMPLETION_VERBS } from '../../constants/turnCompletionVerbs.js';
import { useElapsedTime } from '../../hooks/useElapsedTime.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { stringWidth } from '../../ink/stringWidth.js';
import { Box, Text } from '../../ink.js';
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js';
import { summarizeRecentActivities } from '../../utils/collapseReadSearch.js';
import { formatDuration, formatNumber, truncateToWidth } from '../../utils/format.js';
import { toInkColor } from '../../utils/ink.js';
import { TEAMMATE_SELECT_HINT } from './teammateSelectHint.js';
import figures from 'figures'
import sample from 'lodash-es/sample.js'
import * as React from 'react'
import { useRef, useState } from 'react'
import { getSpinnerVerbs } from '../../constants/spinnerVerbs.js'
import { TURN_COMPLETION_VERBS } from '../../constants/turnCompletionVerbs.js'
import { useElapsedTime } from '../../hooks/useElapsedTime.js'
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
import { stringWidth } from '../../ink/stringWidth.js'
import { Box, Text } from '../../ink.js'
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js'
import { summarizeRecentActivities } from '../../utils/collapseReadSearch.js'
import {
formatDuration,
formatNumber,
truncateToWidth,
} from '../../utils/format.js'
import { toInkColor } from '../../utils/ink.js'
import { TEAMMATE_SELECT_HINT } from './teammateSelectHint.js'
type Props = {
teammate: InProcessTeammateTaskState;
isLast: boolean;
isSelected?: boolean;
isForegrounded?: boolean;
allIdle?: boolean;
showPreview?: boolean;
};
teammate: InProcessTeammateTaskState
isLast: boolean
isSelected?: boolean
isForegrounded?: boolean
allIdle?: boolean
showPreview?: boolean
}
/**
* Extract the last 3 lines of content from a teammate's conversation.
* Shows recent activity from any message type (user or assistant).
*/
function getMessagePreview(messages: InProcessTeammateTaskState['messages']): string[] {
if (!messages?.length) return [];
const allLines: string[] = [];
const maxLineLength = 80;
function getMessagePreview(
messages: InProcessTeammateTaskState['messages'],
): string[] {
if (!messages?.length) return []
const allLines: string[] = []
const maxLineLength = 80
// Collect lines from recent messages (newest first)
for (let i = messages.length - 1; i >= 0 && allLines.length < 3; i--) {
const msg = messages[i];
const msg = messages[i]
// Only process messages that have content (user/assistant messages)
if (!msg || msg.type !== 'user' && msg.type !== 'assistant' || !msg.message?.content?.length) {
continue;
if (
!msg ||
(msg.type !== 'user' && msg.type !== 'assistant') ||
!msg.message?.content?.length
) {
continue
}
const content = msg.message.content;
const content = msg.message.content
for (const block of content) {
if (allLines.length >= 3) break;
if (!block || typeof block !== 'object') continue;
if (allLines.length >= 3) break
if (!block || typeof block !== 'object') continue
if ('type' in block && block.type === 'tool_use' && 'name' in block) {
// Try to show meaningful info from tool input
const input = 'input' in block ? block.input as Record<string, unknown> : null;
let toolLine = `Using ${block.name}`;
const input =
'input' in block ? (block.input as Record<string, unknown>) : null
let toolLine = `Using ${block.name}`
if (input) {
// Look for common descriptive fields
const desc = input.description as string | undefined || input.prompt as string | undefined || input.command as string | undefined || input.query as string | undefined || input.pattern as string | undefined;
const desc =
(input.description as string | undefined) ||
(input.prompt as string | undefined) ||
(input.command as string | undefined) ||
(input.query as string | undefined) ||
(input.pattern as string | undefined)
if (desc) {
toolLine = desc.split('\n')[0] ?? toolLine;
toolLine = desc.split('\n')[0] ?? toolLine
}
}
allLines.push(truncateToWidth(toolLine, maxLineLength));
allLines.push(truncateToWidth(toolLine, maxLineLength))
} else if ('type' in block && block.type === 'text' && 'text' in block) {
const textLines = (block.text as string).split('\n').filter(l => l.trim());
const textLines = (block.text as string)
.split('\n')
.filter(l => l.trim())
// Take from end of text (most recent lines)
for (let j = textLines.length - 1; j >= 0 && allLines.length < 3; j--) {
const line = textLines[j];
if (!line) continue;
allLines.push(truncateToWidth(line, maxLineLength));
const line = textLines[j]
if (!line) continue
allLines.push(truncateToWidth(line, maxLineLength))
}
}
}
}
// Reverse so oldest of the 3 is first (reading order)
return allLines.reverse();
return allLines.reverse()
}
export function TeammateSpinnerLine({
teammate,
isLast,
isSelected,
isForegrounded,
allIdle,
showPreview
showPreview,
}: Props): React.ReactNode {
const [randomVerb] = useState(() => teammate.spinnerVerb ?? sample(getSpinnerVerbs()));
const [pastTenseVerb] = useState(() => teammate.pastTenseVerb ?? sample(TURN_COMPLETION_VERBS));
const isHighlighted = isSelected || isForegrounded;
const treeChar = isHighlighted ? isLast ? '╘═' : '╞═' : isLast ? '└─' : '├─';
const nameColor = toInkColor(teammate.identity.color);
const {
columns
} = useTerminalSize();
const [randomVerb] = useState(
() => teammate.spinnerVerb ?? sample(getSpinnerVerbs()),
)
const [pastTenseVerb] = useState(
() => teammate.pastTenseVerb ?? sample(TURN_COMPLETION_VERBS),
)
const isHighlighted = isSelected || isForegrounded
const treeChar = isHighlighted ? (isLast ? '╘═' : '╞═') : isLast ? '└─' : '├─'
const nameColor = toInkColor(teammate.identity.color)
const { columns } = useTerminalSize()
// Track when teammate became idle (for "Idle for X..." display)
const idleStartRef = useRef<number | null>(null);
const idleStartRef = useRef<number | null>(null)
// Freeze elapsed time when entering all-idle state
const frozenDurationRef = useRef<string | null>(null);
const frozenDurationRef = useRef<string | null>(null)
// Track idle start time
if (teammate.isIdle && idleStartRef.current === null) {
idleStartRef.current = Date.now();
idleStartRef.current = Date.now()
} else if (!teammate.isIdle) {
idleStartRef.current = null;
idleStartRef.current = null
}
// Reset frozen duration when leaving all-idle state
if (!allIdle && frozenDurationRef.current !== null) {
frozenDurationRef.current = null;
frozenDurationRef.current = null
}
// Get elapsed idle time (how long they've been idle) - for "Idle for X..." display
const idleElapsedTime = useElapsedTime(idleStartRef.current ?? Date.now(), teammate.isIdle && !allIdle);
const idleElapsedTime = useElapsedTime(
idleStartRef.current ?? Date.now(),
teammate.isIdle && !allIdle,
)
// Freeze the duration when we first detect all idle
// Use the teammate's actual work time (since task started) for the past-tense display
if (allIdle && frozenDurationRef.current === null) {
frozenDurationRef.current = formatDuration(Math.max(0, Date.now() - teammate.startTime - (teammate.totalPausedMs ?? 0)));
frozenDurationRef.current = formatDuration(
Math.max(
0,
Date.now() - teammate.startTime - (teammate.totalPausedMs ?? 0),
),
)
}
// Use frozen work duration when all idle, otherwise use idle elapsed time
const displayTime = allIdle ? frozenDurationRef.current ?? (() => {
throw new Error(`frozenDurationRef is null for idle teammate ${teammate.identity.agentName}`);
})() : idleElapsedTime;
const displayTime = allIdle
? (frozenDurationRef.current ??
(() => {
throw new Error(
`frozenDurationRef is null for idle teammate ${teammate.identity.agentName}`,
)
})())
: idleElapsedTime
// Layout: paddingLeft(3) + pointer(1) + space(1) + treeChar(2) + space(1) = 8 fixed chars
// Then optionally: @name + ": " OR just ": "
// Then: activity text + optional extras (stats, hints)
const basePrefix = 8;
const fullAgentName = `@${teammate.identity.agentName}`;
const fullNameWidth = stringWidth(fullAgentName);
const basePrefix = 8
const fullAgentName = `@${teammate.identity.agentName}`
const fullNameWidth = stringWidth(fullAgentName)
// Get stats from progress
const toolUseCount = teammate.progress?.toolUseCount ?? 0;
const tokenCount = teammate.progress?.tokenCount ?? 0;
const statsText = ` · ${toolUseCount} tool ${toolUseCount === 1 ? 'use' : 'uses'} · ${formatNumber(tokenCount)} tokens`;
const statsWidth = stringWidth(statsText);
const selectHintText = ` · ${TEAMMATE_SELECT_HINT}`;
const selectHintWidth = stringWidth(selectHintText);
const viewHintText = ' · enter to view';
const viewHintWidth = stringWidth(viewHintText);
const toolUseCount = teammate.progress?.toolUseCount ?? 0
const tokenCount = teammate.progress?.tokenCount ?? 0
const statsText = ` · ${toolUseCount} tool ${toolUseCount === 1 ? 'use' : 'uses'} · ${formatNumber(tokenCount)} tokens`
const statsWidth = stringWidth(statsText)
const selectHintText = ` · ${TEAMMATE_SELECT_HINT}`
const selectHintWidth = stringWidth(selectHintText)
const viewHintText = ' · enter to view'
const viewHintWidth = stringWidth(viewHintText)
// Progressive responsive layout:
// Wide (80+): full name + activity + stats + hint
// Medium (60-80): full name + activity
// Narrow (<60): hide name, just show activity
const minActivityWidth = 25;
const minActivityWidth = 25
// Hide name on narrow terminals (< 60 cols) or if there's not enough room
const spaceWithFullName = columns - basePrefix - fullNameWidth - 2;
const showName = columns >= 60 && spaceWithFullName >= minActivityWidth;
const nameWidth = showName ? fullNameWidth + 2 : 0; // +2 for ": " when name shown
const availableForActivity = columns - basePrefix - nameWidth;
const spaceWithFullName = columns - basePrefix - fullNameWidth - 2
const showName = columns >= 60 && spaceWithFullName >= minActivityWidth
const nameWidth = showName ? fullNameWidth + 2 : 0 // +2 for ": " when name shown
const availableForActivity = columns - basePrefix - nameWidth
// Progressive hiding: view hint → select hint → stats
// Stats always visible (dimmed when not selected); hints only when highlighted/selected
const showViewHint = isSelected && !isForegrounded && availableForActivity > viewHintWidth + statsWidth + minActivityWidth + 5;
const showSelectHint = isHighlighted && availableForActivity > selectHintWidth + (showViewHint ? viewHintWidth : 0) + statsWidth + minActivityWidth + 5;
const showStats = availableForActivity > statsWidth + minActivityWidth + 5;
const showViewHint =
isSelected &&
!isForegrounded &&
availableForActivity > viewHintWidth + statsWidth + minActivityWidth + 5
const showSelectHint =
isHighlighted &&
availableForActivity >
selectHintWidth +
(showViewHint ? viewHintWidth : 0) +
statsWidth +
minActivityWidth +
5
const showStats = availableForActivity > statsWidth + minActivityWidth + 5
// Activity text gets remaining space
const extrasCost = (showStats ? statsWidth : 0) + (showSelectHint ? selectHintWidth : 0) + (showViewHint ? viewHintWidth : 0);
const activityMaxWidth = Math.max(minActivityWidth, availableForActivity - extrasCost - 1);
const extrasCost =
(showStats ? statsWidth : 0) +
(showSelectHint ? selectHintWidth : 0) +
(showViewHint ? viewHintWidth : 0)
const activityMaxWidth = Math.max(
minActivityWidth,
availableForActivity - extrasCost - 1,
)
// Format the activity text for active teammates, rolling up search/read ops
const activityText = (() => {
const activities = teammate.progress?.recentActivities;
const activities = teammate.progress?.recentActivities
if (activities && activities.length > 0) {
const summary = summarizeRecentActivities(activities);
if (summary) return truncateToWidth(summary, activityMaxWidth);
const summary = summarizeRecentActivities(activities)
if (summary) return truncateToWidth(summary, activityMaxWidth)
}
const desc = teammate.progress?.lastActivity?.activityDescription;
if (desc) return truncateToWidth(desc, activityMaxWidth);
return randomVerb;
})();
const desc = teammate.progress?.lastActivity?.activityDescription
if (desc) return truncateToWidth(desc, activityMaxWidth)
return randomVerb
})()
// Status rendering logic
const renderStatus = (): React.ReactNode => {
if (teammate.shutdownRequested) {
return <Text dimColor>[stopping]</Text>;
return <Text dimColor>[stopping]</Text>
}
if (teammate.awaitingPlanApproval) {
return <Text color="warning">[awaiting approval]</Text>;
return <Text color="warning">[awaiting approval]</Text>
}
if (teammate.isIdle) {
if (allIdle) {
return <Text dimColor>
return (
<Text dimColor>
{pastTenseVerb} for {displayTime}
</Text>;
</Text>
)
}
return <Text dimColor>Idle for {idleElapsedTime}</Text>;
return <Text dimColor>Idle for {idleElapsedTime}</Text>
}
// Active - show spinner glyph + activity description (only when not highlighted;
// when highlighted, the main spinner above already shows the verb)
if (isHighlighted) {
return null;
return null
}
return <Text dimColor>
return (
<Text dimColor>
{activityText?.endsWith('…') ? activityText : `${activityText}`}
</Text>;
};
</Text>
)
}
// Get preview lines if enabled
const previewLines = showPreview ? getMessagePreview(teammate.messages) : [];
const previewLines = showPreview ? getMessagePreview(teammate.messages) : []
// Tree continuation character for preview lines
const previewTreeChar = isLast ? ' ' : '│ ';
return <Box flexDirection="column">
const previewTreeChar = isLast ? ' ' : '│ '
return (
<Box flexDirection="column">
<Box paddingLeft={3}>
{/* Selection indicator: pointer when selected, otherwise space */}
<Text color={isSelected ? 'suggestion' : undefined} bold={isSelected}>
@@ -207,26 +267,33 @@ export function TeammateSpinnerLine({
</Text>
<Text dimColor={!isSelected}>{treeChar} </Text>
{/* Agent name: hidden on very narrow screens */}
{showName && <Text color={isSelected ? 'suggestion' : nameColor}>
{showName && (
<Text color={isSelected ? 'suggestion' : nameColor}>
@{teammate.identity.agentName}
</Text>}
</Text>
)}
{showName && <Text dimColor={!isSelected}>: </Text>}
{renderStatus()}
{/* Stats: only shown when selected and terminal is wide enough */}
{showStats && <Text dimColor>
{showStats && (
<Text dimColor>
{' '}
· {toolUseCount} tool {toolUseCount === 1 ? 'use' : 'uses'} ·{' '}
{formatNumber(tokenCount)} tokens
</Text>}
</Text>
)}
{/* Hints: select hint when highlighted, view hint when selected but not foregrounded */}
{showSelectHint && <Text dimColor> · {TEAMMATE_SELECT_HINT}</Text>}
{showViewHint && <Text dimColor> · enter to view</Text>}
</Box>
{/* Preview lines */}
{previewLines.map((line, idx) => <Box key={idx} paddingLeft={3}>
{previewLines.map((line, idx) => (
<Box key={idx} paddingLeft={3}>
<Text dimColor> </Text>
<Text dimColor>{previewTreeChar} </Text>
<Text dimColor>{line}</Text>
</Box>)}
</Box>;
</Box>
))}
</Box>
)
}

View File

@@ -1,271 +1,130 @@
import { c as _c } from "react/compiler-runtime";
import figures from 'figures';
import * as React from 'react';
import { Box, Text, type TextProps } from '../../ink.js';
import { useAppState } from '../../state/AppState.js';
import { getRunningTeammatesSorted } from '../../tasks/InProcessTeammateTask/InProcessTeammateTask.js';
import { formatNumber } from '../../utils/format.js';
import { TeammateSpinnerLine } from './TeammateSpinnerLine.js';
import { TEAMMATE_SELECT_HINT } from './teammateSelectHint.js';
import figures from 'figures'
import * as React from 'react'
import { Box, Text, type TextProps } from '../../ink.js'
import { useAppState } from '../../state/AppState.js'
import { getRunningTeammatesSorted } from '../../tasks/InProcessTeammateTask/InProcessTeammateTask.js'
import { formatNumber } from '../../utils/format.js'
import { TeammateSpinnerLine } from './TeammateSpinnerLine.js'
import { TEAMMATE_SELECT_HINT } from './teammateSelectHint.js'
type Props = {
selectedIndex?: number;
isInSelectionMode?: boolean;
allIdle?: boolean;
selectedIndex?: number
isInSelectionMode?: boolean
allIdle?: boolean
/** Leader's active verb (when leader is actively processing) */
leaderVerb?: string;
leaderVerb?: string
/** Leader's token count (when leader is actively processing) */
leaderTokenCount?: number;
leaderTokenCount?: number
/** Leader's idle status text (when leader is idle, e.g. "✻ Idle for 3s") */
leaderIdleText?: string;
};
export function TeammateSpinnerTree(t0) {
const $ = _c(61);
const {
selectedIndex,
isInSelectionMode,
allIdle,
leaderVerb,
leaderTokenCount,
leaderIdleText
} = t0;
const tasks = useAppState(_temp);
const viewingAgentTaskId = useAppState(_temp2);
const showTeammateMessagePreview = useAppState(_temp3);
let T0;
let isHideSelected;
let t1;
let t2;
let t3;
let t4;
let t5;
if ($[0] !== allIdle || $[1] !== isInSelectionMode || $[2] !== leaderIdleText || $[3] !== leaderTokenCount || $[4] !== leaderVerb || $[5] !== selectedIndex || $[6] !== showTeammateMessagePreview || $[7] !== tasks || $[8] !== viewingAgentTaskId) {
t5 = Symbol.for("react.early_return_sentinel");
bb0: {
const teammateTasks = getRunningTeammatesSorted(tasks);
if (teammateTasks.length === 0) {
t5 = null;
break bb0;
}
const isLeaderForegrounded = viewingAgentTaskId === undefined;
const isLeaderSelected = isInSelectionMode && selectedIndex === -1;
const isLeaderHighlighted = isLeaderForegrounded || isLeaderSelected;
isHideSelected = isInSelectionMode === true && selectedIndex === teammateTasks.length;
T0 = Box;
t1 = "column";
t2 = 1;
const t6 = isLeaderSelected ? "suggestion" : undefined;
const t7 = isLeaderSelected ? figures.pointer : " ";
let t8;
if ($[16] !== isLeaderHighlighted || $[17] !== t6 || $[18] !== t7) {
t8 = <Text color={t6} bold={isLeaderHighlighted}>{t7}</Text>;
$[16] = isLeaderHighlighted;
$[17] = t6;
$[18] = t7;
$[19] = t8;
} else {
t8 = $[19];
}
const t9 = !isLeaderHighlighted;
const t10 = isLeaderHighlighted ? "\u2552\u2550" : "\u250C\u2500";
let t11;
if ($[20] !== isLeaderHighlighted || $[21] !== t10 || $[22] !== t9) {
t11 = <Text dimColor={t9} bold={isLeaderHighlighted}>{t10}{" "}</Text>;
$[20] = isLeaderHighlighted;
$[21] = t10;
$[22] = t9;
$[23] = t11;
} else {
t11 = $[23];
}
const t12 = isLeaderSelected ? "suggestion" : "cyan_FOR_SUBAGENTS_ONLY";
let t13;
if ($[24] !== isLeaderHighlighted || $[25] !== t12) {
t13 = <Text bold={isLeaderHighlighted} color={t12}>team-lead</Text>;
$[24] = isLeaderHighlighted;
$[25] = t12;
$[26] = t13;
} else {
t13 = $[26];
}
let t14;
if ($[27] !== isLeaderForegrounded || $[28] !== leaderVerb) {
t14 = !isLeaderForegrounded && leaderVerb && <Text dimColor={true}>: {leaderVerb}</Text>;
$[27] = isLeaderForegrounded;
$[28] = leaderVerb;
$[29] = t14;
} else {
t14 = $[29];
}
let t15;
if ($[30] !== isLeaderForegrounded || $[31] !== leaderIdleText || $[32] !== leaderVerb) {
t15 = !isLeaderForegrounded && !leaderVerb && leaderIdleText && <Text dimColor={true}>: {leaderIdleText}</Text>;
$[30] = isLeaderForegrounded;
$[31] = leaderIdleText;
$[32] = leaderVerb;
$[33] = t15;
} else {
t15 = $[33];
}
let t16;
if ($[34] !== isLeaderHighlighted || $[35] !== leaderTokenCount) {
t16 = leaderTokenCount !== undefined && leaderTokenCount > 0 && <Text dimColor={!isLeaderHighlighted}>{" "}· {formatNumber(leaderTokenCount)} tokens</Text>;
$[34] = isLeaderHighlighted;
$[35] = leaderTokenCount;
$[36] = t16;
} else {
t16 = $[36];
}
let t17;
if ($[37] !== isLeaderHighlighted) {
t17 = isLeaderHighlighted && <Text dimColor={true}> · {TEAMMATE_SELECT_HINT}</Text>;
$[37] = isLeaderHighlighted;
$[38] = t17;
} else {
t17 = $[38];
}
let t18;
if ($[39] !== isLeaderForegrounded || $[40] !== isLeaderSelected) {
t18 = isLeaderSelected && !isLeaderForegrounded && <Text dimColor={true}> · enter to view</Text>;
$[39] = isLeaderForegrounded;
$[40] = isLeaderSelected;
$[41] = t18;
} else {
t18 = $[41];
}
if ($[42] !== t11 || $[43] !== t13 || $[44] !== t14 || $[45] !== t15 || $[46] !== t16 || $[47] !== t17 || $[48] !== t18 || $[49] !== t8) {
t3 = <Box paddingLeft={3}>{t8}{t11}{t13}{t14}{t15}{t16}{t17}{t18}</Box>;
$[42] = t11;
$[43] = t13;
$[44] = t14;
$[45] = t15;
$[46] = t16;
$[47] = t17;
$[48] = t18;
$[49] = t8;
$[50] = t3;
} else {
t3 = $[50];
}
t4 = teammateTasks.map((teammate, index) => <TeammateSpinnerLine key={teammate.id} teammate={teammate} isLast={!isInSelectionMode && index === teammateTasks.length - 1} isSelected={isInSelectionMode && selectedIndex === index} isForegrounded={viewingAgentTaskId === teammate.id} allIdle={allIdle} showPreview={showTeammateMessagePreview} />);
}
$[0] = allIdle;
$[1] = isInSelectionMode;
$[2] = leaderIdleText;
$[3] = leaderTokenCount;
$[4] = leaderVerb;
$[5] = selectedIndex;
$[6] = showTeammateMessagePreview;
$[7] = tasks;
$[8] = viewingAgentTaskId;
$[9] = T0;
$[10] = isHideSelected;
$[11] = t1;
$[12] = t2;
$[13] = t3;
$[14] = t4;
$[15] = t5;
} else {
T0 = $[9];
isHideSelected = $[10];
t1 = $[11];
t2 = $[12];
t3 = $[13];
t4 = $[14];
t5 = $[15];
}
if (t5 !== Symbol.for("react.early_return_sentinel")) {
return t5;
}
let t6;
if ($[51] !== isHideSelected || $[52] !== isInSelectionMode) {
t6 = isInSelectionMode && <HideRow isSelected={isHideSelected} />;
$[51] = isHideSelected;
$[52] = isInSelectionMode;
$[53] = t6;
} else {
t6 = $[53];
}
let t7;
if ($[54] !== T0 || $[55] !== t1 || $[56] !== t2 || $[57] !== t3 || $[58] !== t4 || $[59] !== t6) {
t7 = <T0 flexDirection={t1} marginTop={t2}>{t3}{t4}{t6}</T0>;
$[54] = T0;
$[55] = t1;
$[56] = t2;
$[57] = t3;
$[58] = t4;
$[59] = t6;
$[60] = t7;
} else {
t7 = $[60];
}
return t7;
leaderIdleText?: string
}
function _temp3(s_1) {
return s_1.showTeammateMessagePreview;
export function TeammateSpinnerTree({
selectedIndex,
isInSelectionMode,
allIdle,
leaderVerb,
leaderTokenCount,
leaderIdleText,
}: Props): React.ReactNode {
const tasks = useAppState(s => s.tasks)
const viewingAgentTaskId = useAppState(s => s.viewingAgentTaskId)
const showTeammateMessagePreview = useAppState(
s => s.showTeammateMessagePreview,
)
const teammateTasks = getRunningTeammatesSorted(tasks)
// Don't render if no running teammates
if (teammateTasks.length === 0) {
return null
}
// Leader highlighting follows same pattern as teammates:
// isHighlighted = isForegrounded || isSelected
const isLeaderForegrounded = viewingAgentTaskId === undefined
const isLeaderSelected = isInSelectionMode && selectedIndex === -1
const isLeaderHighlighted = isLeaderForegrounded || isLeaderSelected
const leaderColor: TextProps['color'] = 'cyan_FOR_SUBAGENTS_ONLY'
// Is the "hide" row selected? (index === teammateCount in selection mode)
const isHideSelected =
isInSelectionMode === true && selectedIndex === teammateTasks.length
return (
<Box flexDirection="column" marginTop={1}>
{/* Leader row - always visible, uses ┌─ to enclose the tree */}
{
<Box paddingLeft={3}>
<Text
color={isLeaderSelected ? 'suggestion' : undefined}
bold={isLeaderHighlighted}
>
{isLeaderSelected ? figures.pointer : ' '}
</Text>
<Text dimColor={!isLeaderHighlighted} bold={isLeaderHighlighted}>
{isLeaderHighlighted ? '╒═' : '┌─'}{' '}
</Text>
<Text
bold={isLeaderHighlighted}
color={isLeaderSelected ? 'suggestion' : leaderColor}
>
team-lead
</Text>
{/* When backgrounded and active: show spinner + verb */}
{!isLeaderForegrounded && leaderVerb && (
<Text dimColor>: {leaderVerb}</Text>
)}
{/* When backgrounded and idle: show idle text */}
{!isLeaderForegrounded && !leaderVerb && leaderIdleText && (
<Text dimColor>: {leaderIdleText}</Text>
)}
{/* Stats (tokens) - same dimColor logic as teammates */}
{leaderTokenCount !== undefined && leaderTokenCount > 0 && (
<Text dimColor={!isLeaderHighlighted}>
{' '}
· {formatNumber(leaderTokenCount)} tokens
</Text>
)}
{/* Hints - select hint when highlighted, view hint when selected but not foregrounded */}
{isLeaderHighlighted && (
<Text dimColor> · {TEAMMATE_SELECT_HINT}</Text>
)}
{isLeaderSelected && !isLeaderForegrounded && (
<Text dimColor> · enter to view</Text>
)}
</Box>
}
{teammateTasks.map((teammate, index) => (
<TeammateSpinnerLine
key={teammate.id}
teammate={teammate}
isLast={!isInSelectionMode && index === teammateTasks.length - 1}
isSelected={isInSelectionMode && selectedIndex === index}
isForegrounded={viewingAgentTaskId === teammate.id}
allIdle={allIdle}
showPreview={showTeammateMessagePreview}
/>
))}
{/* Hide row - only visible during selection mode */}
{isInSelectionMode && <HideRow isSelected={isHideSelected} />}
</Box>
)
}
function _temp2(s_0) {
return s_0.viewingAgentTaskId;
}
function _temp(s) {
return s.tasks;
}
function HideRow(t0) {
const $ = _c(18);
const {
isSelected
} = t0;
const t1 = isSelected ? "suggestion" : undefined;
const t2 = isSelected ? figures.pointer : " ";
let t3;
if ($[0] !== isSelected || $[1] !== t1 || $[2] !== t2) {
t3 = <Text color={t1} bold={isSelected}>{t2}</Text>;
$[0] = isSelected;
$[1] = t1;
$[2] = t2;
$[3] = t3;
} else {
t3 = $[3];
}
const t4 = !isSelected;
const t5 = isSelected ? "\u2558\u2550" : "\u2514\u2500";
let t6;
if ($[4] !== isSelected || $[5] !== t4 || $[6] !== t5) {
t6 = <Text dimColor={t4} bold={isSelected}>{t5}{" "}</Text>;
$[4] = isSelected;
$[5] = t4;
$[6] = t5;
$[7] = t6;
} else {
t6 = $[7];
}
const t7 = !isSelected;
let t8;
if ($[8] !== isSelected || $[9] !== t7) {
t8 = <Text dimColor={t7} bold={isSelected}>hide</Text>;
$[8] = isSelected;
$[9] = t7;
$[10] = t8;
} else {
t8 = $[10];
}
let t9;
if ($[11] !== isSelected) {
t9 = isSelected && <Text dimColor={true}> · enter to collapse</Text>;
$[11] = isSelected;
$[12] = t9;
} else {
t9 = $[12];
}
let t10;
if ($[13] !== t3 || $[14] !== t6 || $[15] !== t8 || $[16] !== t9) {
t10 = <Box paddingLeft={3}>{t3}{t6}{t8}{t9}</Box>;
$[13] = t3;
$[14] = t6;
$[15] = t8;
$[16] = t9;
$[17] = t10;
} else {
t10 = $[17];
}
return t10;
function HideRow({ isSelected }: { isSelected: boolean }): React.ReactNode {
return (
<Box paddingLeft={3}>
<Text color={isSelected ? 'suggestion' : undefined} bold={isSelected}>
{isSelected ? figures.pointer : ' '}
</Text>
<Text dimColor={!isSelected} bold={isSelected}>
{isSelected ? '╘═' : '└─'}{' '}
</Text>
<Text dimColor={!isSelected} bold={isSelected}>
hide
</Text>
{isSelected && <Text dimColor> · enter to collapse</Text>}
</Box>
)
}