From e6ef79b69c8e561302065fcf63e15f88bd26f8a7 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 24 Oct 2026 09:42:35 -0700 Subject: [PATCH] fix(design): use Signal cobalt on remaining accent surfaces Replace retired accent in favicon, guide/feature bridges, CLI authorization, Activity compose focus and the portable selection rule. Align guide/feature accent variants, exclude guides from the legacy button reset, and use the cobalt SVG mark in guide headers. Bind guide text-button hover to Signal. Preserve stable avatars and label old preview cards historical. Align static SEO accents and browser theme color with Signal. Add focused accent regression guards with positive structural fixtures. Written by Sprint Impl, a Commonly agent Pod: https://commonly.me/pods/6a692a1be833c668acdb84cf Co-authored-by: sprint-impl Agent-Model: codex/gpt-6.1-sol/medium --- .../design-system/preview/brand-logo.html | 1 + .../design-system/preview/colors-accent.html | 1 + .../preview/colors-agent-roles.html | 1 + .../preview/colors-semantic.html | 1 + .../preview/components-buttons.html | 1 + frontend/design-system/tokens.css | 2 +- frontend/index.html | 14 +- frontend/public/favicon.svg | 10 +- frontend/src/App.css | 6 +- frontend/src/components/landing/GuidePage.tsx | 15 +-- .../components/landing/GuidesIndexPage.tsx | 9 +- frontend/src/components/landing/guideShell.ts | 16 ++- .../src/v2/__tests__/signalAccent.test.ts | 126 ++++++++++++++++++ frontend/src/v2/components/V2CliAuthorize.css | 8 +- frontend/src/v2/components/V2FeaturePage.tsx | 10 +- frontend/src/v2/v2.css | 2 +- 16 files changed, 179 insertions(+), 44 deletions(-) create mode 100644 frontend/src/v2/__tests__/signalAccent.test.ts diff --git a/frontend/design-system/preview/brand-logo.html b/frontend/design-system/preview/brand-logo.html index 5b8588f712..c3a4c8a75d 100644 --- a/frontend/design-system/preview/brand-logo.html +++ b/frontend/design-system/preview/brand-logo.html @@ -89,6 +89,7 @@ } .verdict .dot { width:7px; height:7px; border-radius:50%; background:var(--c-accent); } + diff --git a/frontend/design-system/preview/colors-accent.html b/frontend/design-system/preview/colors-accent.html index 1fcf52fafc..a23b30a40d 100644 --- a/frontend/design-system/preview/colors-accent.html +++ b/frontend/design-system/preview/colors-accent.html @@ -38,6 +38,7 @@ font-size:11px; font-weight:700; letter-spacing:0.01em; } +
diff --git a/frontend/design-system/preview/colors-agent-roles.html b/frontend/design-system/preview/colors-agent-roles.html index 9cf49d705d..2a3ebf658f 100644 --- a/frontend/design-system/preview/colors-agent-roles.html +++ b/frontend/design-system/preview/colors-agent-roles.html @@ -42,6 +42,7 @@ } .chip .dot { width:8px; height:8px; border-radius:50%; } +
diff --git a/frontend/design-system/preview/colors-semantic.html b/frontend/design-system/preview/colors-semantic.html index 3a0350afd2..b864ae103e 100644 --- a/frontend/design-system/preview/colors-semantic.html +++ b/frontend/design-system/preview/colors-semantic.html @@ -29,6 +29,7 @@ .strike { text-decoration:line-through; color:var(--c-fg-tertiary); font-family:var(--c-font-mono); font-size:11px; } +
diff --git a/frontend/design-system/preview/components-buttons.html b/frontend/design-system/preview/components-buttons.html index ca025d3809..b5eaa7b179 100644 --- a/frontend/design-system/preview/components-buttons.html +++ b/frontend/design-system/preview/components-buttons.html @@ -92,6 +92,7 @@ } .chip.is-active .chip__count { opacity:.8; } + - + - - - - + + + + diff --git a/frontend/src/App.css b/frontend/src/App.css index b25711db71..390190cb96 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -162,7 +162,7 @@ body.modern-ui:not(.v2-canvas) input:focus { box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.25); } -body.modern-ui:not(.v2-canvas) button { +body.modern-ui:not(.v2-canvas):not(.guide-canvas) button { background-color: #2563eb; color: #f8fafc; padding: 12px 20px; @@ -174,12 +174,12 @@ body.modern-ui:not(.v2-canvas) button { transition: background-color 0.2s, transform 0.1s; } -body.modern-ui:not(.v2-canvas) button:hover { +body.modern-ui:not(.v2-canvas):not(.guide-canvas) button:hover { background-color: #1d4ed8; transform: translateY(-1px); } -body.modern-ui:not(.v2-canvas) button:active { +body.modern-ui:not(.v2-canvas):not(.guide-canvas) button:active { transform: translateY(1px); } diff --git a/frontend/src/components/landing/GuidePage.tsx b/frontend/src/components/landing/GuidePage.tsx index efab082b34..9e8037a679 100644 --- a/frontend/src/components/landing/GuidePage.tsx +++ b/frontend/src/components/landing/GuidePage.tsx @@ -10,9 +10,8 @@ import { } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import commonlyLogo from '../../assets/commonly-logo.png'; import guides from '../../content/guides.json'; -import { guidePalette, useGuideCanvas } from './guideShell'; +import { guidePalette, guideTextButtonSx, useGuideCanvas } from './guideShell'; interface GuideStrongParagraph { strong: string; @@ -89,7 +88,7 @@ const GuidePage: React.FC = () => { return ( - @@ -107,11 +106,11 @@ const GuidePage: React.FC = () => { - - Commonly Logo + Commonly Logo Commonly @@ -194,7 +193,7 @@ const GuidePage: React.FC = () => { ))} {section.links && ( - {section.links.map((link) => )} + {section.links.map((link) => )} )} @@ -236,8 +235,8 @@ const GuidePage: React.FC = () => { // Compare is emitted as a static document. React Router would // otherwise hand this path to the V2 catch-all instead of // requesting build/compare/index.html. - ? - : + ? + : ))} diff --git a/frontend/src/components/landing/GuidesIndexPage.tsx b/frontend/src/components/landing/GuidesIndexPage.tsx index fca1e32419..b09a6ff705 100644 --- a/frontend/src/components/landing/GuidesIndexPage.tsx +++ b/frontend/src/components/landing/GuidesIndexPage.tsx @@ -9,9 +9,8 @@ import { } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import commonlyLogo from '../../assets/commonly-logo.png'; import guides from '../../content/guides.json'; -import { guidePalette, useGuideCanvas } from './guideShell'; +import { guidePalette, guideTextButtonSx, useGuideCanvas } from './guideShell'; interface Guide { eyebrow: string; @@ -29,11 +28,11 @@ const GuidesIndexPage: React.FC = () => { - - Commonly Logo + Commonly Logo Commonly @@ -60,7 +59,7 @@ const GuidesIndexPage: React.FC = () => { {guide.summary} - diff --git a/frontend/src/components/landing/guideShell.ts b/frontend/src/components/landing/guideShell.ts index 2acbfc6cf4..a2cc5b1058 100644 --- a/frontend/src/components/landing/guideShell.ts +++ b/frontend/src/components/landing/guideShell.ts @@ -9,11 +9,17 @@ export const guidePalette = { textPrimary: '#111827', textSecondary: '#4b5563', textTertiary: '#7b8494', - accent: '#2f6feb', - accentStrong: '#1f55c9', - accentSoft: '#e8efff', - accentText: '#2456c8', - accentDeep: '#14306f', + accent: '#1d3fd1', + accentStrong: '#1633a8', + accentSoft: '#e9edfb', + accentText: '#1d3fd1', + accentDeep: '#0f2378', +} as const; + +// Text-button state colors must not fall back to the legacy root MUI theme. +export const guideTextButtonSx = { + color: guidePalette.accentText, + '&:hover': { backgroundColor: guidePalette.accentSoft }, } as const; // Guides render outside V2's component tree, so keep their page canvas light diff --git a/frontend/src/v2/__tests__/signalAccent.test.ts b/frontend/src/v2/__tests__/signalAccent.test.ts new file mode 100644 index 0000000000..c97eb4a176 --- /dev/null +++ b/frontend/src/v2/__tests__/signalAccent.test.ts @@ -0,0 +1,126 @@ +import fs from 'fs'; +import path from 'path'; +import { guidePalette, guideTextButtonSx } from '../../components/landing/guideShell'; + +const frontend = path.resolve(__dirname, '../../..'); +const read = (name: string) => fs.readFileSync(path.join(frontend, name), 'utf8'); +const retiredAccent = /#2f6feb|rgba?\(\s*47\s*,\s*111\s*,\s*235/i; + +// These standalone assets and legacy bridges cannot all inherit .v2-root. +// Historical preview cards and stable avatar colors are outside this sweep. +describe('Signal accent on standalone and bridged surfaces', () => { + test('the standalone favicon paints all four mark shapes cobalt', () => { + const svg = new DOMParser().parseFromString(read('public/favicon.svg'), 'image/svg+xml'); + expect(svg.querySelector('parsererror')).toBeNull(); + const shapes = Array.from(svg.querySelectorAll('path, circle')); + expect(shapes).toHaveLength(4); + shapes.forEach((shape) => { + expect(shape.getAttribute(shape.tagName === 'path' ? 'stroke' : 'fill')).toBe('#1d3fd1'); + }); + }); + + // Structural source guard: these fixtures prove the absence check can detect old colors. + test.each(['color: #2F6FEB;', 'color: rgb(47, 111, 235);', 'background: rgba(47,111,235,.2);'])( + 'the structural retired-accent guard detects %s', (fixture) => { + expect(fixture).toMatch(retiredAccent); + }, + ); + + test.each([ + 'index.html', + 'public/favicon.svg', + 'src/components/landing/guideShell.ts', + 'src/v2/components/V2FeaturePage.tsx', + 'src/v2/components/V2CliAuthorize.css', + 'src/v2/v2.css', + 'design-system/tokens.css', + ])('%s excludes the retired accent and its RGB channels', (name) => { + const source = read(name); + expect(source).not.toMatch(retiredAccent); + }); + + test('the static shell uses Signal for browser chrome and SEO accent states', () => { + const html = read('index.html'); + expect(html).toContain(''); + const expected = [ + ['a', 'color', '#1d3fd1'], ['a:hover', 'color', '#0f2378'], + ['.seo-kicker', 'color', '#1d3fd1'], ['.seo-primary', 'background', '#1d3fd1'], + ['.seo-primary:hover', 'background', '#1633a8'], + ['.seo-band', 'color', '#0f2378'], ['.seo-band', 'background', '#e9edfb'], + ]; + expected.forEach(([selector, property, value]) => { + const rule = html.split(`#seo-page ${selector} {`)[1]?.split('}')[0]; + expect(rule).toContain(`${property}: ${value};`); + }); + }); + + test.each([ + ['src/components/landing/guideShell.ts', /accent:\s*'#1d3fd1'/], + ['src/v2/components/V2FeaturePage.tsx', /accent:\s*'#1d3fd1'/], + ['design-system/tokens.css', /::selection\s*\{\s*background:\s*rgba\(29, 63, 209, 0\.20\)/], + ['src/v2/v2.css', /\.v2-activity__compose:focus-within\s*\{\s*box-shadow: 0 0 0 4px rgba\(29, 63, 209, 0\.10\)/], + ])('%s retains its intended cobalt accent', (name, expected) => { + expect(read(name as string)).toMatch(expected as RegExp); + }); + + test.each([ + ['src/components/landing/guideShell.ts', ['accentStrong', 'accentSoft', 'accentText', 'accentDeep']], + ['src/v2/components/V2FeaturePage.tsx', ['accentStrong', 'accentSoft']], + ])('%s uses Signal accent variants', (name, keys) => { + const source = read(name as string); + const expected: Record = { + accentStrong: '#1633a8', accentSoft: '#e9edfb', accentText: '#1d3fd1', accentDeep: '#0f2378', + }; + (keys as string[]).forEach((key) => { + expect(source).toContain(`${key}: '${expected[key]}'`); + }); + }); + + test('guide buttons are excluded from the legacy blue reset in every interaction state', () => { + const css = read('src/App.css'); + ['', ':hover', ':active'].forEach((state) => { + expect(css).toContain(`body.modern-ui:not(.v2-canvas):not(.guide-canvas) button${state} {`); + expect(css).not.toContain(`body.modern-ui:not(.v2-canvas) button${state} {`); + }); + }); + + test.each(['GuidePage.tsx', 'GuidesIndexPage.tsx'])('%s uses the cobalt mark instead of the legacy PNG', (name) => { + const source = read(`src/components/landing/${name}`); + expect(source).toContain('src="/favicon.svg"'); + expect(source).not.toContain('commonly-logo.png'); + }); + + test('guide text buttons bind their hover tint to Signal instead of the root theme', () => { + expect(guideTextButtonSx.color).toBe('#1d3fd1'); + expect(guideTextButtonSx['&:hover'].backgroundColor).toBe(guidePalette.accentSoft); + expect(guidePalette.accentSoft).toBe('#e9edfb'); + ['GuidePage.tsx', 'GuidesIndexPage.tsx'].forEach((name) => { + const source = read(`src/components/landing/${name}`); + expect(source).toContain('sx={guideTextButtonSx}'); + expect(source).not.toContain('sx={{ color: guidePalette.accentText }}'); + }); + const guidePage = read('src/components/landing/GuidePage.tsx'); + expect(guidePage).toContain("sx={{ ...guideTextButtonSx, alignSelf: 'flex-start' }}"); + expect(guidePage).toContain('href={link.path} sx={guideTextButtonSx}>'); + expect(guidePage).toContain('sx={guideTextButtonSx} onClick={() => navigate(link.path)}>'); + }); + + test('the CLI authorize surface uses shared tokens with standalone fallbacks', () => { + const css = read('src/v2/components/V2CliAuthorize.css'); + expect(css).toContain('var(--v2-accent, #1d3fd1)'); + expect(css).toContain('var(--v2-focus-ring, 0 0 0 3px rgba(29, 63, 209, .18))'); + }); + + test.each([ + 'colors-accent.html', + 'colors-semantic.html', + 'colors-agent-roles.html', + 'brand-logo.html', + 'components-buttons.html', + ])('the legacy %s preview identifies its values as historical', (name) => { + const html = read(`design-system/preview/${name}`); + expect(html).toContain('Historical, pre-Signal reference.'); + expect(html).toContain('href="../../../docs/design/signal-identity.md"'); + }); + +}); diff --git a/frontend/src/v2/components/V2CliAuthorize.css b/frontend/src/v2/components/V2CliAuthorize.css index 90ff28b459..d457b39dbd 100644 --- a/frontend/src/v2/components/V2CliAuthorize.css +++ b/frontend/src/v2/components/V2CliAuthorize.css @@ -25,19 +25,19 @@ height: 36px; place-items: center; border-radius: 10px; - background: #2f6feb; + background: var(--v2-accent, #1d3fd1); color: #fff; font-weight: 800; } -.v2-cli-authorize__eyebrow { margin: 16px 0 0 !important; color: #2f6feb !important; font-size: 11px; font-weight: 800; letter-spacing: .11em; } +.v2-cli-authorize__eyebrow { margin: 16px 0 0 !important; color: var(--v2-accent, #1d3fd1) !important; font-size: 11px; font-weight: 800; letter-spacing: .11em; } .v2-cli-authorize__card h1 { margin: 18px 0 8px; font-size: 24px; } .v2-cli-authorize__card p { margin: 0 0 20px; color: #4b5563; line-height: 1.5; } .v2-cli-authorize__field { display: grid; gap: 7px; margin-bottom: 18px; font-size: 13px; font-weight: 700; } .v2-cli-authorize__field input { width: 100%; box-sizing: border-box; padding: 12px; border: 1px solid #d7dce7; border-radius: 9px; font: 600 18px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .08em; } -.v2-cli-authorize__field input:focus { outline: none; border-color: #2f6feb; box-shadow: 0 0 0 3px rgba(47, 111, 235, .18); } +.v2-cli-authorize__field input:focus { outline: none; border-color: var(--v2-accent, #1d3fd1); box-shadow: var(--v2-focus-ring, 0 0 0 3px rgba(29, 63, 209, .18)); } .v2-cli-authorize__primary, .v2-cli-authorize__secondary { display: inline-flex; justify-content: center; align-items: center; min-height: 42px; padding: 0 16px; border-radius: 9px; font: inherit; font-weight: 700; text-decoration: none; cursor: pointer; } -.v2-cli-authorize__primary { border: 1px solid #2f6feb; background: #2f6feb; color: #fff; } +.v2-cli-authorize__primary { border: 1px solid var(--v2-accent, #1d3fd1); background: var(--v2-accent, #1d3fd1); color: #fff; } .v2-cli-authorize__primary:disabled { cursor: wait; opacity: .65; } .v2-cli-authorize__secondary { border: 1px solid #d7dce7; background: #fff; color: #374151; } .v2-cli-authorize__facts { margin: 0 0 18px; padding: 14px; border-radius: 10px; background: #f7f7fa; } diff --git a/frontend/src/v2/components/V2FeaturePage.tsx b/frontend/src/v2/components/V2FeaturePage.tsx index 1c461843f5..ca1625e908 100644 --- a/frontend/src/v2/components/V2FeaturePage.tsx +++ b/frontend/src/v2/components/V2FeaturePage.tsx @@ -33,9 +33,9 @@ const V2_COLORS = { textPrimary: '#111827', textSecondary: '#4b5563', textMuted: '#6b7280', - accent: '#2f6feb', - accentStrong: '#1f55c9', - accentSoft: '#e8efff', + accent: '#1d3fd1', + accentStrong: '#1633a8', + accentSoft: '#e9edfb', success: '#10b981', warning: '#f4a23a', danger: '#ef4444', @@ -50,12 +50,12 @@ const v2FeatureTheme = createTheme({ mode: 'light', primary: { main: V2_COLORS.accent, - light: '#5b8dff', + light: '#e9edfb', dark: V2_COLORS.accentStrong, contrastText: V2_COLORS.surface, }, secondary: { - main: '#14306f', + main: '#0f2378', contrastText: V2_COLORS.surface, }, success: { main: V2_COLORS.success }, diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 665496d1d0..1354f28505 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -9677,7 +9677,7 @@ body.modern-ui.v2-canvas { } .v2-activity__compose:focus-within { - box-shadow: 0 0 0 4px rgba(47, 111, 235, 0.10); + box-shadow: 0 0 0 4px rgba(29, 63, 209, 0.10); } .v2-activity__compose-top,