Files
coder/scripts/docshtmlcheck/README.md
T
Nick Vigilante 3d30e201b5 docs: move contributor guides out of the published docs into the repo (#29751)
## Merge order

Merge first; then republish the docs; then coder/coder.com#1100 (the
redirects).

## Summary

The nine pages under `docs/about/contributing/` are for people working
in this repository, not for Coder users or administrators, so they leave
the published documentation.
The repository becomes their canonical home.
Until now the root `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md` were
two-line stubs that only linked to the docs site, so contributors on
GitHub were bounced out to read them.

A companion PR in `coder/coder.com` adds redirects from every retired
`/docs/about/contributing/*` URL to the new GitHub location.
It must merge only after this PR has merged and the docs have
republished.

## Changes

| Old path | New path |
| -- | -- |
| `docs/about/contributing/CONTRIBUTING.md` | `CONTRIBUTING.md`
(replaces the stub) |
| `docs/about/contributing/CODE_OF_CONDUCT.md` | `CODE_OF_CONDUCT.md`
(replaces the stub) |
| `docs/about/contributing/SECURITY.md` | deleted; the root
`SECURITY.md` already holds the policy |
| `docs/about/contributing/AI_CONTRIBUTING.md` |
`contributing/AI_CONTRIBUTING.md` |
| `docs/about/contributing/backend.md` | `contributing/backend.md` |
| `docs/about/contributing/frontend.md` | `contributing/frontend.md` |
| `docs/about/contributing/modules.md` | `contributing/modules.md` |
| `docs/about/contributing/templates.md` | `contributing/templates.md` |
| `docs/about/contributing/documentation.md` |
`contributing/documentation.md` (see note below) |

For every moved file: the docs front matter is stripped and its `title`
becomes the H1, relative links are rewritten for the new depth
(`../../../foo` becomes `./foo` at the root and `../foo` under
`contributing/`; the two links into `docs/admin/` become
`../docs/admin/...`), and the docs-only `<div class="tabs">` wrapper in
`CONTRIBUTING.md` is removed.
`CONTRIBUTING.md` gains a short intro that links the code of conduct,
the security policy and the area guides, since GitHub only auto-surfaces
the root files.

`docs/manifest.json` loses the About → Contributing group and
`docs/images/icons/contributing.svg`, which only that group used.

Inbound references updated:

- `README.md`: contribution guide link now points at `CONTRIBUTING.md`
on GitHub.
- `docs/tutorials/example-guide.md` and
`docs/install/releases/feature-stages.md`: link to
`contributing/documentation.md` on GitHub, since a docs page cannot link
to a repo-only file.
- `docs/.style/style-guide/README.md`: the "Relationship to" section
names the new location.
- `scripts/docshtmlcheck/main.go`: both the package comment and the
user-facing error string name `contributing/documentation.md`.
- `scripts/docshtmlcheck/README.md`: relative link to the new location.
- `coderd/database/migrations/migrate_test.go`: `t.Log` link now points
at `contributing/backend.md#database-fixtures-for-testing-migrations`.
- `.github/pull_request_template.md`: AI contribution guidelines link.
- `.github/workflows/contrib.yaml`: commit-message guidelines link in
the PR title check.
- `.github/workflows/test-docs-preview-mapper.sh`: the mapper test case
that used `docs/about/contributing/CONTRIBUTING.md` now uses
`docs/admin/security/audit-logs.md`.
-
`.claude/docs/{ARCHITECTURE,PR_STYLE_GUIDE,WORKFLOWS,DOCS_STYLE_GUIDE}.md`:
links to `CONTRIBUTING.md`.

`git grep about/contributing` is empty after this change.
`CODEOWNERS` had no entry for the old paths.

Note on `documentation.md`: it is docs-authoring guidance and is parked
in `contributing/` for now.
Folding it into `docs/.style/` is tracked separately and is deliberately
not attempted here.

## Testing

- `go run ./scripts/docshtmlcheck` over `docs/`, and again over
`contributing/`, `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md`: clean.
- `go vet ./scripts/docshtmlcheck/...` and `go test
./scripts/docshtmlcheck/...`: 47 tests pass.
- `go vet ./coderd/database/migrations/...`: clean. `go test` for that
package fails to link on this machine (`tailscale.com/tstime/mono:
invalid reference to runtime.nanotime1`) identically on unmodified
`main`, so it is a local toolchain issue and not exercised here; the
change is a string in a `t.Log`.
- `pnpm check-docs` (markdownlint-cli2 and markdown-table-formatter over
`docs/`, `.claude/docs/`, root `*.md`): 495 files, 0 errors. The same
two tools over `contributing/*.md`: 0 errors.
- Every relative link in the eight moved files resolves to an existing
path (scripted check).
- `bash .github/workflows/test-docs-preview-mapper.sh`: the changed
`assert_maps_to` case passes; the six `resolve_ref`/`pages_for_image`
failures it reports are identical on unmodified `main` on macOS.

Linear: [DOCS-975](https://linear.app/codercom/issue/DOCS-975)

---

🤖 Built with AI assistance.
2026-09-24 19:42:18 +00:00

3.6 KiB

docshtmlcheck

docshtmlcheck fails CI when Markdown under docs/ contains invalid inline HTML that the documentation site's renderer silently drops or mangles. It runs as make lint/docs-html (part of make lint).

What it catches

  • Swallowed angle-bracket placeholders. An unwrapped placeholder such as <region> or <server>__ is parsed as an unknown HTML tag and stripped from the rendered page, so readers see broken text. Wrap placeholders in backticks so they render as inline code (see contributing/documentation.md). This also covers CLI --help strings and Swagger annotations, whose text is generated into docs/reference/**.
  • Void-element end tags such as </br>. Void elements like <br>, <img>, and <hr> have no end tag.
  • Capitalized or unregistered component tags such as <Image> or <Table>. The docs renderer reads a capitalized tag as a component reference and drops it unless the component is registered (only the lowercase <children> directive is). Any name outside the standard HTML5 element set is reported the same way.
  • Unclosed container tags, for example a <div class="tabs"> that is never closed and leaks its wrapper over the rest of the page.

How it works

Each file is parsed with goldmark and only raw-HTML nodes are inspected, so angle brackets inside fenced code blocks, inline code spans, HTML comments, and <https://…> / <user@host> autolinks are ignored. Each raw-HTML node is tokenized as a whole with golang.org/x/net/html, so a tag whose attributes wrap across lines is not torn in half. A tag whose raw name is capitalized is reported as a component reference; otherwise any name outside the standard HTML5 element set (plus the intentional <children> renderer component, which is still balance-checked) is reported. Inline SVG and MathML are intentionally not in the allowed set (no docs page uses them); add the element to allowedElements in main.go if that changes. A finding on a generated page under docs/reference/** also prints a note pointing at the generator source, since edits to the generated file do not persist.

Limitations

A few gaps are accepted because no docs page hits them today:

  • A placeholder whose name is itself a real HTML element (<input>, <time>) is indistinguishable from intended markup and passes. Such placeholders almost always live in fenced code blocks, which are ignored.
  • The interior of a raw-text element (<script>, <style>) is a single opaque token to the HTML tokenizer, so a tag nested inside one is not scanned. An unclosed <script>/<style> is still caught.

Usage

$ go run ./scripts/docshtmlcheck        # scans docs/
$ go run ./scripts/docshtmlcheck path/to/file.md path/to/dir

Allowlist

allowedUnknownTags in main.go is a deliberately narrow, per-file escape hatch for placeholders whose source is outside this repository (so they cannot be fixed by a source edit here). It currently holds a temporary entry for docs/reference/cli/agent-firewall.md (<host>/<glob>, generated from the external github.com/coder/boundary CLI help).

The escape hatch is self-clearing: if an allowlisted tag no longer appears in its file (for example once the upstream fix and dependency bump land and the generated page no longer emits the bare placeholders), the linter reports a stale-allowlist-entry and fails until the dead entry is removed, so a suppression can never silently mask a later regression of the same tag.