|
| 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. |
0 commit comments