Fix onboarding rerun project continuation (#2735)
This commit is contained in:
parent
f0627cccc6
commit
83c90db261
|
|
@ -84,7 +84,12 @@ export default function OnboardingFlow({
|
|||
const shouldShowSkipToProjectSetup = currentStep.id !== 'repo' && currentStep.id !== 'tour'
|
||||
const shouldShowStepHeading = !isTourStep
|
||||
const footerPrimaryLabel = isTourStep ? 'Skip the tour' : primaryActionLabel
|
||||
const { next: flowNext, openFolder: flowOpenFolder, skipTourToRepo: flowSkipTourToRepo } = flow
|
||||
const {
|
||||
next: flowNext,
|
||||
openFolder: flowOpenFolder,
|
||||
continueWithExistingProject: flowContinueWithExistingProject,
|
||||
skipTourToRepo: flowSkipTourToRepo
|
||||
} = flow
|
||||
// Why: depend on stable callbacks + step id only so the listener doesn't
|
||||
// re-bind on every render of the parent (flow object identity changes).
|
||||
useEffect(() => {
|
||||
|
|
@ -110,14 +115,26 @@ export default function OnboardingFlow({
|
|||
return
|
||||
}
|
||||
if (currentStep.id === 'repo') {
|
||||
void flowOpenFolder()
|
||||
if (flow.hasExistingProject) {
|
||||
void flowContinueWithExistingProject('keyboard')
|
||||
} else {
|
||||
void flowOpenFolder()
|
||||
}
|
||||
} else {
|
||||
void flowNext('keyboard')
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKeyDown, { capture: true })
|
||||
return () => window.removeEventListener('keydown', onKeyDown, { capture: true })
|
||||
}, [currentStep.id, flowNext, flowOpenFolder, flowSkipTourToRepo, tourStarted])
|
||||
}, [
|
||||
currentStep.id,
|
||||
flow.hasExistingProject,
|
||||
flowContinueWithExistingProject,
|
||||
flowNext,
|
||||
flowOpenFolder,
|
||||
flowSkipTourToRepo,
|
||||
tourStarted
|
||||
])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -319,7 +336,7 @@ export default function OnboardingFlow({
|
|||
Skip
|
||||
</button>
|
||||
)}
|
||||
{currentStep.id !== 'repo' && (
|
||||
{(currentStep.id !== 'repo' || flow.hasExistingProject) && (
|
||||
<button
|
||||
className="inline-flex items-center justify-center gap-2 rounded-md bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
aria-busy={Boolean(busyLabel)}
|
||||
|
|
@ -329,6 +346,10 @@ export default function OnboardingFlow({
|
|||
void flow.skipTourToRepo()
|
||||
return
|
||||
}
|
||||
if (currentStep.id === 'repo') {
|
||||
void flow.continueWithExistingProject()
|
||||
return
|
||||
}
|
||||
void flow.next()
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import type { ComponentProps } from 'react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { RepoStep } from './RepoStep'
|
||||
|
||||
function renderRepoStep(overrides: Partial<ComponentProps<typeof RepoStep>> = {}): string {
|
||||
return renderToStaticMarkup(
|
||||
<RepoStep
|
||||
cloneUrl=""
|
||||
onCloneUrlChange={vi.fn()}
|
||||
onOpenFolder={vi.fn()}
|
||||
onOpenServerFolder={vi.fn()}
|
||||
onClone={vi.fn()}
|
||||
onOpenSshSettings={vi.fn()}
|
||||
serverPath=""
|
||||
onServerPathChange={vi.fn()}
|
||||
cloneDestination=""
|
||||
onCloneDestinationChange={vi.fn()}
|
||||
workspaceDir="/workspace"
|
||||
runtimeActive={false}
|
||||
busyLabel={null}
|
||||
error={null}
|
||||
{...overrides}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
describe('RepoStep', () => {
|
||||
it('renders the add project options without existing-project chrome', () => {
|
||||
const html = renderRepoStep()
|
||||
|
||||
expect(html).not.toContain('Project already added')
|
||||
expect(html).toContain('Open a folder')
|
||||
expect(html).toContain('Clone a repo')
|
||||
})
|
||||
})
|
||||
|
|
@ -88,6 +88,9 @@ export function useOnboardingFlow(
|
|||
const preflightStatusLoading = useAppStore((s) => s.preflightStatusLoading)
|
||||
const linearStatus = useAppStore((s) => s.linearStatus)
|
||||
const linearStatusChecked = useAppStore((s) => s.linearStatusChecked)
|
||||
// Why: App hydrates repos before mounting onboarding. Reading the store
|
||||
// synchronously lets the final step render its already-added state without a flash.
|
||||
const repos = useAppStore((s) => s.repos)
|
||||
|
||||
const initialStep = Math.min(Math.max(onboarding.lastCompletedStep, 0), STEPS.length - 1)
|
||||
const [stepIndex, setStepIndex] = useState(initialStep)
|
||||
|
|
@ -193,6 +196,7 @@ export function useOnboardingFlow(
|
|||
|
||||
const detectedSet = useMemo(() => new Set(detectedAgentIds ?? []), [detectedAgentIds])
|
||||
const currentStep = STEPS[stepIndex]
|
||||
const hasExistingProject = repos.length > 0
|
||||
|
||||
// Why: refs let `setSelectedAgentInteractive` (a stable useCallback) read
|
||||
// the freshest detection snapshot at click time without re-rebinding the
|
||||
|
|
@ -634,6 +638,35 @@ export function useOnboardingFlow(
|
|||
}
|
||||
}, [busyLabel, cloneDestination, cloneUrl, completeRepo, settings])
|
||||
|
||||
const continueWithExistingProject = useCallback(
|
||||
async (advancedVia: 'button' | 'keyboard' = 'button') => {
|
||||
if (busyLabel !== null || currentStep.id !== 'repo' || repos.length === 0) {
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
setBusyLabel('Finishing...')
|
||||
try {
|
||||
const checklist = repos.some((repo) => isGitRepoKind(repo))
|
||||
? { addedRepo: true }
|
||||
: { addedFolder: true }
|
||||
const closed = await closeWith('completed', checklist, ONBOARDING_FINAL_STEP)
|
||||
if (!closed) {
|
||||
return
|
||||
}
|
||||
emitPendingTourOutcome()
|
||||
track('onboarding_step_completed', {
|
||||
step: ONBOARDING_FINAL_STEP,
|
||||
value_kind: 'repo',
|
||||
duration_ms: consumeStepDurationMs(),
|
||||
advanced_via: advancedVia
|
||||
})
|
||||
} finally {
|
||||
setBusyLabel(null)
|
||||
}
|
||||
},
|
||||
[busyLabel, closeWith, consumeStepDurationMs, currentStep.id, emitPendingTourOutcome, repos]
|
||||
)
|
||||
|
||||
const skipToRepo = useCallback(async () => {
|
||||
if (busyLabel) {
|
||||
return
|
||||
|
|
@ -882,6 +915,7 @@ export function useOnboardingFlow(
|
|||
hasSelectedFeatureSetup,
|
||||
cloneUrl,
|
||||
setCloneUrl,
|
||||
hasExistingProject,
|
||||
serverPath,
|
||||
setServerPath,
|
||||
cloneDestination,
|
||||
|
|
@ -901,6 +935,7 @@ export function useOnboardingFlow(
|
|||
back,
|
||||
jumpToStep,
|
||||
openFolder,
|
||||
continueWithExistingProject,
|
||||
openSshSettings,
|
||||
clone
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue