Session Routing Rewrite
The Rule
One function. One rule.
getSessionTarget(sessionId) → div[data-session=X]
Every event has a session ID. Every session has a div. Event comes in → has session ID → find the div → put content there. No botId. No domain. No active session comparison. No hide/show filtering. The div might be hidden (viewing different session). Content still goes in. When user switches to that session, everything is already there.
state.ts — The Router
- Rewrite
getSessionTarget()(line 136)- Takes ONLY sessionId
document.querySelector('.session-messages[data-session=X]')- If div doesn't exist → create it (hidden, inside bot container)
- No botId param, no domain param, no fallback to active session lookup
- Kill
getActiveChatSessionId()fallback inside this function
render.ts — addMessage
-
addMessage()(line 454)- Accept sessionId param (already partially done)
- Call
getSessionTarget(sessionId)— notgetSessionTarget(domain) - Every caller must pass sessionId
message-list.ts — renderMessages + tool events
renderMessages()(line 262)- Caller must pass sessionId
- Route to
getSessionTarget(sessionId)
handleToolEvent()(line 1035)- Already extracts toolCsId from event.sessionKey
- Pass to
getSessionTarget(toolCsId)— notgetSessionTarget(activeBotId, toolCsId)
chat-events.ts — Delta/final/error streaming
Remove active session skip block (line 182-208)
- Currently: if eventSessionKey !== activeSessionKey → skip rendering
- New: route to
getSessionTarget(sessionId)always. Content goes to its div. - Keep the unread badge increment for background sessions
Guest session handler (line 135)
- Route to session div
Delta typing repositioning (line 303)
- Use
getSessionTarget(sessionId)from payload
- Use
Final cleanup (line 449)
- Use
getSessionTarget(sessionId)from payload
- Use
main.ts — SSE event handlers
queue:senthandler (line 567)- Route to session div using event's sessionId
new_messagehandler (line 669-679)- Remove "div doesn't exist? skip" logic
- Get sessionId from event.chatSessionId
- Pass sessionId to
addMessage() - Content goes to session div, whether visible or not
Tool event session filter (line 501-506)
- Already removed skip logic (content goes to div)
- Typing label update: only for active session (cosmetic, not content routing)
typing.ts — Typing indicators (8 call sites)
-
showTyping()(line 23) — route to session div -
showTyping()fallback (line 70) — route to session div -
hideTyping()(line 131) — route to session div - Hook typing show (line 170) — route to session div
- Hook typing hide (line 182) — route to session div
- Hook message show (line 194) — route to session div
- Hook message update (line 210) — route to session div
- Finalize hook response (line 225) — route to session div
history.ts — History loading
- Warm target check (line 118) — use session div
- Load older messages container (line 451) — use session div
- Scroll sentinel setup (line 750) — use session div
- Scroll load handler (line 798) — use session div
spawn-bubbles.ts — CS spawn UI (4 call sites)
-
renderSpawnBubble()(line 64) — route to session div -
updateSpawnBubble()(line 119) — route to session div -
completeSpawnBubble()(line 233) — route to session div - Active container in complete (line 289) — route to session div
messaging.ts — Send + queue
- Send message optimistic render (line 348) — route to session div
- Queue dock legacy cleanup (line 561) — route to session div
- Queue sent render (line 931) — route to session div
- Queue dock filter by active session (already done)
sessions.ts — Working state
- Context usage bar container (line 36) — route to session div
tasks-sidebar.ts — Task UI
- Task complete render (line 167) — route to session div
- Task action render (line 288) — route to session div
ui.ts — Bot switching
- Empty state check (line 770) — route to session div
- Bot switch scroll restore (line 2752) — route to session div
Server side (already done, needs restart)
-
message-capture.ts:669— chatSessionId in new_message SSE event -
message-capture.ts:835— chatSessionId in hook new_message SSE event -
relay.ts:211— chatSessionId in relay new_message SSE event -
bot-state.ts— isSessionWorking() for per-session working check -
message-queue.ts:39— use isSessionWorking instead of isBotWorking
Other (already done)
- Scroll position save/restore per session in showSessionContainer
- Queue dock filters by active session ID
Deploy
- Restart GATE server (pm2 restart gate)
- Build chat bundle (bun run build:chat)
- Verify: send message to session 301 while viewing 305 → goes to 301's div
- Verify: tool events for session 305 → go to 305's div, not current view
- Verify: switch sessions → scroll position preserved
- Verify: queue dock only shows items for active session
Summary
34 frontend changes across 11 files. 5 server changes done. 1 restart.
One function. One rule. Session ID in, div out.