mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 14:02:19 +00:00
fix(threads): preserve selectedThreadId on cold-boot identity hydration (#1196)
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
import { socketService } from '../services/socketService';
|
||||
import { store } from '../store';
|
||||
import { resetUserScopedState } from '../store/resetActions';
|
||||
import { clearAllThreads, loadThreads } from '../store/threadSlice';
|
||||
import { loadThreads, resetThreadCachesPreservingSelection } from '../store/threadSlice';
|
||||
import { getActiveUserId, setActiveUserId } from '../store/userScopedStorage';
|
||||
import {
|
||||
openhumanUpdateAnalyticsSettings,
|
||||
@@ -257,7 +257,12 @@ export default function CoreStateProvider({ children }: { children: ReactNode })
|
||||
!isLogout
|
||||
) {
|
||||
const threadReloadRequestId = requestId;
|
||||
store.dispatch(clearAllThreads());
|
||||
// Reset the in-memory thread caches (rows from a pre-auth bucket — see
|
||||
// #1157) but preserve the redux-persisted `selectedThreadId` so a
|
||||
// reload of an already-authed user resumes the user's last-viewed
|
||||
// thread (#1168). The Conversations mount effect falls back to "most
|
||||
// recent" if the persisted id is no longer in the reloaded list.
|
||||
store.dispatch(resetThreadCachesPreservingSelection());
|
||||
void store
|
||||
.dispatch(loadThreads())
|
||||
.unwrap()
|
||||
|
||||
@@ -153,9 +153,13 @@ describe('CoreStateProvider — identity flip cleanup (#900)', () => {
|
||||
setActiveSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('seed matches next user (no restart) but identity hydrates null→B: clears threads and reloads (#1157)', async () => {
|
||||
it('seed matches next user (no restart) but identity hydrates null→B: resets thread caches (preserving selection) and reloads (#1157, #1168)', async () => {
|
||||
userScopedStorage.setActiveUserId('B');
|
||||
fetchSnapshot.mockResolvedValue(makeSnapshot({ userId: 'B', sessionToken: 'tokB' }));
|
||||
// Seed a persisted selection that should survive the boot-time reset so
|
||||
// the user resumes their last-viewed thread instead of falling through
|
||||
// to "most recent".
|
||||
store.dispatch(threadSlice.setSelectedThread('thr-persisted'));
|
||||
const dispatchSpy = vi.spyOn(store, 'dispatch');
|
||||
const loadThreadsSpy = vi.spyOn(threadSlice, 'loadThreads');
|
||||
|
||||
@@ -173,10 +177,22 @@ describe('CoreStateProvider — identity flip cleanup (#900)', () => {
|
||||
expect(
|
||||
dispatchSpy.mock.calls.some(([action]) => {
|
||||
if (!action || typeof action !== 'object' || !('type' in action)) return false;
|
||||
return (action as { type: string }).type === threadSlice.clearAllThreads().type;
|
||||
return (
|
||||
(action as { type: string }).type ===
|
||||
threadSlice.resetThreadCachesPreservingSelection().type
|
||||
);
|
||||
})
|
||||
).toBe(true);
|
||||
// The legacy `clearAllThreads` (which nulls selectedThreadId) must NOT
|
||||
// be dispatched on this boot path — that was the #1168 regression.
|
||||
expect(
|
||||
dispatchSpy.mock.calls.some(([action]) => {
|
||||
if (!action || typeof action !== 'object' || !('type' in action)) return false;
|
||||
return (action as { type: string }).type === threadSlice.clearAllThreads().type;
|
||||
})
|
||||
).toBe(false);
|
||||
expect(loadThreadsSpy).toHaveBeenCalled();
|
||||
expect(store.getState().thread.selectedThreadId).toBe('thr-persisted');
|
||||
|
||||
dispatchSpy.mockRestore();
|
||||
loadThreadsSpy.mockRestore();
|
||||
|
||||
@@ -279,6 +279,19 @@ const threadSlice = createSlice({
|
||||
state.activeThreadId = null;
|
||||
state.welcomeThreadId = null;
|
||||
},
|
||||
// Like `clearAllThreads` but keeps `selectedThreadId`. Used on the
|
||||
// post-bootstrap identity-observation path (#1168 + #1157): we need to
|
||||
// drop pre-auth in-memory thread rows but the persisted last-viewed
|
||||
// thread id is still valid for the reloaded user, so preserving it lets
|
||||
// the Conversations effect resume that thread instead of falling
|
||||
// through to "most recent".
|
||||
resetThreadCachesPreservingSelection: state => {
|
||||
state.threads = [];
|
||||
state.messagesByThreadId = {};
|
||||
state.messages = [];
|
||||
state.activeThreadId = null;
|
||||
state.welcomeThreadId = null;
|
||||
},
|
||||
// [#1123] True no-op — welcome-agent onboarding replaced by Joyride walkthrough.
|
||||
// Kept to avoid breaking existing imports; state.welcomeThreadId is never
|
||||
// mutated because the welcome-agent flow no longer runs.
|
||||
@@ -350,6 +363,7 @@ export const {
|
||||
clearSelectedThread,
|
||||
setActiveThread,
|
||||
clearAllThreads,
|
||||
resetThreadCachesPreservingSelection,
|
||||
setWelcomeThreadId,
|
||||
} = threadSlice.actions;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user