Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions frontend/src/i18n/__tests__/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ describe('i18n configuration', () => {
expect(i18n.t(key, { lng: 'en' })).toBe(expected);
});

it.each([
[0, '1 · 0 agents', '1 member, 0 agents'],
[1, '1 · 1 agent', '1 member, 1 agent'],
[2, '1 · 2 agents', '1 member, 2 agents'],
])('pluralizes the compact pod header at %i agents', (count, visible, accessible) => {
const members = i18n.t('podChat.header.members', { lng: 'en', count: 1 });
const agents = i18n.t('podChat.header.agents', { lng: 'en', count });
expect(i18n.t('podChat.header.compactMeta', { lng: 'en', members: 1, agents })).toBe(visible);
expect(i18n.t('podChat.header.compactMetaAccessible', { lng: 'en', members, agents })).toBe(accessible);
});

it('keeps the Chinese compact count while naming it for assistive tech', () => {
const members = i18n.t('podChat.header.members', { lng: 'zh-CN', count: 1 });
const agents = i18n.t('podChat.header.agents', { lng: 'zh-CN', count: 1 });
expect(i18n.t('podChat.header.compactMeta', { lng: 'zh-CN', members: 1, agents })).toBe('1 · 1个智能体');
expect(i18n.t('podChat.header.compactMetaAccessible', { lng: 'zh-CN', members, agents })).toBe('1名成员,1个智能体');
});

it('defaults to English when no stored supported language exists', async () => {
const languages = jest.spyOn(window.navigator, 'languages', 'get').mockReturnValue(['en-US']);
const language = jest.spyOn(window.navigator, 'language', 'get').mockReturnValue('en-US');
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,8 @@
"agents_other": "{{count}} agents",
"boardOpen": "board {{count}} open",
"backToPods": "Back to pods",
"compactMeta": "{{members}} · {{count}} agents"
"compactMeta": "{{members}} · {{agents}}",
"compactMetaAccessible": "{{members}}, {{agents}}"
},
"team": {
"view": "View pod team",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,8 @@
"agents_other": "{{count}}个智能体",
"boardOpen": "看板{{count}}项待办",
"backToPods": "返回 Pod 列表",
"compactMeta": "{{members}} · {{count}}个智能体"
"compactMeta": "{{members}} · {{agents}}",
"compactMetaAccessible": "{{members}},{{agents}}"
},
"team": {
"view": "查看 Pod 团队",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/v2/__tests__/V2Composer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ describe('V2Composer send button', () => {
const meta = header.querySelector('.v2-pod-header__meta:not(.v2-pod-header__meta--compact)');
expect(meta).toHaveTextContent('2 members');
expect(meta).toHaveTextContent('1 agent');
expect(header.querySelector('.v2-pod-header__meta--compact')).toHaveTextContent('2 · 1 agents');
expect(header.querySelector('.v2-pod-header__meta--compact')).toHaveTextContent('2 · 1 agent');
expect(view.getByRole('group', { name: '2 members, 1 agent' })).toBeInTheDocument();
expect(view.container.querySelector('.v2-thread__working')).toBeNull();
});

Expand Down
11 changes: 11 additions & 0 deletions frontend/src/v2/__tests__/V2ThreadStarter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ describe('V2Thread teaching empty states', () => {
expect(document.querySelector('.v2-thread__empty-line')).toHaveTextContent('no messages yet');
});

test('compact header shows singular agents and names both counts for assistive tech', () => {
const detail = makeDetail({
agents: [{ agentName: 'openclaw', instanceId: 'aria', displayName: 'Aria' }],
});
renderChat(detail);

const compact = screen.getByRole('group', { name: '1 member, 1 agent' });
expect(compact).toHaveTextContent('1 · 1 agent');
expect(compact.querySelector('[aria-hidden="true"]')).toHaveTextContent('1 · 1 agent');
});

test('multi-member regular pods use the same teaching state', () => {
renderChat(makeDetail({
members: [
Expand Down
14 changes: 10 additions & 4 deletions frontend/src/v2/components/V2Thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1564,9 +1564,11 @@ const V2Thread: React.FC<V2ThreadProps> = ({ detail, firstRunVisible = false, in
// Header meta (direction C): members · agents · board N open · bound
// channels. The agents-working count left the header for the inspector.
const humanMemberCount = (members || []).filter((member) => !member?.isBot).length;
const memberCountText = t('podChat.header.members', { count: humanMemberCount });
const agentCountText = t('podChat.header.agents', { count: agents.length });
const metaParts: React.ReactNode[] = [
<span key="members">{t('podChat.header.members', { count: humanMemberCount })}</span>,
<span key="agents">{t('podChat.header.agents', { count: agents.length })}</span>,
<span key="members">{memberCountText}</span>,
<span key="agents">{agentCountText}</span>,
];
if (headerMeta.boardOpen !== null) {
metaParts.push(
Expand All @@ -1591,8 +1593,12 @@ const V2Thread: React.FC<V2ThreadProps> = ({ detail, firstRunVisible = false, in
{pod.description && <p>{pod.description}</p>}
</div>
</div>
<span className="v2-pod-header__meta v2-pod-header__meta--compact" aria-hidden="true">
{t('podChat.header.compactMeta', { members: humanMemberCount, count: agents.length })}
<span
className="v2-pod-header__meta v2-pod-header__meta--compact"
role="group"
aria-label={t('podChat.header.compactMetaAccessible', { members: memberCountText, agents: agentCountText })}
>
<span aria-hidden="true">{t('podChat.header.compactMeta', { members: humanMemberCount, agents: agentCountText })}</span>
</span>
<span className="v2-pod-header__meta">
{metaParts.map((part, index) => (
Expand Down
Loading