mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
## CHANGES
- Cloudflared cache clearing now resets installed and path caches reliably! 🧹 - Cache reset leaves cloudflared path null after failed detection attempts! 🔒 - Claude-code session renames now call `window.maestro.claude.updateSessionName` correctly! ✍️ - RenameSessionModal updated to use Claude rename API for claude-code! 🧠 - Tab switcher now syncs named tabs via Claude session renaming! 🔁 - Remote tab rename persists names through Claude-code integration pathway! 🌐 - Agent sessions API expanded with `setSessionName` for safer naming! 🏷️ - Agent sessions origins fetching added via new `getOrigins` method! 🧭 - AutoRunLightbox now renders via portal with ultra-high z-index! 🪟 - Save buttons now use black text for better contrast readability! 🎨
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -101,7 +101,11 @@ describe('RenameSessionModal', () => {
|
||||
|
||||
// Setup window.maestro mock
|
||||
(window as unknown as { maestro: Record<string, unknown> }).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(
|
||||
<TestWrapper>
|
||||
<RenameSessionModal
|
||||
@@ -555,7 +559,8 @@ describe('RenameSessionModal', () => {
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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')
|
||||
);
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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'
|
||||
}}
|
||||
>
|
||||
<Save className="w-3 h-3" />
|
||||
@@ -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'
|
||||
}}
|
||||
>
|
||||
<Save className="w-3 h-3" />
|
||||
|
||||
@@ -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(
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/90"
|
||||
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/90"
|
||||
onClick={onClose}
|
||||
onKeyDown={handleKeyDown}
|
||||
tabIndex={-1}
|
||||
@@ -301,7 +302,8 @@ export const AutoRunLightbox = memo(({
|
||||
onClose={() => setShowDeleteConfirm(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user