From f256a4c538a2b13f9800fa9a846cc03ffad2b0ca Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:24:31 +0800 Subject: [PATCH] fix(app): preserve agent picker for existing users (#39300) --- packages/app/src/context/settings.test.ts | 17 +++++++++++ packages/app/src/context/settings.tsx | 30 ++++++++++++++++++-- packages/desktop/src/renderer/onboarding.tsx | 1 + 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/app/src/context/settings.test.ts b/packages/app/src/context/settings.test.ts index 3f94f22ec3f..51b35eacbba 100644 --- a/packages/app/src/context/settings.test.ts +++ b/packages/app/src/context/settings.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test" import { hasExistingWebState, + initialAgentVisibility, isAppUpgrade, layoutTransitionState, maximumSunsetTimeout, @@ -11,6 +12,22 @@ import { shouldEnableNewLayout, } from "./settings" +describe("agent visibility", () => { + test("shows the picker for existing profiles and hides it for first-time installs", () => { + expect(initialAgentVisibility(undefined, true)).toBe(true) + expect(initialAgentVisibility(undefined, false)).toBe(false) + }) + + test("shows the picker when updating from a recent release", () => { + expect(initialAgentVisibility(undefined, false, "1.18.8")).toBe(true) + }) + + test("preserves the preference after initialization", () => { + expect(initialAgentVisibility(true, true, "1.18.8")).toBeUndefined() + expect(initialAgentVisibility(true, false)).toBeUndefined() + }) +}) + describe("layout transition", () => { test("blank profiles default to the new layout", () => { expect(newLayoutDesignsDefault).toBe(true) diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index fe8b4e3c03f..23dbb531743 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -1,5 +1,5 @@ import { createStore, reconcile } from "solid-js/store" -import { createEffect, createMemo, createSignal, onCleanup } from "solid-js" +import { batch, createEffect, createMemo, createSignal, onCleanup } from "solid-js" import { createSimpleContext } from "@opencode-ai/ui/context" import { persisted } from "@/utils/persist" import { usePlatform } from "@/context/platform" @@ -36,6 +36,7 @@ export interface Settings { mobileTitlebarPosition: "top" | "bottom" newLayoutDesigns?: boolean layoutTransitionEligible?: boolean + agentVisibilityInitialized?: boolean newInterfaceNoticeDismissed?: boolean shouldDisplayTabsToast?: boolean } @@ -93,6 +94,15 @@ export function hasExistingWebState(settings: Promise | string | null, p return settings !== null || previousVersion !== undefined } +export function initialAgentVisibility( + initialized: boolean | undefined, + existing: boolean, + previousVersion?: string, +) { + if (initialized === true) return + return existing || previousVersion !== undefined +} + export function shouldEnableNewLayout(previous: string | undefined, current: string | undefined) { if (!current) return false const currentComparison = compareVersions(current, newLayoutDesignsUpgradeCutoff) @@ -271,6 +281,18 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont ) }) const visible = (preference: () => boolean) => createMemo(() => !newLayoutDesigns() || preference()) + const initializeAgentVisibility = (existing: boolean) => { + const initial = initialAgentVisibility( + store.general?.agentVisibilityInitialized, + existing, + launchState.previous, + ) + if (initial === undefined) return + batch(() => { + setStore("general", "showCustomAgents", initial) + setStore("general", "agentVisibilityInitialized", true) + }) + } if (sunset && !oldInterfaceRetired()) { const timeout = { current: undefined as ReturnType | undefined } @@ -299,8 +321,9 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont createEffect(() => { if (!ready() || !launchState.classified || platform.platform !== "web") return - if (layoutTransitionClassified()) return - setStore("general", "layoutTransitionEligible", hasExistingWebState(settingsInit, launchState.previous)) + const existing = hasExistingWebState(settingsInit, launchState.previous) + if (!layoutTransitionClassified()) setStore("general", "layoutTransitionEligible", existing) + initializeAgentVisibility(existing) }) createEffect(() => { @@ -426,6 +449,7 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont if (typeof current === "boolean") return setStore("general", "layoutTransitionEligible", eligible) }, + initializeAgentVisibility, layoutTransitionAvailable: createMemo(() => ready() && layoutTransition().available), newInterfaceNoticeVisible: createMemo(() => ready() && layoutTransition().notice), dismissNewInterfaceNotice() { diff --git a/packages/desktop/src/renderer/onboarding.tsx b/packages/desktop/src/renderer/onboarding.tsx index 76c7ff683ad..c5d7ff53896 100644 --- a/packages/desktop/src/renderer/onboarding.tsx +++ b/packages/desktop/src/renderer/onboarding.tsx @@ -17,6 +17,7 @@ export function DesktopFirstLaunchOnboarding(props: { initialUrl: string; onLoad ) const existingInstall = await window.api.isOldLayoutEligible() settings.general.setOldLayoutEligible(existingInstall) + settings.general.initializeAgentVisibility(existingInstall) if (!server.isLocal()) return const pending = await window.api.isFirstLaunchOnboardingPending()