Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/components/RunFrameWithApi/api-base.ts
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 5 additions & 1 deletion lib/components/RunFrameWithApi/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ export const useRunFrameStore = create<RunFrameState>()(
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
Expand All @@ -150,7 +154,7 @@ export const useRunFrameStore = create<RunFrameState>()(
)

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)),
Expand Down
43 changes: 43 additions & 0 deletions tests/runframe-api-polling-recovery.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
Loading