From 4260b7469bd487e574f97bdf99db31abf2caaa67 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 10 Sep 2026 22:20:38 -0700 Subject: Don't show "Get started" to someone who is signed in. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boot decides whether a session restores, but plenty renders before it gets there — the deferred feature chunks land and repaint, an emitter ticks — and a render that early has no idea whether anyone is signed in. It painted the welcome screen anyway, so a signed-in wallet flashed the sign-in page for the ~75ms before boot restored it. Waiting for the ark store widened the gap; the race was already there. The signed-out screen is now the one screen boot won't guess at: until the session question is answered, the frame holds the boot shell (the brand row it's already showing). Every other screen is a deliberate choice — a gift claim, a locked vault, a chat — and still paints immediately. A release timer makes sure a boot path that returns early can't hold it back for good. tools/boot-signin-flash-test.js widens the race and checks every frame of four reloads; before this it caught the flash on every one. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx --- src/app.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'src/app.js') diff --git a/src/app.js b/src/app.js index e5c3a06..f36c9bc 100644 --- a/src/app.js +++ b/src/app.js @@ -444,6 +444,16 @@ let _renderDeferred = false; // and every app render (feature init, emitter ticks) must stand down or the // onboarding screen paints over the chat moments after it mounts. let _publicChatMode = false; +// True until boot has decided whether a session is restoring. Anything that +// renders before then (a deferred feature chunk landing, an emitter tick) is +// rendering without knowing whether anyone is signed in — and the signed-out +// welcome screen is the one screen that must never be guessed at. The boot +// shell holds the frame for those few ms instead; see the dispatch below. +let _bootDeciding = true; +// Belt and braces: a boot path that returns early (or throws) must not leave +// the welcome screen held back forever. Nothing legitimate is still deciding +// four seconds in — the store wait alone caps at 800ms. +setTimeout(() => { if (_bootDeciding) { _bootDeciding = false; render(); } }, 4000); function render() { if (_publicChatMode) return; // A background render mid carousel-drag rebuilds the strip; the morph then @@ -529,7 +539,13 @@ function renderInner() { ? vaultScreen() : ui.screen === 'howItWorks' ? howItWorksScreen() - : shouldOnboard() ? onboardScreen() : unlockScreen()); + : _bootDeciding + // Not "nobody is signed in" — "we don't know yet". Painting + // Get started here is what flashed the sign-in page over a + // wallet that was about to restore. Every other screen is a + // deliberate choice and still paints; this one waits. + ? h('div', { class: 'col', style: 'gap:16px' }, brandHeader(false)) + : shouldOnboard() ? onboardScreen() : unlockScreen()); // Navigation animates; background repaints must not. The key is every // ui field that decides which page is on screen. const navKey = [ui.screen, ui.tab === 'settings', ui.chatOpen, ui.msgView, ui.msgPeer, ui.msgCommunity, @@ -5175,6 +5191,7 @@ loadLocale(getLang()).finally(async () => { // wrong balance. The hook caps its own wait. const storeReady = featureHook('arkStoreReady'); if (storeReady) await storeReady; + _bootDeciding = false; // from here on the screen is a decision, not a guess if (!restoreAccountsState()) render(); }); -- cgit v1.2.3