fix(updates): sync update center state after manual component changes

This commit is contained in:
zipg authored and GitHub committed 2026-09-20 18:43:02 +08:00
1 parent 1ef865008a
commit 4d150fc909
13 files changed
+143 -11

No files matched your search

@@ -19,7 +19,7 @@ import { physicalDropPositionInsideRect } from "@/lib/ai/aiAttachments";
import { createFrontendPluginRegistry, pluginConnectionProviderIcon } from "@/lib/plugins/frontendPlugin";
import { beaconPluginInstall, buildMarketplacePluginListings, filterMarketplacePluginListings, listingRepositoryCanVerify, marketplaceHomepageUrl, type MarketplacePluginListing } from "@/lib/plugins/pluginMarketplace";
import { isBatchSelectableListing, runBatch } from "@/lib/plugins/pluginBatch";
import { COMPONENT_PLUGINS_UPDATED_EVENT } from "@/lib/updates/componentUpdateEvents";
import { COMPONENT_PLUGINS_UPDATED_EVENT, notifyComponentUpdatesChanged } from "@/lib/updates/componentUpdateEvents";
import { formatBytes } from "@/lib/database/serverMetrics";
import type { PluginCenterFocus } from "@/lib/plugins/pluginCenterNavigation";
import { useConnectionStore } from "@/stores/connectionStore";
@@ -216,6 +216,7 @@ async function installMarketplaceListing(listing: MarketplacePluginListing) {
const result = await installListing(listing);
toast(t(listing.status === "update" ? "pluginPlatform.updateSuccess" : "pluginPlatform.installSuccess", { name: result.plugin.manifest.name, version: result.plugin.manifest.version }));
installedPlugins.value = await api.listPlugins();
notifyComponentUpdatesChanged();
selectPlugin(result.plugin.manifest.id);
} catch (cause) {
toast(translateBackendError(t, cause), 8000);
@@ -223,6 +224,7 @@ async function installMarketplaceListing(listing: MarketplacePluginListing) {
// instance), so re-read the installed list instead of leaving the card on state it may no longer
// describe.
installedPlugins.value = await api.listPlugins().catch(() => installedPlugins.value);
notifyComponentUpdatesChanged();
} finally {
marketplaceInstallingKey.value = "";
}
@@ -325,6 +327,7 @@ async function runBatchInstallUpdate() {
clearBatchSelection();
reportBatchSummary(outcome);
await refreshAfterBatch();
notifyComponentUpdatesChanged();
} finally {
batchRunning.value = false;
}
@@ -348,6 +351,7 @@ async function runBatchUninstall() {
clearBatchSelection();
reportBatchSummary(outcome);
await refreshAfterBatch();
notifyComponentUpdatesChanged();
} finally {
batchRunning.value = false;
}
@@ -543,6 +547,7 @@ async function finishInstall(result: PluginInstallResult) {
toast(t("pluginPlatform.installSuccess", { name: result.plugin.manifest.name, version: result.plugin.manifest.version }));
clearPluginIconCache();
installedPlugins.value = await api.listPlugins();
notifyComponentUpdatesChanged();
selectPlugin(result.plugin.manifest.id);
activeSection.value = "installed";
}
@@ -689,6 +694,7 @@ async function rollbackSelectedPlugin() {
toast(t("pluginPlatform.rollbackSuccess", { version: result.plugin.manifest.version }));
clearPluginIconCache();
installedPlugins.value = await api.listPlugins();
notifyComponentUpdatesChanged();
selectPlugin(result.plugin.manifest.id);
} catch (cause) {
toast(translateBackendError(t, cause), 8000);
@@ -703,6 +709,7 @@ async function uninstallSelectedPlugin() {
operating.value = true;
try {
installedPlugins.value = await api.uninstallPlugin(definition.plugin.manifest.id);
notifyComponentUpdatesChanged();
clearPluginIconCache();
toast(t("pluginPlatform.uninstallSuccess", { name: definition.plugin.manifest.name }));
selectFirstProvider();
@@ -5,7 +5,7 @@ import { readFileSync } from "node:fs";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { InstalledPlugin, PluginRepositoryCatalogResult } from "@/types/database";
import type { MarketplacePluginListing } from "@/lib/plugins/pluginMarketplace";
import { COMPONENT_PLUGINS_UPDATED_EVENT } from "@/lib/updates/componentUpdateEvents";
import { COMPONENT_PLUGINS_UPDATED_EVENT, COMPONENT_UPDATES_CHANGED_EVENT } from "@/lib/updates/componentUpdateEvents";
const mocks = vi.hoisted(() => ({
listPlugins: vi.fn(),
@@ -246,6 +246,24 @@ describe("PluginContributionsPanel batch source validation", () => {
});
});
describe("PluginContributionsPanel update center synchronization", () => {
it("notifies after a single marketplace update without duplicating per batch item", async () => {
const listener = vi.fn();
window.addEventListener(COMPONENT_UPDATES_CHANGED_EVENT, listener);
try {
await state.installMarketplaceListing(state.marketplaceListings[0]);
expect(listener).toHaveBeenCalledOnce();
listener.mockClear();
state.selectAllUpdatable();
await state.runBatchInstallUpdate();
expect(listener).toHaveBeenCalledOnce();
} finally {
window.removeEventListener(COMPONENT_UPDATES_CHANGED_EVENT, listener);
}
});
});
describe("PluginContributionsPanel installed plugin pin controls", () => {
it("renders selection and pin actions as sibling native buttons", async () => {
state.batchMode = false;