chenbhao commited on
Commit
4e35993
·
1 Parent(s): 64c10f6

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 = 30
7
 
8
- function statusColor(status: GoalStatus): string {
9
  switch (status) {
10
  case 'pursuing':
11
- return 'green'
12
- case 'paused':
13
  return 'yellow'
 
 
14
  case 'achieved':
15
- return 'cyan'
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 color = planSuppressed ? 'yellow' : statusColor(goal.status)
38
 
39
  return (
40
  <Text>
41
- <Text color={color}>●</Text>
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
- // Goal indicator — only renders when an active goal exists
371
- <GoalIndicator key="goal" />];
 
 
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
- if (parts.length === 0 && !tasksPart && !modePart) {
 
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
- return <Box height={1} overflow="hidden">
474
- {modePart && <Box flexShrink={0}>
475
- {modePart}
476
- {(tasksPart || parts.length > 0) && <Text dimColor> · </Text>}
477
- </Box>}
478
- {tasksPart && <Box flexShrink={0}>
479
- {tasksPart}
480
- {parts.length > 0 && <Text dimColor> · </Text>}
481
- </Box>}
482
- {parts.length > 0 && <Text wrap="truncate">
483
- <Byline>{parts}</Byline>
484
- </Text>}
 
 
 
 
 
 
 
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[] {