Skip to content
Merged
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
21 changes: 15 additions & 6 deletions .ai/todo.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
# PR #16 main conflict resolution
# Codex plan approval backend fix

- [x] Merge main 2e23c71d, retain managed Claude installer and upstream settings behavior.
- [x] Resolve icon migration and removed smoke-test import.
- [x] Full check:all: 2,876 frontend tests, 1,339 Rust tests passed (one ignored); jean:dev rebuilt.
- [x] Agent-browser smoke check: Settings → Claude shows Install latest with PATH selected.
- [x] Pushed merge 342c6083 to open PR #16; GitHub reports MERGEABLE.
- [x] Add focused regression test for active-chat YOLO approval backend.
- [x] Pass active backend through persisted and streaming Build/YOLO approval sends.
- [x] Run targeted tests and frontend gates; `check:all` reaches Rust then stops because local Cargo is unavailable (covered by Docker build).
- [x] Build `jean:dev` and smoke-test the built UI with agent-browser at 1440×1000.
- [x] Review diff, commit, push, and create/update PR.

## Review

- Regression test failed before the fix because the YOLO payload omitted `backend`, then passed with `backend: codex`.
- Active-chat persisted and streaming Build/YOLO paths now pass the backend captured with the approved session.
- Fresh review caught and fixed an async ref race; the regression now switches the active backend while persistence is pending.
- Typecheck, lint, targeted tests, and all 2,877 frontend tests pass (full suite used a 10s timeout after two unrelated tests exceeded 5s under load; both also passed in isolation).
- `make docker-build` produced `jean:dev`; agent-browser confirmed the built UI renders cleanly at 1440×1000.
- `bun run check:all` cannot run local Rust commands because Cargo is unavailable; the Docker build compiled the Rust release binary successfully.
111 changes: 111 additions & 0 deletions src/components/chat/hooks/useMessageHandlers.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { PropsWithChildren, RefObject } from 'react'
import { act, renderHook, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { EffortLevel, ExecutionMode, ThinkingLevel } from '@/types/chat'
import type { CliBackend } from '@/types/preferences'
import { useChatStore } from '@/store/chat-store'
import { useMessageHandlers } from './useMessageHandlers'

const mocks = vi.hoisted(() => ({
invoke: vi.fn(() => Promise.resolve()),
listen: vi.fn(),
markPlanApproved: vi.fn(() => Promise.resolve()),
}))

vi.mock('@/lib/transport', () => ({
invoke: mocks.invoke,
listen: mocks.listen,
}))

vi.mock('@/services/chat', async () => {
const actual = await vi.importActual('@/services/chat')
return {
...(actual as object),
markPlanApproved: mocks.markPlanApproved,
}
})

const ref = <T,>(current: T): RefObject<T> => ({ current })

function renderMessageHandlers() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
})
const sendMessage = { mutate: vi.fn() }
const selectedBackendRef = ref('codex' as CliBackend)
const wrapper = ({ children }: PropsWithChildren) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
const hook = renderHook(
() =>
useMessageHandlers({
activeSessionIdRef: ref('session-1'),
activeWorktreeIdRef: ref('worktree-1'),
activeWorktreePathRef: ref('/repo/worktree'),
selectedModelRef: ref('gpt-6.1-sol'),
buildModelRef: ref(null),
buildBackendRef: ref(null),
buildThinkingLevelRef: ref(null),
buildEffortLevelRef: ref(null),
yoloModelRef: ref(null),
yoloBackendRef: ref(null),
yoloThinkingLevelRef: ref(null),
yoloEffortLevelRef: ref(null),
selectedBackendRef,
getCustomProfileName: () => undefined,
executionModeRef: ref('plan' as ExecutionMode),
selectedThinkingLevelRef: ref('off' as ThinkingLevel),
selectedEffortLevelRef: ref('medium' as EffortLevel),
useAdaptiveThinkingRef: ref(true),
getMcpConfig: () => undefined,
sendMessage,
createSession: { mutateAsync: vi.fn() },
queryClient,
scrollToBottom: vi.fn(),
markAtBottom: vi.fn(),
inputRef: ref(null),
pendingPlanMessage: null,
projectIdRef: ref(null),
}),
{ wrapper }
)
return { hook, selectedBackendRef, sendMessage }
}

describe('useMessageHandlers plan approval', () => {
beforeEach(() => {
vi.clearAllMocks()
useChatStore.setState({
sendingSessionIds: {},
executionModes: {},
selectedModels: {},
lastSentMessages: {},
errors: {},
})
})

it('preserves the Codex backend when approving a plan in yolo mode', async () => {
let finishPersistence!: () => void
mocks.markPlanApproved.mockReturnValueOnce(
new Promise<void>(resolve => {
finishPersistence = resolve
})
)
const { hook, selectedBackendRef, sendMessage } = renderMessageHandlers()

act(() => hook.result.current.handlePlanApprovalYolo('plan-message-1'))
selectedBackendRef.current = 'claude'
finishPersistence()

await waitFor(() => expect(sendMessage.mutate).toHaveBeenCalled())
expect(sendMessage.mutate).toHaveBeenCalledWith(
expect.objectContaining({
model: 'gpt-6.1-sol',
backend: 'codex',
executionMode: 'yolo',
}),
expect.any(Object)
)
})
})
4 changes: 4 additions & 0 deletions src/components/chat/hooks/useMessageHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ export function useMessageHandlers({
worktreePath,
message,
model: buildModel,
backend: sessionBackend,
executionMode: 'build',
thinkingLevel: buildThinking,
effortLevel: useAdaptiveThinkingRef.current
Expand Down Expand Up @@ -917,6 +918,7 @@ export function useMessageHandlers({
worktreePath,
message,
model: yoloModel,
backend: sessionBackendYolo,
executionMode: 'yolo',
thinkingLevel: yoloThinking,
effortLevel: useAdaptiveThinkingRef.current
Expand Down Expand Up @@ -1031,6 +1033,7 @@ export function useMessageHandlers({
worktreePath,
message: buildApprovalMsg,
model: streamBuildModel,
backend: streamBuildSessionBackend,
executionMode: 'build',
thinkingLevel: streamBuildThinking,
effortLevel: useAdaptiveThinkingRef.current
Expand Down Expand Up @@ -1131,6 +1134,7 @@ export function useMessageHandlers({
worktreePath,
message: yoloApprovalMsg,
model: streamYoloModel,
backend: streamYoloSessionBackend,
executionMode: 'yolo',
thinkingLevel: streamYoloThinking,
effortLevel: useAdaptiveThinkingRef.current
Expand Down
Loading