Files
kandev/apps/web/app/settings/layout.tsx
T
2026-07-09 23:20:48 +01:00

109 lines
3.9 KiB
TypeScript

import { SettingsLayoutClient } from "@/components/settings/settings-layout-client";
import { StateHydrator } from "@/components/state-hydrator";
import {
fetchUserSettings,
listAgentDiscovery,
listAgents,
listAvailableAgents,
listExecutors,
listWorkspaces,
} from "@/lib/api";
import {
ACTIVE_WORKSPACE_COOKIE,
mapWorkspaceItem,
resolveSettingsActiveWorkspaceId,
} from "@/lib/routing/route-bootstrap";
import { mapUserSettingsResponse } from "@/lib/ssr/user-settings";
import { readCookies } from "@/lib/server/cookies";
import { toAgentProfileOption } from "@/lib/state/slices/settings/types";
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
return <SettingsLayoutServer>{children}</SettingsLayoutServer>;
}
async function SettingsLayoutServer({ children }: { children: React.ReactNode }) {
let initialState = {};
try {
// Fetch discovery + available agents alongside the DB-backed list so a
// hard refresh of /settings/agents/[name] (where no profile exists yet)
// can still render the agent from the discovered set.
const [workspaces, executors, agents, discovery, available, userSettingsResponse, cookieStore] =
await Promise.all([
listWorkspaces({ cache: "no-store" }),
listExecutors({ cache: "no-store" }),
listAgents({ cache: "no-store" }),
listAgentDiscovery({ cache: "no-store" }),
listAvailableAgents({ cache: "no-store" }),
fetchUserSettings({ cache: "no-store" }).catch(() => null),
readCookies().catch(() => null),
]);
// Hydrate userSettings into the ROOT store so app-global, override-driven
// shortcuts (TOGGLE_SIDEBAR, Quick Chat) work on settings routes too. The
// settings/general page mounts its own nested store for editing; that store
// is invisible to the root-mounted GlobalCommands/useAppShortcuts.
const mappedUserSettings = mapUserSettingsResponse(userSettingsResponse);
const workspaceItems = workspaces.workspaces.map(mapWorkspaceItem);
const activeWorkspaceId = resolveSettingsActiveWorkspaceId(
workspaceItems,
// `readCookies()` is client-only in this path; during SSR this is empty.
// Settings active workspace selection is completed on first client render by
// spa-routes via `readActiveWorkspaceCookie()`.
cookieStore?.get(ACTIVE_WORKSPACE_COOKIE)?.value ?? null,
userSettingsResponse?.settings?.workspace_id ?? null,
);
initialState = {
workspaces: {
items: workspaceItems,
activeId: activeWorkspaceId,
},
executors: {
items: executors.executors,
},
agentProfiles: {
items: agents.agents.flatMap((agent) =>
agent.profiles.map((profile) => toAgentProfileOption(agent, profile)),
),
},
settingsAgents: {
items: agents.agents,
},
agentDiscovery: {
items: discovery.agents,
loading: false,
loaded: true,
},
availableAgents: {
items: available.agents,
tools: available.tools ?? [],
loading: false,
loaded: true,
},
settingsData: {
executorsLoaded: true,
agentsLoaded: true,
},
...(mappedUserSettings.loaded
? {
userSettings: {
...mappedUserSettings,
workspaceId: activeWorkspaceId,
},
}
: {}),
};
} catch {
// If any non-settings fetch (workspaces, executors, agents, …) throws, we
// render with empty initial state — losing the userSettings overrides too.
// That's acceptable: the page is already degraded (no executors/agents), so
// override-driven shortcuts being inactive until client hydration is fine.
initialState = {};
}
return (
<>
<StateHydrator initialState={initialState} />
<SettingsLayoutClient>{children}</SettingsLayoutClient>
</>
);
}