diff --git a/frontend/src/__tests__/timingClaims.test.ts b/frontend/src/__tests__/timingClaims.test.ts index 3ef2898c0c..bcee9be227 100644 --- a/frontend/src/__tests__/timingClaims.test.ts +++ b/frontend/src/__tests__/timingClaims.test.ts @@ -15,8 +15,8 @@ describe('timing claims in the copy', () => { // at 2.54 s (install 1.83 + provision 0.63); walk 4: 2.47 s. // QA report qa-hosted-oob-5, timing table. test('click to live says "a few seconds", in both languages', () => { - expect(en.agentByo.mode.hostedMeta).toBe('Live in a few seconds · Beta: {{turns}} turns a day'); - expect(en.agentByo.compact.lead).toBe('Live in a few seconds.'); + expect(en.agentByo.mode.hostedMeta).toBe('Nothing to install. Live in a few seconds. Beta: {{turns}} turns a day.'); + expect(en.agentByo.compact.lead).toBe('Live in a few seconds. Beta: {{agents}} hosted agent per account, {{turns}} turns a day.'); expect(zh.agentByo.mode.hostedMeta).toContain('几秒'); expect(zh.agentByo.compact.lead).toContain('几秒'); }); diff --git a/frontend/src/i18n/__tests__/i18n.test.ts b/frontend/src/i18n/__tests__/i18n.test.ts index 1687a57b2b..a90d183c4e 100644 --- a/frontend/src/i18n/__tests__/i18n.test.ts +++ b/frontend/src/i18n/__tests__/i18n.test.ts @@ -133,8 +133,8 @@ describe('i18n configuration', () => { }); it('uses a Chinese comma in the BYO machine hint', () => { - const hint = i18n.t('agentByo.mode.machineHint', { lng: 'zh-CN' }); - expect(hint).toBe('在你已添加的电脑上运行,你的文件、你的 CLI'); + const hint = i18n.t('agentByo.mode.machineMeta', { lng: 'zh-CN' }); + expect(hint).toBe('在你已添加的电脑上运行,用你的文件和 CLI。需要那台电脑运行 Commonly 守护进程。'); expect(hint).not.toContain('—'); }); }); diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 15d85018a9..fc064be479 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -134,7 +134,7 @@ "avatar": { "regenerate": "Regenerate", "uploadPhoto": "Upload photo", - "description": "No face and no skin tone until you choose one. Regenerate for another, or upload a photo.", + "description": "Regenerate for another, or upload a photo.", "descriptionAfterChoice": "Regenerate for another, or upload a photo.", "uploadTitle": "Upload photo", "cropPreview": "Avatar crop preview", @@ -495,7 +495,7 @@ "people": "Invite people", "agent": "Add agent" }, - "peopleHint": "Create a link anyone with a Commonly account can use to join this pod.", + "peopleHint": "Anyone with the link can join.", "podMember": "Pod member", "options": { "expiresAfter": "Expires after", @@ -513,7 +513,6 @@ "generate": "Generate invite link", "generating": "Generating…", "newLinkLabel": "New invite link", - "newLinkHint": "The new link also appears under Active links below.", "activeLinks": "Active links", "loadingLinks": "Loading links…", "noActiveLinks": "No active invite links.", @@ -541,7 +540,7 @@ "unavailable": "Expiry unavailable", "expiresOn": "Expires {{date}}" }, - "agentHint": "Add an agent — Claude Code or Codex, or run one here — and it joins this pod. Takes about two minutes.", + "agentHint": "Claude Code, Codex, or run one here.", "connectOwnAgent": "Connect your own agent →", "errors": { "loadFailed": "Could not load invite links.", @@ -1627,7 +1626,7 @@ "description": "A running agent answers @mentions in its pod.", "form": { "nameLabel": "Agent name", - "nameHint": "Lower-case letters, digits, and dashes. It posts under this name.", + "nameHint": "Lowercase letters, digits and dashes.", "podLabel": "Pod it joins", "loadingPods": "Loading pods…", "machineLabel": "Computer", @@ -1682,7 +1681,7 @@ "cursorInstall": "Add to Cursor", "cursorInstallHint": "The link adds Commonly with a placeholder key. Replace it in Cursor’s MCP settings with the key above.", "memory": "Bring its memory (optional)", - "claudeCodeBody": "Gives your own Claude Code sessions Commonly’s tools. It does not make {{name}} answer @mentions; the command above does.", + "claudeCodeBody": "Gives your Claude Code sessions Commonly’s tools. Answering @mentions needs the command above.", "claudeCodeBodyOwn": "Gives your own Claude Code sessions Commonly’s tools.", "claudeCodeBodyWindows": "Paste both lines in PowerShell. They give your own Claude Code sessions on this machine Commonly’s tools.", "claudeCodeMain": "Let Claude Code use the pod in your own sessions" @@ -1738,17 +1737,12 @@ "mode": { "label": "Where should it run?", "hosted": "Run it here", - "hostedHint": "Commonly runs it for you. Nothing to install. Beta: {{turns}} turns a day.", "byo": "Use my own agent", - "byoHint": "Your Claude Code or Codex, on your computer.", - "onCommonly": "on Commonly", - "yours": "your machine", - "hostedMeta": "Live in a few seconds · Beta: {{turns}} turns a day", - "byoMeta": "One command in Terminal", + "hostedMeta": "Nothing to install. Live in a few seconds. Beta: {{turns}} turns a day.", + "byoMeta": "Your Claude Code or Codex, on your computer. One command in Terminal.", "moreAbout": "More about {{mode}}", "machine": "On my computer", - "machineHint": "Runs on a computer you've added — your files, your CLIs", - "machineMeta": "Needs the Commonly daemon running there" + "machineMeta": "Runs on a computer you've added, with your files and CLIs. Needs the Commonly daemon running there." }, "hosted": { "offerAction": "Run it here instead", @@ -1798,7 +1792,7 @@ }, "compact": { "title": "Run an agent here", - "lead": "Live in a few seconds.", + "lead": "Live in a few seconds. Beta: {{agents}} hosted agent per account, {{turns}} turns a day.", "joins": "It joins {{pod}} as {{name}}.", "change": "Change", "limits": "Beta: {{agents}} hosted agent per account, {{turns}} turns a day.", @@ -2224,7 +2218,7 @@ }, "own": { "title": "Your own agent", - "text": "Claude Code, Codex, or anything that speaks the protocol. One command on your machine; it keeps its identity here.", + "text": "Claude Code or Codex, from your own machine.", "command": "npx @commonlyai/cli agent attach claude", "setup": "Set it up" } @@ -2306,7 +2300,6 @@ "codeExpired": "The enable code expired.", "connect": "Connect", "connectChannel": "Connect a channel", - "connectChannelHint": "Choose a channel and the pod it should join.", "copied": "Copied", "copyCommand": "Copy command", "description": "Each channel gets one agent from a pod. It answers there in its own name; the rest of the team stays behind it.", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 9984f7b282..73513cecd1 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -134,7 +134,7 @@ "avatar": { "regenerate": "换一个", "uploadPhoto": "上传照片", - "description": "选择前不会显示面部或肤色。你可以换一个造型,或上传照片。", + "description": "你可以换一个造型,或上传照片。", "descriptionAfterChoice": "你可以换一个造型,或上传照片。", "uploadTitle": "上传照片", "cropPreview": "头像裁剪预览", @@ -495,7 +495,7 @@ "people": "邀请成员", "agent": "添加智能体" }, - "peopleHint": "创建邀请链接,拥有 Commonly 账号的人都能用它加入这个 Pod。", + "peopleHint": "拿到链接的人都能加入。", "podMember": "Pod 成员", "options": { "expiresAfter": "有效期", @@ -513,7 +513,6 @@ "generate": "生成邀请链接", "generating": "正在生成…", "newLinkLabel": "新邀请链接", - "newLinkHint": "新链接也会出现在下方的有效链接中。", "activeLinks": "有效链接", "loadingLinks": "正在加载链接…", "noActiveLinks": "暂无有效邀请链接。", @@ -541,7 +540,7 @@ "unavailable": "无法查看有效期", "expiresOn": "{{date}} 到期" }, - "agentHint": "添加一个智能体 —— Claude Code 或 Codex,或在这里运行一个 —— 它就会加入这个 Pod。大约需要两分钟。", + "agentHint": "Claude Code、Codex,或在这里运行一个。", "connectOwnAgent": "连接你自己的智能体 →", "errors": { "loadFailed": "邀请链接加载失败。", @@ -1686,7 +1685,7 @@ "description": "智能体运行期间会在所在 Pod 回复 @提及。", "form": { "nameLabel": "智能体名称", - "nameHint": "仅支持小写字母、数字和连字符。它会用这个名字发言。", + "nameHint": "仅支持小写字母、数字和连字符。", "podLabel": "要加入的 Pod", "loadingPods": "正在加载 Pod…", "machineLabel": "电脑", @@ -1741,7 +1740,7 @@ "cursorInstall": "添加到 Cursor", "cursorInstallHint": "该链接会以占位密钥添加 Commonly。请在 Cursor 的 MCP 设置中将其替换为上面的密钥。", "memory": "导入它的记忆(可选)", - "claudeCodeBody": "为你自己的 Claude Code 会话加上 Commonly 的工具。它不会让 {{name}} 回复 @提及,上面那条命令才会。", + "claudeCodeBody": "为你的 Claude Code 会话加上 Commonly 的工具。回复 @提及需要上面的命令。", "claudeCodeBodyOwn": "为你自己的 Claude Code 会话加上 Commonly 的工具。", "claudeCodeBodyWindows": "两行都粘贴到 PowerShell 里运行。这样你在这台电脑上的 Claude Code 会话就能用上 Commonly 的工具。", "claudeCodeMain": "让 Claude Code 在你自己的会话里使用这个 Pod" @@ -1797,17 +1796,12 @@ "mode": { "label": "在哪里运行?", "hosted": "在这里运行", - "hostedHint": "由 Commonly 代为运行,无需安装。测试期:每天{{turns}}轮。", "byo": "用我自己的智能体", - "byoHint": "你电脑上的 Claude Code 或 Codex。", - "onCommonly": "在 Commonly 上", - "yours": "你的电脑", - "hostedMeta": "几秒即可上线 · 测试期每天{{turns}}轮", - "byoMeta": "只需一条终端命令", + "hostedMeta": "无需安装,几秒即可上线。测试期每天{{turns}}轮。", + "byoMeta": "你电脑上的 Claude Code 或 Codex,只需一条终端命令。", "moreAbout": "更多关于{{mode}}的信息", "machine": "在我的电脑上", - "machineHint": "在你已添加的电脑上运行,你的文件、你的 CLI", - "machineMeta": "需要那台电脑运行 Commonly 守护进程" + "machineMeta": "在你已添加的电脑上运行,用你的文件和 CLI。需要那台电脑运行 Commonly 守护进程。" }, "hosted": { "offerAction": "改为在这里运行", @@ -1857,7 +1851,7 @@ }, "compact": { "title": "在这里运行智能体", - "lead": "几秒后即可上线。", + "lead": "几秒后即可上线。测试期:每个账号{{agents}}个托管智能体,每天{{turns}}轮。", "joins": "它会以 {{name}} 的身份加入 {{pod}}。", "change": "更改", "limits": "测试期:每个账号{{agents}}个托管智能体,每天{{turns}}轮。", @@ -2214,7 +2208,7 @@ }, "own": { "title": "你自己的智能体", - "text": "Claude Code、Codex,或任何支持该协议的工具。在你的机器上运行一条命令,它的身份保留在这里。", + "text": "你自己电脑上的 Claude Code 或 Codex。", "command": "npx @commonlyai/cli agent attach claude", "setup": "开始设置" } @@ -2296,7 +2290,6 @@ "codeExpired": "启用码已过期。", "connect": "连接", "connectChannel": "连接频道", - "connectChannelHint": "选择频道,以及它要加入的 Pod。", "copied": "已复制", "copyCommand": "复制命令", "description": "每个频道从一个 Pod 接入一个智能体。它会在那里以自己的名义回复,其余团队成员都在幕后。", diff --git a/frontend/src/v2/__tests__/V2AgentBYOHosted.test.tsx b/frontend/src/v2/__tests__/V2AgentBYOHosted.test.tsx index 52b9bb3cf5..a7db131b17 100644 --- a/frontend/src/v2/__tests__/V2AgentBYOHosted.test.tsx +++ b/frontend/src/v2/__tests__/V2AgentBYOHosted.test.tsx @@ -56,6 +56,11 @@ const mockGet = ({ configured, status }) => { }); }; +// The hosted mode card is titled "Run it here" too, so the act button is the +// "Run it here" button that is not a mode toggle (no aria-pressed). +const actButton = () => screen.queryAllByRole('button', { name: /^run it here$/i }) + .find((b) => !b.hasAttribute('aria-pressed')) ?? null; + const renderPage = () => render( @@ -79,12 +84,12 @@ describe('V2AgentBYO — Run it here', () => { await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); expect(screen.getByRole('group', { name: 'Where should it run?' })).toBeInTheDocument(); - expect(screen.getByText('Live in a few seconds · Beta: 200 turns a day')).toBeInTheDocument(); + expect(screen.getByText('Nothing to install. Live in a few seconds. Beta: 200 turns a day.')).toBeInTheDocument(); expect(document.body.textContent).not.toContain('{{'); expect(screen.getByTestId('byo-mode-hosted')).toHaveAttribute('aria-pressed', 'false'); expect(screen.getByTestId('byo-mode-byo')).toHaveAttribute('aria-pressed', 'false'); expect(screen.getByTestId('byo-mode-unchosen')).toHaveTextContent('Choose where it runs first'); - expect(screen.queryByRole('button', { name: /^run it here$/i })).not.toBeInTheDocument(); + expect(actButton()).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /get my connect command/i })).not.toBeInTheDocument(); fireEvent.click(screen.getByTestId('byo-mode-byo')); @@ -148,9 +153,9 @@ describe('V2AgentBYO — Run it here', () => { renderPage(); await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); - expect(screen.getAllByText(/beta: 200 turns a day/i)).toHaveLength(2); + expect(screen.getAllByText(/beta: 200 turns a day/i)).toHaveLength(1); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); await screen.findByTestId('byo-hosted-result'); const installCall = axios.post.mock.calls.find(([url]) => url === '/api/registry/install'); @@ -172,7 +177,7 @@ describe('V2AgentBYO — Run it here', () => { await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - const act = screen.getByRole('button', { name: /^run it here$/i }); + const act = actButton(); const addComputer = await screen.findByTestId('byo-add-computer'); // eslint-disable-next-line no-bitwise expect(act.compareDocumentPosition(addComputer) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); @@ -223,12 +228,13 @@ describe('V2AgentBYO — Run it here', () => { const compact = await screen.findByTestId('byo-hosted-compact'); expect(screen.getByRole('heading', { name: 'Run an agent here' })).toBeInTheDocument(); await waitFor(() => expect(compact).toHaveTextContent('It joins Workspace as sam-agent.')); - await waitFor(() => expect(compact).toHaveTextContent('Beta: 1 hosted agent per account, 200 turns a day.')); + // The beta cap lives in the page-head (i), not in the body. + expect(screen.getByText(/Beta: 1 hosted agent per account, 200 turns a day\./)).toBeInTheDocument(); // None of the full page's choice and setup copy is in the way. expect(screen.queryByTestId('byo-mode-hosted')).not.toBeInTheDocument(); expect(screen.queryByTestId('byo-add-computer')).not.toBeInTheDocument(); - const act = screen.getByRole('button', { name: /^run it here$/i }); + const act = actButton(); await waitFor(() => expect(act).toBeEnabled()); fireEvent.click(act); await screen.findByTestId('byo-hosted-result'); @@ -237,6 +243,18 @@ describe('V2AgentBYO — Run it here', () => { window.history.pushState({}, '', '/'); }); + // Before availability resolves, or when hosting is not configured, the lead + // must not interpolate caps: the (i) would read "Beta: undefined ...". + test('the compact hosted lead never shows undefined caps when hosting is not configured', async () => { + mockGet({ configured: false, status: { runtime: { lastPollAt: 1 } } }); + renderAt('/v2/agents/byo?pod=p1&mode=hosted'); + + await screen.findByTestId('byo-hosted-compact'); + expect(screen.queryByText(/undefined/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Beta:/)).not.toBeInTheDocument(); + window.history.pushState({}, '', '/'); + }); + test('"Other ways to connect" opens the full page with Run it here still chosen', async () => { mockGet({ configured: true, status: { runtime: { lastPollAt: 1 } } }); renderAt('/v2/agents/byo?pod=p1&mode=hosted'); @@ -266,7 +284,8 @@ describe('V2AgentBYO — Run it here', () => { , ); - const act = await screen.findByRole('button', { name: /^run it here$/i }); + await waitFor(() => expect(actButton()).not.toBeNull()); + const act = actButton(); await waitFor(() => expect(act).toBeEnabled()); fireEvent.click(act); fireEvent.click(await screen.findByRole('button', { name: /go to your pod/i })); @@ -290,10 +309,10 @@ describe('V2AgentBYO — Run it here', () => { // The compact page shows at once, with the act disabled until the // instance answers that it can host. expect(await screen.findByTestId('byo-hosted-compact')).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /^run it here$/i })).toBeDisabled(); + expect(actButton()).toBeDisabled(); await act(async () => { answerAvailability({ data: { configured: true, caps: { agentsPerUser: 1, turnsPerDay: 30 } } }); }); - await waitFor(() => expect(screen.getByRole('button', { name: /^run it here$/i })).toBeEnabled()); + await waitFor(() => expect(actButton()).toBeEnabled()); window.history.pushState({}, '', '/'); }); @@ -303,7 +322,7 @@ describe('V2AgentBYO — Run it here', () => { await waitFor(() => expect(screen.getByRole('button', { name: /get my connect command/i })).toBeEnabled()); expect(screen.queryByTestId('byo-mode-hosted')).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: /^run it here$/i })).not.toBeInTheDocument(); + expect(actButton()).not.toBeInTheDocument(); window.history.pushState({}, '', '/'); }); @@ -323,7 +342,7 @@ describe('V2AgentBYO — Run it here', () => { // A persona link says what to hire, not where it runs. expect(screen.getByTestId('byo-mode-hosted')).toHaveAttribute('aria-pressed', 'false'); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); await screen.findByTestId('byo-hosted-result'); const installCall = axios.post.mock.calls.find(([url]) => url === '/api/registry/install'); expect(installCall[1]).toMatchObject({ @@ -341,7 +360,7 @@ describe('V2AgentBYO — Run it here', () => { renderPage(); await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); await screen.findByTestId('byo-hosted-starting'); await act(async () => { jest.advanceTimersByTime(4000); }); @@ -361,7 +380,7 @@ describe('V2AgentBYO — Run it here', () => { renderPage(); await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); expect(await screen.findByTestId('byo-hosted-running')).toBeInTheDocument(); expect(screen.queryByTestId('byo-hosted-starting')).not.toBeInTheDocument(); @@ -375,7 +394,7 @@ describe('V2AgentBYO — Run it here', () => { renderPage(); await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); // No timer advanced: the first check runs on its own. expect(await screen.findByTestId('byo-hosted-running')).toBeInTheDocument(); @@ -389,7 +408,7 @@ describe('V2AgentBYO — Run it here', () => { renderPage(); await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); await screen.findByTestId('byo-hosted-starting'); await act(async () => { jest.advanceTimersByTime(4000); }); @@ -411,7 +430,7 @@ describe('V2AgentBYO — Run it here', () => { renderPage(); await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); await screen.findByText(/already have a hosted agent \(beta allows 1\)/i); expect(axios.post.mock.calls.some(([url]) => url === '/api/hosted/provision')).toBe(false); }); @@ -427,7 +446,7 @@ describe('V2AgentBYO — Run it here', () => { renderPage(); await waitFor(() => expect(screen.getByTestId('byo-mode-hosted')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('byo-mode-hosted')); - fireEvent.click(screen.getByRole('button', { name: /^run it here$/i })); + fireEvent.click(actButton()); await screen.findByText(/maya-agent already holds your hosted agent slot \(beta allows 1\)/i); expect(axios.post.mock.calls.some(([url]) => url === '/api/hosted/provision')).toBe(false); }); diff --git a/frontend/src/v2/__tests__/V2SettingsPage.test.tsx b/frontend/src/v2/__tests__/V2SettingsPage.test.tsx index 73e73a97f5..7176f681c4 100644 --- a/frontend/src/v2/__tests__/V2SettingsPage.test.tsx +++ b/frontend/src/v2/__tests__/V2SettingsPage.test.tsx @@ -322,11 +322,11 @@ describe('V2SettingsPage', () => { expect(regenerateButton).toHaveFocus(); }); - test('keeps the no-face explanation for Paper and shortens it for a picked face or photo', () => { + test('shows the same short avatar hint for Paper and for a picked face', () => { (axios.get as jest.Mock).mockResolvedValue({ data: { hasToken: false } }); const { rerender } = renderSettings({ ...auth.currentUser, profilePicture: 'default' }); - expect(screen.getByText('No face and no skin tone until you choose one. Regenerate for another, or upload a photo.')) + expect(screen.getByText('Regenerate for another, or upload a photo.')) .toBeInTheDocument(); rerender( @@ -334,7 +334,7 @@ describe('V2SettingsPage', () => {
, ); - expect(screen.getByText('No face and no skin tone until you choose one. Regenerate for another, or upload a photo.')) + expect(screen.getByText('Regenerate for another, or upload a photo.')) .toBeInTheDocument(); rerender( @@ -343,8 +343,6 @@ describe('V2SettingsPage', () => { , ); expect(screen.getByText('Regenerate for another, or upload a photo.')).toBeInTheDocument(); - expect(screen.queryByText('No face and no skin tone until you choose one. Regenerate for another, or upload a photo.')) - .not.toBeInTheDocument(); }); test('Regenerate reports a profile save error without losing the current avatar', async () => { diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 7f96ca6c82..31a678ab93 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -3193,6 +3193,9 @@ describe('Bring your own agent onto Signal (TASK-166)', () => { // The 1px-per-side compensation is derived from the base shorthand, so it is // pinned to it rather than restated as a magic number. expect(base.padding).toBe('16px 18px 14px'); + // With only the title left, a card sizes to its text unless it stretches + // across its cell (the chooser collapsed at 1440, 720 and 390 without this). + expect(base['align-self']).toBe('stretch'); const pressed = cssDeclarations( ruleBody(v2, '.v2-root button.v2-byo__mode[aria-pressed="true"]'), @@ -3208,21 +3211,6 @@ describe('Bring your own agent onto Signal (TASK-166)', () => { ).toContain('box-shadow: var(--v2-focus-ring)'); }); - test('the mode kicker is mono 500 at 11px, lowercase, cobalt only when chosen (item 3)', () => { - const kicker = cssDeclarations(ruleBody(v2, '.v2-byo__mode-kicker')); - expect(kicker.font).toBe('500 11px/16px var(--v2-font-mono)'); - expect(kicker['text-transform']).toBeUndefined(); - expect(kicker.color).toBe('var(--v2-text-muted)'); - const chosen = cssDeclarations( - ruleBody(v2, '.v2-root button.v2-byo__mode[aria-pressed="true"] .v2-byo__mode-kicker'), - ); - expect(chosen.color).toBe('var(--v2-accent-text)'); - // The caps were data, not only CSS, so the data is where they must not come - // back from: a lowercase transform over zh-CN copy would be the wrong fix. - expect(en).toContain('"onCommonly": "on Commonly"'); - expect(en).toContain('"yours": "your machine"'); - }); - test('the preview label is mono 500 at 11px with no caps transform (item 4)', () => { const label = cssDeclarations(ruleBody(v2, '.v2-byo__preview-label')); expect(label.font).toBe('500 11px/16px var(--v2-font-mono)'); diff --git a/frontend/src/v2/components/V2AgentBYO.tsx b/frontend/src/v2/components/V2AgentBYO.tsx index e324763a2e..8eb80142f8 100644 --- a/frontend/src/v2/components/V2AgentBYO.tsx +++ b/frontend/src/v2/components/V2AgentBYO.tsx @@ -1112,7 +1112,7 @@ const V2AgentBYO: React.FC = () => { })()} hint={(() => { if (issued) return undefined; - if (showCompactHosted && !hosted) return t('agentByo.compact.lead'); + if (showCompactHosted && !hosted && hosting?.configured) return t('agentByo.compact.lead',{ agents: hosting?.caps.agentsPerUser, turns: hosting?.caps.turnsPerDay }); if (showCompactByo) return t('agentByo.compactByo.lead'); return t('agentByo.description'); })()} @@ -1159,11 +1159,6 @@ const V2AgentBYO: React.FC = () => { > {submitting ? t('agentByo.actions.starting') : t('agentByo.actions.runHere')} - {hosting?.configured && ( -

- {t('agentByo.compact.limits', { agents: hosting.caps.agentsPerUser, turns: hosting.caps.turnsPerDay })} -

- )} {t('agentByo.mode.hostedMeta', { turns: hosting.caps.turnsPerDay })} @@ -1256,9 +1247,7 @@ const V2AgentBYO: React.FC = () => { onClick={() => chooseMode('machine')} data-testid="byo-mode-machine" > - {t('agentByo.mode.yours')} {t('agentByo.mode.machine')} - {t('agentByo.mode.machineHint')} {t('agentByo.mode.machineMeta')} @@ -1273,9 +1262,7 @@ const V2AgentBYO: React.FC = () => { onClick={() => chooseMode('byo')} data-testid="byo-mode-byo" > - {t('agentByo.mode.yours')} {t('agentByo.mode.byo')} - {t('agentByo.mode.byoHint')} {t('agentByo.mode.byoMeta')} diff --git a/frontend/src/v2/components/V2ConnectorsPage.tsx b/frontend/src/v2/components/V2ConnectorsPage.tsx index f4032ee912..635bb14fb8 100644 --- a/frontend/src/v2/components/V2ConnectorsPage.tsx +++ b/frontend/src/v2/components/V2ConnectorsPage.tsx @@ -1690,7 +1690,6 @@ const V2ConnectorsPage: React.FC = () => { - {(pods === null || pods.length > 0) &&

{t('connectors.connectChannelHint', { defaultValue: 'Choose a channel and the pod it should join.' })}

} )} @@ -1707,7 +1706,6 @@ const V2ConnectorsPage: React.FC = () => { diff --git a/frontend/src/v2/components/V2InviteModal.tsx b/frontend/src/v2/components/V2InviteModal.tsx index 8a8697068b..bcac6f174a 100644 --- a/frontend/src/v2/components/V2InviteModal.tsx +++ b/frontend/src/v2/components/V2InviteModal.tsx @@ -264,9 +264,6 @@ const V2InviteModal: React.FC = ({ {copiedToken === 'new' ? t('common.copied') : t('common.copy')} -

- {t('inviteModal.newLinkHint')} -

)} {error &&
{error}
} diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index a27973a1b0..a4cf11cd9a 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -6480,6 +6480,7 @@ body.modern-ui.v2-canvas { display: flex; flex-direction: column; align-items: flex-start; + align-self: stretch; gap: 5px; text-align: left; padding: 16px 18px 14px; @@ -6490,21 +6491,6 @@ body.modern-ui.v2-canvas { cursor: pointer; transition: border-color 0.08s ease, background 0.08s ease; } -/* Signal rule 3 — meta is mono 500 at 11px and lowercase; the caps lived in - en.json, which now carries lowercase strings, so no transform is needed - (and a transform over zh-CN copy would be wrong). */ -.v2-byo__mode-kicker { - color: var(--v2-text-muted); - font: 500 11px/16px var(--v2-font-mono); - letter-spacing: 0; -} -.v2-byo__mode-kicker--quiet { color: var(--v2-text-muted); } - -/* The chosen card is the row's one cobalt mark (Signal rule 1); the modifier - above is what the untouched cards wear. */ -.v2-root button.v2-byo__mode[aria-pressed="true"] .v2-byo__mode-kicker { - color: var(--v2-accent-text); -} .v2-byo__mode-option > .v2-hint { margin-top: 0; } .v2-root button.v2-byo__mode:hover { background: var(--v2-surface-hover);