diff --git a/src/__tests__/main/utils/cliDetection.test.ts b/src/__tests__/main/utils/cliDetection.test.ts index bd07c177..e7207eac 100644 --- a/src/__tests__/main/utils/cliDetection.test.ts +++ b/src/__tests__/main/utils/cliDetection.test.ts @@ -235,9 +235,7 @@ describe('cliDetection.ts', () => { }); describe('getCloudflaredPath', () => { - // Note: The path cache is separate from the installed cache. - // clearCloudflaredCache() only clears the installed cache, NOT the path cache. - // This means once a path is found, it persists until the module is reloaded. + // Note: clearCloudflaredCache() clears both the installed cache AND the path cache. it('should return the path after successful detection', async () => { mockedExecFileNoThrow.mockResolvedValue({ @@ -251,7 +249,7 @@ describe('cliDetection.ts', () => { expect(getCloudflaredPath()).toBe('/usr/bin/cloudflared'); }); - it('should NOT update path cache when detection fails after previous success', async () => { + it('should clear path when clearCloudflaredCache is called', async () => { // First, ensure we have a successful detection mockedExecFileNoThrow.mockResolvedValueOnce({ stdout: '/first/path/cloudflared\n', @@ -260,11 +258,14 @@ describe('cliDetection.ts', () => { }); clearCloudflaredCache(); await isCloudflaredInstalled(); - const firstPath = getCloudflaredPath(); + expect(getCloudflaredPath()).toBe('/first/path/cloudflared'); - // Clear the installed cache (not path cache) + // Clear the cache - this should clear both installed and path caches clearCloudflaredCache(); + // Path should be null after cache clear + expect(getCloudflaredPath()).toBeNull(); + // Now a failed detection mockedExecFileNoThrow.mockResolvedValueOnce({ stdout: '', @@ -273,9 +274,8 @@ describe('cliDetection.ts', () => { }); await isCloudflaredInstalled(); - // Path should still be the old value since the code doesn't clear it on failure - // This tests the ACTUAL behavior of the code - expect(getCloudflaredPath()).toBe(firstPath); + // Path should still be null since detection failed + expect(getCloudflaredPath()).toBeNull(); }); it('should update path when detection succeeds again with new path', async () => { diff --git a/src/__tests__/renderer/components/AgentSessionsBrowser.test.tsx b/src/__tests__/renderer/components/AgentSessionsBrowser.test.tsx index d0021ec8..5842bdb0 100644 --- a/src/__tests__/renderer/components/AgentSessionsBrowser.test.tsx +++ b/src/__tests__/renderer/components/AgentSessionsBrowser.test.tsx @@ -195,7 +195,9 @@ describe('AgentSessionsBrowser', () => { }; }); vi.mocked(window.maestro.claude.updateSessionStarred).mockResolvedValue(undefined); + vi.mocked(window.maestro.claude.updateSessionName).mockResolvedValue(undefined); vi.mocked(window.maestro.agentSessions.updateSessionName).mockResolvedValue(undefined); + vi.mocked(window.maestro.agentSessions.setSessionName).mockResolvedValue(undefined); }); afterEach(() => { @@ -1412,7 +1414,8 @@ describe('AgentSessionsBrowser', () => { await vi.runAllTimersAsync(); }); - expect(window.maestro.agentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + expect(window.maestro.claude.updateSessionName).toHaveBeenCalledWith( '/path/to/project', 'session-1', 'New Name' @@ -1460,7 +1463,8 @@ describe('AgentSessionsBrowser', () => { // Verify that "New Name" was NOT saved - if updateSessionName was called, // it should NOT have been called with 'New Name' - const calls = vi.mocked(window.maestro.agentSessions.updateSessionName).mock.calls; + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + const calls = vi.mocked(window.maestro.claude.updateSessionName).mock.calls; const savedWithNewName = calls.some(call => call[2] === 'New Name'); expect(savedWithNewName).toBe(false); }); @@ -1494,7 +1498,8 @@ describe('AgentSessionsBrowser', () => { await vi.runAllTimersAsync(); }); - expect(window.maestro.agentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + expect(window.maestro.claude.updateSessionName).toHaveBeenCalledWith( '/path/to/project', 'session-1', 'Blur Name' @@ -1533,7 +1538,8 @@ describe('AgentSessionsBrowser', () => { await vi.runAllTimersAsync(); }); - expect(window.maestro.agentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + expect(window.maestro.claude.updateSessionName).toHaveBeenCalledWith( '/path/to/project', 'session-1', '' @@ -1582,7 +1588,8 @@ describe('AgentSessionsBrowser', () => { }); // Should use projectRoot, NOT cwd - expect(window.maestro.agentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + expect(window.maestro.claude.updateSessionName).toHaveBeenCalledWith( '/path/to/project', // projectRoot, not '/path/to/project/some/subdir' 'session-1', 'New Name' @@ -2655,7 +2662,8 @@ describe('AgentSessionsBrowser', () => { await vi.runAllTimersAsync(); }); - expect(window.maestro.agentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + expect(window.maestro.claude.updateSessionName).toHaveBeenCalledWith( '/path/to/project', 'session-1', 'Detail View Name' diff --git a/src/__tests__/renderer/components/RenameSessionModal.test.tsx b/src/__tests__/renderer/components/RenameSessionModal.test.tsx index a6d6d229..14e5fc61 100644 --- a/src/__tests__/renderer/components/RenameSessionModal.test.tsx +++ b/src/__tests__/renderer/components/RenameSessionModal.test.tsx @@ -101,7 +101,11 @@ describe('RenameSessionModal', () => { // Setup window.maestro mock (window as unknown as { maestro: Record }).maestro = { + claude: { + updateSessionName: vi.fn().mockResolvedValue(undefined), + }, agentSessions: { + setSessionName: vi.fn().mockResolvedValue(undefined), updateSessionName: vi.fn().mockResolvedValue(undefined), }, }; @@ -538,7 +542,7 @@ describe('RenameSessionModal', () => { }); describe('Agent Session Name Update', () => { - it('updates agent session name when session has agentSessionId and cwd', () => { + it('updates agent session name when session has agentSessionId and projectRoot (claude-code)', () => { render( { fireEvent.click(screen.getByRole('button', { name: 'Rename' })); - expect((window as any).maestro.agentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + expect((window as any).maestro.claude.updateSessionName).toHaveBeenCalledWith( '/home/user/project', 'claude-123', 'New Name' @@ -579,7 +584,8 @@ describe('RenameSessionModal', () => { fireEvent.click(screen.getByRole('button', { name: 'Rename' })); - expect((window as any).maestro.agentSessions.updateSessionName).not.toHaveBeenCalled(); + expect((window as any).maestro.claude.updateSessionName).not.toHaveBeenCalled(); + expect((window as any).maestro.agentSessions.setSessionName).not.toHaveBeenCalled(); }); }); diff --git a/src/__tests__/renderer/components/TabSwitcherModal.test.tsx b/src/__tests__/renderer/components/TabSwitcherModal.test.tsx index 12c13080..2644c8e7 100644 --- a/src/__tests__/renderer/components/TabSwitcherModal.test.tsx +++ b/src/__tests__/renderer/components/TabSwitcherModal.test.tsx @@ -1641,7 +1641,8 @@ describe('TabSwitcherModal', () => { await waitFor(() => { // Should sync only the named tab - expect(window.maestro.agentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions (default), it uses window.maestro.claude.updateSessionName + expect(window.maestro.claude.updateSessionName).toHaveBeenCalledWith( '/test/project', 'session-123', 'Named Tab' @@ -1649,13 +1650,14 @@ describe('TabSwitcherModal', () => { }); // Should NOT sync the unnamed tab (only 1 call total) - expect(window.maestro.agentSessions.updateSessionName).toHaveBeenCalledTimes(1); + expect(window.maestro.claude.updateSessionName).toHaveBeenCalledTimes(1); }); it('handles sync errors gracefully', async () => { const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - vi.mocked(window.maestro.agentSessions.updateSessionName).mockRejectedValue( + // For claude-code sessions (default), it uses window.maestro.claude.updateSessionName + vi.mocked(window.maestro.claude.updateSessionName).mockRejectedValue( new Error('Sync failed') ); diff --git a/src/__tests__/renderer/hooks/useRemoteIntegration.test.ts b/src/__tests__/renderer/hooks/useRemoteIntegration.test.ts index 8afc99b5..6fa392c5 100644 --- a/src/__tests__/renderer/hooks/useRemoteIntegration.test.ts +++ b/src/__tests__/renderer/hooks/useRemoteIntegration.test.ts @@ -111,9 +111,15 @@ describe('useRemoteIntegration', () => { broadcastTabsChange: vi.fn(), }; + const mockClaude = { + ...window.maestro.claude, + updateSessionName: vi.fn().mockResolvedValue(undefined), + }; + const mockAgentSessions = { ...window.maestro.agentSessions, updateSessionName: vi.fn().mockResolvedValue(true), + setSessionName: vi.fn().mockResolvedValue(undefined), }; const mockHistory = { @@ -137,6 +143,7 @@ describe('useRemoteIntegration', () => { process: mockProcess as typeof window.maestro.process, live: mockLive as typeof window.maestro.live, web: mockWeb as typeof window.maestro.web, + claude: mockClaude as typeof window.maestro.claude, agentSessions: mockAgentSessions as typeof window.maestro.agentSessions, history: mockHistory as typeof window.maestro.history, }; @@ -468,12 +475,13 @@ describe('useRemoteIntegration', () => { }); describe('remote rename tab', () => { - it('renames tab and persists to agent session', () => { + it('renames tab and persists to agent session (claude-code)', () => { const tab = createMockTab({ id: 'tab-1', agentSessionId: 'agent-session-1' }); const session = createMockSession({ id: 'session-1', aiTabs: [tab], projectRoot: '/test/project', + toolType: 'claude-code', }); const deps = createDeps({ sessions: [session] }); @@ -484,7 +492,8 @@ describe('useRemoteIntegration', () => { }); expect(deps.setSessions).toHaveBeenCalled(); - expect(mockAgentSessions.updateSessionName).toHaveBeenCalledWith( + // For claude-code sessions, it uses window.maestro.claude.updateSessionName + expect(mockClaude.updateSessionName).toHaveBeenCalledWith( '/test/project', 'agent-session-1', 'New Tab Name' @@ -505,7 +514,8 @@ describe('useRemoteIntegration', () => { onRemoteRenameTabHandler?.('session-1', 'nonexistent', 'New Name'); }); - expect(mockAgentSessions.updateSessionName).not.toHaveBeenCalled(); + expect(mockClaude.updateSessionName).not.toHaveBeenCalled(); + expect(mockAgentSessions.setSessionName).not.toHaveBeenCalled(); }); }); diff --git a/src/__tests__/renderer/hooks/useSessionPagination.test.ts b/src/__tests__/renderer/hooks/useSessionPagination.test.ts index b8e0b282..8b44fac5 100644 --- a/src/__tests__/renderer/hooks/useSessionPagination.test.ts +++ b/src/__tests__/renderer/hooks/useSessionPagination.test.ts @@ -17,6 +17,7 @@ import { useSessionPagination } from '../../../renderer/hooks/useSessionPaginati const mockListPaginated = vi.fn(); const mockGetSessionOrigins = vi.fn(); const mockGetProjectStats = vi.fn(); +const mockGetOrigins = vi.fn(); vi.mock('../../../renderer/types', () => ({})); @@ -27,6 +28,7 @@ beforeEach(() => { (window as unknown as { maestro: unknown }).maestro = { agentSessions: { listPaginated: mockListPaginated, + getOrigins: mockGetOrigins, }, claude: { getSessionOrigins: mockGetSessionOrigins, @@ -43,6 +45,7 @@ beforeEach(() => { }); mockGetSessionOrigins.mockResolvedValue({}); mockGetProjectStats.mockResolvedValue({}); + mockGetOrigins.mockResolvedValue({}); }); afterEach(() => { diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts index 9b6f5427..1ead7fae 100644 --- a/src/__tests__/setup.ts +++ b/src/__tests__/setup.ts @@ -243,6 +243,8 @@ const mockMaestro = { // Session management methods (for TabSwitcherModal and RenameSessionModal) getAllNamedSessions: vi.fn().mockResolvedValue([]), getSessionOrigins: vi.fn().mockResolvedValue({}), + getOrigins: vi.fn().mockResolvedValue({}), + setSessionName: vi.fn().mockResolvedValue(undefined), updateSessionName: vi.fn().mockResolvedValue(undefined), updateSessionStarred: vi.fn().mockResolvedValue(undefined), registerSessionOrigin: vi.fn().mockResolvedValue(undefined), diff --git a/src/renderer/components/AICommandsPanel.tsx b/src/renderer/components/AICommandsPanel.tsx index 984f2e09..1dcebae8 100644 --- a/src/renderer/components/AICommandsPanel.tsx +++ b/src/renderer/components/AICommandsPanel.tsx @@ -281,7 +281,7 @@ export function AICommandsPanel({ theme, customAICommands, setCustomAICommands } className="flex items-center gap-1 px-3 py-1.5 rounded text-xs font-medium transition-all disabled:opacity-50" style={{ backgroundColor: theme.colors.success, - color: 'white' + color: '#000000' }} > @@ -377,7 +377,7 @@ export function AICommandsPanel({ theme, customAICommands, setCustomAICommands } className="flex items-center gap-1 px-3 py-1.5 rounded text-xs font-medium transition-all" style={{ backgroundColor: theme.colors.success, - color: 'white' + color: '#000000' }} > diff --git a/src/renderer/components/AutoRunLightbox.tsx b/src/renderer/components/AutoRunLightbox.tsx index 1877e12e..d81b6ffe 100644 --- a/src/renderer/components/AutoRunLightbox.tsx +++ b/src/renderer/components/AutoRunLightbox.tsx @@ -1,4 +1,5 @@ import React, { useState, useCallback, memo, useEffect, useRef } from 'react'; +import { createPortal } from 'react-dom'; import { X, ChevronLeft, ChevronRight, Copy, Check, Trash2, FileText } from 'lucide-react'; import type { Theme } from '../types'; import { useLayerStack } from '../contexts/LayerStackContext'; @@ -201,9 +202,9 @@ export const AutoRunLightbox = memo(({ return null; } - return ( + return createPortal(
setShowDeleteConfirm(false)} /> )} -
+ , + document.body ); });