chore: goal promptinput-footsider lastline
Browse files
src/components/PromptInput/GoalIndicator.tsx
CHANGED
|
@@ -3,16 +3,16 @@ import { Text } from '../../ink.js'
|
|
| 3 |
import { useAppState } from '../../state/AppState.js'
|
| 4 |
import type { GoalStatus } from '../../state/AppStateStore.js'
|
| 5 |
|
| 6 |
-
const MAX_OBJECTIVE_CHARS =
|
| 7 |
|
| 8 |
-
function
|
| 9 |
switch (status) {
|
| 10 |
case 'pursuing':
|
| 11 |
-
return 'green'
|
| 12 |
-
case 'paused':
|
| 13 |
return 'yellow'
|
|
|
|
|
|
|
| 14 |
case 'achieved':
|
| 15 |
-
return '
|
| 16 |
case 'blocked':
|
| 17 |
return 'red'
|
| 18 |
case 'usage-limited':
|
|
@@ -34,11 +34,11 @@ export function GoalIndicator(): React.ReactNode {
|
|
| 34 |
|
| 35 |
const planSuppressed = goal.status === 'pursuing' && mode === 'plan'
|
| 36 |
const label = planSuppressed ? 'paused: plan mode' : goal.status
|
| 37 |
-
const
|
| 38 |
|
| 39 |
return (
|
| 40 |
<Text>
|
| 41 |
-
<Text color={
|
| 42 |
<Text dimColor> goal: </Text>
|
| 43 |
<Text>{truncated}</Text>
|
| 44 |
<Text dimColor> [{label}]</Text>
|
|
|
|
| 3 |
import { useAppState } from '../../state/AppState.js'
|
| 4 |
import type { GoalStatus } from '../../state/AppStateStore.js'
|
| 5 |
|
| 6 |
+
const MAX_OBJECTIVE_CHARS = 40
|
| 7 |
|
| 8 |
+
export function goalStatusColor(status: GoalStatus): string {
|
| 9 |
switch (status) {
|
| 10 |
case 'pursuing':
|
|
|
|
|
|
|
| 11 |
return 'yellow'
|
| 12 |
+
case 'paused':
|
| 13 |
+
return 'gray'
|
| 14 |
case 'achieved':
|
| 15 |
+
return 'green'
|
| 16 |
case 'blocked':
|
| 17 |
return 'red'
|
| 18 |
case 'usage-limited':
|
|
|
|
| 34 |
|
| 35 |
const planSuppressed = goal.status === 'pursuing' && mode === 'plan'
|
| 36 |
const label = planSuppressed ? 'paused: plan mode' : goal.status
|
| 37 |
+
const dotColor = planSuppressed ? 'yellow' : goalStatusColor(goal.status)
|
| 38 |
|
| 39 |
return (
|
| 40 |
<Text>
|
| 41 |
+
<Text color={dotColor}>●</Text>
|
| 42 |
<Text dimColor> goal: </Text>
|
| 43 |
<Text>{truncated}</Text>
|
| 44 |
<Text dimColor> [{label}]</Text>
|
src/components/PromptInput/PromptInputFooterLeftSide.tsx
CHANGED
|
@@ -366,9 +366,11 @@ function ModeIndicator({
|
|
| 366 |
// its click-target Box isn't nested inside the <Text wrap="truncate">
|
| 367 |
// wrapper (reconciler throws on Box-in-Text).
|
| 368 |
// Tmux pill (ant-only) — appears right after tasks in nav order
|
| 369 |
-
...("external" === 'ant' && hasTmuxSession ? [<TungstenPill key="tmux" selected={tmuxSelected} />] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [<TeamStatus key="teams" teamsSelected={teamsSelected} showHint={showHint && !hasBackgroundTasks} />] : []), ...(shouldShowPrStatus ? [<PrBadge key="pr-status" number={prStatus.number!} url={prStatus.url!} reviewState={prStatus.reviewState!} />] : [])
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
| 372 |
|
| 373 |
// Check if any in-process teammates exist (for hint text cycling)
|
| 374 |
const hasAnyInProcessTeammates = Object.values(tasks).some(t_2 => t_2.type === 'in_process_teammate' && t_2.status === 'running');
|
|
@@ -398,6 +400,7 @@ function ModeIndicator({
|
|
| 398 |
{otherParts.length > 0 && <Box>
|
| 399 |
<Byline>{otherParts}</Byline>
|
| 400 |
</Box>}
|
|
|
|
| 401 |
</Box>;
|
| 402 |
}
|
| 403 |
|
|
@@ -464,24 +467,35 @@ function ModeIndicator({
|
|
| 464 |
// part (e.g. the selection copy/native-select hints) grow the column
|
| 465 |
// from 0→1 row. Always render 1 row in fullscreen; return a space when
|
| 466 |
// empty so Yoga reserves the row without painting anything visible.
|
| 467 |
-
|
|
|
|
| 468 |
return isFullscreenEnvEnabled() ? <Text> </Text> : null;
|
| 469 |
}
|
|
|
|
|
|
|
|
|
|
| 470 |
|
| 471 |
// flexShrink=0 keeps mode + pill at natural width; the remaining parts
|
| 472 |
// truncate at the tail as one string inside the Text wrapper.
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
<
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
<
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
</Box>;
|
| 486 |
}
|
| 487 |
function getSpinnerHintParts(isLoading: boolean, escShortcut: string, todosShortcut: string, killAgentsShortcut: string, hasTaskItems: boolean, expandedView: 'none' | 'tasks' | 'teammates', hasTeammates: boolean, hasRunningAgentTasks: boolean, isKillAgentsConfirmShowing: boolean): React.ReactElement[] {
|
|
|
|
| 366 |
// its click-target Box isn't nested inside the <Text wrap="truncate">
|
| 367 |
// wrapper (reconciler throws on Box-in-Text).
|
| 368 |
// Tmux pill (ant-only) — appears right after tasks in nav order
|
| 369 |
+
...("external" === 'ant' && hasTmuxSession ? [<TungstenPill key="tmux" selected={tmuxSelected} />] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [<TeamStatus key="teams" teamsSelected={teamsSelected} showHint={showHint && !hasBackgroundTasks} />] : []), ...(shouldShowPrStatus ? [<PrBadge key="pr-status" number={prStatus.number!} url={prStatus.url!} reviewState={prStatus.reviewState!} />] : [])];
|
| 370 |
+
|
| 371 |
+
// Goal indicator renders on its own row at the bottom — not mixed into the
|
| 372 |
+
// main parts line so it doesn't get truncated when the terminal is narrow.
|
| 373 |
+
const goalPart = <GoalIndicator key="goal" />;
|
| 374 |
|
| 375 |
// Check if any in-process teammates exist (for hint text cycling)
|
| 376 |
const hasAnyInProcessTeammates = Object.values(tasks).some(t_2 => t_2.type === 'in_process_teammate' && t_2.status === 'running');
|
|
|
|
| 400 |
{otherParts.length > 0 && <Box>
|
| 401 |
<Byline>{otherParts}</Byline>
|
| 402 |
</Box>}
|
| 403 |
+
{goalPart && <Box><Byline>{goalPart}</Byline></Box>}
|
| 404 |
</Box>;
|
| 405 |
}
|
| 406 |
|
|
|
|
| 467 |
// part (e.g. the selection copy/native-select hints) grow the column
|
| 468 |
// from 0→1 row. Always render 1 row in fullscreen; return a space when
|
| 469 |
// empty so Yoga reserves the row without painting anything visible.
|
| 470 |
+
const hasMainRowContent = modePart || tasksPart || parts.length > 0;
|
| 471 |
+
if (!hasMainRowContent && !goalPart) {
|
| 472 |
return isFullscreenEnvEnabled() ? <Text> </Text> : null;
|
| 473 |
}
|
| 474 |
+
// When there IS a goal but no other main content, we still need to render
|
| 475 |
+
// the main row so that the goal part is below it — fall through to the
|
| 476 |
+
// main return below.
|
| 477 |
|
| 478 |
// flexShrink=0 keeps mode + pill at natural width; the remaining parts
|
| 479 |
// truncate at the tail as one string inside the Text wrapper.
|
| 480 |
+
// Goal gets its own line to avoid truncation in narrow terminals.
|
| 481 |
+
// Main row first, goal indicator always at the bottom.
|
| 482 |
+
return <Box flexDirection="column">
|
| 483 |
+
<Box height={1} overflow="hidden">
|
| 484 |
+
{modePart && <Box flexShrink={0}>
|
| 485 |
+
{modePart}
|
| 486 |
+
{(tasksPart || parts.length > 0 || goalPart) && <Text dimColor> · </Text>}
|
| 487 |
+
</Box>}
|
| 488 |
+
{tasksPart && <Box flexShrink={0}>
|
| 489 |
+
{tasksPart}
|
| 490 |
+
{(parts.length > 0 || goalPart) && <Text dimColor> · </Text>}
|
| 491 |
+
</Box>}
|
| 492 |
+
{parts.length > 0 && <Text wrap="truncate">
|
| 493 |
+
<Byline>{parts}</Byline>
|
| 494 |
+
</Text>}
|
| 495 |
+
{parts.length === 0 && !tasksPart && !modePart && !goalPart && isFullscreenEnvEnabled() && <Text> </Text>}
|
| 496 |
+
{parts.length === 0 && !tasksPart && !modePart && !goalPart && !isFullscreenEnvEnabled() && null}
|
| 497 |
+
</Box>
|
| 498 |
+
{goalPart && <Box height={1}><Byline>{goalPart}</Byline></Box>}
|
| 499 |
</Box>;
|
| 500 |
}
|
| 501 |
function getSpinnerHintParts(isLoading: boolean, escShortcut: string, todosShortcut: string, killAgentsShortcut: string, hasTaskItems: boolean, expandedView: 'none' | 'tasks' | 'teammates', hasTeammates: boolean, hasRunningAgentTasks: boolean, isKillAgentsConfirmShowing: boolean): React.ReactElement[] {
|