import type { FontHead } from "../theme/fonts.ts"; import type { UIStrings } from "./i18n-ui.ts"; import type { ResolvedConfig, SearchProvider } from "./schema.ts"; import type { Navigation, RouteAlternate, VersionAlternate } from "./types.ts"; /** * The shape of the `blume:data` virtual module — the resolved, serializable * snapshot of a site that custom `.astro` pages read with * `import data from "blume:data"`. `buildRuntimeData` (`astro/generate.ts`) * produces exactly this object and annotates it with {@link BlumeData}, so the * documented type and the emitted JSON stay in lockstep. */ /** Resolved site logo: an inlined SVG, or light/dark image URLs. */ export interface BlumeLogo { svg?: string; light?: string; dark?: string; dimensions?: { dark?: { height: number; width: number }; light?: { height: number; width: number }; }; alt: string; href: string; /** Wordmark text beside the mark; `undefined` falls back to the site title. */ text?: string; } /** A favicon or apple-touch-icon: a link href plus an optional MIME type. */ export interface BlumeFavicon { href: string; type?: string; /** * Dark-scheme variant: the `-dark` sibling of the resolved icon file (e.g. * `icon.svg` → `icon-dark.svg`), or the bundled default pair. When set, the * layout emits an unconditional light link plus both icons behind * `media="(prefers-color-scheme: …)"` so a dark mark doesn't vanish against * dark browser chrome. */ dark?: { href: string; type?: string }; } /** Announcement banner, normalized from its config (string shorthand or object). */ export interface BlumeBanner { content: string; link?: { href: string; text: string }; dismissible: boolean; /** Dismissal key: the configured id, else the content itself. */ key: string; } /** A generated syndication feed surfaced in the UI. */ export interface BlumeFeed { href: string; title: string; } /** One configured locale, as exposed to the runtime. */ export interface BlumeDataLocale { code: string; dir: "ltr" | "rtl"; label: string; } /** Resolved i18n settings; `null` when the site is single-locale. */ export interface BlumeDataI18n { defaultLocale: string; /** Locale whose content renders for a missing translation; `null` disables it. */ fallbackLocale: string | null; hideDefaultLocalePrefix: boolean; locales: BlumeDataLocale[]; } /** A single content route, with the metadata custom pages can read. */ export interface BlumeRoute { /** Locales this logical page is translated into (excludes fallbacks). */ alternates: RouteAlternate[]; /** Astro collection the entry renders through (`"docs"` | `"staged"`). */ collection: string; draft: boolean; /** "Edit this page" URL, or `null` when no repo/source provides one. */ editUrl: string | null; /** Astro collection entry id (for `getEntry`/`getCollection`); matches `id`. */ entryId: string; /** True when this route renders fallback content for a missing translation. */ fallback: boolean; hidden: boolean; id: string; /** Whether the page is part of the search index. */ indexable: boolean; /** ISO "last updated" date when the feature is on, else `null`. */ lastModified: string | null; /** Resolved locale code (the default locale when not under i18n). */ locale: string; path: string; title: string; /** Resolved docs version (`""` for the current docs). */ version: string; /** * Versions this logical page exists in within this route's locale — the * current version first, then archived versions in configured order. Empty * when versioning is off. */ versionAlternates: VersionAlternate[]; } /** Site-wide settings derived from `blume.config` — the `config` field of {@link BlumeData}. */ export interface BlumeDataConfig { analytics: NonNullable | null; /** Apple touch icon, or `null` when none is configured/detected. */ appleIcon: BlumeFavicon | null; /** Ask AI empty-state suggestions, or `null` when Ask AI is off. */ ask: { endpoint: string | null; suggestions: NonNullable["suggestions"]; } | null; banner: BlumeBanner | null; /** Site-wide route mount point, normalized to `""` or `/seg` (see config). */ basePath: string; /** `markdown.codeBlocks.theme`: light/dark Shiki themes for code surfaces. */ codeThemes: ResolvedConfig["markdown"]["codeBlocks"]["theme"]; /** `markdown.code.wrap`: wrap long code lines instead of scrolling. */ codeWrap: boolean; /** `dateFormat`: `Intl.DateTimeFormat` options for the date stamps. */ dateFormat: ResolvedConfig["dateFormat"]; description: string | undefined; /** * Which agent-discovery resources exist for the layout to advertise in every * page's `` (`seo.agentReadability`, `ai.llmsTxt.enabled`) — the HTML * counterpart of the homepage-only HTTP `Link` header, for agents that enter * on a deep page (see `ai/link-headers.ts`). */ discovery: { agentReadability: boolean; llmsTxt: boolean }; favicon: BlumeFavicon; feedback: boolean; i18n: BlumeDataI18n | null; /** `markdown.imageZoom`: click-to-zoom content images. */ imageZoom: boolean; logo: BlumeLogo | null; /** Hosted MCP server, or `null` when MCP is off. */ mcp: { name: string; route: string } | null; /** * Open Graph image generation. Card fonts are baked into the generated OG * endpoint (they can carry absolute build-machine paths) and deliberately * kept out of this snapshot, which pages serialize into HTML. */ og: { /** Card subtitle: `seo.og.description` (`false` omits it) over the site description. */ description?: string; enabled: boolean; /** Inlined SVG brand mark; `false` renders the card without any mark. */ logo?: string | false; palette?: ResolvedConfig["seo"]["og"]["palette"]; /** * Footer site text: `seo.og.site` if set (`false` hides it), otherwise * the deployment site's host plus `deployment.base` (`docs.acme.com`, * `user.github.io/repo`) — so a subpath deploy's card names the actual * site rather than the platform's shared apex host. */ site?: string; }; /** * "Open in chat" page-action providers (`ai.openInChat`), in display order; * empty hides the action. */ openInChat: ResolvedConfig["ai"]["openInChat"]; /** Repository URL for header/edit links, or `null`. */ repoUrl: string | null; search: { enabled: boolean; /** Resolved empty-state links; empty when unset (Search falls back to sidebar). */ popular: { icon?: string; label: string; route: string }[]; provider: SearchProvider; }; /** Deployment site URL, or `null` when none is configured/detected. */ site: string | null; structuredData: boolean; theme: ResolvedConfig["theme"]; title: string; /** Table-of-contents settings: whether to show it and the heading range. */ toc: ResolvedConfig["toc"]; /** * WebMCP in-page tools (`ai.webmcp`), plus whether llms.txt exists for the * list tool to fetch (`ai.llmsTxt.enabled`). */ /** Docs versioning config; `null` when the site is unversioned. */ versions: NonNullable | null; webmcp: { enabled: boolean; llms: boolean }; /** X (Twitter) attribution: the site's account, and a default creator. */ x: { creator?: string; handle?: string }; } /** * The compact snapshot the layout serializes into the page for React island * hooks (`blume/hooks`). Islands hydrate independently, so this is read from a * `