fix: extension list not refreshing after installing from deeplink (#3878)

This commit is contained in:
Zane
2025-08-07 08:28:48 -07:00
committed by GitHub
parent 5eb01004a4
commit d3b76df676
3 changed files with 24 additions and 39 deletions
@@ -1,4 +1,4 @@
import { useEffect, useState, useCallback } from 'react';
import { useEffect, useState, useCallback, useMemo } from 'react';
import { Button } from '../../ui/button';
import { Plus } from 'lucide-react';
import { GPSIcon } from '../../ui/icons';
@@ -33,8 +33,7 @@ export default function ExtensionsSection({
customToggle,
selectedExtensions = [],
}: ExtensionSectionProps) {
const { getExtensions, addExtension, removeExtension } = useConfig();
const [extensions, setExtensions] = useState<FixedExtensionEntry[]>([]);
const { getExtensions, addExtension, removeExtension, extensionsList } = useConfig();
const [selectedExtension, setSelectedExtension] = useState<FixedExtensionEntry | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
@@ -51,19 +50,11 @@ export default function ExtensionsSection({
setShowEnvVarsStateVar(showEnvVars);
}, [deepLinkConfig, showEnvVars]);
// Reset deep link state when component is re-mounted (via key prop changes)
useEffect(() => {
return () => {
// Cleanup function to reset state when component unmounts
setDeepLinkConfigStateVar(null);
setShowEnvVarsStateVar(null);
};
}, []);
// Process extensions from context - this automatically updates when extensionsList changes
const extensions = useMemo(() => {
if (extensionsList.length === 0) return [];
const fetchExtensions = useCallback(async () => {
const extensionsList = await getExtensions(true); // Force refresh
// Sort extensions by name to maintain consistent order
const sortedExtensions = [...extensionsList]
return [...extensionsList]
.sort((a, b) => {
// First sort by builtin
if (a.type === 'builtin' && b.type !== 'builtin') return -1;
@@ -83,24 +74,15 @@ export default function ExtensionsSection({
// Use selectedExtensions to determine enabled state in recipe editor
enabled: disableConfiguration ? selectedExtensions.includes(ext.name) : ext.enabled,
}));
}, [extensionsList, disableConfiguration, selectedExtensions]);
setExtensions(sortedExtensions);
}, [getExtensions, disableConfiguration, selectedExtensions]);
useEffect(() => {
fetchExtensions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const fetchExtensions = useCallback(async () => {
await getExtensions(true); // Force refresh - this will update the context
}, [getExtensions]);
const handleExtensionToggle = async (extension: FixedExtensionEntry) => {
if (customToggle) {
await customToggle(extension);
// After custom toggle, update the local state to reflect the change
setExtensions((prevExtensions) =>
prevExtensions.map((ext) =>
ext.name === extension.name ? { ...ext, enabled: !ext.enabled } : ext
)
);
return true;
}
@@ -14,18 +14,20 @@ interface ApiResponse {
export async function extensionApiCall(
endpoint: string,
payload: ExtensionConfig | string,
options: ToastServiceOptions = {}
options: ToastServiceOptions & { isDelete?: boolean } = {}
): Promise<Response> {
// Configure toast notifications
toastService.configure(options);
// Determine if we're activating or removing an extension
// Determine if we're activating, deactivating, or removing an extension
const isActivating = endpoint == '/extensions/add';
const isRemoving = options.isDelete === true;
const action = {
type: isActivating ? 'activating' : 'removing',
verb: isActivating ? 'Activating' : 'Removing',
pastTense: isActivating ? 'activated' : 'removed',
presentTense: isActivating ? 'activate' : 'remove',
type: isActivating ? 'activating' : isRemoving ? 'removing' : 'deactivating',
verb: isActivating ? 'Activating' : isRemoving ? 'Removing' : 'Deactivating',
pastTense: isActivating ? 'activated' : isRemoving ? 'removed' : 'deactivated',
presentTense: isActivating ? 'activate' : isRemoving ? 'remove' : 'deactivate',
};
// for adding the payload is an extensionConfig, for removing payload is just the name
@@ -71,7 +73,7 @@ export async function extensionApiCall(
toastService.dismiss(toastId);
toastService.success({
title: extensionName,
msg: `Successfully ${action.pastTense} extension!`,
msg: `Successfully ${action.pastTense} extension`,
});
return response;
} catch (error) {
@@ -114,7 +116,7 @@ function handleErrorResponse(
}
// General error case
const msg = `Failed to ${action.type === 'activating' ? 'add' : 'remove'} ${extensionName} extension: ${errorMsg}`;
const msg = `Failed to ${action.type === 'activating' ? 'add' : action.type === 'removing' ? 'remove' : 'deactivate'} ${extensionName} extension: ${errorMsg}`;
toastService.dismiss(toastId);
toastService.error({
title: extensionName,
@@ -168,12 +170,13 @@ export async function addToAgent(
*/
export async function removeFromAgent(
name: string,
options: ToastServiceOptions = {}
options: ToastServiceOptions & { isDelete?: boolean } = {}
): Promise<Response> {
try {
return await extensionApiCall('/extensions/remove', sanitizeName(name), options);
} catch (error) {
console.error(`Failed to remove extension ${name} from agent:`, error);
const action = options.isDelete ? 'remove' : 'deactivate';
console.error(`Failed to ${action} extension ${name} from agent:`, error);
throw error;
}
}
@@ -336,7 +336,7 @@ export async function deleteExtension({ name, removeFromConfig }: DeleteExtensio
// remove from agent
let agentRemoveError = null;
try {
await removeFromAgent(name);
await removeFromAgent(name, { isDelete: true });
} catch (error) {
console.error('Failed to remove extension from agent during deletion:', error);
agentRemoveError = error;