From 65c8897e091042cd504e681c4a2175d72200af9d Mon Sep 17 00:00:00 2001 From: Wes McKinney Date: Fri, 19 Jun 2026 19:15:02 -0500 Subject: [PATCH] fix: sync active PR detail during polling Active PR detail polling was only re-reading the cached API response every minute, so changes that lived only on the provider stayed invisible until another sync path happened to run. Use the existing background detail sync flow for the poll tick so the Activity drawer and PR view actually refresh provider state while keeping the UI non-blocking. Generated with Codex Co-authored-by: Codex --- packages/ui/src/stores/detail.svelte.test.ts | 34 +++++++++++++++++++- packages/ui/src/stores/detail.svelte.ts | 2 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/stores/detail.svelte.test.ts b/packages/ui/src/stores/detail.svelte.test.ts index cad1db0e7..05ab85721 100644 --- a/packages/ui/src/stores/detail.svelte.test.ts +++ b/packages/ui/src/stores/detail.svelte.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vite-plus/test"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import type { PullDetail } from "../api/types.js"; import type { MiddlemanClient } from "../types.js"; @@ -40,6 +40,10 @@ function mockClient(overrides: Partial = {}): MiddlemanClient { } describe("createDetailStore", () => { + afterEach(() => { + vi.useRealTimers(); + }); + it("syncs detail and resolves after applying the refreshed head", async () => { const post = vi.fn().mockResolvedValue({ data: pullDetail("fresh-head"), @@ -62,4 +66,32 @@ describe("createDetailStore", () => { expect(store.getDetail()?.platform_head_sha).toBe("fresh-head"); expect(pulls.loadPulls).toHaveBeenCalledTimes(1); }); + + it("enqueues background sync when active detail polling fires", async () => { + vi.useFakeTimers(); + const post = vi.fn().mockResolvedValue({ error: undefined }); + const get = vi.fn().mockResolvedValue({ data: pullDetail("cached-head") }); + const store = createDetailStore({ + client: mockClient({ GET: get, POST: post }), + }); + + store.startDetailPolling("acme", "widget", 7, { + provider: "github", + platformHost: "github.com", + repoPath: "acme/widget", + }); + + await vi.advanceTimersByTimeAsync(60_000); + + expect(post).toHaveBeenCalledWith("/pulls/{provider}/{owner}/{name}/{number}/sync/async", { + params: { + path: { + provider: "github", + owner: "acme", + name: "widget", + number: 7, + }, + }, + }); + }); }); diff --git a/packages/ui/src/stores/detail.svelte.ts b/packages/ui/src/stores/detail.svelte.ts index 88f8f9d1d..286b1f287 100644 --- a/packages/ui/src/stores/detail.svelte.ts +++ b/packages/ui/src/stores/detail.svelte.ts @@ -908,7 +908,7 @@ export function createDetailStore(opts: DetailStoreOptions) { const ref = detailRequestRef(owner, name, number, identity); stopDetailPolling(); detailPollHandle = setInterval(() => { - void refreshDetail(owner, name, number, syncGeneration, ref); + void enqueueBackgroundDetailSync(owner, name, number, syncGeneration, detail?.detail_fetched_at, ref); }, 60_000); if (syncDep) { unsubSyncComplete = syncDep.subscribeSyncComplete(() => {