Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/docs-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ jobs:
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

# The whole assembled tree, not just the version just built: dev/, every
# version directory still being served, and latest/. A version published
# months ago is still a page a reader can open, and nothing else re-checks
# it.
- name: Internal links across the assembled tree
run: node docs-site/scripts/check-links.mjs published${{ steps.plan.outputs.scratch && '/_dryrun' || '' }}

- name: Verify the dry run stayed inside its scratch prefix
if: github.event_name == 'workflow_dispatch'
working-directory: published
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/docs-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ jobs:
- name: Build (under a version segment, as published)
run: npm run build:web -- --base=/dev/

# Every internal link and image in the built output, checked against the
# base it was built with. The adapter's own check runs on SOURCE, so it
# cannot see a reference that only breaks once the pages are emitted.
- name: Internal links
run: npm run check:links

# Report only, on purpose: link rot in a third party's URL is not a reason
# a contributor's merge cannot happen. `continue-on-error` as well as the
# script's own exit 0, so a crash in the reporter cannot block either.
- name: Outbound link report (never blocks)
continue-on-error: true
run: npm run report:external

- uses: actions/upload-artifact@v4
with:
name: docs-site
Expand Down Expand Up @@ -92,6 +105,11 @@ jobs:
- name: Build the local reader
run: npm run build:local

# The offline reader's links are relative rather than base-prefixed, so
# this is a genuinely different resolution path from the web build's.
- name: Internal links
run: npm run check:links

- uses: actions/upload-artifact@v4
with:
name: docs-manual-local
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Current version on `main`: **0.0.1**.

### Added
- **The documentation workstream is closed out: an authoring guide, link
hardening, and dependency hygiene**
([#348](https://github.com/Robomous/RoadMaker/issues/348), docs-s4 —
[ADR-0009](docs/decisions/0009-documentation-site-tiered-docs.md)).
[Writing user documentation](docs/contributing/documentation.md) answers
"I want to document a thing — where does it go, and what may I write?" without
reading a generator: the two tiers and how to choose, the exact syntax budget
the in-app renderer supports, the bridge convention, image placement, and the
`index.md` manifest rule.

The syntax budget is stated from what the renderer actually does rather than
from convention — no footnotes, no front matter, no admonitions, no code-fence
transforms, and **no heading anchors**, so `page.md#section` lands at the top
of the page in the app while working on the site. The `<kbd>` inconsistency is
settled in favour of backticks, with the reasoning; applying it to the seven
pages that use `<kbd>` stays a separate change.

`check:links` verifies every internal link and image in a **built** tree —
each version directory, `dev/`, and `latest/`, each against the base it was
built with. That is a different check from the adapter's: the adapter reads
source and cannot see a reference that only breaks once pages are emitted.
Outbound links get a **non-blocking report** instead; link rot in a third
party's URL is not a reason a contributor's merge cannot happen.

npm dependency policy, absent until now, is written down: scoped to
`docs-site/`, `npm ci` against a committed lockfile, the licence gate, a
documented **monthly** cadence, and an explicit rule that no npm automation may
open pull requests or fail a job for the C++ side. The site's build tools and
the deliberately stubbed `sharp` are recorded in `THIRD_PARTY_LICENSES.md`.

Drift fixed: `docs/README.md` no longer says the tree is arranged so a
static-site generator "could be adopted later", and the roadmap no longer
describes an `_order` manifest for guides that was sketched during planning
and never built.
- **The documentation site publishes itself, versioned**
([#347](https://github.com/Robomous/RoadMaker/issues/347), docs-s3 —
[ADR-0009](docs/decisions/0009-documentation-site-tiered-docs.md)). GitHub
Expand Down
28 changes: 28 additions & 0 deletions THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ BSL-1.0, Unlicense/CC0. License files verified in each upstream archive.
| {fmt} | 12.2.0 | MIT | https://github.com/fmtlib/fmt | Formatting (kernel-wide, no iostream) |
| spdlog | 1.17.0 | MIT | https://github.com/gabime/spdlog | Logging (built against external fmt) |

## Documentation-site build tools (npm, `docs-site/` only)

Build-time only, on Linux CI and on a contributor's machine. **Nothing here is
compiled into, linked by, or shipped inside any RoadMaker artifact** — the
kernel, the Python wheels, and the editor never see them, and CMake never
invokes npm. What ships from this tree is the generated HTML, which is
RoadMaker's own content.

Direct dependencies only; the full installed tree (374 packages at the time of
writing, all MIT/ISC/Apache-2.0/BSD/BlueOak/Python-2.0) is verified on every CI
run by `npm run licenses`, which fails on anything outside the permitted set.
Policy and the monthly update cadence:
[dependencies — npm](docs/standards/dependencies.md).

| What | Version | License | Where | Notes |
|---|---|---|---|---|
| Astro | 5.14.1 | MIT | https://github.com/withastro/astro | Static site generator for the documentation site |
| Starlight | 0.36.0 | MIT | https://github.com/withastro/starlight | Documentation theme built on Astro |
| Node.js | 24.x (pinned by `.nvmrc` + `engines`) | MIT | https://nodejs.org | Runtime for the adapter, build and check scripts |

**`sharp` is deliberately stubbed out.** Astro lists it as an optional
dependency for its default image service, and its prebuilt libvips binaries are
**LGPL-3.0-or-later**. Qt is this project's only sanctioned LGPL dependency, so
`docs-site/package.json` overrides `sharp` to a local no-op that throws if
anything imports it, and `astro.config.mjs` uses the passthrough image service
instead. `--omit=optional` was not usable — it would also drop a required
native binary. No libvips binary is ever downloaded, built, or shipped.

## Bundled documentation (not dependencies)

Third-party copyrighted material that lives in the repository but is **not**
Expand Down
8 changes: 8 additions & 0 deletions docs-site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@ npm run build:local # the offline reader that ships in a release
npm run build:web -- --base=/dev/ # the published site, for one version
npm run dev # same as build, then a dev server
npm run licenses # licence gate over the installed tree
npm run check:links # internal links, over a build or a whole publish tree
npm run report:external # outbound links — a report, never a failure
npm test # script tests (node:test)
```

Both builds end with `check:links`, so a broken reference fails the build that
produced it. Each build also writes a `.rm-docs-build.json` stamp recording the
base it used: the checker runs in its own shell, where the build's environment
is long gone, and a checker that assumed the wrong base would report every link
in the build as broken.

The adapter **fails the build** on a broken link, naming the source page and the
target.

Expand Down
4 changes: 3 additions & 1 deletion docs-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
"licenses": "node scripts/licenses.mjs",
"check": "node scripts/check-f1-coverage.mjs",
"check:local": "node scripts/check-local-build.mjs",
"test": "node --test \"test/**/*.test.mjs\""
"test": "node --test \"test/**/*.test.mjs\"",
"check:links": "node scripts/check-links.mjs",
"report:external": "node scripts/report-external-links.mjs"
},
"dependencies": {
"@astrojs/starlight": "0.36.0",
Expand Down
10 changes: 10 additions & 0 deletions docs-site/scripts/build-local.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
// The release packaging job runs this and hands the resulting dist/ to CMake as
// a path; CMake never invokes npm.
import { spawnSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

Expand Down Expand Up @@ -51,6 +52,15 @@ step('adapt docs/user-guide', node, [join(here, 'adapt.mjs')]);
step('F1 coverage', node, [join(here, 'check-f1-coverage.mjs')]);
step('astro build (file format, search off)', node, [astro, 'build']);
step('relativize references', node, [join(here, 'relativize.mjs')]);

// See build-web.mjs: the checker runs in its own shell and must not have to
// guess. The offline reader's references are relative, so its base is `/`.
writeFileSync(
join(root, 'dist', '.rm-docs-build.json'),
`${JSON.stringify({ target: 'local', base: '/' }, null, 2)}\n`,
);

step('verify the local build', node, [join(here, 'check-local-build.mjs')]);
step('internal links', node, [join(here, 'check-links.mjs')]);

console.log('\nbuild:local: dist/ is ready to open from file://');
13 changes: 13 additions & 0 deletions docs-site/scripts/build-web.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// Markdown — content Astro passes through untouched). Passing it through one
// environment variable is what keeps those two from disagreeing.
import { spawnSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

Expand Down Expand Up @@ -59,6 +60,18 @@ step('theme tokens', [join(here, 'theme-css.mjs')]);
step('adapt docs/user-guide', [join(here, 'adapt.mjs')]);
step('F1 coverage', [join(here, 'check-f1-coverage.mjs')]);
step('astro build', [join(root, 'node_modules', 'astro', 'astro.js'), 'build']);

// Record the base in the output. check-links.mjs runs in its own shell — in CI
// and on a contributor's machine — where this environment variable no longer
// exists, and a link checker that assumes the wrong base reports every link in
// the build as broken. The stamp rides along into the published tree, so a
// version directory (and the `latest/` copy of one) stays self-describing.
writeFileSync(
join(root, 'dist', '.rm-docs-build.json'),
`${JSON.stringify({ target: 'web', base }, null, 2)}\n`,
);

step('verify the base', [join(here, 'check-web-build.mjs')]);
step('internal links', [join(here, 'check-links.mjs')]);

console.log(`\nbuild:web: dist/ is ready to publish under ${base}`);
Loading
Loading