# ADR 0034. Documentation site: Docusaurus, OpenAPI, Scalar; internal first

## Status
Accepted.

## Context

Docs were plain Markdown in `docs/`, `spec/` and per-service READMEs, with no
site, link checking or API specification. Routes are hard-coded `v1/...`
prefixes validated by hand-written TypeScript; there was no OpenAPI. Partners
need integration guides and an API reference; founders need architecture,
decisions and operations in one place, with honest implementation status.

## Decision

1. **Docusaurus** (`docs-site/`, own `package.json` and lockfile) renders
   `docs/` in place. No files move. `markdown.format: 'detect'` keeps `.md`
   as CommonMark; `.mdx` opts into MDX. Broken links and anchors fail the
   build. Links to files outside the site are rewritten to GitHub
   (`docs-site/plugins/remark-repo-links.mjs`).
2. **OpenAPI is the source of the API reference**, generated code-first with
   `@nestjs/swagger` decorators (`services/api/src/openapi/`). Two documents:
   `internal.json` (every route; in `docs-site/static-internal/`, never copied
   into the public build) and `partner.json` (operations marked
   `x-audience: partner`). Both are committed under
   `docs-site/static/openapi/`; `npm run openapi:check` fails when they differ
   from the code. Unbuilt endpoints are not added to OpenAPI.
3. **Scalar** renders both documents, self-hosted: the renderer is vendored
   from a pinned `@scalar/api-reference` at build time, not loaded from a CDN.
   Request testing and the client button are off and no proxy is configured,
   until a sandbox exists.
4. **Internal first, fail closed.** Every page is internal unless its front
   matter says `audience: public`. The public build (`DOCS_AUDIENCE=public`)
   contains only those pages and the partner reference, and fails if a public
   page links to an internal page or into the repository. The internal site
   is hosted on Cloudflare Pages behind Cloudflare Access, which is already
   used for other hosts. CI deploys it on push to `main`; setup is in
   [Contributing to the docs](../contributing/docs.md#hosting). The public
   build is not deployed yet.
5. **Status labels.** Page front matter `status: implemented | in-progress |
   proposed`, optional `demo: true` and `verified: <commit>`; section-level
   `> **Status: …** — evidence: …` lines on mixed pages. A status must cite
   code or tests; a mockup alone never makes something implemented.
6. **Spec and glossary** (`spec/PLAYERPAL_AUTH_IDENTITY_SPEC.md`,
   `CONTEXT.md`) are rendered on the internal site only, from copies made at
   build time. They stay where they are and the spec still wins on conflict.

## Defaults chosen for open questions

These were taken as defaults when the site was built and can be revisited:

| Question | Default |
|---|---|
| Public site now or later | Later. No page is marked public. |
| Code-first or spec-first OpenAPI | Code-first |
| Sandbox for request testing | None yet; reference is read-only |
| PR previews | None; local preview plus CI build |
| App location | `docs-site/` with its own lockfile |
| Publish the spec | No; internal build only |
| Partner doc scope | Includes routes behind the shared machine key (activity batches and uploads, link intents and results, aliases) because integrations call them today. Revisit before any public release: per-partner credentials do not exist for these routes yet. |

Still undecided and not answered by this ADR: retention schedule and purpose
limits, reward-partner scope, the canonical redemption state model, packaging
and pricing. Pages describing them say so.

## Amendment: shareable public site

The site now serves partners, prospects and investors as well as the team.
Public pages are `.mdx` under `product/`, `developers/`, `analytics/`,
`rewards/` and `trust/`, written to [contributing/style.md](../contributing/style.md):
reader-facing status labels (Available / In development / Planned), evidence
kept in MDX comments, no repository links or internals. The PlayerPal brand
theme, a landing page, offline search and a social preview image are part of
the build. `npm run check:public` scans the public build output for hostnames,
infrastructure vendors, configuration keys, non-placeholder IDs, repository
links and the internal OpenAPI document, and runs in CI.

## Consequences

- A behaviour change updates its page and, for endpoints, the regenerated
  OpenAPI files in the same PR; CI checks the OpenAPI part and the KPI IDs.
- The API gains decorators and hand-written JSON schemas that must track the
  hand-written validators until validation moves to a schema library.
- Internal hosting needs a Cloudflare Access application before the docs image
  is added to `build-push.yml` and `k8s/apps/`.
