fix(intro): restore the typing intro on fresh kiosk installs + seamless splash-to-onboarding handoff
Three regressions of the fresh-install cinematic, all in App.vue: 1. The kiosk chromium opens /kiosk (a marker route that stamps localStorage.kiosk and redirects to /). The deep-route splash suppression added in PR #91 reads window.location.pathname, so the kiosk boot was classified as a deep route and the typing intro → "Welcome Noderunner" → onboarding sequence never played. Normalize /kiosk to / for the splash decision. 2. The pre-splash onboarding-status check runs a ~30s retry ladder; against a still-booting backend it held the black !isReady screen the whole time. Bound it with a 2.5s race — unknown means "play the intro" (a fresh install IS the slow-backend case; onboarded nodes answer in milliseconds so their suppression is unaffected). 3. handleSplashComplete revealed RouterView before routing, flashing RootRedirect's spinner at '/' between the intro's final frame and the onboarding screen. Commit the destination route first, then reveal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e5c7210663
commit
06186ab30c
@ -411,7 +411,13 @@ onMounted(async () => {
|
|||||||
// the initial navigation resolves asynchronously — so a deep-route load
|
// the initial navigation resolves asynchronously — so a deep-route load
|
||||||
// (e.g. a direct /login visit) would masquerade as a root boot. The URL
|
// (e.g. a direct /login visit) would masquerade as a root boot. The URL
|
||||||
// bar is the only truthful source this early.
|
// bar is the only truthful source this early.
|
||||||
const bootPath = window.location.pathname
|
//
|
||||||
|
// The kiosk chromium opens /kiosk — a marker route whose beforeEnter
|
||||||
|
// stamps localStorage.kiosk and immediately redirects to '/'. That is a
|
||||||
|
// root boot, not a deep route: without normalizing it, the fresh-install
|
||||||
|
// kiosk never plays the typing intro (deep-route suppression regression).
|
||||||
|
const rawBootPath = window.location.pathname
|
||||||
|
const bootPath = rawBootPath === '/kiosk' ? '/' : rawBootPath
|
||||||
// Public demo: every fresh boot at the root (first visit or a browser
|
// Public demo: every fresh boot at the root (first visit or a browser
|
||||||
// refresh) starts with the typing splash for the full effect. In-session SPA
|
// refresh) starts with the typing splash for the full effect. In-session SPA
|
||||||
// navigation never remounts App, and deep-route refreshes keep their place.
|
// navigation never remounts App, and deep-route refreshes keep their place.
|
||||||
@ -424,7 +430,17 @@ onMounted(async () => {
|
|||||||
if (splashCandidate && onboardingComplete !== true) {
|
if (splashCandidate && onboardingComplete !== true) {
|
||||||
try {
|
try {
|
||||||
const { checkOnboardingStatus } = await import('@/composables/useOnboarding')
|
const { checkOnboardingStatus } = await import('@/composables/useOnboarding')
|
||||||
onboardingComplete = await checkOnboardingStatus()
|
// Bound the pre-splash status check: its retry ladder can spend ~30s
|
||||||
|
// against a still-booting backend, and this await holds the black
|
||||||
|
// "!isReady" screen the whole time — exactly where the typing intro
|
||||||
|
// should be playing on a fresh kiosk boot. Unknown within 2.5s → let
|
||||||
|
// the splash play (a fresh install IS the slow-backend case; onboarded
|
||||||
|
// nodes answer in milliseconds, so their suppression path is intact).
|
||||||
|
// handleSplashComplete re-checks with full retries after the intro.
|
||||||
|
onboardingComplete = await Promise.race([
|
||||||
|
checkOnboardingStatus(),
|
||||||
|
new Promise<null>((resolve) => setTimeout(() => resolve(null), 2500)),
|
||||||
|
])
|
||||||
} catch {
|
} catch {
|
||||||
onboardingComplete = localStorage.getItem('neode_onboarding_complete') === '1' ? true : null
|
onboardingComplete = localStorage.getItem('neode_onboarding_complete') === '1' ? true : null
|
||||||
}
|
}
|
||||||
@ -524,14 +540,24 @@ function onShareToMeshMessage(ev: MessageEvent) {
|
|||||||
* Routes user directly to appropriate screen based on onboarding status (from backend)
|
* Routes user directly to appropriate screen based on onboarding status (from backend)
|
||||||
*/
|
*/
|
||||||
async function handleSplashComplete() {
|
async function handleSplashComplete() {
|
||||||
showSplash.value = false
|
|
||||||
document.body.classList.add('splash-complete')
|
|
||||||
isReady.value = true
|
|
||||||
sessionStorage.setItem('archipelago_from_splash', '1')
|
sessionStorage.setItem('archipelago_from_splash', '1')
|
||||||
|
|
||||||
|
// Commit the destination route BEFORE revealing RouterView. Revealing
|
||||||
|
// first rendered whatever route was current — '/' → RootRedirect's
|
||||||
|
// spinner — for a beat between the intro's final frame and the
|
||||||
|
// onboarding/login screen actually mounting: a visible flash on every
|
||||||
|
// fresh install. Await the push so the first RouterView render is
|
||||||
|
// already the destination.
|
||||||
|
const reveal = () => {
|
||||||
|
showSplash.value = false
|
||||||
|
document.body.classList.add('splash-complete')
|
||||||
|
isReady.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const devMode = import.meta.env.VITE_DEV_MODE
|
const devMode = import.meta.env.VITE_DEV_MODE
|
||||||
if (devMode === 'setup' || devMode === 'existing') {
|
if (devMode === 'setup' || devMode === 'existing') {
|
||||||
router.push('/login').catch(() => {})
|
await router.push('/login').catch(() => {})
|
||||||
|
reveal()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,7 +567,8 @@ async function handleSplashComplete() {
|
|||||||
{
|
{
|
||||||
const { IS_DEMO } = await import('@/composables/useDemoIntro')
|
const { IS_DEMO } = await import('@/composables/useDemoIntro')
|
||||||
if (IS_DEMO) {
|
if (IS_DEMO) {
|
||||||
router.push('/onboarding/intro').catch(() => {})
|
await router.push('/onboarding/intro').catch(() => {})
|
||||||
|
reveal()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -550,27 +577,24 @@ async function handleSplashComplete() {
|
|||||||
const { checkOnboardingStatus } = await import('@/composables/useOnboarding')
|
const { checkOnboardingStatus } = await import('@/composables/useOnboarding')
|
||||||
const seenOnboarding = await checkOnboardingStatus()
|
const seenOnboarding = await checkOnboardingStatus()
|
||||||
if (seenOnboarding === true) {
|
if (seenOnboarding === true) {
|
||||||
router.push('/login').catch(() => {})
|
await router.push('/login').catch(() => {})
|
||||||
return
|
} else if (seenOnboarding === false) {
|
||||||
}
|
await router.push('/onboarding/intro').catch(() => {})
|
||||||
if (seenOnboarding === false) {
|
} else if (localStorage.getItem('neode_onboarding_complete') === '1') {
|
||||||
router.push('/onboarding/intro').catch(() => {})
|
// Backend unreachable after retries. Prefer the localStorage
|
||||||
return
|
// cache on THIS browser (if a prior successful check set it) —
|
||||||
}
|
// otherwise defer to RootRedirect which polls + retries rather
|
||||||
// Backend unreachable after retries. Prefer the localStorage
|
// than forcing an already-onboarded user through the wizard.
|
||||||
// cache on THIS browser (if a prior successful check set it) —
|
await router.push('/login').catch(() => {})
|
||||||
// otherwise defer to RootRedirect which polls + retries rather
|
|
||||||
// than forcing an already-onboarded user through the wizard.
|
|
||||||
if (localStorage.getItem('neode_onboarding_complete') === '1') {
|
|
||||||
router.push('/login').catch(() => {})
|
|
||||||
} else {
|
} else {
|
||||||
router.push('/').catch(() => {})
|
await router.push('/').catch(() => {})
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Do NOT default to /onboarding/intro here. RootRedirect has retry
|
// Do NOT default to /onboarding/intro here. RootRedirect has retry
|
||||||
// + polling + boot-screen handling; let it decide.
|
// + polling + boot-screen handling; let it decide.
|
||||||
router.push('/').catch(() => {})
|
await router.push('/').catch(() => {})
|
||||||
}
|
}
|
||||||
|
reveal()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user