mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
feat/redesign header compact (#20922)
This commit is contained in:
@@ -17,16 +17,30 @@ import { theme } from '../semantic-colors.js';
|
||||
import { ThemedGradient } from './ThemedGradient.js';
|
||||
import { CliSpinner } from './CliSpinner.js';
|
||||
|
||||
import { isAppleTerminal } from '@google/gemini-cli-core';
|
||||
|
||||
interface AppHeaderProps {
|
||||
version: string;
|
||||
showDetails?: boolean;
|
||||
}
|
||||
|
||||
const ICON = `▝▜▄
|
||||
const DEFAULT_ICON = `▝▜▄
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▝▀ `;
|
||||
|
||||
/**
|
||||
* The default Apple Terminal.app adds significant line-height padding between
|
||||
* rows. This breaks Unicode block-drawing characters that rely on vertical
|
||||
* adjacency (like half-blocks). This version is perfectly symmetric vertically,
|
||||
* which makes the padding gaps look like an intentional "scanline" design
|
||||
* rather than a broken image.
|
||||
*/
|
||||
const MAC_TERMINAL_ICON = `▝▜▄
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▗▟▀ `;
|
||||
|
||||
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||
const settings = useSettings();
|
||||
const config = useConfig();
|
||||
@@ -39,6 +53,8 @@ export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||
settings.merged.ui.hideBanner || config.getScreenReader()
|
||||
);
|
||||
|
||||
const ICON = isAppleTerminal() ? MAC_TERMINAL_ICON : DEFAULT_ICON;
|
||||
|
||||
if (!showDetails) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
|
||||
49
packages/cli/src/ui/components/AppHeaderIcon.test.tsx
Normal file
49
packages/cli/src/ui/components/AppHeaderIcon.test.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { AppHeader } from './AppHeader.js';
|
||||
|
||||
// We mock the entire module to control the isAppleTerminal export
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
isAppleTerminal: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { isAppleTerminal } from '@google/gemini-cli-core';
|
||||
|
||||
describe('AppHeader Icon Rendering', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('renders the default icon in standard terminals', async () => {
|
||||
vi.mocked(isAppleTerminal).mockReturnValue(false);
|
||||
|
||||
const result = renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
await result.waitUntilReady();
|
||||
|
||||
await expect(result).toMatchSvgSnapshot();
|
||||
});
|
||||
|
||||
it('renders the symmetric icon in Apple Terminal', async () => {
|
||||
vi.mocked(isAppleTerminal).mockReturnValue(true);
|
||||
|
||||
const result = renderWithProviders(<AppHeader version="1.0.0" />);
|
||||
await result.waitUntilReady();
|
||||
|
||||
await expect(result).toMatchSvgSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -51,6 +51,24 @@ describe('<UserIdentity />', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render the user email on the very first frame (regression test)', () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
|
||||
authType: AuthType.LOGIN_WITH_GOOGLE,
|
||||
model: 'gemini-pro',
|
||||
} as unknown as ContentGeneratorConfig);
|
||||
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
|
||||
|
||||
const { lastFrameRaw, unmount } = renderWithProviders(
|
||||
<UserIdentity config={mockConfig} />,
|
||||
);
|
||||
|
||||
// Assert immediately on the first available frame before any async ticks happen
|
||||
const output = lastFrameRaw();
|
||||
expect(output).toContain('test@example.com');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render login message if email is missing', async () => {
|
||||
// Modify the mock for this specific test
|
||||
vi.mocked(UserAccountManager).mockImplementationOnce(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useMemo, useEffect, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import {
|
||||
@@ -20,13 +20,12 @@ interface UserIdentityProps {
|
||||
|
||||
export const UserIdentity: React.FC<UserIdentityProps> = ({ config }) => {
|
||||
const authType = config.getContentGeneratorConfig()?.authType;
|
||||
const [email, setEmail] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const email = useMemo(() => {
|
||||
if (authType) {
|
||||
const userAccountManager = new UserAccountManager();
|
||||
setEmail(userAccountManager.getCachedGoogleAccount() ?? undefined);
|
||||
return userAccountManager.getCachedGoogleAccount() ?? undefined;
|
||||
}
|
||||
return undefined;
|
||||
}, [authType]);
|
||||
|
||||
const tierName = useMemo(
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="224" viewBox="0 0 920 224">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="224" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="18" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="27" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="36" y="19" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="90" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="180" y="19" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.0.0</text>
|
||||
<text x="36" y="36" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="45" y="36" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="54" y="36" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="27" y="53" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▗</text>
|
||||
<text x="36" y="53" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▟</text>
|
||||
<text x="45" y="53" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="18" y="70" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="27" y="70" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="0" y="121" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="138" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="138" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="138" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="155" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="155" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="155" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="224" viewBox="0 0 920 224">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="920" height="224" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="18" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="27" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="36" y="19" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="81" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Gemini CLI</text>
|
||||
<text x="171" y="19" fill="#afafaf" textLength="63" lengthAdjust="spacingAndGlyphs"> v1.0.0</text>
|
||||
<text x="36" y="36" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▝</text>
|
||||
<text x="45" y="36" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▜</text>
|
||||
<text x="54" y="36" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="36" y="53" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▗</text>
|
||||
<text x="45" y="53" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">▟</text>
|
||||
<text x="54" y="53" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="18" y="70" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">▗</text>
|
||||
<text x="27" y="70" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">▟</text>
|
||||
<text x="36" y="70" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">▀</text>
|
||||
<text x="0" y="121" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Tips for getting started:</text>
|
||||
<text x="0" y="138" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">1. Create </text>
|
||||
<text x="90" y="138" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">GEMINI.md</text>
|
||||
<text x="171" y="138" fill="#ffffff" textLength="333" lengthAdjust="spacingAndGlyphs"> files to customize your interactions</text>
|
||||
<text x="0" y="155" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">2. </text>
|
||||
<text x="27" y="155" fill="#afafaf" textLength="45" lengthAdjust="spacingAndGlyphs">/help</text>
|
||||
<text x="72" y="155" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs"> for more information</text>
|
||||
<text x="0" y="172" fill="#ffffff" textLength="450" lengthAdjust="spacingAndGlyphs">3. Ask coding questions, edit code or run commands</text>
|
||||
<text x="0" y="189" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs">4. Be specific for the best results</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,31 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`AppHeader Icon Rendering > renders the default icon in standard terminals 1`] = `
|
||||
"
|
||||
▝▜▄ Gemini CLI v1.0.0
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▝▀
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results"
|
||||
`;
|
||||
|
||||
exports[`AppHeader Icon Rendering > renders the symmetric icon in Apple Terminal 1`] = `
|
||||
"
|
||||
▝▜▄ Gemini CLI v1.0.0
|
||||
▝▜▄
|
||||
▗▟▀
|
||||
▗▟▀
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results"
|
||||
`;
|
||||
Reference in New Issue
Block a user