-
-
-
+
{entry.name}
diff --git a/apps/examples/desktop-app/webview/components/views/settings/customize-view.tsx b/apps/examples/desktop-app/webview/components/views/settings/customize-view.tsx
index 833cee30f2..94f317734d 100644
--- a/apps/examples/desktop-app/webview/components/views/settings/customize-view.tsx
+++ b/apps/examples/desktop-app/webview/components/views/settings/customize-view.tsx
@@ -1,14 +1,10 @@
"use client";
import { Store } from "lucide-react";
-import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
+import { useCallback, useEffect, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
-import {
- fetchComposioStatus,
- getComposioAvailability,
- subscribeComposioAvailability,
-} from "@/lib/composio";
+import { fetchComposioStatus } from "@/lib/composio";
import { desktopClient } from "@/lib/desktop-client";
import { cn } from "@/lib/utils";
import { PageFrame, PageHeader } from "../page-layout";
@@ -36,13 +32,13 @@ type CustomizeTab =
| "tools";
const CUSTOMIZE_TABS: { id: CustomizeTab; label: string }[] = [
+ { id: "integrations", label: "Connectors" },
{ id: "tools", label: "Tools" },
{ id: "plugins", label: "Plugins" },
{ id: "skills", label: "Skills" },
{ id: "rules", label: "Rules" },
{ id: "mcp", label: "MCP" },
{ id: "hooks", label: "Hooks" },
- { id: "integrations", label: "Connectors" },
];
type TabCounts = Partial
>;
@@ -68,16 +64,10 @@ export function CustomizeView({
onOpenMarketplace?: (filter?: "connector") => void;
onOpenModelProviders?: () => void;
}) {
- const [tab, setTab] = useState("tools");
+ // Connectors is always the first tab; when the account cannot use them
+ // the tab itself explains why and offers sign-in.
+ const [tab, setTab] = useState("integrations");
const [counts, setCounts] = useState({});
- // Connectors are an org-provisioned feature: the tab only exists when the
- // account has Composio beta access.
- const connectorsAvailable =
- useSyncExternalStore(
- subscribeComposioAvailability,
- getComposioAvailability,
- () => null,
- ) === true;
const refreshCounts = useCallback(async () => {
const [inventory, composioStatus] = await Promise.all([
@@ -86,7 +76,7 @@ export function CustomizeView({
.catch(() => null),
fetchComposioStatus().catch(() => null),
]);
- const connectedIntegrations = composioStatus
+ const connectedIntegrations = composioStatus?.configured
? composioStatus.integrations.filter(
(integration) => integration.status === "connected",
).length
@@ -152,10 +142,7 @@ export function CustomizeView({
/>
- {CUSTOMIZE_TABS.filter(
- (customizeTab) =>
- customizeTab.id !== "integrations" || connectorsAvailable,
- ).map((customizeTab) => {
+ {CUSTOMIZE_TABS.map((customizeTab) => {
const count = counts[customizeTab.id];
const active = tab === customizeTab.id;
return (
@@ -210,6 +197,7 @@ export function CustomizeView({
/>
) : tab === "integrations" ? (
diff --git a/apps/examples/desktop-app/webview/contexts/account-context.tsx b/apps/examples/desktop-app/webview/contexts/account-context.tsx
index dc99eba9c4..b20838747f 100644
--- a/apps/examples/desktop-app/webview/contexts/account-context.tsx
+++ b/apps/examples/desktop-app/webview/contexts/account-context.tsx
@@ -21,6 +21,9 @@ const SIGNED_OUT_ERROR_MARKERS = [
type AccountContextValue = {
user: ClineAccountUser | null;
+ /** False until the first identity lookup has finished, so a `null` user
+ * can be told apart from "still loading". */
+ accountReady: boolean;
organizations: ClineAccountOrganization[];
activeOrganization: ClineAccountOrganization | null;
refreshAccount: () => Promise
;
@@ -28,6 +31,7 @@ type AccountContextValue = {
const AccountContext = createContext({
user: null,
+ accountReady: false,
organizations: [],
activeOrganization: null,
refreshAccount: async () => undefined,
@@ -95,6 +99,7 @@ export function isSignedOutAccountError(error: unknown): boolean {
export function AccountProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState(null);
+ const [accountReady, setAccountReady] = useState(false);
const refreshAccount = useCallback(async () => {
try {
@@ -118,6 +123,8 @@ export function AccountProvider({ children }: { children: React.ReactNode }) {
}
// Transient failures (offline, sidecar restarting) keep the cached
// identity rather than flashing a signed-out state.
+ } finally {
+ setAccountReady(true);
}
}, []);
@@ -135,12 +142,13 @@ export function AccountProvider({ children }: { children: React.ReactNode }) {
const organizations = user?.organizations ?? [];
return {
user,
+ accountReady,
organizations,
activeOrganization:
organizations.find((organization) => organization.active) ?? null,
refreshAccount,
};
- }, [refreshAccount, user]);
+ }, [accountReady, refreshAccount, user]);
return (
{children}