Skip to content

Commit 3e2fcff

Browse files
committed
fix disable AI-Q new session during shallow activity
Block the logo new-session action while shallow chat streaming or HITL is active, but keep it available during deep research jobs. Made-with: Cursor
1 parent eb1ba38 commit 3e2fcff

File tree

4 files changed

+57
-6
lines changed

4 files changed

+57
-6
lines changed

frontends/ui/src/features/layout/components/AppBar.spec.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ describe('AppBar', () => {
8989
expect(onNewSession).toHaveBeenCalledOnce()
9090
})
9191

92+
test('disables new session button when shallow navigation is blocked', () => {
93+
render(<AppBar isAuthenticated={true} isNewSessionDisabled={true} />)
94+
95+
expect(screen.getByRole('button', { name: /create new session/i })).toBeDisabled()
96+
// Other action buttons remain enabled.
97+
expect(screen.getByRole('button', { name: /toggle sessions sidebar/i })).not.toBeDisabled()
98+
})
99+
92100
test('toggles sessions panel when menu button clicked', async () => {
93101
const user = userEvent.setup()
94102

frontends/ui/src/features/layout/components/AppBar.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ interface AppBarProps {
3535
}
3636
/** Callback when a new session is requested */
3737
onNewSession?: () => void
38+
/** Disable creating a new session while shallow research/HITL is active */
39+
isNewSessionDisabled?: boolean
3840
/** Callback when sign in is clicked */
3941
onSignIn?: () => void
4042
/** Callback when sign out is clicked */
@@ -51,6 +53,7 @@ export const AppBar: FC<AppBarProps> = ({
5153
authRequired = false,
5254
user,
5355
onNewSession,
56+
isNewSessionDisabled = false,
5457
onSignIn,
5558
onSignOut,
5659
}) => {
@@ -85,9 +88,9 @@ export const AppBar: FC<AppBarProps> = ({
8588
}, [])
8689

8790
const handleNewSessionClick = useCallback(() => {
88-
if (!isAuthenticated) return
91+
if (!isAuthenticated || isNewSessionDisabled) return
8992
onNewSession?.()
90-
}, [isAuthenticated, onNewSession])
93+
}, [isAuthenticated, isNewSessionDisabled, onNewSession])
9194

9295
const handleSignOut = useCallback(() => {
9396
setIsUserMenuOpen(false)
@@ -103,9 +106,13 @@ export const AppBar: FC<AppBarProps> = ({
103106
kind="tertiary"
104107
size="small"
105108
onClick={handleNewSessionClick}
106-
disabled={!isAuthenticated}
109+
disabled={!isAuthenticated || isNewSessionDisabled}
107110
aria-label="Create new session"
108-
title="Create new session"
111+
title={
112+
isNewSessionDisabled
113+
? 'Cannot create new session while shallow research is active'
114+
: 'Create new session'
115+
}
109116
>
110117
<Flex align="center" gap="density-lg">
111118
<Logo kind="logo-only" size="small" />

frontends/ui/src/features/layout/components/MainLayout.spec.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ vi.mock('@/features/chat', () => ({
3333
deleteConversation: mockDeleteConversation,
3434
deleteAllConversations: mockDeleteAllConversations,
3535
updateConversationTitle: mockUpdateConversationTitle,
36+
isStreaming: false,
37+
pendingInteraction: null,
3638
isDeepResearchStreaming: false,
3739
deepResearchOwnerConversationId: null,
3840
})),
@@ -58,10 +60,18 @@ vi.mock('../store', () => ({
5860

5961
// Mock child components
6062
vi.mock('./AppBar', () => ({
61-
AppBar: ({ sessionTitle, onNewSession }: { sessionTitle: string; onNewSession?: () => void }) => (
63+
AppBar: ({
64+
sessionTitle,
65+
onNewSession,
66+
isNewSessionDisabled,
67+
}: {
68+
sessionTitle: string
69+
onNewSession?: () => void
70+
isNewSessionDisabled?: boolean
71+
}) => (
6272
<>
6373
<div data-testid="app-bar">{sessionTitle}</div>
64-
<button type="button" onClick={onNewSession}>
74+
<button type="button" onClick={onNewSession} disabled={isNewSessionDisabled}>
6575
Header New Session
6676
</button>
6777
</>
@@ -127,6 +137,8 @@ describe('MainLayout', () => {
127137
deleteConversation: vi.fn(),
128138
deleteAllConversations: vi.fn(),
129139
updateConversationTitle: vi.fn(),
140+
isStreaming: false,
141+
pendingInteraction: null,
130142
isDeepResearchStreaming: false,
131143
deepResearchOwnerConversationId: null,
132144
} as unknown as ReturnType<typeof useChatStore>)
@@ -163,6 +175,26 @@ describe('MainLayout', () => {
163175
expect(mockCloseRightPanel).toHaveBeenCalledOnce()
164176
})
165177

178+
test('disables new session action while shallow streaming is active', () => {
179+
vi.mocked(useChatStore).mockReturnValue({
180+
currentConversation: { id: 'session-1', title: 'Test Session' },
181+
getUserConversations: vi.fn(() => []),
182+
selectConversation: vi.fn(),
183+
startNewSessionDraft: vi.fn(),
184+
deleteConversation: vi.fn(),
185+
deleteAllConversations: vi.fn(),
186+
updateConversationTitle: vi.fn(),
187+
isStreaming: true,
188+
pendingInteraction: null,
189+
isDeepResearchStreaming: false,
190+
deepResearchOwnerConversationId: null,
191+
} as unknown as ReturnType<typeof useChatStore>)
192+
193+
render(<MainLayout />)
194+
195+
expect(screen.getByRole('button', { name: /header new session/i })).toBeDisabled()
196+
})
197+
166198
test('adjusts chat width when details panel is open', () => {
167199
vi.mocked(useLayoutStore).mockReturnValue({
168200
rightPanel: 'research',

frontends/ui/src/features/layout/components/MainLayout.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export const MainLayout: FC<MainLayoutProps> = ({
6868
deleteConversation,
6969
deleteAllConversations,
7070
updateConversationTitle,
71+
isStreaming,
72+
pendingInteraction,
7173
isDeepResearchStreaming,
7274
deepResearchOwnerConversationId,
7375
} = useChatStore()
@@ -117,6 +119,7 @@ export const MainLayout: FC<MainLayoutProps> = ({
117119

118120
// Check if research panel is open (pushes content instead of overlaying)
119121
const isResearchPanelOpen = rightPanel === 'research'
122+
const isNavigationBlocked = isStreaming || pendingInteraction !== null
120123

121124
// Get only conversations for the current authenticated user
122125
const userConversations = getUserConversations()
@@ -142,6 +145,7 @@ export const MainLayout: FC<MainLayoutProps> = ({
142145
authRequired={authRequired}
143146
user={user}
144147
onNewSession={handleNewSession}
148+
isNewSessionDisabled={isNavigationBlocked}
145149
onSignIn={onSignIn}
146150
onSignOut={onSignOut}
147151
/>

0 commit comments

Comments
 (0)