fix: hide empty new chats from sidebar until first message (#1162)

This commit is contained in:
paisley
2026-07-14 18:17:32 +08:00
committed by GitHub
parent 3d134a563a
commit b598e2482d
6 changed files with 85 additions and 6 deletions
+7 -2
View File
@@ -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">
+3
View File
@@ -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);
+3 -2
View File
@@ -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);
}
+4 -2
View File
@@ -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');
+32
View File
@@ -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();