fix(ui): hide unsupported permission UI on non-macOS for Screen Intelligence (#1194)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Mega Mind
2026-05-04 12:35:18 -07:00
committed by GitHub
co-authored by Cursor
parent 34a6b4021f
commit 66d1f870d1
4 changed files with 208 additions and 5 deletions
@@ -68,11 +68,15 @@ const ScreenIntelligenceDebugPanelContent = ({
<h4 className="mb-2 text-xs font-medium uppercase tracking-wide text-stone-400">
Permissions
</h4>
<div className="grid grid-cols-3 gap-2 text-xs">
<PermissionDot label="Screen" value={permissions?.screen_recording} />
<PermissionDot label="Accessibility" value={permissions?.accessibility} />
<PermissionDot label="Input" value={permissions?.input_monitoring} />
</div>
{status?.platform_supported === false ? (
<p className="text-xs text-stone-500">Platform not supported</p>
) : (
<div className="grid grid-cols-3 gap-2 text-xs">
<PermissionDot label="Screen" value={permissions?.screen_recording} />
<PermissionDot label="Accessibility" value={permissions?.accessibility} />
<PermissionDot label="Input" value={permissions?.input_monitoring} />
</div>
)}
</div>
{/* Session Status */}
@@ -117,6 +117,20 @@ describe('ScreenIntelligenceDebugPanel', () => {
);
});
it('shows "Platform not supported" and hides permission dots when platform_supported is false', () => {
const state: ScreenIntelligenceState = {
...baseState,
status: { ...baseState.status!, platform_supported: false },
};
render(<ScreenIntelligenceDebugPanel state={state} />);
expect(screen.getByText('Platform not supported')).toBeInTheDocument();
expect(screen.queryByText('Screen')).not.toBeInTheDocument();
expect(screen.queryByText('Accessibility')).not.toBeInTheDocument();
expect(screen.queryByText('Input')).not.toBeInTheDocument();
});
it('renders capture failures without breaking the diagnostics panel', async () => {
const state: ScreenIntelligenceState = {
...baseState,
@@ -147,6 +147,54 @@ export default function ScreenIntelligenceSetupModal({ onClose, initialStep }: P
navigate('/settings/screen-intelligence');
};
if (status?.platform_supported === false) {
return createPortal(
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
onClick={e => {
if (e.target === e.currentTarget) onClose();
}}>
<div
role="dialog"
aria-modal="true"
aria-labelledby="si-setup-title"
className="w-full max-w-md mx-4 rounded-2xl bg-white shadow-xl overflow-hidden animate-fade-up">
<div className="flex items-center justify-between border-b border-stone-100 px-5 py-4">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-primary-50 flex items-center justify-center text-primary-600">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M3 5h18v12H3zM8 21h8m-4-4v4" />
</svg>
</div>
<h2 id="si-setup-title" className="text-sm font-semibold text-stone-900">Screen Intelligence</h2>
</div>
<button
type="button"
aria-label="Close"
onClick={onClose}
className="w-7 h-7 rounded-lg flex items-center justify-center text-stone-400 hover:text-stone-600 hover:bg-stone-100 transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="px-5 py-6 space-y-4">
<p className="text-sm text-stone-600 leading-relaxed">
Screen Intelligence is currently available on macOS only.
</p>
<button
type="button"
onClick={onClose}
className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-2.5 text-sm font-medium text-stone-600 hover:bg-stone-100 transition-colors">
Close
</button>
</div>
</div>
</div>,
document.body
);
}
return createPortal(
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
@@ -0,0 +1,137 @@
import { render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
type ScreenIntelligenceState,
useScreenIntelligenceState,
} from '../../../features/screen-intelligence/useScreenIntelligenceState';
import ScreenIntelligenceSetupModal from '../ScreenIntelligenceSetupModal';
vi.mock('../../../features/screen-intelligence/useScreenIntelligenceState', () => ({
useScreenIntelligenceState: vi.fn(),
}));
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<Record<string, unknown>>('react-router-dom');
return { ...actual, useNavigate: vi.fn(() => vi.fn()) };
});
vi.mock('../../../utils/tauriCommands', async () => {
const actual = await vi.importActual<Record<string, unknown>>('../../../utils/tauriCommands');
return {
...actual,
openhumanUpdateScreenIntelligenceSettings: vi.fn().mockResolvedValue(undefined),
};
});
const baseState: ScreenIntelligenceState = {
status: {
platform_supported: true,
permissions: {
screen_recording: 'unknown',
accessibility: 'unknown',
input_monitoring: 'unknown',
},
features: { screen_monitoring: false },
session: {
active: false,
started_at_ms: null,
expires_at_ms: null,
remaining_ms: null,
ttl_secs: 300,
panic_hotkey: 'Cmd+Shift+.',
stop_reason: null,
frames_in_memory: 0,
last_capture_at_ms: null,
last_context: null,
vision_enabled: true,
vision_state: 'idle',
vision_queue_depth: 0,
last_vision_at_ms: null,
last_vision_summary: null,
},
config: {
enabled: false,
capture_policy: 'hybrid',
policy_mode: 'all_except_blacklist',
baseline_fps: 1,
vision_enabled: true,
session_ttl_secs: 300,
panic_stop_hotkey: 'Cmd+Shift+.',
autocomplete_enabled: true,
use_vision_model: true,
keep_screenshots: false,
allowlist: [],
denylist: [],
},
denylist: [],
is_context_blocked: false,
},
lastRestartSummary: null,
recentVisionSummaries: [],
captureTestResult: null,
isCaptureTestRunning: false,
isLoading: false,
isRequestingPermissions: false,
isRestartingCore: false,
isStartingSession: false,
isStoppingSession: false,
isLoadingVision: false,
isFlushingVision: false,
lastError: null,
refreshStatus: vi.fn().mockResolvedValue(null),
requestPermission: vi.fn().mockResolvedValue(null),
refreshPermissionsWithRestart: vi.fn().mockResolvedValue(null),
startSession: vi.fn().mockResolvedValue(null),
stopSession: vi.fn().mockResolvedValue(null),
refreshVision: vi.fn().mockResolvedValue([]),
flushVision: vi.fn().mockResolvedValue(undefined),
runCaptureTest: vi.fn().mockResolvedValue(undefined),
clearError: vi.fn(),
};
describe('ScreenIntelligenceSetupModal', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('shows macOS-only message and a Close button when platform_supported is false', () => {
vi.mocked(useScreenIntelligenceState).mockReturnValue({
...baseState,
status: { ...baseState.status!, platform_supported: false },
});
render(<ScreenIntelligenceSetupModal onClose={vi.fn()} />);
expect(screen.getByText(/macOS only/i)).toBeInTheDocument();
expect(screen.getByText('Close', { selector: 'button' })).toBeInTheDocument();
expect(screen.queryByText('Grant permissions')).not.toBeInTheDocument();
expect(screen.queryByText('Screen Recording')).not.toBeInTheDocument();
});
it('calls onClose when the Close button is clicked on the unsupported-platform screen', () => {
const onClose = vi.fn();
vi.mocked(useScreenIntelligenceState).mockReturnValue({
...baseState,
status: { ...baseState.status!, platform_supported: false },
});
render(<ScreenIntelligenceSetupModal onClose={onClose} />);
screen.getByText('Close', { selector: 'button' }).click();
expect(onClose).toHaveBeenCalledTimes(1);
});
it('renders the permissions setup flow when platform_supported is true', () => {
vi.mocked(useScreenIntelligenceState).mockReturnValue(baseState);
render(<ScreenIntelligenceSetupModal onClose={vi.fn()} />);
expect(screen.getByText('Grant permissions')).toBeInTheDocument();
expect(screen.getByText('Screen Recording')).toBeInTheDocument();
expect(screen.getByText('Accessibility')).toBeInTheDocument();
expect(screen.getByText('Input Monitoring')).toBeInTheDocument();
expect(screen.queryByText(/macOS only/i)).not.toBeInTheDocument();
});
});