From dec1c7e2c539fb98deb403faa1956a0a70b9be78 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 14 Aug 2026 01:26:49 -0700 Subject: [PATCH 1/2] fix(onboarding): in-pod "add an agent" leads to connect, not the v1 catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The invite modal's Agent tab offered exactly one button, into /v2/agents/browse — the v1 AgentsHub (5,039 lines, living in src/components, not src/v2). Its catalog serves 21 internal and smoke-test rows beside real entries: smoke-claude, demo-target, demo-clean2, smokea50698-*, pod-architect, cl-critic, claude-on-dev, sam-local-codex, hq-support, moltbot. On 2026-08-14 a real user took that door. They installed the `claude-code` template, never started a local session, asked it the same question three times across two pods, and got silence — while a working Scout sat in the next room answering them in four seconds. Connect-your-own now leads; the catalog is demoted to a secondary link rather than removed. Note `?pod=` — V2AgentBYO reads `pod`, not `podId`, and the old link passed `podId`, so even the catalog hop lost the pod context. Copy changed with it: the hint described picking from a catalog, which set the wrong expectation before the click. Both locales — two of the users this is for wrote Chinese. The secondary button carries the `.v2-root button.` prefix: the global reset is 0-2-1 and beats a bare class, which has shipped once as a styled action rendering like plain prose (#870). Tests pin the destinations, including the `?pod=` vs `?podId=` distinction that turns a two-minute flow into an unprefilled form. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8 --- frontend/src/i18n/locales/en.json | 5 +- frontend/src/i18n/locales/zh-CN.json | 5 +- .../__tests__/V2InviteModalAgentPath.test.tsx | 83 +++++++++++++++++++ frontend/src/v2/components/V2InviteModal.tsx | 22 +++++ frontend/src/v2/v2.css | 20 +++++ 5 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 frontend/src/v2/__tests__/V2InviteModalAgentPath.test.tsx diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 6aa13d30..1f136cec 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -228,8 +228,9 @@ "unavailable": "Expiry unavailable", "expiresOn": "Expires {{date}}" }, - "agentHint": "Pick an agent from the catalog to install into this pod.", - "browseAgents": "Browse agents →", + "agentHint": "Connect an agent you already run — Claude Code, Codex, Cursor — and it joins this pod. Takes about two minutes.", + "connectOwnAgent": "Connect your own agent →", + "browseAgents": "Or browse the catalog", "errors": { "loadFailed": "Could not load invite links.", "generateFailed": "Could not generate invite.", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index c1f539e4..87828b35 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -228,8 +228,9 @@ "unavailable": "无法查看有效期", "expiresOn": "{{date}} 到期" }, - "agentHint": "从目录中选择要安装到这个 Pod 的智能体。", - "browseAgents": "浏览智能体 →", + "agentHint": "连接你已经在运行的智能体 —— Claude Code、Codex、Cursor —— 它就会加入这个 Pod。大约需要两分钟。", + "connectOwnAgent": "连接你自己的智能体 →", + "browseAgents": "或浏览目录", "errors": { "loadFailed": "邀请链接加载失败。", "generateFailed": "邀请链接生成失败。", diff --git a/frontend/src/v2/__tests__/V2InviteModalAgentPath.test.tsx b/frontend/src/v2/__tests__/V2InviteModalAgentPath.test.tsx new file mode 100644 index 00000000..d612aae7 --- /dev/null +++ b/frontend/src/v2/__tests__/V2InviteModalAgentPath.test.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import V2InviteModal from '../components/V2InviteModal'; +import en from '../../i18n/locales/en.json'; +import zhCN from '../../i18n/locales/zh-CN.json'; + +/** + * The in-pod "add an agent" path must lead to the flow that works. + * + * It used to offer exactly one button, into `/v2/agents/browse` — the v1 + * AgentsHub, whose catalog still serves 21 internal and smoke-test rows + * (`smoke-claude`, `demo-target`, `pod-architect`, `cl-critic`, …) beside real + * entries. On 2026-08-14 a real user took that door, installed the + * `claude-code` template, never started a local session, asked it the same + * question three times and got silence. They had a working Scout in the next + * room the entire time. + * + * So: connect-your-own leads, the catalog stays reachable underneath. + */ + +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useNavigate: () => mockNavigate }; +}); + +jest.mock('../hooks/useV2Api', () => ({ + useV2Api: () => ({ + get: jest.fn().mockResolvedValue({ invites: [] }), + post: jest.fn(), patch: jest.fn(), del: jest.fn(), + }), +})); + +jest.mock('../../context/AuthContext', () => ({ + useAuth: () => ({ currentUser: { _id: 'u1', username: 'sam' } }), +})); + +const POD = '6a7d154b0ec237d4b15dd28b'; + +const open = () => render( + + + , +); + +describe('the in-pod add-agent path', () => { + beforeEach(() => jest.clearAllMocks()); + + test('the primary action goes to BYO connect, with the pod prefilled', () => { + open(); + + const cta = screen.queryByText(en.inviteModal.connectOwnAgent); + expect(cta).toBeTruthy(); + fireEvent.click(cta as HTMLElement); + + // `?pod=` — V2AgentBYO reads `pod`, NOT `podId`. Getting this wrong sends + // the user to an unprefilled form, which is how a two-minute flow becomes + // a dead end. + expect(mockNavigate).toHaveBeenCalledWith(`/v2/agents/byo?pod=${POD}`); + }); + + test('the v1 catalog is demoted, not the only door', () => { + open(); + + const browse = screen.queryByText(en.inviteModal.browseAgents); + expect(browse).toBeTruthy(); + fireEvent.click(browse as HTMLElement); + + expect(mockNavigate).toHaveBeenCalledWith(`/v2/agents/browse?podId=${POD}`); + }); +}); + +describe('both locales carry the new primary action', () => { + test.each([['en', en as any], ['zh-CN', zhCN as any]])('%s', (_l, bundle) => { + // Two of the users this change is for wrote Chinese. + expect(bundle.inviteModal.connectOwnAgent).toBeTruthy(); + expect(bundle.inviteModal.browseAgents).toBeTruthy(); + // The hint must describe connecting your own agent, not picking from a + // catalog — the sentence is what sets the expectation before the click. + expect(bundle.inviteModal.agentHint).not.toMatch(/catalog|目录/); + }); +}); diff --git a/frontend/src/v2/components/V2InviteModal.tsx b/frontend/src/v2/components/V2InviteModal.tsx index 1696c6f1..02257958 100644 --- a/frontend/src/v2/components/V2InviteModal.tsx +++ b/frontend/src/v2/components/V2InviteModal.tsx @@ -337,12 +337,34 @@ const V2InviteModal: React.FC = ({ )} {tab === 'agent' && ( <> + {/* The primary action is CONNECT YOUR OWN, not the catalog. + This tab used to offer exactly one button, into + /v2/agents/browse — the v1 AgentsHub, whose catalog still + serves 21 internal and smoke-test rows alongside real + entries. A user on 2026-08-14 took that door, installed the + `claude-code` template, never started a local session, asked + it a question three times and got silence. Connecting your + own agent is the path that works and the one the honesty + surface can report on, so it leads. The catalog stays + reachable underneath rather than being the only way in. + + Note `?pod=` — V2AgentBYO reads `pod`, not `podId`. */}

{t('inviteModal.agentHint')}

+