fix(frontend): isolate sidebar registration to unblock navigation

Separate shell subscriptions from Home session data and cover effect convergence, transition cleanup, current callbacks and signed-out fallback with real React lifecycle tests. Record baseline-equivalent local failures and outstanding browser/build verification.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
jesse-ux
2026-09-19 14:32:42 +08:00
co-authored by Claude Code
parent a2bcdee006
commit 2c92f6100e
14 changed files with 519 additions and 10 deletions
+3 -3
View File
@@ -4,13 +4,13 @@ import type { ReactNode } from "react";
import { AppSidebar } from "@/components/app-sidebar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { SessionListProvider, sessionListSidebarModel, useSessionList } from "@/hooks/use-session-list";
import { SessionListProvider, sessionListSidebarModel, useSessionList, useShellRegistration } from "@/hooks/use-session-list";
import "../site-styles";
function AppShell({ children }: { children: ReactNode }) {
const list = useSessionList();
const { sessions, account } = sessionListSidebarModel(list);
const registration = list.registration;
const registration = useShellRegistration();
const { sessions, account } = sessionListSidebarModel(list, registration);
return (
<SidebarProvider escapeBlocked={registration?.escapeBlocked ?? false}>
+1 -1
View File
@@ -1 +1 @@
export { SessionListProvider, useSessionList, sessionListSidebarModel } from "@/lib/session-list-context";
export { SessionListProvider, useSessionList, useShellRegistration, sessionListSidebarModel } from "@/lib/session-list-context";
+19 -6
View File
@@ -55,10 +55,13 @@ export type SessionListContextValue = {
ready: Promise<void>;
boot: () => SessionListBoot | null;
registerShellControls: (registration: ShellRegistration | null) => void;
registration: ShellRegistration | null;
};
const SessionListContext = createContext<SessionListContextValue | null>(null);
// Home produces these controls; only the shell subscribes to their updates.
// Publishing them through the data context re-renders Home, whose registration
// effect has fresh callbacks/rows, feeding another update back to the provider.
const ShellRegistrationContext = createContext<ShellRegistration | null>(null);
function createReadyGate(): { promise: Promise<void>; resolve: () => void } {
let settle = () => {};
@@ -167,20 +170,24 @@ export function SessionListProvider({ children }: { children: ReactNode }) {
ready: readyPack.promise,
boot,
registerShellControls,
registration,
}), [
account,
boot,
readyPack.promise,
registerShellControls,
registration,
sessions,
sessionsCursor,
settled,
signedOut,
]);
return <SessionListContext.Provider value={value}>{children}</SessionListContext.Provider>;
return (
<SessionListContext.Provider value={value}>
<ShellRegistrationContext.Provider value={registration}>
{children}
</ShellRegistrationContext.Provider>
</SessionListContext.Provider>
);
}
export function useSessionList(): SessionListContextValue {
@@ -191,11 +198,17 @@ export function useSessionList(): SessionListContextValue {
return value;
}
export function sessionListSidebarModel(list: SessionListContextValue): {
export function useShellRegistration(): ShellRegistration | null {
return useContext(ShellRegistrationContext);
}
export function sessionListSidebarModel(
list: SessionListContextValue,
registered: ShellRegistration | null,
): {
sessions: readonly SidebarSession[];
account: SidebarAccount | null;
} {
const registered = list.registration;
return {
sessions: registered?.sidebarSessions ?? sessionsToSidebarRows(list.sessions),
account: registered?.sidebarAccount ?? toSidebarAccount(list.account),