From bc18c9d0d35f4ffd7550e8f1e4f4097e7c85a354 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 22:31:32 +0000 Subject: [PATCH 1/3] Initial plan From 2d98cb2d0eb7152da74447f55cda20b406747b7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 22:36:04 +0000 Subject: [PATCH 2/3] fix(ui): refresh flags cache on non-error stream events --- ui/src/app/flags/streamingUtils.ts | 3 +++ ui/src/store.test.ts | 17 +++++++++++++++++ ui/src/store.ts | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/flags/streamingUtils.ts create mode 100644 ui/src/store.test.ts diff --git a/ui/src/app/flags/streamingUtils.ts b/ui/src/app/flags/streamingUtils.ts new file mode 100644 index 0000000000..e800efd1e6 --- /dev/null +++ b/ui/src/app/flags/streamingUtils.ts @@ -0,0 +1,3 @@ +export const shouldInvalidateFromStreamEvent = ( + eventData: { type?: string; etag?: string } | null +) => eventData?.type !== 'error'; diff --git a/ui/src/store.test.ts b/ui/src/store.test.ts new file mode 100644 index 0000000000..41eee96281 --- /dev/null +++ b/ui/src/store.test.ts @@ -0,0 +1,17 @@ +import { shouldInvalidateFromStreamEvent } from '~/app/flags/streamingUtils'; + +describe('shouldInvalidateFromStreamEvent', () => { + it('invalidates for refetchEvaluation events', () => { + expect(shouldInvalidateFromStreamEvent({ type: 'refetchEvaluation' })).toBe( + true + ); + }); + + it('invalidates for stream payloads without a type', () => { + expect(shouldInvalidateFromStreamEvent({ etag: 'digest' })).toBe(true); + }); + + it('does not invalidate for error events', () => { + expect(shouldInvalidateFromStreamEvent({ type: 'error' })).toBe(false); + }); +}); diff --git a/ui/src/store.ts b/ui/src/store.ts index aeb56bfd0d..dce51c764a 100644 --- a/ui/src/store.ts +++ b/ui/src/store.ts @@ -14,6 +14,7 @@ import { eventKey, eventSlice } from '~/app/events/eventSlice'; import { analyticsApi } from '~/app/flags/analyticsApi'; import { flagsApi, flagsTableSlice } from '~/app/flags/flagsApi'; import { eventReceived, streamingReducer } from '~/app/flags/streamingApi'; +import { shouldInvalidateFromStreamEvent } from '~/app/flags/streamingUtils'; import { metaSlice } from '~/app/meta/metaSlice'; import { namespaceApi, @@ -114,7 +115,7 @@ listenerMiddleware.startListening({ etag?: string; } | null; - if (eventData?.type !== 'refetchEvaluation') { + if (!shouldInvalidateFromStreamEvent(eventData)) { console.warn('unexpected sse event', eventData); return; } From dd0d9ece04d3236f074a66180181027106f78ef3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 22:39:48 +0000 Subject: [PATCH 3/3] test(ui): cover stream event invalidation guard --- ui/src/app/flags/streamingUtils.ts | 6 +++++- ui/src/store.test.ts | 4 ++++ ui/src/store.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/src/app/flags/streamingUtils.ts b/ui/src/app/flags/streamingUtils.ts index e800efd1e6..dd0246d781 100644 --- a/ui/src/app/flags/streamingUtils.ts +++ b/ui/src/app/flags/streamingUtils.ts @@ -1,3 +1,7 @@ +/** + * Stream payloads without an explicit `type` can still represent data updates. + * We only skip invalidation for explicit error events. + */ export const shouldInvalidateFromStreamEvent = ( eventData: { type?: string; etag?: string } | null -) => eventData?.type !== 'error'; +) => eventData !== null && eventData.type !== 'error'; diff --git a/ui/src/store.test.ts b/ui/src/store.test.ts index 41eee96281..2d0d142c21 100644 --- a/ui/src/store.test.ts +++ b/ui/src/store.test.ts @@ -14,4 +14,8 @@ describe('shouldInvalidateFromStreamEvent', () => { it('does not invalidate for error events', () => { expect(shouldInvalidateFromStreamEvent({ type: 'error' })).toBe(false); }); + + it('does not invalidate for null payloads', () => { + expect(shouldInvalidateFromStreamEvent(null)).toBe(false); + }); }); diff --git a/ui/src/store.ts b/ui/src/store.ts index dce51c764a..ac4b164b92 100644 --- a/ui/src/store.ts +++ b/ui/src/store.ts @@ -116,7 +116,7 @@ listenerMiddleware.startListening({ } | null; if (!shouldInvalidateFromStreamEvent(eventData)) { - console.warn('unexpected sse event', eventData); + console.warn('skipping cache invalidation for sse event', eventData); return; }