Skip to content

Commit d133088

Browse files
jsell-rhclaude
andcommitted
fix(ambient-ui): address CodeRabbit review findings
- Dialog closes after mutation settles, not before (shows errors) - Send path honors disabled prop (blocks Enter key when loading) - Sidebar syncs with browser back/forward via popstate listener - Drag listeners cleaned up on unmount (no leaked event handlers) - Normalize tool_use.input to always be an object Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 813d5be commit d133088

5 files changed

Lines changed: 40 additions & 15 deletions

File tree

components/ambient-ui/src/app/(dashboard)/[projectId]/fleet/[sessionId]/_components/session-header.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,19 @@ export function SessionHeader({ session }: { session: DomainSession }) {
5050
const canRestart = RESTARTABLE_PHASES.has(session.phase)
5151

5252
const handleConfirmStop = useCallback(() => {
53-
stopSession.mutate(session.id)
54-
setStopDialogOpen(false)
53+
stopSession.mutate(session.id, {
54+
onSettled: () => setStopDialogOpen(false),
55+
})
5556
}, [stopSession, session.id])
5657

5758
const handleConfirmDelete = useCallback(() => {
5859
deleteSession.mutate(session.id, {
5960
onSuccess: () => {
61+
setDeleteDialogOpen(false)
6062
router.push(`/${projectId}/fleet`)
6163
},
64+
onError: () => setDeleteDialogOpen(false),
6265
})
63-
setDeleteDialogOpen(false)
6466
}, [deleteSession, session.id, router, projectId])
6567

6668
const handleExport = useCallback(async () => {

components/ambient-ui/src/components/chat-messages.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ export function ChatInput({ sessionId, phase, disabled }: ChatInputProps) {
352352

353353
const handleSend = useCallback(() => {
354354
const trimmed = input.trim()
355-
if (!trimmed || !isRunning || sendMessage.isPending) return
355+
if (!trimmed || !isRunning || disabled || sendMessage.isPending) return
356356
sendMessage.mutate(trimmed, {
357357
onSuccess: () => {
358358
setInput('')

components/ambient-ui/src/components/chat-sidebar-context.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
useContext,
66
useState,
77
useCallback,
8+
useEffect,
89
useMemo,
910
type ReactNode,
1011
} from 'react'
@@ -46,6 +47,12 @@ export function ChatSidebarProvider({ children }: { children: ReactNode }) {
4647
updateChatParam(null)
4748
}, [])
4849

50+
useEffect(() => {
51+
const handlePopState = () => setOpenSessionId(readChatParam())
52+
window.addEventListener('popstate', handlePopState)
53+
return () => window.removeEventListener('popstate', handlePopState)
54+
}, [])
55+
4956
const value = useMemo<ChatSidebarState>(
5057
() => ({
5158
openSessionId,

components/ambient-ui/src/components/chat-sidebar.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,28 @@ export function ChatSidebar() {
7373
hasScrolledOnLoad.current = false
7474
}, [openSessionId])
7575

76-
// Drag-to-resize handler
76+
// Drag-to-resize handler with cleanup refs
77+
const dragListenersRef = useRef<{ move: (e: MouseEvent) => void; up: () => void } | null>(null)
78+
79+
const cleanupDrag = useCallback(() => {
80+
if (dragListenersRef.current) {
81+
document.removeEventListener('mousemove', dragListenersRef.current.move)
82+
document.removeEventListener('mouseup', dragListenersRef.current.up)
83+
dragListenersRef.current = null
84+
}
85+
isDragging.current = false
86+
document.body.style.cursor = ''
87+
document.body.style.userSelect = ''
88+
}, [])
89+
90+
useEffect(() => {
91+
return cleanupDrag
92+
}, [cleanupDrag])
93+
7794
const handleMouseDown = useCallback(
7895
(e: React.MouseEvent) => {
7996
e.preventDefault()
97+
cleanupDrag()
8098
isDragging.current = true
8199
startX.current = e.clientX
82100
startWidth.current = width
@@ -90,18 +108,13 @@ export function ChatSidebar() {
90108
setWidth(newWidth)
91109
}
92110

93-
const handleMouseUp = () => {
94-
isDragging.current = false
95-
document.body.style.cursor = ''
96-
document.body.style.userSelect = ''
97-
document.removeEventListener('mousemove', handleMouseMove)
98-
document.removeEventListener('mouseup', handleMouseUp)
99-
}
111+
const handleMouseUp = () => cleanupDrag()
100112

113+
dragListenersRef.current = { move: handleMouseMove, up: handleMouseUp }
101114
document.addEventListener('mousemove', handleMouseMove)
102115
document.addEventListener('mouseup', handleMouseUp)
103116
},
104-
[width],
117+
[width, cleanupDrag],
105118
)
106119

107120
// Handle Escape key to close

components/runners/ambient-runner/ambient_runner/bridges/claude/operational_events.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ def _build_payload(
6363
payload: dict = {"tool": name, "tool_call_id": tool_id}
6464
if args:
6565
try:
66-
payload["input"] = json.loads(args)
66+
parsed = json.loads(args)
67+
payload["input"] = (
68+
parsed if isinstance(parsed, dict) else {"value": parsed}
69+
)
6770
except (json.JSONDecodeError, TypeError):
68-
payload["input"] = args
71+
payload["input"] = {"raw": args}
6972
return json.dumps(payload)
7073

7174
if event_type_str == "TOOL_CALL_RESULT":

0 commit comments

Comments
 (0)