Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 19 additions & 11 deletions packages/cli/src/ui/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export interface FooterRowItem {
flexGrow?: number;
flexShrink?: number;
isFocused?: boolean;
alignItems?: 'flex-start' | 'center' | 'flex-end';
}

const COLUMN_GAP = 3;
Expand All @@ -117,10 +118,17 @@ export const FooterRow: React.FC<{
const elements: React.ReactNode[] = [];

items.forEach((item, idx) => {
if (idx > 0 && !showLabels) {
if (idx > 0) {
elements.push(
<Box key={`sep-${item.key}`} height={1}>
<Text color={theme.ui.comment}> · </Text>
<Box
key={`sep-${item.key}`}
flexGrow={1}
flexShrink={1}
minWidth={showLabels ? COLUMN_GAP : 3}
justifyContent="center"
alignItems="center"
>
{!showLabels && <Text color={theme.ui.comment}> · </Text>}
</Box>,
);
}
Expand All @@ -131,6 +139,7 @@ export const FooterRow: React.FC<{
flexDirection="column"
flexGrow={item.flexGrow ?? 0}
flexShrink={item.flexShrink ?? 1}
alignItems={item.alignItems}
backgroundColor={item.isFocused ? theme.background.focus : undefined}
>
{showLabels && (
Expand All @@ -148,12 +157,7 @@ export const FooterRow: React.FC<{
});

return (
<Box
flexDirection="row"
flexWrap="nowrap"
width="100%"
columnGap={showLabels ? COLUMN_GAP : 0}
>
<Box flexDirection="row" flexWrap="nowrap" width="100%">
{elements}
</Box>
);
Expand Down Expand Up @@ -441,8 +445,9 @@ export const Footer: React.FC = () => {
}
}

const rowItems: FooterRowItem[] = columnsToRender.map((col) => {
const rowItems: FooterRowItem[] = columnsToRender.map((col, index) => {
const isWorkspace = col.id === 'workspace';
const isLast = index === columnsToRender.length - 1;

// Calculate exact space available for growth to prevent over-estimation truncation
const otherItemsWidth = columnsToRender
Expand All @@ -464,8 +469,10 @@ export const Footer: React.FC = () => {
key: col.id,
header: col.header,
element: col.element(estimatedWidth),
flexGrow: isWorkspace ? 1 : 0,
flexGrow: 0,
flexShrink: isWorkspace ? 1 : 0,
alignItems:
isLast && !droppedAny && index > 0 ? 'flex-end' : 'flex-start',
};
});

Expand All @@ -476,6 +483,7 @@ export const Footer: React.FC = () => {
element: <Text color={theme.ui.comment}>…</Text>,
flexGrow: 0,
flexShrink: 0,
alignItems: 'flex-end',
});
}

Expand Down
57 changes: 57 additions & 0 deletions packages/cli/src/ui/components/FooterConfigDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { FooterConfigDialog } from './FooterConfigDialog.js';
import { createMockSettings } from '../../test-utils/settings.js';
import { ALL_ITEMS } from '../../config/footerItems.js';
import { act } from 'react';

describe('<FooterConfigDialog />', () => {
Expand Down Expand Up @@ -213,4 +214,60 @@ describe('<FooterConfigDialog />', () => {
expect(bIdxAfter).toBeLessThan(wIdxAfter);
});
});

it('updates the preview when Show footer labels is toggled off', async () => {
const settings = createMockSettings();
const renderResult = renderWithProviders(
<FooterConfigDialog onClose={mockOnClose} />,
{ settings },
);

const { lastFrame, stdin, waitUntilReady } = renderResult;
await waitUntilReady();

// By default labels are on
expect(lastFrame()).toContain('workspace (/directory)');
expect(lastFrame()).toContain('sandbox');
expect(lastFrame()).toContain('/model');

// Move to "Show footer labels" (which is the second to last item)
for (let i = 0; i < ALL_ITEMS.length; i++) {
act(() => {
stdin.write('\u001b[B'); // Down arrow
});
Comment on lines +233 to +237
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

The use of the magic number 10 to navigate to the 'Show footer labels' item makes this test fragile. If the number of configurable footer items changes in the future, this test will break. To make the test more robust, please avoid hardcoding the number of navigation steps. You could, for example, determine the required number of steps dynamically based on the length of the items list.

References
  1. Magic numbers, especially those used for layout or padding, should be replaced with named constants to improve readability and maintainability. This makes the intent clearer and simplifies future updates.

}

await waitFor(() => {
expect(lastFrame()).toMatch(/> \[✓\] Show footer labels/);
});

// Toggle it off
act(() => {
stdin.write('\r');
});

await waitFor(() => {
expect(lastFrame()).toMatch(/> \[ \] Show footer labels/);
// The headers should no longer be in the preview
expect(lastFrame()).not.toContain('workspace (/directory)');
expect(lastFrame()).not.toContain('/model');

// We can't strictly search for "sandbox" because the menu item also says "sandbox".
// Let's assert that the spacer dots are now present in the preview instead.
const previewLine =
lastFrame()
.split('\n')
.find((line) => line.includes('Preview:')) || '';
const nextLine =
lastFrame().split('\n')[
lastFrame().split('\n').indexOf(previewLine) + 1
] || '';
expect(nextLine).toContain('·');
expect(nextLine).toContain('~/project/path');
expect(nextLine).toContain('docker');
expect(nextLine).toContain('97%');
});

await expect(renderResult).toMatchSvgSnapshot();
});
});
2 changes: 1 addition & 1 deletion packages/cli/src/ui/components/FooterConfigDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
key: id,
header: ALL_ITEMS.find((i) => i.id === id)?.header ?? id,
element: mockData[id],
flexGrow: 1,
flexGrow: 0,
isFocused: id === focusKey,
}));

Expand Down
22 changes: 11 additions & 11 deletions packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`<Footer /> > displays "Limit reached" message when remaining is 0 1`] = `
" workspace (/directory) sandbox /model /stats
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro limit reached
" workspace (/directory) sandbox /model /stats
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro limit reached
"
`;

exports[`<Footer /> > displays the usage indicator when usage is low 1`] = `
" workspace (/directory) sandbox /model /stats
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 85%
" workspace (/directory) sandbox /model /stats
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 85%
"
`;

exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders complete footer in narrow terminal (baseline narrow) > complete-footer-narrow 1`] = `
" workspace (/directory) sandbox /model context
...me/more/directories/to/make/it/long no sandbox gemini-pro 14%
" workspace (/directory) sandbox /model context
...me/more/directories/to/make/it/long no sandbox gemini-pro 14%
"
`;

exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders complete footer with all sections visible (baseline) > complete-footer-wide 1`] = `
" workspace (/directory) sandbox /model context
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 14% used
" workspace (/directory) sandbox /model context
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 14% used
"
`;

Expand All @@ -33,13 +33,13 @@ exports[`<Footer /> > footer configuration filtering (golden snapshots) > render
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`] = `
" workspace (/directory) sandbox
" workspace (/directory) sandbox
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox
"
`;

exports[`<Footer /> > hides the usage indicator when usage is not near limit 1`] = `
" workspace (/directory) sandbox /model /stats
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 15%
" workspace (/directory) sandbox /model /stats
~/project/foo/bar/and/some/more/directories/to/make/it/long no sandbox gemini-pro 15%
"
`;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading