fix(wizard): mode-aware redirect target — break /sovereign/wizard ↔ /sovereign/dashboard loop (#975) (#989)

WizardPage and StepReview both call navigate({to:'/dashboard',
params:{deploymentId}}) when an inflight deployment is detected. On
the mothership the bare /dashboard matches the Sovereign-Console
clean-root route which renders SovereignConsoleLayout — that layout's
mothership-fall-through guard (added in #987) redirects back to
/sovereign/, indexRoute redirects to /wizard, and WizardPage sees
inflight again and re-fires the navigate, looping forever between
/sovereign/, /sovereign/wizard, /sovereign/dashboard.

Fix: distinguish DETECTED_MODE.mode in both call sites:
- 'sovereign' (per-Sovereign self-mode SPA): /dashboard (clean root)
- 'catalyst-zero' (mothership): /provision/$deploymentId/dashboard

This is the third lap of #976's clean-URL cleanup catching mothership
flows that weren't migrated to the parameterised routes.

Co-authored-by: hatiyildiz <hatiyildiz@users.noreply.github.com>
This commit is contained in:
e3mrah 2026-05-05 22:21:05 +04:00 committed by GitHub
parent 6498eff476
commit 0daaac5bd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 9 deletions

View File

@ -4,6 +4,7 @@ import { Link, useNavigate } from '@tanstack/react-router'
import { useWizardStore } from '@/entities/deployment/store'
import { useSession } from '@/shared/lib/useSession'
import { useInflightDeployment } from '@/shared/lib/useInflightDeployment'
import { DETECTED_MODE } from '@/shared/lib/detectMode'
import { StepOrg } from './steps/StepOrg'
import { StepDomain } from './steps/StepDomain'
import { StepTopology } from './steps/StepTopology'
@ -92,11 +93,32 @@ export function WizardPage() {
// replace:true so the wizard URL doesn't sit in history — a
// back-button press from /provision/<id> should land on the
// referrer, not on a doomed wizard step.
navigate({
to: '/dashboard',
params: { deploymentId: inflight.id },
replace: true,
})
//
// Target depends on mode:
// • mothership (catalyst-zero): /provision/$deploymentId/dashboard
// — the parameterised mothership URL where deploymentId scopes
// the surface.
// • Sovereign self-mode: /dashboard (clean root, sovereign is
// implicit from hostname).
//
// Bug history: pre-fix this called navigate({to:'/dashboard',
// params:{deploymentId}}) unconditionally. On the mothership the
// bare /dashboard matched the Sovereign-Console clean-root route
// which renders SovereignConsoleLayout — that layout's mothership
// guard then redirected back to /sovereign/, indexRoute redirected
// to /wizard, WizardPage saw inflight again and looped.
if (DETECTED_MODE.mode === 'sovereign') {
navigate({
to: '/dashboard',
replace: true,
})
} else {
navigate({
to: '/provision/$deploymentId/dashboard' as never,
params: { deploymentId: inflight.id } as never,
replace: true,
})
}
}, [session.loading, session.signedIn, inflight, navigate])
useEffect(() => {

View File

@ -55,6 +55,7 @@ import { findNodeSize } from '@/shared/constants/providerSizes'
import { API_BASE } from '@/shared/config/urls'
import { useRouter } from '@tanstack/react-router'
import { useSession } from '@/shared/lib/useSession'
import { DETECTED_MODE } from '@/shared/lib/detectMode'
import { PinSignInModal } from '@/widgets/auth/PinSignInModal'
import { StepShell, useStepNav } from './_shared'
import {
@ -788,10 +789,20 @@ export function StepReview() {
return
}
store.setDeploymentId(data.id)
router.navigate({
to: '/dashboard',
params: { deploymentId: data.id },
})
// Mode-aware target: Sovereign self-mode uses the clean root
// /dashboard, mothership uses /provision/$deploymentId/dashboard.
// Sending all callers to bare /dashboard with a params payload
// matches the Sovereign-Console clean-root route on the
// mothership and triggers an infinite redirect loop with
// SovereignConsoleLayout's mothership-fall-through guard.
if (DETECTED_MODE.mode === 'sovereign') {
router.navigate({ to: '/dashboard' })
} else {
router.navigate({
to: '/provision/$deploymentId/dashboard' as never,
params: { deploymentId: data.id } as never,
})
}
} catch (err) {
alert(`Failed to start provisioning: ${err}`)
setLoading(false)