mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-27 02:14:50 +10:00
refactor: better resume two-way sync in case of MCP/API updates
This commit is contained in:
@@ -57,6 +57,7 @@ import { getOrpcErrorMessage } from "@/libs/error-message";
|
||||
import { client, orpc, streamClient } from "@/libs/orpc/client";
|
||||
import { AgentThreadSidebar } from "./-components/thread-sidebar";
|
||||
import { attachmentIdsFromTransportBody, buildAgentChatSubmission } from "./-helpers/chat-attachments";
|
||||
import { useAgentResumeUpdateSubscription } from "./-hooks/use-agent-resume-updates";
|
||||
|
||||
type AgentThreadDetail = RouterOutput["agent"]["threads"]["get"];
|
||||
type AgentAction = AgentThreadDetail["actions"][number];
|
||||
@@ -1218,6 +1219,7 @@ function RouteComponent() {
|
||||
const [isThreadsCollapsed, setIsThreadsCollapsed] = useState(false);
|
||||
const [isResumeCollapsed, setIsResumeCollapsed] = useState(false);
|
||||
const { data, isLoading, error } = useQuery(orpc.agent.threads.get.queryOptions({ input: { id: threadId } }));
|
||||
useAgentResumeUpdateSubscription({ resumeId: data?.resume?.id, threadId });
|
||||
|
||||
const toggleThreadsPanel = useCallback(() => {
|
||||
const panel = threadsPanelRef.current;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useAgentResumeUpdateSubscription } from "./use-agent-resume-updates";
|
||||
|
||||
const subscriptionMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
const queryClientMock = vi.hoisted(() => ({
|
||||
invalidateQueries: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@tanstack/react-query", () => ({
|
||||
useQueryClient: () => queryClientMock,
|
||||
}));
|
||||
|
||||
vi.mock("@/features/resume/builder/draft", () => ({
|
||||
useResumeUpdateSubscription: subscriptionMock,
|
||||
}));
|
||||
|
||||
vi.mock("@/libs/orpc/client", () => ({
|
||||
orpc: {
|
||||
agent: {
|
||||
threads: {
|
||||
get: {
|
||||
queryKey: ({ input }: { input: { id: string } }) => ["agent", "threads", "get", input.id],
|
||||
},
|
||||
list: {
|
||||
queryKey: () => ["agent", "threads", "list"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("useAgentResumeUpdateSubscription", () => {
|
||||
beforeEach(() => {
|
||||
subscriptionMock.mockReset();
|
||||
queryClientMock.invalidateQueries.mockClear();
|
||||
});
|
||||
|
||||
it("invalidates the active thread and thread list when the working resume changes", async () => {
|
||||
renderHook(() =>
|
||||
useAgentResumeUpdateSubscription({
|
||||
resumeId: "resume-1",
|
||||
threadId: "thread-1",
|
||||
}),
|
||||
);
|
||||
|
||||
const options = subscriptionMock.mock.calls[0]?.[0] as
|
||||
| { resumeId?: string; onUpdate: () => Promise<void> }
|
||||
| undefined;
|
||||
expect(options?.resumeId).toBe("resume-1");
|
||||
|
||||
await options?.onUpdate();
|
||||
|
||||
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ["agent", "threads", "list"],
|
||||
});
|
||||
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ["agent", "threads", "get", "thread-1"],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useCallback } from "react";
|
||||
import { useResumeUpdateSubscription } from "@/features/resume/builder/draft";
|
||||
import { orpc } from "@/libs/orpc/client";
|
||||
|
||||
type UseAgentResumeUpdateSubscriptionInput = {
|
||||
resumeId?: string;
|
||||
threadId: string;
|
||||
};
|
||||
|
||||
export function useAgentResumeUpdateSubscription({ resumeId, threadId }: UseAgentResumeUpdateSubscriptionInput) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const onUpdate = useCallback(async () => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: orpc.agent.threads.list.queryKey() }),
|
||||
queryClient.invalidateQueries({ queryKey: orpc.agent.threads.get.queryKey({ input: { id: threadId } }) }),
|
||||
]);
|
||||
}, [queryClient, threadId]);
|
||||
|
||||
useResumeUpdateSubscription({
|
||||
resumeId,
|
||||
onUpdate,
|
||||
onError: useCallback((error: unknown) => {
|
||||
console.warn("Agent resume update stream failed, reconnecting:", error);
|
||||
}, []),
|
||||
});
|
||||
}
|
||||
@@ -8,11 +8,11 @@ import { useEffect, useRef } from "react";
|
||||
import { usePanelRef } from "react-resizable-panels";
|
||||
import { ResizableGroup, ResizablePanel, ResizableSeparator } from "@reactive-resume/ui/components/resizable";
|
||||
import {
|
||||
useBuilderResumeUpdateSubscription,
|
||||
useInitializeResumeStore,
|
||||
useMergeResumeMetadata,
|
||||
useResumeCleanup,
|
||||
useResumeStore,
|
||||
useResumeUpdateSubscription,
|
||||
} from "@/features/resume/builder/draft";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { orpc } from "@/libs/orpc/client";
|
||||
@@ -62,7 +62,7 @@ function RouteComponent() {
|
||||
const isInitialized = isReady && initializedResumeId === resumeId;
|
||||
|
||||
useResumeCleanup();
|
||||
useResumeUpdateSubscription();
|
||||
useBuilderResumeUpdateSubscription();
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) return;
|
||||
|
||||
Reference in New Issue
Block a user