diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 2d3bf1125..e1a4f81f9 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -161,6 +161,7 @@ const messages: TranslationMap = { 'clearData.failed': 'فشل مسح البيانات وتسجيل الخروج. يرجى المحاولة مرة أخرى.', 'clearData.failedLogout': 'فشل تسجيل الخروج. يرجى المحاولة مرة أخرى.', 'clearData.failedPersist': 'فشل مسح حالة التطبيق المحفوظة. يرجى المحاولة مرة أخرى.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'مرحبًا بك في OpenHuman', 'welcome.subtitle': 'مساعدك الذكي الشخصي. خاص وبسيط وبالغ القوة.', 'welcome.connectPrompt': 'ضبط عنوان URL للـ RPC (متقدم)', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index d67f82e50..b7884d1bf 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -161,6 +161,7 @@ const messages: TranslationMap = { 'clearData.failed': 'ডেটা মুছতে ও লগ আউট করতে ব্যর্থ হয়েছে। আবার চেষ্টা করুন।', 'clearData.failedLogout': 'লগ আউট করতে ব্যর্থ হয়েছে। আবার চেষ্টা করুন।', 'clearData.failedPersist': 'অ্যাপ স্টেট মুছতে ব্যর্থ হয়েছে। আবার চেষ্টা করুন।', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'OpenHuman-এ স্বাগতম', 'welcome.subtitle': 'আপনার ব্যক্তিগত AI সুপার ইন্টেলিজেন্স। ব্যক্তিগত, সহজ এবং অত্যন্ত শক্তিশালী।', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index fcd654fff..b193a84c0 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -169,6 +169,7 @@ const messages: TranslationMap = { 'clearData.failedLogout': 'Abmelden fehlgeschlagen. Bitte versuche es erneut.', 'clearData.failedPersist': 'Der persistente App-Status konnte nicht gelöscht werden. Bitte versuche es erneut.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Willkommen bei OpenHuman', 'welcome.subtitle': 'Deine persönliche KI-Superintelligenz. Privat, einfach und äußerst leistungsstark.', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index cacb9fdfa..73a4da931 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -178,6 +178,7 @@ const en: TranslationMap = { 'clearData.failedPersist': 'Failed to clear persisted app state. Please try again.', // Welcome page + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Welcome to OpenHuman', 'welcome.subtitle': 'Your personal AI super intelligence. Private, simple and extremely powerful.', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index e69a212ec..2fc099241 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -170,6 +170,7 @@ const messages: TranslationMap = { 'clearData.failedLogout': 'No se pudo cerrar sesión. Inténtalo de nuevo.', 'clearData.failedPersist': 'No se pudo borrar el estado persistido de la app. Inténtalo de nuevo.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Bienvenido a OpenHuman', 'welcome.subtitle': 'Tu super inteligencia artificial personal. Privada, simple y extremadamente poderosa.', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index 49b9abb6d..665f14489 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -168,6 +168,7 @@ const messages: TranslationMap = { 'clearData.failed': "Échec de l'effacement des données et de la déconnexion. Réessaie.", 'clearData.failedLogout': 'Échec de la déconnexion. Réessaie.', 'clearData.failedPersist': "Échec de l'effacement de l'état persisté. Réessaie.", + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Bienvenue sur OpenHuman', 'welcome.subtitle': 'Ton assistant IA personnel super-intelligent. Privé, simple et extrêmement puissant.', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 7454ef2a7..980ddc943 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -162,6 +162,7 @@ const messages: TranslationMap = { 'clearData.failed': 'डेटा क्लियर करने और लॉगआउट में दिक्कत आई। दोबारा कोशिश करें।', 'clearData.failedLogout': 'लॉग आउट नहीं हो पाया। दोबारा कोशिश करें।', 'clearData.failedPersist': 'सेव्ड ऐप स्टेट क्लियर नहीं हो पाई। दोबारा कोशिश करें।', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'OpenHuman में आपका स्वागत है', 'welcome.subtitle': 'आपकी पर्सनल AI सुपर इंटेलिजेंस। प्राइवेट, सिंपल और बेहद पावरफुल।', 'welcome.connectPrompt': 'RPC URL कॉन्फिगर करें (एडवांस्ड)', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 71e0226cd..1f021566a 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -163,6 +163,7 @@ const messages: TranslationMap = { 'clearData.failed': 'Gagal membersihkan data dan keluar. Silakan coba lagi.', 'clearData.failedLogout': 'Gagal keluar. Silakan coba lagi.', 'clearData.failedPersist': 'Gagal membersihkan status aplikasi tersimpan. Silakan coba lagi.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Selamat datang di OpenHuman', 'welcome.subtitle': 'Asisten AI Anda untuk komunitas', 'welcome.connectPrompt': 'Konfigurasikan RPC URL (Lanjutan)', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index 8c9176741..2b23ed57d 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -167,6 +167,7 @@ const messages: TranslationMap = { 'clearData.failed': 'Impossibile cancellare i dati ed effettuare il logout. Riprova.', 'clearData.failedLogout': 'Impossibile effettuare il logout. Riprova.', 'clearData.failedPersist': "Impossibile cancellare lo stato persistente dell'app. Riprova.", + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Benvenuto in OpenHuman', 'welcome.subtitle': 'La tua super intelligenza AI personale. Privata, semplice ed estremamente potente.', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 15b2ebc46..ef66fdda6 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -161,6 +161,7 @@ const messages: TranslationMap = { 'clearData.failed': '데이터 삭제 및 로그아웃에 실패했습니다. 다시 시도해 주세요.', 'clearData.failedLogout': '로그아웃에 실패했습니다. 다시 시도해 주세요.', 'clearData.failedPersist': '저장된 앱 상태를 삭제하지 못했습니다. 다시 시도해 주세요.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'OpenHuman에 오신 것을 환영합니다', 'welcome.subtitle': '개인용 AI 슈퍼 인텔리전스입니다. 비공개이며, 간단하고, 매우 강력합니다.', 'welcome.connectPrompt': 'RPC URL 구성(고급)', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index b44d25e0d..807e0f8ef 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -165,6 +165,7 @@ const messages: TranslationMap = { 'clearData.failedLogout': 'Nie udało się wylogować. Spróbuj ponownie.', 'clearData.failedPersist': 'Nie udało się wyczyścić utrwalonego stanu aplikacji. Spróbuj ponownie.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Witaj w OpenHuman', 'welcome.subtitle': 'Twoja osobista superinteligencja AI. Prywatna, prosta i niezwykle potężna.', 'welcome.connectPrompt': 'Skonfiguruj URL RPC (zaawansowane)', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index 39594219e..4d768b638 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -168,6 +168,7 @@ const messages: TranslationMap = { 'clearData.failedLogout': 'Falha ao sair. Por favor, tente novamente.', 'clearData.failedPersist': 'Falha ao limpar o estado persistido do app. Por favor, tente novamente.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Bem-vindo ao OpenHuman', 'welcome.subtitle': 'Sua super inteligência artificial pessoal. Privada, simples e extremamente poderosa.', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index a7f5933a3..bbff41e81 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -164,6 +164,7 @@ const messages: TranslationMap = { 'clearData.failed': 'Не удалось очистить данные и выйти. Попробуй ещё раз.', 'clearData.failedLogout': 'Не удалось выйти. Попробуй ещё раз.', 'clearData.failedPersist': 'Не удалось сбросить состояние приложения. Попробуй ещё раз.', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': 'Добро пожаловать в OpenHuman', 'welcome.subtitle': 'Твой персональный суперинтеллект. Приватный, простой и невероятно мощный.', 'welcome.connectPrompt': 'Настроить RPC URL (дополнительно)', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index e76829e01..9b95a7b60 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -158,6 +158,7 @@ const messages: TranslationMap = { 'clearData.failed': '清除数据失败,请重试。', 'clearData.failedLogout': '退出登录失败,请重试。', 'clearData.failedPersist': '清除持久化应用状态失败,请重试。', + 'welcome.logoAlt': 'OpenHuman', 'welcome.title': '欢迎使用 OpenHuman', 'welcome.subtitle': '你的私人 AI 超级智能。私密、简单、强大。', 'welcome.connectPrompt': '输入 Core RPC 地址以开始使用', diff --git a/app/src/pages/Welcome.tsx b/app/src/pages/Welcome.tsx index 7a1f43b97..5103f40c6 100644 --- a/app/src/pages/Welcome.tsx +++ b/app/src/pages/Welcome.tsx @@ -4,7 +4,6 @@ import { useNavigate } from 'react-router-dom'; import OAuthProviderButton from '../components/oauth/OAuthProviderButton'; import { oauthProviderConfigs } from '../components/oauth/providerConfigs'; -import RotatingTetrahedronCanvas from '../components/RotatingTetrahedronCanvas'; import Button from '../components/ui/Button'; import { useT } from '../lib/i18n/I18nContext'; import { useCoreState } from '../providers/CoreStateProvider'; @@ -127,9 +126,11 @@ const Welcome = () => {
-
- -
+ {t('welcome.logoAlt')}

diff --git a/app/src/pages/__tests__/Welcome.test.tsx b/app/src/pages/__tests__/Welcome.test.tsx index 2ff6c15f6..573eb7cd6 100644 --- a/app/src/pages/__tests__/Welcome.test.tsx +++ b/app/src/pages/__tests__/Welcome.test.tsx @@ -23,10 +23,6 @@ vi.mock('../../providers/CoreStateProvider', () => ({ const oauthButtonSpy = vi.fn(); const oauthOverrideSpy = vi.fn(); -vi.mock('../../components/RotatingTetrahedronCanvas', () => ({ - default: () =>
, -})); - vi.mock('../../components/oauth/OAuthProviderButton', () => ({ default: ({ provider, diff --git a/app/src/pages/onboarding/pages/CustomInferencePage.test.tsx b/app/src/pages/onboarding/pages/CustomInferencePage.test.tsx index c15bac803..cea4639fe 100644 --- a/app/src/pages/onboarding/pages/CustomInferencePage.test.tsx +++ b/app/src/pages/onboarding/pages/CustomInferencePage.test.tsx @@ -1,5 +1,5 @@ import { configureStore } from '@reduxjs/toolkit'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -11,6 +11,7 @@ import CustomInferencePage from './CustomInferencePage'; const navigateMock = vi.fn(); const setDraftMock = vi.fn(); +const clearSessionMock = vi.fn().mockResolvedValue(undefined); vi.mock('react-router-dom', async importOriginal => { const actual = await importOriginal(); @@ -22,7 +23,10 @@ vi.mock('../../../components/settings/panels/AIPanel', () => ({ })); vi.mock('../../../providers/CoreStateProvider', () => ({ - useCoreState: () => ({ snapshot: { sessionToken: 'header.payload.local' } }), + useCoreState: () => ({ + snapshot: { sessionToken: 'header.payload.local' }, + clearSession: clearSessionMock, + }), })); vi.mock('../OnboardingContext', () => ({ @@ -54,6 +58,7 @@ describe('CustomInferencePage', () => { beforeEach(() => { navigateMock.mockReset(); setDraftMock.mockReset(); + clearSessionMock.mockClear(); }); it('forces configure mode and hides the default/configure chooser for local sessions', () => { @@ -67,4 +72,39 @@ describe('CustomInferencePage', () => { screen.queryByTestId('onboarding-custom-inference-step-configure') ).not.toBeInTheDocument(); }); + + it('waits for clearSession to finish before navigating back to the welcome page', async () => { + let resolveClearSession!: () => void; + clearSessionMock.mockReturnValueOnce( + new Promise(resolve => { + resolveClearSession = resolve; + }) + ); + + renderPage(); + + fireEvent.click(screen.getByRole('button', { name: 'Back' })); + + // Navigation must not race ahead of the session being cleared — otherwise + // PublicRoute bounces "/" back to /home with the session still live. + expect(clearSessionMock).toHaveBeenCalledTimes(1); + expect(navigateMock).not.toHaveBeenCalled(); + + resolveClearSession(); + await waitFor(() => expect(navigateMock).toHaveBeenCalledWith('/')); + }); + + it('stays on the step and does not navigate when clearSession fails', async () => { + clearSessionMock.mockRejectedValueOnce(new Error('clear failed')); + + renderPage(); + + fireEvent.click(screen.getByRole('button', { name: 'Back' })); + + await waitFor(() => expect(clearSessionMock).toHaveBeenCalledTimes(1)); + // Give the rejected promise a chance to settle, then confirm we did not + // navigate to "/" with a still-active session. + await Promise.resolve(); + expect(navigateMock).not.toHaveBeenCalled(); + }); }); diff --git a/app/src/pages/onboarding/pages/CustomInferencePage.tsx b/app/src/pages/onboarding/pages/CustomInferencePage.tsx index 4f1842be2..35ec94410 100644 --- a/app/src/pages/onboarding/pages/CustomInferencePage.tsx +++ b/app/src/pages/onboarding/pages/CustomInferencePage.tsx @@ -4,7 +4,7 @@ import CustomWizardConfigPage from './CustomWizardConfigPage'; const CustomInferencePage = () => ( } /> ); diff --git a/app/src/pages/onboarding/pages/CustomWizardConfigPage.tsx b/app/src/pages/onboarding/pages/CustomWizardConfigPage.tsx index c776e57e6..fc704360c 100644 --- a/app/src/pages/onboarding/pages/CustomWizardConfigPage.tsx +++ b/app/src/pages/onboarding/pages/CustomWizardConfigPage.tsx @@ -1,3 +1,4 @@ +import createDebug from 'debug'; import { type ReactNode, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -13,6 +14,10 @@ import { } from '../OnboardingContext'; import CustomWizardStep from '../steps/CustomWizardStep'; +const log = createDebug('app:onboarding:custom'); + +const describeError = (err: unknown): string => (err instanceof Error ? err.message : String(err)); + const LOCAL_DEFAULT_DISABLED_REASON = 'Managed setup requires OpenHuman sign-in and is unavailable in local mode.'; @@ -29,7 +34,7 @@ export default function CustomWizardConfigPage({ }: CustomWizardConfigPageProps) { const { t } = useT(); const navigate = useNavigate(); - const { snapshot } = useCoreState(); + const { snapshot, clearSession } = useCoreState(); const { draft, setDraft, completeAndExit } = useOnboardingContext(); const isLocalSession = isLocalSessionToken(snapshot.sessionToken); const stepIndex = CUSTOM_WIZARD_STEPS.indexOf(stepKey); @@ -52,8 +57,26 @@ export default function CustomWizardConfigPage({ }; const isLast = stepIndex === CUSTOM_WIZARD_STEPS.length - 1; + const isFirst = stepIndex === 0; const namespace = `onboarding.custom.${stepKey}`; + const handleBack = async () => { + // Going back from the first step returns to the welcome/login screen. + // A session is always present here (OAuth or "Continue Locally"), so we + // must clear it first — otherwise PublicRoute bounces "/" to /home. + if (isFirst) { + try { + await clearSession(); + } catch (err) { + // Navigating to "/" with a live session would just bounce back to /home + // via PublicRoute — so stay on the step and surface a dev-only diagnostic. + log('[onboarding:custom-%s] clearSession on back failed: %s', stepKey, describeError(err)); + return; + } + } + navigate(backRoute ?? CUSTOM_WIZARD_ROUTES[CUSTOM_WIZARD_STEPS[stepIndex - 1]]); + }; + return ( navigate(backRoute ?? CUSTOM_WIZARD_ROUTES[CUSTOM_WIZARD_STEPS[stepIndex - 1]])} + onBack={() => void handleBack()} onContinue={async () => { trackEvent('onboarding_step_complete', { step_name: `custom_${stepKey}`, @@ -79,7 +102,7 @@ export default function CustomWizardConfigPage({ try { await completeAndExit(); } catch (err) { - console.error(`[onboarding:custom-${stepKey}] completeAndExit failed`, err); + log('[onboarding:custom-%s] completeAndExit failed: %s', stepKey, describeError(err)); } return; }