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
18 changes: 17 additions & 1 deletion packages/cli/src/ui/components/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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">
Expand Down
49 changes: 49 additions & 0 deletions packages/cli/src/ui/components/AppHeaderIcon.test.tsx
Original file line number Diff line number Diff line change
@@ -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();
});
});
18 changes: 18 additions & 0 deletions packages/cli/src/ui/components/UserIdentity.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
9 changes: 4 additions & 5 deletions packages/cli/src/ui/components/UserIdentity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand Down
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.
Original file line number Diff line number Diff line change
@@ -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"
`;
Loading