From c07f0963a83f1037f45c0a4a4e7e2687ae8ff827 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 6 Oct 2026 18:49:19 -0700 Subject: [PATCH 1/2] fix(v2): size pod list marks for phones --- .../v2/__tests__/V2PodsSidebar.scale.test.tsx | 29 ++++++++++++++++++ .../v2/__tests__/v2-layout-invariants.test.ts | 26 ++++++++++++++++ frontend/src/v2/components/V2PodsSidebar.tsx | 2 ++ frontend/src/v2/v2.css | 30 +++++++++++++++++++ 4 files changed, 87 insertions(+) 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..b4257cf54 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 the phone targets at 640px and below', () => { + const phone = blockContaining(v2, '@media (max-width: 640px)', '.v2-root button.v2-pods__row'); + const row = ruleBody(v2, '.v2-root button.v2-pods__row', { within: phone }); + const mark = ruleBody(v2, '.v2-pods__row-mark', { within: phone }); + const avatar = ruleBody(v2, '.v2-pods__row-mark.v2-avatar', { within: phone }); + const name = ruleBody(v2, '.v2-pods__row-name', { within: phone }); + const pin = ruleBody(v2, '.v2-root button.v2-pods__pin', { within: phone }); + const meta = ruleBody(v2, '.v2-pods__rowwrap--pinned .v2-pods__row-meta', { within: phone }); + + expect(phone).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..1e8a929b1 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,35 @@ body.modern-ui.v2-canvas { font: 700 11px/1 var(--v2-font-mono); } +@media (max-width: 640px) { + .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); From bbfb5bf49d8ea20195383aca39ec65e0aec77a8d Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:00:53 -0700 Subject: [PATCH 2/2] fix(v2): align pod list targets with mobile breakpoint --- .../v2/__tests__/v2-layout-invariants.test.ts | 20 +++++++++---------- frontend/src/v2/v2.css | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index b4257cf54..28be0650f 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -290,16 +290,16 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(v2).not.toContain('.v2-pods__channel-dot'); }); - test('pod list rows and marks reach the phone targets at 640px and below', () => { - const phone = blockContaining(v2, '@media (max-width: 640px)', '.v2-root button.v2-pods__row'); - const row = ruleBody(v2, '.v2-root button.v2-pods__row', { within: phone }); - const mark = ruleBody(v2, '.v2-pods__row-mark', { within: phone }); - const avatar = ruleBody(v2, '.v2-pods__row-mark.v2-avatar', { within: phone }); - const name = ruleBody(v2, '.v2-pods__row-name', { within: phone }); - const pin = ruleBody(v2, '.v2-root button.v2-pods__pin', { within: phone }); - const meta = ruleBody(v2, '.v2-pods__rowwrap--pinned .v2-pods__row-meta', { within: phone }); - - expect(phone).not.toBe(''); + 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'); diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 1e8a929b1..8f1a891f0 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -11194,7 +11194,8 @@ body.modern-ui.v2-canvas { font: 700 11px/1 var(--v2-font-mono); } -@media (max-width: 640px) { +/* 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;