@@ -99,9 +117,15 @@ export const ConnectionSettings: React.FC = () => {
)}
-
+ {config.mode === 'local' ? (
+
+ ) : (
+
+ )}
diff --git a/frontend/src/desktop/components/DesktopOnboardingModal.tsx b/frontend/src/desktop/components/DesktopOnboardingModal.tsx
new file mode 100644
index 0000000000..6fa13017d2
--- /dev/null
+++ b/frontend/src/desktop/components/DesktopOnboardingModal.tsx
@@ -0,0 +1,176 @@
+import { useState, useEffect, useMemo } from 'react';
+import { Modal, Stack, Group, Button, ActionIcon } from '@mantine/core';
+import { useTranslation } from 'react-i18next';
+import CloseIcon from '@mui/icons-material/Close';
+import LocalIcon from '@app/components/shared/LocalIcon';
+import AnimatedSlideBackground from '@app/components/onboarding/slides/AnimatedSlideBackground';
+import OnboardingStepper from '@app/components/onboarding/OnboardingStepper';
+import { SetupWizard } from '@app/components/SetupWizard';
+import WelcomeSlide from '@app/components/onboarding/slides/WelcomeSlide';
+import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from '@app/styles/zIndex';
+import styles from '@app/components/onboarding/InitialOnboardingModal/InitialOnboardingModal.module.css';
+import { connectionModeService } from '@app/services/connectionModeService';
+
+const ONBOARDING_KEY = 'stirling-desktop-onboarding-seen';
+
+const SIGN_IN_GRADIENT: [string, string] = ['#3B82F6', '#7C3AED'];
+
+/**
+ * Desktop-specific onboarding modal.
+ * Shown on first launch: welcome slide β sign-in slide.
+ * Replaces the core onboarding (which targets server/admin users).
+ */
+export function DesktopOnboardingModal() {
+ const { t } = useTranslation();
+ const [visible, setVisible] = useState(() => !localStorage.getItem(ONBOARDING_KEY));
+ const [step, setStep] = useState(0);
+ // null = still checking, true = locked (suppress modal), false = not locked (show modal)
+ const [isLocked, setIsLocked] = useState
(null);
+
+ // Provisioned (locked) deployments skip the onboarding entirely β the non-dismissible
+ // SignInModal handles authentication and shows the correct self-hosted login flow.
+ useEffect(() => {
+ connectionModeService.getCurrentConfig().then((cfg) => {
+ setIsLocked(cfg.lock_connection_mode && !!cfg.server_config?.url);
+ });
+ }, []);
+
+ const dismissFinal = () => {
+ localStorage.setItem(ONBOARDING_KEY, 'true');
+ setVisible(false);
+ };
+
+ // X on slide 0 advances to sign-in slide rather than dismissing entirely
+ const handleClose = () => {
+ if (step === 0) {
+ setStep(1);
+ } else {
+ dismissFinal();
+ }
+ };
+
+ const handleComplete = () => {
+ localStorage.setItem(ONBOARDING_KEY, 'true');
+ setVisible(false);
+ // No reload needed β AppProviders subscribes to connectionModeService and remounts
+ // the SaaS provider tree when mode changes, avoiding the Windows WebView2 freeze
+ // that window.location.reload() causes during a backgrounded OAuth flow.
+ };
+
+
+ // Call WelcomeSlide as a data factory (not a component render) β memoised so it
+ // isn't reconstructed on every render while the modal is open.
+ const welcomeSlide = useMemo(() => WelcomeSlide(), []);
+ const totalSteps = 2;
+
+ if (!visible || isLocked === null || isLocked) return null;
+
+ return (
+
+
+ {/* Hero section β gradient changes per slide */}
+
+
+
+
+
+
+
+ {step === 0 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* Body section */}
+
+ {step === 0 ? (
+ // Welcome slide
+
+
+ {welcomeSlide.title}
+
+
+
+ {welcomeSlide.body}
+
+
+
+
+
+
+
+
+
+
+ ) : (
+ // Sign-in slide
+
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx b/frontend/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx
index 644e5fa985..2dc749548c 100644
--- a/frontend/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx
+++ b/frontend/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx
@@ -15,6 +15,8 @@ interface SaaSLoginScreenProps {
onOAuthSuccess: (userInfo: UserInfo) => Promise;
onSelfHostedClick: () => void;
onSwitchToSignup: () => void;
+ onSkipSignIn?: () => void;
+ onClose?: () => void;
loading: boolean;
error: string | null;
}
@@ -25,6 +27,8 @@ export const SaaSLoginScreen: React.FC = ({
onOAuthSuccess,
onSelfHostedClick,
onSwitchToSignup,
+ onSkipSignIn,
+ onClose,
loading,
error,
}) => {
@@ -57,7 +61,7 @@ export const SaaSLoginScreen: React.FC = ({
return (
<>
-
+
@@ -110,6 +114,19 @@ export const SaaSLoginScreen: React.FC = ({