# `docs-theme/` — the shared Norsk docs site theme

The parts of an Astro + Starlight docs site that must **never** differ between
Norsk products: the theme, the three component overrides, the content schema,
and the Astro/Starlight configuration itself. A product's site owns only what
genuinely differs — its content, its sidebar, its origin and its two names.

    custom.css              the palette + self-hosted Geist faces
    fonts/                  the woff2 those @font-face rules point at
    public/favicon.png      the favicon `starlight.ts` names (copied to the site ROOT, not src/)
    components/Footer.astro    the "captured against" line on a generated guide
    components/Sidebar.astro   the "Built against" versions footer
    components/SiteTitle.astro the masthead wordmark, decoupled from the tab title
    content.config.ts       the docs collection, incl. `capturedAgainst`
    starlight.ts            `norskDocsSite()` — the whole Astro config bar the site's own parts
    sync.ts                 the writer and the drift gate

## Consuming it

A docs site's `astro.config.mjs` becomes its sidebar plus five lines:

```js
import { defineConfig } from "astro/config";
import { isPublicBuild, norskDocsSite } from "./src/starlight.ts";

export default defineConfig(
  norskDocsSite({
    origin: "https://ctl.norsk.video",
    tabTitle: "Norsk",              // browser tab suffix — tabs truncate
    wordmark: "norsk-ctl",          // masthead — names the tool in hand
    buildVersions: [{ label: "norsk-ctl", value: version }],
    sidebar: [...],
  }),
);
```

`norskDocsSite()` supplies the two-build shape (`dist-embedded` at base `/docs`
for the copy baked into a product binary, `dist-public` at base `/` for the
site on its own domain, selected by `ASTRO_BUILD=public`), the /docs-prefix
strip that makes the public build's absolute links work, the mermaid pipeline,
the favicon convention, and the component + CSS wiring.

## Why the files are copied into the site, not imported from here

A docs site is a **standalone package** — its own `bun.lock`, its own
`node_modules`, outside the monorepo's workspaces, because Astro's dependency
tree has no business in a product install. It therefore cannot depend on this
package the way every other dev-kit consumer does. That is measured, not
assumed: adding `"@norskvideo/ctl-dev-kit": "file:../../dev-kit"` to norsk-ctl's
website and installing fails with

    error: @norskvideo/ctl-sdk@workspace:* failed to resolve

because bun must then resolve this package's own `workspace:*` dependencies
from a directory that is not in a workspace.

So the theme is a **forced copy**, handled the way this package already handles
every forced copy — canonical bytes here, a writer, and a gate:

```
bun node_modules/@norskvideo/ctl-dev-kit/docs-theme/sync.ts sync  <site>
bun node_modules/@norskvideo/ctl-dev-kit/docs-theme/sync.ts check <site>
```

`<site>` is the directory holding `astro.config.mjs`; the copies land in its
`src/`, so this directory mirrors a site's `src/` and the path map is an
identity. The one exception is `public/`, which mirrors the site's `public/` —
Astro serves it verbatim from the site root, and `starlight.ts` already dictates
the favicon's path (`favicon: "/favicon.png"`), so its bytes belong here too.

A site runs `check` in its own test suite (norsk-ctl:
`website/tests/theme-drift.test.ts`, which `bun run check` runs), so a
hand-edited copy fails before it is published. A PRODUCT repo has no such suite;
there, `conventions/check-drift.ts` runs the same check as part of
`bun run check:drift`, and `sync-drift.ts` rewrites the copies as part of
`bun run sync:drift` — see below.

**Keeping the copies at those exact paths is load-bearing.** Astro derives a
component's scoped-style class from its path, so moving `Footer.astro` rewrites
a class attribute on every page of the site.

## The two gates are one gate

`conventions/check-drift.ts` gains a clause when — and only when — the repo has a
site: `hasDocsSite()` tests for `website/astro.config.mjs`, and
`docsThemeProblems()` routes this directory's `checkTheme` into the same report as
`biome.json` and the shared workflows. `conventions/sync-drift.ts` is the writer
half, so `bump.yml` rolls a theme change out to every product like any
other convention, through a bot PR that `bot-prs.yml` merges once its tiers pass. The site directory is `website/` across the fleet
(`DOCS_SITE_DIR`).

A site inside THIS monorepo still runs `sync.ts check` from its own test suite,
because it has one and because `bun run check` is what a monorepo contributor
runs; a product repo has no second suite, which is why the clause exists.

## `create-product` renders a site

Every generated repo comes out with a `website/` on this theme, five sections
deep, that builds — `create-product/docs-site.ts`. Two notes on how:

- the theme files are written by **this directory's `syncTheme`**, called from
  `createProduct` after the plan is written, not re-rendered by the generator: one
  copier, one gate, and the woff2 faces are not strings;
- what a product repo needs in order to have a site at all — the five sections,
  the two builds, publishing, and how the site relates to the illustrated manual
  the image serves — is `conventions/docs-site.md`.
