From fb46c0c9eae5acd9a4dddef355ef3c2d4780c55c Mon Sep 17 00:00:00 2001 From: webdevcody Date: Thu, 1 Jan 2026 16:49:35 -0500 Subject: [PATCH] feat: enhance sandbox risk dialog and settings management - Updated the SandboxRiskDialog to include a checkbox for users to opt-out of future warnings, passing the state to the onConfirm callback. - Modified SettingsView to manage the skipSandboxWarning state, allowing users to reset the warning preference. - Enhanced DangerZoneSection to display a message when the sandbox warning is disabled and provide an option to reset this setting. - Updated RootLayoutContent to respect the user's choice regarding the sandbox warning, auto-confirming if the user opts to skip it. - Added skipSandboxWarning state management to the app store for persistent user preferences. --- .../dialogs/sandbox-risk-dialog.tsx | 51 ++++++--- .../ui/src/components/views/settings-view.tsx | 4 + .../danger-zone/danger-zone-section.tsx | 103 +++++++++++++----- apps/ui/src/routes/__root.tsx | 31 +++++- apps/ui/src/store/app-store.ts | 10 ++ 5 files changed, 151 insertions(+), 48 deletions(-) diff --git a/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx b/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx index 905d82a1..94940257 100644 --- a/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx +++ b/apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx @@ -16,10 +16,12 @@ import { DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Label } from '@/components/ui/label'; interface SandboxRiskDialogProps { open: boolean; - onConfirm: () => void; + onConfirm: (skipInFuture: boolean) => void; onDeny: () => void; } @@ -27,6 +29,13 @@ const DOCKER_COMMAND = 'npm run dev:docker'; export function SandboxRiskDialog({ open, onConfirm, onDeny }: SandboxRiskDialogProps) { const [copied, setCopied] = useState(false); + const [skipInFuture, setSkipInFuture] = useState(false); + + const handleConfirm = () => { + onConfirm(skipInFuture); + // Reset checkbox state after confirmation + setSkipInFuture(false); + }; const handleCopy = async () => { try { @@ -93,18 +102,34 @@ export function SandboxRiskDialog({ open, onConfirm, onDeny }: SandboxRiskDialog - - - + +
+ setSkipInFuture(checked === true)} + data-testid="sandbox-skip-checkbox" + /> + +
+
+ + +
diff --git a/apps/ui/src/components/views/settings-view.tsx b/apps/ui/src/components/views/settings-view.tsx index ce737fee..037f6743 100644 --- a/apps/ui/src/components/views/settings-view.tsx +++ b/apps/ui/src/components/views/settings-view.tsx @@ -55,6 +55,8 @@ export function SettingsView() { setAutoLoadClaudeMd, enableSandboxMode, setEnableSandboxMode, + skipSandboxWarning, + setSkipSandboxWarning, promptCustomization, setPromptCustomization, } = useAppStore(); @@ -184,6 +186,8 @@ export function SettingsView() { setShowDeleteDialog(true)} + skipSandboxWarning={skipSandboxWarning} + onResetSandboxWarning={() => setSkipSandboxWarning(false)} /> ); default: diff --git a/apps/ui/src/components/views/settings-view/danger-zone/danger-zone-section.tsx b/apps/ui/src/components/views/settings-view/danger-zone/danger-zone-section.tsx index 80732bdb..0a1d6ed9 100644 --- a/apps/ui/src/components/views/settings-view/danger-zone/danger-zone-section.tsx +++ b/apps/ui/src/components/views/settings-view/danger-zone/danger-zone-section.tsx @@ -1,16 +1,21 @@ import { Button } from '@/components/ui/button'; -import { Trash2, Folder, AlertTriangle } from 'lucide-react'; +import { Trash2, Folder, AlertTriangle, Shield, RotateCcw } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { Project } from '../shared/types'; interface DangerZoneSectionProps { project: Project | null; onDeleteClick: () => void; + skipSandboxWarning: boolean; + onResetSandboxWarning: () => void; } -export function DangerZoneSection({ project, onDeleteClick }: DangerZoneSectionProps) { - if (!project) return null; - +export function DangerZoneSection({ + project, + onDeleteClick, + skipSandboxWarning, + onResetSandboxWarning, +}: DangerZoneSectionProps) { return (
Danger Zone

- Permanently remove this project from Automaker. + Destructive actions and reset options.

-
-
-
-
- -
-
-

{project.name}

-

{project.path}

+
+ {/* Sandbox Warning Reset */} + {skipSandboxWarning && ( +
+
+
+ +
+
+

Sandbox Warning Disabled

+

+ The sandbox environment warning is hidden on startup +

+
+
- -
+ )} + + {/* Project Delete */} + {project && ( +
+
+
+ +
+
+

{project.name}

+

{project.path}

+
+
+ +
+ )} + + {/* Empty state when nothing to show */} + {!skipSandboxWarning && !project && ( +

+ No danger zone actions available. +

+ )}
); diff --git a/apps/ui/src/routes/__root.tsx b/apps/ui/src/routes/__root.tsx index 8cffeff5..d486ce59 100644 --- a/apps/ui/src/routes/__root.tsx +++ b/apps/ui/src/routes/__root.tsx @@ -25,7 +25,13 @@ import { SandboxRejectionScreen } from '@/components/dialogs/sandbox-rejection-s function RootLayoutContent() { const location = useLocation(); - const { setIpcConnected, currentProject, getEffectiveTheme } = useAppStore(); + const { + setIpcConnected, + currentProject, + getEffectiveTheme, + skipSandboxWarning, + setSkipSandboxWarning, + } = useAppStore(); const { setupComplete } = useSetupStore(); const navigate = useNavigate(); const [isMounted, setIsMounted] = useState(false); @@ -106,6 +112,9 @@ function RootLayoutContent() { if (result.isContainerized) { // Running in a container, no warning needed setSandboxStatus('containerized'); + } else if (skipSandboxWarning) { + // User opted to skip the warning, auto-confirm + setSandboxStatus('confirmed'); } else { // Not containerized, show warning dialog setSandboxStatus('needs-confirmation'); @@ -113,17 +122,27 @@ function RootLayoutContent() { } catch (error) { console.error('[Sandbox] Failed to check environment:', error); // On error, assume not containerized and show warning - setSandboxStatus('needs-confirmation'); + if (skipSandboxWarning) { + setSandboxStatus('confirmed'); + } else { + setSandboxStatus('needs-confirmation'); + } } }; checkSandbox(); - }, [sandboxStatus]); + }, [sandboxStatus, skipSandboxWarning]); // Handle sandbox risk confirmation - const handleSandboxConfirm = useCallback(() => { - setSandboxStatus('confirmed'); - }, []); + const handleSandboxConfirm = useCallback( + (skipInFuture: boolean) => { + if (skipInFuture) { + setSkipSandboxWarning(true); + } + setSandboxStatus('confirmed'); + }, + [setSkipSandboxWarning] + ); // Handle sandbox risk denial const handleSandboxDeny = useCallback(async () => { diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index c04daa8f..a57e4d93 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -487,6 +487,7 @@ export interface AppState { // Claude Agent SDK Settings autoLoadClaudeMd: boolean; // Auto-load CLAUDE.md files using SDK's settingSources option enableSandboxMode: boolean; // Enable sandbox mode for bash commands (may cause issues on some systems) + skipSandboxWarning: boolean; // Skip the sandbox environment warning dialog on startup // MCP Servers mcpServers: MCPServerConfig[]; // List of configured MCP servers for agent use @@ -775,6 +776,7 @@ export interface AppActions { // Claude Agent SDK Settings actions setAutoLoadClaudeMd: (enabled: boolean) => Promise; setEnableSandboxMode: (enabled: boolean) => Promise; + setSkipSandboxWarning: (skip: boolean) => Promise; setMcpAutoApproveTools: (enabled: boolean) => Promise; setMcpUnrestrictedTools: (enabled: boolean) => Promise; @@ -976,6 +978,7 @@ const initialState: AppState = { validationModel: 'opus', // Default to opus for GitHub issue validation autoLoadClaudeMd: false, // Default to disabled (user must opt-in) enableSandboxMode: false, // Default to disabled (can be enabled for additional security) + skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog) mcpServers: [], // No MCP servers configured by default mcpAutoApproveTools: true, // Default to enabled - bypass permission prompts for MCP tools mcpUnrestrictedTools: true, // Default to enabled - don't filter allowedTools when MCP enabled @@ -1623,6 +1626,12 @@ export const useAppStore = create()( const { syncSettingsToServer } = await import('@/hooks/use-settings-migration'); await syncSettingsToServer(); }, + setSkipSandboxWarning: async (skip) => { + set({ skipSandboxWarning: skip }); + // Sync to server settings file + const { syncSettingsToServer } = await import('@/hooks/use-settings-migration'); + await syncSettingsToServer(); + }, setMcpAutoApproveTools: async (enabled) => { set({ mcpAutoApproveTools: enabled }); // Sync to server settings file @@ -2921,6 +2930,7 @@ export const useAppStore = create()( validationModel: state.validationModel, autoLoadClaudeMd: state.autoLoadClaudeMd, enableSandboxMode: state.enableSandboxMode, + skipSandboxWarning: state.skipSandboxWarning, // MCP settings mcpServers: state.mcpServers, mcpAutoApproveTools: state.mcpAutoApproveTools,