feat(realtime): skip WS refetch for self-triggered events

Backend WS messages now include actor_id from the Event struct.
Frontend useRealtimeSync skips the debounced refetch when the event
was triggered by the current user, eliminating unnecessary re-renders
of heavy components (~400ms after each user action).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing 2026-03-31 11:25:41 +08:00
parent c1f81427f6
commit 3e8c715de8
3 changed files with 11 additions and 4 deletions

View file

@ -68,6 +68,11 @@ export function useRealtimeSync(ws: WSClient | null) {
};
const unsubAny = ws.onAny((msg) => {
const myUserId = useAuthStore.getState().user?.id;
if (msg.actor_id && msg.actor_id === myUserId) {
logger.debug("skipping self-event", msg.type);
return;
}
const prefix = msg.type.split(":")[0] ?? "";
const refresh = refreshMap[prefix];
if (refresh) debouncedRefresh(prefix, refresh);

View file

@ -47,6 +47,7 @@ export type WSEventType =
export interface WSMessage<T = unknown> {
type: WSEventType;
payload: T;
actor_id?: string;
}
export interface IssueCreatedPayload {

View file

@ -30,7 +30,7 @@ func registerListeners(bus *events.Bus, hub *realtime.Hub) {
if recipientID == "" {
return
}
data, err := json.Marshal(map[string]any{"type": e.Type, "payload": e.Payload})
data, err := json.Marshal(map[string]any{"type": e.Type, "payload": e.Payload, "actor_id": e.ActorID})
if err != nil {
return
}
@ -87,7 +87,7 @@ func registerListeners(bus *events.Bus, hub *realtime.Hub) {
if userID == "" {
return
}
data, err := json.Marshal(map[string]any{"type": e.Type, "payload": e.Payload})
data, err := json.Marshal(map[string]any{"type": e.Type, "payload": e.Payload, "actor_id": e.ActorID})
if err != nil {
return
}
@ -102,8 +102,9 @@ func registerListeners(bus *events.Bus, hub *realtime.Hub) {
}
msg := map[string]any{
"type": e.Type,
"payload": e.Payload,
"type": e.Type,
"payload": e.Payload,
"actor_id": e.ActorID,
}
data, err := json.Marshal(msg)
if err != nil {