generateMetadata already resolves the title from the request host — a Lux console renders <title>Lux Cloud Console</title>. The description beside it was a literal and shipped 'Unified admin console for Hanzo Cloud and all cloud products.' to console.lux.cloud and console.zoo.cloud. The correct title is exactly why nobody caught it: the visible tab looked white-labeled, so the head element behind it was never read. Both strings now come from brandName. Every brand-visible value in this function must; a literal here re-opens the leak.
58 lines
2.4 KiB
TypeScript
58 lines
2.4 KiB
TypeScript
import './fonts.css'
|
|
import '@hanzogui/core/reset.css'
|
|
// Hanzo Design System tokens (vendored from hanzoai/design) — the monochrome
|
|
// source of truth. Imported BEFORE globals.css so the console's Tamagui theme can
|
|
// derive its ladder from the design neutral/semantic tokens.
|
|
import './design/index.css'
|
|
import './globals.css'
|
|
|
|
import type { Metadata, Viewport } from 'next'
|
|
import type { ReactNode } from 'react'
|
|
import { headers } from 'next/headers'
|
|
|
|
import { Provider } from '~/components/Provider'
|
|
import { ChunkGuard } from '~/components/ChunkGuard'
|
|
import { BrandTitle } from '~/components/BrandTitle'
|
|
import { resolveConfig } from '~/config'
|
|
|
|
// The document <title> is SSR metadata, so it must reflect the REQUEST host's
|
|
// brand (console.lux.cloud -> "Lux Cloud Console"), not the build-time default.
|
|
// The visible shell resolves the brand client-side from window.location, but the
|
|
// tab title is server-rendered — without reading the Host header here the browser
|
|
// tab leaks "Hanzo Cloud Console" on Lux/Zoo hosts, a white-label violation.
|
|
//
|
|
// The description is the same metadata read by the same brand, so it resolves the
|
|
// same way. It did not, and shipped `content="Unified admin console for Hanzo Cloud
|
|
// and all cloud products."` to console.lux.cloud and console.zoo.cloud — the title
|
|
// beside it was already correct, which is exactly why nobody noticed. Every
|
|
// brand-visible string in this function comes from `brandName`; adding a literal
|
|
// here re-opens the leak.
|
|
export async function generateMetadata(): Promise<Metadata> {
|
|
const host = (await headers()).get('host') ?? undefined
|
|
const { brandName } = resolveConfig(host)
|
|
return {
|
|
title: `${brandName} Console`,
|
|
description: `Unified admin console for ${brandName} and all cloud products.`,
|
|
}
|
|
}
|
|
|
|
export const viewport: Viewport = {
|
|
themeColor: '#000000',
|
|
// Extend the layout into the display cutout / home-indicator area so the
|
|
// `env(safe-area-inset-*)` values become non-zero on notched devices — the mobile
|
|
// drawers + chat composer read them to keep content clear of the notch/indicator.
|
|
viewportFit: 'cover',
|
|
}
|
|
|
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
return (
|
|
<html lang="en" className="t_dark" style={{ backgroundColor: '#000000', colorScheme: 'dark' }} suppressHydrationWarning>
|
|
<body style={{ margin: 0 }}>
|
|
<ChunkGuard />
|
|
<BrandTitle />
|
|
<Provider>{children}</Provider>
|
|
</body>
|
|
</html>
|
|
)
|
|
}
|