Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4476c35
feat: first pass at /footer
jackwotherspoon Feb 13, 2026
57a05b3
chore: tests and cleanup
jackwotherspoon Feb 13, 2026
0b79d75
chore: test fix
jackwotherspoon Feb 13, 2026
3c58639
chore: refactor duplicate code
jackwotherspoon Feb 13, 2026
0ea3aed
chore: cleanup
jackwotherspoon Feb 13, 2026
88f95f5
chore: review
jackwotherspoon Feb 13, 2026
39be41d
chore: minor tweaks
jackwotherspoon Feb 13, 2026
686998c
chore: refactor based on feedback
jackwotherspoon Feb 16, 2026
2796db5
chore: docs
jackwotherspoon Feb 16, 2026
bfcf495
chore: test cleanup
jackwotherspoon Feb 16, 2026
97d3973
chore: cleanup
jackwotherspoon Feb 16, 2026
c51925b
chore: update
jackwotherspoon Feb 16, 2026
4412314
chore: reduce duplication
jackwotherspoon Feb 16, 2026
f40f475
chore: refactor
jackwotherspoon Feb 16, 2026
3c9bded
chore: use preview memory value
jackwotherspoon Feb 16, 2026
b2d1590
chore: update tests
jackwotherspoon Feb 16, 2026
86017b9
chore: update
jackwotherspoon Feb 16, 2026
2d3b43b
chore: format
jackwotherspoon Feb 17, 2026
88c8e9c
chore: add space to toggle footer
jackwotherspoon Feb 25, 2026
450cb19
chore: fix preview wrapping
jackwotherspoon Feb 26, 2026
8d112f5
chore: make Windows paths better
jackwotherspoon Feb 26, 2026
0292a20
chore: update tests
jackwotherspoon Feb 26, 2026
32e01f7
test(cli): fix cross-platform footer test alignment and path normaliz…
jackwotherspoon Feb 26, 2026
ea965bc
ui: update footer labels to lowercase and 'workspace (/directory)'
keithguerin Mar 1, 2026
94272f3
ui: address PR comments and rename 'usage-limit' to 'quota'
keithguerin Mar 1, 2026
f4c52d7
ui: implement responsive footer wrapping
keithguerin Mar 1, 2026
dc2cc57
test: fix TypeScript errors in Footer tests
keithguerin Mar 1, 2026
542e77a
test: fix ContextUsageDisplay tests to correctly await waitUntilReady
keithguerin Mar 1, 2026
d19388e
docs: regenerate settings schema and documentation
keithguerin Mar 1, 2026
fce85e9
chore: merge main
jackwotherspoon Mar 5, 2026
f21ba48
Merge branch 'main' into statusline
jackwotherspoon Mar 5, 2026
e1fc485
Merge branch 'main' into statusline
jacob314 Mar 5, 2026
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
2 changes: 1 addition & 1 deletion docs/cli/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ they appear in the UI.
| Show Shortcuts Hint | `ui.showShortcutsHint` | Show the "? for shortcuts" hint above the input. | `true` |
| Hide Banner | `ui.hideBanner` | Hide the application banner | `false` |
| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` |
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` |
| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory in the footer. | `false` |
| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` |
| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` |
| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window usage percentage. | `true` |
Expand Down
12 changes: 11 additions & 1 deletion docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,18 @@ their corresponding top-level category object in your `settings.json` file.
input.
- **Default:** `false`

- **`ui.footer.items`** (array):
- **Description:** List of item IDs to display in the footer. Rendered in
order
- **Default:** `undefined`

- **`ui.footer.showLabels`** (boolean):
- **Description:** Display a second line above the footer items with
descriptive headers (e.g., /model).
- **Default:** `true`

- **`ui.footer.hideCWD`** (boolean):
- **Description:** Hide the current working directory path in the footer.
- **Description:** Hide the current working directory in the footer.
- **Default:** `false`

- **`ui.footer.hideSandboxStatus`** (boolean):
Expand Down
91 changes: 91 additions & 0 deletions packages/cli/src/config/footerItems.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect } from 'vitest';
import { deriveItemsFromLegacySettings } from './footerItems.js';
import { createMockSettings } from '../test-utils/settings.js';

describe('deriveItemsFromLegacySettings', () => {
it('returns defaults when no legacy settings are customized', () => {
const settings = createMockSettings({
ui: { footer: { hideContextPercentage: true } },
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).toEqual([
'workspace',
'git-branch',
'sandbox',
'model-name',
'quota',
]);
});

it('removes workspace when hideCWD is true', () => {
const settings = createMockSettings({
ui: { footer: { hideCWD: true, hideContextPercentage: true } },
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).not.toContain('workspace');
});

it('removes sandbox when hideSandboxStatus is true', () => {
const settings = createMockSettings({
ui: { footer: { hideSandboxStatus: true, hideContextPercentage: true } },
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).not.toContain('sandbox');
});

it('removes model-name, context-used, and quota when hideModelInfo is true', () => {
const settings = createMockSettings({
ui: { footer: { hideModelInfo: true, hideContextPercentage: true } },
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).not.toContain('model-name');
expect(items).not.toContain('context-used');
expect(items).not.toContain('quota');
});

it('includes context-used when hideContextPercentage is false', () => {
const settings = createMockSettings({
ui: { footer: { hideContextPercentage: false } },
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).toContain('context-used');
// Should be after model-name
const modelIdx = items.indexOf('model-name');
const contextIdx = items.indexOf('context-used');
expect(contextIdx).toBe(modelIdx + 1);
});

it('includes memory-usage when showMemoryUsage is true', () => {
const settings = createMockSettings({
ui: { showMemoryUsage: true, footer: { hideContextPercentage: true } },
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).toContain('memory-usage');
});

it('handles combination of settings', () => {
const settings = createMockSettings({
ui: {
showMemoryUsage: true,
footer: {
hideCWD: true,
hideModelInfo: true,
hideContextPercentage: false,
},
},
}).merged;
const items = deriveItemsFromLegacySettings(settings);
expect(items).toEqual([
'git-branch',
'sandbox',
'context-used',
'memory-usage',
]);
});
});
132 changes: 132 additions & 0 deletions packages/cli/src/config/footerItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { MergedSettings } from './settings.js';

export const ALL_ITEMS = [
{
id: 'workspace',
header: 'workspace (/directory)',
description: 'Current working directory',
},
{
id: 'git-branch',
header: 'branch',
description: 'Current git branch name (not shown when unavailable)',
},
{
id: 'sandbox',
header: 'sandbox',
description: 'Sandbox type and trust indicator',
},
{
id: 'model-name',
header: '/model',
description: 'Current model identifier',
},
{
id: 'context-used',
header: 'context',
description: 'Percentage of context window used',
},
{
id: 'quota',
header: '/stats',
description: 'Remaining usage on daily limit (not shown when unavailable)',
},
{
id: 'memory-usage',
header: 'memory',
description: 'Memory used by the application',
},
{
id: 'session-id',
header: 'session',
description: 'Unique identifier for the current session',
},
{
id: 'code-changes',
header: 'diff',
description: 'Lines added/removed in the session (not shown when zero)',
},
{
id: 'token-count',
header: 'tokens',
description: 'Total tokens used in the session (not shown when zero)',
},
] as const;

export type FooterItemId = (typeof ALL_ITEMS)[number]['id'];

export const DEFAULT_ORDER = [
'workspace',
'git-branch',
'sandbox',
'model-name',
'context-used',
'quota',
'memory-usage',
'session-id',
'code-changes',
'token-count',
];

export function deriveItemsFromLegacySettings(
settings: MergedSettings,
): string[] {
const defaults = [
'workspace',
'git-branch',
'sandbox',
'model-name',
'quota',
];
const items = [...defaults];

const remove = (arr: string[], id: string) => {
const idx = arr.indexOf(id);
if (idx !== -1) arr.splice(idx, 1);
};

if (settings.ui.footer.hideCWD) remove(items, 'workspace');
if (settings.ui.footer.hideSandboxStatus) remove(items, 'sandbox');
if (settings.ui.footer.hideModelInfo) {
remove(items, 'model-name');
remove(items, 'context-used');
remove(items, 'quota');
}
if (
!settings.ui.footer.hideContextPercentage &&
!items.includes('context-used')
) {
const modelIdx = items.indexOf('model-name');
if (modelIdx !== -1) items.splice(modelIdx + 1, 0, 'context-used');
else items.push('context-used');
}
if (settings.ui.showMemoryUsage) items.push('memory-usage');

return items;
}

const VALID_IDS: Set<string> = new Set(ALL_ITEMS.map((i) => i.id));

/**
* Resolves the ordered list and selected set of footer items from settings.
* Used by FooterConfigDialog to initialize and reset state.
*/
export function resolveFooterState(settings: MergedSettings): {
orderedIds: string[];
selectedIds: Set<string>;
} {
const source = (
settings.ui?.footer?.items ?? deriveItemsFromLegacySettings(settings)
).filter((id: string) => VALID_IDS.has(id));
const others = DEFAULT_ORDER.filter((id) => !source.includes(id));
return {
orderedIds: [...source, ...others],
selectedIds: new Set(source),
};
}
24 changes: 22 additions & 2 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,14 +565,34 @@ const SETTINGS_SCHEMA = {
description: 'Settings for the footer.',
showInDialog: false,
properties: {
items: {
type: 'array',
label: 'Footer Items',
category: 'UI',
requiresRestart: false,
default: undefined as string[] | undefined,
description:
'List of item IDs to display in the footer. Rendered in order',
showInDialog: false,
items: { type: 'string' },
},
showLabels: {
type: 'boolean',
label: 'Show Footer Labels',
category: 'UI',
requiresRestart: false,
default: true,
description:
'Display a second line above the footer items with descriptive headers (e.g., /model).',
showInDialog: false,
},
hideCWD: {
type: 'boolean',
label: 'Hide CWD',
category: 'UI',
requiresRestart: false,
default: false,
description:
'Hide the current working directory path in the footer.',
description: 'Hide the current working directory in the footer.',
showInDialog: true,
},
hideSandboxStatus: {
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/services/BuiltinCommandLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { docsCommand } from '../ui/commands/docsCommand.js';
import { directoryCommand } from '../ui/commands/directoryCommand.js';
import { editorCommand } from '../ui/commands/editorCommand.js';
import { extensionsCommand } from '../ui/commands/extensionsCommand.js';
import { footerCommand } from '../ui/commands/footerCommand.js';
import { helpCommand } from '../ui/commands/helpCommand.js';
import { shortcutsCommand } from '../ui/commands/shortcutsCommand.js';
import { rewindCommand } from '../ui/commands/rewindCommand.js';
Expand Down Expand Up @@ -119,6 +120,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
]
: [extensionsCommand(this.config?.getEnableExtensionReloading())]),
helpCommand,
footerCommand,
shortcutsCommand,
...(this.config?.getEnableHooksUI() ? [hooksCommand] : []),
rewindCommand,
Expand Down
18 changes: 17 additions & 1 deletion packages/cli/src/test-utils/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { vi } from 'vitest';
import stripAnsi from 'strip-ansi';
import { act, useState } from 'react';
import os from 'node:os';
import path from 'node:path';
import { LoadedSettings } from '../config/settings.js';
import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
import { SettingsContext } from '../ui/contexts/SettingsContext.js';
Expand Down Expand Up @@ -502,7 +503,22 @@ const configProxy = new Proxy({} as Config, {
get(_target, prop) {
if (prop === 'getTargetDir') {
return () =>
'/Users/test/project/foo/bar/and/some/more/directories/to/make/it/long';
path.join(
path.parse(process.cwd()).root,
'Users',
'test',
'project',
'foo',
'bar',
'and',
'some',
'more',
'directories',
'to',
'make',
'it',
'long',
);
}
if (prop === 'getUseBackgroundColor') {
return () => true;
Expand Down
25 changes: 25 additions & 0 deletions packages/cli/src/ui/commands/footerCommand.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {
type SlashCommand,
type CommandContext,
type OpenCustomDialogActionReturn,
CommandKind,
} from './types.js';
import { FooterConfigDialog } from '../components/FooterConfigDialog.js';

export const footerCommand: SlashCommand = {
name: 'footer',
altNames: ['statusline'],
description: 'Configure which items appear in the footer (statusline)',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: (context: CommandContext): OpenCustomDialogActionReturn => ({
type: 'custom_dialog',
component: <FooterConfigDialog onClose={context.ui.removeComponent} />,
}),
};
Loading
Loading