diff --git a/frontend/src/v2/__tests__/V2PodsSidebar.scale.test.tsx b/frontend/src/v2/__tests__/V2PodsSidebar.scale.test.tsx index a8fc75114..e3c960f41 100644 --- a/frontend/src/v2/__tests__/V2PodsSidebar.scale.test.tsx +++ b/frontend/src/v2/__tests__/V2PodsSidebar.scale.test.tsx @@ -8,6 +8,7 @@ import i18n, { i18nReady } from '../../i18n'; import V2PodsSidebar, { groupPodsByKind, podKind, recentPods, } from '../components/V2PodsSidebar'; +import { characterAvatarFor, paperAvatarFor } from '../utils/avatars'; import { POD_VISITS_KEY } from '../lib/podRecency'; const mockCreatePod = jest.fn(); @@ -162,6 +163,34 @@ describe('V2PodsSidebar — direction C', () => { expect(within(dm).getByText('1d')).toBeInTheDocument(); }); + test('direct rows render the peer character for agents and Paper for unpicked people', () => { + const agentDm = pod('agent-dm', 'Helper Agent', 'agent-dm', [ + human('me'), { ...agent('builder-id'), username: 'Builder' }, + ]); + const agentRoom = pod('agent-room', 'Scout', 'agent-room', [ + human('me'), { ...agent('scout-id'), username: 'Scout' }, + ]); + const humanDm = pod('human-dm', 'Direct chat', 'chat', [ + human('me'), { ...human('sam-id'), username: 'Sam' }, + ]); + renderSidebar([agentDm, agentRoom, humanDm], 'none'); + + const agentRow = screen.getByRole('button', { name: /Helper Agent/ }); + const agentImage = within(agentRow).getByRole('img', { name: 'Builder' }); + const agentSource = characterAvatarFor('builder-id', 'agent'); + expect(agentSource).not.toBeNull(); + expect(agentImage).toHaveAttribute('src', agentSource); + + const agentRoomRow = screen.getByRole('button', { name: /^Scout/ }); + expect(within(agentRoomRow).getByRole('img', { name: 'Scout' })).toHaveAttribute( + 'src', characterAvatarFor('scout-id', 'agent'), + ); + + const humanRow = screen.getByRole('button', { name: /Direct chat/ }); + const humanImage = within(humanRow).getByRole('img', { name: 'Sam' }); + expect(humanImage).toHaveAttribute('src', paperAvatarFor('sam-id')); + }); + test('Recent is ordered by my last visit, not by the last message; the time column stays the last message', () => { localStorage.setItem(POD_VISITS_KEY, JSON.stringify({ files: NOW - 1000, study: NOW - 2000 })); renderSidebar(fleet, null); diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 2d3a50644..28be0650f 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -265,6 +265,9 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(row).toContain('border-radius: 0'); expect(mark).toContain('width: 22px'); expect(mark).toContain('height: 22px'); + const directMark = ruleBody(v2, '.v2-pods__row-mark.v2-avatar'); + expect(directMark).toContain('border: 0'); + expect(directMark).toContain('border-radius: 4px'); expect(meta).toContain('font: 400 11px/16px var(--v2-font-mono)'); expect(pill).toContain('background: var(--v2-ink)'); expect(podsSidebar).toContain("'podsSidebar.workspace.recent'"); @@ -287,6 +290,29 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(v2).not.toContain('.v2-pods__channel-dot'); }); + test('pod list rows and marks reach mobile targets at 760px and below', () => { + const mobile = blockContaining(v2, '@media (max-width: 760px)', '.v2-root button.v2-pods__row'); + const row = ruleBody(v2, '.v2-root button.v2-pods__row', { within: mobile }); + const mark = ruleBody(v2, '.v2-pods__row-mark', { within: mobile }); + const avatar = ruleBody(v2, '.v2-pods__row-mark.v2-avatar', { within: mobile }); + const name = ruleBody(v2, '.v2-pods__row-name', { within: mobile }); + const pin = ruleBody(v2, '.v2-root button.v2-pods__pin', { within: mobile }); + const meta = ruleBody(v2, '.v2-pods__rowwrap--pinned .v2-pods__row-meta', { within: mobile }); + + expect(mobile).not.toBe(''); + expect(row).toContain('min-height: 44px'); + expect(row).toContain('grid-template-columns: 28px minmax(0, 1fr) auto'); + expect(mark).toContain('width: 28px'); + expect(mark).toContain('height: 28px'); + expect(mark).toContain('font-size: 13px'); + expect(avatar).toContain('width: 28px'); + expect(avatar).toContain('height: 28px'); + expect(name).toContain('font-size: 16px'); + expect(pin).toContain('width: 24px'); + expect(pin).toContain('height: 24px'); + expect(meta).toContain('padding-right: 32px'); + }); + test('the selected pod keeps the sidebar’s one cobalt block treatment', () => { const selected = selectorRuleBody(v2, '.v2-root button.v2-pods__row--selected'); expect(selected).toContain('background: var(--v2-accent)'); diff --git a/frontend/src/v2/components/V2PodsSidebar.tsx b/frontend/src/v2/components/V2PodsSidebar.tsx index 8c09bd064..c3e1ac78f 100644 --- a/frontend/src/v2/components/V2PodsSidebar.tsx +++ b/frontend/src/v2/components/V2PodsSidebar.tsx @@ -360,6 +360,8 @@ const V2PodsSidebar: React.FC = ({ name={peer.username || pod.name} src={peer.profilePicture || undefined} size="sm" + kind={peer.isBot ? 'agent' : 'human'} + seed={peer._id} /> ) : ( diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 4a22895b5..8f1a891f0 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -11153,6 +11153,7 @@ body.modern-ui.v2-canvas { width: 22px; height: 22px; border-radius: 4px; + border: 0; font-size: 11px; } @@ -11193,6 +11194,36 @@ body.modern-ui.v2-canvas { font: 700 11px/1 var(--v2-font-mono); } +/* Keep row targets aligned with the sidebar's mobile drawer breakpoint. */ +@media (max-width: 760px) { + .v2-root button.v2-pods__row { + min-height: 44px; + grid-template-columns: 28px minmax(0, 1fr) auto; + } + + .v2-pods__row-mark { + width: 28px; + height: 28px; + font-size: 13px; + } + + .v2-pods__row-mark.v2-avatar { + width: 28px; + height: 28px; + } + + .v2-pods__row-name { font-size: 16px; } + + .v2-root button.v2-pods__pin { + width: 24px; + height: 24px; + } + + .v2-pods__rowwrap:hover .v2-pods__row-meta, + .v2-pods__rowwrap:focus-within .v2-pods__row-meta, + .v2-pods__rowwrap--pinned .v2-pods__row-meta { padding-right: 32px; } +} + .v2-root button.v2-pods__row--selected .v2-pods__row-pill { background: #fff; color: var(--v2-accent);