Skip to content

Commit 9db41d0

Browse files
authored
Merge pull request #350 from signalwire/docs-authoring-rules
docs: add documentation authoring rules
2 parents 304bec5 + 6b9f52e commit 9db41d0

3 files changed

Lines changed: 382 additions & 1 deletion

File tree

.claude/rules/docs-conventions.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
paths:
3+
- "fern/**/*.mdx"
4+
---
5+
6+
# Documentation Conventions
7+
8+
This rule covers **which docs are hand-authored vs auto-generated, and how to apply audit
9+
findings**. For *how* to write and structure a page (front matter, slugs, components, page
10+
types, MDX v3 rules, section landing pages, worked examples), see the **`documentation-style`**
11+
rule, which loads alongside this one.
12+
13+
## Doc Types — what you may edit
14+
15+
| Doc type | Location | Edit by hand? |
16+
|----------|----------|---------------|
17+
| **SWML reference** | `fern/products/swml/pages/reference/methods/` | ✅ Manually maintained MDX. |
18+
| **SDK reference** | `fern/products/server-sdks/pages/reference/{lang}/` | ✅ Manual MDX — but every edit must sync across language variants. See the **`sdk-reference-editing`** rule. |
19+
| **REST API reference** | generated from TypeSpec |**Never hand-edit.** Auto-generated by the build pipeline. Fix the spec instead — see the **`typespec-conventions`** rule. |
20+
| **Guides & tutorials** | `fern/products/*/pages/guides/` | ✅ Manual MDX. |
21+
22+
**REST API docs are auto-generated from TypeSpec specs.** Any manual change to a generated REST
23+
reference page is overwritten on the next build. To change REST docs, edit the `.tsp` spec
24+
(`@doc`/`@example` decorators and models) under `specs/` per the `typespec-conventions` rule.
25+
26+
## Product-specific structural conventions
27+
28+
The `documentation-style` rule defines the generic page patterns. A few product-specific additions:
29+
30+
- **SWML method pages** document output variables in a `## **Variables**` section when the method
31+
sets any, and show every example in **both YAML and JSON** inside `<CodeBlocks>`.
32+
- **SDK reference** mirrors the SDK's own naming/types per language — verify against source, don't
33+
assume language idioms (see `sdk-reference-editing`).
34+
35+
## Working with audit findings
36+
37+
When updating docs from an audit (spec/source comparison), apply only what the source supports:
38+
39+
- **Missing parameters** — add a `<ParamField>` for each undocumented param.
40+
- **Incorrect types/defaults** — update the existing `<ParamField>` `type`/`default`.
41+
- **Missing enum values** — add the valid values to the param description (or an anchored values table).
42+
- **Incorrect descriptions** — correct them to match actual behavior.
43+
- **New methods** — create a new page following the `documentation-style` rule, with at least one example.
44+
- **Do not** add params that aren't in the source, and **do not** remove params without explicit instruction.
45+
- When adding a new page, confirm it lands in the right folder so the nav picks it up (see `documentation-style` → file & folder structure).
46+
47+
## Related rules
48+
49+
- **`documentation-style`** — how to structure, style, and format any doc page (front matter, components, MDX v3, page types).
50+
- **`sdk-reference-editing`** — cross-variant sync for SDK reference pages.
51+
- **`typespec-conventions`** — editing the TypeSpec specs that generate REST API docs.
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
---
2+
paths:
3+
- "fern/**/*.mdx"
4+
---
5+
6+
# Documentation style (Fern / MDX v3)
7+
8+
How to author and edit `.mdx` pages so the docs site stays consistent.
9+
10+
This is the only guardrail — nothing lints these pages for you, so follow all of it. It covers
11+
page structure, page shape, and the MDX v3 syntax that breaks the build. Companion:
12+
`docs-conventions.md` (which docs are hand-authored vs generated — never hand-edit the generated
13+
REST reference).
14+
15+
## Structure: the folder tree is the nav
16+
17+
Fern builds the sidebar from the folder layout; each label comes from a page's front-matter
18+
`title`. You don't write nav config — you produce a clean tree that reads like the sidebar you
19+
want. `reference/` (exhaustive, one page per symbol) and `guides/` (task-oriented narrative)
20+
stay separate. One concept per file, `kebab-case.mdx`. Alphabetical order by default — don't set
21+
`position` except on `overview.mdx`.
22+
23+
**Section landing pages** — a folder is a sidebar section. Pick by section type:
24+
25+
| Section type | Landing file | Behavior | Use for |
26+
|---|---|---|---|
27+
| Top-level (under a tab) | `overview.mdx` (`position: 0`) | Non-clickable header; landing shows as "Overview" child | Main namespace / top-level area |
28+
| Nested topic | `index.mdx` | Clickable header → landing; detail pages nest beneath | A class, or a complex multi-page method/module |
29+
| Organizational only | *(none)* | Collapsible grouping, nothing to click | A grouping that just lists child pages |
30+
31+
All three cases in one tree (names illustrate a FreeSWITCH reference — adapt to your product):
32+
33+
```
34+
pages/
35+
├── reference/
36+
│ ├── applications/ # top-level section
37+
│ │ ├── overview.mdx # non-clickable label; "Overview" is its first child
38+
│ │ └── playback.mdx
39+
│ │
40+
│ ├── modules/ # top-level section
41+
│ │ ├── overview.mdx
42+
│ │ └── mod-dptools/ # nested topic — a module worth its own landing
43+
│ │ ├── index.mdx # clickable landing; child pages nest beneath it
44+
│ │ └── set.mdx
45+
│ │
46+
│ └── channel-variables/ # top-level section
47+
│ ├── overview.mdx
48+
│ └── core/ # organizational — only groups variables
49+
│ └── caller-id-name.mdx # no landing file; collapsible label only
50+
51+
└── guides/
52+
└── get-started/ # top-level guide section
53+
├── overview.mdx
54+
└── quickstart.mdx
55+
```
56+
57+
**Slug ≠ folder path.** Folder drives nav; `slug` drives the URL. Keep them independent so you
58+
can reorganize sections without breaking URLs. Slugs are lowercase-kebab, 2–3 segments, drop
59+
filing buckets (`methods/`, `basics/`, `recipes/`) but keep stable domains (`calling`). No
60+
product prefix, no leading/trailing slash, no extension. A reference slug *may* mirror a
61+
meaningful hierarchy (`/reference/python/agents/agent-base/on-debug-event`) — mirror meaning,
62+
never just folders.
63+
64+
## Front matter
65+
66+
Four fields **required on every page**:
67+
68+
```yaml
69+
---
70+
title: "originate" # H1 + sidebar label; exact symbol name for reference pages
71+
slug: /reference/api-commands/originate # URL; see slug rules above
72+
description: One sentence, for search/SEO.
73+
max-toc-depth: 3 # always exactly 3
74+
---
75+
```
76+
77+
> **The H1 is the `title`, not body Markdown.** Every page has exactly one H1, rendered from the
78+
> `title` field. Never write a `#` heading in the body — it produces a duplicate, wrongly-styled
79+
> heading. The body opens with the description paragraph; all body headings are `##` or deeper.
80+
81+
Optional when they apply: `subtitle`, `sidebar-title` (e.g. `Overview`), `position` (`0` on
82+
overviews only), `hidden`. Never fabricate an `id`/UUID.
83+
84+
## Page types
85+
86+
Classify every page as one of four types and follow its skeleton. Two conventions apply to all of
87+
them: body headings are `##` or deeper (never a body `# H1`), in **sentence case** ("Next steps",
88+
not "Next Steps"); and the reference section names `## **Parameters**`, `## **Properties**`,
89+
`## **Returns**`, `## **Examples**` are bolded and used verbatim.
90+
91+
### Reference
92+
93+
One page per symbol — an application, command, function, module, or channel variable. Terse and
94+
lookup-oriented (~50–80 lines). Sections in order, dropping any that don't apply: description
95+
paragraph → optional `<Note>` / `<Warning>` → interface shape → `## **Returns**`
96+
`## **Examples**`.
97+
98+
````mdx
99+
---
100+
title: "originate"
101+
slug: /reference/api-commands/originate
102+
description: Originate a new call and bridge it to a dialplan target.
103+
max-toc-depth: 3
104+
---
105+
106+
Originate a new call from FreeSWITCH and bridge it to a dialplan extension or application.
107+
108+
<Note>
109+
`originate` runs synchronously on the API/CLI unless you prefix the dial string with
110+
inline `{ }` channel-variable options.
111+
</Note>
112+
113+
## **Parameters**
114+
115+
<ParamField path="dial_string" type="string" required={true} toc={true}>
116+
The endpoint to call, e.g. `sofia/gateway/my_gateway/15551234567`.
117+
</ParamField>
118+
119+
<ParamField path="options" type="object" toc={true}>
120+
Channel variables passed inline in `{ }` before the dial string.
121+
</ParamField>
122+
123+
<Indent>
124+
125+
<ParamField path="options.call_timeout" type="integer" default="60" toc={true}>
126+
Seconds to wait for an answer before giving up.
127+
</ParamField>
128+
129+
</Indent>
130+
131+
## **Returns**
132+
133+
`+OK <uuid>` on success; `-ERR <reason>` on failure.
134+
135+
## **Example**
136+
137+
```bash
138+
originate sofia/gateway/my_gateway/15551234567 &playback(/tmp/welcome.wav)
139+
```
140+
````
141+
142+
That `## **Parameters**` block is the workhorse of every reference page — one `<ParamField>` per
143+
element in the order they appear, objects nested with `<Indent>`, followed by a native-syntax example.
144+
Only the heading and `path` form change with the interface; the pattern stays the same:
145+
146+
| Interface | Heading | `path` form |
147+
|---|---|---|
148+
| Data object (JSON/YAML) | `## **Properties**` | dot-notation (`answer.max_duration`) |
149+
| Function / method | `## **Parameters**` | bare arg name (`timeout`) |
150+
| XML element | `## Attributes` | bare attribute (`data`) |
151+
| Callback / event | `## **Parameters**` | arg name; dot-notation for payload |
152+
153+
*Data object (SWML JSON/YAML) — nested keys via dot-notation + `<Indent>`:*
154+
155+
```mdx
156+
## **Properties**
157+
158+
<ParamField path="answer" type="object" toc={true}>
159+
An object accepting the following properties.
160+
</ParamField>
161+
162+
<Indent>
163+
164+
<ParamField path="answer.max_duration" type="integer" default="14400" toc={true}>
165+
Maximum call duration in seconds.
166+
</ParamField>
167+
168+
</Indent>
169+
```
170+
171+
*Function / method — write `type` in the language's own terms (here Python):*
172+
173+
```mdx
174+
## **Parameters**
175+
176+
<ParamField path="hint" type="str" required={true} toc={true}>
177+
A word or phrase to boost recognition accuracy.
178+
</ParamField>
179+
```
180+
181+
*XML element — one field per attribute, bare attribute name as `path`:*
182+
183+
```mdx
184+
## Attributes
185+
186+
<ParamField path="application" type="string" required={true} toc={true}>
187+
The dialplan application to execute, e.g. `playback`, `bridge`, `set`.
188+
</ParamField>
189+
```
190+
191+
*Callback / event handler — document payload fields inline under the handler:*
192+
193+
```mdx
194+
## **Parameters**
195+
196+
<ParamField path="handler" type="Callable[[str, dict], None]" required={true} toc={true}>
197+
Invoked on each event with signature `(event_type, data)`.
198+
199+
- `event_type` — the event label string
200+
- `data` — full payload, including `call_id` and event-specific fields
201+
</ParamField>
202+
```
203+
204+
In `<ParamField>`: add `required={true}` only to required params (never `required={false}`),
205+
`default` only when the param has one, and `toc={true}` on reference params.
206+
207+
### Overview and index pages
208+
209+
A section's landing page: intro → optional concept `##` sections → a `<CardGroup>` linking the
210+
child pages. Use `overview.mdx` for a top-level section, `index.mdx` for a nested topic (per the
211+
landing-page table above).
212+
213+
```mdx
214+
---
215+
title: "API Commands"
216+
sidebar-title: Overview
217+
slug: /reference/api-commands
218+
description: Reference for FreeSWITCH API commands available via fs_cli, ESL, and mod_xml_rpc.
219+
max-toc-depth: 3
220+
position: 0
221+
---
222+
223+
API commands query and control a running FreeSWITCH instance — originating calls,
224+
inspecting channels, reloading config — without restarting.
225+
226+
<CardGroup cols={3}>
227+
<Card title="originate" href="/reference/api-commands/originate">
228+
Originate a new outbound call.
229+
</Card>
230+
<Card title="reloadxml" href="/reference/api-commands/reloadxml">
231+
Reload the XML configuration.
232+
</Card>
233+
</CardGroup>
234+
```
235+
236+
### Guide
237+
238+
Task-oriented and narrative: intro → optional `<Info>` / `<Tip>` → body (prose + `<CodeBlocks>`,
239+
with `<Steps>` for ordered procedures) → `## Next steps` (bullet links or `<Cards>`).
240+
241+
```mdx
242+
---
243+
title: Route inbound calls with the XML dialplan
244+
subtitle: Match inbound calls and send them to dialplan applications.
245+
slug: /guides/inbound-routing
246+
description: Route inbound calls to dialplan applications using FreeSWITCH XML dialplan conditions.
247+
max-toc-depth: 3
248+
---
249+
250+
This guide shows how to match inbound calls and route them to dialplan applications.
251+
252+
<Info>
253+
For every application you can call from a condition, see the
254+
[Applications reference](/reference/applications).
255+
</Info>
256+
257+
## Add a dialplan extension
258+
259+
<Steps>
260+
261+
### Create the extension
262+
263+
Add an `<extension>` block to `conf/dialplan/default.xml`.
264+
265+
### Match the destination number
266+
267+
Use a `<condition>` on `destination_number` with a regular expression.
268+
269+
</Steps>
270+
271+
## Next steps
272+
273+
- **[Applications reference](/reference/applications)** — every application you can call.
274+
- **[Channel variables](/reference/channel-variables)** — variables you can read and set.
275+
```
276+
277+
### Landing page
278+
279+
The single top page for the whole doc set — like an overview but broader: intro, a few concept
280+
sections, and a `<Cards>` grid pointing at the major areas.
281+
282+
For overview, guide, and landing pages, **match an existing repo page of the same type** rather
283+
than inventing a layout.
284+
285+
## Components — use only these
286+
287+
Full examples in `component-catalog.md`. At a glance:
288+
289+
| Component | For |
290+
|---|---|
291+
| `ParamField` + `Indent` | One interface element; nest sub-fields |
292+
| `CodeBlocks` / `CodeBlock` | Same example in multiple formats/langs (single example → plain fenced block) |
293+
| `Tabs` / `Tab` | Switchable non-code variants |
294+
| `Note` / `Tip` / `Warning` / `Info` | Prerequisite / suggestion / hazard / pointer |
295+
| `Accordion` / `AccordionGroup` | Hide long/optional content |
296+
| `Card` / `CardGroup` / `Cards` | Link grids (overviews, "Next steps") |
297+
| `Steps` / `Step` | Ordered procedure in a guide |
298+
| `Frame` | Image/diagram with caption |
299+
| `Markdown` | Shared snippet include (only real partials) |
300+
| `Button` / `Tooltip` / `Icon` | CTA / inline definition / inline icon |
301+
302+
## Strict MDX v3 rules (these break the build)
303+
304+
The site compiles MDX 3 with Acorn JSX parsing — these are hard errors, not style preferences:
305+
306+
- **Close every tag**: `<br />`, `<img src="x.webp" alt="y" />`; self-close standalone
307+
components (`<Markdown src="..." />`).
308+
- **Comments are JSX expressions**: `{/* ... */}`, never `<!-- ... -->`.
309+
- **Attributes**: quote strings (`title="YAML"`); brace booleans/numbers/expressions
310+
(`required={true}`, `cols={3}`, `toc={true}`).
311+
- **No bare `<` or `{` in prose** — backtick it inline or put it in a fenced block. This is the
312+
single most common failure (describing C syntax, templates, comparisons).
313+
- **Blank lines** around block components and inside `<Indent>` / `<Steps>` / `<Accordion>`, or
314+
the inner Markdown won't parse.
315+
- **Every code fence has a language** (` ```bash `, ` ```xml `, ` ```python `).
316+
- **No body `# H1`**; headings are `##` or deeper.
317+
318+
## Before you emit
319+
320+
The MDX rules above break the build if missed; the build won't catch these, so verify them by hand:
321+
322+
- All four front-matter fields present; `slug` lowercase-kebab, decoupled from the folder path
323+
(drops filing buckets); no fabricated `id`.
324+
- Page type identified; sections match its skeleton order; body opens with the description
325+
paragraph; no placeholder sections.
326+
- Correct landing filename (`overview.mdx` top-level / `index.mdx` nested topic / none for
327+
organizational).
328+
- Headings are sentence case; house section names used verbatim.
329+
- Only catalog components used; every internal link/anchor resolves to a real slug/heading.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ node_modules
44
dist
55
.link-check
66
.serena
7-
.claude
7+
.claude/*
8+
!.claude/rules/
89
.vscode
910
.env
1011
.env.*

0 commit comments

Comments
 (0)