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
6 changes: 3 additions & 3 deletions frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@
"title": "Create your account",
"subtitle": "Join the shared space where agents and humans collaborate.",
"invitationCode": "Invitation code (optional)",
"invitationPlaceholder": "Unlocks hosted agents",
"invitationHint": "No code? Leave blank — you can bring your own agents and redeem a code later in Settings.",
"invitationPlaceholder": "Unlocks hiring from the catalog",
"invitationHint": "No code? Leave blank — you can still run an agent here or bring your own, and redeem a code later in Settings.",
"submit": "Create account",
"creating": "Creating account...",
"alreadyHaveAccount": "Already have an account?",
Expand Down Expand Up @@ -1791,7 +1791,7 @@
"unlocking": "Unlocking…",
"gated": "Hiring an agent from the catalog needs an invitation code during beta.",
"haveCode": "Have an invitation code?",
"success": "Hosted agents unlocked — hire your first one from the catalog."
"success": "Catalog unlocked — hire your first agent."
},
"empty": {
"title": "Build your AI team",
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@
"title": "注册",
"subtitle": "加入这个让智能体与人共同协作的空间。",
"invitationCode": "邀请码(选填)",
"invitationPlaceholder": "解锁托管智能体",
"invitationHint": "没有邀请码?可以留空——你仍可接入自己的智能体,之后再到设置中兑换邀请码。",
"invitationPlaceholder": "解锁应用市场中的智能体",
"invitationHint": "没有邀请码?可以留空——你仍可选择「在这里运行」或接入自己的智能体,之后再到设置中兑换邀请码。",
"submit": "注册",
"creating": "正在注册…",
"alreadyHaveAccount": "已有账号?",
Expand Down Expand Up @@ -1783,7 +1783,7 @@
"unlocking": "解锁中…",
"gated": "内测期间,从应用市场雇用智能体需要邀请码。",
"haveCode": "有邀请码?",
"success": "已解锁托管智能体 —— 从应用市场雇佣第一个智能体。"
"success": "已解锁应用市场——去雇用你的第一个智能体吧。"
},
"empty": {
"title": "组建你的 AI 团队",
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/v2/__tests__/V2AgentBYOHosted.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ describe('V2AgentBYO — Run it here', () => {
expect(screen.getByText(/sam-agent is live in Workspace/i)).toBeInTheDocument();
});

// QA 2026-10-16: with "Run it here" chosen at 1440x900, the act's top edge
// sat at y=882, under the "Add a computer" box, so a stranger saw a selected
// card and no button. The act now comes straight after the fields it acts on.
// jsdom has no layout, so this pins the order; the fold itself was checked
// in a real browser at 1440x900 (see the PR).
test('the act comes before the "Add a computer" box', async () => {
mockGet({ configured: true, status: { runtime: { lastPollAt: 1 } } });
renderPage();

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 addComputer = await screen.findByTestId('byo-add-computer');
// eslint-disable-next-line no-bitwise
expect(act.compareDocumentPosition(addComputer) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});

test('a connect-card deep link (?name=) keeps "Bring your own runtime" selected even when hosting is available', async () => {
mockGet({ configured: true, status: { runtime: { lastPollAt: 1 } } });
// Scout's connect_local_agent card lands here with the seat it proposed: the
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/v2/__tests__/V2Register.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,24 @@ describe('V2Register', () => {
expect(screen.queryByText(/join the Community pod/)).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'sign in now' })).not.toBeInTheDocument();
});

// QA 2026-10-16: the field said the code "Unlocks hosted agents", but
// "Run it here" works without one (hosted is capped per user, not gated), so
// a stranger with no code read hosting as locked. The code unlocks hiring from
// the catalog (the cloudAgents entitlement), and the copy says that, in both
// languages.
it('never tells a stranger the invitation code unlocks hosting', async () => {
renderRegister();

const field = await screen.findByLabelText(/invitation code/i);
expect(field).toHaveAttribute('placeholder', 'Unlocks hiring from the catalog');
expect(field.getAttribute('placeholder')).not.toMatch(/host/i);
expect(screen.getByText(/you can still run an agent here or bring your own/i)).toBeInTheDocument();

// eslint-disable-next-line global-require
const zh = require('../../i18n/locales/zh-CN.json');
const { invitationPlaceholder, invitationHint } = zh.auth.register;
expect(`${invitationPlaceholder} ${invitationHint}`).not.toMatch(/托管/);
expect(zh.yourTeam.redeem.success).not.toMatch(/托管/);
});
});
37 changes: 20 additions & 17 deletions frontend/src/v2/components/V2AgentBYO.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -811,23 +811,9 @@ const V2AgentBYO: React.FC = () => {
{t('agentByo.form.podHint')}
</span>
</label>
{machines.length === 0 && (
<div className="v2-byo__add-computer" data-testid="byo-add-computer">
<span className="v2-byo__label">{t('agentByo.addComputer.title')}</span>
<p className="v2-byo__hint">{t('agentByo.addComputer.lead')}</p>
<div className="v2-byo__add-computer-row">
<code>{DAEMON_SETUP_COMMAND}</code>
<button
type="button"
className="v2-byo__copy"
onClick={() => copy('daemon-setup', DAEMON_SETUP_COMMAND)}
>
{copied === 'daemon-setup' ? t('agentByo.addComputer.copied') : t('agentByo.addComputer.copy')}
</button>
</div>
<p className="v2-byo__hint">{t('agentByo.addComputer.after')}</p>
</div>
)}
{/* The act sits right under the fields it acts on. Below "Add a computer" it
fell under the fold at 1440x900 with "Run it here" chosen (QA 2026-10-16:
its top edge at y=882), so a stranger saw a selected card and no button. */}
{error && <div className="v2-byo__error">{error}</div>}
{mode === null ? (
// Signal §3 not yet: a dashed slot where the act will be, until a
Expand All @@ -849,6 +835,23 @@ const V2AgentBYO: React.FC = () => {
})()}
</button>
)}
{machines.length === 0 && (
<div className="v2-byo__add-computer" data-testid="byo-add-computer">
<span className="v2-byo__label">{t('agentByo.addComputer.title')}</span>
<p className="v2-byo__hint">{t('agentByo.addComputer.lead')}</p>
<div className="v2-byo__add-computer-row">
<code>{DAEMON_SETUP_COMMAND}</code>
<button
type="button"
className="v2-byo__copy"
onClick={() => copy('daemon-setup', DAEMON_SETUP_COMMAND)}
>
{copied === 'daemon-setup' ? t('agentByo.addComputer.copied') : t('agentByo.addComputer.copy')}
</button>
</div>
<p className="v2-byo__hint">{t('agentByo.addComputer.after')}</p>
</div>
)}
<p className="v2-byo__footnote">
{t('agentByo.footnote.preferCli')} <code>{CLI_INSTALL_COMMAND}</code>, {t('agentByo.footnote.then')}{' '}
<code>{CLI_INIT_COMMAND}</code>.{' '}
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/v2/components/V2Register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,11 @@ const V2Register: React.FC = () => {
/>
</label>

{/* With open registration the code is optional — it unlocks hosted
(cloud) agents. Under invite-only the page already redirected
unless a code arrived via the URL, so the field stays hidden. */}
{/* With open registration the code is optional — it unlocks hiring
from the catalog (the cloudAgents entitlement). "Run it here" needs
no code, so the copy must not say hosting is locked (QA 2026-10-16).
Under invite-only the page already redirected unless a code arrived
via the URL, so the field stays hidden. */}
{policy.loaded && !policy.inviteOnly && (
<label className="v2-login__field">
<span className="v2-login__label">{t('auth.register.invitationCode')}</span>
Expand Down
Loading