diff --git a/lib/components/RunFrameWithApi/api-base.ts b/lib/components/RunFrameWithApi/api-base.ts index 7bb542f9a..beed3eb03 100644 --- a/lib/components/RunFrameWithApi/api-base.ts +++ b/lib/components/RunFrameWithApi/api-base.ts @@ -1 +1,4 @@ -export const API_BASE = window.TSCIRCUIT_FILESERVER_API_BASE_URL ?? "/api" +export const API_BASE = + (typeof window !== "undefined" + ? window.TSCIRCUIT_FILESERVER_API_BASE_URL + : undefined) ?? "/api" diff --git a/lib/components/RunFrameWithApi/store.ts b/lib/components/RunFrameWithApi/store.ts index 65a7b4b11..4e054acf2 100644 --- a/lib/components/RunFrameWithApi/store.ts +++ b/lib/components/RunFrameWithApi/store.ts @@ -140,6 +140,10 @@ export const useRunFrameStore = create()( try { const events = await getEvents(state.lastEventTime) + if (state.error) { + set({ error: null }) + } + if (events.length > 0) { // Update lastEventTime to most recent event const newLastEventTime = events[events.length - 1].created_at @@ -150,7 +154,7 @@ export const useRunFrameStore = create()( ) set((state) => ({ - recentEvents: [...state.recentEvents, ...events].slice(0, 100), + recentEvents: [...state.recentEvents, ...events].slice(-100), lastEventTime: newLastEventTime, // TODO sort // .sort((a, b) => b.created_at.localeCompare(a.created_at)), diff --git a/tests/runframe-api-polling-recovery.test.ts b/tests/runframe-api-polling-recovery.test.ts new file mode 100644 index 000000000..61b50dedd --- /dev/null +++ b/tests/runframe-api-polling-recovery.test.ts @@ -0,0 +1,43 @@ +import { expect, test, describe } from "bun:test" +import { useRunFrameStore } from "../lib/components/RunFrameWithApi/store" + +describe("RunFrameWithApi store polling recovery and recentEvents queue", () => { + test("recovers error state to null when polling succeeds after failure", () => { + const store = useRunFrameStore + + // Simulate initial error state from disconnected dev server + store.setState({ error: new Error("Dev server disconnected") }) + expect(store.getState().error).not.toBeNull() + + // When a poll succeeds, error is cleared + if (store.getState().error) { + store.setState({ error: null }) + } + expect(store.getState().error).toBeNull() + }) + + test("slice(-100) preserves the newest events instead of discarding them", () => { + // Generate 150 events + const initialEvents = Array.from({ length: 100 }, (_, i) => ({ + event_id: `old_${i}`, + event_type: "FILE_UPDATED", + created_at: new Date(Date.now() - (150 - i) * 1000).toISOString(), + })) + + const newEvents = Array.from({ length: 20 }, (_, i) => ({ + event_id: `new_${i}`, + event_type: "FILE_UPDATED", + created_at: new Date().toISOString(), + })) + + // Buggy behavior: .slice(0, 100) discards all newEvents + const buggySlice = [...initialEvents, ...newEvents].slice(0, 100) + expect(buggySlice[buggySlice.length - 1].event_id).toBe("old_99") + + // Fixed behavior: .slice(-100) keeps the 100 newest events + const fixedSlice = [...initialEvents, ...newEvents].slice(-100) + expect(fixedSlice.length).toBe(100) + expect(fixedSlice[fixedSlice.length - 1].event_id).toBe("new_19") + expect(fixedSlice.some((e) => e.event_id.startsWith("new_"))).toBe(true) + }) +})