2026-10-25 17:18:44 +03:00
|
|
|
import { headers } from "next/headers";
|
|
|
|
|
import { readSettingsRecord } from "@/src/server";
|
|
|
|
|
import { resolveLanguage, systemLanguage, translate } from "@/src/core";
|
|
|
|
|
import { I18nProvider } from "@/components/i18n";
|
2026-08-22 02:18:27 +03:00
|
|
|
import type { Metadata } from "next";
|
2026-10-25 17:18:44 +03:00
|
|
|
import { JetBrains_Mono, Source_Sans_3, Noto_Sans } from "next/font/google";
|
2026-08-22 03:23:11 +03:00
|
|
|
import "katex/dist/katex.min.css";
|
2026-08-22 02:18:27 +03:00
|
|
|
import "./globals.css";
|
2026-08-22 04:28:13 +03:00
|
|
|
import { AppShell, SidebarProvider } from "@/components/sidebar";
|
2026-09-14 11:56:22 +03:00
|
|
|
import { ActivityProvider } from "@/components/activity-dock";
|
2026-10-02 17:15:17 +03:00
|
|
|
import { ServerEventsProvider } from "@/components/server-events";
|
2026-09-19 18:26:25 +03:00
|
|
|
import { AppearanceProvider } from "@/components/settings/appearance";
|
2026-10-10 14:11:19 +03:00
|
|
|
import UpdateBanner from "@/components/update-banner";
|
2026-08-22 02:18:27 +03:00
|
|
|
|
2026-09-12 23:37:31 +03:00
|
|
|
// Chrome = JetBrains Mono, teaching prose = Source Sans 3, titles = CMU
|
|
|
|
|
// Serif (the bundled lh-cmu @font-face in globals.css — no CDN).
|
2026-10-25 17:18:44 +03:00
|
|
|
const ui = JetBrains_Mono({ subsets: ["latin", "cyrillic"], variable: "--font-jetbrains" });
|
2026-09-12 23:37:31 +03:00
|
|
|
const prose = Source_Sans_3({ subsets: ["latin"], variable: "--font-sans3" });
|
|
|
|
|
|
2026-10-25 17:18:44 +03:00
|
|
|
const russianProse = Noto_Sans({ subsets: ["latin", "cyrillic"], variable: "--font-russian-prose" });
|
2026-08-22 02:18:27 +03:00
|
|
|
|
2026-10-25 17:18:44 +03:00
|
|
|
export async function generateMetadata(): Promise<Metadata> {
|
|
|
|
|
const language = resolveLanguage(readSettingsRecord()?.general?.language, (await headers()).get("accept-language"));
|
|
|
|
|
return {
|
|
|
|
|
title: "Learning Harness",
|
|
|
|
|
description: translate(language, "A teacher agent with a memory — sessions, quizzes, and a knowledge graph of what you know."),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const general = readSettingsRecord()?.general ?? {};
|
|
|
|
|
const system = systemLanguage((await headers()).get("accept-language"));
|
|
|
|
|
const language = resolveLanguage(general.language, system);
|
2026-08-22 02:18:27 +03:00
|
|
|
return (
|
2026-10-25 17:18:44 +03:00
|
|
|
<html lang={language} className={`dark ${ui.variable} ${prose.variable} ${russianProse.variable}`}>
|
2026-09-12 23:53:47 +03:00
|
|
|
<body className="min-h-dvh antialiased">
|
2026-10-02 17:15:17 +03:00
|
|
|
{/* The notification channel rides OUTSIDE everything: sidebar, dock,
|
|
|
|
|
and pages all subscribe as the polls retire (push S3, 2026-10-02). */}
|
2026-10-25 17:18:44 +03:00
|
|
|
<I18nProvider initialGeneral={general} system={system}><ServerEventsProvider>
|
2026-10-02 17:15:17 +03:00
|
|
|
<AppearanceProvider>
|
|
|
|
|
<SidebarProvider>
|
|
|
|
|
<ActivityProvider>
|
|
|
|
|
<AppShell>{children}</AppShell>
|
|
|
|
|
</ActivityProvider>
|
|
|
|
|
</SidebarProvider>
|
|
|
|
|
</AppearanceProvider>
|
|
|
|
|
</ServerEventsProvider>
|
2026-10-10 14:11:19 +03:00
|
|
|
{/* Auto-update banner (Wave C): fixed card, Electron-only pushes. */}
|
|
|
|
|
<UpdateBanner />
|
2026-10-25 17:18:44 +03:00
|
|
|
</I18nProvider>
|
2026-08-22 04:28:13 +03:00
|
|
|
</body>
|
2026-08-22 02:18:27 +03:00
|
|
|
</html>
|
|
|
|
|
);
|
|
|
|
|
}
|