Skip to content

fix(AGENT-675): add sessionStorage for multi-tab chat isolation#26

Open
diecoscai wants to merge 3 commits into
a-mainfrom
feature/AGENT-675-fix-multi-tab-localstorage
Open

fix(AGENT-675): add sessionStorage for multi-tab chat isolation#26
diecoscai wants to merge 3 commits into
a-mainfrom
feature/AGENT-675-fix-multi-tab-localstorage

Conversation

@diecoscai
Copy link
Copy Markdown

@diecoscai diecoscai commented Mar 23, 2026

Summary

Implements AGENT-675 by making chat state tab-scoped with sessionStorage as the primary store, while keeping localStorage for compatibility/migration.

Why this change

Users with multiple tabs were sharing the same localStorage key, which caused state collisions (for example, feedback and chat history crossing between tabs). This PR isolates active tab state to eliminate cross-tab contamination while preserving existing persisted behavior.

What changed

1) src/components/Bot.tsx

  • Uses sessionStorage as the primary source for active-tab chat state.
  • Falls back to localStorage when session data is missing (returning users/new tab).
  • Seeds session storage from local storage when needed.
  • Writes chat history to both stores during message updates and stream end.
  • Clears both stores on chat reset (removeSessionStorageChatHistory + removeLocalStorageChatHistory).

2) src/components/bubbles/BotBubble.tsx

  • Rating updates now write to both session and local storage to keep state consistent.

3) src/components/bubbles/LeadCaptureBubble.tsx

  • Lead capture now writes to both session and local storage.

4) src/utils/index.ts

  • Added session storage helpers:
    • setSessionStorageChatflow
    • getSessionStorageChatflow
    • removeSessionStorageChatHistory
  • Included two safety fixes:
    • Removed incorrect fallback behavior in session storage catch path.
    • Preserved lead data when clearing session chat history (parity with local storage behavior).

Behavior after this PR

  • New independent tabs: isolated active chat state (no cross-tab leaks).
  • Returning user / reload: previous data still recoverable via local storage fallback.
  • Clear chat: chat history is cleared but lead data remains preserved.

Notes

  • This PR intentionally addresses tab isolation for normal multi-tab usage.
  • Duplicate-tab browser behavior (which can clone session state depending on browser) is out of scope for this ticket.

Linear

Resolves AGENT-675

diecoscai and others added 2 commits February 16, 2026 12:07
Use sessionStorage as primary store (tab-scoped) with localStorage as
backup for returning users. On mount, prefer sessionStorage; fall back
to localStorage and seed sessionStorage from it. Ratings and lead data
write to both stores.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear Bot commented Mar 23, 2026

@diecoscai diecoscai self-assigned this Mar 23, 2026
@diecoscai
Copy link
Copy Markdown
Author

Code Review — AGENT-675 sessionStorage tab isolation

Verdict: ✅ Ready to merge — The core approach is correct and the three-commit arc on this branch shows good iteration. The implementation does what it claims.


What's solid

  • Approach is right. sessionStorage is tab-scoped by spec — no locking, no events, no coordination needed. Using it as primary and localStorage as compat fallback is the correct pattern for embedded widget tab isolation.
  • Dual-write is consistent. Every mutation path (message update, stream end, rating, lead capture, clear) writes to both stores. No write path was missed.
  • chatId rename fixes a real pre-existing bug. The original const chatId = params.chatId in fetchResponseFromEventStream shadowed the SolidJS chatId() reactive signal. The new chatId() || requestChatId correctly prefers the signal's live value. Good catch.
  • Lead preservation layering is correct. removeSessionStorageChatHistory preserves lead (mirrors localStorage behavior), and the init path uses chatMessage.lead || localStorageMessage?.lead as belt-and-suspenders. The two follow-up commits (7862e97, 7f429fe) tightened this correctly.
  • Seed-on-mount logic handles the returning-user case. First load in a fresh tab hydrates from localStorage and seeds sessionStorage, so subsequent writes stay tab-local.

Non-blocking nit — BotBubble.tsx bypasses shared helpers

saveToLocalStorage() constructs the storage key manually and calls sessionStorage.getItem / setItem directly, while everything else in this PR uses the getSessionStorageChatflow / setSessionStorageChatflow helpers:

// Current — raw access, bypasses helpers:
const chatDetails = sessionStorage.getItem(`${props.chatflowid}_EXTERNAL`) || localStorage.getItem(`${props.chatflowid}_EXTERNAL`);
// ...
sessionStorage.setItem(`${props.chatflowid}_EXTERNAL`, updated);
localStorage.setItem(`${props.chatflowid}_EXTERNAL`, updated);

Functionally equivalent today, but if the key schema or merge logic in the helpers changes, this won't pick it up. Can be a follow-up cleanup — not blocking this PR.


Out-of-scope (no action needed)

  • Duplicate-tab session cloning (browser clones sessionStorage on Ctrl+Duplicate): correctly scoped out in PR description.
  • BroadcastChannel for live cross-tab localStorage sync: would be the next increment if two tabs actively writing localStorage becomes a problem, but not required here.
  • TTL/version envelope on stored values: not this PR's scope.

Merge sequence

  1. ✅ Merge this PR → a-main
  2. Note the resulting a-main HEAD SHA
  3. Update theanswer#973 submodule pointer to that SHA (currently stale — see comment on that PR)
  4. CI re-runs on #973 → merge to staging

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant