Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5e90dbb
rename Explorer tab to Skills across all locales
senamakel Jun 12, 2026
0f97cc3
rename Tools tab to MCP Servers across all locales
senamakel Jun 12, 2026
9343db4
rename Talents tab to Meetings across all locales
senamakel Jun 12, 2026
c2fe9b6
rename Apps tab to Composio across all locales
senamakel Jun 12, 2026
1bf367b
rename Messaging tab to Channels across all locales
senamakel Jun 12, 2026
8cc0be8
make chat a regular pill tab instead of a raised FAB
senamakel Jun 12, 2026
08d5009
rename Assistant tab to Chat and hide Talk to Tiny button
senamakel Jun 12, 2026
43aa67c
remove threads header bar from chat sidebar
senamakel Jun 12, 2026
2379107
remove Send OpenHuman to a meeting button from Human page
senamakel Jun 12, 2026
05ae22b
inline Meetings tab content — remove banner→modal indirection
senamakel Jun 12, 2026
424526b
reset scroll to top on forward navigation
senamakel Jun 12, 2026
a745141
remove brain page loading overlay
senamakel Jun 12, 2026
ffcbbac
fit graph view to source and summary nodes
senamakel Jun 12, 2026
1101083
center graph on source nodes at a fixed default zoom
senamakel Jun 12, 2026
304e2c9
add master root node to memory graph with source repulsion
senamakel Jun 12, 2026
4cd9ae6
apply uniform repulsion to all graph nodes
senamakel Jun 12, 2026
f319a0c
fix Pixi reset view to center on root at fixed zoom
senamakel Jun 12, 2026
976cd6e
zoom out default graph view to 0.4x
senamakel Jun 12, 2026
d870fd0
give root/source nodes stronger repulsion and collision radius
senamakel Jun 12, 2026
7221d0e
add card background to MCP Servers tab
senamakel Jun 12, 2026
215302d
fix graph SVG colors for light mode
senamakel Jun 12, 2026
0d5eed4
make graph edges more visible in light mode
senamakel Jun 12, 2026
68a876e
thicken graph edges in light mode to 1.2px
senamakel Jun 12, 2026
136ae08
reduce padding, font size and border on thread list rows
senamakel Jun 12, 2026
26a4a51
use ocean primary color for root graph node
senamakel Jun 12, 2026
a9f6b14
use coral red for root graph node
senamakel Jun 12, 2026
203aa1e
zoom out default graph view to ~6x
senamakel Jun 12, 2026
40f3fa6
add reusable BetaBanner component with i18n
senamakel Jun 12, 2026
b6234ea
split Brain page into Memory and Subconscious tabs
senamakel Jun 12, 2026
8acb1fc
remove Activity nav tab, move Automations into Settings
senamakel Jun 12, 2026
11ed3c3
replace cycle pill with token stats line below composer
senamakel Jun 12, 2026
ab02907
flatten composer to single row with higher max height
senamakel Jun 12, 2026
26079db
normalize composer spacing and token stats padding
senamakel Jun 12, 2026
3a55274
even out composer spacing and vertical alignment
senamakel Jun 12, 2026
d222336
add context window usage to composer stats and fix vertical alignment
senamakel Jun 12, 2026
900dc3f
guard against NaN in token stats display and reducer
senamakel Jun 12, 2026
76c7752
update padding
senamakel Jun 12, 2026
1391205
change root graph node color to purple
senamakel Jun 12, 2026
1d63875
remove Meetings thread filter, fold meeting threads into Tasks
senamakel Jun 12, 2026
ccf42cd
chore: apply prettier auto-fixes
senamakel Jun 12, 2026
66e91fc
fix(i18n): add missing Bengali token stats translations
senamakel Jun 12, 2026
ef5709b
fix: address CodeRabbit review feedback
senamakel Jun 12, 2026
8e38c0e
fix(e2e): update navigation spec for /activity → /settings/notificati…
senamakel Jun 12, 2026
5ee7bd4
fix(e2e): update all Playwright specs for reverted tab names and /act…
senamakel Jun 12, 2026
5d5dcf8
fix(e2e): remove stale rename comments in composio/gmail specs
senamakel Jun 12, 2026
53b9ee5
fix(test): update unit tests for graph root hub and composer flattening
senamakel Jun 12, 2026
b7a75c8
style: apply prettier formatting to MemoryGraph test files
senamakel Jun 12, 2026
20130b0
fix(e2e): update gmeet-connections-tab for inline join form
senamakel Jun 12, 2026
85d1d14
fix(test): update unit tests for reverted tab names and Brain mocks
senamakel Jun 12, 2026
98f5f16
style: apply prettier formatting to Brain and Skills test files
senamakel Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import * as Sentry from '@sentry/react';
import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { Provider } from 'react-redux';
import { HashRouter as Router, useLocation, useNavigate } from 'react-router-dom';
import {
HashRouter as Router,
useLocation,
useNavigate,
useNavigationType,
} from 'react-router-dom';
import { PersistGate } from 'redux-persist/integration/react';

import AppRoutes from './AppRoutes';
Expand Down Expand Up @@ -200,11 +205,22 @@ function AppShellDesktop() {
// the core is ready (once per boot). Extracted to a hook so it's testable.
useNotchBootSync(isBootstrapping);

const scrollRef = useRef<HTMLDivElement>(null);
const navType = useNavigationType();

useEffect(() => {
if (navType !== 'POP') {
scrollRef.current?.scrollTo(0, 0);
}
}, [location.pathname, navType]);

return (
<div className="relative h-screen flex flex-col overflow-hidden">
<AppBackground />
<div className="relative z-10 flex-1 flex flex-col overflow-hidden">
<div className={`flex-1 overflow-y-auto ${fullscreen || onOnboardingRoute ? '' : 'pb-16'}`}>
<div
ref={scrollRef}
className={`flex-1 overflow-y-auto ${fullscreen || onOnboardingRoute ? '' : 'pb-16'}`}>
<GlobalUpsellBanner />
<AppRoutes />
</div>
Expand Down
24 changes: 5 additions & 19 deletions app/src/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import PublicRoute from './components/PublicRoute';
import HumanPage from './features/human/HumanPage';
import { getIsMobile } from './lib/platform';
import Accounts from './pages/Accounts';
import Activity from './pages/Activity';
import Brain from './pages/Brain';
import AgentInsightsPreview from './pages/dev/AgentInsightsPreview';
import Home from './pages/Home';
Expand Down Expand Up @@ -88,20 +87,9 @@ const AppRoutes = () => {
}
/>

{/* Primary Activity surface — replaces /intelligence (Phase 3). */}
<Route
path="/activity"
element={
<ProtectedRoute requireAuth={true}>
<Activity />
</ProtectedRoute>
}
/>

{/* Back-compat: /intelligence → /activity (preserves ?tab= deep links).
Deep links such as ?tab=memory or ?tab=agents still resolve but fall
back to the tasks tab in prod (dev-only tabs are gated inside Activity). */}
<Route path="/intelligence" element={<Navigate to="/activity" replace />} />
{/* Back-compat: /activity and /intelligence → settings notifications hub. */}
<Route path="/activity" element={<Navigate to="/settings/notifications-hub" replace />} />
<Route path="/intelligence" element={<Navigate to="/settings/notifications-hub" replace />} />

{/* Connections page lives at /connections (Phase 2 rename from /skills).
The old /skills path is kept as a back-compat redirect so bookmarks
Expand Down Expand Up @@ -177,7 +165,7 @@ const AppRoutes = () => {
{/* Back-compat: /routines was an orphaned dead page (superseded by the
Cron Jobs settings panel). Redirect to Activity → Automations so
any surviving deep links land somewhere sensible. */}
<Route path="/routines" element={<Navigate to="/activity?tab=automations" replace />} />
<Route path="/routines" element={<Navigate to="/settings/automations" replace />} />

<Route
path="/rewards"
Expand All @@ -188,9 +176,7 @@ const AppRoutes = () => {
}
/>

{/* Workflows moved onto the Activity page (Automations tab). Keep the
old /workflows path working as a deep link into that tab. */}
<Route path="/workflows" element={<Navigate to="/activity?tab=automations" replace />} />
<Route path="/workflows" element={<Navigate to="/settings/automations" replace />} />

<Route path="/webhooks" element={<Navigate to="/settings/webhooks-triggers" replace />} />

Expand Down
54 changes: 7 additions & 47 deletions app/src/components/BottomTabBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

import { AVATAR_MENU_ITEMS, CENTER_TAB, NAV_TABS } from '../config/navConfig';
import { AVATAR_MENU_ITEMS, NAV_TABS } from '../config/navConfig';
import { useT } from '../lib/i18n/I18nContext';
import { useCoreState } from '../providers/CoreStateProvider';
import { trackEvent } from '../services/analytics';
Expand All @@ -16,10 +16,8 @@ import { resolveUserName } from '../utils/userName';

// ── SVG icons, keyed by tab id ────────────────────────────────────────────────

function TabIcon({ id, large = false }: { id: string; large?: boolean }) {
// Regular pill tabs render small (w-4); the raised center FAB renders large
// (w-6) so its glyph reads as the centerpiece.
const cls = large ? 'w-6 h-6' : 'w-4 h-4';
function TabIcon({ id }: { id: string }) {
const cls = 'w-4 h-4';
switch (id) {
case 'home':
return (
Expand Down Expand Up @@ -95,8 +93,7 @@ function TabIcon({ id, large = false }: { id: string; large?: boolean }) {
</svg>
);
case 'brain':
// Two symmetric lobes — reads clearly as a brain. Rendered larger and
// white inside the raised center circle.
// Two symmetric lobes — reads clearly as a brain.
return (
<svg className={cls} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
Expand Down Expand Up @@ -282,43 +279,8 @@ const BottomTabBar = () => {
);
};

// The Assistant — a raised circular button rising out of the center of the
// bar. The bg-colored ring fakes a notch cut into the pill's top edge.
// `center-fab` marks the button (test/identification hook); it renders a
// static glow when active — no pulse.
//
// `-my-3` collapses the button's 48px (h-12) layout footprint so it no longer
// forces the nav row taller than the ~32px pill tabs — the bar height is
// driven by the tabs, while `-translate-y-4` still lifts the circle above the
// top edge. Without it the lower half of the raised circle left a dead band
// of empty bar height beneath the tabs.
const renderCenterButton = () => {
const active = isActive(CENTER_TAB.path);
const centerTab = { ...CENTER_TAB, label: t(CENTER_TAB.labelKey) };
return (
<button
key={CENTER_TAB.id}
type="button"
data-walkthrough={CENTER_TAB.walkthroughAttr}
onClick={() => handleTabClick(centerTab, active)}
aria-label={centerTab.label}
title={centerTab.label}
className={`center-fab group relative mx-1 flex h-12 w-12 -my-3 -translate-y-4 items-center justify-center rounded-full text-white shadow-soft ring-4 ring-stone-200 transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] cursor-pointer dark:ring-neutral-900 ${
active
? 'bg-primary-600 shadow-[0_0_16px_rgba(74,131,221,0.55)] scale-105'
: 'bg-primary-500 hover:bg-primary-600 hover:scale-105'
}`}>
<TabIcon id={CENTER_TAB.id} large />
</button>
);
};

// Home is a normal pill tab now (no longer pinned/icon-only). The regular
// tabs split evenly around the centered Assistant button; only the avatar
// stays pinned to the far-right behind a divider:
// | home · human · brain ( 💬 ) connections · activity · settings | [ avatar ]
const leftTabs = tabs.slice(0, 3);
const rightTabs = tabs.slice(3);
// All tabs render as uniform pill buttons in a single row; the avatar
// stays pinned to the far-right behind a divider.

return (
// pointer-events-none on the full-width shell so transparent areas (e.g.
Expand All @@ -344,9 +306,7 @@ const BottomTabBar = () => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) setRevealed(false);
}}>
<nav className="pointer-events-auto inline-flex items-center gap-1 rounded-sm border border-stone-300 dark:border-neutral-700 bg-stone-200 dark:bg-neutral-900 shadow-soft px-1 py-1">
{leftTabs.map(tab => renderTab(tab))}
{renderCenterButton()}
{rightTabs.map(tab => renderTab(tab))}
{tabs.map(tab => renderTab(tab))}
<div
className="relative ml-1 border-l border-stone-300 pl-1 dark:border-neutral-700"
ref={profileMenuRef}>
Expand Down
42 changes: 13 additions & 29 deletions app/src/components/__tests__/BottomTabBar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
/**
* Tests for BottomTabBar — verifies that:
* - 6 tabs are rendered (no Rewards tab; Human restored), Activity label is present
* - Assistant tab is present (was "Chat", id stays 'chat', label now 'Assistant')
* - Walkthrough attributes reflect the new ids (tab-connections, tab-activity)
* - 6 tabs are rendered (no Rewards or Activity tab; Human restored; Chat is a regular tab)
* - Chat tab is present (id 'chat', label 'Chat')
* - Walkthrough attributes reflect the new ids (tab-connections)
* - Avatar menu opens and shows Account / Billing / Rewards / Invites / Wallet
* - Clicking an avatar menu item navigates or opens URL
* - The bar is hidden on '/' and '/login' paths
*
* Human tab restored as a first-class entry (after the IA Phase 6 merge into
* Assistant); Chat keeps its "Assistant" label.
*/
import { configureStore } from '@reduxjs/toolkit';
import { fireEvent, render, screen } from '@testing-library/react';
Expand Down Expand Up @@ -167,14 +164,10 @@ describe('BottomTabBar', () => {
agentProfilesApiMock.select.mockResolvedValue(testProfiles);
});

it('renders exactly 6 regular tab buttons (Assistant is rendered separately)', async () => {
it('renders exactly 6 regular tab buttons (Chat is a regular tab)', async () => {
await renderBottomTabBar('/home');
// Query only the regular pill tabs inside <nav>: exclude the avatar button
// (aria-haspopup) and the special raised Assistant center button (tab-chat).
const nav = document.querySelector('nav');
const navButtons = nav?.querySelectorAll(
'button:not([aria-haspopup]):not([data-walkthrough="tab-chat"])'
);
const navButtons = nav?.querySelectorAll('button:not([aria-haspopup])');
expect(navButtons).toHaveLength(6);
});

Expand All @@ -188,19 +181,18 @@ describe('BottomTabBar', () => {
expect(humanBtn.querySelector('.truncate')).not.toBeNull();
});

it('renders the raised Assistant center button with data-walkthrough="tab-chat"', async () => {
it('renders the Chat tab with data-walkthrough="tab-chat"', async () => {
await renderBottomTabBar('/home');
const assistantBtn = screen.getByRole('button', { name: 'Assistant' });
expect(assistantBtn).toBeInTheDocument();
expect(assistantBtn).toHaveAttribute('data-walkthrough', 'tab-chat');
expect(assistantBtn).toHaveClass('center-fab');
const chatBtn = screen.getByRole('button', { name: 'Chat' });
expect(chatBtn).toBeInTheDocument();
expect(chatBtn).toHaveAttribute('data-walkthrough', 'tab-chat');
});

it('navigates to /chat and tracks the change when the Assistant center button is clicked', async () => {
it('navigates to /chat and tracks the change when the Chat tab is clicked', async () => {
const { trackEvent } = await import('../../services/analytics');
await renderBottomTabBar('/home');

fireEvent.click(screen.getByRole('button', { name: 'Assistant' }));
fireEvent.click(screen.getByRole('button', { name: 'Chat' }));

expect(trackEvent).toHaveBeenCalledWith('tab_bar_change', {
from_tab: 'home',
Expand Down Expand Up @@ -236,18 +228,16 @@ describe('BottomTabBar', () => {
});
});

it('renders the Activity tab', async () => {
it('does NOT render an Activity tab', async () => {
await renderBottomTabBar('/home');
expect(screen.getByRole('button', { name: 'Activity' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Activity' })).toBeNull();
});

it('renders the Brain tab in the regular row with data-walkthrough="tab-brain"', async () => {
await renderBottomTabBar('/home');
const brainBtn = screen.getByRole('button', { name: 'Brain' });
expect(brainBtn).toBeInTheDocument();
expect(brainBtn).toHaveAttribute('data-walkthrough', 'tab-brain');
// It's a regular pill tab now, not the raised center FAB.
expect(brainBtn).not.toHaveClass('center-fab');
});

it('renders the Connections tab with data-walkthrough="tab-connections"', async () => {
Expand All @@ -257,12 +247,6 @@ describe('BottomTabBar', () => {
expect(connectionsBtn).toHaveAttribute('data-walkthrough', 'tab-connections');
});

it('renders Activity tab with data-walkthrough="tab-activity"', async () => {
await renderBottomTabBar('/home');
const activityBtn = screen.getByRole('button', { name: 'Activity' });
expect(activityBtn).toHaveAttribute('data-walkthrough', 'tab-activity');
});

it('renders Settings tab with data-walkthrough="tab-settings"', async () => {
await renderBottomTabBar('/home');
const settingsBtn = screen.getByRole('button', { name: 'Settings' });
Expand Down
Loading
Loading