fix(settings): Antigravity's sign-in reads as an Account line, never an empty box (#30850)

Settings > Chat > Providers showed Antigravity's sign-in as a filled grey box holding only a
bright primary "Sign in" button. The host learns whether its chats are signed in only when a
chat starts, so after launch the status line had nothing to say and the box sat empty.

The area is now a plain row under the provider's name: "Account" on one line, the status on the
next, and an outline "Sign in" button. Until the host knows, the status line says chats need an
Antigravity sign-in separate from the agy terminal account.
This commit is contained in:
Brennan Benson
2026-10-19 16:35:46 -07:00
committed by GitHub
parent da0db9fc73
commit 2bdef8d375
9 changed files with 94 additions and 29 deletions
@@ -9,6 +9,7 @@ import type {
import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target'
import { callAntigravityChatSignIn } from '@/runtime/runtime-antigravity-accounts-client'
import { AntigravityChatSignInRow } from './AntigravityChatSignInRow'
import { AntigravityProviderSignInArea } from './AntigravityProviderSignInArea'
import { TooltipProvider } from '../ui/tooltip'
const url = 'https://accounts.google.com/o/oauth2/v2/auth?state=s'
@@ -157,3 +158,40 @@ it('sends the address pasted from a browser on another computer to the host', as
callbackUrl: pasted
})
})
// A host learns its sign-in only when a chat starts, so after launch it has no answer to show.
it("names the account and why it's needed before the host knows its sign-in", async () => {
const owner = { kind: 'environment', environmentId: 'provider-unanswered' } as const
render(
<AntigravityProviderSignInArea
owner={owner}
label="this computer"
install={{
kind: 'active',
subject: { target: owner, runtimeId: null, agent: 'antigravity' },
removable: true,
snapshot: {
kind: 'known',
state: {
agent: 'antigravity',
revision: 1,
available: true,
version: '1.0.0',
installedVersion: '1.0.0',
installedCurrent: true,
source: 'managed',
operation: null
}
}
}}
/>
)
await act(async () => undefined)
expect(screen.getByText('Account')).toBeTruthy()
expect(screen.getByRole('status').textContent).toBe(
'Chats need an Antigravity sign-in, separate from your agy terminal account.'
)
expect(screen.getByRole('button', { name: 'Sign in' }).hasAttribute('disabled')).toBe(false)
await act(async () => publish(owner, null, 'signedOut'))
expect(screen.getByRole('status').textContent).toMatch(/^Not signed in\./)
})
@@ -203,11 +203,13 @@ export function PastedAddressForm(props: {
}
/** One host's chat sign-in as both Settings layouts show it. `signedOut` is the line for a host
* that answered it is not signed in. */
* that answered it is not signed in; `unanswered` stands in while it has nothing else to say, as a
* host learns its sign-in only when a chat starts or asks to sign in. */
export function useAntigravityChatSignInControls(props: {
owner: RuntimeClientTarget
label: string
signedOut: string
unanswered?: string
disabled: boolean
}) {
const { owner, label } = props
@@ -235,7 +237,11 @@ export function useAntigravityChatSignInControls(props: {
/** The browser's link, while this client's sign-in waits on it. */
url: ownFlow && flow?.phase === 'waiting' ? (flow.url ?? null) : null,
line:
status === 'known' ? chatSignInLine(flow, auth, openFailed, label, props.signedOut) : null,
status === 'known'
? (chatSignInLine(flow, auth, openFailed, label, props.signedOut) ??
props.unanswered ??
null)
: null,
alert:
error ??
(status === 'readFailed' ? readFailed : null) ??
@@ -38,6 +38,10 @@ export function AntigravityProviderSignInArea(props: {
'settings.chat.providers.antigravityNotSignedIn',
'Not signed in. Chats need an Antigravity sign-in, separate from your agy terminal account.'
),
unanswered: translate(
'settings.chat.providers.antigravitySignInNeeded',
'Chats need an Antigravity sign-in, separate from your agy terminal account.'
),
disabled: antigravityChatSignInBlocked(props.install)
})
const { alert, copy, copyLabel, ownFlow, url } = signIn
@@ -45,26 +49,31 @@ export function AntigravityProviderSignInArea(props: {
const line = alert && !ownFlow ? null : signIn.line
return (
// Indented to the provider's name, past its icon.
<div className="ml-10 space-y-2 rounded-md bg-muted/40 px-3 py-2.5">
{/* The line keeps its height until the host answers, so nothing shifts when it does. */}
<div className="flex min-h-6 items-center gap-2">
<div
role="status"
className="flex min-w-0 flex-1 items-center gap-1.5 text-xs text-muted-foreground"
>
{ownFlow ? <LoadingSpinner className="size-3 shrink-0" /> : null}
<span className="min-w-0 break-words">
{line}
{line && alert ? ' ' : null}
{alert && (
<span role="alert" className="text-destructive">
{alert}
</span>
)}
</span>
<div className="ml-10 space-y-2">
<div className="flex items-center gap-3">
<div className="min-w-0 flex-1">
<p className="text-xs font-medium leading-none">
{translate('settings.chat.providers.antigravityAccount', 'Account')}
</p>
{/* The line keeps its height until the host answers, so nothing shifts when it does. */}
<div
role="status"
className="mt-1 flex min-h-4 items-center gap-1.5 text-xs text-muted-foreground"
>
{ownFlow ? <LoadingSpinner className="size-3 shrink-0" /> : null}
<span className="min-w-0 break-words">
{line}
{line && alert ? ' ' : null}
{alert && (
<span role="alert" className="text-destructive">
{alert}
</span>
)}
</span>
</div>
</div>
{ownFlow ? (
<>
<div className="flex shrink-0 items-center gap-1">
{url && (
<Button size="xs" variant="ghost" onClick={() => void copy.copyText()}>
{copy.status === 'copied' ? <Check /> : <Copy />}
@@ -74,12 +83,12 @@ export function AntigravityProviderSignInArea(props: {
<Button size="xs" variant="ghost" disabled={signIn.pending} onClick={signIn.cancel}>
{translate('accounts.antigravity.chatCancel', 'Cancel')}
</Button>
</>
</div>
) : (
<Button
size="xs"
// Signed in, the button only switches account, so it steps back from the primary.
variant={signIn.action === 'changeAccount' ? 'ghost' : 'default'}
// Signed in, the button only switches account, so it steps back further.
variant={signIn.action === 'changeAccount' ? 'ghost' : 'outline'}
disabled={signIn.signInDisabled}
onClick={signIn.start}
>
+3 -1
View File
@@ -217,7 +217,9 @@
"install": "Install",
"toggleLabel": "Open {{value0}} as a chat",
"antigravityDescription": "Antigravity chats run a server Orca downloads to this host, separate from the agy CLI.",
"antigravityNotSignedIn": "Not signed in. Chats need an Antigravity sign-in, separate from your agy terminal account."
"antigravityNotSignedIn": "Not signed in. Chats need an Antigravity sign-in, separate from your agy terminal account.",
"antigravityAccount": "Account",
"antigravitySignInNeeded": "Chats need an Antigravity sign-in, separate from your agy terminal account."
}
},
"terminal": {
+3 -1
View File
@@ -40,7 +40,9 @@
"providers": {
"title": "Proveedores",
"antigravityDescription": "Los chats de Antigravity usan un servidor que Orca descarga en este host, independiente de la CLI de agy.",
"antigravityNotSignedIn": "No has iniciado sesión. Los chats necesitan un inicio de sesión de Antigravity, independiente de tu cuenta de agy en la terminal."
"antigravityNotSignedIn": "No has iniciado sesión. Los chats necesitan un inicio de sesión de Antigravity, independiente de tu cuenta de agy en la terminal.",
"antigravityAccount": "Cuenta",
"antigravitySignInNeeded": "Los chats necesitan un inicio de sesión de Antigravity, independiente de tu cuenta de agy en la terminal."
}
},
"terminal": {
+3 -1
View File
@@ -144,7 +144,9 @@
"providers": {
"title": "Fournisseurs",
"antigravityDescription": "Les chats Antigravity utilisent un serveur qu’Orca télécharge sur cet hôte, distinct de la CLI agy.",
"antigravityNotSignedIn": "Non connecté. Les discussions nécessitent une connexion Antigravity, distincte de votre compte agy du terminal."
"antigravityNotSignedIn": "Non connecté. Les discussions nécessitent une connexion Antigravity, distincte de votre compte agy du terminal.",
"antigravityAccount": "Compte",
"antigravitySignInNeeded": "Les discussions nécessitent une connexion Antigravity, distincte de votre compte agy du terminal."
}
},
"terminal": {
+3 -1
View File
@@ -40,7 +40,9 @@
"providers": {
"title": "プロバイダー",
"antigravityDescription": "Antigravity チャットは、Orca がこのホストにダウンロードするサーバーで動作します。agy CLI とは別です。",
"antigravityNotSignedIn": "サインインしていません。チャットには、ターミナルの agy アカウントとは別の Antigravity サインインが必要です。"
"antigravityNotSignedIn": "サインインしていません。チャットには、ターミナルの agy アカウントとは別の Antigravity サインインが必要です。",
"antigravityAccount": "アカウント",
"antigravitySignInNeeded": "チャットには、ターミナルの agy アカウントとは別の Antigravity サインインが必要です。"
}
},
"terminal": {
+3 -1
View File
@@ -40,7 +40,9 @@
"providers": {
"title": "공급자",
"antigravityDescription": "Antigravity 채팅은 Orca가 이 호스트에 다운로드하는 서버에서 실행되며, agy CLI와는 별개입니다.",
"antigravityNotSignedIn": "로그인되어 있지 않습니다. 채팅에는 터미널 agy 계정과 별개인 Antigravity 로그인이 필요합니다."
"antigravityNotSignedIn": "로그인되어 있지 않습니다. 채팅에는 터미널 agy 계정과 별개인 Antigravity 로그인이 필요합니다.",
"antigravityAccount": "계정",
"antigravitySignInNeeded": "채팅에는 터미널 agy 계정과 별개인 Antigravity 로그인이 필요합니다."
}
},
"terminal": {
+3 -1
View File
@@ -40,7 +40,9 @@
"providers": {
"title": "提供方",
"antigravityDescription": "Antigravity 聊天运行在 Orca 下载到此主机的服务器上,与 agy CLI 相互独立。",
"antigravityNotSignedIn": "未登录。聊天需要单独的 Antigravity 登录,与终端中的 agy 帐户分开。"
"antigravityNotSignedIn": "未登录。聊天需要单独的 Antigravity 登录,与终端中的 agy 帐户分开。",
"antigravityAccount": "账户",
"antigravitySignInNeeded": "聊天需要单独的 Antigravity 登录,与终端中的 agy 帐户分开。"
}
},
"terminal": {