Commit Graph
3 Commits
Author SHA1 Message Date
Nick Vigilante b95f2531b5 feat: populate docs prose style guide as a landing page plus subpages (#26632)
Replace the `docs/.style/style-guide.md` scaffold with the populated
prose style guide,
structured as a `README.md` landing page plus one subpage per topic so
GitHub auto-renders the landing when readers open the style-guide
folder.

## Layout

```text
docs/.style/
  style-guide/
    README.md                          (landing: intro, section list, editing conventions, Vale enforcement)
    audience-and-scope.md              (one audience, one outcome, declared up front; canonical personas)
    voice-and-tone.md
    word-choice.md
    accessibility-and-inclusion.md     (new)
    capitalization-and-punctuation.md
    formatting.md                      (text formatting + block elements + screenshots sparingly)
    numbers-units-and-dates.md
    editor-setup.md                    (placeholder)
```

Every repo reference to the old path is rewired to the new path:
`AGENTS.md` (and its `CLAUDE.md` / `.cursorrules` symlinks),
`.claude/docs/DOCS_STYLE_GUIDE.md`,
`docs/about/contributing/documentation.md`, `docs/.style/README.md`,
`docs/.style/styles/Coder/README.md`, and a comment in
`.github/workflows/ci.yaml`. The touched paragraph in each of those
files is reformatted to one sentence per line per the touch-paragraph
rule (refer to [Conventions the guide
dogfoods](#conventions-the-guide-dogfoods)).

## What each page covers

- **Audience and scope** (new): every page targets **one audience
working toward one outcome**; the **install-vs-deploy Coder example**
(workspace user vs platform engineer); pick one audience per page (write
two pages and cross-link rather than tagging sections); pick one outcome
per page (`Configure SSO with Okta` is one outcome, `Configure SSO` is
not); declare audience and scope up front (the H1 names the outcome; the
first paragraph names the audience); **canonical Coder personas**
inlined as four primary (Dave the Developer, Ada the Infrastructure
Admin, Perry the Platform Engineer, Steven the Sponsor) and six
secondary (Melissa the Machine Learner, Tommy the Tester, Caitlin the
Citizen Developer, Felipe the FinOps, Sergio the Security Officer, Tara
the Team Leader), each with a `Coder surface:` line covering the
relevant CLI/workspace/template/RBAC surfaces.
- **Voice and tone**: address the reader directly, avoid first-person
singular, reserve first-person plural for **Coder Technologies the
company** (with an explicit ban on `we` for the product itself and on
combined `you and the docs`), active voice, present tense with a
**conditional/predictive `will` exception** (`If you do X, Y will
happen`), **no sentence-ending prepositions** with a clunky-exception
note.
- **Word choice**: Coder product and feature names with the **Coder CLI
always in backticks (`coder`)** rule, brand names with a parallel
**Terraform CLI in backticks (`terraform`)** rule, **Dev Container**
terminology (proper-noun specification vs lowercase instance, parallel
to Coder / workspace), **phrasal verbs and their noun forms generalized
as a table** (set up/setup, log in/login, sign in/sign-in, log
out/logout, back up/backup, roll out/rollout, start up/startup, shut
down/shutdown, with the `Quickstart` exception), `refer to` / `check
out` / `visit` over `see`, `Learn more` versus `Next steps` with an
**ableism rationale** (`steps` as a physical-mobility metaphor),
`tutorial` versus `walkthrough` with an **ableism rationale**,
**`select` over `click`**, **`Don't assume simplicity or
difficulty`** (covers both `simple`/`easy` and `complex`/`non-trivial`),
**`Avoid weasel words`** (vague attributions in the Wikipedia sense like
`many believe`, `experts agree`, `studies show`), plain language for
product actions with an **industry-term exception scope** for the Linux
`kill` command, the `SIGKILL` signal, and the `disabled` config flag
state.
- **Accessibility and inclusion** (new): WCAG 2.1 Level AA as the
minimum target with AAA as a stretch goal; heading structure (one H1 per
page, no skipped levels, **substantive content between headings**);
inclusive pronouns; inclusive-language substitutions including a
**dedicated `sanity check` row** with `smoke testing` / `confidence
testing` / `acceptance testing` alternatives; descriptive link text; alt
text and decorative-image conventions; **plain English for international
readers** (no idioms; common Latin abbreviations `e.g.`, `i.e.`, `etc.`,
`vs.`, and `et al.` allowed, less common ones not); page descriptions in
`docs/manifest.json` (the docs site does not yet support YAML front
matter); reading level; color contrast deferred to the docs site theme.
- **Capitalization and punctuation**: sentence-case headings, no
gerund-leading headings with **documented exceptions** (`Pricing`,
`Billing`, `Logging`, `String formatting`, etc.), **trailing heading
punctuation in three tiers** (periods and exclamation marks forbidden at
error severity, question marks allowed sparingly at suggestion severity,
characters inside backticks exempt for both), no em or en-dashes with a
**corrected example** showing parenthetical em-dash use rather than
series-joining, Oxford comma, US-style quotation, semicolons sparingly,
rare exclamation marks, numeric ranges.
- **Formatting**: text formatting (bold for UI with **explicit
greater-than separator rule for navigation paths**, italics for
emphasis, code font for identifiers presented as a **bulleted list**)
and block elements (code blocks with language fences plus **link to the
Prism supported-languages reference**, callouts with tightened
scenarios, tabs with the actual `` syntax and a **macOS/Linux/Windows
example**, lists with a **five-item prose-list cap rule** and an
**explicit terminal-punctuation rule** (complete sentences end in
periods, phrases completing a lead-in paragraph end in periods,
single-word labels carry no terminal punctuation, no mixing styles in
one list), tables with a **narrow-table guideline** that reconsiders the
structure when many columns are needed, links including the rule that
**non-docs codebase links also use relative paths**, images,
**screenshots sparingly** with a maintenance-burden rationale and an
adapted quote from Lorna Jane Mitchell's `Short tech writing style
guide for developers`), with cross-references to the accessibility page
for link text and alt text.
- **Numbers, units, and dates**: digits everywhere preference,
non-breaking space between number and unit with **separate pre-render
(Markdown source) and post-render (visible output) demonstrations** plus
a **window-shrink tip** for confirming the rule visually, `Month Day,
Year` date format, 12-hour time with AM/PM, ordinals exception.
- **Editor setup**: placeholder.

## Conventions the guide dogfoods

- **One sentence per line**. Source lines follow a one-sentence-per-line
policy: each sentence sits on its own Markdown source line, sentences
are not split across lines, and lines do not wrap to a fixed column
width. The same convention applies corpus-wide through an **incremental
touch-paragraph rule**: when a contributor edits any line inside a
paragraph, the whole paragraph is reformatted to one sentence per line
as part of the same edit. Bullet items, numbered list entries, and
blockquote lines are each their own paragraph for the rule. Headings,
fenced code blocks, and tables are out of scope. `markdownlint`'s
`MD013` is already disabled, so the convention is editorial.
- **No navigational `see`**. Replaced with **refer to** (formal
default), **check out** (informal/tutorials), or **visit** (external
URLs). `See` is reserved for the observational meaning.
- **HTML entities for em-dashes inside demos**. The em-dash demo encodes
`—` / `–` so the source stays ASCII while the rendered output still
shows the character.
- **No semicolons in body prose**. Body prose prefers two sentences over
a semicolon. Semicolons survive only in heading and rule labels where
they act as separators.
- **Common Latin abbreviations allowed in own prose**. `e.g.`, `i.e.`,
`etc.`, `vs.`, and `et al.` (citation contexts) are fine. Less common
Latin abbreviations (`a priori`, `q.v.`, `viz.`, `n.b.`, `cf.`, `ibid.`)
are not. The rule covers punctuation too: prefer parentheses around
`e.g.` and `i.e.` clauses, one period when `etc.` ends a sentence, both
periods when `etc.` ends a parenthetical that ends a sentence.
- **No idioms or industry-jargon idioms**. `deep dive`, `paved path`,
etc. are rewritten in plain language.

## Rule conventions

Each rule pairs a rationale with **Do** / **Don't** blockquoted
examples and a parenthetical noting the Vale rule that enforces (or will
enforce) the policy. Documentation-only rules are explicitly labeled as
such. Substitution rules use tables.

## Out of scope

- Wiring any new Vale rule. Per-rule PRs land separately per the
rule-authoring doctrine in `docs/.style/README.md`.
- Editor setup page population.
- Redirecting `docs/about/contributing/documentation.md` to the
populated guide (needs a coordinated `coder.com` PR after merge).
- Trimming the `Writing Style` block in
`.claude/docs/DOCS_STYLE_GUIDE.md` and removing the `currently a
scaffold` framing in the agent docs.
- A separate demo PR for the callout types rendered against an existing
docs page.
- Sweeping navigational `see` out of other docs files. The new rule only
dogfoods on the style guide itself; a corpus-wide sweep is a separate
ticket.

## Validation

- `make fmt/markdown`: clean.
- `make lint/markdown`: 0 errors across 494 files.
- `./scripts/check_emdash.sh`: clean.
- Pre-commit-light: passes (fmt + lint + emdash + shellcheck + typos +
actionlint + migrations + helm).
- Dogfood scan: no first-person singular in own prose, no idioms, only
the five allowed Latin abbreviations in own prose, no `walkthrough` or
`Next steps` outside rule definitions and examples, no navigational
`see`, no `click` outside rule definitions and examples, no semicolons
in body prose.

<details>
<summary>CI flake note: <code>check-docs</code> (linkspector)</summary>

The `check-docs` job can fail intermittently on pre-existing external
links in `docs/about/contributing/documentation.md` (lines 29 and 30):
Merriam-Webster occasionally returns HTTP 403 to GitHub Actions runners
and Chicago Manual of Style can time out at 30s. Neither link is touched
by this PR. `docs/.style/` itself is in `.github/.linkspector.yml`
`excludedDirs`, and linkspector annotations confirm zero broken links
from the new pages.

</details>

Resolves DOCS-434.

---

*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick&#39;s behalf.*
2026-06-25 21:11:39 +00:00
Nick Vigilante ee3572ab9a feat: wire Vale prose linter into docs CI (#25467)
Wires Vale into docs CI as an advisory (non-blocking) prose-lint step.
Closes [DOCS-40](https://linear.app/codercom/issue/DOCS-40).

> **Integration update (rebased onto `main`).** Since this branch was
opened, `main` consolidated docs linting into the **required**
`lint-docs` job in `ci.yaml` and removed the standalone `docs-ci.yaml`
([#25608](https://github.com/coder/coder/pull/25608)). This PR adds Vale
to that `lint-docs` job instead of resurrecting `docs-ci.yaml`, and the
`docs/.style/` scaffold defers to the merged
[#25466](https://github.com/coder/coder/pull/25466) (DOCS-180). Vale
stays advisory.

> **Post-review refactor.** Following the Coder Agents review, Vale is
now invoked through `mise exec "aqua:errata-ai/vale"` (the same pattern
as `actionlint`/`zizmor`) instead of a bespoke `curl`/`tar` download.
This removed the GNU-only `grep -oP` version extraction and `uname`/arch
mapping that broke on macOS BSD grep, and the prose step now skips paths
a PR deletes. See the resolved review threads for CRF-17/19/20/21/22.

A sample of what this check does is as follows:

<img width="1443" height="1293" alt="image"
src="https://github.com/user-attachments/assets/cf68dbf9-d9df-49ba-8dbf-200875bc289e"
/>

## What changes

- `.vale.ini` at the repo root: Google base + Coder (custom, empty in
v1) + curated write-good. `alex` rules are pulled in a la carte. Inline
comments justify every enable/disable.
- `mise.toml`: pin Vale `3.7.1` via aqua. `mise.lock`: lock that pin
across all platforms so `mise install --locked` (used by `build_image`)
resolves it.
- `Makefile`: a `docs/.style/.vale-synced` sentinel that gates `vale
sync`, and a `lint/prose` target that runs `vale --no-exit`. Both invoke
Vale via `mise exec "aqua:errata-ai/vale" -- vale ...`, so mise owns the
version and the OS/arch download (no hand-rolled install path).
- `.github/workflows/ci.yaml`: append Vale steps to the existing
required `lint-docs` job: `Detect changed Markdown`, `Restore Vale
styles`, `Prepare Vale styles` (`make docs/.style/.vale-synced`), `Vale
prose lint`, and a default-branch-only `Save Vale styles`. They lint
only changed Markdown under `docs/` that still exists on disk, with a
problem matcher for inline PR annotations.
- `.github/vale-problem-matcher.json`: parses `vale --output=line` so
alerts surface as annotations on the Files Changed tab.
- `.gitignore` and the workflow cache `path:`: use
`docs/.style/styles/*` plus a `!docs/.style/styles/Coder` negation so
adding a package does not require parallel edits.
- `.markdownlint-cli2.jsonc`: ignore the synced styles so `make
lint/markdown` does not lint upstream READMEs.

Scaffold prose under `docs/.style/` and
`.claude/docs/DOCS_STYLE_GUIDE.md` / `AGENTS.md` come from the merged
DOCS-180; this PR no longer touches them. Net diff against `main` is the
8 Vale-wiring files only.

## Severity policy (v1)

Rule severity reflects two things together: the rule's false-positive
rate against real Coder docs and the gravity of the rule. Low FPs plus
high gravity argues for `error`; lower gravity or more judgment calls
argue for `warning` or `suggestion`. v1 lands most rules at `warning`
and the wordiness rules at `suggestion`.

A rule promotes to `error` only when (a) its false-positive rate against
real content is effectively zero and (b) the existing-content violation
count for that rule is also zero. Vale exits non-zero only on
error-level alerts regardless of `MinAlertLevel`; the Makefile and CI
invoke Vale with `--no-exit` so the baseline error count from
un-overridden Google rules does not fail the build while real failures
(bad config, missing files) still propagate.

## CI integration

Vale runs as steps appended to the required `lint-docs` job in
`ci.yaml`, gated on changed Markdown:

1. **`Detect changed Markdown`** (`tj-actions/changed-files`) scopes to
changed `**.md`; the prose step re-filters to `docs/` (the `docs/**.md`
glob silently skips dot-prefixed dirs and would miss
`docs/.style/style-guide.md`).
2. **`Restore Vale styles`** (`actions/cache/restore`), keyed off
`hashFiles('.vale.ini', 'mise.toml', 'docs/.style/styles/Coder/**')`.
mise manages the Vale binary, so only the synced styles are cached.
3. **`Prepare Vale styles`** runs `make docs/.style/.vale-synced` (`mise
exec ... vale sync`).
4. **`Vale prose lint`** filters the changed set to `docs/` paths still
present on disk, then runs `mise exec ... vale --no-exit --output=line`,
emitting inline annotations via the problem matcher.
5. **`Save Vale styles`** writes the cache, gated to `refs/heads/main`
only so PR runs cannot poison the cache other branches restore from (the
zizmor `cache-poisoning` concern).

**Every Vale step is `continue-on-error: true`.** This is a deliberate
change from the original standalone-workflow design: now that Vale lives
inside the *required* `lint-docs` job, a transient `vale sync` network
failure (or first-use `mise` install blip) would otherwise block merges.
`continue-on-error` keeps Vale advisory, so only the markdownlint /
table-formatter checks above (`pnpm check-docs`) remain merge-blocking.
`vale --no-exit` additionally keeps the baseline error count from
un-overridden Google rules from failing the step.

## Verification

- `actionlint` clean on `ci.yaml` (local + `make
lint/actions/actionlint`); `zizmor --persona regular` reports no
findings.
- `make lint/prose` on the full `docs/` corpus: ~406 errors, ~5,346
warnings, ~7,928 suggestions across 461 files, exit 0 (`--no-exit`),
Vale `3.7.1` installed by mise.
- Net diff vs `main` is the 8 Vale-wiring files only; the `docs/.style/`
scaffold already matches `main`.

<details>
<summary>Implementation plan and decision log</summary>

### Why this rule set

The Vale evaluation against the full docs corpus (measured 2026-05-18)
produced ~43,940 raw violations across six candidate base styles. The
selection here drops Microsoft and RedHat (overlap with Google, and
RedHat's Spacing rule hammers technical IDs), and proselint (Annotations
rule treats `> [!NOTE]` admonitions as TODO markers).

Within the kept styles:

- **Google** is the base. Disables: `EmDash` (conflicts with `make
lint/emdash`), `Latin` (i.e./e.g. are fine for our audience), `Spacing`
(4,500 errors on `codersdk.SomeType` patterns in the auto-generated API
reference). Softened: `Parens` to `suggestion`, `WordList` to `warning`.
- **write-good** is the base, with `Passive` and `E-Prime` off.
`TooWordy` and `ThereIs` are suggestions; `Weasel` is a warning.
- **alex** is cherry-picked (not in `BasedOnStyles`): `Ablist`,
`Condescending`, `LGBTQ`, `ProfanityLikely`, `Race`, `Suicide` at
warning. The `ProfanityMaybe`/`ProfanityUnlikely` rules trip on
`execute`, `kill`, `failed`, and `attack`, which read as technical
vocabulary in our context.
- **Coder** is in `BasedOnStyles` but the directory is empty in v1.
Rules land through the per-rule tickets in the [Docs style
guide](https://linear.app/codercom/project/docs-style-guide-7828445b9afc)
project.

### Why `mise exec` instead of a download block

Vale is pinned in `mise.toml` like `actionlint` and `zizmor`, so
invoking it via `mise exec "aqua:errata-ai/vale" -- vale ...` makes the
pin the single source of truth and lets mise handle the OS/arch-specific
download. This replaced an earlier ~30-line `curl`/`tar` block whose
GNU-only `grep -oP ...\K` version extraction returned empty on macOS BSD
grep. Note: the bare `vale` short name in `mise exec` ignores the pin
and resolves to the latest release, so the full aqua key is required.

### Why `vale sync` instead of vendoring

The three style packages weigh ~272 KB combined, so vendoring is cheap.
But Vale's ecosystem treats `Packages = ` + `vale sync` as canonical,
the upstream LICENSE files are not in the package tarballs (would need
to be added manually), and the CI cache makes the sync nearly free after
the first run. Sticking with the canonical pattern keeps the repo lean
and the upgrade path obvious.

### Why `lint/prose` is not in `lint:` or `lint-light:`

Vale on the full docs corpus takes ~20s on cold caches. Forcing every
pre-commit through that would be aggressive for a feature that ships as
warnings. `make lint/typos` follows the same pattern (it is in
`lint-light` but not `lint`; CI invokes it directly). v1 keeps Vale
opt-in locally and CI-only by default; promote to `lint:` once the rule
set stabilizes.

### Exit-code handling

Two mechanisms combine, and the choice changed when the step moved into
the required `lint-docs` job:

- `vale --no-exit` suppresses Vale's non-zero exit on alerts, so the
baseline error-level violations from un-overridden Google rules do not
fail the step while the cleanup PRs land. Real failures (config invalid,
file missing) still exit non-zero.
- `continue-on-error: true` on every Vale step. Because the steps now
run inside the *required* `lint-docs` job, a `vale sync`
download/network blip must not block merges. The original (standalone,
non-required) design rejected `continue-on-error` for showing a
misleading yellow badge; in a required job that tradeoff flips, and
advisory-yellow is strictly preferable to merge-blocking-red on an
infrastructure flake. `|| true` in the Makefile was also rejected: it
swallows missing-config failures indiscriminately.

### Pre-mortem

- **Generated docs noise**: `docs/reference/` is dominated by
auto-generated content (clidocgen, apidocgen, auditdocgen,
metricsdocgen). The architectural decision is to fix the generators, not
exclude paths in Vale. Google.Spacing is the only rule silenced
specifically to defer the generator fix; everything else surfaces as
warnings.
- **First-run cost**: `mise` installs the pinned Vale (a single small
binary) and `vale sync` pulls the style packages on a cold run. The
Actions cache keyed off `hashFiles('.vale.ini', 'mise.toml',
'docs/.style/styles/Coder/**')` makes subsequent runs near-instant; the
`Coder/**` hash is defense-in-depth against
[actions/toolkit#713](https://github.com/actions/toolkit/issues/713) so
a future cache release that regresses path-negation cannot serve a stale
`Coder/` from cache.
- **Required-job blast radius**: moving Vale into the required
`lint-docs` job means any Vale step failure would gate merges. Mitigated
by `continue-on-error` on all Vale steps plus a clean skip when no
changed `docs/` Markdown remains on disk, so only `pnpm check-docs`
stays blocking.
- **Cross-platform install**: handled by mise (aqua backend) rather than
a hand-rolled `uname`/arch map, which removes the macOS BSD-grep break
the review flagged.
- **Deleted files**: `all_changed_files` is ACMRD and lists paths a PR
removes; the prose step filters to files still present on disk so Vale
does not error on a missing file.
- **Local-vs-CI parity**: CI lints changed files only; local `make
lint/prose` lints the full tree. This mirrors `make lint/markdown` (full
tree) vs the changed-files CI step. Acceptable for v1.

</details>

---

*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
2026-06-22 14:14:31 -04:00
Edward AngertandEdwardAngert f1cca03ed3 docs: reorganize the About section (#18236)
As part of an information architecture overhaul, this PR reorganizes the
About section and adds a Support section (but not content to it yet)

[preview](https://coder.com/docs/@docs-ia-about/about)

this PR is intentionally limited in scope so that we can ship meaningful
changes faster and followup PRs should include:

- [ ] edit + overhaul the About page
- [ ] decide on the `start` directory
- [ ] ~screenshots page updates~ (this should happen July or later)

redirects PR: https://github.com/coder/coder.com/pull/944

---------

Co-authored-by: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com>
2025-06-12 13:56:45 -04:00