From d487dfbf46be344e818725be70ee04be2436bfb4 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Fri, 4 Sep 2026 04:25:32 -0700 Subject: [PATCH] fix(web): resume Antigravity threads without repeated sign-in (#9647) Allow Antigravity threads to resume while saved Google sign-in is unchecked after a server restart. Keep confirmed authentication failures and installation errors visible. Validated with 136 focused tests, web typecheck, targeted lint, and CI. Browser verification was omitted at the maintainer's request. Created with GPT-6 Astra (preview) in Codex. --- .../web/src/components/ChatView.logic.test.ts | 15 +++++--- apps/web/src/components/ChatView.logic.ts | 11 +++--- .../chat/ProviderStatusBanner.test.tsx | 36 +++++++++++++++++++ .../components/chat/ProviderStatusBanner.tsx | 19 +++++++--- docs/user/providers-antigravity.md | 12 ++++--- 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/apps/web/src/components/ChatView.logic.test.ts b/apps/web/src/components/ChatView.logic.test.ts index 8e9c80641f2ba312e7c0b77473d220b2a7014678..c8afd10d37252055c8efb42f9e5084c512d81cf8 100644 --- a/apps/web/src/components/ChatView.logic.test.ts +++ b/apps/web/src/components/ChatView.logic.test.ts @@ -1,4 +1,5 @@ import { + ANTIGRAVITY_DEFAULT_MODEL, CheckpointRef, EnvironmentId, MessageId, @@ -793,14 +794,20 @@ describe("resolveComposerProviderSelection", () => { ); }); - it("blocks sends until Antigravity confirms authentication", () => { + it("lets Antigravity check saved credentials when resuming after a restart", () => { const provider = entry("antigravity", "google_work", { + status: "warning", auth: { status: "unknown" }, - models: catalogModels, + models: [], }).snapshot; - expect(getAntigravitySendBlockReason(provider, "gemini-pro")).toBe( - "Sign in to Antigravity in provider settings before sending.", + expect(getAntigravitySendBlockReason(provider, "gemini-pro")).toBeNull(); + expect(getAntigravitySendBlockReason(provider, ANTIGRAVITY_DEFAULT_MODEL)).toBeNull(); + expect( + getAntigravitySendBlockReason({ ...provider, models: catalogModels }, "gemini-pro"), + ).toBeNull(); + expect(getAntigravitySendBlockReason(provider, "")).toBe( + "Choose an Antigravity model before sending.", ); }); diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index 1a6b1b775f4127e8e1ecc375241e692e3b4e34ff..4a0b9f5761037fc56d385240ce472424f48d1586 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -421,14 +421,17 @@ export function getAntigravitySendBlockReason( if (!provider.installed) { return "Install Antigravity in provider settings before sending."; } - if (provider.auth.status !== "authenticated") { + if (provider.auth.status === "unauthenticated") { return "Sign in to Antigravity in provider settings before sending."; } - if (provider.models.length === 0) { - return "Refresh Antigravity models in provider settings before sending."; - } const slug = model.trim(); if (slug.length === 0) return "Choose an Antigravity model before sending."; + // A restart clears the account status and catalog. Session startup checks + // saved credentials and validates the model before sending the prompt. + if (provider.auth.status === "unknown") return null; + if (provider.models.length === 0) { + return "Refresh Antigravity models in provider settings before sending."; + } // A saved model that left the catalog is kept in the picker as unavailable // so the user sees what the thread used. The server rejects it at turn // start, so block here unless the provider is in an error state, where a diff --git a/apps/web/src/components/chat/ProviderStatusBanner.test.tsx b/apps/web/src/components/chat/ProviderStatusBanner.test.tsx index f27cda19d957907aa5f75de0ad85a215dd759d5c..e51383bc69fe58b3f8a7eadbcaadeaa6775a4a22 100644 --- a/apps/web/src/components/chat/ProviderStatusBanner.test.tsx +++ b/apps/web/src/components/chat/ProviderStatusBanner.test.tsx @@ -28,6 +28,42 @@ function warningProvider(): ServerProvider { } describe("ProviderStatusBanner", () => { + it("waits for an Antigravity auth result before showing a sign-in warning", () => { + const status: ServerProvider = { + ...warningProvider(), + instanceId: ProviderInstanceId.make("google_work"), + driver: ProviderDriverKind.make("antigravity"), + auth: { status: "unknown" }, + message: "Antigravity is installed. Google account access is not checked yet.", + }; + + expect(shouldShowProviderStatusBanner(status, null)).toBe(false); + expect( + shouldShowProviderStatusBanner( + { + ...status, + auth: { status: "unauthenticated" }, + message: "Sign in with Google to use Antigravity.", + }, + null, + ), + ).toBe(true); + }); + + it("shows Antigravity installation and startup failures before auth is checked", () => { + const status: ServerProvider = { + ...warningProvider(), + driver: ProviderDriverKind.make("antigravity"), + auth: { status: "unknown" }, + }; + + expect(shouldShowProviderStatusBanner({ ...status, installed: false }, null)).toBe(true); + expect(shouldShowProviderStatusBanner({ ...status, status: "error" }, null)).toBe(true); + expect( + shouldShowProviderStatusBanner({ ...status, driver: ProviderDriverKind.make("codex") }, null), + ).toBe(true); + }); + it("stays hidden after its current warning is dismissed", () => { const status = warningProvider(); diff --git a/apps/web/src/components/chat/ProviderStatusBanner.tsx b/apps/web/src/components/chat/ProviderStatusBanner.tsx index 2ae4bc8b58d44d25888a3ac4a76fe288e40442f1..12f9dc04f26a092adca1d801a28762cd08084f8d 100644 --- a/apps/web/src/components/chat/ProviderStatusBanner.tsx +++ b/apps/web/src/components/chat/ProviderStatusBanner.tsx @@ -7,9 +7,20 @@ import { formatProviderDriverKindLabel } from "../../providerModels"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; export function getProviderStatusBannerKey(status: ServerProvider | null): string | null { - return !status || status.status === "ready" || status.status === "disabled" - ? null - : [status.instanceId, status.status, status.auth.status, status.message ?? ""].join("\u0000"); + if (!status || status.status === "ready" || status.status === "disabled") return null; + // Antigravity checks saved credentials when a session starts. Its local + // health check leaves auth unknown after a restart, which is not a failure. + if ( + status.driver === "antigravity" && + status.installed && + status.status === "warning" && + status.auth.status === "unknown" + ) { + return null; + } + return [status.instanceId, status.status, status.auth.status, status.message ?? ""].join( + "\u0000", + ); } export function shouldShowProviderStatusBanner( @@ -59,7 +70,7 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void; status: ServerProvider | null; }) { - if (!status || status.status === "ready" || status.status === "disabled") { + if (!status || getProviderStatusBannerKey(status) === null) { return null; } diff --git a/docs/user/providers-antigravity.md b/docs/user/providers-antigravity.md index bd84ced3a3951eee46a7ee39fdaca768cf24e627..24fe8c05780ee59679da5ba9ef9eb637ada2dc14 100644 --- a/docs/user/providers-antigravity.md +++ b/docs/user/providers-antigravity.md @@ -179,10 +179,14 @@ paid-plan tier or remaining subscription quota. See Google's [Antigravity plans] [personal Google sign-in guide][google-setup]. After an environment restarts, Google sign-in can show as not checked until an authenticated -session succeeds. To check account access and reload models, use **Refresh provider status** -in web or desktop provider settings, or **Refresh models** in the mobile model picker. Refresh -uses saved Google sign-in and does not open a login page. If sign-in is required, use the -provider's setup controls. Automatic status checks verify the installation only. +session succeeds. You can continue an existing thread. Antigravity checks saved Google sign-in +when the session starts. An unchecked status does not require signing in again. + +To check account access and reload models on web or desktop, open **Settings** > **Providers** +and select the circular arrow beside **Checked** at the top of the page. Its tooltip says +**Refresh provider status**. On mobile, use **Refresh models** in the model picker. +Refresh uses saved Google sign-in and does not open a login page. If sign-in is required, +use the provider's setup controls. Automatic status checks verify the installation only. The packaged runtime can be slow to start, especially on Windows. Health checks, model refresh, and sign-out each allow up to 90 seconds before reporting a timeout. -- 2.54.0