diff --git a/apps/desktop/src/renderer/components/PostHogSurfaceTagger/PostHogSurfaceTagger.tsx b/apps/desktop/src/renderer/components/PostHogSurfaceTagger/PostHogSurfaceTagger.tsx
index fefef77ea13..c0997122bcf 100644
--- a/apps/desktop/src/renderer/components/PostHogSurfaceTagger/PostHogSurfaceTagger.tsx
+++ b/apps/desktop/src/renderer/components/PostHogSurfaceTagger/PostHogSurfaceTagger.tsx
@@ -1,19 +1,13 @@
import { useEffect } from "react";
import { useIsV2CloudEnabled } from "renderer/hooks/useIsV2CloudEnabled";
import { posthog } from "renderer/lib/posthog";
-import { useV2LocalOverrideStore } from "renderer/stores/v2-local-override";
export function PostHogSurfaceTagger() {
- const { isV2CloudEnabled, isRemoteV2Enabled } = useIsV2CloudEnabled();
- const optInV2 = useV2LocalOverrideStore((s) => s.optInV2);
+ const isV2CloudEnabled = useIsV2CloudEnabled();
useEffect(() => {
const surface = isV2CloudEnabled ? "v2" : "v1";
- const surface_source = !isRemoteV2Enabled
- ? "v2-flag-off"
- : optInV2
- ? "opted-in"
- : "opted-out";
+ const surface_source = isV2CloudEnabled ? "opted-in" : "opted-out";
posthog.register({ surface, surface_source });
@@ -24,7 +18,7 @@ export function PostHogSurfaceTagger() {
surface_ever_v2: true,
});
}
- }, [isV2CloudEnabled, isRemoteV2Enabled, optInV2]);
+ }, [isV2CloudEnabled]);
return null;
}
diff --git a/apps/desktop/src/renderer/components/V2AvailableBanner/V2AvailableBanner.tsx b/apps/desktop/src/renderer/components/V2AvailableBanner/V2AvailableBanner.tsx
new file mode 100644
index 00000000000..a66259e1eca
--- /dev/null
+++ b/apps/desktop/src/renderer/components/V2AvailableBanner/V2AvailableBanner.tsx
@@ -0,0 +1,46 @@
+import { SidebarCard } from "@superset/ui/sidebar-card";
+import { AnimatePresence, motion } from "framer-motion";
+import { useIsV2CloudEnabled } from "renderer/hooks/useIsV2CloudEnabled";
+import { track } from "renderer/lib/analytics";
+import { useV2AvailableBannerStore } from "renderer/stores/v2-available-banner";
+import { useV2LocalOverrideStore } from "renderer/stores/v2-local-override";
+
+export function V2AvailableBanner() {
+ const isV2CloudEnabled = useIsV2CloudEnabled();
+ const dismissed = useV2AvailableBannerStore((s) => s.dismissed);
+ const dismiss = useV2AvailableBannerStore((s) => s.dismiss);
+ const setOptInV2 = useV2LocalOverrideStore((s) => s.setOptInV2);
+
+ function handleSwitch() {
+ track("surface_toggled", { from: "v1", to: "v2", source: "v1_banner" });
+ setOptInV2(true);
+ }
+
+ function handleDismiss() {
+ track("v2_banner_dismissed");
+ dismiss();
+ }
+
+ return (
+
+ {!dismissed && (
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/desktop/src/renderer/components/V2AvailableBanner/index.ts b/apps/desktop/src/renderer/components/V2AvailableBanner/index.ts
new file mode 100644
index 00000000000..bfdb50e87be
--- /dev/null
+++ b/apps/desktop/src/renderer/components/V2AvailableBanner/index.ts
@@ -0,0 +1 @@
+export { V2AvailableBanner } from "./V2AvailableBanner";
diff --git a/apps/desktop/src/renderer/hooks/useIsV2CloudEnabled.ts b/apps/desktop/src/renderer/hooks/useIsV2CloudEnabled.ts
index b9f55c10667..93cb7f0f51d 100644
--- a/apps/desktop/src/renderer/hooks/useIsV2CloudEnabled.ts
+++ b/apps/desktop/src/renderer/hooks/useIsV2CloudEnabled.ts
@@ -1,29 +1,6 @@
-import { FEATURE_FLAGS } from "@superset/shared/constants";
-import { useFeatureFlagEnabled } from "posthog-js/react";
import { useV2LocalOverrideStore } from "renderer/stores/v2-local-override";
-const IS_DEV = process.env.NODE_ENV === "development";
-
-/**
- * Returns effective v2 state: remote PostHog flag AND local opt-in.
- * Also returns the raw remote flag so the toggle can be shown conditionally.
- */
-export function useIsV2CloudEnabled() {
- const remoteV2Enabled =
- useFeatureFlagEnabled(FEATURE_FLAGS.V2_CLOUD) ?? false;
- const optInV2 = useV2LocalOverrideStore((s) => s.optInV2);
-
- if (IS_DEV) {
- return {
- isV2CloudEnabled: optInV2,
- isRemoteV2Enabled: true,
- };
- }
-
- return {
- /** The effective value — use this wherever you previously checked the flag directly. */
- isV2CloudEnabled: remoteV2Enabled && optInV2,
- /** Whether the remote PostHog flag is on (for showing the toggle). */
- isRemoteV2Enabled: remoteV2Enabled,
- };
+/** Returns whether v2 is currently active for this user. */
+export function useIsV2CloudEnabled(): boolean {
+ return useV2LocalOverrideStore((s) => s.optInV2);
}
diff --git a/apps/desktop/src/renderer/lib/hasPriorSupersetUsage.ts b/apps/desktop/src/renderer/lib/hasPriorSupersetUsage.ts
new file mode 100644
index 00000000000..88ed37a6060
--- /dev/null
+++ b/apps/desktop/src/renderer/lib/hasPriorSupersetUsage.ts
@@ -0,0 +1,9 @@
+/**
+ * True when this install has been used before. Backed by `tabs-storage`,
+ * which is written the first time any workspace tab opens. Use this to
+ * distinguish a fresh install from a returning user.
+ */
+export function hasPriorSupersetUsage(): boolean {
+ if (typeof localStorage === "undefined") return false;
+ return localStorage.getItem("tabs-storage") !== null;
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
index eb1c5a9e1fb..a4d63759959 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
@@ -24,6 +24,7 @@ import { useMatchRoute, useNavigate } from "@tanstack/react-router";
import { memo, useCallback, useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { HiOutlineCog6Tooth } from "react-icons/hi2";
+import { V2AvailableBanner } from "renderer/components/V2AvailableBanner";
import { useHotkeyDisplay } from "renderer/hotkeys";
import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState";
import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider";
@@ -236,6 +237,7 @@ export function DashboardSidebar({
projectName={activeV2Project.name}
/>
)}
+ {!isCollapsed && }
s.isOpen);
const isSidebarCollapsed = useWorkspaceSidebarStore((s) => s.isCollapsed());
// Default to Mac layout while loading to avoid overlap with traffic lights
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
index 362b36d5bbe..bbe7d63dbaa 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
@@ -32,7 +32,7 @@ export const Route = createFileRoute("/_authenticated/_dashboard")({
function DashboardLayout() {
const navigate = useNavigate();
const openNewWorkspaceModal = useOpenNewWorkspaceModal();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
useDevSeedV2Sidebar();
useMigrateV1DataToV2();
// Get current workspace from route to pre-select project in new workspace modal
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/PropertiesSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/PropertiesSidebar.tsx
index 26e3dd19827..32ca7789fd5 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/PropertiesSidebar.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/PropertiesSidebar.tsx
@@ -14,7 +14,7 @@ interface PropertiesSidebarProps {
export function PropertiesSidebar({ task }: PropertiesSidebarProps) {
const labels = task.labels ?? [];
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
return (
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/TasksTopBar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/TasksTopBar.tsx
index 77bb21693a1..02a44301a8b 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/TasksTopBar.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/TasksTopBar.tsx
@@ -70,7 +70,7 @@ export function TasksTopBar({
const selectedCount = selectedTasks.length;
const searchInputRef = useRef
(null);
const [isCreateTaskOpen, setIsCreateTaskOpen] = useState(false);
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
useHotkey(
"FOCUS_TASK_SEARCH",
diff --git a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts
index 6e3f1727632..88f6ea81f3c 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts
+++ b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts
@@ -102,7 +102,7 @@ export function useMigrateV1DataToV2({
} = {}) {
const { data: session } = authClient.useSession();
const { activeHostUrl } = useLocalHostService();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const collections = useCollections();
const isRunning = useSyncExternalStore(
subscribeMigrationRunning,
diff --git a/apps/desktop/src/renderer/routes/_authenticated/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/layout.tsx
index 30823cad3ef..b1a0b08d13f 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/layout.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/layout.tsx
@@ -64,7 +64,7 @@ function AuthenticatedLayout() {
const setOriginRoute = useSettingsStore((s) => s.setOriginRoute);
const utils = electronTrpc.useUtils();
const shownWorkspaceInitWarningsRef = useRef(new Set());
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const requiredComplete = useOnboardingStore(selectRequiredStepsComplete);
const firstIncompleteStep = useOnboardingStore(selectFirstIncompleteStep);
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/AgentsSettings/AgentsSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/AgentsSettings/AgentsSettings.tsx
index 39234033c70..872aacdc89d 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/AgentsSettings/AgentsSettings.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/agents/components/AgentsSettings/AgentsSettings.tsx
@@ -18,7 +18,7 @@ export function AgentsSettings({
visibleItems,
initialAgentPresetId,
}: AgentsSettingsProps) {
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
if (isV2CloudEnabled) {
return ;
}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx
index 420b0140a02..8ae71d8e541 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx
@@ -11,7 +11,7 @@ export const Route = createFileRoute("/_authenticated/settings/behavior/")({
function BehaviorSettingsPage() {
const searchQuery = useSettingsSearchQuery();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const visibleItems = useMemo(
() =>
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/GeneralSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/GeneralSettings.tsx
index 828bd6c7dd6..b55c975d604 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/GeneralSettings.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/GeneralSettings.tsx
@@ -205,7 +205,7 @@ export function GeneralSettings({ matchCounts }: GeneralSettingsProps) {
const matchRoute = useMatchRoute();
const { data: platform } = electronTrpc.window.getPlatform.useQuery();
const isMac = platform === "darwin";
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const allowedSections = useMemo(
() => getAllowedSectionsForVariant(isV2CloudEnabled),
[isV2CloudEnabled],
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/SettingsSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/SettingsSidebar.tsx
index 16fdbd386ab..85d0ebb4193 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/SettingsSidebar.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/SettingsSidebar.tsx
@@ -19,7 +19,7 @@ export function SettingsSidebar() {
const searchQuery = useSettingsSearchQuery();
const setSearchQuery = useSetSettingsSearchQuery();
const originRoute = useSettingsOriginRoute();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const normalizedSearchQuery = searchQuery.trim();
const matchCounts = normalizedSearchQuery
? getVisibleMatchCountBySection(normalizedSearchQuery, isV2CloudEnabled)
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/components/ExperimentalSettings/ExperimentalSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/components/ExperimentalSettings/ExperimentalSettings.tsx
index d8995df38c1..0aa00725cad 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/components/ExperimentalSettings/ExperimentalSettings.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/components/ExperimentalSettings/ExperimentalSettings.tsx
@@ -55,7 +55,7 @@ export function ExperimentalSettings({
SETTING_ITEM_ID.EXPERIMENTAL_RESTART_ONBOARDING,
visibleItems,
);
- const { isV2CloudEnabled, isRemoteV2Enabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const { rerun, isRunning } = useMigrateV1DataToV2({ autoRun: false });
const setOptInV2 = useV2LocalOverrideStore((state) => state.setOptInV2);
const resetOnboarding = useOnboardingStore((state) => state.reset);
@@ -98,13 +98,8 @@ export function ExperimentalSettings({
Try Superset v2
- Use the new workspace experience when early access is available.
+ Use the new workspace experience.
- {!isRemoteV2Enabled && (
-
- Early access is not enabled for this account.
-
- )}
{
track("surface_toggled", {
from: isV2CloudEnabled ? "v2" : "v1",
- to: enabled && isRemoteV2Enabled ? "v2" : "v1",
+ to: enabled ? "v2" : "v1",
});
setOptInV2(enabled);
}}
- disabled={!isRemoteV2Enabled}
/>
)}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/page.tsx
index 5709fb91576..6cd4d7d9e9c 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/experimental/page.tsx
@@ -11,7 +11,7 @@ export const Route = createFileRoute("/_authenticated/settings/experimental/")({
function ExperimentalSettingsPage() {
const searchQuery = useSettingsSearchQuery();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const visibleItems = useMemo(
() =>
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx
index 727c5067dbe..8e9bbc0e3fd 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx
@@ -11,7 +11,7 @@ export const Route = createFileRoute("/_authenticated/settings/git/")({
function GitSettingsPage() {
const searchQuery = useSettingsSearchQuery();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const visibleItems = useMemo(
() =>
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/links/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/links/page.tsx
index 40af55e5444..c6b04ff653d 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/links/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/links/page.tsx
@@ -11,7 +11,7 @@ export const Route = createFileRoute("/_authenticated/settings/links/")({
function LinksSettingsPage() {
const searchQuery = useSettingsSearchQuery();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const visibleItems = useMemo(
() =>
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
index 6909f87e70b..fd3257dde3a 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
@@ -47,7 +47,7 @@ export function TerminalSettings({
pendingCreateProjectId,
onPendingCreateProjectIdChange,
}: TerminalSettingsProps) {
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const showPresets = isItemVisible(
SETTING_ITEM_ID.TERMINAL_PRESETS,
visibleItems,
diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/page.tsx
index d2662dd9de2..bb8da40ddb1 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/page.tsx
@@ -28,7 +28,7 @@ function TerminalSettingsPage() {
const navigate = Route.useNavigate();
const { editPresetId, createProjectId } = Route.useSearch();
const searchQuery = useSettingsSearchQuery();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const visibleItems = useMemo(
() =>
diff --git a/apps/desktop/src/renderer/routes/_authenticated/setup/adopt-worktrees/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/setup/adopt-worktrees/page.tsx
index e4d5c1f07f1..906814ae69d 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/setup/adopt-worktrees/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/setup/adopt-worktrees/page.tsx
@@ -31,7 +31,7 @@ function OnboardingAdoptWorktreesPage() {
);
const utils = electronTrpc.useUtils();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const { data: projects, isPending } =
electronTrpc.projects.getRecents.useQuery();
diff --git a/apps/desktop/src/renderer/routes/_authenticated/setup/project/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/setup/project/page.tsx
index 2d0ff8e0420..4da6c694274 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/setup/project/page.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/setup/project/page.tsx
@@ -41,7 +41,7 @@ function OnboardingProjectPage() {
electronTrpc.projects.getRecents.useQuery();
const { openNew, isPending: isOpenPending } = useOpenProject();
const utils = electronTrpc.useUtils();
- const { isV2CloudEnabled } = useIsV2CloudEnabled();
+ const isV2CloudEnabled = useIsV2CloudEnabled();
const closeProject = electronTrpc.projects.close.useMutation({
onSuccess: async () => {
await utils.projects.getRecents.invalidate();
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
index 4198ed8636c..e1415e2e854 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo } from "react";
+import { V2AvailableBanner } from "renderer/components/V2AvailableBanner";
import { useWorkspaceShortcuts } from "renderer/hooks/useWorkspaceShortcuts";
import { useWorkspaceSelectionStore } from "renderer/stores/workspace-selection";
import { MultiDragPreview } from "./MultiDragPreview";
@@ -115,6 +116,8 @@ export function WorkspaceSidebar({
projectName={activeProjectName}
/>
+ {!isCollapsed && }
+
diff --git a/apps/desktop/src/renderer/stores/v2-available-banner/index.ts b/apps/desktop/src/renderer/stores/v2-available-banner/index.ts
new file mode 100644
index 00000000000..5757d78a124
--- /dev/null
+++ b/apps/desktop/src/renderer/stores/v2-available-banner/index.ts
@@ -0,0 +1 @@
+export { useV2AvailableBannerStore } from "./store";
diff --git a/apps/desktop/src/renderer/stores/v2-available-banner/store.ts b/apps/desktop/src/renderer/stores/v2-available-banner/store.ts
new file mode 100644
index 00000000000..f4bc9b53b35
--- /dev/null
+++ b/apps/desktop/src/renderer/stores/v2-available-banner/store.ts
@@ -0,0 +1,20 @@
+import { create } from "zustand";
+import { devtools, persist } from "zustand/middleware";
+
+interface V2AvailableBannerState {
+ dismissed: boolean;
+ dismiss: () => void;
+}
+
+export const useV2AvailableBannerStore = create()(
+ devtools(
+ persist(
+ (set) => ({
+ dismissed: false,
+ dismiss: () => set({ dismissed: true }),
+ }),
+ { name: "v2-available-banner-v1" },
+ ),
+ { name: "V2AvailableBannerStore" },
+ ),
+);
diff --git a/apps/desktop/src/renderer/stores/v2-local-override.ts b/apps/desktop/src/renderer/stores/v2-local-override.ts
index 783a41f7655..135153b6210 100644
--- a/apps/desktop/src/renderer/stores/v2-local-override.ts
+++ b/apps/desktop/src/renderer/stores/v2-local-override.ts
@@ -1,19 +1,23 @@
+import { hasPriorSupersetUsage } from "renderer/lib/hasPriorSupersetUsage";
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
-const IS_DEV = process.env.NODE_ENV === "development";
-
interface V2LocalOverrideState {
/** When true, the user has opted into v2. v2 is gated behind both the remote flag and this opt-in. */
optInV2: boolean;
setOptInV2: (optInV2: boolean) => void;
}
+// Fresh installs default to v2; returning v1 users default to v1 and discover
+// v2 via the in-sidebar banner. Persist hydration overrides this for anyone
+// with a saved override.
+const initialOptInV2 = !hasPriorSupersetUsage();
+
export const useV2LocalOverrideStore = create()(
devtools(
persist(
(set) => ({
- optInV2: IS_DEV,
+ optInV2: initialOptInV2,
setOptInV2: (optInV2) => set({ optInV2 }),
}),
{ name: "v2-local-override-v2" },
diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts
index d1c266d0cdd..f8b931169fe 100644
--- a/packages/shared/src/constants.ts
+++ b/packages/shared/src/constants.ts
@@ -71,8 +71,6 @@ export const FEATURE_FLAGS = {
CLOUD_ACCESS: "cloud-access",
/** When enabled, blocks remote agent execution on the desktop (e.g., for enterprise orgs). */
DISABLE_REMOTE_AGENT: "disable-remote-agent",
- /** Gates access to V2 Cloud features (host-service, cloud sprites). */
- V2_CLOUD: "v2-cloud",
/**
* Gates the Automations feature in the UI (sidebar entry, routes, create
* flow). Complementary to the subscriptions.plan paid-tier check —