diff --git a/core/Cargo.lock b/core/Cargo.lock index 1c648364..1da20db5 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "archipelago" -version = "1.7.32-alpha" +version = "1.7.33-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index edc724e3..28e858db 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "archipelago" -version = "1.7.32-alpha" +version = "1.7.33-alpha" edition = "2021" description = "Archipelago Bitcoin Node OS - Native backend" authors = ["Archipelago Team"] diff --git a/neode-ui/src/composables/useOnboarding.ts b/neode-ui/src/composables/useOnboarding.ts index b20e9156..4c70acd3 100644 --- a/neode-ui/src/composables/useOnboarding.ts +++ b/neode-ui/src/composables/useOnboarding.ts @@ -19,11 +19,13 @@ async function callWithRetry(fn: () => Promise, maxRetries = 3): Promise { - // localStorage is set on completion and survives backend restarts/resets - if (localStorage.getItem('neode_onboarding_complete') === '1') return true + // Prefer the backend — localStorage gets stale across nodes (a + // browser that onboarded node A would otherwise treat fresh node B + // as already-onboarded and skip the wizard entirely). Only fall + // back to localStorage if the backend is unreachable. const result = await callWithRetry(() => rpcClient.isOnboardingComplete(), 2) if (result !== null) return result - return false + return localStorage.getItem('neode_onboarding_complete') === '1' } export async function completeOnboarding(): Promise { diff --git a/neode-ui/src/stores/__tests__/loginTransition.test.ts b/neode-ui/src/stores/__tests__/loginTransition.test.ts index 089986e3..0eb66cc3 100644 --- a/neode-ui/src/stores/__tests__/loginTransition.test.ts +++ b/neode-ui/src/stores/__tests__/loginTransition.test.ts @@ -10,10 +10,19 @@ describe('useLoginTransitionStore', () => { it('starts with all flags false', () => { const store = useLoginTransitionStore() expect(store.justLoggedIn).toBe(false) + expect(store.justCompletedOnboarding).toBe(false) expect(store.pendingWelcomeTyping).toBe(false) expect(store.startWelcomeTyping).toBe(false) }) + it('setJustCompletedOnboarding updates justCompletedOnboarding', () => { + const store = useLoginTransitionStore() + store.setJustCompletedOnboarding(true) + expect(store.justCompletedOnboarding).toBe(true) + store.setJustCompletedOnboarding(false) + expect(store.justCompletedOnboarding).toBe(false) + }) + it('setJustLoggedIn updates justLoggedIn', () => { const store = useLoginTransitionStore() store.setJustLoggedIn(true) diff --git a/neode-ui/src/stores/loginTransition.ts b/neode-ui/src/stores/loginTransition.ts index c85d71b4..c1004000 100644 --- a/neode-ui/src/stores/loginTransition.ts +++ b/neode-ui/src/stores/loginTransition.ts @@ -4,6 +4,13 @@ import { ref } from 'vue' /** Signals that we just logged in - Dashboard uses this for zoom + oomph */ export const useLoginTransitionStore = defineStore('loginTransition', () => { const justLoggedIn = ref(false) + /** + * True only when the user just finished the onboarding wizard + * (first password setup), as distinct from a regular re-login. + * Dashboard uses this to decide whether to play the full glitchy + * reveal vs just a quick interface-draw. + */ + const justCompletedOnboarding = ref(false) /** Show empty welcome block until typing starts (hide static text) */ const pendingWelcomeTyping = ref(false) /** Trigger welcome typing on Home - set true after dashboard animation finishes */ @@ -13,6 +20,10 @@ export const useLoginTransitionStore = defineStore('loginTransition', () => { justLoggedIn.value = value } + function setJustCompletedOnboarding(value: boolean) { + justCompletedOnboarding.value = value + } + function setPendingWelcomeTyping(value: boolean) { pendingWelcomeTyping.value = value } @@ -24,6 +35,8 @@ export const useLoginTransitionStore = defineStore('loginTransition', () => { return { justLoggedIn, setJustLoggedIn, + justCompletedOnboarding, + setJustCompletedOnboarding, pendingWelcomeTyping, setPendingWelcomeTyping, startWelcomeTyping, diff --git a/neode-ui/src/views/Dashboard.vue b/neode-ui/src/views/Dashboard.vue index 35cf90a9..571d80cb 100644 --- a/neode-ui/src/views/Dashboard.vue +++ b/neode-ui/src/views/Dashboard.vue @@ -264,10 +264,13 @@ watch(() => route.path, (newPath) => { onMounted(() => { previousRoutePath = route.path document.body.classList.add('dashboard-active') - if (loginTransition.justLoggedIn) { + if (loginTransition.justCompletedOnboarding) { + // Full glitchy reveal — only on the very first dashboard entry + // right after onboarding (one-time event, persists in feel). playDashboardLoadOomph() showZoomIn.value = true loginTransition.setPendingWelcomeTyping(true) + loginTransition.setJustCompletedOnboarding(false) loginTransition.setJustLoggedIn(false) const triggerRevealGlitch = () => { isGlitching.value = true @@ -281,6 +284,18 @@ onMounted(() => { loginTransition.setStartWelcomeTyping(true) loginTransition.setPendingWelcomeTyping(false) }, 4000) + } else if (loginTransition.justLoggedIn) { + // Regular re-login — quick interface draw, no triple glitch flashes. + // Just the zoom-in for a short beat, then welcome typing fires fast. + playDashboardLoadOomph() + showZoomIn.value = true + loginTransition.setPendingWelcomeTyping(true) + loginTransition.setJustLoggedIn(false) + scheduledTimeout(() => { showZoomIn.value = false }, 1200) + scheduledTimeout(() => { + loginTransition.setStartWelcomeTyping(true) + loginTransition.setPendingWelcomeTyping(false) + }, 600) } window.addEventListener('keydown', handleKioskShortcuts) diff --git a/neode-ui/src/views/Login.vue b/neode-ui/src/views/Login.vue index ce2b17fd..12ca0062 100644 --- a/neode-ui/src/views/Login.vue +++ b/neode-ui/src/views/Login.vue @@ -408,6 +408,7 @@ async function handleSetup() { stopSynthwave() whooshAway.value = true playLoginSuccessWhoosh() + loginTransition.setJustCompletedOnboarding(true) loginTransition.setJustLoggedIn(true) await new Promise(r => setTimeout(r, 520)) await router.replace(loginRedirectTo.value).catch(() => { diff --git a/neode-ui/src/views/OnboardingWrapper.vue b/neode-ui/src/views/OnboardingWrapper.vue index 76e2546d..0b9f3da1 100644 --- a/neode-ui/src/views/OnboardingWrapper.vue +++ b/neode-ui/src/views/OnboardingWrapper.vue @@ -86,9 +86,23 @@ const videoBackgroundRoutes = ['/onboarding/intro', '/login'] // Login uses video when coming from splash, or static + glitch when direct const isLoginRoute = computed(() => route.path === '/login') +// True once onboarding is complete. Used to skip the intro video on +// the /login route so that returning (logged-out) users go straight +// to the screensaver-style static + glitch background instead of +// replaying the full intro every time. +const onboardingDone = computed(() => { + try { + return localStorage.getItem('neode_onboarding_complete') === '1' + } catch { + return false + } +}) + // Check if current route should use video background const useVideoBackground = computed(() => { - return videoBackgroundRoutes.includes(route.path) + if (!videoBackgroundRoutes.includes(route.path)) return false + if (route.path === '/login' && onboardingDone.value) return false + return true }) // Map each route to a specific background image diff --git a/neode-ui/src/views/RootRedirect.vue b/neode-ui/src/views/RootRedirect.vue index c3935b75..caac6831 100644 --- a/neode-ui/src/views/RootRedirect.vue +++ b/neode-ui/src/views/RootRedirect.vue @@ -129,7 +129,31 @@ onMounted(async () => { return } - // Server not ready — show boot screen (waiting for backend) + // Server not ready. The full BootScreen is meant for a genuine + // cold-start (fresh install), not for the brief blip during an + // OTA update where the backend restarts. If onboarding has already + // completed we just keep the spinner and retry until the server + // responds again. + const wasOnboardedBefore = localStorage.getItem('neode_onboarding_complete') === '1' + if (wasOnboardedBefore) { + log('server down + onboarded → polling without boot screen') + let retries = 0 + const maxRetries = 30 // 30 * 2s = 60s before giving up and showing boot screen + const poll = setInterval(async () => { + retries++ + if (await quickHealthCheck()) { + clearInterval(poll) + proceedToApp() + return + } + if (retries >= maxRetries) { + clearInterval(poll) + log('server still down after retries → falling back to boot screen') + showBootScreen.value = true + } + }, 2000) + return + } showBootScreen.value = true }) diff --git a/neode-ui/vite.config.ts b/neode-ui/vite.config.ts index dcd3167c..6c84b093 100644 --- a/neode-ui/vite.config.ts +++ b/neode-ui/vite.config.ts @@ -94,7 +94,7 @@ export default defineConfig({ urlPattern: /\/assets\/.*/i, handler: 'CacheFirst', options: { - cacheName: 'assets-cache-v2', + cacheName: 'assets-cache-v3', expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days diff --git a/releases/manifest.json b/releases/manifest.json index acea168f..9f29e1b8 100644 --- a/releases/manifest.json +++ b/releases/manifest.json @@ -1,26 +1,29 @@ { - "version": "1.7.32-alpha", + "version": "1.7.33-alpha", "release_date": "2026-04-22", "changelog": [ - "Critical fix: the v1.7.31-alpha frontend package shipped with the wrong archive layout, which caused the web UI to return 403/500 after the update landed. v1.7.32-alpha ships the frontend correctly — nodes that got stuck on the 403 page will auto-recover on this update.", - "Shutdown fix: updates no longer briefly show the archipelago service as 'Failed' in systemd. The old version was logging 'shut down cleanly' but leaving a background mDNS thread alive, so systemd would force-kill it 15 seconds later and mark the unit failed. The process now exits promptly after saving its state." + "Onboarding fix: a fresh node would skip the full onboarding wizard and dump you straight at 'set password' if your browser had ever onboarded another node. The check now asks the actual node first instead of trusting browser memory.", + "Lock screen fix: logging out used to replay the full intro video every single time. Now once you've onboarded, logging out drops you on the static lock-screen background — login is instant, no movie.", + "Update fix: a brief network hiccup during an OTA update no longer triggers the full mock 'boot screen' animation on already-onboarded nodes. The page just shows a quiet spinner and reconnects when the backend is back. Boot screen is reserved for genuine fresh boots.", + "Login animation: the glitchy zoom-and-flash reveal now only plays the very first time you reach the dashboard after onboarding. Every subsequent login gets a quick interface draw — fast, no glitches.", + "PWA cache bumped so old browsers/devices reliably pick up new UI versions instead of serving stale cached assets after an update." ], "components": [ { "name": "archipelago", - "current_version": "1.7.31-alpha", - "new_version": "1.7.32-alpha", - "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.32-alpha/archipelago", - "sha256": "f5c0d51a3235b7619ac5b71140abd07b04cc90555205a4c0416c8c8c4a9a4588", - "size_bytes": 40791792 + "current_version": "1.7.32-alpha", + "new_version": "1.7.33-alpha", + "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.33-alpha/archipelago", + "sha256": "c75a226658cb8af7ecb4eff937cbc221bb2b1c93bf1dadd61c99b2f550376c8b", + "size_bytes": 40793648 }, { - "name": "archipelago-frontend-1.7.32-alpha.tar.gz", - "current_version": "1.7.31-alpha", - "new_version": "1.7.32-alpha", - "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.32-alpha/archipelago-frontend-1.7.32-alpha.tar.gz", - "sha256": "1eb1deaf479538f0552f395fc1aea67b1a247ddef6bfbf436353ba1997eac1be", - "size_bytes": 77008678 + "name": "archipelago-frontend-1.7.33-alpha.tar.gz", + "current_version": "1.7.32-alpha", + "new_version": "1.7.33-alpha", + "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.33-alpha/archipelago-frontend-1.7.33-alpha.tar.gz", + "sha256": "d6fd4648046d4ea05d33ef56180afda80e118f6d655ba7d339a2135a0a28e838", + "size_bytes": 77011007 } ] } diff --git a/releases/v1.7.33-alpha/archipelago b/releases/v1.7.33-alpha/archipelago new file mode 100755 index 00000000..7c52c378 Binary files /dev/null and b/releases/v1.7.33-alpha/archipelago differ diff --git a/releases/v1.7.33-alpha/archipelago-frontend-1.7.33-alpha.tar.gz b/releases/v1.7.33-alpha/archipelago-frontend-1.7.33-alpha.tar.gz new file mode 100644 index 00000000..4daea319 Binary files /dev/null and b/releases/v1.7.33-alpha/archipelago-frontend-1.7.33-alpha.tar.gz differ