update onboarding (#410)

* feat(onboarding): move Tools step from onboarding to Settings

Remove the Tools toggle step from onboarding (5 → 4 steps) and add it
as a dedicated Tools panel under Settings > AI & Skills. This reduces
onboarding friction while keeping tool configuration accessible.

* feat(onboarding): remove Local AI step from onboarding

Remove the Local AI download/consent step, reducing onboarding from
4 to 3 steps (Welcome → Screen Permissions → Skills). Local AI setup
is already accessible from Settings and auto-bootstraps from Home.
This commit is contained in:
Cyrus Gray
2026-04-07 18:31:17 +05:30
committed by GitHub
parent db7eeee859
commit c3dd1370d7
+5 -114
View File
@@ -1,12 +1,9 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useState } from 'react';
import ProgressIndicator from '../../components/ProgressIndicator';
import { useCoreState } from '../../providers/CoreStateProvider';
import { userApi } from '../../services/api/userApi';
import { bootstrapLocalAiWithRecommendedPreset } from '../../utils/localAiBootstrap';
import { getDefaultEnabledTools } from '../../utils/toolDefinitions';
import LocalAIStep from './steps/LocalAIStep';
import ScreenPermissionsStep from './steps/ScreenPermissionsStep';
import SkillsStep from './steps/SkillsStep';
import WelcomeStep from './steps/WelcomeStep';
@@ -17,56 +14,18 @@ interface OnboardingProps {
}
interface OnboardingDraft {
localModelConsentGiven: boolean;
localModelDownloadStarted: boolean;
accessibilityPermissionGranted: boolean;
connectedSources: string[];
}
const LOCAL_AI_ERROR_DISMISS_MS = 10_000;
const Onboarding = ({ onComplete, onDefer }: OnboardingProps) => {
const { setOnboardingCompletedFlag, setOnboardingTasks } = useCoreState();
const [currentStep, setCurrentStep] = useState(0);
const [downloadError, setDownloadError] = useState<string | null>(null);
const retryInFlightRef = useRef(false);
const [draft, setDraft] = useState<OnboardingDraft>({
localModelConsentGiven: false,
localModelDownloadStarted: false,
accessibilityPermissionGranted: false,
connectedSources: [],
});
const totalSteps = 4;
// Auto-dismiss the error banner after LOCAL_AI_ERROR_DISMISS_MS milliseconds.
useEffect(() => {
if (!downloadError) return;
console.debug(
'[Onboarding] Local AI download error surfaced; will auto-dismiss in',
LOCAL_AI_ERROR_DISMISS_MS,
'ms'
);
const timer = setTimeout(() => {
setDownloadError(null);
}, LOCAL_AI_ERROR_DISMISS_MS);
return () => clearTimeout(timer);
}, [downloadError]);
// Re-fires both download commands when the user clicks "Retry" in the error banner.
const retryDownload = useCallback(() => {
if (retryInFlightRef.current) return;
retryInFlightRef.current = true;
console.debug('[Onboarding] User retrying Local AI download');
setDownloadError(null);
void bootstrapLocalAiWithRecommendedPreset(false, '[Onboarding retry]')
.catch((err: unknown) => {
console.warn('[Onboarding] Retry download failed:', err);
setDownloadError('Local AI setup encountered an issue');
})
.finally(() => {
retryInFlightRef.current = false;
});
}, []);
const totalSteps = 3;
const handleNext = () => {
if (currentStep < totalSteps - 1) {
@@ -80,15 +39,6 @@ const Onboarding = ({ onComplete, onDefer }: OnboardingProps) => {
}
};
const handleLocalAINext = (result: { consentGiven: boolean; downloadStarted: boolean }) => {
setDraft(prev => ({
...prev,
localModelConsentGiven: result.consentGiven,
localModelDownloadStarted: result.downloadStarted,
}));
handleNext();
};
const handleAccessibilityNext = (accessibilityPermissionGranted: boolean) => {
setDraft(prev => ({ ...prev, accessibilityPermissionGranted }));
handleNext();
@@ -99,8 +49,8 @@ const Onboarding = ({ onComplete, onDefer }: OnboardingProps) => {
await setOnboardingTasks({
accessibilityPermissionGranted: draft.accessibilityPermissionGranted,
localModelConsentGiven: draft.localModelConsentGiven,
localModelDownloadStarted: draft.localModelDownloadStarted,
localModelConsentGiven: false,
localModelDownloadStarted: false,
enabledTools: getDefaultEnabledTools(),
connectedSources,
updatedAtMs: Date.now(),
@@ -128,16 +78,8 @@ const Onboarding = ({ onComplete, onDefer }: OnboardingProps) => {
case 0:
return <WelcomeStep onNext={handleNext} />;
case 1:
return (
<LocalAIStep
onNext={handleLocalAINext}
onBack={handleBack}
onDownloadError={setDownloadError}
/>
);
case 2:
return <ScreenPermissionsStep onNext={handleAccessibilityNext} onBack={handleBack} />;
case 3:
case 2:
return <SkillsStep onNext={handleSkillsNext} onBack={handleBack} />;
default:
return null;
@@ -160,57 +102,6 @@ const Onboarding = ({ onComplete, onDefer }: OnboardingProps) => {
<ProgressIndicator currentStep={currentStep} totalSteps={totalSteps} />
{renderStep()}
</div>
{downloadError &&
createPortal(
<div
role="alert"
aria-live="assertive"
className="fixed bottom-4 left-4 z-[9997] w-[320px] animate-fade-up">
<div className="bg-white border border-coral-500/30 rounded-2xl shadow-large p-4">
<div className="flex items-start gap-3">
{/* Warning icon */}
<svg
aria-hidden="true"
className="flex-shrink-0 mt-0.5 w-5 h-5 text-coral-400"
viewBox="0 0 20 20"
fill="currentColor">
<path
fillRule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
clipRule="evenodd"
/>
</svg>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-stone-900">{downloadError}</p>
<p className="mt-0.5 text-xs text-stone-500">
You can retry or continue downloads can be resumed later.
</p>
<button
type="button"
onClick={retryDownload}
className="mt-2 text-xs font-medium text-primary-600 hover:text-primary-500 transition-colors">
Retry
</button>
</div>
{/* Dismiss button */}
<button
type="button"
aria-label="Dismiss Local AI error"
onClick={() => setDownloadError(null)}
className="flex-shrink-0 text-stone-400 hover:text-stone-600 transition-colors">
<svg
aria-hidden="true"
className="w-4 h-4"
viewBox="0 0 20 20"
fill="currentColor">
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
</svg>
</button>
</div>
</div>
</div>,
document.body
)}
</div>
);
};