Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions frontend/src/v2/__tests__/signalAccent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,29 @@ describe('Signal accent on standalone and bridged surfaces', () => {
expect(guidePage).toContain('sx={guideTextButtonSx} onClick={() => navigate(link.path)}>');
});

test('the CLI authorize surface uses shared tokens with standalone fallbacks', () => {
test('the CLI authorize surface mounts in .v2-root and reads tokens only', () => {
// TASK-682: wrapped in .v2-root so the tokens resolve and the dark theme reaches it.
const tsx = read('src/v2/components/V2CliAuthorize.tsx');
expect(tsx).toContain('<main className="v2-root v2-cli-authorize">');
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))');
expect(css).toContain('background: var(--v2-accent-fill)');
expect(css).toContain('box-shadow: var(--v2-focus-ring)');
// No fallbacks inside var() (it merges after #3058), and no card shadow.
expect(css).not.toMatch(/var\(--v2-[a-z-]+,/);
expect(css).not.toContain('0 12px 32px');
// The only hexes are the warning-ink token's light and dark values.
expect(css.replace(/\/\*[\s\S]*?\*\//g, '').replace(/var\(--v2-[a-z-]+, #[0-9a-f]+\)/gi, '').match(/#[0-9a-f]{3,8}\b/gi)).toEqual(['#7c4a03', '#f4c27a', '#f4c27a']);
// The input must outrank `body.modern-ui:not(.v2-canvas) input` (0,2,2).
expect(css).toContain('.v2-root.v2-cli-authorize .v2-cli-authorize__field input {');
expect(css).toContain('color: var(--v2-warning-ink) !important');
expect(css).toContain(':root[data-v2-theme="dark"] .v2-cli-authorize { --v2-warning-ink: #f4c27a; }');
expect(css).toContain(':root:not([data-v2-theme="light"]) .v2-cli-authorize { --v2-warning-ink: #f4c27a; }');
// App.css's legacy button rule is (0,3,2), its :hover (0,4,2): the controls must out-specify both.
expect(css).toContain('.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__primary {');
expect(css).toContain('.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__primary:hover');
expect(css).toContain('.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__secondary {');
// Inputs set their own ground, or dark leaves them browser white.
expect(css).toMatch(/\.v2-cli-authorize__field input \{[^}]*background: var\(--v2-surface\)/);
});

test.each([
Expand Down
45 changes: 37 additions & 8 deletions frontend/src/v2/__tests__/v2-layout-invariants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@ const bareElementSelectors = (css: string): string[] => {
};

describe('v2 layout invariants (CSS rule presence)', () => {
test('/cli/authorize scrolls inside the .v2-root shell on a short viewport (TASK-682)', () => {
// .v2-root is height 100vh + overflow hidden; the page must override it or
// the lower controls (Deny / Authorize) are unreachable on a short screen.
const cli = fs.readFileSync(path.join(__dirname, '../components/V2CliAuthorize.css'), 'utf8');
const root = /^\.v2-root\.v2-cli-authorize \{([^}]*)\}/m.exec(cli)?.[1] ?? '';
expect(root).toContain('overflow-y: auto');
expect(root).toContain('display: grid');
expect(root).not.toContain('place-items: center');
expect(/^\.v2-cli-authorize__card \{([^}]*)\}/m.exec(cli)?.[1]).toContain('margin: auto');
// The legacy V1 form rule (0,2,2) would paint the form #111827.
expect(cli).toContain('.v2-root.v2-cli-authorize .v2-cli-authorize__card form {');
});

const v2 = read('../v2.css');
const tokens = read('../../../design-system/tokens.css');
const aprofile = read('../agents/v2-agent-profile.css');
Expand Down Expand Up @@ -1013,8 +1026,8 @@ describe('v2 layout invariants (CSS rule presence)', () => {
// The footer CTA sits on the accent-deep band, so it keeps main's white fill.
// An ink fill there reads as a dark hole (review 5673841827).
const footerPrimary = ruleBody(aprofile, '.v2-root .v2-aprofile__footer-cta .v2-aprofile__btn--primary');
expect(footerPrimary).toContain('background: #fff');
expect(footerPrimary).toContain('color: var(--v2-accent)');
expect(footerPrimary).toContain('background: var(--v2-on-accent, #fff)');
expect(footerPrimary).toContain('color: var(--v2-accent-fill, var(--v2-accent))');

const avatarFocus = ruleBody(aprofile, '.v2-root button.v2-aprofile__avatar-button:focus-visible');
expect(avatarFocus).toContain('outline: none');
Expand Down Expand Up @@ -4144,18 +4157,18 @@ describe('TASK-167 row A — the landing hero onto Signal', () => {
expect(bar).not.toContain('<Mark');
// The lang trigger is a v2.css <button>; the override has to carry the
// .v2-root prefix plus the band class to outrank the rail pin.
expect(landing).toContain('.v2-root .v2-landing__bar--band button.v2-lang-switch__trigger { color: #ffffff; }');
expect(landing).toContain('.v2-root .v2-landing__bar--band button.v2-lang-switch__trigger { color: var(--v2-on-accent, #ffffff); }');
});

test('the rotator is white on the band — the accent is cobalt and would vanish', () => {
const rotator = selectorRuleBody(landing, '.v2-landing__rotator {');
expect(rotator).toContain('color: #ffffff');
expect(rotator).toContain('color: var(--v2-on-accent, #ffffff)');
expect(rotator).not.toContain('var(--v2-accent)');
});

test('the demo caption is on the band, above the frame, and the old lift is gone', () => {
const cap = selectorRuleBody(landing, '.v2-landing__demo-cap {');
expect(cap).toContain('color: #ffffff');
expect(cap).toContain('color: var(--v2-on-accent, #ffffff)');
expect(cap).toContain('margin: 44px 0 0');
expect(cap).not.toContain('var(--v2-font-mono)');
expect(mediaAt(landing, '@media (max-width: 760px) {')).toContain('.v2-landing__demo-cap { margin: 28px 0 0; }');
Expand Down Expand Up @@ -4419,8 +4432,24 @@ describe('TASK-167 row C — How it works, Why open source and What you get onto
expect(adapter).not.toContain('background');
expect(adapter).not.toContain('border');
const code = topLevelRuleBody(landing, '.v2-landing__code');
expect(code).toContain('background: var(--v2-text-primary)');
expect(code).toContain('color: #ffffff');
// Code tokens, not text-primary or ink: both flip light in dark (TASK-682).
expect(code).toContain('background: var(--v2-code-bg)');
expect(code).toContain('color: var(--v2-code-text)');
expect(code).toContain('outline: 1px solid var(--v2-code-edge)');
expect(code).toContain('outline-offset: -1px');
expect(topLevelRuleBody(landing, '.v2-landing__code-comment')).toContain('color: var(--v2-code-muted)');
// Light keeps today's slab exactly; dark is a dark slab with an edge.
const lightCode = /^\.v2-landing \{([^}]*--v2-code-bg[^}]*)\}/m.exec(landing)?.[1] ?? '';
expect(lightCode).toContain('--v2-code-bg: #101828');
expect(lightCode).toContain('--v2-code-text: #ffffff');
expect(lightCode).toContain('--v2-code-muted: #98a2b3');
expect(lightCode).toContain('--v2-code-edge: var(--v2-code-bg)');
const darkCode = topLevelRuleBody(landing, ':root[data-v2-theme="dark"] .v2-landing');
expect(darkCode).toContain('--v2-code-bg: #0b0e13');
expect(darkCode).toContain('--v2-code-text: #e8ebf1');
expect(darkCode).toContain('--v2-code-muted: #8b94a5');
expect(darkCode).toContain('--v2-code-edge: #364051');
expect(landing).toContain(':root:not([data-v2-theme="light"]) .v2-landing {');
expect(code).toContain('font-size: 14px');
expect(code).toContain('line-height: 22px');
// 1 1 auto, so the three blocks share the row's height instead of each
Expand All @@ -4430,7 +4459,7 @@ describe('TASK-167 row C — How it works, Why open source and What you get onto
expect(code).toContain('overflow-x: auto');
// The comment line is still the same text; it is a span now so the copied
// bytes stay identical while the colour can differ.
expect(topLevelRuleBody(landing, '.v2-landing__code-comment')).toContain('#98a2b3');
expect(topLevelRuleBody(landing, '.v2-landing__code-comment')).toContain('var(--v2-code-muted)');
expect(landingTsx).toContain('v2-landing__code-comment');
expect(landingTsx).toContain('commonly agent run my-agent');
expect(landingTsx).toContain('# joins pods, replies to @mentions');
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/v2/agents/v2-agent-profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
background: var(--v2-accent-deep); border-radius: var(--v2-radius-lg);
color: rgba(255, 255, 255, 0.85); font-size: 15px;
}
.v2-root .v2-aprofile__footer-cta .v2-aprofile__btn--primary { background: #fff; color: var(--v2-accent); border-color: #fff; }
.v2-root .v2-aprofile__footer-cta .v2-aprofile__btn--primary { background: var(--v2-on-accent, #fff); color: var(--v2-accent-fill, var(--v2-accent)); border-color: var(--v2-on-accent, #fff); }
.v2-root .v2-aprofile__footer-cta .v2-aprofile__btn--primary:hover { background: rgba(255,255,255,0.9); }

@media (max-width: 760px) {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/v2/agents/v2-persona-catalog.css
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@
padding: 7px 16px;
font-size: 13px;
font-weight: 600;
color: #fff;
background: var(--v2-accent);
color: var(--v2-on-accent, #fff);
background: var(--v2-accent-fill, var(--v2-accent));
border: none;
border-radius: var(--v2-radius-md, 8px);
cursor: pointer;
Expand Down
78 changes: 52 additions & 26 deletions frontend/src/v2/components/V2CliAuthorize.css
Original file line number Diff line number Diff line change
@@ -1,54 +1,80 @@
.v2-cli-authorize {
min-height: 100vh;
/* Mounted inside .v2-root (TASK-682), so every value below is a token and
follows the dark theme. Two sets of resets compete with these rules:
.v2-root's button/link/input resets at (0,1,0)-(0,1,1), and App.css's
legacy `body.modern-ui:not(.v2-canvas) input` (0,2,2) and
`...:not(.guide-canvas) button` (0,3,2) with :hover at (0,4,2). The
controls carry .v2-root and .v2-cli-authorize (plus __card on buttons and
the hover rules) so they win on specificity, not on stylesheet order.
No fallbacks inside var(): this file merges strictly after #3058, which
defines --v2-accent-fill and --v2-on-accent (UX Lead, msg 120679). */
/* Warning ink (UX Lead, pod msg 120615): #7c4a03 vanishes on dark
warning-soft. Scoped here with the same dark selectors as v2.css until the
token has a second consumer. */
.v2-cli-authorize { --v2-warning-ink: #7c4a03; }
:root[data-v2-theme="dark"] .v2-cli-authorize { --v2-warning-ink: #f4c27a; }
@media (prefers-color-scheme: dark) {
:root:not([data-v2-theme="light"]) .v2-cli-authorize { --v2-warning-ink: #f4c27a; }
}

/* .v2-root is the app shell: height 100vh, overflow hidden, flex column.
This route is a page, not a shell, so it scrolls itself; the card centres
with margin:auto, which never pushes it past the top edge on a short
viewport the way place-items:center would. */
.v2-root.v2-cli-authorize {
height: 100vh;
overflow-y: auto;
display: grid;
place-items: center;
padding: 24px;
background: #f8f8fb;
color: #111827;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--v2-bg-subtle);
color: var(--v2-text-primary);
font-family: var(--v2-font);
}

.v2-cli-authorize__card {
width: min(100%, 460px);
margin: auto;
padding: 32px;
border: 1px solid #e5e7eb;
border-radius: 18px;
background: #fff;
box-shadow: 0 12px 32px rgba(17, 24, 39, 0.08);
border: 1px solid var(--v2-border);
border-radius: var(--v2-radius-lg);
background: var(--v2-surface);
}

.v2-cli-authorize__card form { margin: 0; padding: 0; border: 0; background: transparent; box-shadow: none; }
/* Outranks App.css's legacy `body.modern-ui:not(.v2-canvas) form` (0,2,2). */
.v2-root.v2-cli-authorize .v2-cli-authorize__card form { max-width: none; margin: 0; padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; }

.v2-cli-authorize__mark {
display: grid;
width: 36px;
height: 36px;
place-items: center;
border-radius: 10px;
background: var(--v2-accent, #1d3fd1);
color: #fff;
border-radius: var(--v2-radius-lg);
background: var(--v2-accent-fill);
color: var(--v2-on-accent);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
font-weight: 800;
}

.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__eyebrow { margin: 16px 0 0 !important; color: var(--v2-accent-text) !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__card p { margin: 0 0 20px; color: var(--v2-text-secondary); 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: 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 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; }
.v2-root.v2-cli-authorize .v2-cli-authorize__field input { width: 100%; box-sizing: border-box; margin: 0; padding: 12px; border: 1px solid var(--v2-border-strong); border-radius: var(--v2-radius); background: var(--v2-surface); color: var(--v2-text-primary); font: 600 18px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .08em; }
.v2-root.v2-cli-authorize .v2-cli-authorize__field input:focus { outline: none; border-color: var(--v2-accent); box-shadow: var(--v2-focus-ring); }
.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__primary, .v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__secondary { display: inline-flex; justify-content: center; align-items: center; min-height: 42px; padding: 0 16px; border-radius: var(--v2-radius); font: inherit; font-weight: 700; text-decoration: none; cursor: pointer; }
.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__primary { border: 1px solid var(--v2-accent-fill); background: var(--v2-accent-fill); color: var(--v2-on-accent); }
.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__primary:disabled { cursor: wait; opacity: .65; }
.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__secondary { border: 1px solid var(--v2-border-strong); background: var(--v2-surface); color: var(--v2-text-secondary); }
.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__primary:hover, .v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__primary:active { background: var(--v2-accent-fill); transform: none; }
.v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__secondary:hover, .v2-root.v2-cli-authorize .v2-cli-authorize__card .v2-cli-authorize__secondary:active { background: var(--v2-surface-hover); transform: none; }
.v2-cli-authorize__facts { margin: 0 0 18px; padding: 14px; border-radius: var(--v2-radius-lg); background: var(--v2-bg-subtle); }
.v2-cli-authorize__facts div { display: flex; justify-content: space-between; gap: 18px; padding: 5px 0; }
.v2-cli-authorize__facts dt { color: #6b7280; }
.v2-cli-authorize__facts dt { color: var(--v2-text-muted); }
.v2-cli-authorize__facts dd { margin: 0; font-weight: 600; text-align: right; }
.v2-cli-authorize__warning { padding: 10px 12px; border-radius: 8px; background: #fdefdc; color: #7c4a03 !important; font-size: 13px; }
.v2-cli-authorize__warning { padding: 10px 12px; border-radius: var(--v2-radius); background: var(--v2-warning-soft); color: var(--v2-warning-ink) !important; font-size: 13px; }
.v2-cli-authorize__actions { display: flex; justify-content: flex-end; gap: 10px; }

@media (max-width: 480px) {
.v2-cli-authorize { padding: 16px; align-items: start; }
.v2-root.v2-cli-authorize { padding: 16px; }
.v2-cli-authorize__card { margin: 0 auto auto; }
.v2-cli-authorize__card { padding: 24px 20px; }
.v2-cli-authorize__facts div { display: grid; gap: 2px; }
.v2-cli-authorize__facts dd { text-align: left; }
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/v2/components/V2CliAuthorize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const V2CliAuthorize: React.FC = () => {
const next = `/cli/authorize${code ? `?code=${encodeURIComponent(code)}` : ''}`;

return (
<main className="v2-cli-authorize">
<main className="v2-root v2-cli-authorize">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
<section className="v2-cli-authorize__card" aria-live="polite">
<div className="v2-cli-authorize__mark" aria-hidden="true">C</div>
<p className="v2-cli-authorize__eyebrow">CLI SIGN-IN</p>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/v2/landing/__tests__/landingInstallCopy.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,12 @@ describe('landing install line copy control (TASK-154)', () => {
// than anything drawn by the pill itself.
expect(pill).toContain('background: transparent');
expect(pill).toContain('border: 1px solid transparent');
expect(pill).toContain('color: #ffffff');
expect(pill).toContain('color: var(--v2-on-accent, #ffffff)');
// The hover state needs the same tier: at (0,2,0) it lost to the (0,2,1) reset.
expect(ruleBody(`${PILL_SELECTOR}:hover`)).toContain('background: rgba(255, 255, 255, 0.12)');
// Focus is invisible against cobalt without an explicit ring, and the
// band's own hairline is too faint to serve as one.
expect(ruleBody(`${PILL_SELECTOR}:focus-visible`)).toContain('outline: 2px solid #ffffff');
expect(ruleBody(`${PILL_SELECTOR}:focus-visible`)).toContain('outline: 2px solid var(--v2-on-accent, #ffffff)');
});

it('keeps the line 46px tall and gives the pill a 44px target on phones', () => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/v2/landing/demo-workspace.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@
display: grid;
place-items: center;
border-radius: var(--v2-radius-sm);
background: var(--v2-accent);
color: #ffffff;
background: var(--v2-accent-fill, var(--v2-accent));
color: var(--v2-on-accent, #ffffff);
font-family: var(--v2-font-display);
font-weight: 800;
font-size: 18px;
Expand Down
Loading
Loading