Prevent Android chats from freezing or going blank (#1989)
* fix(app): stabilize streamed chat rendering Use deterministic Markdown AST keys and preserve unique row identities when an assistant message resumes after a tool. Keep native gesture and plan-card children correctly keyed to avoid unsupported Fabric reconciliation paths. * fix(app): stabilize retained native panels Keep retained workspace roots in a stable native order and make active panels present in the selection commit. Hidden panels now stop expensive subscriptions and animations without losing mounted state. * fix(app): harden retained panel activity
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { useMemo, useRef } from "react";
|
||||
import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import {
|
||||
areSidebarWorkspaceSessionsEqual,
|
||||
buildSidebarWorkspaceEntries,
|
||||
selectSidebarWorkspaceSessions,
|
||||
type SidebarWorkspaceEntry,
|
||||
type SidebarWorkspacePlacement,
|
||||
type SidebarWorkspaceSession,
|
||||
} from "./sidebar-workspaces-view-model";
|
||||
|
||||
const EMPTY_ENTRIES = new Map<string, SidebarWorkspaceEntry>();
|
||||
const EMPTY_SESSIONS: SidebarWorkspaceSession[] = [];
|
||||
const EMPTY_PENDING_CREATE_ATTEMPTS: Record<string, never> = {};
|
||||
|
||||
export function useSidebarWorkspaceEntries(
|
||||
placements: readonly SidebarWorkspacePlacement[],
|
||||
enabled = true,
|
||||
): ReadonlyMap<string, SidebarWorkspaceEntry> {
|
||||
const serverIds = useMemo(
|
||||
() => Array.from(new Set(placements.map((placement) => placement.serverId))),
|
||||
[placements],
|
||||
);
|
||||
const sessions = useStoreWithEqualityFn(
|
||||
useSessionStore,
|
||||
(state) =>
|
||||
enabled ? selectSidebarWorkspaceSessions(state.sessions, serverIds) : EMPTY_SESSIONS,
|
||||
areSidebarWorkspaceSessionsEqual,
|
||||
);
|
||||
const pendingCreateAttempts = useCreateFlowStore((state) =>
|
||||
enabled ? state.pendingByDraftId : EMPTY_PENDING_CREATE_ATTEMPTS,
|
||||
);
|
||||
const previousEntriesRef = useRef<ReadonlyMap<string, SidebarWorkspaceEntry>>(EMPTY_ENTRIES);
|
||||
|
||||
// Collection ownership is intentional: retained sidebars have one cheap
|
||||
// subscription to structurally shared indexes, never one session-store
|
||||
// subscription per mounted row.
|
||||
return useMemo(() => {
|
||||
if (!enabled || placements.length === 0 || sessions.length === 0) {
|
||||
previousEntriesRef.current = EMPTY_ENTRIES;
|
||||
return EMPTY_ENTRIES;
|
||||
}
|
||||
const entries = buildSidebarWorkspaceEntries({
|
||||
placements,
|
||||
sessions,
|
||||
pendingCreateAttempts,
|
||||
previousEntries: previousEntriesRef.current,
|
||||
});
|
||||
previousEntriesRef.current = entries;
|
||||
return entries;
|
||||
}, [enabled, pendingCreateAttempts, placements, sessions]);
|
||||
}
|
||||
Reference in New Issue
Block a user