Skip to content

feat(studio): Add a pop out window for the Studio Code Agent#422

Open
htolentino-nvidia wants to merge 1 commit into
mainfrom
studio-code-agent-pop-out-chat/htolentino
Open

feat(studio): Add a pop out window for the Studio Code Agent#422
htolentino-nvidia wants to merge 1 commit into
mainfrom
studio-code-agent-pop-out-chat/htolentino

Conversation

@htolentino-nvidia

@htolentino-nvidia htolentino-nvidia commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a compact Code Agent chat pop-out accessible from the top bar for quick interactions.
    • Introduced a dedicated Code Agent chat workspace route for comprehensive chat sessions.
    • Automatic session persistence: your last active chat is restored when you return.
    • Session management enables loading previous Claude Code chats or starting fresh sessions.
    • Visual status indicators display agent thinking progress and unread responses in the compact chat view.

@htolentino-nvidia htolentino-nvidia requested review from a team as code owners June 23, 2026 20:10
@github-actions github-actions Bot added the feat label Jun 23, 2026
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces a shared ClaudeCodeChatProvider context that owns a single chat runtime per workspace, backed by localStorage active-session persistence. ClaudeCodeChatRoute is refactored to consume context instead of instantiating its own runtime. A new ClaudeCodeTopBarChat pop-out is added and conditionally mounted in GlobalNav based on route. DashboardLandingRoute clears stored sessions on submit.

Changes

Claude Code Shared Runtime and Top-Bar Pop-out

Layer / File(s) Summary
localStorage helpers and runtime primitives
src/util/localStorage.ts, src/routes/agents/ClaudeCodeChatRoute/activeSessionStorage.ts, src/routes/agents/ClaudeCodeChatRoute/useCustomAssistantChatRuntime.ts, src/routes/agents/ClaudeCodeChatRoute/useClaudeCodeChatRuntime.ts, src/routes/agents/ClaudeCodeChatRoute/useClaudeCodeChatRuntime.test.ts
Adds CLAUDE_CODE_ACTIVE_SESSION_KEY_PREFIX and three localStorage helpers. Extends useCustomAssistantChatRuntime with replaceMessages and extends useClaudeCodeChatRuntime with onSessionIdChange, studioPathname, loadSession, and the ClaudeCodeChatRuntime type alias.
ClaudeCodeChatContext and Provider
src/routes/agents/ClaudeCodeChatRoute/context/useClaudeCodeChatContext.ts, src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.tsx, src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.test.tsx
Defines ClaudeCodeChatLoadStatus, ClaudeCodeChatContextValue, context, and hook. ClaudeCodeChatProvider owns the per-workspace runtime, restores the last stored session on mount, loads sessions with stale-request protection, and exposes chat/loadStatus/loadSession/startNewChat.
PageLayout wraps ClaudeCodeChatProvider
src/routes/PageLayout/index.tsx
Calls useWorkspaceFromPathIfExists, extracts layout to a constant, and conditionally wraps it in ClaudeCodeChatProvider when CODING_AGENT_STUDIO_ENABLED is true and workspace is present.
ClaudeCodeChatThread extracted component
src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeChatThread.tsx
Extracts a reusable ClaudeCodeChatThread component with auto-scroll, composerOverride switching between AgentDecisionInput and BlockingInputComposer, and AssistantRuntimeProvider/AssistantChatThread wiring.
ClaudeCodeChatRoute refactored to use context
src/routes/agents/ClaudeCodeChatRoute/index.tsx, src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeLayout.tsx, src/routes/agents/ClaudeCodeChatRoute/index.test.tsx
Route drops local runtime, reads from useClaudeCodeChatContext, adds effects for URL-selected session loading and one-time initialPrompt consumption, renders ClaudeCodeLayout + ClaudeCodeChatThread. ClaudeCodeLayout gains optional onNewChat prop.
DashboardLandingRoute clears active session on submit
src/routes/DashboardLandingRoute/index.tsx, src/routes/DashboardLandingRoute/index.test.tsx
handleSubmit calls writeStoredActiveSessionId(workspace, null) before navigating to the Claude Code chat route. Test seeds localStorage and asserts removal.
ClaudeCodeTopBarChat pop-out component
src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeTopBarChat.tsx, src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeTopBarChat.test.tsx
New Popover pop-out rendering ClaudeCodeChatThread in compact mode with backdrop portal, running/unread/inputRequest badges, new-chat/open-main/close actions, and studio feature gating.
GlobalNav conditionally mounts ClaudeCodeTopBarChat
src/components/Layouts/GlobalNav/index.tsx, src/components/Layouts/GlobalNav/index.test.tsx
useLocation + matchPath derives shouldMountClaudeCodeTopBarChat (false on dashboard and full Claude Code chat routes). Tests assert presence/absence per route.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GlobalNav
  participant TopBarChat as ClaudeCodeTopBarChat
  participant Provider as ClaudeCodeChatProvider
  participant Runtime as useClaudeCodeChatRuntime
  participant Storage as activeSessionStorage
  participant API as fetchSessionHistory

  rect rgba(100, 149, 237, 0.5)
    note over GlobalNav,TopBarChat: App load / route change
    GlobalNav->>GlobalNav: matchPath → shouldMountClaudeCodeTopBarChat
    GlobalNav->>TopBarChat: render (when not dashboard/full-chat route)
  end

  rect rgba(144, 238, 144, 0.5)
    note over Provider,Storage: Provider mount
    Provider->>Storage: readStoredActiveSessionId(workspace)
    Storage-->>Provider: sessionId
    Provider->>API: fetchSessionHistory(sessionId)
    API-->>Provider: history
    Provider->>Runtime: loadSession(sessionId, artifacts, messages)
  end

  rect rgba(255, 165, 0, 0.5)
    note over User,Provider: User opens top-bar pop-out and starts new chat
    User->>TopBarChat: click open
    User->>TopBarChat: click New chat
    TopBarChat->>Provider: startNewChat()
    Provider->>Runtime: handleReset()
    Runtime->>Storage: writeStoredActiveSessionId(workspace, null)
  end
Loading

Possibly related PRs

  • NVIDIA-NeMo/nemo-platform#144: Wired the initial DashboardLandingRouteROUTES.workspace.claudeCodeChat navigation that this PR extends by clearing stored active sessions on submit.
  • NVIDIA-NeMo/nemo-platform#153: Both PRs heavily modify ClaudeCodeChatRoute/index.tsx—the retrieved PR added session history fetching that this PR now moves into ClaudeCodeChatProvider.
  • NVIDIA-NeMo/nemo-platform#155: This PR's ClaudeCodeChatThread uses composerOverride/showRunningIndicator props introduced in PR #155's AssistantChatThread API changes.

Suggested labels

feat

Suggested reviewers

  • dmariali
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main change: adding a pop-out chat UI for the Studio Code Agent, which is the primary feature across the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch studio-code-agent-pop-out-chat/htolentino

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
web/packages/studio/src/components/Layouts/GlobalNav/index.test.tsx (1)

200-208: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test for session-scoped full-chat paths

Current cases only check the base Claude route. Add one case for a session URL to prevent regressions in top-bar mount gating.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/studio/src/components/Layouts/GlobalNav/index.test.tsx` around
lines 200 - 208, Add a new test case after the existing test to verify that
GlobalNav does not mount on session-scoped full-chat paths. Create a similar
test structure to the current one in the renderGlobalNav function, but instead
of using just the base ROUTES.workspace.claudeCodeChat route, pass a
session-scoped full-chat path (such as one that includes a session ID in the URL
parameters) to ensure the top-bar chat element is not mounted for
session-specific chat routes as well.
web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/index.test.tsx (1)

44-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression assertion that onNewChat is passed to ClaudeCodeLayout.

Current tests won’t catch the missing onNewChat wiring from the route to layout.

Suggested test hardening
 vi.mock('`@studio/routes/agents/ClaudeCodeChatRoute/ClaudeCodeLayout`', () => ({
   ClaudeCodeLayout: ({
     activeSessionId,
+    onNewChat,
     children,
   }: {
     activeSessionId?: string;
+    onNewChat?: () => void;
     children: ReactNode;
   }) => (
-    <div data-active-session-id={activeSessionId ?? ''} data-testid="chat-layout">
+    <div
+      data-active-session-id={activeSessionId ?? ''}
+      data-has-on-new-chat={String(Boolean(onNewChat))}
+      data-testid="chat-layout"
+    >
       {children}
     </div>
   ),
 }));
   it('renders the chat for the active session once loaded', () => {
     mocks.chat.sessionId = 'session-existing';

     renderClaudeCodeChatRoute({ search: '?session=session-existing' });

     expect(screen.getByTestId('chat-thread')).toBeInTheDocument();
     expect(screen.getByTestId('chat-layout')).toHaveAttribute(
       'data-active-session-id',
       'session-existing'
     );
+    expect(screen.getByTestId('chat-layout')).toHaveAttribute('data-has-on-new-chat', 'true');
   });

Also applies to: 107-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/index.test.tsx`
around lines 44 - 56, The ClaudeCodeLayout mock function does not capture or
verify the onNewChat prop, which means tests cannot catch if this prop is
missing from the route implementation. Update the mock for ClaudeCodeLayout to
accept onNewChat as part of the destructured props (along with activeSessionId
and children), and then add test assertions to verify that onNewChat is actually
being passed to the ClaudeCodeLayout component when it's rendered. This ensures
that any future changes that remove or forget to wire onNewChat from the route
will be caught by the test suite.
web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.test.tsx (1)

64-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for “load then reset” race.

Current tests don’t assert that an in-flight loadSession cannot apply after startNewChat. This is the critical stale-request edge for this provider.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.test.tsx`
around lines 64 - 97, Add a new test case in the ClaudeCodeChatProvider.test.tsx
file that validates the race condition between loadSession and startNewChat. The
test should: set up mocks for getClaudeCodeSessionHistory to simulate an
in-flight request, initiate a loadSession action, immediately call startNewChat
before the loadSession promise resolves, then verify that applySession is never
called or is called with the expected new session state (not the stale loaded
session). This ensures that stale responses from loadSession cannot overwrite
state after startNewChat has been invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/packages/studio/src/components/Layouts/GlobalNav/index.tsx`:
- Around line 32-34: The matchPath call on line 33 with end: true performs an
exact match on the pathname, which fails when the Claude code chat URL includes
additional segments like session IDs, causing line 34 to incorrectly set
shouldMountClaudeCodeTopBarChat to true on full chat pages. Remove the end: true
parameter from the matchPath call for ROUTES.workspace.claudeCodeChat so it
matches any pathname that starts with the Claude code chat route, regardless of
additional path segments that follow it.

In
`@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.tsx`:
- Around line 58-59: The requestedSessionIdRef is not being cleared when
startNewChat executes, which allows pending loadSession requests from the
previous session to complete and unexpectedly rehydrate the old session after a
reset. Clear or invalidate requestedSessionIdRef (set to null or undefined) at
the beginning of the startNewChat function to cancel any pending requests, and
apply the same fix to the other reset/new chat scenario referenced at lines
95-97 to ensure consistency across all places where a new chat session is
initiated.

In `@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/index.tsx`:
- Around line 103-107: The ClaudeCodeLayout component at line 103 is missing the
onNewChat callback prop, which prevents the "New Chat" button in the history
panel from resetting the chat state. Add the onNewChat prop to the
ClaudeCodeLayout component and wire it to the appropriate reset handler (likely
startNewChat or handleChatReset) so that clicking "New Chat" properly resets the
shared chat runtime instead of only navigating.

In
`@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/useClaudeCodeChatRuntime.ts`:
- Around line 565-576: The `loadSession` callback function does not cancel any
active/in-flight runs before loading a new session. This allows old stream
events to corrupt the newly loaded session state if a user loads history while a
run is still executing. Add a call to cancel active runs (such as a `cancelRun`
or similar method that stops in-flight streams) at the very beginning of the
`loadSession` callback body, before any state updates like `setSessionId`,
`setArtifacts`, or `replaceMessages` are called.

In `@web/packages/studio/src/routes/PageLayout/index.tsx`:
- Around line 50-52: The ClaudeCodeChatProvider component is being reused when
the workspace changes, which allows internal state to persist across workspace
boundaries. Add a key prop to the ClaudeCodeChatProvider component that uniquely
identifies each workspace (such as workspace.id or workspace.name) to force
React to destroy and recreate the provider instance whenever the workspace value
changes, preventing state from bleeding between workspaces.

---

Nitpick comments:
In `@web/packages/studio/src/components/Layouts/GlobalNav/index.test.tsx`:
- Around line 200-208: Add a new test case after the existing test to verify
that GlobalNav does not mount on session-scoped full-chat paths. Create a
similar test structure to the current one in the renderGlobalNav function, but
instead of using just the base ROUTES.workspace.claudeCodeChat route, pass a
session-scoped full-chat path (such as one that includes a session ID in the URL
parameters) to ensure the top-bar chat element is not mounted for
session-specific chat routes as well.

In
`@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.test.tsx`:
- Around line 64-97: Add a new test case in the ClaudeCodeChatProvider.test.tsx
file that validates the race condition between loadSession and startNewChat. The
test should: set up mocks for getClaudeCodeSessionHistory to simulate an
in-flight request, initiate a loadSession action, immediately call startNewChat
before the loadSession promise resolves, then verify that applySession is never
called or is called with the expected new session state (not the stale loaded
session). This ensures that stale responses from loadSession cannot overwrite
state after startNewChat has been invoked.

In `@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/index.test.tsx`:
- Around line 44-56: The ClaudeCodeLayout mock function does not capture or
verify the onNewChat prop, which means tests cannot catch if this prop is
missing from the route implementation. Update the mock for ClaudeCodeLayout to
accept onNewChat as part of the destructured props (along with activeSessionId
and children), and then add test assertions to verify that onNewChat is actually
being passed to the ClaudeCodeLayout component when it's rendered. This ensures
that any future changes that remove or forget to wire onNewChat from the route
will be caught by the test suite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 48b00c48-b875-4635-8c05-378062f630d5

📥 Commits

Reviewing files that changed from the base of the PR and between 41e97a0 and 5a2d2d1.

📒 Files selected for processing (19)
  • web/packages/studio/src/components/Layouts/GlobalNav/index.test.tsx
  • web/packages/studio/src/components/Layouts/GlobalNav/index.tsx
  • web/packages/studio/src/routes/DashboardLandingRoute/index.test.tsx
  • web/packages/studio/src/routes/DashboardLandingRoute/index.tsx
  • web/packages/studio/src/routes/PageLayout/index.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeChatThread.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeLayout.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeTopBarChat.test.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/ClaudeCodeTopBarChat.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/activeSessionStorage.ts
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.test.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/useClaudeCodeChatContext.ts
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/index.test.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/index.tsx
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/useClaudeCodeChatRuntime.test.ts
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/useClaudeCodeChatRuntime.ts
  • web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/useCustomAssistantChatRuntime.ts
  • web/packages/studio/src/util/localStorage.ts

Comment on lines +32 to +34
const isClaudeCodeChatRoute =
matchPath({ path: ROUTES.workspace.claudeCodeChat, end: true }, location.pathname) !== null;
const shouldMountClaudeCodeTopBarChat = !isDashboardRoute && !isClaudeCodeChatRoute;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

matchPath(..., end: true) can misclassify full chat subroutes

If the full chat URL includes a session segment, Line 33 won’t match and Line 34 will mount the compact pop-out on full-chat pages. Use a non-exact match for the Claude route family.

Suggested fix
-  const isClaudeCodeChatRoute =
-    matchPath({ path: ROUTES.workspace.claudeCodeChat, end: true }, location.pathname) !== null;
+  const isClaudeCodeChatRoute =
+    matchPath({ path: ROUTES.workspace.claudeCodeChat, end: false }, location.pathname) !== null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isClaudeCodeChatRoute =
matchPath({ path: ROUTES.workspace.claudeCodeChat, end: true }, location.pathname) !== null;
const shouldMountClaudeCodeTopBarChat = !isDashboardRoute && !isClaudeCodeChatRoute;
const isClaudeCodeChatRoute =
matchPath({ path: ROUTES.workspace.claudeCodeChat, end: false }, location.pathname) !== null;
const shouldMountClaudeCodeTopBarChat = !isDashboardRoute && !isClaudeCodeChatRoute;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/studio/src/components/Layouts/GlobalNav/index.tsx` around lines
32 - 34, The matchPath call on line 33 with end: true performs an exact match on
the pathname, which fails when the Claude code chat URL includes additional
segments like session IDs, causing line 34 to incorrectly set
shouldMountClaudeCodeTopBarChat to true on full chat pages. Remove the end: true
parameter from the matchPath call for ROUTES.workspace.claudeCodeChat so it
matches any pathname that starts with the Claude code chat route, regardless of
additional path segments that follow it.

Comment on lines +58 to +59
requestedSessionIdRef.current = trimmedSessionId;
setLoadStatus('loading');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate pending loadSession requests on reset/new chat.

requestedSessionIdRef is not cleared when startNewChat runs. A slow history fetch can complete after reset and rehydrate an old session unexpectedly.

Suggested fix
+  const startNewChat = useCallback(() => {
+    requestedSessionIdRef.current = null;
+    setLoadStatus('idle');
+    handleReset();
+  }, [handleReset]);
+
   const value = useMemo(
-    () => ({ chat, loadStatus, loadSession, startNewChat: handleReset }),
-    [chat, handleReset, loadSession, loadStatus]
+    () => ({ chat, loadStatus, loadSession, startNewChat }),
+    [chat, loadStatus, loadSession, startNewChat]
   );

Also applies to: 95-97

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/context/ClaudeCodeChatProvider.tsx`
around lines 58 - 59, The requestedSessionIdRef is not being cleared when
startNewChat executes, which allows pending loadSession requests from the
previous session to complete and unexpectedly rehydrate the old session after a
reset. Clear or invalidate requestedSessionIdRef (set to null or undefined) at
the beginning of the startNewChat function to cancel any pending requests, and
apply the same fix to the other reset/new chat scenario referenced at lines
95-97 to ensure consistency across all places where a new chat session is
initiated.

Comment on lines +103 to +107
<ClaudeCodeLayout activeSessionId={sessionId ?? undefined} artifacts={artifacts}>
<AccessibleTitle title={`Code Agent chat for ${workspace}`}>
<Stack className="h-full w-full py-density-lg">
<Stack className="min-h-0 w-full flex-1">
<AssistantRuntimeProvider runtime={runtime}>
<AssistantChatThread
contentClassName="mx-auto w-full max-w-180 px-density-2xl"
composerContainerClassName="mx-auto w-full max-w-180 px-density-2xl"
viewportClassName={CHAT_VIEWPORT_SCROLLBAR_CLASS}
hideAssistantMessageActions
toolCallPartComponent={ClaudeCodeToolCallPart}
attributes={{
ThreadViewport: {
ref: chatViewportRef,
},
}}
placeholder="Ask Claude Code to work in this workspace"
onReset={handleChatReset}
showRunningIndicator={!decisionRequest && !inputRequest}
messageContentProps={{ markdownLinkComponent: ClaudeCodeStudioLink }}
emptyState={{
slotHeading: 'Start a Claude Code session',
slotSubheading: 'Ask Claude Code to work in this workspace.',
}}
composerOverride={
decisionRequest ? (
<AgentDecisionInput
request={decisionRequest}
choices={decisionChoices}
defaultChoiceId={decisionChoices[0]?.id}
status={decisionStatus}
onSubmit={resolveDecisionRequest}
onSkip={skipDecisionRequest}
/>
) : inputRequest ? (
<BlockingInputComposer
inputRequest={inputRequest}
inputStatus={inputStatus}
workspace={workspace}
onSubmit={handleInputSubmit}
onSkip={skipInputRequest}
/>
) : undefined
}
/>
</AssistantRuntimeProvider>
<ClaudeCodeChatThread chat={chat} onReset={handleChatReset} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wire startNewChat into ClaudeCodeLayout to make “New Chat” actually reset state.

At Line 103, ClaudeCodeLayout is rendered without onNewChat. In ClaudeCodeLayout, the history-panel “New Chat” path only invokes reset logic through that callback, so current behavior navigates without resetting the shared chat runtime.

Suggested fix
-  return (
-    <ClaudeCodeLayout activeSessionId={sessionId ?? undefined} artifacts={artifacts}>
+  return (
+    <ClaudeCodeLayout
+      activeSessionId={sessionId ?? undefined}
+      artifacts={artifacts}
+      onNewChat={startNewChat}
+    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ClaudeCodeLayout activeSessionId={sessionId ?? undefined} artifacts={artifacts}>
<AccessibleTitle title={`Code Agent chat for ${workspace}`}>
<Stack className="h-full w-full py-density-lg">
<Stack className="min-h-0 w-full flex-1">
<AssistantRuntimeProvider runtime={runtime}>
<AssistantChatThread
contentClassName="mx-auto w-full max-w-180 px-density-2xl"
composerContainerClassName="mx-auto w-full max-w-180 px-density-2xl"
viewportClassName={CHAT_VIEWPORT_SCROLLBAR_CLASS}
hideAssistantMessageActions
toolCallPartComponent={ClaudeCodeToolCallPart}
attributes={{
ThreadViewport: {
ref: chatViewportRef,
},
}}
placeholder="Ask Claude Code to work in this workspace"
onReset={handleChatReset}
showRunningIndicator={!decisionRequest && !inputRequest}
messageContentProps={{ markdownLinkComponent: ClaudeCodeStudioLink }}
emptyState={{
slotHeading: 'Start a Claude Code session',
slotSubheading: 'Ask Claude Code to work in this workspace.',
}}
composerOverride={
decisionRequest ? (
<AgentDecisionInput
request={decisionRequest}
choices={decisionChoices}
defaultChoiceId={decisionChoices[0]?.id}
status={decisionStatus}
onSubmit={resolveDecisionRequest}
onSkip={skipDecisionRequest}
/>
) : inputRequest ? (
<BlockingInputComposer
inputRequest={inputRequest}
inputStatus={inputStatus}
workspace={workspace}
onSubmit={handleInputSubmit}
onSkip={skipInputRequest}
/>
) : undefined
}
/>
</AssistantRuntimeProvider>
<ClaudeCodeChatThread chat={chat} onReset={handleChatReset} />
<ClaudeCodeLayout
activeSessionId={sessionId ?? undefined}
artifacts={artifacts}
onNewChat={startNewChat}
>
<AccessibleTitle title={`Code Agent chat for ${workspace}`}>
<Stack className="h-full w-full py-density-lg">
<Stack className="min-h-0 w-full flex-1">
<ClaudeCodeChatThread chat={chat} onReset={handleChatReset} />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/index.tsx` around
lines 103 - 107, The ClaudeCodeLayout component at line 103 is missing the
onNewChat callback prop, which prevents the "New Chat" button in the history
panel from resetting the chat state. Add the onNewChat prop to the
ClaudeCodeLayout component and wire it to the appropriate reset handler (likely
startNewChat or handleChatReset) so that clicking "New Chat" properly resets the
shared chat runtime instead of only navigating.

Comment on lines +565 to +576
const loadSession = useCallback(
({ artifacts: nextArtifacts, messages, sessionId: nextSessionId }: LoadClaudeCodeSessionOptions) => {
sessionIdRef.current = nextSessionId;
setSessionId(nextSessionId);
onSessionIdChange?.(nextSessionId);
setArtifacts(createWorkspaceArtifacts(nextArtifacts, workspace));
clearPermissionRequest();
clearInputRequest();
replaceMessages(messages);
},
[clearInputRequest, clearPermissionRequest, onSessionIdChange, replaceMessages, workspace]
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Cancel active runs before loadSession hydration.

Lines 565-574 hydrate sessionId/messages/artifacts without stopping an in-flight stream. If a user loads history while running, old-stream events can append into the newly loaded chat and corrupt session state.

Suggested fix
   const loadSession = useCallback(
     ({ artifacts: nextArtifacts, messages, sessionId: nextSessionId }: LoadClaudeCodeSessionOptions) => {
+      // Abort any in-flight run before hydrating a different session.
+      resetThread();
       sessionIdRef.current = nextSessionId;
       setSessionId(nextSessionId);
       onSessionIdChange?.(nextSessionId);
       setArtifacts(createWorkspaceArtifacts(nextArtifacts, workspace));
       clearPermissionRequest();
       clearInputRequest();
       replaceMessages(messages);
     },
-    [clearInputRequest, clearPermissionRequest, onSessionIdChange, replaceMessages, workspace]
+    [
+      clearInputRequest,
+      clearPermissionRequest,
+      onSessionIdChange,
+      replaceMessages,
+      resetThread,
+      workspace,
+    ]
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/packages/studio/src/routes/agents/ClaudeCodeChatRoute/useClaudeCodeChatRuntime.ts`
around lines 565 - 576, The `loadSession` callback function does not cancel any
active/in-flight runs before loading a new session. This allows old stream
events to corrupt the newly loaded session state if a user loads history while a
run is still executing. Add a call to cancel active runs (such as a `cancelRun`
or similar method that stops in-flight streams) at the very beginning of the
`loadSession` callback body, before any state updates like `setSessionId`,
`setArtifacts`, or `replaceMessages` are called.

Comment on lines +50 to +52
{CODING_AGENT_STUDIO_ENABLED && workspace ? (
<ClaudeCodeChatProvider workspace={workspace}>{layout}</ClaudeCodeChatProvider>
) : (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Key ClaudeCodeChatProvider by workspace to prevent cross-workspace state bleed.

Lines 50-52 reuse one provider instance as workspace changes. That can carry session/runtime state across workspace boundaries.

Suggested fix
       {CODING_AGENT_STUDIO_ENABLED && workspace ? (
-        <ClaudeCodeChatProvider workspace={workspace}>{layout}</ClaudeCodeChatProvider>
+        <ClaudeCodeChatProvider key={workspace} workspace={workspace}>
+          {layout}
+        </ClaudeCodeChatProvider>
       ) : (
         layout
       )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{CODING_AGENT_STUDIO_ENABLED && workspace ? (
<ClaudeCodeChatProvider workspace={workspace}>{layout}</ClaudeCodeChatProvider>
) : (
{CODING_AGENT_STUDIO_ENABLED && workspace ? (
<ClaudeCodeChatProvider key={workspace} workspace={workspace}>
{layout}
</ClaudeCodeChatProvider>
) : (
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/studio/src/routes/PageLayout/index.tsx` around lines 50 - 52,
The ClaudeCodeChatProvider component is being reused when the workspace changes,
which allows internal state to persist across workspace boundaries. Add a key
prop to the ClaudeCodeChatProvider component that uniquely identifies each
workspace (such as workspace.id or workspace.name) to force React to destroy and
recreate the provider instance whenever the workspace value changes, preventing
state from bleeding between workspaces.

@github-actions

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 20898/27461 76.1% 61.1%
Integration Tests 12133/26230 46.3% 19.8%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant