From 1b8dfb95407cda1dc705afa5f529e703a4f3c092 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Thu, 2 Apr 2026 18:46:07 +0800 Subject: [PATCH] fix(realtime): update issue in-place on WS event to prevent inbox loading flash When viewing an issue in the inbox, issue:updated WS events triggered a full useIssueStore.fetch() that replaced the entire issues array. This caused all consumers to re-render, producing a loading flash. Now issue:updated events use the WS payload to surgically update the single affected issue via updateIssue(), avoiding array replacement. Other issue events (created/deleted) still use full refetch. WS reconnect recovery path is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/web/features/realtime/use-realtime-sync.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/web/features/realtime/use-realtime-sync.ts b/apps/web/features/realtime/use-realtime-sync.ts index 74ba9a718..108a42d6f 100644 --- a/apps/web/features/realtime/use-realtime-sync.ts +++ b/apps/web/features/realtime/use-realtime-sync.ts @@ -74,6 +74,20 @@ export function useRealtimeSync(ws: WSClient | null) { logger.debug("skipping self-event", msg.type); return; } + + // issue:updated — update in-place using WS payload instead of full + // refetch. Replacing the entire issues array causes every consumer + // (including IssueDetail in the inbox) to re-render, which can flash + // a loading state. The payload already contains the full issue object + // so a surgical merge is safe. + if (msg.type === "issue:updated") { + const { issue } = (msg.payload ?? {}) as IssueUpdatedPayload; + if (issue?.id) { + useIssueStore.getState().updateIssue(issue.id, issue); + } + return; + } + const prefix = msg.type.split(":")[0] ?? ""; const refresh = refreshMap[prefix]; if (refresh) debouncedRefresh(prefix, refresh);