diff --git a/docs/design/artboards/d215/cap/measure_after.js b/docs/design/artboards/d215/cap/measure_after.js index 865f4c990..b54c10ee3 100644 --- a/docs/design/artboards/d215/cap/measure_after.js +++ b/docs/design/artboards/d215/cap/measure_after.js @@ -18,7 +18,7 @@ const fs = require('fs'); const HERE = __dirname; const TARGETS = path.join(HERE, '..', 'targets.json'); -const SURFACES = ['settings', 'agent-settings', 'agent-memory', 'board', 'connectors', 'chat', 'sidebar', 'byo', 'byo-handoff', 'firstrun-welcome', 'firstrun-waiting', 'firstrun-connected', 'firstrun-error']; +const SURFACES = ['settings', 'agent-settings', 'agent-memory', 'board', 'connectors', 'chat', 'sidebar', 'byo', 'byo-handoff', 'firstrun-welcome', 'firstrun-waiting', 'firstrun-connected', 'firstrun-error', 'connect']; // byo-handoff is drawn but can't be captured on prod: reaching it issues a real key. The first-run // dialog is a latch-gated overlay, and the Scout welcome only exists in a brand-new pod, so neither is measured on prod. const PROD_SURFACES = SURFACES.filter(s => s !== 'byo-handoff' && !s.startsWith('firstrun-')); @@ -165,6 +165,7 @@ async function prod(only) { ['board', () => `/v2/pods/${pod}/board`], ['connectors', () => '/v2/connectors'], ['byo', () => '/v2/agents/byo'], + ['connect', () => '/v2/connect'], ['chat', () => `/v2/pods/${pod}`], ['sidebar', vp => (vp === '1440' ? `/v2/pods/${side}` : '/v2'), { list390: SIDEBAR_390 }], ]; diff --git a/docs/design/artboards/d215/cap/measure_after.test.js b/docs/design/artboards/d215/cap/measure_after.test.js index 33997bb53..d40f3a3e1 100644 --- a/docs/design/artboards/d215/cap/measure_after.test.js +++ b/docs/design/artboards/d215/cap/measure_after.test.js @@ -29,7 +29,7 @@ test('--only with an unknown surface throws', () => { test('the committed targets.json passes its own validation', () => { const t = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'targets.json'), 'utf8')); - assert.strictEqual(Object.keys(t).length, 26); + assert.strictEqual(Object.keys(t).length, 28); validateTargets(t); }); @@ -39,7 +39,7 @@ test('an empty or partial measurement is refused', () => { const partial = { ...t }; delete partial['board-390']; assert.throws(() => validateTargets(partial), /missing board-390/); assert.throws(() => validateTargets({ ...t, 'chat-1440': { helperWords: NaN, innerRatio: 2 } }), /invalid chat-1440/); - assert.strictEqual(EXPECTED.length, 26); + assert.strictEqual(EXPECTED.length, 28); }); // A page whose SPA shell answers 200 and then redirects to `landsOn`. Its metrics would score diff --git a/docs/design/artboards/d215/index.html b/docs/design/artboards/d215/index.html index 50eb1d71b..3f4e0adfb 100644 --- a/docs/design/artboards/d215/index.html +++ b/docs/design/artboards/d215/index.html @@ -489,6 +489,34 @@ .app.m .v2-feature--byo .v2-byo__copy { position: static; width: 100%; height: 44px; margin-top: 10px; font-size: 14px; } .app.m .v2-feature--byo .v2-byo__more > summary { min-height: 48px; padding: 8px 16px 8px 12px; } +/* connect: the page's own v2-connect__* names, in the connectors family (key-gradient Copy, panel code + blocks like BYO's command, mono labels). Scoped to .v2-connect, the page's root. */ +.v2-connect { display: flex; flex-direction: column; gap: 16px; max-width: 960px; margin: 0 auto; } +.v2-connect .v2-connect__lane { display: flex; flex-direction: column; gap: 14px; padding: 20px; border-radius: 14px; background: var(--sf); box-shadow: inset 0 0 0 1px var(--d); } +.v2-connect .v2-connect__lane-heading { display: flex; align-items: baseline; gap: 10px; } +.v2-connect .v2-connect__number { flex: none; font: 500 11.5px/1 var(--f-mono); color: var(--c-text); } +.v2-connect .v2-connect__lane h2 { margin: 0; font: 800 16px/1.25 var(--f-display); letter-spacing: -0.02em; color: var(--ink); } +.v2-connect .v2-connect__code-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } +.v2-connect .v2-connect__code-card { min-width: 0; overflow: hidden; border-radius: 12px; background: var(--p); box-shadow: inset 0 0 0 1px var(--d); } +.v2-connect .v2-connect__code-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; padding: 8px 8px 8px 14px; border-bottom: 1px solid var(--d); font: 500 11.5px/16px var(--f-mono); color: var(--mu); } +.v2-connect .v2-connect__code-card pre { margin: 0; padding: 12px 14px; font: 500 12.5px/21px var(--f-mono); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; } +.v2-connect .v2-connect__copy { all: unset; box-sizing: border-box; cursor: pointer; position: relative; flex: none; display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 0 12px; border-radius: 8px; font: 600 12.5px/1 var(--f-body); color: var(--ink); background: linear-gradient(180deg, var(--key-a), var(--key-b)); border: 1px solid var(--key-edge); box-shadow: var(--sh-key); } +.v2-connect .v2-connect__rule .v2-connect__code-head span { min-width: 0; } +.v2-connect .v2-connect__rule .v2-connect__code-head code { font: 500 11.5px/16px var(--f-mono); color: var(--ink); overflow-wrap: anywhere; } +.v2-connect .v2-connect__cursor { display: flex; flex-wrap: wrap; align-items: center; gap: 0 12px; font-size: 13px; line-height: 20px; color: var(--se); } +.v2-connect .v2-connect__link { display: inline-flex; align-items: center; min-height: 32px; font: 600 13px/20px var(--f-body); color: var(--c-text); text-decoration: none; } +.v2-connect .v2-connect__lane > .v2-connect__link, .v2-connect .v2-connect__lane > .v2-connect__secondary { align-self: flex-start; } +.v2-connect .v2-connect__actions { display: flex; flex-wrap: wrap; gap: 8px; } +.v2-connect .v2-connect__cta, .v2-connect .v2-connect__secondary { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; height: 34px; padding: 0 13px; border-radius: 10px; font: 600 13px/1 var(--f-body); white-space: nowrap; text-decoration: none; cursor: pointer; } +.v2-connect .v2-connect__cta { color: var(--on-fill); background: linear-gradient(180deg, var(--fill-a), var(--fill-b)); border: 1px solid var(--fill-edge); } +.v2-connect .v2-connect__secondary { color: var(--ink); background: linear-gradient(180deg, var(--key-a), var(--key-b)); border: 1px solid var(--key-edge); box-shadow: var(--sh-key); } +.app.m .v2-connect { gap: 10px; } +.app.m .v2-connect .v2-connect__lane { padding: 16px; } +.app.m .v2-connect .v2-connect__code-grid { grid-template-columns: minmax(0, 1fr); } +.app.m .v2-connect .v2-connect__link { min-height: 44px; } +.app.m .v2-connect .v2-connect__actions { flex-direction: column; } +.app.m .v2-connect .v2-connect__cta, .app.m .v2-connect .v2-connect__lane > .v2-connect__secondary, .app.m .v2-connect .v2-connect__secondary { align-self: stretch; height: 44px; font-size: 14px; } + /* phone */ .mtop { flex: none; height: 52px; display: flex; align-items: center; gap: 8px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--d); } .mtop h1 { font: 800 21px/1 var(--f-display); letter-spacing: -0.03em; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } @@ -659,7 +687,7 @@

Same look. Less said.

  • One screen firstRepeated blocks become a table; rare settings collapse.
  • @@ -898,6 +926,49 @@

    Same look. Less said.

    +
    +

    Connect

    /v2/connect
    +
    +
    • Class names are the page's own (v2-connect__*), so the build copies them and its tests keep passing. The rail, page head and (i) stand in for the shared shell and V2Hint.
    • Same family as Connectors. Lanes are bordered panels, each code block is a panel like BYO's command, and Copy is the same key.
    • Links and acts are controls. Links sit in a 32px row at 1440 and a 44px row at 390; the agent lane's two acts are the ink key and a plain key, full width at 390.

    Each state, one line

    MemberDrawn. Lanes 01 and 02.
    AdminNot drawn. Lane 03, Instance keys, is a third lane with one plain key.
    CopiedNot drawn. The key's label reads Copied for 1.5s.
    +
    Member · 1440

    Connect

    Connect the CLI or MCP

    Commonly CLI
    npm install -g @commonlyai/cli
    +commonly login --instance https://api.commonly.me
    Claude Code MCP
    claude mcp add commonly \
    +  -e COMMONLY_API_URL=https://api.commonly.me \
    +  -e COMMONLY_AGENT_TOKEN=cm_agent_… \
    +  -- npx -y @commonlyai/mcp
    Add Commonly to CursorAdd your agent key in Cursor’s MCP settings.
    Cursor rule · save as .cursor/rules/commonly.mdc in your project
    ---
    +description: Use the team's Commonly pod for shared context and handoffs
    +alwaysApply: true
    +---
    +
    +When a task names a Commonly pod, call `commonly_get_context` with that pod's id before you start, so you have the team's current priorities and recent messages. Use `commonly_get_messages` when you need more history.
    +
    +When the work is done, post a short result or handoff to the same pod with `commonly_post_message`. Don't post step-by-step progress.
    +
    +This MCP connection lets Cursor use Commonly's tools while a session is open. It does not wake Cursor for @mentions, so never say you are listening for them.
    +
    +Keep agent keys out of pod messages, URLs, logs and committed files.
    +
    Manage connected CLI devices

    Connect an agent you run

    +
    +
    +
    Member · 390
    Connect

    Connect the CLI or MCP

    Commonly CLI
    npm install -g @commonlyai/cli
    +commonly login --instance https://api.commonly.me
    Claude Code MCP
    claude mcp add commonly \
    +  -e COMMONLY_API_URL=https://api.commonly.me \
    +  -e COMMONLY_AGENT_TOKEN=cm_agent_… \
    +  -- npx -y @commonlyai/mcp
    Add Commonly to CursorAdd your agent key in Cursor’s MCP settings.
    Cursor rule · save as .cursor/rules/commonly.mdc in your project
    ---
    +description: Use the team's Commonly pod for shared context and handoffs
    +alwaysApply: true
    +---
    +
    +When a task names a Commonly pod, call `commonly_get_context` with that pod's id before you start, so you have the team's current priorities and recent messages. Use `commonly_get_messages` when you need more history.
    +
    +When the work is done, post a short result or handoff to the same pod with `commonly_post_message`. Don't post step-by-step progress.
    +
    +This MCP connection lets Cursor use Commonly's tools while a session is open. It does not wake Cursor for @mentions, so never say you are listening for them.
    +
    +Keep agent keys out of pod messages, URLs, logs and committed files.
    +
    Manage connected CLI devices

    Connect an agent you run

    Pods
    157Needs you
    Team
    Settings
    +
    +
    +

    First run

    /v2/pods/:id · Scout welcome + Guide dialog
    helper words welcome – → 16dialog – → 68scroll 390 – → 1.0×
    diff --git a/docs/design/artboards/d215/targets.json b/docs/design/artboards/d215/targets.json index 0d50b8e37..fe89f9530 100644 --- a/docs/design/artboards/d215/targets.json +++ b/docs/design/artboards/d215/targets.json @@ -71,6 +71,14 @@ "helperWords": 106, "innerRatio": 1.52 }, + "connect-1440": { + "helperWords": 20, + "innerRatio": 1.09 + }, + "connect-390": { + "helperWords": 20, + "innerRatio": 1.77 + }, "firstrun-welcome-1440": { "helperWords": 16, "innerRatio": 1 diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index c8ce18ebb..addf7a449 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -172,6 +172,8 @@ describe('v2 layout invariants (CSS rule presence)', () => { const aprofile = read('../agents/v2-agent-profile.css'); const agentSettings = read('../agents/v2-agent-settings.css'); const byoCss = read('../components/V2AgentBYO.css'); + // R05 lifted the Connect page onto the d215 frame (V2ConnectPage.css). + const connectCss = read('../components/V2ConnectPage.css'); const landing = read('../landing/v2-landing.css'); const podChat = read('../components/V2Thread.tsx'); const podsSidebar = read('../components/V2PodsSidebar.tsx'); @@ -301,8 +303,8 @@ describe('v2 layout invariants (CSS rule presence)', () => { // #2670: the Cursor rule card's header names an unbroken save path; without // these two declarations it pushes the Copy button out of the card at 390px. test('Cursor rule card header wraps its save path inside the card', () => { - expect(ruleBody(v2, '.v2-connect__rule .v2-connect__code-head span')).toContain('min-width: 0'); - expect(ruleBody(v2, '.v2-connect__rule .v2-connect__code-head code')).toContain('overflow-wrap: anywhere'); + expect(ruleBody(connectCss, '.v2-connect .v2-connect__rule .v2-connect__code-head span')).toContain('min-width: 0'); + expect(ruleBody(connectCss, '.v2-connect .v2-connect__rule .v2-connect__code-head code')).toContain('overflow-wrap: anywhere'); }); test('Your Team card name owns its line so the category chip cannot crush it', () => { @@ -1437,9 +1439,11 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(columnCount).not.toContain('border-radius:'); expect(columnCount).not.toContain('padding:'); - const connectCta = lastRuleBody(v2, '.v2-root .v2-connect__cta'); - expect(connectCta).toContain('background: var(--v2-ink)'); - expect(connectCta).toContain('color: var(--v2-on-ink)'); + // R05: Connect's primary act is the frame's ink key, the fill gradient + // (--v2-fill-*) with on-fill text, the same key as BYO's submit. + const connectCta = lastRuleBody(connectCss, '.v2-connect .v2-connect__cta'); + expect(connectCta).toContain('background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b))'); + expect(connectCta).toContain('color: var(--v2-on-fill)'); }); test('the board craft-pass buttons use icon components instead of unicode glyphs', () => { @@ -5541,6 +5545,54 @@ describe('pod board column scroll (TASK-698)', () => { }); }); +describe('Connect lifted from the d215 frame (R05)', () => { + const v2 = read('../v2.css'); + const connectCss = read('../components/V2ConnectPage.css'); + const connect = read('../components/V2ConnectPage.tsx'); + + test('the page imports its own sheet and v2.css carries no Connect rule', () => { + expect(connect).toContain("import './V2ConnectPage.css';"); + // `v2-connect__` and the bare root; `v2-connector*` is a different surface. + expect(v2).not.toMatch(/\.v2-connect(__|[\s,{:])/); + }); + + // Deviation from the frame: Copy is a 28px key, so on a phone an invisible + // ::after ring makes its hit area 44px. With the key's 1px border, `inset` + // measures from the 26px padding box, so -9px is what reaches 44. + test('on a phone, Copy keeps its 28px key and gains a 44px hit ring', () => { + const key = ruleBody(connectCss, '.v2-connect .v2-connect__copy'); + expect(key).toContain('height: 28px'); + expect(key).toContain('border: 1px solid var(--v2-key-edge)'); + expect(key).toContain('position: relative'); + const phone = mediaBlockContaining(connectCss, '.v2-connect .v2-connect__copy::after'); + const ring = ruleBody(connectCss, '.v2-connect .v2-connect__copy::after', { within: phone }); + expect(ring).toContain('content: ""'); + expect(ring).toContain('position: absolute'); + expect(ring).toContain('inset: -9px'); + }); + + test('on a phone, links and the agent lane acts are 44px tall', () => { + const phone = mediaBlockContaining(connectCss, '.v2-connect .v2-connect__link'); + expect(ruleBody(connectCss, '.v2-connect .v2-connect__link', { within: phone })).toContain('min-height: 44px'); + expect(selectorRuleBody(phone, '.v2-connect .v2-connect__cta, ')).toContain('height: 44px'); + // The same rule must size both keys, the CTA and the secondary, not the CTA alone. + expect(phone).toContain('.v2-connect .v2-connect__cta, .v2-connect .v2-connect__lane > .v2-connect__secondary, .v2-connect .v2-connect__secondary { align-self: stretch; height: 44px;'); + }); + + // The v2.css :lang(zh) reset (TASK-055) scans only v2.css, so the entry that + // left it with this page is held here: every negative-tracking selector in + // the sheet has a zh reset in the same sheet. + test('zh-CN: every negative-tracking Connect title is reset under :lang(zh)', () => { + const negative = [...connectCss.matchAll(/\n([^\n{}]+) \{[^}]*letter-spacing:\s*-[^;]+;/g)] + .flatMap((m) => m[1].split(',').map((sel) => sel.trim())) + .filter(Boolean); + expect(negative).toEqual(['.v2-connect .v2-connect__lane h2']); + for (const sel of negative) { + expect(ruleBody(connectCss, `.v2-root:lang(zh) ${sel}`)).toContain('letter-spacing: 0'); + } + }); +}); + describe('chat sheets lifted out of v2.css (S3)', () => { const chatSheets = ['chat-message', 'chat-pane', 'chat-composer', 'chat-inspector', 'chat-states'] .map((name) => read(`../chat/${name}.css`)); diff --git a/frontend/src/v2/__tests__/v2-type-floors.test.ts b/frontend/src/v2/__tests__/v2-type-floors.test.ts index 25096dd8a..11260e233 100644 --- a/frontend/src/v2/__tests__/v2-type-floors.test.ts +++ b/frontend/src/v2/__tests__/v2-type-floors.test.ts @@ -26,8 +26,9 @@ import { readV2Css } from '../__testkit__/v2Css'; * longer violates). Target is an empty list. */ // v2.css plus the surface files lifted out of it (D223): the chat sheets V2App -// loads, and the per-surface sheets their components import (R04 BYO, S4 board). -const CSS = [readV2Css(), ...['../components/V2AgentBYO.css', '../components/V2PodBoard.css'] +// loads, and the per-surface sheets their components import (R04 BYO, S4 board, +// R05 Connect). +const CSS = [readV2Css(), ...['../components/V2AgentBYO.css', '../components/V2PodBoard.css', '../components/V2ConnectPage.css'] .map((rel) => fs.readFileSync(path.join(__dirname, rel), 'utf8'))] .join('\n'); @@ -151,9 +152,10 @@ const ALLOWLIST: string[] = [ '.v2-runtime-row__byo', // 9.0px — no-mono-family '.v2-runtime-row__label', // 10.5px — no-mono-family // unassigned + // R05 took `.v2-connect__number` (11.0px, no family) to mono 500 11.5px + // with the Connect lift, so its entry left this list. '.v2-approval__badge', // 10.5px — mono-below-floor '.v2-approval__time', // 11.5px — no-mono-family - '.v2-connect__number', // 11.0px — no-mono-family '.v2-filter-count', // 11.0px — no-mono-family '.v2-invite-card__meta', // 11.0px — no-mono-family '.v2-invite-link', // 11.0px — no-mono-family diff --git a/frontend/src/v2/components/V2ConnectPage.css b/frontend/src/v2/components/V2ConnectPage.css new file mode 100644 index 000000000..2d3b81876 --- /dev/null +++ b/frontend/src/v2/components/V2ConnectPage.css @@ -0,0 +1,52 @@ +/* Connect (/v2/connect), lifted from the d215 frame (R05): + docs/design/artboards/d215/index.html, section s-connect, frame "connect" at + 1440 and 390. The rules below are the frame's own, same selectors and values; + only the tokens are translated to --v2-* (so light, dark and auto switch + together) and the frame's `.app.m` phone rules sit in the page's 760px phone + query. Scoped to .v2-connect, the page's root. */ +.v2-connect { display: flex; flex-direction: column; gap: 16px; max-width: 960px; margin: 0 auto; } +.v2-connect .v2-connect__lane { display: flex; flex-direction: column; gap: 14px; padding: 20px; border-radius: 14px; background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } +.v2-connect .v2-connect__lane-heading { display: flex; align-items: baseline; gap: 10px; } +.v2-connect .v2-connect__number { flex: none; font: 500 11.5px/1 var(--v2-font-mono); color: var(--v2-accent-text); } +.v2-connect .v2-connect__lane h2 { margin: 0; font: 800 16px/1.25 var(--v2-font-display); letter-spacing: -0.02em; color: var(--v2-text-primary); } +.v2-connect .v2-connect__code-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } +.v2-connect .v2-connect__code-card { min-width: 0; overflow: hidden; border-radius: 12px; background: var(--v2-bg-subtle); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } +.v2-connect .v2-connect__code-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; padding: 8px 8px 8px 14px; border-bottom: 1px solid var(--v2-border-soft); font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); } +.v2-connect .v2-connect__code-card pre { margin: 0; padding: 12px 14px; font: 500 12.5px/21px var(--v2-font-mono); color: var(--v2-text-primary); white-space: pre-wrap; overflow-wrap: anywhere; } +.v2-connect .v2-connect__copy { all: unset; box-sizing: border-box; cursor: pointer; position: relative; flex: none; display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 0 12px; border-radius: 8px; font: 600 12.5px/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); } +.v2-connect .v2-connect__rule .v2-connect__code-head span { min-width: 0; } +.v2-connect .v2-connect__rule .v2-connect__code-head code { font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-primary); overflow-wrap: anywhere; } +.v2-connect .v2-connect__cursor { display: flex; flex-wrap: wrap; align-items: center; gap: 0 12px; font-size: 13px; line-height: 20px; color: var(--v2-text-secondary); } +.v2-connect .v2-connect__link { display: inline-flex; align-items: center; min-height: 32px; font: 600 13px/20px var(--v2-font); color: var(--v2-accent-text); text-decoration: none; } +.v2-connect .v2-connect__lane > .v2-connect__link, .v2-connect .v2-connect__lane > .v2-connect__secondary { align-self: flex-start; } +.v2-connect .v2-connect__actions { display: flex; flex-wrap: wrap; gap: 8px; } +.v2-connect .v2-connect__cta, .v2-connect .v2-connect__secondary { box-sizing: border-box; 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; text-decoration: none; cursor: pointer; } +.v2-connect .v2-connect__cta { 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-connect .v2-connect__secondary { 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); } +@media (max-width: 760px) { + .v2-connect { gap: 10px; } + .v2-connect .v2-connect__lane { padding: 16px; } + .v2-connect .v2-connect__code-grid { grid-template-columns: minmax(0, 1fr); } + .v2-connect .v2-connect__link { min-height: 44px; } + .v2-connect .v2-connect__actions { flex-direction: column; } + .v2-connect .v2-connect__cta, .v2-connect .v2-connect__lane > .v2-connect__secondary, .v2-connect .v2-connect__secondary { align-self: stretch; height: 44px; font-size: 14px; } + /* Not in the frame: a 44px hit area around the 28px Copy key. With its 1px + border, `inset` measures from the padding box (26px), so -9px makes 44. */ + .v2-connect .v2-connect__copy::after { content: ""; position: absolute; inset: -9px; } +} + +/* Live behaviour the frame does not draw, kept working with prod's values. */ +.v2-connect .v2-connect__copy:hover, +.v2-connect .v2-connect__secondary:hover { + border-color: var(--v2-border-strong); + background: var(--v2-surface-hover); + color: var(--v2-text-primary); +} +.v2-connect .v2-connect__cta:hover { + border-color: var(--v2-ink-hover); + background: var(--v2-ink-hover); +} +.v2-connect .v2-connect__link:hover { text-decoration: underline; } +/* CJK never takes negative letter-spacing (TASK-055); this entry left the + v2.css :lang(zh) reset list with the rest of the page. */ +.v2-root:lang(zh) .v2-connect .v2-connect__lane h2 { letter-spacing: 0; } diff --git a/frontend/src/v2/components/V2ConnectPage.tsx b/frontend/src/v2/components/V2ConnectPage.tsx index 5f8abfe35..9fa883c5d 100644 --- a/frontend/src/v2/components/V2ConnectPage.tsx +++ b/frontend/src/v2/components/V2ConnectPage.tsx @@ -4,6 +4,7 @@ import getApiBaseUrl from '../../utils/apiBaseUrl'; import { useAuth } from '../../context/AuthContext'; import cursorMcpInstallLink from '../utils/cursorMcpInstallLink'; import { CURSOR_PROJECT_RULE, CURSOR_PROJECT_RULE_PATH } from '../utils/cursorProjectRule'; +import './V2ConnectPage.css'; type CopyKey = 'cli' | 'mcp' | 'cursorRule'; diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index cb371f880..1a18745b8 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -4854,188 +4854,6 @@ body.modern-ui.v2-canvas { } -/* ---------- Connect — CLI, MCP, and agent runtime entry points ---------- */ - -.v2-connect { - display: flex; - flex-direction: column; - gap: 16px; - max-width: 960px; - margin: 0 auto; -} - -.v2-connect__lane { - padding: 20px; - border: 1px solid var(--v2-border); - border-radius: var(--v2-radius-lg); - background: var(--v2-surface); -} - -.v2-connect__lane-heading { - display: flex; - align-items: flex-start; - gap: 12px; -} - -.v2-connect__number { - flex: 0 0 auto; - padding-top: 3px; - color: var(--v2-accent-text); - font-family: "SF Mono", ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 11px; - font-weight: 700; -} - -.v2-connect__lane h2 { - margin: 0; - color: var(--v2-text-primary); - font-size: 16px; - font-weight: 700; - letter-spacing: -0.01em; -} - -.v2-connect__lane-heading p, -.v2-connect__note { - margin: 5px 0 0; - color: var(--v2-text-secondary); - font-size: 13px; - line-height: 1.5; -} - -.v2-connect__code-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px; - margin-top: 18px; -} - -.v2-connect__code-card { - overflow: hidden; - border: 1px solid var(--v2-border-soft); - border-radius: var(--v2-radius); - background: var(--v2-bg-subtle); -} - -.v2-connect__code-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 8px 10px; - border-bottom: 1px solid var(--v2-border-soft); - color: var(--v2-text-secondary); - font-size: 12px; - font-weight: 650; -} - -.v2-connect__code-card pre { - min-height: 92px; - margin: 0; - padding: 12px; - overflow-x: auto; - color: var(--v2-text-primary); - font-family: "SF Mono", ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 12px; - line-height: 1.55; - white-space: pre-wrap; - word-break: break-word; -} - -.v2-root button.v2-connect__copy, -.v2-root .v2-connect__secondary, -.v2-root .v2-connect__cta { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 34px; - padding: 7px 11px; - border-radius: var(--v2-radius-sm); - font-size: 13px; - font-weight: 650; - text-decoration: none; - cursor: pointer; -} - -.v2-root button.v2-connect__copy, -.v2-root .v2-connect__secondary { - border: 1px solid var(--v2-border); - background: var(--v2-surface); - color: var(--v2-text-secondary); -} - -.v2-root button.v2-connect__copy:hover, -.v2-root .v2-connect__secondary:hover { - border-color: var(--v2-border-strong); - background: var(--v2-surface-hover); - color: var(--v2-text-primary); -} - -.v2-root .v2-connect__cta { - border: 1px solid var(--v2-ink); - background: var(--v2-ink); - color: var(--v2-on-ink); -} - -.v2-root .v2-connect__cta:hover:not(:disabled) { - border-color: var(--v2-ink-hover); - background: var(--v2-ink-hover); -} - -.v2-root .v2-connect__cta:disabled { - cursor: default; - opacity: 0.6; -} - -.v2-connect__note { - margin-top: 12px; -} - -.v2-connect__note code { - font-family: "SF Mono", ui-monospace, SFMono-Regular, Menlo, monospace; -} - -.v2-root a.v2-connect__link { - display: inline-block; - margin-top: 12px; - color: var(--v2-accent-text); - font-size: 13px; - font-weight: 650; - text-decoration: none; -} - -.v2-root a.v2-connect__link:hover { - text-decoration: underline; -} - -.v2-connect__cursor { - display: flex; - flex-wrap: wrap; - align-items: baseline; - gap: 4px 12px; - margin-top: 12px; - color: var(--v2-text-tertiary); - font: 400 13px/20px var(--v2-font); -} -.v2-root .v2-connect__cursor .v2-connect__link { margin-top: 0; } -.v2-connect__rule { margin-top: 12px; } -.v2-connect__rule .v2-connect__code-head span { min-width: 0; } -.v2-connect__rule .v2-connect__code-head code { - font-family: "SF Mono", ui-monospace, SFMono-Regular, Menlo, monospace; - overflow-wrap: anywhere; -} - -.v2-connect__actions { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 16px; -} - -@media (max-width: 760px) { - .v2-connect__lane { padding: 16px; } - .v2-connect__code-grid { grid-template-columns: 1fr; } -} - /* ---------- Admin · Users (waitlist + invitation codes) ---------- */ .v2-admin-users { display: flex; @@ -6601,8 +6419,7 @@ body.modern-ui.v2-canvas { .v2-root:lang(zh) .v2-activity__title, .v2-root:lang(zh) .v2-activity__section h2, .v2-root:lang(zh) .v2-activity__agent-card h3, -.v2-root:lang(zh) .v2-activity__board-row h3, -.v2-root:lang(zh) .v2-connect__lane h2 { +.v2-root:lang(zh) .v2-activity__board-row h3 { letter-spacing: 0; }