Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
91 changes: 91 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Docs

# The docs site lives in modelplaneai/docs-site: the Hugo project, the theme,
# the build, and the Vercel project. This repo owns the prose and tells that
# repo when to render it. Nothing here builds the site, and nothing there
# writes back to this repo.
#
# On a pull request, the site deploys a preview of that revision and aliases it
# to a hostname derived from the pull request number - which is why the link
# can be posted here immediately, from this repo's own token, before that build
# finishes.
#
# On a merge, the site rebuilds every version from the tip of each branch it
# serves. That is what publishes a docs change: nothing pins a content
# revision, so publishing is a rebuild.
#
# The trigger is pull_request_target so that a fork's pull request is previewed
# too. That runs this workflow in the base repository's context, with secrets,
# so it must never execute anything from the pull request - and it doesn't:
# there is no checkout here, and no step reads a file from the branch. Never
# add one.
on:
pull_request_target:
paths: ['docs/**', 'apis/**']
push:
branches: [main, 'release-*']
paths: ['docs/**', 'apis/**']

permissions:
contents: read
pull-requests: write

concurrency:
group: docs-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
render:
runs-on: ubuntu-24.04
env:
SITE_REPO: modelplaneai/docs-site

steps:
# Dispatching to another repository needs a credential this repo's own
# token cannot provide. A GitHub App installed on the site repo alone
# mints one that expires in an hour and belongs to no person, so it does
# not quietly die when someone leaves. Contents: read & write is the
# permission repository_dispatch requires.
- name: Mint a token for the site repo
id: token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.DOCS_SITE_APP_ID }}
private-key: ${{ secrets.DOCS_SITE_APP_KEY }}
owner: modelplaneai
repositories: docs-site

# The head sha rather than the merge commit: it is what the reviewer is
# reading. A fork's commits are reachable here through refs/pull/<n>/head,
# so the site can fetch that sha from this repo without knowing the fork
# exists.
- name: Request a preview
if: github.event_name == 'pull_request_target'
env:
GH_TOKEN: ${{ steps.token.outputs.token }}
REF: ${{ github.event.pull_request.head.sha }}
PR: ${{ github.event.number }}
run: |
gh api "repos/$SITE_REPO/dispatches" \
-f event_type=content-preview \
-f "client_payload[ref]=$REF" \
-f "client_payload[pr]=$PR"

# Once per pull request. The hostname follows from the number, so it
# stays correct as the branch is pushed to and rebuilt.
- name: Post the preview link
if: github.event.action == 'opened' || github.event.action == 'reopened'
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.number }}
run: |
gh pr comment "$PR" --body \
"Docs preview: https://modelplane-docs-pr-${PR}.vercel.app (ready once the site's Content workflow finishes)"

- name: Publish
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ steps.token.outputs.token }}
run: |
gh api "repos/$SITE_REPO/dispatches" \
-f event_type=content-published
44 changes: 0 additions & 44 deletions .github/workflows/docsearch.yml

This file was deleted.

12 changes: 0 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@ result
.direnv
__pycache__/

# Docs Resources #
######################
docs/resources/*
docs/resources/_gen/assets/*
docs/public/*
docs/hugo
docs/hugo-*
docs/.hugo_build.lock
docs/hugo_stats.json
docs/node_modules
docs/utils/webpack/node_modules

# Local superpowers planning artifacts (specs/plans/notes) — not repo content
docs/superpowers/

Expand Down
71 changes: 42 additions & 29 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,31 +395,27 @@ moves both.

## Working on the docs site

The documentation site under `docs/` is a [Hugo](https://gohugo.io/) project.
`nix flake check` builds it as one of its checks, so a broken site fails CI.
Docs prose lives here under `docs/content/`, with the example manifests it
embeds under `docs/manifests/` and the API reference's grouping in `docs/data/`.
The site that renders it — the [Hugo](https://gohugo.io/) project, its layouts,
theme, and asset pipelines — is the
[docs-site](https://github.com/modelplaneai/docs-site) repo. Edit prose here;
edit the site there.

Run the commands below from the repository root, not from `docs/`. They're flake
apps (`nix run .#...`), so they resolve against the flake at the root regardless
of which file you're editing.

Preview it locally with live reload:
Preview what you are editing, with live reload, from the root of this repo:

```bash
nix run .#docs-serve # http://localhost:1313
nix run github:modelplaneai/docs-site#preview # http://localhost:1313
```

`nix build .#docs` produces the production site in `result/`. The production
build compiles the theme's SCSS and runs it through PostCSS to strip unused
CSS, sort media queries, and minify. Those Node dependencies are pinned in
`docs/package-lock.json` and built reproducibly; the local preview skips them.
The site serves this working tree, so the pages you see are the files under
your cursor: no branch to push, no pin to move, and nothing about Hugo checked
in here. The version switcher lists every version and those links 404 locally,
since only this one is being served.

The site's JavaScript bundle is built by webpack and committed to git under the
theme's assets. Rebuild it after changing anything under
`docs/utils/webpack/src/` and commit the result:

```bash
nix run .#docs-generate
```
Versions are this repo's `release-X.Y` branches: whatever is on `release-0.2` is
what the 0.2 docs say. The site repo builds each of them, decides which release
is latest, and deploys; see [RELEASING.md](RELEASING.md).

### Manifest shortcodes

Expand Down Expand Up @@ -474,14 +470,19 @@ validator is `docs/utils/validate/validate_manifests.py`.

### Linting and link checking

Docs prose is linted with [Vale](https://vale.sh) and internal links are checked
with [htmltest](https://github.com/wjdp/htmltest). Both run as flake checks, so
run them with the rest of CI:
Docs prose is linted with [Vale](https://vale.sh), which runs as a flake check,
so run it with the rest of CI:

```bash
nix flake check
```

Internal links are checked with [htmltest](https://github.com/wjdp/htmltest)
against the built site, which means it runs in the site repo, not here. Nothing
there pins a revision of this repo, so a content change that breaks a link
fails on the next build there: the preview of your pull request, or the
rebuild your merge triggers.

Custom Modelplane rules live in `docs/utils/vale/styles/Modelplane/`.

Vale flags brand names, acronyms, API types, and technical terms it doesn't
Expand All @@ -495,13 +496,25 @@ CI runs them on every pull request via the same check (see

### Deployment

The site deploys to [Vercel](https://vercel.com/). Vercel builds it with the
same `nix build .#docs` derivation that `nix flake check` verifies, so what
ships matches what CI checks. `vercel.json` points the build at
[`docs/vercel-build.sh`](docs/vercel-build.sh), which installs Nix into
Vercel's build image, runs the build, and writes the static site to `public/`.
Vercel's GitHub app drives deploys as usual: preview URLs on pull requests
(including from forks) and production on merge to `main`.
The site repo holds the only Vercel project.
[`.github/workflows/docs.yml`](.github/workflows/docs.yml) here asks it to
render, and never renders anything itself:

| Here | There |
|---|---|
| a pull request touching `docs/` or `apis/` | deploys that revision as a preview |
| a merge to `main` or a `release-*` branch | rebuilds every version into production |

A merge is what publishes: nothing there pins a content revision, so
publishing is a rebuild that reads the tip of every branch.

The preview link is posted on the pull request as soon as it opens, because the
hostname follows from the pull request number rather than from the deployment —
so the site repo needs no write access here. It answers once that repo's
`Content` workflow finishes, a minute or so later.

A pull request from a fork gets neither secrets nor a write token, so it gets
no preview; use the local command above.

## Releasing

Expand Down
88 changes: 14 additions & 74 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,77 +22,17 @@ release from it and run the workflow against the new tag.

## Versioning the docs

Docs are versioned at the minor level, each version on its own subdomain. Two
Vercel projects serve them, and each domain serves its build's content directly —
nothing redirects:

- **dev project** (`main` branch) — serves `main.docs.modelplane.ai`, the
unreleased docs, so work in progress stays browsable.
- **release project** (latest `release-X.Y` branch) — serves both
`docs.modelplane.ai` (the canonical apex, always the latest release) and that
release's permanent `vX-Y.docs.modelplane.ai` subdomain.

Older releases keep their own project on their `vX-Y.docs.modelplane.ai`
subdomain. There is no apex redirect: `docs.modelplane.ai` *is* the latest
release's build, so a reader on the bare domain gets the latest docs with the URL
unchanged. What a build serves is decided entirely by its Vercel project and
baseURL, never by the home page (`docs/themes/geekboot/layouts/index.html`).

Each project's baseURL:
- dev project → `HUGO_BASEURL = https://main.docs.modelplane.ai/` (scoped to `main`).
- release project → `docs.modelplane.ai` (leave `HUGO_BASEURL` unset in
production so the build uses the baked `https://docs.modelplane.ai/` from
`nix/docs.nix`). The `vX-Y` subdomain aliases the same build.
- PR previews → root-relative `HUGO_BASEURL = /`, so assets resolve against the
preview host.
- local (`hugo server`) → `localhost` from hugo.toml's `baseURL = "/"`.

`docs/data/versions.yaml` is the single list of every version and its URL. It
drives the version dropdown and must be identical on every branch — `main` and all
release branches — so each build offers the same switcher.

One-time DNS setup (already done): a wildcard CNAME `*.docs.modelplane.ai →
cname.vercel-dns.com` covers `main.docs` and every release subdomain. No new DNS
record is needed per release.

To publish docs for a new minor release (e.g. `v0.1.0`):

1. On `release-0.1`, set `version = "0.1"` in `docs/hugo.toml`.

2. Add the release to `docs/data/versions.yaml`, newest first, on both `main` and
`release-0.1` (keep the file identical across branches):
```yaml
versions:
- version: "main"
url: "https://main.docs.modelplane.ai"
- version: "0.1"
url: "https://v0-1.docs.modelplane.ai"
```
On `main`, also set `latest = "0.1"` in `docs/hugo.toml` so the version
dropdown and the "not the latest release" banners point at the new release.

3. In the Vercel dashboard, create the release project for `release-0.1` (or
reuse the existing one) and, under Settings → Domains, assign it both
`docs.modelplane.ai` (the apex) and `v0-1.docs.modelplane.ai`. Leave
`HUGO_BASEURL` unset in Production so the build bakes `https://docs.modelplane.ai/`.
Trigger a redeployment and confirm both domains serve.

4. Merge the changes. The apex now serves the new release directly, and the
version dropdown on every build links to it.

The dev project's `main.docs.modelplane.ai` domain and its
`HUGO_BASEURL = https://main.docs.modelplane.ai/` env var (scoped to `main`) are a
one-time setup, done when the first release ships.

To fix a typo or update content in an archived version, push to the release branch.
The versioned deployment rebuilds automatically.

When a new minor ships (e.g. `v0.2.0`):

1. Repeat steps 1–4 for `release-0.2`, adding the `v0.2` entry above `v0.1` in
`versions.yaml` on every branch and bumping `latest` to `0.2` on `main`.
2. Move `docs.modelplane.ai` to the `release-0.2` project so the apex tracks the
new latest.
3. On the old `release-0.1` project, set `HUGO_BASEURL = https://v0-1.docs.modelplane.ai/`
so it stays self-canonical at its permanent subdomain now that it no longer
owns the apex.
Docs are versioned at the minor level, and the versions are this repo's own
`release-X.Y` branches: whatever is on `release-0.2` is what the 0.2 docs say.
Cutting that branch in step 1 above is this repo's whole part in publishing a
version.

The rest happens in the docs site repo,
[docs-site](https://github.com/modelplaneai/docs-site). It builds every version
from its own `main` into one deployment — the latest release at the root, older
releases under `/vX.Y/`, and this repo's `main` under `/main/` — and it is the
one place that decides which release is latest. Publishing a new version is one
entry added to its version list; see that repo's README.

To fix a typo in a released version, push the fix to that `release-X.Y` branch
here.
Loading
Loading