Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/cli/src/ui/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('App', () => {
quittingMessages: null,
dialogsVisible: false,
mainControlsRef: { current: null },
subagentFullscreenPanel: null,
historyManager: {
addItem: vi.fn(),
history: [],
Expand Down
17 changes: 12 additions & 5 deletions packages/cli/src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { useIsScreenReaderEnabled } from 'ink';
import { Box, useIsScreenReaderEnabled } from 'ink';
import { useTerminalSize } from './hooks/useTerminalSize.js';
import { lerp } from '../utils/math.js';
import { useUIState } from './contexts/UIStateContext.js';
import { StreamingContext } from './contexts/StreamingContext.js';
import { QuittingDisplay } from './components/QuittingDisplay.js';
import { ScreenReaderAppLayout } from './layouts/ScreenReaderAppLayout.js';
import { DefaultAppLayout } from './layouts/DefaultAppLayout.js';
import { SubagentFullscreenPanel } from './components/subagents/runtime/SubagentFullscreenPanel.js';

const getContainerWidth = (terminalWidth: number): string => {
if (terminalWidth <= 80) {
Expand All @@ -38,12 +39,18 @@ export const App = () => {
return <QuittingDisplay />;
}

const showFullscreen = Boolean(uiState.subagentFullscreenPanel);
const mainLayout = isScreenReaderEnabled ? (
<ScreenReaderAppLayout />
) : (
<DefaultAppLayout width={containerWidth} />
);

return (
<StreamingContext.Provider value={uiState.streamingState}>
{isScreenReaderEnabled ? (
<ScreenReaderAppLayout />
) : (
<DefaultAppLayout width={containerWidth} />
<Box display={showFullscreen ? 'none' : 'flex'}>{mainLayout}</Box>
{showFullscreen && uiState.subagentFullscreenPanel && (
<SubagentFullscreenPanel panel={uiState.subagentFullscreenPanel} />
)}
</StreamingContext.Provider>
);
Expand Down
28 changes: 28 additions & 0 deletions packages/cli/src/ui/AppContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,34 @@ describe('AppContainer State Management', () => {
capturedUIActions.handleProQuotaChoice('auth');
expect(mockHandler).toHaveBeenCalledWith('auth');
});

it('disables the input prompt when subagent fullscreen panel is open', async () => {
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);

expect(capturedUIState.isInputActive).toBe(true);

capturedUIActions.openSubagentFullscreenPanel({
panelId: 'test-panel',
subagentName: 'Agent',
status: 'running',
content: [],
getSnapshot: () => [],
});

await new Promise((resolve) => setTimeout(resolve, 0));
expect(capturedUIState.isInputActive).toBe(false);

capturedUIActions.closeSubagentFullscreenPanel('test-panel');
await new Promise((resolve) => setTimeout(resolve, 0));
expect(capturedUIState.isInputActive).toBe(true);
});
});

describe('Terminal Title Update Feature', () => {
Expand Down
54 changes: 53 additions & 1 deletion packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ToolCallStatus,
type HistoryItemWithoutId,
AuthState,
type SubagentFullscreenPanelState,
} from './types.js';
import { MessageType, StreamingState } from './types.js';
import {
Expand Down Expand Up @@ -149,6 +150,8 @@ export const AppContainer = (props: AppContainerProps) => {
initializationResult.geminiMdFileCount,
);
const [shellModeActive, setShellModeActive] = useState(false);
const [subagentFullscreenPanel, setSubagentFullscreenPanel] =
useState<SubagentFullscreenPanelState | null>(null);
const [modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError] =
useState<boolean>(false);
const [historyRemountKey, setHistoryRemountKey] = useState(0);
Expand Down Expand Up @@ -450,6 +453,46 @@ export const AppContainer = (props: AppContainerProps) => {
closeAgentsManagerDialog,
} = useAgentsManagerDialog();

const openSubagentFullscreenPanel = useCallback(
(panel: SubagentFullscreenPanelState) => {
setSubagentFullscreenPanel(panel);
},
[],
);

const closeSubagentFullscreenPanel = useCallback((panelId: string) => {
setSubagentFullscreenPanel((current) => {
if (!current || current.panelId !== panelId) {
return current;
}
current.onClose?.();
return null;
});
}, []);

const updateSubagentFullscreenPanel = useCallback(
(
panelId: string,
updates: Partial<
Pick<
SubagentFullscreenPanelState,
'content' | 'status' | 'subagentName'
>
>,
) => {
setSubagentFullscreenPanel((current) => {
if (!current || current.panelId !== panelId) {
return current;
}
return {
...current,
...updates,
};
});
},
[],
);

// Vision model auto-switch dialog state (must be before slashCommandActions)
const [isVisionSwitchDialogOpen, setIsVisionSwitchDialogOpen] =
useState(false);
Expand Down Expand Up @@ -730,7 +773,8 @@ export const AppContainer = (props: AppContainerProps) => {
!isProcessing &&
(streamingState === StreamingState.Idle ||
streamingState === StreamingState.Responding) &&
!proQuotaRequest;
!proQuotaRequest &&
!subagentFullscreenPanel;

const [controlsHeight, setControlsHeight] = useState(0);

Expand Down Expand Up @@ -1294,6 +1338,7 @@ export const AppContainer = (props: AppContainerProps) => {
// Subagent dialogs
isSubagentCreateDialogOpen,
isAgentsManagerDialogOpen,
subagentFullscreenPanel,
}),
[
isThemeDialogOpen,
Expand Down Expand Up @@ -1389,6 +1434,7 @@ export const AppContainer = (props: AppContainerProps) => {
// Subagent dialogs
isSubagentCreateDialogOpen,
isAgentsManagerDialogOpen,
subagentFullscreenPanel,
],
);

Expand Down Expand Up @@ -1427,6 +1473,9 @@ export const AppContainer = (props: AppContainerProps) => {
// Subagent dialogs
closeSubagentCreateDialog,
closeAgentsManagerDialog,
openSubagentFullscreenPanel,
closeSubagentFullscreenPanel,
updateSubagentFullscreenPanel,
}),
[
handleThemeSelect,
Expand Down Expand Up @@ -1460,6 +1509,9 @@ export const AppContainer = (props: AppContainerProps) => {
// Subagent dialogs
closeSubagentCreateDialog,
closeAgentsManagerDialog,
openSubagentFullscreenPanel,
closeSubagentFullscreenPanel,
updateSubagentFullscreenPanel,
],
);

Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/ui/components/Composer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
errorCount: 0,
nightly: false,
isTrustedFolder: true,
subagentFullscreenPanel: null,
...overrides,
}) as UIState;

Expand All @@ -134,6 +135,9 @@ const createMockUIActions = (): UIActions =>
setShellModeActive: vi.fn(),
onEscapePromptChange: vi.fn(),
vimHandleInput: vi.fn(),
openSubagentFullscreenPanel: vi.fn(),
closeSubagentFullscreenPanel: vi.fn(),
updateSubagentFullscreenPanel: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;

Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/ui/components/Footer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
lastPromptTokenCount: 100,
},
branchName: defaultProps.branchName,
subagentFullscreenPanel: null,
...overrides,
}) as UIState;

Expand Down
Loading