diff --git a/frontend/src/i18n/__tests__/i18n.test.ts b/frontend/src/i18n/__tests__/i18n.test.ts index 567da518ba..323dd697d5 100644 --- a/frontend/src/i18n/__tests__/i18n.test.ts +++ b/frontend/src/i18n/__tests__/i18n.test.ts @@ -39,22 +39,10 @@ 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('pluralizes the member count the compact pod header shows', () => { + expect(i18n.t('podChat.header.members', { lng: 'en', count: 1 })).toBe('1 member'); + expect(i18n.t('podChat.header.members', { lng: 'en', count: 2 })).toBe('2 members'); + expect(i18n.t('podChat.header.members', { lng: 'zh-CN', count: 1 })).toBe('1名成员'); }); it('uses the singular agent count in the Your Team header', () => { diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 3dbd8b52a5..7a21f26eaa 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -648,9 +648,7 @@ "agents_one": "{{count}} agent", "agents_other": "{{count}} agents", "boardOpen": "board {{count}} open", - "backToPods": "Back to pods", - "compactMeta": "{{members}} · {{agents}}", - "compactMetaAccessible": "{{members}}, {{agents}}" + "backToPods": "Back to pods" }, "team": { "view": "View pod team", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 4755445bb2..930ec558fa 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -647,9 +647,7 @@ "agents_one": "{{count}}个智能体", "agents_other": "{{count}}个智能体", "boardOpen": "看板{{count}}项待办", - "backToPods": "返回 Pod 列表", - "compactMeta": "{{members}} · {{agents}}", - "compactMetaAccessible": "{{members}},{{agents}}" + "backToPods": "返回 Pod 列表" }, "team": { "view": "查看 Pod 团队", diff --git a/frontend/src/v2/__tests__/V2AgentBYOHostedOffer.test.tsx b/frontend/src/v2/__tests__/V2AgentBYOHostedOffer.test.tsx index e1f8fb243c..ca87d134bc 100644 --- a/frontend/src/v2/__tests__/V2AgentBYOHostedOffer.test.tsx +++ b/frontend/src/v2/__tests__/V2AgentBYOHostedOffer.test.tsx @@ -207,7 +207,7 @@ test('"Run it here" exposure is counted on first paint, once, and a pick counts fireEvent.click(screen.getByTestId('byo-mode-hosted')); expect(offerEvents()).toEqual([{ step: 'hosted-offer-shown' }, { step: 'hosted-offer-clicked' }]); }); -test('the compact hosted form counts its submit as a click, and the compact BYO form counts no offer', async () => { +test('the compact hosted form counts its submit as a click', async () => { window.history.pushState({}, '', '/v2/agents/byo?mode=hosted'); renderPage(); await waitFor(() => expect(document.querySelector('[data-testid="byo-hosted-compact"] .v2-byo__submit')).not.toBeNull()); @@ -216,3 +216,15 @@ test('the compact hosted form counts its submit as a click, and the compact BYO fireEvent.click(document.querySelector('[data-testid="byo-hosted-compact"] .v2-byo__submit')); expect(offerEvents()).toEqual([{ step: 'hosted-offer-shown' }, { step: 'hosted-offer-clicked' }]); }); +test('the compact BYO form counts no offer, and Other ways counts the exposure once the form is left', async () => { + window.history.pushState({}, '', '/v2/agents/byo?from=card&name=sam-claude'); + renderPage(); + await waitFor(() => expect(screen.getByTestId('byo-card-other-ways')).toBeInTheDocument()); + // The instance's hosting answer must land before the no-offer check means anything. + await waitFor(() => expect(axios.get).toHaveBeenCalledWith(expect.stringContaining('/api/hosted/availability'), expect.anything())); + for (let i = 0; i < 5; i += 1) await act(async () => {}); + expect(offerEvents()).toEqual([]); + fireEvent.click(screen.getByTestId('byo-card-other-ways')); + await waitFor(() => expect(offerEvents()).toEqual([{ step: 'hosted-offer-shown' }])); + window.history.pushState({}, '', '/'); +}); diff --git a/frontend/src/v2/__tests__/V2Composer.test.tsx b/frontend/src/v2/__tests__/V2Composer.test.tsx index d728f06ad2..74220a3e28 100644 --- a/frontend/src/v2/__tests__/V2Composer.test.tsx +++ b/frontend/src/v2/__tests__/V2Composer.test.tsx @@ -99,8 +99,7 @@ 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 agent'); - expect(view.getByRole('group', { name: '2 members, 1 agent' })).toBeInTheDocument(); + expect(header.querySelector('.v2-pod-header__meta--compact')).toHaveTextContent('2 members'); expect(view.container.querySelector('.v2-thread__working')).toBeNull(); }); diff --git a/frontend/src/v2/__tests__/V2ThreadStarter.test.tsx b/frontend/src/v2/__tests__/V2ThreadStarter.test.tsx index 3042133474..43e6c69926 100644 --- a/frontend/src/v2/__tests__/V2ThreadStarter.test.tsx +++ b/frontend/src/v2/__tests__/V2ThreadStarter.test.tsx @@ -117,15 +117,16 @@ describe('V2Thread teaching empty states', () => { expect(document.querySelector('.v2-teammate')).toBeNull(); }); - test('compact header shows singular agents and names both counts for assistive tech', () => { + test('compact header shows the member count; the agent count stays in the desktop meta', () => { 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'); + const compact = document.querySelector('.v2-pod-header__meta--compact'); + expect(compact).toHaveTextContent('1 member'); + expect(compact).not.toHaveTextContent('agent'); + expect(document.querySelector('.v2-pod-header__meta:not(.v2-pod-header__meta--compact)')).toHaveTextContent('1 agent'); }); test('multi-member regular pods use the same teaching state', () => { diff --git a/frontend/src/v2/__tests__/v2-copy-budget.test.tsx b/frontend/src/v2/__tests__/v2-copy-budget.test.tsx index 0882d042a7..33d0f12f25 100644 --- a/frontend/src/v2/__tests__/v2-copy-budget.test.tsx +++ b/frontend/src/v2/__tests__/v2-copy-budget.test.tsx @@ -50,7 +50,7 @@ describe('page head copy budget', () => { expect(css).toContain('.v2-thread__title-line { display: flex; flex-wrap: wrap; align-items: center; }'); expect(css).toContain('.v2-thread__title-line .v2-hint__content { width: 100%; flex-basis: 100%; order: 1; }'); expect(css).toMatch(/\.v2-pod-header \.v2-thread__header-row \{[^}]*padding: 11px 0;[^}]*align-items: flex-start;/); - expect(css).toContain('.v2-pod-header__meta.v2-pod-header__meta--compact { display: flex; min-height: 28px; }'); + expect(css).toContain('.v2-pod-header__meta.v2-pod-header__meta--compact { display: flex; min-height: 28px; flex: 0 0 auto; white-space: nowrap; }'); expect(css).toContain('box-shadow: var(--v2-focus-ring);'); }); }); diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 48bb48d98e..327a8c375f 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -1554,6 +1554,10 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(compactMeta).toBeGreaterThan(-1); expect(ruleBody(v2, '.v2-pod-header__meta')).toContain('display: flex'); expect(compactMeta).toBeGreaterThan(baseMeta); + // TASK-647 item 3: the phone line keeps the board link, with a 44px hit + // area drawn as a pseudo-element so the 28px meta row does not grow. + expect(ruleBody(v2, 'button.v2-pod-header__board--compact::before')).toContain('inset: -14px -10px'); + expect(ruleBody(v2, '.v2-thread__title-line > h1')).toContain('max-width: calc(100% - 36px)'); expect(v2).not.toContain('.v2-thread__working'); }); @@ -2884,7 +2888,7 @@ describe('v2 layout invariants (CSS rule presence)', () => { // row under Scout's message, in the slot the delivery cue and the // no-agent row share, so they land on the message text column too. expect(threadMessages).toContain('className="v2-chat__starts"'); - expect(threadMessages).toContain('className="v2-chat__start"'); + expect(threadMessages).toContain('v2-chat__start${start.action === WELCOME_HOSTED_ACTION ? \' v2-chat__start--primary\' : \'\'}'); // The 44px phone minimum must come AFTER the 32px base in the sheet: at // equal specificity the later rule wins, and a base rule placed after // the media block would silently render 32 on a phone. diff --git a/frontend/src/v2/components/V2AgentBYO.tsx b/frontend/src/v2/components/V2AgentBYO.tsx index 0375afc34d..d7571f97ab 100644 --- a/frontend/src/v2/components/V2AgentBYO.tsx +++ b/frontend/src/v2/components/V2AgentBYO.tsx @@ -1326,8 +1326,9 @@ const V2AgentBYO: React.FC = () => { )} {nameLookupError && mode === 'byo' &&