Skip to content
Open

Vale #14

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
73 changes: 73 additions & 0 deletions .github/workflows/vale-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Vale documentation lint

on:
Copy link
Copy Markdown
Contributor

@lucas-tortora lucas-tortora Jun 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not run this on PRs?

Suggested change
on:
on:
pull_request:
paths: ['**/*.md', '**/*.mdx']

workflow_call:
inputs:
filepaths:
description: "Comma- or newline-separated Markdown/MDX globs to lint."
required: false
type: string
default: |
**/*.md
**/*.mdx
fail_on_error:
description: "Set to true to fail CI when Vale reports errors."
required: false
type: boolean
default: false
docs_template_ref:
description: "docs-template ref to use as the Vale source of truth."
required: false
type: string
default: main

jobs:
vale:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: Checkout caller repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false

- name: Checkout central Vale configuration
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: tetherto/docs-template
ref: ${{ inputs.docs_template_ref }}
path: .tether-vale-source
persist-credentials: false

- name: Detect changed documentation files
id: changed-files
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
with:
path: "."
files: ${{ inputs.filepaths }}
separator: ","

- name: List changed documentation files
if: steps.changed-files.outputs.any_changed == 'true'
run: |
echo "The following files were detected for Vale:"
echo "${{ steps.changed-files.outputs.all_changed_files }}"

- name: Set up Python for Vale
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v6
with:
python-version: "3.12"

- name: Vale
if: steps.changed-files.outputs.any_changed == 'true'
uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # v2.1.1
with:
files: ${{ steps.changed-files.outputs.all_changed_files }}
vale_flags: "--config .tether-vale-source/.vale.ini"
fail_on_error: ${{ inputs.fail_on_error }}
reporter: github-pr-check
separator: ","
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
.contentlayer
.content-collections
.source
/styles/Google/
/styles/proselint/
/styles/write-good/
/content/docs/api-reference.mdx
/content/docs/qvac/examples.mdx

Expand Down
114 changes: 114 additions & 0 deletions .vale.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Vale configuration
# See vale_styles/README.md file for details and licensing information
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this README be part of the PR?


# Styles directory for all packages and config (vocab) files

StylesPath = ./styles

# The ignore config references project-words.txt

Spelling.Ignore = config/ignore/Tether-common


# Packages to sync with. Remember never adapt the external packages, all changes will be lost on update

Packages = Google, proselint, write-good

Vocab = Tether-common

# The minimum alert level to display (suggestion, warning, or error).
# Builds not set to fail too many false positives
MinAlertLevel = warning

# Treat .mdx files as Markdown so Vale can apply Markdown rules and styles.
# This does NOT automatically make Vale lint .mdx files; it only defines
# how .mdx files are parsed once they are matched by a glob below.

[formats]
mdx = md

# Apply Markdown linting rules to both .md and .mdx files.
# Note: Vale only runs rules on files that match a section glob.
# Including .mdx here is required; the [formats] mapping alone is not sufficient.
# Global settings (applied to every syntax)

[*.{md,mdx}]

# ignore includes and latex math code
TokenIgnores = ({![^!}]+!}),\
(\$[^\n$`]+\$),\
(\$\$[^$`]+\$\$),\
(\$\{\{[^}]+\}\}),\
({%[^}]+%}),\
({{[^}]+}}),\
(\+\+[A-Za-z]+\+[A-Za-z0-9]+\+\+),\
(:[a-z\-]+:),\
(^\s*import\s.+\s+from\s+.+$),\
(^\s*export\s.+$),\
(^\s*<img\b.*$),\
(^\s*style=\{\{.*\}\}.*$),\
(^\s*src=\{.*\}.*$),\
(^\s*\/>\s*$),\
(\bPolyfill\b),\
(\[!(?:NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]),\
([Pp]ower meters)

# Ignore MDX import/export lines (React code).
BlockIgnores = (?m)^\s*import\s+[^\n]*$,\
(?m)^\s*export\s+[^\n]*$

# List of styles to load

BasedOnStyles = Tether, Vale, Google, proselint, write-good

# Allows Tether Headings to take precedence
Google.Headings = NO

# Allows using first-person plural like 'We'
Google.We = NO
Google.FirstPerson = NO

# Acronyms are replaced by the Tether list
Google.Acronyms = NO

# General terminology preferences are replaced by custom terms
Google.WordList = NO

# Quiet quotes: too many false positives
Google.Quotes = NO

# Quiet contractions: too many positives
Google.Contractions = NO

# Quiet dashes: spaces around em dashes are acceptable
Google.EmDash = NO

# Quiet foreign phrases: 'e.g.'/'i.e.' are acceptable in technical docs
Google.Latin = NO

# Quiet ellipses: literal '...' is acceptable in technical docs
Google.Ellipses = NO

# Allows proselint.Cliches to take precedence
write-good.Cliches = NO

# Quiet weasel: is replaced by Tether list
write-good.Weasel = NO

# Quiet E-Prime: too many false positives
write-good.E-Prime = NO

# Allows Vale.Repetition to take precedence
write-good.Illusions = NO

# Quiet too wordy: too many false positives
write-good.TooWordy = NO

# Allows write-good passive voice to take precedence
Google.Passive = NO
write-good.Passive = NO

proselint.Hyperbole = warning

# Quiet typography: literal '...' and straight quotes are acceptable
proselint.Typography = NO
30 changes: 12 additions & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Maintainers decide the final merge strategy. Prefer small, focused pull requests
- Use clear MDX headings, stable links, and concise examples
- Keep SEO metadata current when adding or moving docs pages
- Avoid adding new abstractions unless they reduce real duplication or match an existing local pattern
- You may follow this opinionated style guide
- You MAY follow this [opinionated style guide](#opinionated-style-guide)

## Issues and security

Expand All @@ -239,25 +239,20 @@ Happy contributing, and thanks for helping improve `docs-template`.

### Overview

Google developer style
US English
Bullet lists no stop (- Avalon not - Avalon.)
Numbered lists stop
Diataxis ia
No positional references ("Swap the filename for any other model from the table” NOT "Swap the filename for any other model from the table above”)

### Frontmatter and linking strategy

Links are from relevant text NOT "see ..." (do "The [Worker install pattern][install-pattern] defines the per-Worker mechanics." NOT "See the Worker [install pattern][install-pattern] for the per-Worker mechanics.")

Ask maintainer if the page you are building is to be ported to `tether.io`, if so follow reference-style link definitions plus routing comments:

/mdk-prv/docs/reference/maintainers/port-signals.md
Comment on lines -249 to -255
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove this?

- [Google developer style](https://developers.google.com/style) | [Vale](./README.md#vale-linting) lints per this guide
- US English
- Bullet lists no stop (- Avalon not - Avalon.)
- Numbered lists stop
- Diataxis ia
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Diataxis ia
- Diátaxis ia

- No positional references ("Swap the filename for any other model from the table” NOT "Swap the filename for any other model from the table above”)
- Links from relevent text NOT "see ..." (do "The [Worker install pattern][install-pattern] defines the per-Worker mechanics." NOT "See the Worker [install pattern][install-pattern] for the per-Worker mechanics.")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Links from relevent text NOT "see ..." (do "The [Worker install pattern][install-pattern] defines the per-Worker mechanics." NOT "See the Worker [install pattern][install-pattern] for the per-Worker mechanics.")
- Links from relevant text NOT "see ..." (do "The [Worker install pattern][install-pattern] defines the per-Worker mechanics." NOT "See the Worker [install pattern][install-pattern] for the per-Worker mechanics.")

- Restrict line length to ~150 chars

### Fixed sections, in order

1. `## Overview` — one paragraph or `## How it works`+ "This page ...
2. `## Next steps` — bullet list, each item `Description — [link](path)`
1. `## Overview` or `## How it works`— one paragraph or sentence "This page ..." clarifying page's purpose
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. `## Overview` or `## How it works`— one paragraph or sentence "This page ..." clarifying page's purpose
1. `## Overview` or `## How it works` — one paragraph or sentence "This page ..." clarifying page's purpose

2. Body content
3. Help the user discover more `## Next steps` — bullet list, each item `Description — [link](path)`

### Admonitions

Expand Down Expand Up @@ -293,7 +288,6 @@ This is a **success** callout — use for success messages.
This is an **idea** callout — use for tips or suggestions.
</Callout>


### Code blocks

- Always fenced with language tag (`bash`, `js`, etc.) except terminal session output which uses plain ` ``` `
Expand Down
84 changes: 79 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DOCS docs
# Tether.to documentation template site

[This site](https://github.com/tetherto/docs-template.git) is the official documentation and single source of truth for the `tether.io` Documentation guild:

Expand All @@ -7,6 +7,20 @@

The site is a **static export** from a Next.js + [Fumadocs](https://fumadocs.dev) app (`output: 'export'`). SEO behavior is implemented with workspace packages under `@tether/docs-*` (see below).

## Table of contents

- [Installation](#installation)
- [Monorepo packages (`packages/`)](#monorepo-packages-packages)
- [Using these packages from another repository](#using-these-packages-from-another-repository)
- [SEO and frontmatter](#seo-and-frontmatter)
- [Environment variables](#environment-variables)
- [Open Graph images (Takumi, static hosting)](#open-graph-images-takumi-static-hosting)
- [Development](#development)
- [Vale linting](#vale-linting)
- [Maintainers](#maintainers)
- [Build](#build)
- [Repository layout](#repository-layout)

## Installation

Prerequisites:
Expand Down Expand Up @@ -76,7 +90,7 @@ Important: **set an expiration on the token.** Tokens that never expire are reje

After creating the token, if the `tetherto` organization uses SSO, click **"Configure SSO"** next to the token in the tokens list and authorize it for the org. Without that step the registry returns `403` even with the right scopes.

A fine-grained personal access token also works, scoped to the `tetherto` resource owner with **Packages: Read** repository permission. Same expiration requirement applies.
A fine-grained personal access token also works, scoped to the `tetherto` resource owner with **Packages read** repository permission. Same expiration requirement applies.

#### 3. Save the token locally

Expand All @@ -92,7 +106,7 @@ npm whoami --registry=https://npm.pkg.github.com # should print your GitHub us
npm view @tetherto/docs-seo-schema version --registry=https://npm.pkg.github.com
```

If `npm whoami` prints your username but the second command 403s, the token authenticates but lacks `read:packages` (and/or SSO authorization).
If `npm whoami` prints your username but the second command returns HTTP 403, the token authenticates but lacks `read:packages` (and/or SSO authorization).

#### 5. Install

Expand Down Expand Up @@ -121,7 +135,7 @@ Extended fields are merged in [`source.config.ts`](source.config.ts) via `tether

Per-page metadata, sitemap, robots, and JSON-LD share the same logic through [`src/lib/seo-config.ts`](src/lib/seo-config.ts) and `@tether/docs-seo-next`.

During `next build` / dev, `getPageSeoState` and `buildDocsMetadata` emit **`[@tether/docs-seo]`** `console.warn` lines for missing optional fields (`ogImage`, `schemaType`, `docType`, `lastModified`, and empty `description` if it bypasses MDX validation). Warnings are deduped per page per Node process. Two env knobs control them:
During `next build` / dev, `getPageSeoState` and `buildDocsMetadata` emit **`[@tether/docs-seo]`** `console.warn` lines for missing optional fields (`ogImage`, `schemaType`, `docType`, `lastModified`, and empty `description` if it bypasses MDX validation). Warnings are deduplicated per page per Node process. Two env knobs control them:

- **`DOCS_SEO_SILENT=1`** — silence ALL warnings (including the required-`description` warning). Use sparingly.
- **`DOCS_SEO_QUIET_GENERATED=1`** — silence only the warnings for fields that have sensible auto-generated/inferred defaults (`ogImage`, `schemaType`, `lastModified`). `description` and `docType` warnings stay loud because neither has a useful default. Recommended when you opt into the Takumi OG prebuild + the `fumadocs-mdx` `lastModified` plugin.
Expand Down Expand Up @@ -162,10 +176,39 @@ Because static export cannot use dynamic OG Route Handlers, images are **generat
- Run the generator alone: **`npm run build:og`**
- Replace [`public/og-default.png`](public/og-default.png) with a proper **1200×630** asset if you rely on the `SKIP_OG_BUILD` fallback

**Git:** This template **gitignores** `public/og/docs/` (see [`.gitignore`](.gitignore)). CI and local **`npm run build`** must run **`prebuild`** so those WebP files exist before static export. To vendor generated images instead, stop ignoring that directory and commit the files.
**Git note:** this template **gitignores** `public/og/docs/` (see [`.gitignore`](.gitignore)). CI and local **`npm run build`** must run **`prebuild`** so those WebP files exist before static export. To vendor generated images instead, stop ignoring that directory and commit the files.

## Development

### Vale linting

Vale is available for local documentation linting. Run `vale sync` before linting so Vale downloads the configured package styles into the gitignored `styles/` package directories.

Use the project vocabulary to check custom spelling. For example, Tether should pass, while Tehtr should fail.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a short install vale snippet may be helpful here.

Run Vale against a single file:

```bash
vale sync
vale README.md
```

Run Vale against a single folder:

```bash
vale sync
vale content/docs
```

Run Vale against the entire repo:

```bash
vale sync
vale .
```

Vale can be added to CI as an advisory check, but do not configure it to fail CI. The current rule set has too many false positives for a hard gate.

Check broken links:

```bash
Expand All @@ -184,6 +227,37 @@ For local dev without generating OG files, you can use:
SKIP_OG_BUILD=1 npm run dev
```

## Maintainers

### Using Vale in downstream CI

This repository is the source of truth for Tether Vale configuration. Downstream documentation repositories should not copy `.vale.ini` or `styles/`; they can call this repository's reusable workflow instead.

Add a workflow like this to the downstream repository:

```yaml
name: Vale documentation lint

on:
pull_request:
paths:
- "**/*.md"
- "**/*.mdx"

jobs:
vale:
uses: tetherto/docs-template/.github/workflows/vale-docs.yml@main
with:
filepaths: |
**/*.md
**/*.mdx
fail_on_error: false
```

The reusable workflow checks out the downstream repository, checks out this template beside it, detects changed Markdown and MDX files, and runs Vale with this repository's [`.vale.ini`](.vale.ini) and [`styles/`](styles/) configuration. `fail_on_error` defaults to `false` because Vale has too many false positives for a hard CI gate; set it to `true` only after the downstream repository has cleaned up or accepted the rule set.

Synced package styles such as Google, `proselint`, and `write-good` are generated by `vale sync` during the workflow run. Do not commit those generated package directories to downstream repositories.

## Build

Set **`NEXT_PUBLIC_DOCS_ORIGIN`** for production; add **`NEXT_PUBLIC_INKEEP_API_KEY`** only if you use Inkeep instead of default Fumadocs search (see [`env.example`](env.example)).
Expand Down
Loading