wulf-pulse/app/layout.tsx
lorentz 586c04ad2a feat(09-05): ThemeSessionBridge + ThemeToggle write-through to /api/me/theme
- Create ThemeSessionBridge.tsx: useEffect compares session.user.theme to
  next-themes value; calls setTheme(serverTheme) on mismatch; validates
  against 3-string allowlist ('light'|'dark'|'system'); renders null
- Mount <ThemeSessionBridge /> as first child of <AuthProvider> in app/layout.tsx
- Modify ThemeToggle: writeTheme() calls setTheme() then fire-and-forget
  PUT /api/me/theme; silent catch for network errors (best-effort desktop UX)
2026-05-10 07:48:57 -04:00

86 lines
2.8 KiB
TypeScript

import type { Metadata, Viewport } from "next";
import { IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { AppNavigation } from "@/components/navigation/app-navigation";
import { CommandPalette } from "@/components/navigation/command-palette";
import { TaglineFooter } from "@/components/branding/tagline-footer";
import { Toaster } from "sonner";
import { AuthProvider } from "@/components/auth/auth-provider";
import { ThemeSessionBridge } from "@/components/mobile/profile/ThemeSessionBridge";
// IBM Plex Sans replaces the brand-mandated Helvetica/Arial. The 2013
// standards guide called for Helvetica Bold for headers and Helvetica
// Light for the tagline; Plex Sans honors the spirit (clean engineered
// sans) while loading reliably from Google Fonts. Weight 300 covers the
// "Light" usage in the tagline footer.
const plexSans = IBM_Plex_Sans({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
variable: "--font-plex-sans",
});
const plexMono = IBM_Plex_Mono({
subsets: ["latin"],
weight: ["400", "500", "600"],
variable: "--font-plex-mono",
});
export const metadata: Metadata = {
title: "Pulse · Operations console",
description: "Wulf Consulting operations console — tickets, RMM, IT Glue, backups, and analytics in one place.",
manifest: "/manifest.json",
icons: {
icon: [
{ url: "/favicon.png", sizes: "any" },
{ url: "/wulff-logo.png", sizes: "32x32", type: "image/png" },
],
shortcut: "/favicon.png",
apple: "/wulff-logo.png",
},
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "Pulse",
},
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: "cover",
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#FFFFFF" },
{ media: "(prefers-color-scheme: dark)", color: "#0A0A0A" },
],
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning className={`${plexSans.variable} ${plexMono.variable}`}>
<body className="font-sans antialiased">
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<AuthProvider>
<ThemeSessionBridge />
<div className="min-h-screen bg-background flex flex-col">
<AppNavigation />
<main className="flex-1">{children}</main>
<TaglineFooter />
</div>
<CommandPalette />
</AuthProvider>
<Toaster position="top-right" richColors />
</ThemeProvider>
</body>
</html>
);
}