Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0a36530
fix: Restore footer configuration settings (#8041)
Lyonk71 Sep 9, 2025
2821d45
style: Apply prettier formatting to Footer.tsx
Lyonk71 Sep 10, 2025
9162085
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 10, 2025
73dee32
fix: Restore footer configuration settings (#8041)
Lyonk71 Sep 9, 2025
e023663
style: Apply prettier formatting to Footer.tsx
Lyonk71 Sep 10, 2025
c52fc84
fix(cli): Restore context summary alignment tweaks
Lyonk71 Sep 11, 2025
19b38b7
Merge branch 'fix/8041-footer-config-regression' of https://github.co…
miguelsolorio Sep 11, 2025
ba6e27f
Adjust alignment for accept/yolo/shell mode
miguelsolorio Sep 11, 2025
cdaafb3
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 11, 2025
f78a964
Format
miguelsolorio Sep 11, 2025
7626cbe
Merge branch 'fix/8041-footer-config-regression' of https://github.co…
miguelsolorio Sep 11, 2025
a0fd00a
test(cli): Add golden snapshot tests for footer
Lyonk71 Sep 11, 2025
67fbd4c
Merge remote-tracking branch 'origin/main' into pr/Lyonk71/8053
miguelsolorio Sep 11, 2025
b10168b
Fix conflict override
miguelsolorio Sep 11, 2025
bf3cf48
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 11, 2025
4dd9e7d
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 11, 2025
cbbd0d4
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 11, 2025
4e6dae1
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 11, 2025
1e98695
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 11, 2025
4436252
Merge branch 'main' into fix/8041-footer-config-regression
miguelsolorio Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/cli/src/ui/components/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export const Composer = () => {
promptTokenCount: uiState.sessionStats.lastPromptTokenCount,
nightly: uiState.nightly,
isTrustedFolder: uiState.isTrustedFolder,
hideCWD: settings.merged.ui?.footer?.hideCWD || false,
hideSandboxStatus: settings.merged.ui?.footer?.hideSandboxStatus || false,
hideModelInfo: settings.merged.ui?.footer?.hideModelInfo || false,
Comment on lines 57 to 59
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

These lines repeatedly access the nested settings.merged.ui?.footer object. This violates the DRY (Don't Repeat Yourself) principle and creates a maintenance concern. If the path to the footer settings were to change, it would require updates in multiple places, increasing the risk of introducing inconsistencies or bugs. It would be more robust to retrieve the footer object once into a local variable before defining footerProps.

For example:

const footerSettings = settings.merged.ui?.footer;
const footerProps: Omit<FooterProps, 'vimMode'> = {
  // ... other props
  hideCWD: footerSettings?.hideCWD || false,
  hideSandboxStatus: footerSettings?.hideSandboxStatus || false,
  hideModelInfo: footerSettings?.hideModelInfo || false,
};

};

return (
Expand Down Expand Up @@ -107,12 +110,16 @@ export const Composer = () => {

<Box
marginTop={1}
justifyContent="space-between"
justifyContent={
settings.merged.ui?.hideContextSummary
? 'flex-start'
: 'space-between'
}
width="100%"
flexDirection={isNarrow ? 'column' : 'row'}
alignItems={isNarrow ? 'flex-start' : 'center'}
>
<Box>
<Box marginRight={1}>
{process.env['GEMINI_SYSTEM_MD'] && (
<Text color={theme.status.error}>|⌐■_■| </Text>
)}
Expand Down
52 changes: 52 additions & 0 deletions packages/cli/src/ui/components/Footer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,56 @@ describe('<Footer />', () => {
vi.unstubAllEnvs();
});
});

describe('footer configuration filtering (golden snapshots)', () => {
it('renders complete footer with all sections visible (baseline)', () => {
const { lastFrame } = renderWithWidth(120, {
...defaultProps,
hideCWD: false,
hideSandboxStatus: false,
hideModelInfo: false,
});
expect(lastFrame()).toMatchSnapshot('complete-footer-wide');
});

it('renders footer with all optional sections hidden (minimal footer)', () => {
const { lastFrame } = renderWithWidth(120, {
...defaultProps,
hideCWD: true,
hideSandboxStatus: true,
hideModelInfo: true,
});
expect(lastFrame()).toMatchSnapshot('footer-minimal');
});

it('renders footer with only model info hidden (partial filtering)', () => {
const { lastFrame } = renderWithWidth(120, {
...defaultProps,
hideCWD: false,
hideSandboxStatus: false,
hideModelInfo: true,
});
expect(lastFrame()).toMatchSnapshot('footer-no-model');
});

it('renders footer with CWD and model info hidden to test alignment (only sandbox visible)', () => {
const { lastFrame } = renderWithWidth(120, {
...defaultProps,
hideCWD: true,
hideSandboxStatus: false,
hideModelInfo: true,
});
expect(lastFrame()).toMatchSnapshot('footer-only-sandbox');
});

it('renders complete footer in narrow terminal (baseline narrow)', () => {
const { lastFrame } = renderWithWidth(79, {
...defaultProps,
hideCWD: false,
hideSandboxStatus: false,
hideModelInfo: false,
});
expect(lastFrame()).toMatchSnapshot('complete-footer-narrow');
});
});
});
178 changes: 99 additions & 79 deletions packages/cli/src/ui/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export interface FooterProps {
nightly: boolean;
vimMode?: string;
isTrustedFolder?: boolean;
hideCWD?: boolean;
hideSandboxStatus?: boolean;
hideModelInfo?: boolean;
}

export const Footer: React.FC<FooterProps> = ({
Expand All @@ -49,6 +52,9 @@ export const Footer: React.FC<FooterProps> = ({
nightly,
vimMode,
isTrustedFolder,
hideCWD = false,
hideSandboxStatus = false,
hideModelInfo = false,
}) => {
const { columns: terminalWidth } = useTerminalSize();

Expand All @@ -60,100 +66,114 @@ export const Footer: React.FC<FooterProps> = ({
? path.basename(tildeifyPath(targetDir))
: shortenPath(tildeifyPath(targetDir), pathLength);

const justifyContent = hideCWD && hideModelInfo ? 'center' : 'space-between';

return (
<Box
justifyContent="space-between"
justifyContent={justifyContent}
width="100%"
flexDirection={isNarrow ? 'column' : 'row'}
alignItems={isNarrow ? 'flex-start' : 'center'}
>
<Box>
{debugMode && <DebugProfiler />}
{vimMode && <Text color={theme.text.secondary}>[{vimMode}] </Text>}
{nightly ? (
<Gradient colors={theme.ui.gradient}>
<Text>
{displayPath}
{branchName && <Text> ({branchName}*)</Text>}
{(debugMode || vimMode || !hideCWD) && (
<Box>
{debugMode && <DebugProfiler />}
{vimMode && <Text color={theme.text.secondary}>[{vimMode}] </Text>}
{!hideCWD &&
(nightly ? (
<Gradient colors={theme.ui.gradient}>
<Text>
{displayPath}
{branchName && <Text> ({branchName}*)</Text>}
</Text>
</Gradient>
) : (
<Text color={theme.text.link}>
{displayPath}
{branchName && (
<Text color={theme.text.secondary}> ({branchName}*)</Text>
)}
</Text>
))}
{debugMode && (
<Text color={theme.status.error}>
{' ' + (debugMessage || '--debug')}
</Text>
</Gradient>
) : (
<Text color={theme.text.link}>
{displayPath}
{branchName && (
<Text color={theme.text.secondary}> ({branchName}*)</Text>
)}
</Text>
)}
{debugMode && (
<Text color={theme.status.error}>
{' ' + (debugMessage || '--debug')}
</Text>
)}
</Box>
)}
</Box>
)}

{/* Middle Section: Centered Trust/Sandbox Info */}
<Box
flexGrow={isNarrow ? 0 : 1}
alignItems="center"
justifyContent={isNarrow ? 'flex-start' : 'center'}
display="flex"
paddingX={isNarrow ? 0 : 1}
paddingTop={isNarrow ? 1 : 0}
>
{isTrustedFolder === false ? (
<Text color={theme.status.warning}>untrusted</Text>
) : process.env['SANDBOX'] &&
process.env['SANDBOX'] !== 'sandbox-exec' ? (
<Text color="green">
{process.env['SANDBOX'].replace(/^gemini-(?:cli-)?/, '')}
</Text>
) : process.env['SANDBOX'] === 'sandbox-exec' ? (
<Text color={theme.status.warning}>
macOS Seatbelt{' '}
<Text color={theme.text.secondary}>
({process.env['SEATBELT_PROFILE']})
{!hideSandboxStatus && (
<Box
flexGrow={isNarrow || hideCWD || hideModelInfo ? 0 : 1}
alignItems="center"
justifyContent={isNarrow || hideCWD ? 'flex-start' : 'center'}
display="flex"
paddingX={isNarrow ? 0 : 1}
paddingTop={isNarrow ? 1 : 0}
>
{isTrustedFolder === false ? (
<Text color={theme.status.warning}>untrusted</Text>
) : process.env['SANDBOX'] &&
process.env['SANDBOX'] !== 'sandbox-exec' ? (
<Text color="green">
{process.env['SANDBOX'].replace(/^gemini-(?:cli-)?/, '')}
</Text>
</Text>
) : (
<Text color={theme.status.error}>
no sandbox <Text color={theme.text.secondary}>(see /docs)</Text>
</Text>
)}
</Box>

{/* Right Section: Gemini Label and Console Summary */}
<Box alignItems="center" paddingTop={isNarrow ? 1 : 0}>
<Box alignItems="center">
<Text color={theme.text.accent}>
{isNarrow ? '' : ' '}
{model}{' '}
<ContextUsageDisplay
promptTokenCount={promptTokenCount}
model={model}
/>
</Text>
{showMemoryUsage && <MemoryUsageDisplay />}
</Box>
<Box alignItems="center" paddingLeft={2}>
{corgiMode && (
<Text>
<Text color={theme.ui.comment}>| </Text>
<Text color={theme.status.error}>▼</Text>
<Text color={theme.text.primary}>(´</Text>
<Text color={theme.status.error}>ᴥ</Text>
<Text color={theme.text.primary}>`)</Text>
<Text color={theme.status.error}>▼ </Text>
) : process.env['SANDBOX'] === 'sandbox-exec' ? (
<Text color={theme.status.warning}>
macOS Seatbelt{' '}
<Text color={theme.text.secondary}>
({process.env['SEATBELT_PROFILE']})
</Text>
</Text>
) : (
<Text color={theme.status.error}>
no sandbox <Text color={theme.text.secondary}>(see /docs)</Text>
</Text>
)}
{!showErrorDetails && errorCount > 0 && (
<Box>
<Text color={theme.ui.comment}>| </Text>
<ConsoleSummaryDisplay errorCount={errorCount} />
</Box>
)}

{/* Right Section: Gemini Label and Console Summary */}
{(!hideModelInfo ||
showMemoryUsage ||
corgiMode ||
(!showErrorDetails && errorCount > 0)) && (
<Box alignItems="center" paddingTop={isNarrow ? 1 : 0}>
{!hideModelInfo && (
<Box alignItems="center">
<Text color={theme.text.accent}>
{isNarrow ? '' : ' '}
{model}{' '}
<ContextUsageDisplay
promptTokenCount={promptTokenCount}
model={model}
/>
</Text>
{showMemoryUsage && <MemoryUsageDisplay />}
</Box>
)}
<Box alignItems="center" paddingLeft={2}>
{corgiMode && (
<Text>
{!hideModelInfo && <Text color={theme.ui.comment}>| </Text>}
<Text color={theme.status.error}>▼</Text>
<Text color={theme.text.primary}>(´</Text>
<Text color={theme.status.error}>ᴥ</Text>
<Text color={theme.text.primary}>`)</Text>
<Text color={theme.status.error}>▼ </Text>
</Text>
)}
{!showErrorDetails && errorCount > 0 && (
<Box>
{!hideModelInfo && <Text color={theme.ui.comment}>| </Text>}
<ConsoleSummaryDisplay errorCount={errorCount} />
</Box>
)}
</Box>
</Box>
</Box>
)}
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders complete footer in narrow terminal (baseline narrow) > complete-footer-narrow 1`] = `
"long (main*)

no sandbox (see /docs)

gemini-pro (100% context left)"
`;

exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders complete footer with all sections visible (baseline) > complete-footer-wide 1`] = `
"...bar/and/some/more/directories/to/make/it/long no sandbox (see gemini-pro (100% context
(main*) /docs) left)"
`;

exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with CWD and model info hidden to test alignment (only sandbox visible) > footer-only-sandbox 1`] = `" no sandbox (see /docs)"`;

exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `""`;

exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with only model info hidden (partial filtering) > footer-no-model 1`] = `"...bar/and/some/more/directories/to/make/it/long (main*) no sandbox (see /docs)"`;
Loading