diff --git a/AGENTS.md b/AGENTS.md index d11e6bd7..d5d53b84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,6 +3,7 @@ ## Project Structure & Module Organization - `crates/`: Rust workspace crates — `server` (API + bins), `db` (SQLx models/migrations), `executors`, `services`, `utils`, `deployment`, `local-deployment`. - `frontend/`: React + TypeScript app (Vite, Tailwind). Source in `frontend/src`. +- `frontend/src/components/dialogs`: Dialog components for the frontend. - `shared/`: Generated TypeScript types (`shared/types.ts`). Do not edit directly. - `assets/`, `dev_assets_seed/`, `dev_assets/`: Packaged and local dev assets. - `npx-cli/`: Files published to the npm CLI package. diff --git a/STYLE_OVERRIDE.md b/STYLE_OVERRIDE.md deleted file mode 100644 index b96af621..00000000 --- a/STYLE_OVERRIDE.md +++ /dev/null @@ -1,57 +0,0 @@ -# Style Override via postMessage - -Simple API for overriding styles when embedding the frontend in an iframe. - -## Usage - -```javascript -// Switch theme -iframe.contentWindow.postMessage({ - type: 'VIBE_STYLE', - theme: 'purple' // 'system', 'light', 'dark', 'purple', 'green', 'blue', 'orange', 'red' -}, 'https://your-app-domain.com'); - -// Override CSS variables (--vibe-* prefix only) -iframe.contentWindow.postMessage({ - type: 'VIBE_STYLE', - css: { - '--vibe-primary': '220 14% 96%', - '--vibe-background': '0 0% 100%' - } -}, 'https://your-app-domain.com'); - -// Both together -iframe.contentWindow.postMessage({ - type: 'VIBE_STYLE', - theme: 'dark', - css: { - '--vibe-accent': '210 100% 50%' - } -}, 'https://your-app-domain.com'); -``` - -## Security - -- Origin validation via `VITE_PARENT_ORIGIN` environment variable -- Only `--vibe-*` prefixed CSS variables can be overridden -- Browser validates CSS values automatically - -## Example - -```html - - -``` diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0e1e3fd4..0545f0e5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { BrowserRouter, Route, @@ -16,171 +16,103 @@ import { AgentSettings, McpSettings, } from '@/pages/settings/'; -import { DisclaimerDialog } from '@/components/DisclaimerDialog'; -import { OnboardingDialog } from '@/components/OnboardingDialog'; -import { PrivacyOptInDialog } from '@/components/PrivacyOptInDialog'; -import { ConfigProvider, useConfig } from '@/components/config-provider'; +import { + UserSystemProvider, + useUserSystem, +} from '@/components/config-provider'; import { ThemeProvider } from '@/components/theme-provider'; import { SearchProvider } from '@/contexts/search-context'; -import { - EditorDialogProvider, - useEditorDialog, -} from '@/contexts/editor-dialog-context'; -import { CreatePRDialogProvider } from '@/contexts/create-pr-dialog-context'; -import { EditorSelectionDialog } from '@/components/tasks/EditorSelectionDialog'; -import CreatePRDialog from '@/components/tasks/Toolbar/CreatePRDialog'; -import { TaskDialogProvider } from '@/contexts/task-dialog-context'; -import { TaskFormDialogContainer } from '@/components/tasks/TaskFormDialogContainer'; + import { ProjectProvider } from '@/contexts/project-context'; -import type { EditorType } from 'shared/types'; import { ThemeMode } from 'shared/types'; -import type { ExecutorProfileId } from 'shared/types'; -import { configApi } from '@/lib/api'; import * as Sentry from '@sentry/react'; import { Loader } from '@/components/ui/loader'; -import { GitHubLoginDialog } from '@/components/GitHubLoginDialog'; -import { ReleaseNotesDialog } from '@/components/ReleaseNotesDialog'; + import { AppWithStyleOverride } from '@/utils/style-override'; import { WebviewContextMenu } from '@/vscode/ContextMenu'; import { DevBanner } from '@/components/DevBanner'; +import NiceModal from '@ebay/nice-modal-react'; +import { OnboardingResult } from './components/dialogs/global/OnboardingDialog'; const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); function AppContent() { - const { config, updateConfig, loading } = useConfig(); + const { config, updateAndSaveConfig, loading } = useUserSystem(); const location = useLocation(); - const { - isOpen: editorDialogOpen, - selectedAttempt, - closeEditorDialog, - } = useEditorDialog(); - const [showDisclaimer, setShowDisclaimer] = useState(false); - const [showOnboarding, setShowOnboarding] = useState(false); - const [showPrivacyOptIn, setShowPrivacyOptIn] = useState(false); - const [showGitHubLogin, setShowGitHubLogin] = useState(false); - const [showReleaseNotes, setShowReleaseNotes] = useState(false); + const showNavbar = !location.pathname.endsWith('/full'); useEffect(() => { - if (config) { - setShowDisclaimer(!config.disclaimer_acknowledged); - if (config.disclaimer_acknowledged) { - setShowOnboarding(!config.onboarding_acknowledged); - if (config.onboarding_acknowledged) { - if (!config.github_login_acknowledged) { - setShowGitHubLogin(true); - } else if (!config.telemetry_acknowledged) { - setShowPrivacyOptIn(true); - } else if (config.show_release_notes) { - setShowReleaseNotes(true); - } - } - } - } - }, [config]); - - const handleDisclaimerAccept = async () => { - if (!config) return; - - updateConfig({ disclaimer_acknowledged: true }); - - try { - await configApi.saveConfig({ ...config, disclaimer_acknowledged: true }); - setShowDisclaimer(false); - setShowOnboarding(!config.onboarding_acknowledged); - } catch (err) { - console.error('Error saving config:', err); - } - }; - - const handleOnboardingComplete = async (onboardingConfig: { - profile: ExecutorProfileId; - editor: { editor_type: EditorType; custom_command: string | null }; - }) => { - if (!config) return; - - const updatedConfig = { - ...config, - onboarding_acknowledged: true, - executor_profile: onboardingConfig.profile, - editor: onboardingConfig.editor, - }; - - updateConfig(updatedConfig); - - try { - await configApi.saveConfig(updatedConfig); - setShowOnboarding(false); - } catch (err) { - console.error('Error saving config:', err); - } - }; - - const handlePrivacyOptInComplete = async (telemetryEnabled: boolean) => { - if (!config) return; - - const updatedConfig = { - ...config, - telemetry_acknowledged: true, - analytics_enabled: telemetryEnabled, - }; - - updateConfig(updatedConfig); - - try { - await configApi.saveConfig(updatedConfig); - setShowPrivacyOptIn(false); - if (updatedConfig.show_release_notes) { - setShowReleaseNotes(true); - } - } catch (err) { - console.error('Error saving config:', err); - } - }; - - const handleGitHubLoginComplete = async () => { - try { - // Refresh the config to get the latest GitHub authentication state - const latestUserSystem = await configApi.getConfig(); - updateConfig(latestUserSystem.config); - setShowGitHubLogin(false); - - // If user skipped (no GitHub token), we need to manually set the acknowledgment - + const handleOnboardingComplete = async ( + onboardingConfig: OnboardingResult + ) => { const updatedConfig = { - ...latestUserSystem.config, - github_login_acknowledged: true, + ...config, + onboarding_acknowledged: true, + executor_profile: onboardingConfig.profile, + editor: onboardingConfig.editor, }; - updateConfig(updatedConfig); - await configApi.saveConfig(updatedConfig); - } catch (err) { - console.error('Error refreshing config:', err); - } finally { - if (!config?.telemetry_acknowledged) { - setShowPrivacyOptIn(true); - } else if (config?.show_release_notes) { - setShowReleaseNotes(true); - } - } - }; - const handleReleaseNotesClose = async () => { - if (!config) return; - - const updatedConfig = { - ...config, - show_release_notes: false, + updateAndSaveConfig(updatedConfig); }; - updateConfig(updatedConfig); + const handleDisclaimerAccept = async () => { + await updateAndSaveConfig({ disclaimer_acknowledged: true }); + }; - try { - await configApi.saveConfig(updatedConfig); - setShowReleaseNotes(false); - } catch (err) { - console.error('Error saving config:', err); - } - }; + const handleGitHubLoginComplete = async () => { + await updateAndSaveConfig({ github_login_acknowledged: true }); + }; + + const handleTelemetryOptIn = async (analyticsEnabled: boolean) => { + await updateAndSaveConfig({ + telemetry_acknowledged: true, + analytics_enabled: analyticsEnabled, + }); + }; + + const handleReleaseNotesClose = async () => { + await updateAndSaveConfig({ show_release_notes: false }); + }; + + const checkOnboardingSteps = async () => { + if (!config) return; + + if (!config.disclaimer_acknowledged) { + await NiceModal.show('disclaimer'); + await handleDisclaimerAccept(); + await NiceModal.hide('disclaimer'); + } + + if (!config.onboarding_acknowledged) { + const onboardingResult: OnboardingResult = + await NiceModal.show('onboarding'); + await handleOnboardingComplete(onboardingResult); + await NiceModal.hide('onboarding'); + } + + if (!config.github_login_acknowledged) { + await NiceModal.show('github-login'); + await handleGitHubLoginComplete(); + await NiceModal.hide('github-login'); + } + + if (!config.telemetry_acknowledged) { + const analyticsEnabled: boolean = + await NiceModal.show('privacy-opt-in'); + await handleTelemetryOptIn(analyticsEnabled); + await NiceModal.hide('privacy-opt-in'); + } + + if (config.show_release_notes) { + await NiceModal.show('release-notes'); + await handleReleaseNotesClose(); + await NiceModal.hide('release-notes'); + } + }; + + checkOnboardingSteps(); + }, [config]); if (loading) { return ( @@ -197,33 +129,7 @@ function AppContent() {