From 094a56758a665384a0c63f8b183195ab1867ae3c Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:20:15 -0700 Subject: [PATCH 1/5] fix(v2): apply the D223 sidebar rulings, rail glyph grey and 12px sans tab labels (TASK-735) Cofounder ruled on the five deviations #3139 listed: - Rail glyphs at rest take the artboard's placeholder grey in both themes. - Phone tab labels take the artboard's sans family at the 12px sans floor. - Phone rows stay 44px, mono stays at the 11px floor, and the selected tint stays the artboard's (the cobalt mark carries selection at 7.8:1 light and 5.1:1 dark against the pane), so none of those change. Also pins the inactive tab glyph colour, from CodeRabbit on #3139. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpQmb6BbxDrAtw45bgN --- .../src/v2/__tests__/v2-layout-invariants.test.ts | 10 ++++++++++ frontend/src/v2/v2.css | 11 +++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 5214c436c..542940420 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -458,6 +458,16 @@ describe('v2 layout invariants (CSS rule presence)', () => { test('phone tab labels keep text contrast while the glyphs take the artboard grey (D223)', () => { expect(v2).toContain('.v2-root button.v2-mobile-tabs__item:not(.v2-mobile-tabs__item--active) .v2-mobile-tabs__label { color: var(--v2-text-muted); }'); + // The glyph half: inactive tabs take the artboard's placeholder grey. + const phone = blockContaining(v2, '@media (max-width: 760px)', '.v2-mobile-tabs__label {'); + expect(ruleBody(v2, '.v2-root button.v2-mobile-tabs__item', { within: phone })).toContain('color: var(--v2-text-placeholder)'); + // D223 ruling: the artboard's sans family at the 12px sans floor. + expect(ruleBody(v2, '.v2-mobile-tabs__label', { within: phone })).toContain('font: 500 12px/14px var(--v2-font)'); + }); + + test('rail glyphs at rest are the artboard placeholder grey in both themes (D223 ruling)', () => { + expect(v2).toContain('--v2-rail-icon: #98a2b3;'); + expect(v2.match(/--v2-rail-icon: #5f6979;/g) || []).toHaveLength(2); }); test('the phone pods page leads with the artboard title bar, search behind an icon (D223)', () => { diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 687c308d1..4905ea056 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -32,7 +32,8 @@ body.modern-ui.v2-canvas { /* TASK-701: inactive rail glyphs, join-radio outlines (3:1 on white and #f2f4f7) and the neutral selected pod row. Dark values sit in both dark sets below. */ - --v2-rail-icon: #374151; + /* D223 ruling (Cofounder, 11-01): the artboard's --ph, exactly. */ + --v2-rail-icon: #98a2b3; --v2-control-outline: #667085; /* D223 (TASK-735): the artboard's .pod.on in both themes, a neutral tint row whose mark alone turns cobalt. */ @@ -273,7 +274,7 @@ body.modern-ui.v2-canvas { --v2-status-blocked-edge: #f97066; --v2-text-primary: #e8ebf1; --v2-message-text: #e8ebf1; - --v2-rail-icon: #aeb6c4; + --v2-rail-icon: #5f6979; --v2-control-outline: #737e92; --v2-row-active: #1e2531; --v2-row-active-text: #e8ebf1; @@ -347,7 +348,7 @@ body.modern-ui.v2-canvas { --v2-status-blocked-edge: #f97066; --v2-text-primary: #e8ebf1; --v2-message-text: #e8ebf1; - --v2-rail-icon: #aeb6c4; + --v2-rail-icon: #5f6979; --v2-control-outline: #737e92; --v2-row-active: #1e2531; --v2-row-active-text: #e8ebf1; @@ -12816,8 +12817,10 @@ body.modern-ui.v2-canvas { .v2-root button.v2-mobile-tabs__item--active .v2-mobile-tabs__icon { color: var(--v2-ink); } + /* D223 ruling (Cofounder, 11-01): the artboard's sans family, at the + 12px sans floor rather than its 10px. */ .v2-mobile-tabs__label { - font: 500 11px/12px var(--v2-font-mono); + font: 500 12px/14px var(--v2-font); white-space: nowrap; } From 29bbd3434e2cafe58cbaf089741078f4c368f16d Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:39:53 -0700 Subject: [PATCH 2/5] docs(v2): the rail glyph comment states its measured contrast (TASK-735) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpQmb6BbxDrAtw45bgN --- frontend/src/v2/v2.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 4905ea056..f5e535dee 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -32,7 +32,9 @@ body.modern-ui.v2-canvas { /* TASK-701: inactive rail glyphs, join-radio outlines (3:1 on white and #f2f4f7) and the neutral selected pod row. Dark values sit in both dark sets below. */ - /* D223 ruling (Cofounder, 11-01): the artboard's --ph, exactly. */ + /* D223 ruling (Cofounder, 11-01): the rail glyph is the artboard's --ph, + exactly, which puts it below the 3:1 above (2.34:1 on #f2f4f7); the + active glyph and the hover labels carry the rail's meaning. */ --v2-rail-icon: #98a2b3; --v2-control-outline: #667085; /* D223 (TASK-735): the artboard's .pod.on in both themes, a neutral tint From 79e191376394b622e3e3caf0a3e18ab8902c745a Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:45:58 -0700 Subject: [PATCH 3/5] fix(v2): rail and tab icons keep 3:1, the nearest grey to the artboard (TASK-735) Cofounder ruling 11-01: follow the artboard unless a value fails a measured standard, then take the nearest value that passes. The artboard grey is 2.34:1 on the light shell; #848c9b is 3.07:1. Inactive phone tab icons move from the placeholder (2.58:1 on white) to the same token. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpQmb6BbxDrAtw45bgN --- .../src/v2/__tests__/v2-layout-invariants.test.ts | 8 +++++--- frontend/src/v2/v2.css | 12 +++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 542940420..801526f7a 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -460,13 +460,15 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(v2).toContain('.v2-root button.v2-mobile-tabs__item:not(.v2-mobile-tabs__item--active) .v2-mobile-tabs__label { color: var(--v2-text-muted); }'); // The glyph half: inactive tabs take the artboard's placeholder grey. const phone = blockContaining(v2, '@media (max-width: 760px)', '.v2-mobile-tabs__label {'); - expect(ruleBody(v2, '.v2-root button.v2-mobile-tabs__item', { within: phone })).toContain('color: var(--v2-text-placeholder)'); + // Icons are controls, so they take the rail's 3:1 grey, not the artboard's placeholder. + expect(ruleBody(v2, '.v2-root button.v2-mobile-tabs__item', { within: phone })).toContain('color: var(--v2-rail-icon)'); // D223 ruling: the artboard's sans family at the 12px sans floor. expect(ruleBody(v2, '.v2-mobile-tabs__label', { within: phone })).toContain('font: 500 12px/14px var(--v2-font)'); }); - test('rail glyphs at rest are the artboard placeholder grey in both themes (D223 ruling)', () => { - expect(v2).toContain('--v2-rail-icon: #98a2b3;'); + test('rail glyphs at rest are the nearest grey to the artboard that clears 3:1 (D223 ruling)', () => { + // The artboard's #98a2b3 measures 2.34:1 on the light shell; #848c9b is 3.07:1. + expect(v2).toContain('--v2-rail-icon: #848c9b;'); expect(v2.match(/--v2-rail-icon: #5f6979;/g) || []).toHaveLength(2); }); diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index f5e535dee..f95945afb 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -32,10 +32,10 @@ body.modern-ui.v2-canvas { /* TASK-701: inactive rail glyphs, join-radio outlines (3:1 on white and #f2f4f7) and the neutral selected pod row. Dark values sit in both dark sets below. */ - /* D223 ruling (Cofounder, 11-01): the rail glyph is the artboard's --ph, - exactly, which puts it below the 3:1 above (2.34:1 on #f2f4f7); the - active glyph and the hover labels carry the rail's meaning. */ - --v2-rail-icon: #98a2b3; + /* D223 ruling (Cofounder, 11-01): the artboard's --ph (#98a2b3) measures + 2.34:1 on #f2f4f7, under the 3:1 above, so this is the nearest grey that + passes: 3.07:1 on #f2f4f7, 3.38:1 on white. */ + --v2-rail-icon: #848c9b; --v2-control-outline: #667085; /* D223 (TASK-735): the artboard's .pod.on in both themes, a neutral tint row whose mark alone turns cobalt. */ @@ -12801,7 +12801,9 @@ body.modern-ui.v2-canvas { gap: 3px; padding: 0 6px; background: transparent; - color: var(--v2-text-placeholder); + /* Icons are controls: the rail's 3:1 grey, not the artboard's 2.58:1 + placeholder (D223 standing rule, 11-01). The label sets its own colour. */ + color: var(--v2-rail-icon); } .v2-root button.v2-mobile-tabs__item--active, From 8c5960db5bc734e484609e2aca32c626f4ea2eb8 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:52:17 -0700 Subject: [PATCH 4/5] fix(v2): the rail fits its 38px glyphs, so the nav stops scrolling sideways (TASK-735) #3139 widened the nav glyph to the artboard's 38px inside a 56px rail with 10px side padding, a 35px content box. The staging smoke failed on it (rail no h-scroll @1440px, over+2). 8px padding gives 39px; measured 0 on staging with the rule injected. The test now derives the fit from the rail width, padding and glyph width. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpQmb6BbxDrAtw45bgN --- frontend/src/v2/__tests__/v2-layout-invariants.test.ts | 9 ++++++++- frontend/src/v2/v2.css | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 801526f7a..1998365ac 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -334,7 +334,14 @@ describe('v2 layout invariants (CSS rule presence)', () => { const brand = ruleBody(v2, '.v2-rail__brand-icon'); const item = ruleBody(v2, '.v2-root button.v2-rail__item'); const active = ruleBody(v2, '.v2-root button.v2-rail__item--active'); - expect(rail).toContain('padding: 6px 10px'); + expect(rail).toContain('padding: 6px 8px'); + // The nav glyph must fit the rail's content box or the nav scrolls + // sideways (staging smoke, "rail no h-scroll @1440px"): rail width, less + // the pane's 1px border, less both side paddings. + const railW = Number(/--v2-rail-w:\s*(\d+)px/.exec(v2)?.[1]); + const padX = Number(/padding: \d+px (\d+)px/.exec(rail)?.[1]); + const itemW = Number(/(?:^|\s)width: (\d+)px/.exec(item)?.[1]); + expect(railW - 1 - 2 * padX).toBeGreaterThanOrEqual(itemW); expect(brand).toContain('width: 36px'); expect(brand).toContain('height: 36px'); expect(item).toContain('width: 38px'); diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index f95945afb..2945370be 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -806,7 +806,10 @@ body.modern-ui.v2-canvas { .v2-rail { height: 100%; - padding: 6px 10px; + /* 56px rail - 1px pane border - 2 x 8px = 39px, room for the 38px nav glyph. + At 10px the nav was 35px wide and scrolled sideways by 2px, which the + staging smoke's "rail no h-scroll @1440px" check caught after #3139. */ + padding: 6px 8px; display: flex; flex-direction: column; gap: 8px; From 5cd51d7d935f5e8f4511495b1023939576ca97c2 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:55:33 -0700 Subject: [PATCH 5/5] fix(v2): measure the rail grey on the rail's own surface (TASK-735) CodeRabbit (verified): the rail sits on --v2-shell-bg #eef0f4, where #848c9b is 2.97:1. #828a99 is 3.05:1 there. The rail item comment no longer claims the old token rationale. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpQmb6BbxDrAtw45bgN --- .../src/v2/__tests__/v2-layout-invariants.test.ts | 6 ++++-- frontend/src/v2/v2.css | 14 ++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 1998365ac..76cac0e42 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -474,8 +474,10 @@ describe('v2 layout invariants (CSS rule presence)', () => { }); test('rail glyphs at rest are the nearest grey to the artboard that clears 3:1 (D223 ruling)', () => { - // The artboard's #98a2b3 measures 2.34:1 on the light shell; #848c9b is 3.07:1. - expect(v2).toContain('--v2-rail-icon: #848c9b;'); + // Measured on the rail's own surface, --v2-shell-bg #eef0f4: the + // artboard's #98a2b3 is 2.26:1 and #828a99 is 3.05:1. + expect(v2).toContain('--v2-shell-bg: #eef0f4;'); + expect(v2).toContain('--v2-rail-icon: #828a99;'); expect(v2.match(/--v2-rail-icon: #5f6979;/g) || []).toHaveLength(2); }); diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 2945370be..d0c0d9192 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -32,10 +32,11 @@ body.modern-ui.v2-canvas { /* TASK-701: inactive rail glyphs, join-radio outlines (3:1 on white and #f2f4f7) and the neutral selected pod row. Dark values sit in both dark sets below. */ - /* D223 ruling (Cofounder, 11-01): the artboard's --ph (#98a2b3) measures - 2.34:1 on #f2f4f7, under the 3:1 above, so this is the nearest grey that - passes: 3.07:1 on #f2f4f7, 3.38:1 on white. */ - --v2-rail-icon: #848c9b; + /* D223 ruling (Cofounder, 11-01): the artboard's --ph (#98a2b3) is under + 3:1 on the rail's own surface, --v2-shell-bg #eef0f4 (2.26:1), so this is + the nearest grey that passes there: 3.05:1, and 3.47:1 on the white tab + bar. Dark #5f6979 is 3.48:1 on the dark shell and 3.17:1 on its surface. */ + --v2-rail-icon: #828a99; --v2-control-outline: #667085; /* D223 (TASK-735): the artboard's .pod.on in both themes, a neutral tint row whose mark alone turns cobalt. */ @@ -898,8 +899,9 @@ body.modern-ui.v2-canvas { align-items: center; justify-content: center; gap: 0; - /* D223 (TASK-735): the artboard's .nav, a bare 38px glyph at rest. The - glyph colour stays the TASK-701 token, so dark keeps its lift. */ + /* D223 (TASK-735): the artboard's .nav, a bare 38px glyph at rest, coloured + by --v2-rail-icon: the nearest grey to the artboard's that clears 3:1 on + the shell (values and ratios at the token). */ width: 38px; min-height: 38px; height: 38px;