From 509e5eb772b72a7ca684898cf6f235586708c0b1 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Wed, 4 Nov 2026 04:52:11 -0800 Subject: [PATCH 1/3] fix(v2): Sam's picked surfaces and the pages drawn after them match their frames again, with 44 px touch areas on phone Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MDs9DT6tbhKm6UhsmFhLk9 --- frontend/src/v2/V2App.tsx | 6 +- .../__tests__/ConnectorsPage.layout.test.tsx | 8 +- .../v2/__tests__/v2-layout-invariants.test.ts | 179 +++++++++++------- frontend/src/v2/agents/V2AgentProfile.tsx | 18 +- frontend/src/v2/agents/v2-agent-profile.css | 55 ++---- frontend/src/v2/components/V2AgentBYO.css | 12 +- frontend/src/v2/components/V2AgentBYO.tsx | 16 +- .../src/v2/components/V2PersonProfile.css | 46 ++--- .../src/v2/components/V2PersonProfile.tsx | 80 ++++---- frontend/src/v2/components/V2RunTrace.css | 36 ++-- frontend/src/v2/components/V2RunTrace.tsx | 27 +-- frontend/src/v2/components/V2SettingsPage.css | 28 +++ frontend/src/v2/connectors/ConnectorsPage.tsx | 6 +- frontend/src/v2/connectors/connectors.css | 3 + frontend/src/v2/v2.css | 37 +++- 15 files changed, 298 insertions(+), 259 deletions(-) diff --git a/frontend/src/v2/V2App.tsx b/frontend/src/v2/V2App.tsx index 193dc97b3d..7c7b562902 100644 --- a/frontend/src/v2/V2App.tsx +++ b/frontend/src/v2/V2App.tsx @@ -201,7 +201,7 @@ const V2AgentProfileRoute: React.FC = () => { return ( {feature(t('common.pageTitle.agentProfile'), page, false, false, 'v2-feature--bar v2-feature--aprofile')} + ? (page) => {feature(t('common.pageTitle.agentProfile'), page, false, false)} : undefined} /> ); @@ -386,7 +386,7 @@ const V2App: React.FC = () => { /> , false, false, 'v2-feature--bar v2-feature--run-trace')} + element={feature(t('runTrace.title'), , false, false, 'v2-feature--run-trace')} /> { /> , false, false, 'v2-feature--bar v2-feature--profile')} + element={feature(t('common.pageTitle.profile'), , false, false, 'v2-feature--profile')} /> { await clickTile('GitHub'); const popover = await findPopover('GitHub'); expect(within(popover).getByText('GitHub tool copy.')).toBeInTheDocument(); - for (const name of names.slice(0, 3)) expect(within(popover).getByText(name)).toBeInTheDocument(); - expect(within(popover).queryByText('d.four')).toBeNull(); + // A chip shows the name after its namespace, as the frame draws it; the full name is its title. + for (const name of names.slice(0, 3)) { + expect(within(popover).getByText(name.slice(name.indexOf('.') + 1))).toHaveAttribute('title', name); + } + expect(within(popover).queryByText('four')).toBeNull(); + expect(within(popover).queryByText('a.one')).toBeNull(); expect(within(popover).getByText('+2')).toBeInTheDocument(); expect(within(popover).getByRole('button', { name: 'Add' })).toBeInTheDocument(); }); diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 3ae6713fc1..58fe1e548c 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -167,14 +167,14 @@ describe('v2 layout invariants (CSS rule presence)', () => { const phone = css.slice(css.lastIndexOf('@media (max-width: 760px)')); expect(phone).toMatch(/\.v2-aprofile__cta-row \.v2-aprofile__btn \{[^}]*height: 44px/); }); - test('/v2/profile: the head is the shared bar, and on a phone its one key takes the full row (rulings 11-04)', () => { - // The page draws V2PageHead with the shared bar class and the wrap variant; - // the full-row key on a phone is the shared rule in v2.css, not a page copy. - expect(read('../components/V2PersonProfile.tsx')).toContain('className="v2-feature__header v2-page-head--wrap v2-profile__head"'); - const v2 = readV2Css(); - const phone = mediaBlockContaining(v2, '.v2-feature__header.v2-page-head--wrap > .v2-page-head__actions'); - expect(ruleBody(v2, '.v2-feature__header.v2-page-head--wrap', { within: phone })).toContain('flex-wrap: wrap'); - expect(ruleBody(v2, '.v2-feature__header.v2-page-head--wrap > .v2-page-head__actions', { within: phone })).toContain('flex: 1 0 100%'); + test('/v2/profile: on a phone the header wraps and its one key takes the full row (d215 s-profile 390)', () => { + // G11 back-to-frames (11-04): the frame draws the header on the page, not in + // the shared head bar, so the page keeps its own head rules. + const profile = read('../components/V2PersonProfile.css'); + const phone = profile.slice(profile.indexOf('@media (max-width: 760px)')); + expect(phone).toMatch(/\.v2-profile \.v2-profile__head \{[^}]*flex-wrap: wrap/); + expect(phone).toMatch(/\.v2-profile \.v2-profile__follow, \.v2-profile \.v2-profile__edit \{[^}]*flex: 1 0 100%/); + expect(read('../components/V2PersonProfile.tsx')).not.toContain('V2PageHead'); }); test('/v2/activity and /v2/artifacts: the shared phone head bar left v2.css with both pages (d215 builds)', () => { @@ -627,6 +627,7 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(v2).not.toContain('.v2-pods__channel-dot'); }); + // The frame draws 36px rows; 44 is the floor where the two cannot both hold (ruling 11-04). test('pod list rows keep a 44px touch target at 760px and below, with the artboard mark', () => { 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 }); @@ -640,6 +641,11 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(ruleBody(v2, '.v2-pods__row-name', { within: mobile })).toBe(''); expect(pin).toContain('width: 28px'); expect(pin).toContain('height: 28px'); + // The 28px pin takes a 44px touch area from a transparent ::before (ruling 11-04). + const pinTouch = ruleBody(v2, '.v2-root button.v2-pods__pin::before', { within: mobile }); + expect(pinTouch).toContain('width: 44px'); + expect(pinTouch).toContain('height: 44px'); + expect(pinTouch).toContain('background: transparent'); }); test('phone tab labels keep text contrast while the glyphs take the artboard grey (D223)', () => { @@ -668,6 +674,7 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(ruleBody(v2, '.v2-pods__page-title')).toContain('font: 800 21px/1 var(--v2-font-display, var(--v2-font))'); }); + // The frame draws these at 32 (theme 28x24); four 44-wide targets do not fit its bar, so the floor wins (ruling 11-04). test('the phone pods bar gives each control a 44px target', () => { const icon = topLevelRuleBody(v2, '.v2-pods-aside--page .v2-pods__bar .v2p-ib'); expect(icon).toContain('width: 44px'); @@ -682,6 +689,36 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(topLevelRuleBody(v2, '.v2-pods-aside--page .v2-pods__bar .seg.v2-theme-seg button::after')).toContain('inset: -2px 0'); }); + // Cofounder ruling 11-04: the d215 pick wins on look, but 44px is a floor. The + // view segment sits alone in its row, so it can be drawn at the frame's 28px with a + // 44px ::before; the rows and bar keys cannot both match the frame and hold 44, so + // they keep the floor (the two tests above). + test('the phone Recent · All · Pinned segment is the frame\'s 28px, with a 44px touch area (ruling 11-04)', () => { + const phone = mediaBlockContaining(v2, '.v2-pods-aside--page .v2-pods__view .v2p-seg button {'); + expect(phone).not.toBe(''); + const option = ruleBody(v2, '.v2-pods-aside--page .v2-pods__view .v2p-seg button', { within: phone }); + expect(option).toContain('position: relative'); + expect(option).toContain('height: 28px'); + const touch = ruleBody(v2, '.v2-pods-aside--page .v2-pods__view .v2p-seg button::before', { within: phone }); + expect(touch).toContain('width: max(100%, 44px)'); + expect(touch).toContain('height: max(100%, 44px)'); + expect(touch).toContain('background: transparent'); + expect(podsSidebar).not.toContain('V2PodsSidebar.css'); + }); + + test('Settings keys keep the frame size on a phone and take 44px from ::before (ruling 11-04)', () => { + const phone = mediaBlockContaining(ss, '.v2-settings .v2p-ib.v2p-b-sm'); + expect(phone).not.toBe(''); + expect(ruleBody(ss, '.v2-settings .v2p-b.v2p-b-sm', { within: phone })).toContain('height: 28px'); + expect(ruleBody(ss, '.v2-settings .v2p-ib.v2p-b-sm', { within: phone })).toContain('width: 28px; height: 28px;'); + expect(ruleBody(ss, '.v2-settings .v2p-seg.v2p-seg--sm button', { within: phone })).toContain('height: 24px'); + const touch = selectorRuleBody(phone, '.v2-settings .v2p-seg button::before {'); + expect(touch).toContain('width: max(100%, 44px)'); + expect(touch).toContain('height: max(100%, 44px)'); + expect(touch).toContain('background: transparent'); + expect(phone).toContain('.v2-settings .v2p-lrow .v2p-b::before,'); + }); + test('pod search shortcut hint hides on narrow and touch-only screens', () => { const hideHint = mediaAt(v2, '@media (max-width: 760px), (hover: none) and (pointer: coarse) {'); expect(hideHint).not.toBe(''); @@ -3380,14 +3417,14 @@ describe('Bring your own agent onto Signal (TASK-166)', () => { expect(ruleBody(byoCss, '.v2-feature--byo .v2-byo__copy:hover')).toContain('var(--v2-surface-hover)'); expect(ruleBody(byoCss, '.v2-feature--byo select.v2-byo__input:focus-visible')).toContain('var(--v2-focus-ring)'); }); - test('the submit button is the ink key, at the small key r8 (item 1, rulings 11-04)', () => { + test('the submit button is the ink key, 34px at radius 10, as d215 s-byo draws it (item 1)', () => { const submit = cssDeclarations(ruleBody(byoCss, '.v2-feature--byo .v2-byo__submit')); expect(submit.background).toBe('linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b))'); expect(submit.color).toBe('var(--v2-on-fill)'); expect(submit.border).toBe('1px solid var(--v2-fill-edge)'); const shared = cssDeclarations(selectorRuleBody(byoCss, '.v2-feature--byo .v2-byo__submit, .v2-feature--byo .v2-byo__secondary')); - expect(shared['border-radius']).toBe('var(--v2-r-8)'); - expect(shared.height).toBe('28px'); + expect(shared['border-radius']).toBe('10px'); + expect(shared.height).toBe('34px'); expect(shared.cursor).toBe('pointer'); // The old rules left v2.css with the lift. expect(v2).not.toContain('button.v2-byo__submit'); @@ -3629,9 +3666,9 @@ describe('the BYO after-submit surfaces onto Signal (TASK-169)', () => { expect(decls(ruleBody(byoCss, '.v2-byo__stat-label')).font).toBe('500 11px/16px var(--v2-font-mono)'); }); - test('item 3: the secondary button is a control, at the small key radius', () => { + test('item 3: the secondary button is a control, at the frame key radius', () => { expect(decls(selectorRuleBody(byoCss, '.v2-feature--byo .v2-byo__submit, .v2-feature--byo .v2-byo__secondary'))['border-radius']) - .toBe('var(--v2-r-8)'); + .toBe('10px'); expect(decls(ruleBody(byoCss, '.v2-feature--byo .v2-byo__secondary')).color).toBe('var(--v2-text-secondary)'); }); @@ -5898,29 +5935,28 @@ describe('chat sheets lifted out of v2.css (S3)', () => { }); }); -// d215 run-trace-after at 390. The frame drew 16px gutters with "Back to pod" -// as the first row; at prod 486240fb a loose rhythm pushed the foot's run-id -// line under the fixed tab bar. Rulings 11-04 move it onto the shared head bar -// (back is the bar's lead key) and the 12px phone gutter of .v2-feature__scroll. -// Measured in a browser; jsdom cannot see it. -describe('run trace keeps the 12px phone gutter and clears the tab bar', () => { +// d215 run-trace-after at 390: the page sits in the shared V2FeaturePage body, +// whose phone padding is 12px all round. The frame draws 16px gutters with no +// top padding ("Back to pod" is the first 44px row) and a tighter rhythm; at +// prod 486240fb the 12px body and loose rhythm pushed the foot's run-id line +// under the fixed tab bar. #3373 moved the page onto the shared head bar and the +// 12px gutter, which the frame does not draw; G11 back-to-frames (11-04) put it +// back. Measured in a browser (foot ends 21px above the tab bar); jsdom cannot see it. +describe('run trace keeps the frame phone gutters and clears the tab bar', () => { const rt = read('../components/V2RunTrace.css'); const v2 = readV2Css(); const app = read('../V2App.tsx'); const page = read('../components/V2RunTrace.tsx'); - const phone = mediaBlockContaining(rt, '.v2-feature--run-trace .v2-run-trace p'); + const phone = mediaBlockContaining(rt, '.v2-feature--run-trace > .v2-feature__body'); - test('the run trace route tags its feature pane and draws the shared head bar', () => { - expect(app).toContain("feature(t('runTrace.title'), , false, false, 'v2-feature--bar v2-feature--run-trace')"); - expect(page).toContain('className="v2-feature__header v2-run-trace__head"'); - expect(page).toContain('className="v2p-b v2p-b-g v2p-ib v2-run-trace__back"'); + test('the run trace route tags its feature pane, and the page owns its h1 (no shared head bar)', () => { + expect(app).toContain("feature(t('runTrace.title'), , false, false, 'v2-feature--run-trace')"); + expect(page).not.toContain('V2PageHead'); + expect(page).toContain(' { - expect(page).toContain('
'); - expect(ruleBody(v2, '.v2-feature__scroll', { within: mediaBlockContaining(v2, ' .v2-feature__scroll {') })).toContain('padding: 12px 12px 16px'); - expect(rt).not.toMatch(/\.v2-feature__body/); - expect(rt).not.toMatch(/padding: 0 16px/); + test('at 760 and below the body has 16px side gutters and no top padding', () => { + expect(ruleBody(rt, '.v2-feature--run-trace > .v2-feature__body', { within: phone })).toContain('padding: 0 16px 16px'); }); test('the frame rhythm: paragraphs flush, a turn is an 8px grid', () => { @@ -5931,6 +5967,7 @@ describe('run trace keeps the 12px phone gutter and clears the tab bar', () => { }); test('desktop is untouched: the overrides live only in the phone block', () => { + expect(topLevelRuleBody(rt, '.v2-feature--run-trace > .v2-feature__body')).toBe(''); expect(topLevelRuleBody(rt, '.v2-feature--run-trace .v2-run-trace p')).toBe(''); }); @@ -6053,14 +6090,16 @@ describe('secondary pages on the reference grammar (ui-audit-1104 findings 2–8 expect(ruleBody(v2, '.v2-feature__header .v2-page-head__meta')).toContain('font: 500 11.5px/16px var(--v2-font-mono)'); }); - test('finding 3: Your team and both profiles draw the shared bar, and no head carries the "i" hint', () => { + test('finding 3: Your team draws the shared bar; the profiles and BYO keep the heads their frames draw', () => { expect(read('../components/V2YourTeamPage.tsx')).toContain('className="v2-feature__header v2-page-head--wrap v2-team__head"'); - expect(read('../components/V2PersonProfile.tsx')).toContain('className="v2-feature__header v2-page-head--wrap v2-profile__head"'); - expect(read('../agents/V2AgentProfile.tsx')).toContain("'v2-aprofile__hero v2-page-head v2-feature__header v2-page-head--wrap'"); - // Each of those routes opts the pane out of the body inset so the bar meets the pane edge. + // G11 back-to-frames (11-04): d215 s-profile and s-agent-profile draw the + // header on the page (56px avatar, name, meta, the key at the end), not the bar. + expect(read('../components/V2PersonProfile.tsx')).toContain('
'); + expect(read('../agents/V2AgentProfile.tsx')).toContain('
'); expect(app).toMatch(/'Your Team',[\s\S]{0,300}'v2-feature--bar'/); - expect(app).toContain("'v2-feature--bar v2-feature--profile'"); - expect(app).toContain("'v2-feature--bar v2-feature--aprofile'"); + expect(app).toContain("feature(t('common.pageTitle.profile'), , false, false, 'v2-feature--profile')"); + expect(app).toContain("feature(t('common.pageTitle.agentProfile'), page, false, false)"); + expect(app).not.toContain('v2-feature--aprofile'); expect(ruleBody(v2, '.v2-feature--bar > .v2-feature__body')).toContain('padding: 0'); expect(ruleBody(v2, '.v2-feature__scroll')).toContain('overflow-y: auto'); // The flex chain that lets the scroller scroll: each link fills and may shrink below its content. @@ -6070,9 +6109,11 @@ describe('secondary pages on the reference grammar (ui-audit-1104 findings 2–8 for (const decl of ['flex: 1 1 auto', 'min-height: 0', 'display: flex', 'flex-direction: column']) expect(legacy).toContain(decl); const scroll = ruleBody(v2, '.v2-feature__scroll'); for (const decl of ['flex: 1 1 auto', 'min-height: 0']) expect(scroll).toContain(decl); - // BYO and Connect moved the hint to an intro line. + // Connect moved the hint to an intro line. d215 s-byo draws BYO's lead as the + // head's "i" hint at 1440 and 390, so BYO keeps it and draws no intro line. expect(app).not.toMatch(/{t('connect.intro')}

"); }); @@ -6127,28 +6168,26 @@ describe('secondary pages on the reference grammar (ui-audit-1104 findings 2–8 } }); - test('the agent profile bar keeps a 44px touch area on the 24px owner avatar control at 390', () => { + test('the agent profile owner avatar control is the frame\'s 56px face, a touch area on its own (d215 s-agent-profile)', () => { + // G11 back-to-frames (11-04): the shell no longer draws the hero as a 24px bar avatar. const css = read('../agents/v2-agent-profile.css'); - const phone = mediaBlockContaining(css, '.v2-feature--aprofile .v2-aprofile__hero .v2-avatar { width: 24px; height: 24px; }'); - expect(phone).not.toBe(''); - const hit = selectorRuleBody(css, '.v2-feature--aprofile .v2-aprofile__avatar-button::after {'); - // 24 + 2 x 10 = 44, from a positioned button. - expect(hit).toContain('inset: -10px'); - expect(ruleBody(css, '.v2-aprofile .v2-aprofile__avatar-button')).toContain('position: relative'); - expect(css.lastIndexOf('@media (max-width: 760px)')).toBeLessThan(css.indexOf('.v2-feature--aprofile .v2-aprofile__avatar-button::after')); + expect(css).not.toContain('.v2-feature--aprofile'); + const face = selectorRuleBody(css, '.v2-aprofile .v2-aprofile__avatar .v2-avatar,\n.v2-aprofile .v2-aprofile__avatar-button .v2-avatar {'); + expect(face).toContain('width: 56px'); + expect(face).toContain('height: 56px'); }); test('the public bar\'s keys (Sign up) reach 44px to touch on a phone', () => { const css = read('../agents/v2-agent-profile.css'); - expect(ruleBody(css, '.v2-aprofile .v2-aprofile__btn')).toContain('height: 28px'); - // Both bar keys carry a 1px border, and an absolute ::before measures from the padding box, - // so the overlay starts from 26, not 28 (measured in Chromium: -8px gave 42). + // The frame's 34px key (d215 s-agent-profile). Both bar keys carry a 1px border, and an + // absolute ::before measures from the padding box, so the overlay starts from 32, not 34. + expect(ruleBody(css, '.v2-aprofile .v2-aprofile__btn')).toContain('height: 34px'); expect(ruleBody(css, '.v2-aprofile .v2-aprofile__btn--primary')).toContain('border: 1px solid'); const phone = css.slice(css.lastIndexOf('@media (max-width: 760px)')); const hits = [...phone.matchAll(/\.v2-aprofile \.v2-aprofile__bar \.v2-aprofile__btn::before \{([^}]*)\}/g)].map((m) => m[1]); - // The last inset wins: its vertical reach must make 44 from the 26px padding box. + // The last inset wins: its vertical reach must make 44 from the 32px padding box. const inset = hits.map((body) => body.match(/inset:\s*(-?\d+)px/)).filter(Boolean).pop(); - expect(28 - 2 - 2 * Number(inset?.[1])).toBeGreaterThanOrEqual(44); + expect(34 - 2 - 2 * Number(inset?.[1])).toBeGreaterThanOrEqual(44); }); test('finding 6: the board header icon keys never shrink on a phone', () => { @@ -6163,9 +6202,10 @@ describe('secondary pages on the reference grammar (ui-audit-1104 findings 2–8 const connect = selectorRuleBody(read('../components/V2ConnectPage.css'), '.v2-connect .v2-connect__cta, .v2-connect .v2-connect__secondary {'); expect(connect).toContain('height: 28px'); expect(connect).toContain('border-radius: var(--v2-r-8)'); + // The agent profile keys stay the frame's 34 at r10 (d215 s-agent-profile; G11 back-to-frames). const aprofile = ruleBody(read('../agents/v2-agent-profile.css'), '.v2-aprofile .v2-aprofile__btn'); - expect(aprofile).toContain('height: 28px'); - expect(aprofile).toContain('border-radius: var(--v2-r-8)'); + expect(aprofile).toContain('height: 34px'); + expect(aprofile).toContain('border-radius: 10px'); }); test('finding 7: cards are the D215 ring at r9 and lanes are r12, never a 1px border', () => { @@ -6181,27 +6221,34 @@ describe('secondary pages on the reference grammar (ui-audit-1104 findings 2–8 for (const sheet of ['../components/V2YourTeamPage.css', '../components/V2RunTrace.css', '../components/V2AgentBYO.css']) { expect(read(sheet)).not.toContain(ring); } - expect(ruleBody(read('../components/V2PersonProfile.css'), '.v2-profile .v2-profile__list')).toContain('border-radius: var(--v2-r-12)'); - expect(ruleBody(read('../agents/v2-agent-profile.css'), '.v2-aprofile .v2-aprofile__card')).toContain('border-radius: var(--v2-r-12)'); + // The profiles' lanes are the frame's 14px bordered lanes (d215 s-profile, s-agent-profile). + expect(ruleBody(read('../components/V2PersonProfile.css'), '.v2-profile .v2-profile__lane')).toContain('border-radius: 14px'); + expect(ruleBody(read('../agents/v2-agent-profile.css'), '.v2-aprofile .v2-aprofile__card')).toContain('border-radius: 14px'); }); test('finding 8: section labels are Settings\' mono 11.5/500 muted, and counts are 11.5', () => { const mono = 'font: 500 11.5px/16px var(--v2-font-mono)'; - expect(ruleBody(read('../components/V2PersonProfile.css'), '.v2-profile .v2-profile__lane h2')).toContain(mono); - expect(ruleBody(read('../agents/v2-agent-profile.css'), '.v2-aprofile .v2-aprofile__card-title')).toContain(mono); expect(ruleBody(read('../components/V2ConnectPage.css'), '.v2-connect .v2-connect__lane h2')).toContain(mono); - expect(ruleBody(read('../components/V2PersonProfile.css'), '.v2-profile .v2-profile__meta')).toContain(mono); - expect(ruleBody(read('../agents/v2-agent-profile.css'), '.v2-aprofile .v2-aprofile__meta')).toContain('11.5px'); expect(ruleBody(boardCss, '.v2-feature--board .v2-board__subtitle')).toContain('500 11.5px/1 var(--v2-font-mono)'); - // Run trace: "Turn 1" was Bricolage 20/700. - expect(ruleBody(read('../components/V2RunTrace.css'), '.v2-run-trace h2')).toContain(mono); }); - test('finding 7: the run trace summary and tool calls are ringed r9 cards, no 1px border', () => { + // G11 back-to-frames (11-04): these frames draw display lane titles, a 12px mono + // meta and Bricolage turn labels, so the pages keep them. + test('the profiles and the run trace keep the labels their d215 frames draw', () => { + const display16 = 'font: 800 16px/1.25 var(--v2-font-display)'; + expect(ruleBody(read('../components/V2PersonProfile.css'), '.v2-profile .v2-profile__lane h2')).toContain(display16); + expect(ruleBody(read('../agents/v2-agent-profile.css'), '.v2-aprofile .v2-aprofile__card-title')).toContain(display16); + expect(ruleBody(read('../components/V2PersonProfile.css'), '.v2-profile .v2-profile__meta')).toContain('font: 500 12px/16px var(--v2-font-mono)'); + expect(ruleBody(read('../agents/v2-agent-profile.css'), '.v2-aprofile .v2-aprofile__meta')).toContain('font: 500 12px/16px var(--v2-font-mono)'); + const turn = ruleBody(read('../components/V2RunTrace.css'), '.v2-run-trace h2'); + expect(turn).toContain('font-family: var(--v2-font-display)'); + expect(turn).toContain('font-size: 20px'); + }); + + test('the run trace summary and tool calls are the frame\'s bordered cards (d215 s-run-trace)', () => { const card = ruleBody(read('../components/V2RunTrace.css'), '.v2-run-trace__summary, .v2-run-trace__call'); - expect(card).toContain('border-radius: var(--v2-r-9)'); - expect(card).toContain('box-shadow: var(--v2-ring-card)'); - expect(card).not.toMatch(/(^|[\s;{])border:/); + expect(card).toContain('border: 1px solid var(--v2-border)'); + expect(card).toContain('padding: 16px'); }); }); @@ -6344,9 +6391,9 @@ describe('front door keys, auth gutter and the shell state line (11-04)', () => .toContain('.v2-root .v2-demo__card button.v2-decision-card__choice { min-height: 44px;'); }); - test('the auth pages sit on the 12px phone gutter', () => { + test('the auth card keeps its frame\'s 20px side inset at 390 (d215 auth frames)', () => { const phone = blockContaining(v2, '@media (max-width: 480px)', '.v2-login__card {'); - expect(ruleBody(phone, ' .v2-login__card')).toContain('padding: 28px 12px'); + expect(ruleBody(phone, ' .v2-login__card')).toContain('padding: 28px 20px'); }); test('the shell state line is memory\'s p.v2m-state grammar', () => { diff --git a/frontend/src/v2/agents/V2AgentProfile.tsx b/frontend/src/v2/agents/V2AgentProfile.tsx index bb53179ba5..c7dacc8b78 100644 --- a/frontend/src/v2/agents/V2AgentProfile.tsx +++ b/frontend/src/v2/agents/V2AgentProfile.tsx @@ -403,13 +403,9 @@ const V2AgentProfile: React.FC = ({ shell }) => { /> ); - // In the app shell the hero is the shared page head bar (.v2-feature__header, - // the route's v2-feature--bar) and the rest scrolls under it; signed out it - // stays the public page's hero. - const inShell = Boolean(shell); const main = (
-
+
{/* The avatar IS the edit control (2026-09-01). Non-owners get the plain avatar; owners get the same avatar as a button. */} {canEditAvatar ? ( @@ -428,13 +424,13 @@ const V2AgentProfile: React.FC = ({ shell }) => { ) : ( {avatar} )} -
+
-

{agent.displayName}

+

{agent.displayName}

{runtimeLabel && {runtimeLabel}} {agent.officialAgent && {t('agentProfile.hero.official')}}
- {agent.description && !inShell &&

{agent.description}

} + {agent.description &&

{agent.description}

}

{t('agentProfile.hero.activeInPods', { count: pods.count })} {memory.has && ` · ${t('agentProfile.hero.memories', { count: memory.entryCount })}`} @@ -445,16 +441,13 @@ const V2AgentProfile: React.FC = ({ shell }) => { )}

-
+
{isOwner && signedIn && ( {t('agentSettings.settings')} )} {primaryKey}
-
- {/* In the bar there is room for the name and one meta line; the description is the intro line under it. */} - {agent.description && inShell &&

{agent.description}

} {avatarDialogOpen && (

{t('agentProfile.avatar.title')}

@@ -619,7 +612,6 @@ const V2AgentProfile: React.FC = ({ shell }) => {
)} -
); diff --git a/frontend/src/v2/agents/v2-agent-profile.css b/frontend/src/v2/agents/v2-agent-profile.css index 2b9bc2d661..c992cf20ae 100644 --- a/frontend/src/v2/agents/v2-agent-profile.css +++ b/frontend/src/v2/agents/v2-agent-profile.css @@ -20,26 +20,22 @@ .v2-aprofile .v2-aprofile__brand .v2-rail__brand-icon svg { width: 16px; height: 16px; } .v2-aprofile .v2-aprofile__nav { display: flex; align-items: center; gap: 18px; } .v2-aprofile .v2-aprofile__navlink { font-size: 13.5px; font-weight: 500; color: var(--v2-text-secondary); text-decoration: none; } -/* The v2p small key's size (.v2p-b-sm: 28 at r8, rulings 11-04); 44 on phones below. */ -.v2-aprofile .v2-aprofile__btn { box-sizing: border-box; cursor: pointer; flex: none; display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 0 10px; border-radius: var(--v2-r-8); font: 600 12.5px/1 var(--v2-font); white-space: nowrap; text-decoration: none; } +.v2-aprofile .v2-aprofile__btn { box-sizing: border-box; cursor: pointer; flex: none; display: inline-flex; align-items: center; justify-content: center; height: 34px; padding: 0 14px; border-radius: 10px; font: 600 13px/1 var(--v2-font); white-space: nowrap; text-decoration: none; } .v2-aprofile .v2-aprofile__btn--primary { color: var(--v2-on-fill); background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b)); border: 1px solid var(--v2-fill-edge); } .v2-aprofile .v2-aprofile__btn--ghost { color: var(--v2-text-primary); background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); border: 1px solid var(--v2-key-edge); box-shadow: var(--v2-key-inset); } .v2-aprofile .v2-aprofile__hero { display: flex; align-items: flex-start; gap: 16px; } .v2-aprofile .v2-aprofile__avatar { flex: none; display: inline-flex; position: relative; } .v2-aprofile .v2-aprofile__avatar-button { all: unset; cursor: pointer; flex: none; display: inline-flex; position: relative; border-radius: 14px; } -/* The public hero's 56px face; in the app shell the bar draws memory's 34 (below). */ -.v2-aprofile--public .v2-aprofile__avatar .v2-avatar, -.v2-aprofile--public .v2-aprofile__avatar-button .v2-avatar { width: 56px; height: 56px; border-radius: 14px; font-size: 18px; } +.v2-aprofile .v2-aprofile__avatar .v2-avatar, +.v2-aprofile .v2-aprofile__avatar-button .v2-avatar { width: 56px; height: 56px; border-radius: 14px; font-size: 18px; } .v2-aprofile .v2-aprofile__avatar-hint { position: absolute; right: -4px; bottom: -4px; height: 18px; padding: 0 6px; border-radius: 9px; background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); font: 600 12px/18px var(--v2-font); color: var(--v2-text-secondary); } .v2-aprofile .v2-aprofile__hero-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; } .v2-aprofile .v2-aprofile__name-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; } -.v2-aprofile .v2-aprofile__name { margin: 0; color: var(--v2-text-primary); overflow-wrap: anywhere; } -/* Signed out the hero sets its own title; in the shell it is the bar's .v2-feature__title. */ -.v2-aprofile--public .v2-aprofile__name { font: 800 24px/1.15 var(--v2-font-display); letter-spacing: -0.03em; } +.v2-aprofile .v2-aprofile__name { margin: 0; font: 800 24px/1.15 var(--v2-font-display); letter-spacing: -0.03em; color: var(--v2-text-primary); overflow-wrap: anywhere; } .v2-aprofile .v2-aprofile__runtime { font: 600 11px/18px var(--v2-font-mono); letter-spacing: .04em; text-transform: uppercase; color: var(--v2-text-secondary); background: var(--v2-surface-tint); padding: 0 7px; border-radius: 5px; } .v2-aprofile .v2-aprofile__badge { font: 600 12px/18px var(--v2-font); color: var(--v2-accent-text); background: var(--v2-accent-soft); padding: 0 8px; border-radius: 9px; } .v2-aprofile .v2-aprofile__desc { margin: 2px 0 0; font-size: 14px; line-height: 20px; color: var(--v2-text-secondary); } -.v2-aprofile .v2-aprofile__meta { margin: 2px 0 0; font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } +.v2-aprofile .v2-aprofile__meta { margin: 2px 0 0; font: 500 12px/16px var(--v2-font-mono); color: var(--v2-text-muted); } .v2-aprofile .v2-aprofile__keys { flex: none; display: flex; gap: 8px; } /* A long agent name truncates inside its key instead of overflowing it. */ .v2-aprofile .v2-aprofile__keys .v2-aprofile__btn { min-width: 0; max-width: 100%; } @@ -47,10 +43,9 @@ .v2-aprofile .v2-agent-settings__tabs { display: flex; gap: 24px; margin: 0; border-bottom: 0; box-shadow: inset 0 -1px 0 var(--v2-border-soft); } .v2-aprofile .v2-agent-settings__tabs > * { padding: 10px 2px; font-size: 14px; color: var(--v2-text-secondary); text-decoration: none; } .v2-aprofile .v2-agent-settings__tabs [aria-current="page"] { color: var(--v2-text-primary); font-weight: 600; border-bottom: 0; box-shadow: inset 0 -2px 0 var(--v2-accent); } -/* A section is a ringed list at r12 with Settings' mono 11.5 label (rulings 11-04). */ -.v2-aprofile .v2-aprofile__card { display: flex; flex-direction: column; gap: 8px; padding: 16px 20px; border-radius: var(--v2-r-12); background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } -.v2-aprofile .v2-aprofile__card-title { display: flex; align-items: baseline; gap: 8px; margin: 0; font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } -.v2-aprofile .v2-aprofile__count { font: 500 11.5px/16px var(--v2-font-mono); letter-spacing: 0; color: var(--v2-text-muted); } +.v2-aprofile .v2-aprofile__card { display: flex; flex-direction: column; gap: 8px; padding: 16px 20px; border-radius: 14px; background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } +.v2-aprofile .v2-aprofile__card-title { display: flex; align-items: baseline; gap: 8px; margin: 0; font: 800 16px/1.25 var(--v2-font-display); letter-spacing: -0.02em; color: var(--v2-text-primary); } +.v2-aprofile .v2-aprofile__count { font: 500 12px/16px var(--v2-font-mono); letter-spacing: 0; color: var(--v2-text-muted); } .v2-aprofile .v2-aprofile__owner-tag { margin-left: auto; font: 500 11.5px/16px var(--v2-font-mono); letter-spacing: 0; color: var(--v2-text-muted); } .v2-aprofile .v2-aprofile__list { list-style: none; margin: 0; padding: 0; } .v2-aprofile .v2-aprofile__list li + li { border-top: 1px solid var(--v2-border-soft); } @@ -111,8 +106,8 @@ /* The avatar picker opens in the page flow under the header it edits, so it never covers the profile and fits a phone without a sideways scroll. */ -.v2-aprofile .v2-aprofile__avatar-picker { padding: 16px 20px; border-radius: var(--v2-r-12); background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } -.v2-aprofile .v2-aprofile__avatar-title { margin: 0 0 12px; font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } +.v2-aprofile .v2-aprofile__avatar-picker { padding: 16px 20px; border-radius: 14px; background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } +.v2-aprofile .v2-aprofile__avatar-title { margin: 0 0 12px; font: 800 16px/1.25 var(--v2-font-display); letter-spacing: -0.02em; color: var(--v2-text-primary); } .v2-aprofile .v2-aprofile__avatar-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(52px, 1fr)); gap: 8px; max-width: 560px; } .v2-aprofile .v2-aprofile__avatar-choice { all: unset; box-sizing: border-box; cursor: pointer; aspect-ratio: 1; padding: 2px; border: 2px solid transparent; border-radius: 50%; line-height: 0; } .v2-aprofile .v2-aprofile__avatar-choice img { width: 100%; height: 100%; border-radius: 50%; } @@ -125,30 +120,10 @@ .v2-aprofile .v2-aprofile__avatar-cancel { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; margin: 12px 0 0; height: 34px; padding: 0 14px; border-radius: 10px; font: 600 13px/1 var(--v2-font); color: var(--v2-text-primary); background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); border: 1px solid var(--v2-key-edge); box-shadow: var(--v2-key-inset); } /* CJK never takes negative letter-spacing (TASK-055). */ .v2-root:lang(zh) .v2-aprofile .v2-aprofile__name, +.v2-root:lang(zh) .v2-aprofile .v2-aprofile__card-title, .v2-root:lang(zh) .v2-aprofile .v2-aprofile__empty-title, .v2-root:lang(zh) .v2-aprofile .v2-aprofile__brand { letter-spacing: 0; } -/* In the app shell (the route passes v2-feature--bar v2-feature--aprofile) the - hero is the shared page head bar, .v2-feature__header: avatar, name and one - meta line, the keys at the end, as agent memory draws it. The rest scrolls - under it in a centred 760 column. Signed out, the public layout above stands. */ -.v2-feature--aprofile .v2-aprofile { flex: 1 1 auto; } -.v2-feature--aprofile .v2-aprofile__main { max-width: none; flex: 1 1 auto; min-height: 0; gap: 0; } -.v2-feature--aprofile .v2-aprofile__hero { min-height: 76px; align-items: center; gap: 12px; } -.v2-feature--aprofile .v2-aprofile__hero-text { gap: 5px; align-items: flex-start; } -/* The 18px "Edit" badge covered the bar's 34px face; the avatar stays the edit - control, named by its aria-label and title. */ -.v2-feature--aprofile .v2-aprofile__avatar-hint { display: none; } -.v2-feature--aprofile .v2-aprofile__body { --v2-feature-col: 760px; display: flex; flex-direction: column; gap: 16px; } -.v2-aprofile--public .v2-aprofile__body { display: contents; } -.v2-aprofile .v2-aprofile__intro { margin: 0 4px; font-size: 13.5px; } -@media (max-width: 760px) { - .v2-feature--aprofile .v2-aprofile__hero { min-height: 52px; gap: 0 10px; } - .v2-feature--aprofile .v2-aprofile__hero .v2-avatar { width: 24px; height: 24px; } - .v2-feature--aprofile .v2-aprofile__hero-text { gap: 3px; padding: 8px 0; align-items: flex-start; } - .v2-feature--aprofile .v2-aprofile__body { gap: 10px; } -} - /* At 390 every control is at least 44px to touch, including the ones the frame draws smaller. Last in the file so it outranks the resets above. */ @media (max-width: 760px) { @@ -156,15 +131,13 @@ .v2-aprofile .v2-aprofile__bar .v2-aprofile__btn { position: relative; } .v2-aprofile .v2-aprofile__navlink::before, .v2-aprofile .v2-aprofile__bar .v2-aprofile__btn::before { content: ''; position: absolute; inset: -5px -6px; } - /* The bar key draws at the v2p small key's 28 with a 1px border, and an absolute overlay - measures from inside the border (26), so it reaches 9px each way: 26 + 18 = 44. */ - .v2-aprofile .v2-aprofile__bar .v2-aprofile__btn::before { inset: -9px -6px; } + /* The bar key draws 34 with a 1px border, and an absolute overlay measures from + inside the border (32), so it reaches 6px each way: 32 + 12 = 44. */ + .v2-aprofile .v2-aprofile__bar .v2-aprofile__btn::before { inset: -6px; } .v2-aprofile .v2-aprofile__navlink::before { inset: -14px -7px; } .v2-aprofile .v2-agent-settings__tabs > * { box-sizing: border-box; min-height: 44px; display: inline-flex; align-items: center; } .v2-aprofile .v2-aprofile__pod-remove { height: 44px; line-height: 44px; } .v2-aprofile button.v2-aprofile__more { display: inline-flex; align-items: center; min-height: 44px; } .v2-aprofile .v2-aprofile__avatar-cancel { min-height: 44px; } .v2-aprofile .v2-aprofile__cta-row .v2-aprofile__btn { height: 44px; font-size: 14px; } - /* The bar draws the owner's avatar control at 24px; the touch area stays 44. */ - .v2-feature--aprofile .v2-aprofile__avatar-button::after { content: ''; position: absolute; inset: -10px; } } diff --git a/frontend/src/v2/components/V2AgentBYO.css b/frontend/src/v2/components/V2AgentBYO.css index 9c0621034b..dc049a64a4 100644 --- a/frontend/src/v2/components/V2AgentBYO.css +++ b/frontend/src/v2/components/V2AgentBYO.css @@ -6,8 +6,6 @@ the page's 760px phone query. Scoped to .v2-feature--byo, the page's wrapper. */ .v2-feature--byo .v2-byo__layout { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 28px; max-width: 1040px; margin: 0 auto; align-items: start; } .v2-feature--byo .v2-byo__main { min-width: 0; } -/* The page's lead line: the head carries no "i" hint (rulings 11-04). */ -.v2-feature--byo .v2-byo__intro { margin: 0 4px 14px; font-size: 13.5px; line-height: 20px; color: var(--v2-text-secondary); } .v2-feature--byo .v2-byo__form, .v2-feature--byo .v2-byo__result, .v2-feature--byo .v2-byo__snippet { display: flex; flex-direction: column; gap: 10px; } .v2-feature--byo .v2-byo__modes-question, .v2-feature--byo .v2-byo__preview-label, .v2-feature--byo .v2-byo__snippet-head { margin: 0 4px; font: 500 11.5px/1 var(--v2-font-mono); color: var(--v2-text-muted); } .v2-feature--byo .v2-byo__modes, .v2-feature--byo .v2-byo__os { margin: 0; display: inline-flex; align-self: flex-start; gap: 2px; padding: 2px; border-radius: 9px; background: var(--v2-surface-tint); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } @@ -23,8 +21,7 @@ /* the page shows a field's hint only while it has focus or an error */ .v2-feature--byo .v2-byo__field > .v2-byo__hint { display: none; } .v2-feature--byo .v2-byo__field:focus-within > .v2-byo__hint, .v2-feature--byo .v2-byo__field--error > .v2-byo__hint { display: block; } -/* The v2p small key's size (.v2p-b-sm: 28 at r8, rulings 11-04); 44 on phones below. */ -.v2-feature--byo .v2-byo__submit, .v2-feature--byo .v2-byo__secondary { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 0 10px; border-radius: var(--v2-r-8); font: 600 12.5px/1 var(--v2-font); white-space: nowrap; } +.v2-feature--byo .v2-byo__submit, .v2-feature--byo .v2-byo__secondary { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; height: 34px; padding: 0 13px; border-radius: 10px; font: 600 13px/1 var(--v2-font); white-space: nowrap; } .v2-feature--byo .v2-byo__submit { align-self: flex-start; margin-top: 6px; color: var(--v2-on-fill); background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b)); border: 1px solid var(--v2-fill-edge); } .v2-feature--byo .v2-byo__secondary { color: var(--v2-text-secondary); } .v2-feature--byo .v2-byo__link { all: unset; align-self: flex-start; cursor: pointer; color: var(--v2-accent-text); font-weight: 600; } @@ -102,8 +99,11 @@ .v2-feature--byo textarea.v2-byo__memory-text { height: auto; padding: 10px 12px; line-height: 20px; } @media (max-width: 760px) { .v2-feature--byo .v2-byo__mode-option > .v2-hint { min-width: 44px; min-height: 44px; justify-content: center; } - .v2-root .v2-feature--byo .v2-byo__mode-option button.v2-hint__trigger { position: relative; } - .v2-root .v2-feature--byo .v2-byo__mode-option button.v2-hint__trigger::after { content: ''; position: absolute; inset: -8px; } + /* Every "i" hint on the page (the head's and each start's) draws 28 with a 1px border; + the frame's info--lg is a 44 touch box. An overlay measures from inside the border + (26), so it reaches 9px each way: 26 + 18 = 44. */ + .v2-root .v2-feature--byo button.v2-hint__trigger { position: relative; } + .v2-root .v2-feature--byo button.v2-hint__trigger::after { content: ''; position: absolute; inset: -9px; } } /* not drawn in the frame yet: kept as it was */ diff --git a/frontend/src/v2/components/V2AgentBYO.tsx b/frontend/src/v2/components/V2AgentBYO.tsx index 9507ad67b1..ffbe657232 100644 --- a/frontend/src/v2/components/V2AgentBYO.tsx +++ b/frontend/src/v2/components/V2AgentBYO.tsx @@ -1238,13 +1238,6 @@ const V2AgentBYO: React.FC = () => { ) : null; - const pageLead = (() => { - if (issued) return undefined; - if (showCompactHosted && !hosted && hosting?.configured) return t('agentByo.compact.lead',{ agents: hosting?.caps.agentsPerUser, turns: hosting?.caps.turnsPerDay }); - if (showCompactByo) return t(setupOs === 'windows' ? 'agentByo.compactByo.leadWindows' : 'agentByo.compactByo.lead'); - return t('agentByo.description'); - })(); - return ( { @@ -1257,13 +1250,18 @@ const V2AgentBYO: React.FC = () => { } return t('agentByo.title'); })()} + // d215 s-byo draws the page's lead as the head's "i" hint, at 1440 and 390. + hint={(() => { + if (issued) return undefined; + if (showCompactHosted && !hosted && hosting?.configured) return t('agentByo.compact.lead',{ agents: hosting?.caps.agentsPerUser, turns: hosting?.caps.turnsPerDay }); + if (showCompactByo) return t(setupOs === 'windows' ? 'agentByo.compactByo.leadWindows' : 'agentByo.compactByo.lead'); + return t('agentByo.description'); + })()} showPodsSidebar={false} className="v2-feature--byo" >
- {/* The page's one-line lead: it was the head's "i" hint, which no head carries now. */} - {pageLead &&

{pageLead}

} {!issued && !hosted && personaCard && (
diff --git a/frontend/src/v2/components/V2PersonProfile.css b/frontend/src/v2/components/V2PersonProfile.css index 89b40f4292..0a1ae38b38 100644 --- a/frontend/src/v2/components/V2PersonProfile.css +++ b/frontend/src/v2/components/V2PersonProfile.css @@ -4,42 +4,37 @@ translated to --v2-* (so light, dark and auto switch together) and the frame's `.app.m` phone rules sit in the 760px phone query. Scoped to .v2-profile, the page's root. */ -/* The head is the shared bar (V2PageHead, .v2-feature__header, the route's - v2-feature--bar) with the avatar as its lead, as agent memory draws it; the - body scrolls under it in a centred 760 column. Keys are the v2p small keys - (28 at r8, 44 on phones). Rulings 11-04. */ -.v2-profile { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; } -.v2-profile .v2-profile__head { min-height: 76px; } -.v2-profile .v2-profile__head > .v2-page-head__identity { flex-direction: column; align-items: flex-start; gap: 5px; min-width: 0; } -.v2-profile .v2-profile__head .v2-profile__name { max-width: 100%; overflow-wrap: anywhere; } -.v2-profile .v2-profile__body { --v2-feature-col: 760px; display: flex; flex-direction: column; gap: 16px; } +.v2-profile { display: flex; flex-direction: column; gap: 16px; max-width: 760px; margin: 0 auto; } +.v2-profile .v2-profile__head { display: flex; align-items: center; gap: 16px; } .v2-profile .v2-profile__avatar { flex: none; display: inline-flex; } -.v2-profile .v2-profile__meta { font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } -.v2-profile .v2-profile__counts { margin: 0 4px; font-size: 13.5px; line-height: 20px; color: var(--v2-text-secondary); } -.v2-profile .v2-profile__follow, .v2-profile .v2-profile__edit { flex: none; text-decoration: none; } -/* A section is Settings' grammar: a mono label over a ringed r12 list (rulings 11-04). */ -.v2-profile .v2-profile__lane { display: flex; flex-direction: column; min-width: 0; } -.v2-profile .v2-profile__lane h2 { margin: 0 4px 8px; font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } -.v2-profile .v2-profile__list { list-style: none; margin: 0; padding: 0 16px; border-radius: var(--v2-r-12); background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } +.v2-profile .v2-profile__avatar .v2-avatar { width: 56px; height: 56px; border-radius: 14px; font-size: 18px; } +.v2-profile .v2-profile__id { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; } +.v2-profile .v2-profile__name { margin: 0; font: 800 24px/1.15 var(--v2-font-display); letter-spacing: -0.03em; color: var(--v2-text-primary); overflow-wrap: anywhere; } +.v2-profile .v2-profile__meta { font: 500 12px/16px var(--v2-font-mono); color: var(--v2-text-muted); } +.v2-profile .v2-profile__counts { font-size: 13px; line-height: 18px; color: var(--v2-text-secondary); } +.v2-profile .v2-profile__follow, .v2-profile .v2-profile__edit { all: unset; box-sizing: border-box; cursor: pointer; flex: none; display: inline-flex; align-items: center; justify-content: center; height: 34px; padding: 0 14px; border-radius: 10px; font: 600 13px/1 var(--v2-font); white-space: nowrap; text-decoration: none; } +.v2-profile .v2-profile__follow { color: var(--v2-on-fill); background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b)); border: 1px solid var(--v2-fill-edge); } +.v2-profile .v2-profile__follow--on, .v2-profile .v2-profile__edit { color: var(--v2-text-primary); background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); border: 1px solid var(--v2-key-edge); box-shadow: var(--v2-key-inset); } +.v2-profile .v2-profile__lane { display: flex; flex-direction: column; gap: 10px; padding: 18px 20px; border-radius: 14px; background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } +.v2-profile .v2-profile__lane h2 { margin: 0; font: 800 16px/1.25 var(--v2-font-display); letter-spacing: -0.02em; color: var(--v2-text-primary); } +.v2-profile .v2-profile__list { list-style: none; margin: 0; padding: 0; } .v2-profile .v2-profile__list li + li { border-top: 1px solid var(--v2-border-soft); } -.v2-profile .v2-profile__row { display: flex; align-items: center; justify-content: space-between; gap: 14px; min-height: 48px; padding: 6px 0; color: var(--v2-text-primary); text-decoration: none; } +.v2-profile .v2-profile__row { display: flex; align-items: center; justify-content: space-between; gap: 14px; min-height: 44px; padding: 6px 0; color: var(--v2-text-primary); text-decoration: none; } .v2-profile .v2-profile__row-title { font-size: 14px; font-weight: 600; color: var(--v2-text-primary); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .v2-profile .v2-profile__row-text { font-size: 13.5px; line-height: 19px; color: var(--v2-ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .v2-profile .v2-profile__row-meta { flex: none; font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } .v2-profile .v2-profile__note { margin: 0; font-size: 13.5px; line-height: 20px; color: var(--v2-text-secondary); } .v2-profile .v2-profile__note a { color: var(--v2-accent-text); font-weight: 600; text-decoration: none; } @media (max-width: 760px) { - .v2-profile .v2-profile__body { gap: 10px; } - /* The bar's 52px row: 24px avatar, the name, the handle; the act takes the row under it. */ - .v2-profile .v2-profile__head { min-height: 52px; gap: 0 10px; } - .v2-profile .v2-profile__head .v2-avatar { width: 24px; height: 24px; } - .v2-profile .v2-profile__head > .v2-page-head__identity { gap: 3px; padding: 8px 0; } - .v2-profile .v2-profile__list { padding: 0 12px; } + .v2-profile { gap: 10px; } + .v2-profile .v2-profile__head { flex-wrap: wrap; gap: 12px 14px; } + .v2-profile .v2-profile__follow, .v2-profile .v2-profile__edit { flex: 1 0 100%; height: 44px; font-size: 14px; } + .v2-profile .v2-profile__lane { padding: 14px 16px; } .v2-profile .v2-profile__row-text { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } /* Live behaviour the frame does not draw. */ -.v2-profile .v2-profile__follow:not(.v2-profile__follow--on):hover:not(:disabled) { border-color: var(--v2-ink-hover); background: var(--v2-ink-hover); } +.v2-profile .v2-profile__follow:hover:not(:disabled) { border-color: var(--v2-ink-hover); background: var(--v2-ink-hover); } .v2-profile .v2-profile__follow--on:hover:not(:disabled), .v2-profile .v2-profile__edit:hover { border-color: var(--v2-border-strong); background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-profile .v2-profile__follow:disabled { cursor: default; opacity: 0.7; } @@ -49,4 +44,5 @@ .v2-profile a.v2-profile__row:hover .v2-profile__row-title { color: var(--v2-accent-text); } .v2-profile .v2-profile__note a:hover { text-decoration: underline; } /* CJK never takes negative letter-spacing (TASK-055). */ -.v2-root:lang(zh) .v2-profile .v2-profile__name { letter-spacing: 0; } +.v2-root:lang(zh) .v2-profile .v2-profile__name, +.v2-root:lang(zh) .v2-profile .v2-profile__lane h2 { letter-spacing: 0; } diff --git a/frontend/src/v2/components/V2PersonProfile.tsx b/frontend/src/v2/components/V2PersonProfile.tsx index 44c7204289..59f923acab 100644 --- a/frontend/src/v2/components/V2PersonProfile.tsx +++ b/frontend/src/v2/components/V2PersonProfile.tsx @@ -4,18 +4,13 @@ import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; import { useV2Api } from '../hooks/useV2Api'; import V2Avatar from './V2Avatar'; -import V2PageHead from './V2PageHead'; import './V2PersonProfile.css'; -// The shared head bar's title and count classes, plus this page's own hook. -const HEAD_TITLE_CLASS = 'v2-feature__title v2-profile__name'; -const HEAD_META_CLASS = 'v2-profile__meta'; - /* * A person's page at /v2/profile/:id, drawn as d215 section s-profile * (docs/design/artboards/d215/index.html). Header (avatar, name, handle and - * join date, Follow) in the shared head bar, the counts as the intro line, - * then "Pods in common" and "Recent posts" as ringed lists. A lane with no rows is omitted, never an empty box. + * join date, counts, Follow), then "Pods in common" and "Recent posts" as + * bordered lanes. A lane with no rows is omitted, never an empty box. */ interface SocialProfile { @@ -121,12 +116,10 @@ const V2PersonProfile: React.FC = () => { if (state !== 'ready' || !profile) { return (
-
-

- {state === 'notFound' ? t('profile.notFound') : t('profile.loadError')}{' '} - {t('profile.backHome')} -

-
+

+ {state === 'notFound' ? t('profile.notFound') : t('profile.loadError')}{' '} + {t('profile.backHome')} +

); } @@ -169,12 +162,12 @@ const V2PersonProfile: React.FC = () => { let action: React.ReactNode = null; if (isSelf) { - action = {t('profile.editProfile')}; + action = {t('profile.editProfile')}; } else if (!profile.isBot) { action = (
); }; diff --git a/frontend/src/v2/components/V2RunTrace.css b/frontend/src/v2/components/V2RunTrace.css index 4d375ef174..9c203a797d 100644 --- a/frontend/src/v2/components/V2RunTrace.css +++ b/frontend/src/v2/components/V2RunTrace.css @@ -1,21 +1,12 @@ -/* Run trace (/v2/pods/:podId/runs/:runId), on the reference grammar (rulings - 11-04): the shared page head bar (.v2-feature__header, the route's - v2-feature--bar) with back as its lead key, the body scrolling under it in a - 760 column (.v2-feature__scroll, 12px phone gutter), cards on the D215 ring, - turn labels in mono 11.5. */ -.v2-run-trace { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; min-width: 0; color: var(--v2-text-primary); } -.v2-run-trace .v2-run-trace__body { --v2-feature-col: 760px; } -.v2-run-trace__head .v2-run-trace__back { flex: none; color: var(--v2-text-secondary); text-decoration: none; } -/* A turn's label is Settings' section label, not a display heading. */ -.v2-run-trace h2 { margin: 0; font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } +.v2-run-trace { max-width: 880px; min-width: 0; color: var(--v2-text-primary); } +.v2-run-trace h1 { font-family: var(--v2-font-display); font-size: 32px; line-height: 1.1; letter-spacing: -0.03em; margin: 16px 0; overflow-wrap: anywhere; } +.v2-run-trace h2 { font-family: var(--v2-font-display); font-size: 20px; margin: 0; } +.v2-run-trace__back { color: var(--v2-text-primary); min-height: 32px; display: inline-flex; align-items: center; } /* Under an agent reply: as quiet as a timestamp (d215 s-chat, trace link). */ .v2-run-trace-link a { color: var(--v2-text-secondary); font-size: 12.5px; text-decoration: none; min-height: 32px; display: inline-flex; align-items: center; } .v2-run-trace-link a:hover { color: var(--v2-text-primary); text-decoration: underline; } -/* A card is the D215 ring and soft shadow at r9, not a 1px border at r6. */ -.v2-run-trace__summary, .v2-run-trace__call { display: grid; gap: 8px; border-radius: var(--v2-r-9); background: var(--v2-surface); box-shadow: var(--v2-ring-card); padding: 16px; margin: 12px 0; min-width: 0; } -.v2-run-trace__summary { margin-top: 0; } -.v2-run-trace__meta { font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } -.v2-run-trace code, .v2-run-trace dd { font-family: var(--v2-font-mono); font-size: 12px; } +.v2-run-trace__summary, .v2-run-trace__call { display: grid; gap: 8px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); padding: 16px; margin: 12px 0; min-width: 0; } +.v2-run-trace__meta, .v2-run-trace code, .v2-run-trace dd { font-family: var(--v2-font-mono); font-size: 12px; } .v2-run-trace code, .v2-run-trace dd, .v2-run-trace__call strong { overflow-wrap: anywhere; } .v2-run-trace__turns { list-style: none; margin: 24px 0; padding: 0; } .v2-run-trace__foot { display: grid; gap: 4px; border-top: 1px solid var(--v2-border-soft); padding-top: 12px; margin-top: 24px; color: var(--v2-text-muted); font-size: 12px; line-height: 16px; } @@ -24,19 +15,22 @@ .v2-run-trace__turns > li { border-top: 1px solid var(--v2-border-soft); padding-top: 16px; margin-top: 16px; } .v2-run-trace dl { display: grid; grid-template-columns: 140px minmax(0, 1fr); gap: 8px; margin: 8px 0; } .v2-run-trace dd { margin: 0; } -.v2-run-trace__body a:focus-visible, .v2-run-trace-link a:focus-visible { outline: 2px solid var(--v2-accent); outline-offset: 2px; } +.v2-run-trace button { min-height: 32px; padding: 0 12px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-text-primary); cursor: pointer; } +.v2-run-trace button:focus-visible, .v2-run-trace a:focus-visible, .v2-run-trace-link a:focus-visible { outline: 2px solid var(--v2-accent); outline-offset: 2px; } .v2-run-trace-link { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } @media (max-width: 760px) { - .v2-run-trace-link a { min-height: 44px; } + .v2-run-trace__back, .v2-run-trace-link a, .v2-run-trace button { min-height: 44px; } .v2-run-trace dl { grid-template-columns: minmax(0, 1fr); gap: 4px; } .v2-run-trace dd { margin-bottom: 8px; } .v2-run-trace-link { display: grid; gap: 0; } } -/* d215 run-trace-after 390: a tighter rhythm (paragraphs flush, a turn's parts - 8px apart) keeps the foot clear of the tab bar, whose 58px .v2-feature - already reserves. The gutter is the shared 12px of .v2-feature__scroll - (rulings 11-04); the back row moved into the head bar. Desktop is unchanged. */ +/* d215 run-trace-after 390 (.mbody2--trace): 16px side gutters and no top + padding, so "Back to pod" is the first 44px row; replaces the shared 12px + feature body for this route only. The frame's tighter rhythm (paragraphs + flush, a turn's parts 8px apart) keeps the foot clear of the tab bar, + whose 58px .v2-feature already reserves. Desktop is unchanged. */ @media (max-width: 760px) { + .v2-feature--run-trace > .v2-feature__body { padding: 0 16px 16px; } .v2-feature--run-trace .v2-run-trace p { margin: 0; } .v2-feature--run-trace .v2-run-trace__turns > li { display: grid; gap: 8px; } } diff --git a/frontend/src/v2/components/V2RunTrace.tsx b/frontend/src/v2/components/V2RunTrace.tsx index 17b57c4a76..1a9bf58327 100644 --- a/frontend/src/v2/components/V2RunTrace.tsx +++ b/frontend/src/v2/components/V2RunTrace.tsx @@ -3,8 +3,6 @@ import { useTranslation } from 'react-i18next'; import { Link, useParams } from 'react-router-dom'; import { useV2Api } from '../hooks/useV2Api'; import { agentKeyFor } from '../utils/agentKey'; -import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import V2PageHead from './V2PageHead'; import './V2RunTrace.css'; export interface RunTrace { @@ -75,26 +73,10 @@ const V2RunTrace: React.FC = () => { : trace?.omittedTurns ? 'limitedTurns' : trace?.omittedToolCalls ? 'limitedCalls' : 'limitedContent'; return
- {/* The shared page head bar, back as its lead key (as the board and memory draw it). */} - -
; }; export default V2RunTrace; diff --git a/frontend/src/v2/components/V2SettingsPage.css b/frontend/src/v2/components/V2SettingsPage.css index 603c3d9f87..5e50eca2d3 100644 --- a/frontend/src/v2/components/V2SettingsPage.css +++ b/frontend/src/v2/components/V2SettingsPage.css @@ -50,6 +50,34 @@ .v2-settings__edit, .v2-settings__token-detail, .v2-settings__fold-body { padding-left: 12px; padding-right: 12px; } } +/* Phones (≤760): the frame draws these keys at their desktop size (28 small keys and + 28 icon keys, 24 small-segment options), so the rows stay 48. The primitives' 44px + phone size is undone here; each key takes its 44px touch area from an invisible + ::before centred on it (Cofounder ruling 11-04: the pick wins on look, no tap target + under 44). */ +@media (max-width: 760px) { + .v2-settings .v2p-b.v2p-b-sm { height: 28px; } + .v2-settings .v2p-ib.v2p-b-sm { width: 28px; height: 28px; } + .v2-settings .v2p-seg.v2p-seg--sm button { height: 24px; } + .v2-settings .v2p-lrow .v2p-b, + .v2-settings .v2p-seg button { position: relative; } + .v2-settings .v2p-lrow .v2p-b::before, + .v2-settings .v2p-seg button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: max(100%, 44px); + height: max(100%, 44px); + transform: translate(-50%, -50%); + background: transparent; + } + /* A segment's end options grow outward only, so a neighbour's area never covers + theirs (EN is 32 wide beside 中文). */ + .v2-settings .v2p-seg button:first-child::before { left: auto; right: 0; transform: translateY(-50%); } + .v2-settings .v2p-seg button:last-child::before { left: 0; transform: translateY(-50%); } +} + .v2-settings__account-row { min-height: 64px; } .v2-settings__avatar { diff --git a/frontend/src/v2/connectors/ConnectorsPage.tsx b/frontend/src/v2/connectors/ConnectorsPage.tsx index 917cc57163..4a2f9ac408 100644 --- a/frontend/src/v2/connectors/ConnectorsPage.tsx +++ b/frontend/src/v2/connectors/ConnectorsPage.tsx @@ -32,6 +32,10 @@ interface Pop { const ASK_HREF = 'https://github.com/Team-Commonly/commonly/issues/new?title=Connector%20request'; const MAX_CHIPS = 3; +// A tool's name is namespaced by its connector ("atlassian.search_jira"); the +// popover already names the connector, so a chip shows the part after the +// namespace, as the d215 frame draws it. The full name stays in the title. +const chipName = (name: string): string => (name.includes('.') ? name.slice(name.indexOf('.') + 1) : name); const FILTERS: Filter[] = ['all', 'channels', 'tools']; // A brand name, not translatable copy. const GITHUB_BRAND = 'GitHub'; @@ -355,7 +359,7 @@ const ConnectorsPage: React.FC = () => {

{catalogDescription(i18n, entry.installableId, entry.description)}

{names.length > 0 && (
- {names.slice(0, MAX_CHIPS).map((name) => {name})} + {names.slice(0, MAX_CHIPS).map((name) => {chipName(name)})} {names.length > MAX_CHIPS && +{names.length - MAX_CHIPS}}
)} diff --git a/frontend/src/v2/connectors/connectors.css b/frontend/src/v2/connectors/connectors.css index cd4a990e3f..77a00c89ad 100644 --- a/frontend/src/v2/connectors/connectors.css +++ b/frontend/src/v2/connectors/connectors.css @@ -79,6 +79,9 @@ .v2-cx-pop p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--v2-text-secondary); } .v2-cx-pop__chips { display: flex; flex-wrap: wrap; gap: 4px; } .v2-cx-chip { display: inline-flex; align-items: center; height: 24px; padding: 0 8px; border-radius: 7px; font: 500 11.5px/1 var(--v2-font-mono, ui-monospace, monospace); color: var(--v2-text-secondary); background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); white-space: nowrap; } +/* A chip never runs past the popover's edge: a long tool name ends in an ellipsis + (its full name is the chip's title). */ +.v2-cx-pop__chips .v2-cx-chip { display: block; max-width: 100%; box-sizing: border-box; overflow: hidden; text-overflow: ellipsis; line-height: 24px; } .v2-cx-pop__acts { display: flex; gap: 8px; flex-wrap: wrap; } .v2-cx-note { font-size: 12.5px; color: var(--v2-text-muted); } .v2-cx-error { margin: 0 0 12px; font-size: 13px; color: var(--v2-danger-text); } diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 27866a5edb..709a173cc5 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -2476,7 +2476,9 @@ body.modern-ui.v2-canvas { max-width: none; box-shadow: none; border-radius: 0; - padding: 28px 12px; + /* The d215 auth frames draw a 20px side inset on every auth page at 390. + Pages drawn after Sam's pick follow their frames (G11 ruling, 11-04). */ + padding: 28px 20px; } .v2-root :where(a, button).v2-login__oauth-btn { @@ -4373,6 +4375,25 @@ body.modern-ui.v2-canvas { .v2-pods-aside--page .v2-pods__view { padding: 0 0 10px; } +/* The phone page's Recent · All · Pinned segment is the frame's 28px (d215 sidebar + 390), not the primitives' 44px phone size, which made this bar 58 tall and pushed + the list down (#3373). Each option keeps a 44px touch area through a transparent + ::before; the segment sits alone in its row, so no neighbour's area covers it. + The bar keys and rows stay at the 44px floor (Cofounder ruling 11-04). */ +@media (max-width: 760px) { + .v2-pods-aside--page .v2-pods__view .v2p-seg button { position: relative; height: 28px; } + .v2-pods-aside--page .v2-pods__view .v2p-seg button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: max(100%, 44px); + height: max(100%, 44px); + transform: translate(-50%, -50%); + background: transparent; + } +} + .v2-pods__list { display: flex; min-height: 0; @@ -4594,6 +4615,20 @@ body.modern-ui.v2-canvas { width: 28px; height: 28px; } + + /* The pin draws at 28 inside the 44px row and takes its 44px touch area from a + transparent ::before, which stays inside the row (ruling 11-04): it ends at the + row's right edge (the pin sits 6px in), since the list clips past it. */ + .v2-root button.v2-pods__pin::before { + content: ''; + position: absolute; + top: 50%; + right: -6px; + width: 44px; + height: 44px; + transform: translateY(-50%); + background: transparent; + } } .v2-pods__create { From 6fb3018c80695ff3df0c33f902c979e5f67e378e Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Wed, 4 Nov 2026 05:09:45 -0800 Subject: [PATCH 2/3] docs(d215): the 390 sidebar frame's pod rows are 44 px, the accessibility floor (Cofounder 11-04, not from Sam's pick) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MDs9DT6tbhKm6UhsmFhLk9 --- docs/design/artboards/d215/README.md | 2 +- docs/design/artboards/d215/index.html | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/design/artboards/d215/README.md b/docs/design/artboards/d215/README.md index 0fbcb17ade..1bbeaf1a2a 100644 --- a/docs/design/artboards/d215/README.md +++ b/docs/design/artboards/d215/README.md @@ -19,7 +19,7 @@ Passing the prod check means a page matches its frame. It means the page matches | Group | Frames | Source | |---|---|---| -| **From Sam's pick** | sidebar, chat, board, agent-memory, agent-settings, settings, connectors | The seven surfaces picked on 10-31 (#3059). Later edits: the theme switch (#3175) and the open thread (#3178). #3288 (the 390 agent-settings and settings frames, brought up to the shipped pages) and #3289 (memory notes drawn as the API holds them) moved those frames toward the live page, so read them with that in mind. | +| **From Sam's pick** | sidebar, chat, board, agent-memory, agent-settings, settings, connectors | The seven surfaces picked on 10-31 (#3059). Later edits: the theme switch (#3175) and the open thread (#3178). #3288 (the 390 agent-settings and settings frames, brought up to the shipped pages) and #3289 (memory notes drawn as the API holds them) moved those frames toward the live page, so read them with that in mind. **Sidebar 390 pod rows: row height raised to the 44 px accessibility floor, Cofounder 11-04, not from Sam's pick** (36 → 44; nothing else in that frame changed). Sam can reverse it. | | **Drawn after the pick** | dm, byo, byo-handoff, connect, team, firstrun-\*, signin, signup, signup-sent, verify-email, forgot-password, reset-password, cli-authorize, invite, invite-required, profile, agent-profile, run-trace-after, chat-trace, chat-agents-away, and the agent-settings and settings state blocks | New drawings in the picked language, from 11-01 on. Sam has not reviewed them as a set. | | **Approved by Sam, 11-03** | activity-busy, activity-dayzero (`#s-activity-signal`); artifacts-busy, artifacts-dayzero (`#s-artifacts-signal`); task-record-\*; handoff-\* | Sam saw each one live-beside-proposed and answered "Build as drawn" for all four pages. Activity and Artifacts are built only from the picked surfaces' blocks, and both are scored since their builds. Task record and handoff still carry `data-state`, so the scorer skips them. | diff --git a/docs/design/artboards/d215/index.html b/docs/design/artboards/d215/index.html index aad205ba4e..cafbaea42f 100644 --- a/docs/design/artboards/d215/index.html +++ b/docs/design/artboards/d215/index.html @@ -250,6 +250,9 @@ .pod.on { background: var(--t); font-weight: 600; color: var(--ink); } .pod.on .mk { background: var(--c); color: #fff; } .pod .pin { display: none; } +/* Sidebar 390: row height raised to the 44 px accessibility floor, Cofounder 11-04, not from Sam's pick. + Eight 36 px rows can't each keep a 44 px target. Only the row height changes. */ +.frame[data-surface="sidebar"][data-vp="390"] .pod { height: 44px; } .pod.hov .pin { display: grid; position: absolute; right: 6px; background: var(--t); } .pod.hov .tm, .pod.hov .un { visibility: hidden; } From 78377862bc8b1de34d2e70fadb884141c5736375 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Wed, 4 Nov 2026 05:13:35 -0800 Subject: [PATCH 3/3] fix(v2): every small Settings key keeps a 44 px touch area on phone, not only the ones in list rows Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MDs9DT6tbhKm6UhsmFhLk9 --- frontend/src/v2/__tests__/v2-layout-invariants.test.ts | 6 +++++- frontend/src/v2/components/V2SettingsPage.css | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 58fe1e548c..21cdba387b 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -716,7 +716,11 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(touch).toContain('width: max(100%, 44px)'); expect(touch).toContain('height: max(100%, 44px)'); expect(touch).toContain('background: transparent'); - expect(phone).toContain('.v2-settings .v2p-lrow .v2p-b::before,'); + // Every key the block shrinks takes the area, wherever it sits: the profile editor, + // the token detail and the folds hold 28px keys too, not only the list rows (#3402). + expect(phone).toContain('.v2-settings .v2p-b.v2p-b-sm::before,'); + expect(phone).toContain('.v2-settings .v2p-b.v2p-b-sm,\n .v2-settings .v2p-seg button { position: relative; }'); + expect(phone).not.toMatch(/\.v2p-lrow \.v2p-b(::before)?,/); }); test('pod search shortcut hint hides on narrow and touch-only screens', () => { diff --git a/frontend/src/v2/components/V2SettingsPage.css b/frontend/src/v2/components/V2SettingsPage.css index 5e50eca2d3..c042ea8673 100644 --- a/frontend/src/v2/components/V2SettingsPage.css +++ b/frontend/src/v2/components/V2SettingsPage.css @@ -59,9 +59,11 @@ .v2-settings .v2p-b.v2p-b-sm { height: 28px; } .v2-settings .v2p-ib.v2p-b-sm { width: 28px; height: 28px; } .v2-settings .v2p-seg.v2p-seg--sm button { height: 24px; } - .v2-settings .v2p-lrow .v2p-b, + /* Every key this block shrinks gets the area, not only the ones in list rows: the + profile editor, the token detail and the folds hold 28px keys too (review, #3402). */ + .v2-settings .v2p-b.v2p-b-sm, .v2-settings .v2p-seg button { position: relative; } - .v2-settings .v2p-lrow .v2p-b::before, + .v2-settings .v2p-b.v2p-b-sm::before, .v2-settings .v2p-seg button::before { content: ''; position: absolute;