Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 63 additions & 5 deletions src/renderer/components/settings/SettingsSandbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Shield, AlertCircle, CheckCircle, Settings, Loader2 } from 'lucide-react';
import { renderLocalizedBannerMessage } from './shared';
Expand Down Expand Up @@ -45,6 +45,16 @@ export function SettingsSandbox() {
const [error, setError] = useState<LocalizedBanner | null>(null);
const [success, setSuccess] = useState<LocalizedBanner | null>(null);
const [isInitialized, setIsInitialized] = useState(false);
const [isTogglingSandbox, setIsTogglingSandbox] = useState(false);
const successTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const reloadTimeoutRef = useRef<ReturnType<typeof setTimeout>>();

useEffect(() => {
return () => {
if (successTimeoutRef.current) clearTimeout(successTimeoutRef.current);
if (reloadTimeoutRef.current) clearTimeout(reloadTimeoutRef.current);
};
}, []);

const platform = window.electronAPI?.platform || 'unknown';
const isWindows = platform === 'win32';
Expand Down Expand Up @@ -103,8 +113,37 @@ export function SettingsSandbox() {
}
}

// TODO: Re-enable when sandbox debugging is complete
// async function handleToggleSandbox() { ... }
async function handleToggleSandbox() {
if (!isElectron) return;
if (isTogglingSandbox) return;
setIsTogglingSandbox(true);
setError(null);
setSuccess(null);

const nextEnabled = !sandboxEnabled;
try {
const result = await window.electronAPI.config.save({ sandboxEnabled: nextEnabled });
if (result.success) {
setSandboxEnabled(result.config.sandboxEnabled !== false);
setSuccess({
text: nextEnabled ? t('sandbox.enabledWillSetup') : t('sandbox.disabled'),
});
// Backend reloads the sandbox bridge on change; refresh status once that settles.
if (reloadTimeoutRef.current) clearTimeout(reloadTimeoutRef.current);
reloadTimeoutRef.current = setTimeout(async () => {
await loadStatus();
}, 500);
if (successTimeoutRef.current) clearTimeout(successTimeoutRef.current);
successTimeoutRef.current = setTimeout(() => setSuccess(null), 3000);
} else {
setError({ text: t('sandbox.failedToSave') });
}
} catch (err) {
setError({ text: err instanceof Error ? err.message : t('sandbox.failedToSave') });
} finally {
setIsTogglingSandbox(false);
}
}

async function handleCheckStatus() {
if (isChecking) return; // Prevent double-click
Expand Down Expand Up @@ -281,12 +320,12 @@ export function SettingsSandbox() {
? status?.wsl?.available
: isMac
? status?.lima?.available
: false;
: status?.initialized === true; // Linux: available once backend adapter reports initialized
const sandboxReady = isWindows
? status?.wsl?.available && status?.wsl?.nodeAvailable
: isMac
? status?.lima?.available && status?.lima?.instanceRunning && status?.lima?.nodeAvailable
: false;
: status?.initialized === true; // Linux: ready once backend adapter reports initialized
const sandboxStatusText = !sandboxEnabled
? t('sandbox.disabledStatus')
: sandboxReady
Expand Down Expand Up @@ -326,6 +365,25 @@ export function SettingsSandbox() {
: t('sandbox.nativeDesc')}
</p>
</div>
<div className="flex items-center justify-center gap-3">
<button
onClick={handleToggleSandbox}
disabled={isTogglingSandbox}
role="switch"
aria-checked={sandboxEnabled}
aria-label={t('sandbox.enableSandbox')}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 disabled:opacity-50 flex-shrink-0 ${
sandboxEnabled ? 'bg-accent' : 'bg-surface-muted'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-text-primary transition-transform ${
sandboxEnabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
{isTogglingSandbox && <Loader2 className="w-4 h-4 animate-spin text-text-muted" />}
</div>
<div
className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-medium ${
sandboxReady
Expand Down
Loading