From 730a8650b3351f795870b776daf15e8f20400690 Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:44:49 -0700 Subject: [PATCH] =?UTF-8?q?feat(shell):=20root=20two-panel=20layout=20?= =?UTF-8?q?=E2=80=94=20unified=20sidebar=20+=20chat-as-home=20(#3751)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/App.tsx | 91 ++-- app/src/AppRoutes.tsx | 12 +- app/src/__tests__/App.boot.test.tsx | 2 +- app/src/components/BottomTabBar.tsx | 359 -------------- app/src/components/ConnectionIndicator.tsx | 19 +- app/src/components/DefaultRedirect.tsx | 4 +- app/src/components/LanguageSelect.tsx | 8 +- .../__tests__/BottomTabBar.test.tsx | 367 -------------- .../__tests__/ConnectionIndicator.test.tsx | 18 +- .../__tests__/DefaultRedirect.test.tsx | 5 +- app/src/components/accounts/WebviewHost.tsx | 15 +- app/src/components/chat/ChatNewWindowHero.tsx | 185 +++++++ .../components/chat/ComposerTokenStats.tsx | 20 +- app/src/components/home/HomeBanners.tsx | 63 +-- .../home/__tests__/HomeBanners.test.tsx | 2 +- .../intelligence/memoryGraphLayout.ts | 18 +- .../components/layout/shell/AppSidebar.tsx | 51 ++ .../layout/shell/RootShellLayout.tsx | 205 ++++++++ .../components/layout/shell/SidebarHeader.tsx | 150 ++++++ .../components/layout/shell/SidebarNav.tsx | 89 ++++ .../components/layout/shell/SidebarSlot.tsx | 65 +++ app/src/components/layout/shell/navIcons.tsx | 107 +++++ .../settings/layout/SettingsLayout.tsx | 60 +-- .../settings/layout/SettingsSidebar.tsx | 6 - app/src/config/__tests__/navConfig.test.ts | 31 +- app/src/config/navConfig.ts | 21 +- app/src/features/human/HumanPage.tsx | 13 +- app/src/lib/i18n/ar.ts | 10 +- app/src/lib/i18n/bn.ts | 10 +- app/src/lib/i18n/de.ts | 9 +- app/src/lib/i18n/en.ts | 20 +- app/src/lib/i18n/es.ts | 10 +- app/src/lib/i18n/fr.ts | 9 +- app/src/lib/i18n/hi.ts | 10 +- app/src/lib/i18n/id.ts | 10 +- app/src/lib/i18n/it.ts | 9 +- app/src/lib/i18n/ko.ts | 10 +- app/src/lib/i18n/pl.ts | 10 +- app/src/lib/i18n/pt.ts | 9 +- app/src/lib/i18n/ru.ts | 9 +- app/src/lib/i18n/zh-CN.ts | 9 +- app/src/pages/Accounts.tsx | 194 ++++---- app/src/pages/Brain.tsx | 58 ++- app/src/pages/Conversations.tsx | 453 ++++++++---------- app/src/pages/Home.tsx | 16 +- app/src/pages/Skills.tsx | 55 +-- .../Conversations.attachments.test.tsx | 20 +- .../__tests__/Conversations.render.test.tsx | 177 +------ app/src/store/themeSlice.test.ts | 6 +- app/src/store/themeSlice.ts | 2 +- app/src/test/test-utils.tsx | 14 +- 51 files changed, 1562 insertions(+), 1563 deletions(-) delete mode 100644 app/src/components/BottomTabBar.tsx delete mode 100644 app/src/components/__tests__/BottomTabBar.test.tsx create mode 100644 app/src/components/chat/ChatNewWindowHero.tsx create mode 100644 app/src/components/layout/shell/AppSidebar.tsx create mode 100644 app/src/components/layout/shell/RootShellLayout.tsx create mode 100644 app/src/components/layout/shell/SidebarHeader.tsx create mode 100644 app/src/components/layout/shell/SidebarNav.tsx create mode 100644 app/src/components/layout/shell/SidebarSlot.tsx create mode 100644 app/src/components/layout/shell/navIcons.tsx diff --git a/app/src/App.tsx b/app/src/App.tsx index f4b42bad9..6bf335a54 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -13,12 +13,14 @@ import AppRoutes from './AppRoutes'; import AppBackground from './components/AppBackground'; import AppUpdatePrompt from './components/AppUpdatePrompt'; import BootCheckGate from './components/BootCheckGate/BootCheckGate'; -import BottomTabBar from './components/BottomTabBar'; import CommandProvider from './components/commands/CommandProvider'; import ServiceBlockingGate from './components/daemon/ServiceBlockingGate'; import DictationHotkeyManager from './components/DictationHotkeyManager'; import ErrorFallbackScreen from './components/ErrorFallbackScreen'; import KeyringConsentOverlay from './components/keyring/KeyringConsentOverlay'; +import AppSidebar from './components/layout/shell/AppSidebar'; +import RootShellLayout from './components/layout/shell/RootShellLayout'; +import { SidebarSlotProvider } from './components/layout/shell/SidebarSlot'; import LocalAIDownloadSnackbar from './components/LocalAIDownloadSnackbar'; import SecretPromptDialog from './components/mcp-setup/SecretPromptDialog'; import OpenhumanLinkModal from './components/OpenhumanLinkModal'; @@ -50,12 +52,13 @@ import { stopInternetStatusListener, } from './services/internetStatusListener'; import { + hideWebviewAccount, startWebviewAccountService, stopWebviewAccountService, } from './services/webviewAccountService'; import { persistor, store } from './store'; import { useAppSelector } from './store/hooks'; -import { isAccountsFullscreen } from './utils/accountsFullscreen'; +import { AGENT_ACCOUNT_ID } from './utils/accountsFullscreen'; import { DEV_FORCE_ONBOARDING } from './utils/config'; // Attach the `webview:event` listener at app boot so background recipe @@ -162,11 +165,6 @@ function AppShellDesktop() { const location = useLocation(); const navigate = useNavigate(); const { snapshot, isBootstrapping } = useCoreState(); - const activeAccountId = useAppSelector(state => state.accounts.activeAccountId); - // On /accounts, only the agent view keeps the tab bar + its reserved - // bottom padding. Any other selected "app" (e.g. WhatsApp) takes the - // full viewport so the embedded webview goes edge-to-edge. - const fullscreen = isAccountsFullscreen(location.pathname, activeAccountId); const onOnboardingRoute = location.pathname.startsWith('/onboarding'); const onboardingPending = !!snapshot.sessionToken && (DEV_FORCE_ONBOARDING || !snapshot.onboardingCompleted); @@ -183,9 +181,9 @@ function AppShellDesktop() { navigate('/onboarding', { replace: true }); } else if (!onboardingPending && onOnboardingRoute) { console.debug( - `[onboarding-gate] redirecting ${location.pathname} -> /home (onboarding complete)` + `[onboarding-gate] redirecting ${location.pathname} -> /chat (onboarding complete)` ); - navigate('/home', { replace: true }); + navigate('/chat', { replace: true }); } }, [ isBootstrapping, @@ -201,6 +199,17 @@ function AppShellDesktop() { trackPageView(location.pathname); }, [location.pathname]); + // Hide the active connected-app webview when we navigate away from the chat + // surface. The native CEF webview composites above the HTML, so without this + // it lingers on top of the newly-routed page until the user returns. + const activeAccountId = useAppSelector(state => state.accounts.activeAccountId); + useEffect(() => { + const onChat = location.pathname === '/chat' || location.pathname.startsWith('/chat/'); + if (!onChat && activeAccountId && activeAccountId !== AGENT_ACCOUNT_ID) { + void hideWebviewAccount(activeAccountId); + } + }, [location.pathname, activeAccountId]); + // Sync the notch indicator to the persisted always-on listening state once // the core is ready (once per boot). Extracted to a hook so it's testable. useNotchBootSync(isBootstrapping); @@ -214,32 +223,48 @@ function AppShellDesktop() { } }, [location.pathname, navType]); - return ( -
+ {statusCopy} +
+ + {/* Recovery: only when the local core is the broken link. */} + {blocking === 'core-unreachable' && ( +{restartError}
+ )} +- {t('home.banners.promoCreditsTitle').replace('{amount}', formatUsd(promoCredits))} -
-- {t('home.banners.promoCreditsBody')}{' '} - {' '} - {t('home.banners.promoCreditsUsage')} -
-+ {(() => { + // Single {amount} template; split so the amount renders bold inline. + const [before, after] = t('home.banners.promoCreditsBody').split('{amount}'); + return ( + <> + {before} + + {formatUsd(promoCredits)} + + {after} + > + ); + })()}{' '} + {' '} + {t('home.banners.promoCreditsUsage')} +