diff --git a/app/src/components/intelligence/ScreenIntelligenceDebugPanel.tsx b/app/src/components/intelligence/ScreenIntelligenceDebugPanel.tsx
index 700b1b259..855f09138 100644
--- a/app/src/components/intelligence/ScreenIntelligenceDebugPanel.tsx
+++ b/app/src/components/intelligence/ScreenIntelligenceDebugPanel.tsx
@@ -68,11 +68,15 @@ const ScreenIntelligenceDebugPanelContent = ({
Permissions
-
+ {status?.platform_supported === false ? (
+ Platform not supported
+ ) : (
+
+ )}
{/* Session Status */}
diff --git a/app/src/components/intelligence/__tests__/ScreenIntelligenceDebugPanel.test.tsx b/app/src/components/intelligence/__tests__/ScreenIntelligenceDebugPanel.test.tsx
index cbefe7947..f4dd46e26 100644
--- a/app/src/components/intelligence/__tests__/ScreenIntelligenceDebugPanel.test.tsx
+++ b/app/src/components/intelligence/__tests__/ScreenIntelligenceDebugPanel.test.tsx
@@ -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();
+
+ 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,
diff --git a/app/src/components/skills/ScreenIntelligenceSetupModal.tsx b/app/src/components/skills/ScreenIntelligenceSetupModal.tsx
index 66a3efef6..6de1c22df 100644
--- a/app/src/components/skills/ScreenIntelligenceSetupModal.tsx
+++ b/app/src/components/skills/ScreenIntelligenceSetupModal.tsx
@@ -147,6 +147,54 @@ export default function ScreenIntelligenceSetupModal({ onClose, initialStep }: P
navigate('/settings/screen-intelligence');
};
+ if (status?.platform_supported === false) {
+ return createPortal(
+ {
+ if (e.target === e.currentTarget) onClose();
+ }}>
+
+
+
+
+
Screen Intelligence
+
+
+
+
+
+ Screen Intelligence is currently available on macOS only.
+
+
+
+
+
,
+ document.body
+ );
+ }
+
return createPortal(
({
+ useScreenIntelligenceState: vi.fn(),
+}));
+
+vi.mock('react-router-dom', async () => {
+ const actual = await vi.importActual>('react-router-dom');
+ return { ...actual, useNavigate: vi.fn(() => vi.fn()) };
+});
+
+vi.mock('../../../utils/tauriCommands', async () => {
+ const actual = await vi.importActual>('../../../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();
+
+ 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();
+
+ 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();
+
+ 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();
+ });
+});