# codocation.yml

The project configuration file lives at the project root. It contains project-global settings,
the site registry, and the authoritative locale registry. Locale-owned authored and published
pages, trees, images, attachments, definitions, and sidecars live below `content/<locale>/`.

```yaml
sites:
  cdc:                                  # site id: lowercase letters, digits, dashes
    type: docs                          # docs (default) | blog
    locales: [en, de]                   # variants published by this site
    defaultLocale: en                  # unprefixed routes and fallback source
    publicUrl: https://codocation.com
    basePath: /docs                     # omit for the domain root
    homeUrl: https://codocation.com     # omit to link the title to this site's root
    web:
      branding:
        logo: images/branding/logo.svg          # locale-owned; resolved with the page locale
        favicon: assets/media/favicon.ico        # global, non-localized browser chrome
        font: assets/fonts/Inter.woff2          # global web font file
        themeColor: "#0b6"
      customization:
        css: assets/css/site.css                # global site CSS
        js: assets/js/site.js                   # global site JavaScript
      analytics:
        enabled: true
        provider: plausible                     # plausible | ga4
        id: codocation.com
      ai:
        llmsTxt: true
        llmsFullTxt: true
        pageMarkdown: true
      listing:
        perPage: 10                             # cards per pagination page: posts feed and archives
      feed:
        enabled: true
        limit: 20
        fullText: false
      links:
        externalLinksNewTab: true
        internalLinkPreview: true               # hover previews on links to published pages
        contributeUrl: https://github.com/org/repo/edit/main/docs/
        markdownActions: true                   # "Copy for LLM" and "View as Markdown" on each page

locales:
  en:
    title: English
  de:
    title: Deutsch


publicUrl: https://codocation.com                # root fallback for sites without their own URL
pdf:
  titlePage:
    enabled: true
    logo: images/pdf/logo.png                    # locale-owned; resolved with the page locale
  font: assets/fonts/Inter.ttf                   # global PDF font
  orientation: portrait
  tableOfContents:
    enabled: true
  pageNumbers:
    enabled: true
    position: bottom-center

build:
  output: dist
  cleanUrls: true
```

## Notes

- **Locale catalog**: root `locales` is the canonical project-wide catalog. Each entry owns the
  locale code, order, and display `title`; catalog membership is the strict union of all site
  memberships, so inactive catalog entries are invalid.
- **Site variants**: `sites.<siteId>.locales` lists the variants that site publishes. A required
  `content/<locale>/<siteId>.tree.yml` and optional sidecars exist only for declared pairs. An
  undeclared pair file is an ERROR and ignored; a `content/<unknown-locale>/` directory is an
  orphan-locale ERROR and ignored.
- **Site default**: `sites.<siteId>.defaultLocale` owns the site's unprefixed route and the
  page, localized-definition, SEO, and PDF fallback. A one-locale site may omit it (the sole
  member is effective); a site with multiple locales must name exactly one member explicitly.
- **Presentation metadata**: required `title` and optional `description` live at the root of
  `content/<locale>/<siteId>.tree.yml`, next to `header`, `toc`, and `footer`.
- **Global technical assets**: every configured global resource must use one of the enforced
  namespaces `assets/media/`, `assets/fonts/`, `assets/css/`, or `assets/js/`. There are no
  locale-specific favicon, font binaries, CSS, JavaScript, or analytics files in this layout.
- **Per-site web overrides**: a site entry may carry its own `web:` block; its explicit values
  override matching global fields. Web has Global and Per-site scopes, never a Locale scope.
- **Outbound links**: `web.links.externalLinksNewTab` governs every external (`http`/`https`)
  link the site renders, including header entries, landing buttons, and page body links; the
  default is `true`. `web.links.contributeUrl` names the folder holding `codocation.yml` in a
  repository; each page's own source path is appended to it to build an "Edit this page" link
  (see [Titles, URLs, and Branding](/site/branding/index.md)). `web.links.internalLinkPreview`
  (default `true`) decides whether a link to a published page carries its target's title and
  annotation for a hover preview. `web.links.markdownActions` (default `true`) shows "Copy for
  LLM" and "View as Markdown" in each page's article meta row. All four merge like `web.branding`:
  a site's own value overrides the matching global field. A site opts a globally-authored
  `contributeUrl` back out for itself by setting its own `web.links.contributeUrl` to an empty
  string.
- **Listing size**: `web.listing.perPage` (default 10) is the number of cards on one pagination
  page - the posts feed and every tag or category archive alike.
- **RSS feed**: `web.feed` can be set globally or under a site's `web` block. It carries
  `enabled`, `limit`, and `fullText`; see [RSS Feeds](/site/feeds/index.md) for the stream dates,
  item identity, and output path.
- **Deployments**: `codocation.yml` has no deployment section. Named deployment groups live in
  project-root `deployments.yml`; tokens stay in the IDE password safe.
- **Locale sidecars**: optional files sit beside the tree:
  `content/<locale>/<siteId>.pdf.yml`, `<siteId>.seo.yml`, and `<siteId>.redirects.yml`.
  SEO resolves requested locale → `sites.<siteId>.defaultLocale` → built-in defaults. PDF resolves
  requested locale → `sites.<siteId>.defaultLocale` → `sites.<siteId>.pdf` → global `pdf` → built-in defaults. Each
  sidecar merges presence-aware leaves; a present leaf replaces the lower layer wholesale,
  with no deep or list merging. Redirects use only the requested locale and absence means an
  empty set.
- **Fallback source**: locale-aware images and attachments resolve using the page's source
  provenance. A page resolved from the default language and shown in another uses the source
  locale's media, so an English source page cannot accidentally use a German download.
- **Pages have no marker**: a page's text follows its own file, resolving from the requested locale
  when that file exists and from the site's effective default otherwise.
- **Classifier marker**: a label or category entry made of `inherited: true` alone takes the whole
  payload from the current site's effective default. Without it, an omitted classifier identity
  stays unavailable in that locale.

## Scope visibility and writes

- **PDF** exposes Global, Per-site, and Locale targets. Per-site is shown when the project has
  more than one site or `sites.<siteId>.pdf` already contains an explicit override. Locale is
  shown when the project has more than one locale or the requested locale's
  `<siteId>.pdf.yml` already exists. A single-site, single-locale project with neither
  override edits the global section directly, without a selector.
- **Web and Output** expose only Global and Per-site targets. Per-site is shown for a
  multi-site project or when the current site's `web` or `build` section already contains an
  explicit override. These scopes never receive a Locale target.
- **SEO and Redirects** have no scope selector: they have no global or per-site layer outside
  the selected locale sidecars. Their write target is always the current site and requested
  locale. SEO displays the requested-locale → site-default → built-in effective value, but
  edits create or change only a sparse requested-locale override; resetting a leaf deletes only
  that override and restores fallback.
- **SEO `metaTags`** is one list-valued leaf. Lists never merge by element, so the first edit
  to an inherited row materializes the complete effective list in the requested-locale sidecar
  before applying the row change. Resetting `metaTags` removes that whole requested leaf.
- **Redirects** read and write only the requested-locale file. Resetting redirects deletes that
  file or leaves the requested redirect set empty.
- **Locale PDF writes** target the requested locale's PDF sidecar. For a non-default requested
  locale they never write the site-default sidecar; the site default's own sidecar is its
  legitimate target. Reset on any layer deletes only that layer's override, and switching
  scope never moves configuration between layers.

## deployments.yml

Named deployment groups are stored separately from content and presentation configuration:

```yaml
deployments:
  production:
    name: Production documentation
    sites:
      - cdc
    provider: cloudflare-pages
    accountId: <account id>
    project: codocation
  github-mirror:
    name: GitHub mirror
    sites:
      - cdc
    provider: github-pages
    repository: company/documentation
    branch: gh-pages
    path: docs/
```

Each group has a stable mapping key, a visible `name`, and at least one site ID. Cloudflare Pages
groups identify an account and project. GitHub Pages groups identify a repository and branch, with
an optional publish path. A site may belong to more than one group. Two groups cannot target the
same normalized destination, and their selected sites must not collide in the generated snapshot.
