fix(session): restore session URL hash writes (removed in cf4e240a) (#5872)

* Fix: restore session URL hash writes (removed in cf4e240a)

Restores history.replaceState() calls in selectSession() and
materializePendingSession() that were dropped during the July 23 merge.
Without these, chat URLs never update the address bar hash, making
sessions unshareable and causing bare-URL reloads to land on the
welcome screen instead of restoring the last active chat.

Root cause: selectSession() had its hash-write deliberately removed;
materializePendingSession() lost its during a larger refactor that
added the stale-response and incognito guards.

Fixes #5870 (upstream)

* fix: session URL hash lost when sending message mid-stream

Two independent bugs caused the session hash to disappear from the URL:

Bug 1 — ReferenceError in catch block silently killed error recovery
  In handleChatSubmit, two const variables (streamingTTS at line 1922 and
  abortCtrl at line 1741) were declared inside the try block but referenced
  in the catch block. Since const is block-scoped in JavaScript, they were
  undefined in catch, causing a ReferenceError that silently aborted the
  error handler. This prevented materializePendingSession() from ever being
  called, so no hash was written to the URL.
  Fix: Hoisted both as let declarations before the try { block.

Bug 2 — Dual sessions.js ES module instances with mismatched state
  app.js imported sessions.js with a version query string
  (?v=20260722ctxheader4) while every other module imported ./sessions.js
  without one. The browser treated them as different URLs, creating two
  separate module instances with independent _pendingChat and
  currentSessionId state. createDirectChat() set pending on one instance
  while handleChatSubmit() checked hasPendingChat() on the other — so the
  pending session never materialized.
  Fix: Removed the version query string from the sessions.js import in
  app.js and from the modulepreload + script tags in index.html. All
  modules now share a single sessions.js instance.

Bonus guard: _adoptOpenedSessionBeforeAutoCreate() now checks
hasPendingChat() before adopting a stale DOM-active session, preventing
the send path from landing in the wrong session when a New Chat is pending.

---------

Co-authored-by: samy <samy@users.noreply.github.com>
This commit is contained in:
Samy
2026-08-07 16:04:53 -04:00
committed by GitHub
parent 99566d28b5
commit f06a0a30a8
4 changed files with 18 additions and 8 deletions
+2 -2
View File
@@ -10,14 +10,14 @@ import modelsModule from './js/models.js?v=20260715startupcalm2';
import ragModule from './js/rag.js';
import presetsModule from './js/presets.js';
import searchModule from './js/search.js';
import chatModule from './js/chat.js?v=20260722ctxheader4';
import chatModule from './js/chat.js?v=20260801fix1';
import compareModule from './js/compare/index.js?v=20260723compareicon2';
import documentModule from './js/document.js?v=20260722emailfastindex1';
import searchChatModule from './js/search-chat.js';
import { makeWindowDraggable } from './js/windowDrag.js';
import markdownModule from './js/markdown.js';
import chatRenderer from './js/chatRenderer.js?v=20260722emailfastindex1';
import sessionModule from './js/sessions.js?v=20260722ctxheader4';
import sessionModule from './js/sessions.js';
import memoryModule from './js/memory.js?v=20260722memoryloading1';
import voiceRecorderModule from './js/voiceRecorder.js';
import censorModule from './js/censor.js';
+4 -4
View File
@@ -250,9 +250,9 @@
</script>
<link rel="stylesheet" href="/static/style.css?v=20260723tasksbulkfeedback1">
<link rel="modulepreload" href="/static/app.js?v=20260723tasksbulkfeedback1">
<link rel="modulepreload" href="/static/js/chat.js?v=20260722ctxheader4">
<link rel="modulepreload" href="/static/js/chat.js?v=20260801fix1">
<link rel="modulepreload" href="/static/js/ui.js">
<link rel="modulepreload" href="/static/js/sessions.js?v=20260722ctxheader4">
<link rel="modulepreload" href="/static/js/sessions.js">
<link rel="modulepreload" href="/static/js/markdown.js">
</head>
<body>
@@ -2504,7 +2504,7 @@
<script type="module" src="/static/js/ui.js"></script>
<script type="module" src="/static/js/markdown.js"></script>
<script type="module" src="/static/js/dragSort.js"></script>
<script type="module" src="/static/js/sessions.js?v=20260722ctxheader4"></script>
<script type="module" src="/static/js/sessions.js"></script>
<script type="module" src="/static/js/memory.js?v=20260722memoryloading1"></script>
<script type="module" src="/static/js/skills.js"></script>
<script type="module" src="/static/js/tourHints.js"></script>
@@ -2522,7 +2522,7 @@
<script type="module" src="/static/js/chatRenderer.js?v=20260722emailfastindex1"></script>
<script type="module" src="/static/js/codeRunner.js"></script>
<script type="module" src="/static/js/chatStream.js?v=20260722emailfastindex1"></script>
<script type="module" src="/static/js/chat.js?v=20260722ctxheader4"></script>
<script type="module" src="/static/js/chat.js?v=20260801fix1"></script>
<script type="module" src="/static/js/cookbook.js"></script>
<script src="/static/js/cookbookSchedule.js"></script>
<script type="module" src="/static/js/search-chat.js"></script>
+7 -2
View File
@@ -349,6 +349,9 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr
async function _adoptOpenedSessionBeforeAutoCreate() {
if (!sessionModule || !sessionModule.getCurrentSessionId || sessionModule.getCurrentSessionId()) return true;
// Don't adopt a stale session when the user explicitly started a New Chat
// (pending state set) — the send path must materialize the pending session.
if (sessionModule.hasPendingChat && sessionModule.hasPendingChat()) return false;
const activeRowId = document.querySelector('.list-item.active-session[data-session-id], .session-item.active[data-session-id]')?.dataset?.sessionId || '';
const hashId = _hashSessionCandidate();
const lastSelectedId = String(window.__odysseusLastSelectedSessionId || '').trim();
@@ -1403,6 +1406,8 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr
currentAccumulated = '';
currentHolder = null;
let abortCtrl = null;
let streamingTTS = false;
try {
// Re-enable auto-scroll when user sends a message
uiModule.setAutoScroll(true);
@@ -1716,7 +1721,7 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr
}
const abortCtrl = new AbortController();
abortCtrl = new AbortController();
abortCtrl._reason = '';
currentAbort = abortCtrl;
@@ -1897,7 +1902,7 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr
let isThinking = false;
let thinkingStartTime = null;
// Streaming TTS: synthesize sentence-by-sentence during streaming
const streamingTTS = !!(window.aiTTSManager && window.aiTTSManager.autoPlay && window.aiTTSManager.available);
streamingTTS = !!(window.aiTTSManager && window.aiTTSManager.autoPlay && window.aiTTSManager.available);
if (streamingTTS) window.aiTTSManager.streamingStart();
// Multi-bubble agent tracking
let roundHolder = holder; // Current AI text bubble (changes per round)
+5
View File
@@ -1847,6 +1847,10 @@ export async function selectSession(id, { keepSidebar = false, showLoading = tru
const _isTransientChat = !!_meta && (_meta.folder === 'Assistant' || _meta.folder === 'Tasks');
if (!_isTransientChat) {
Storage.set('lastSessionId', id);
// Update URL hash without triggering hashchange handler
if (window.location.hash !== '#' + id) {
history.replaceState(null, '', '#' + id);
}
}
// Restore character preset for persistent chats
try {
@@ -2313,6 +2317,7 @@ export async function materializePendingSession() {
currentSessionId = payload.id;
if (!isIncognito) {
Storage.set('lastSessionId', payload.id);
history.replaceState(null, '', '#' + payload.id);
}
// Reload the sidebar in the background. Awaiting this used to block the first