fix(plugins): reconcile runtime changes after tab switch (#695)

This commit is contained in:
InfinityPacer
2026-08-21 18:29:23 +08:00
committed by GitHub
parent ea6d0c8178
commit 9b747c67a9
2 changed files with 44 additions and 1 deletions
+20 -1
View File
@@ -218,6 +218,7 @@ const isMarketRefreshing = ref(false)
const pluginRuntimeSummary = computed(() => pluginRuntimeStore.summary)
const installingPluginIds = ref(new Set<string>())
const isPluginPageActive = ref(false)
const appliedRuntimeReconciliation = ref(0)
// 每类远程快照独立管理 writer 代际,旧请求只能完成自身 Promise,不能覆盖新状态。
let installedWriterGeneration = 0
@@ -1038,6 +1039,17 @@ async function fetchInstalledPlugins(context: KeepAliveRefreshContext = {}): Pro
}
}
/** 运行态变化在市场页发生时延后应用,回到已安装列表后再补一次快照。 */
async function reconcileInstalledRuntime(): Promise<void> {
const reconciliation = pluginRuntimeStore.reconciliation
if (reconciliation <= appliedRuntimeReconciliation.value) return
const refreshed = await fetchInstalledPlugins({ silent: true })
if (refreshed && reconciliation === pluginRuntimeStore.reconciliation) {
appliedRuntimeReconciliation.value = reconciliation
}
}
function isPluginRuntimeSettling(pluginId: string) {
return pluginRuntimeSummary.value?.ready === false || installingPluginIds.value.has(pluginId)
}
@@ -1448,19 +1460,26 @@ watch(activeTab, (newTab, oldTab) => {
if (generation !== tabScrollRestoreGeneration) return
window.scrollTo({ behavior: 'auto', top: tabScrollPositions[newTab] })
})
if (newTab === 'installed' && isPluginPageActive.value && !document.hidden) {
void reconcileInstalledRuntime()
}
})
watch(
() => pluginRuntimeStore.reconciliation,
() => {
if (isPluginPageActive.value && activeTab.value === 'installed' && !document.hidden) {
void fetchInstalledPlugins({ silent: true })
void reconcileInstalledRuntime()
}
},
)
onActivated(() => {
isPluginPageActive.value = true
if (activeTab.value === 'installed' && !document.hidden) {
void reconcileInstalledRuntime()
}
})
onDeactivated(() => {
@@ -794,6 +794,30 @@ describe('PluginCardListView loading and request ownership', () => {
await waitForRequestsToFinish()
})
it('applies a runtime generation received while the market tab is open', async () => {
let installedRequests = 0
const { pinia } = await renderList({
installed: () => {
installedRequests += 1
return [createPlugin({ id: 'Installed', installed: true, plugin_name: '已安装插件' })]
},
market: () => [createPlugin({ id: 'Market', plugin_name: '市场插件' })],
})
await waitForRequestsToFinish()
const initialInstalledRequests = installedRequests
const runtimeStore = usePluginRuntimeStore(pinia)
getHeaderConfig().modelValue.value = 'market'
await nextTick()
runtimeStore.reconciliation = 1
await nextTick()
expect(installedRequests).toBe(initialInstalledRequests)
getHeaderConfig().modelValue.value = 'installed'
await waitFor(() => expect(installedRequests).toBe(initialInstalledRequests + 1))
await waitForRequestsToFinish()
})
it('does not request the superuser runtime summary for an ordinary administrator', async () => {
server.use(
http.get(apiUrls.runtime, () => {