Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/apps/web/app/layout.tsx
blob: cfdfcde32d7c487be11c1ab66a07eaf27d915338 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1

import type { Metadata } from "next";
import { headers } from "next/headers";
import type { ReactNode } from "react";
import { nonceHeader } from "@/lib/content-security-policy";
import { getLocale } from "@/lib/locale.server";
import { textDirection } from "@/lib/localization";
import "./globals.css";
import "./viget.css";

// Apply the stored theme before the body is painted. PreferenceControls keeps
// this value synchronized after hydration, while this small bootstrap prevents
// a light flash on every route—including dashboard loading and error states.
const themeBootstrap = `
  (() => {
    try {
      const theme = localStorage.getItem("progmasoft_theme") === "dark" ? "dark" : "light";
      document.documentElement.dataset.theme = theme;
      document.documentElement.style.colorScheme = theme;
    } catch {}
  })();
`;

export const metadata: Metadata = {
  // The apex host is the only public organization canonical. The www host is a
  // transport-level redirect and account pages opt out of indexing below.
  metadataBase: new URL("https://progmasoft.com"),
  title: {
    default: "Progmasoft",
    template: "%s · Progmasoft",
  },
  description:
    "Progmasoft builds programming-language, package-management, and developer-tooling systems.",
  applicationName: "Progmasoft",
  robots: {
    index: true,
    follow: true,
  },
};

export default async function RootLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  const locale = await getLocale();
  // The proxy gives each page a nonce; its policy admits no other inline script.
  const nonce = (await headers()).get(nonceHeader) ?? undefined;
  return (
    <html
      lang={locale}
      data-text-direction={textDirection(locale)}
      // data-theme belongs to the bootstrap script and the theme control only.
      // Rendered here, React would set it back to light whenever the server
      // re-renders the layout (a language switch), and the page would flash.
      suppressHydrationWarning
    >
      <head>
        <script
          nonce={nonce}
          dangerouslySetInnerHTML={{ __html: themeBootstrap }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}