## 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:
Pedram Amini
2025-12-21 20:14:29 -06:00
parent 29462a7ec0
commit 41680af24a
9 changed files with 62 additions and 29 deletions

View File

@@ -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 () => {

View File

@@ -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'

View File

@@ -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();
});
});

View File

@@ -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')
);

View File

@@ -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();
});
});

View File

@@ -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(() => {

View File

@@ -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),

View File

@@ -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" />

View File

@@ -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
);
});