refactor(chat): trim non-rendered realtime state from session flow

The frontend realtime pipeline was carrying control-plane events and unused state
through message storage as if they were renderable chat content. That made the
session path noisier than necessary and increased the chance of subtle drift
between transport events and UI data.

Why this change:
- Control events like session_created, status, complete, and permission lifecycle
  updates drive UI side effects, not chat transcript rendering.
- Persisting those events in the session store added avoidable churn, memory
  growth, and merge work while providing no user-visible value.
- An unused streamBufferRef existed in the hot path, creating extra writes and
  cognitive overhead with no read consumer.
- useChatRealtimeHandlers accepted selectedProject even though it was not used,
  which widened the hook surface and dependency noise without behavior impact.

What this commit does:
- Removes the write-only streamBufferRef from ChatInterface and realtime handler
  wiring.
- Removes the unused selectedProject argument from useChatRealtimeHandlers.
- Stops appending non-rendered control events to sessionStore realtime messages.
  These events still execute their side effects exactly as before.

Net effect:
The Codex/Claude/Cursor/Gemini realtime path stays behaviorally equivalent for
users, but the data model now stores only message content that can actually be
rendered, reducing unnecessary state traffic in the chat runtime.
This commit is contained in:
Haileyesus
2026-05-05 17:23:17 +03:00
parent b3fe1b4392
commit c064aff568
2 changed files with 11 additions and 15 deletions

View File

@@ -3,7 +3,7 @@ import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
import { usePaletteOps } from '../../../contexts/PaletteOpsContext';
import type { PendingPermissionRequest, SessionNavigationOptions } from '../types/types';
import type { Project, ProjectSession, LLMProvider } from '../../../types/app';
import type { ProjectSession, LLMProvider } from '../../../types/app';
import type { SessionStore, NormalizedMessage } from '../../../stores/useSessionStore';
type PendingViewSession = {
@@ -51,7 +51,6 @@ type LatestChatMessage = {
interface UseChatRealtimeHandlersArgs {
latestMessage: LatestChatMessage | null;
provider: LLMProvider;
selectedProject: Project | null;
selectedSession: ProjectSession | null;
currentSessionId: string | null;
setCurrentSessionId: (sessionId: string | null) => void;
@@ -61,7 +60,6 @@ interface UseChatRealtimeHandlersArgs {
setTokenBudget: (budget: Record<string, unknown> | null) => void;
setPendingPermissionRequests: Dispatch<SetStateAction<PendingPermissionRequest[]>>;
pendingViewSessionRef: MutableRefObject<PendingViewSession | null>;
streamBufferRef: MutableRefObject<string>;
streamTimerRef: MutableRefObject<number | null>;
accumulatedStreamRef: MutableRefObject<string>;
onSessionInactive?: (sessionId?: string | null) => void;
@@ -80,7 +78,6 @@ interface UseChatRealtimeHandlersArgs {
export function useChatRealtimeHandlers({
latestMessage,
provider,
selectedProject,
selectedSession,
currentSessionId,
setCurrentSessionId,
@@ -90,7 +87,6 @@ export function useChatRealtimeHandlers({
setTokenBudget,
setPendingPermissionRequests,
pendingViewSessionRef,
streamBufferRef,
streamTimerRef,
accumulatedStreamRef,
onSessionInactive,
@@ -187,7 +183,6 @@ export function useChatRealtimeHandlers({
if (msg.kind === 'stream_delta') {
const text = msg.content || '';
if (!text) return;
streamBufferRef.current += text;
accumulatedStreamRef.current += text;
if (!streamTimerRef.current) {
streamTimerRef.current = window.setTimeout(() => {
@@ -216,12 +211,18 @@ export function useChatRealtimeHandlers({
sessionStore.finalizeStreaming(sid);
}
accumulatedStreamRef.current = '';
streamBufferRef.current = '';
return;
}
// --- All other messages: route to store ---
if (sid) {
const shouldPersist =
msg.kind !== 'session_created'
&& msg.kind !== 'complete'
&& msg.kind !== 'status'
&& msg.kind !== 'permission_request'
&& msg.kind !== 'permission_cancelled';
if (sid && shouldPersist) {
sessionStore.appendRealtime(sid, msg as NormalizedMessage);
}
@@ -232,6 +233,8 @@ export function useChatRealtimeHandlers({
if (!newSessionId) break;
if (!currentSessionId || currentSessionId.startsWith('new-session-')) {
console.log('Session created with ID:', newSessionId);
console.log('Existing session ID:', currentSessionId);
sessionStorage.setItem('pendingSessionId', newSessionId);
if (pendingViewSessionRef.current && !pendingViewSessionRef.current.sessionId) {
pendingViewSessionRef.current.sessionId = newSessionId;
@@ -257,7 +260,6 @@ export function useChatRealtimeHandlers({
sessionStore.finalizeStreaming(sid);
}
accumulatedStreamRef.current = '';
streamBufferRef.current = '';
setIsLoading(false);
setCanAbortSession(false);
@@ -386,7 +388,6 @@ export function useChatRealtimeHandlers({
}, [
latestMessage,
provider,
selectedProject,
selectedSession,
currentSessionId,
setCurrentSessionId,
@@ -396,7 +397,6 @@ export function useChatRealtimeHandlers({
setTokenBudget,
setPendingPermissionRequests,
pendingViewSessionRef,
streamBufferRef,
streamTimerRef,
accumulatedStreamRef,
onSessionInactive,

View File

@@ -50,7 +50,6 @@ function ChatInterface({
const { t } = useTranslation('chat');
const sessionStore = useSessionStore();
const streamBufferRef = useRef('');
const streamTimerRef = useRef<number | null>(null);
const accumulatedStreamRef = useRef('');
const pendingViewSessionRef = useRef<PendingViewSession | null>(null);
@@ -60,7 +59,6 @@ function ChatInterface({
clearTimeout(streamTimerRef.current);
streamTimerRef.current = null;
}
streamBufferRef.current = '';
accumulatedStreamRef.current = '';
}, []);
@@ -225,7 +223,6 @@ function ChatInterface({
useChatRealtimeHandlers({
latestMessage,
provider,
selectedProject,
selectedSession,
currentSessionId,
setCurrentSessionId,
@@ -235,7 +232,6 @@ function ChatInterface({
setTokenBudget,
setPendingPermissionRequests,
pendingViewSessionRef,
streamBufferRef,
streamTimerRef,
accumulatedStreamRef,
onSessionInactive,