mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 19:35:13 +01:00
Prevent duplicate interaction submissions in runner lab
This commit is contained in:
1 parent
bbbd134b09
commit
c347e99332
2 files changed
+43
-16
No files matched your search
@@ -1010,7 +1010,9 @@ test.describe("pending-request live region", () => {
|
||||
await expect(live).toHaveText(PENDING_ANNOUNCEMENT);
|
||||
await expect(page.locator('[data-composer-state="waiting"]')).toBeVisible();
|
||||
|
||||
let interactionRequests = 0;
|
||||
await page.route("**/api/capability/ui/interaction", async (route) => {
|
||||
interactionRequests += 1;
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
@@ -1026,6 +1028,7 @@ test.describe("pending-request live region", () => {
|
||||
await card.getByRole("button", { name: "Submit answers" }).click();
|
||||
|
||||
await expect(card).toHaveAttribute("data-interaction-state", "answered");
|
||||
expect(interactionRequests).toBe(1);
|
||||
await expect(page.locator('[data-composer-state="ready"]')).toHaveCount(1);
|
||||
await expect(live).not.toContainText(PENDING_ANNOUNCEMENT);
|
||||
await expect(live).toHaveText("Your answer was recorded.");
|
||||
|
||||
@@ -526,30 +526,54 @@ export function App() {
|
||||
|
||||
const respond = useCallback(
|
||||
(response: CapabilityInteractionResponse) => {
|
||||
setSnapshot((current) => {
|
||||
if (current === null) return current;
|
||||
if (route.mode === "live") {
|
||||
void capabilityLiveClient
|
||||
.respond(current.sessionId, response.interactionId, response.outcome, response.result)
|
||||
.then((next) => setSnapshot(next.view))
|
||||
.catch((cause) => setActionError(describe(cause)));
|
||||
return {
|
||||
if (snapshot === null) return;
|
||||
if (route.mode !== "live") {
|
||||
setSnapshot(applyFakeInteractionResponse(snapshot, response));
|
||||
setAnnouncement(ANSWERED_ANNOUNCEMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
// Network effects must never live inside a React state updater. Strict
|
||||
// Mode intentionally invokes updater functions more than once in
|
||||
// development, which previously submitted every interaction twice: the
|
||||
// first request resolved it and the duplicate produced "not pending".
|
||||
const sessionId = snapshot.sessionId;
|
||||
setActionError(null);
|
||||
setSnapshot((current) => current === null ? current : {
|
||||
...current,
|
||||
turns: current.turns.map((turn) => ({
|
||||
...turn,
|
||||
items: turn.items.map((item) =>
|
||||
item.kind === "interaction" && item.interactionId === response.interactionId
|
||||
? { ...item, state: "submitting" as const, stateLabel: "Submitting…" }
|
||||
: item,
|
||||
),
|
||||
})),
|
||||
});
|
||||
void capabilityLiveClient
|
||||
.respond(sessionId, response.interactionId, response.outcome, response.result)
|
||||
.then((next) => {
|
||||
setSnapshot(next.view);
|
||||
setAnnouncement(ANSWERED_ANNOUNCEMENT);
|
||||
})
|
||||
.catch((cause) => {
|
||||
setActionError(describe(cause));
|
||||
setSnapshot((current) => current === null ? current : {
|
||||
...current,
|
||||
turns: current.turns.map((turn) => ({
|
||||
...turn,
|
||||
items: turn.items.map((item) =>
|
||||
item.kind === "interaction" && item.interactionId === response.interactionId
|
||||
? { ...item, state: "submitting" as const, stateLabel: "Submitting…" }
|
||||
item.kind === "interaction" &&
|
||||
item.interactionId === response.interactionId &&
|
||||
item.state === "submitting"
|
||||
? { ...item, state: "pending" as const, stateLabel: "Waiting for you" }
|
||||
: item,
|
||||
),
|
||||
})),
|
||||
};
|
||||
}
|
||||
return applyFakeInteractionResponse(current, response);
|
||||
});
|
||||
setAnnouncement(ANSWERED_ANNOUNCEMENT);
|
||||
});
|
||||
});
|
||||
},
|
||||
[route.mode],
|
||||
[route.mode, snapshot],
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user