mirror of
https://github.com/ValueCell-ai/ClawX.git
synced 2026-08-14 00:48:10 +00:00
fix: hide empty new chats from sidebar until first message (#1162)
This commit is contained in:
@@ -36,6 +36,7 @@ import { useChatStore } from '@/stores/chat';
|
||||
import { useGatewayStore } from '@/stores/gateway';
|
||||
import { useAgentsStore } from '@/stores/agents';
|
||||
import { groupSessionsByWorkspace } from './session-buckets';
|
||||
import { shouldIncludeSessionInSidebarList } from '@/stores/chat/session-key-utils';
|
||||
import { CHANNEL_NAMES } from '@shared/types/channel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -320,8 +321,12 @@ export function Sidebar() {
|
||||
() => Object.fromEntries((agents ?? []).map((agent) => [agent.id, agent.name])),
|
||||
[agents],
|
||||
);
|
||||
const sidebarSessions = useMemo(
|
||||
() => sessions.filter((session) => shouldIncludeSessionInSidebarList(session)),
|
||||
[sessions],
|
||||
);
|
||||
const workspaceSessionGroups = groupSessionsByWorkspace(
|
||||
sessions,
|
||||
sidebarSessions,
|
||||
sessionLastActivity,
|
||||
t('chat:workspace.defaultLabel'),
|
||||
chatWorkspacePath,
|
||||
@@ -476,7 +481,7 @@ export function Sidebar() {
|
||||
</nav>
|
||||
|
||||
{/* Session list — below Settings, only when expanded */}
|
||||
{!sidebarCollapsed && sessions.length > 0 && (
|
||||
{!sidebarCollapsed && sidebarSessions.length > 0 && (
|
||||
<div className="mt-4 flex-1 overflow-y-auto overflow-x-hidden px-2 pb-2">
|
||||
<div className="mb-1 flex items-center justify-between gap-2 pl-2.5">
|
||||
<span className="text-tiny font-semibold uppercase tracking-[0.08em] text-muted-foreground/70">
|
||||
|
||||
@@ -71,6 +71,9 @@ export function findHiddenOpenClawHeartbeatSession(sessionKey: string, sessions:
|
||||
|
||||
export function shouldIncludeSessionInSidebarList(session: ChatSession): boolean {
|
||||
if (!session.key) return false;
|
||||
// Hide renderer-local placeholders created by New Chat until the first message
|
||||
// creates the backing ACP session (acknowledgeAcpSessionCreated clears the flag).
|
||||
if (session.createdLocally) return false;
|
||||
if (isOpenClawHeartbeatOnlySession(session)) return false;
|
||||
if (isChannelSessionKey(session.key)) {
|
||||
return !isPlaceholderChannelSession(session);
|
||||
|
||||
@@ -119,7 +119,7 @@ test.describe('ClawX chat workspace session list', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('new chat appears in the default workspace group', async ({ launchElectronApp }) => {
|
||||
test('new chat stays hidden in the sidebar until the first message', async ({ launchElectronApp }) => {
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
const oldTimestampMs = Date.now() - 35 * 24 * 60 * 60 * 1000;
|
||||
const seededHistory = [
|
||||
@@ -189,8 +189,9 @@ test.describe('ClawX chat workspace session list', () => {
|
||||
|
||||
await page.getByTestId('sidebar-new-chat').click();
|
||||
|
||||
await expect(page.getByTestId(defaultWorkspaceSessionGroupTestId()).getByText(/agent:main:session-/)).toBeVisible();
|
||||
await expect(page.getByTestId(defaultWorkspaceSessionGroupTestId()).getByText(/agent:main:session-/)).toHaveCount(0);
|
||||
await expect(page.getByTestId(defaultWorkspaceSessionGroupToggleTestId())).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(page.getByTestId('acp-chat-empty-state')).toBeVisible();
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ test.describe('ClawX chat workspace context', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('new unbound chat appears under the selected global workspace group from an ACP-only page', async ({ launchElectronApp }) => {
|
||||
test('new unbound chat stays hidden until it has content and then appears under the selected global workspace group', async ({ launchElectronApp }) => {
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
|
||||
try {
|
||||
@@ -282,9 +282,11 @@ test.describe('ClawX chat workspace context', () => {
|
||||
|
||||
await expect(async () => {
|
||||
await page.getByTestId('sidebar-new-chat').click();
|
||||
await expect(globalWorkspaceGroup.getByText(/agent:main:session-/)).toBeVisible({ timeout: 500 });
|
||||
await expect(globalWorkspaceGroup.getByText(/agent:main:session-/)).toHaveCount(0, { timeout: 500 });
|
||||
}).toPass({ timeout: 30_000 });
|
||||
|
||||
await expect(page.getByTestId('acp-chat-empty-state')).toBeVisible();
|
||||
|
||||
await expect(workspaceSelector).toHaveText(GLOBAL_WORKSPACE_LABEL);
|
||||
await expect(workspaceSelector).toHaveAttribute('title', GLOBAL_WORKSPACE);
|
||||
await expect(workspaceSelector).not.toHaveAttribute('aria-disabled', 'true');
|
||||
|
||||
@@ -36,6 +36,38 @@ describe('session-key-utils', () => {
|
||||
expect(shouldIncludeSessionInSidebarList(placeholder)).toBe(false);
|
||||
});
|
||||
|
||||
it('hides locally-created desktop sessions until the first message', () => {
|
||||
const pending: ChatSession = {
|
||||
key: 'agent:main:session-1710000000000',
|
||||
displayName: 'agent:main:session-1710000000000',
|
||||
createdLocally: true,
|
||||
};
|
||||
|
||||
expect(shouldIncludeSessionInSidebarList(pending)).toBe(false);
|
||||
|
||||
const acknowledged: ChatSession = {
|
||||
...pending,
|
||||
createdLocally: false,
|
||||
};
|
||||
|
||||
expect(shouldIncludeSessionInSidebarList(acknowledged)).toBe(true);
|
||||
});
|
||||
|
||||
it('hides locally-created New Chat placeholders until the first message', () => {
|
||||
const pending: ChatSession = {
|
||||
key: 'agent:main:session-1710000000000',
|
||||
displayName: 'agent:main:session-1710000000000',
|
||||
createdLocally: true,
|
||||
};
|
||||
expect(shouldIncludeSessionInSidebarList(pending)).toBe(false);
|
||||
|
||||
const acknowledged: ChatSession = {
|
||||
...pending,
|
||||
createdLocally: false,
|
||||
};
|
||||
expect(shouldIncludeSessionInSidebarList(acknowledged)).toBe(true);
|
||||
});
|
||||
|
||||
it('includes channel sessions once they have a message preview', () => {
|
||||
const active: ChatSession = {
|
||||
key: 'agent:main:feishu:ou_abc123',
|
||||
|
||||
@@ -70,6 +70,42 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('sidebar session helpers', () => {
|
||||
it('hides locally-created empty sessions until they have content', () => {
|
||||
const pendingKey = 'agent:main:session-pending';
|
||||
seedSidebarState();
|
||||
useChatStore.setState({
|
||||
sessions: [
|
||||
{ key: pendingKey, displayName: pendingKey, createdLocally: true },
|
||||
{ key: sidebarSessionKey, displayName: 'Existing chat', updatedAt: 1 },
|
||||
],
|
||||
currentSessionKey: pendingKey,
|
||||
sessionLastActivity: { [sidebarSessionKey]: 1 },
|
||||
});
|
||||
|
||||
renderSidebar();
|
||||
|
||||
expect(screen.queryByTestId(`sidebar-session-${pendingKey}`)).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId(`sidebar-session-${sidebarSessionKey}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides locally-created empty sessions until they have content', () => {
|
||||
const pendingKey = 'agent:main:session-pending';
|
||||
seedSidebarState();
|
||||
useChatStore.setState({
|
||||
sessions: [
|
||||
{ key: sidebarSessionKey, displayName: 'Existing chat', updatedAt: 1 },
|
||||
{ key: pendingKey, displayName: pendingKey, createdLocally: true, updatedAt: 2 },
|
||||
],
|
||||
currentSessionKey: pendingKey,
|
||||
sessionLastActivity: { [sidebarSessionKey]: 1 },
|
||||
});
|
||||
|
||||
renderSidebar();
|
||||
|
||||
expect(screen.getByTestId(`sidebar-session-${sidebarSessionKey}`)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(`sidebar-session-${pendingKey}`)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('marks the current chat session button as the current page', () => {
|
||||
seedSidebarState();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user