@developerhub/dh-skills 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeveloperHub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @developerhub/dh-skills
2
+
3
+ Agent Skills that teach AI coding agents how to author
4
+ **[DeveloperHub](https://developerhub.io) Markdoc** — the exact, round-trip-safe
5
+ syntax for every content block.
6
+
7
+ DeveloperHub pages are stored as Markdoc. When an agent edits a page from an IDE,
8
+ a Git-synced repo, or a docs-as-code pipeline, it needs to emit the *canonical*
9
+ dialect DeveloperHub round-trips — not generic Markdown or a look-alike. Emit the
10
+ wrong shape and the edit churns on the first save or silently drops content. This
11
+ skill gives the agent that ground truth.
12
+
13
+ ## What's inside
14
+
15
+ | Skill | Teaches |
16
+ |---|---|
17
+ | [`write-markdoc`](skills/write-markdoc/SKILL.md) | Every DeveloperHub Markdoc block and inline mark, with the exact syntax, attributes, and the dialect rules that keep an edit round-trip-safe. |
18
+
19
+ Detailed per-block reference: [`skills/write-markdoc/references/blocks.md`](skills/write-markdoc/references/blocks.md).
20
+
21
+ ## Install
22
+
23
+ Install straight from this repo with the [`skills` CLI](https://www.npmjs.com/package/skills):
24
+
25
+ ```bash
26
+ npx skills add developerhub-io/dh-skills
27
+ ```
28
+
29
+ Or copy the `skills/write-markdoc` folder into your agent's skills directory
30
+ (`.claude/skills/`, `.cursor/skills/`, …). `SKILL.md` uses the portable Agent
31
+ Skills format (Markdown with YAML frontmatter), so it works across Claude Code,
32
+ Cursor, Codex, and other compatible agents.
33
+
34
+ It is also published to npm as `@developerhub/dh-skills`, for anyone who prefers to
35
+ vendor the files as a dependency (`npm install @developerhub/dh-skills`).
36
+
37
+ ## Contributing
38
+
39
+ This package is published and maintained by DeveloperHub. Spotted an error, or
40
+ want a block documented differently? Please [open an issue](https://github.com/developerhub-io/dh-skills/issues).
41
+
42
+ ## License
43
+
44
+ MIT — see [LICENSE](LICENSE).
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@developerhub/dh-skills",
3
+ "version": "1.0.2",
4
+ "description": "Agent Skills that teach AI coding agents to author DeveloperHub Markdoc — the exact syntax for every content block.",
5
+ "keywords": [
6
+ "developerhub",
7
+ "markdoc",
8
+ "documentation",
9
+ "agent-skills",
10
+ "claude",
11
+ "ai",
12
+ "docs-as-code"
13
+ ],
14
+ "homepage": "https://developerhub.io",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/developerhub-io/dh-skills.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/developerhub-io/dh-skills/issues"
21
+ },
22
+ "license": "MIT",
23
+ "author": "DeveloperHub <hello@developerhub.io> (https://developerhub.io)",
24
+ "files": [
25
+ "skills",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }
@@ -0,0 +1,270 @@
1
+ ---
2
+ name: write-markdoc
3
+ metadata:
4
+ version: "1.0"
5
+ description: >-
6
+ Write, author, edit, and format DeveloperHub documentation pages in the correct
7
+ Markdoc syntax. Use whenever a task involves creating or editing a DeveloperHub
8
+ docs page; writing page frontmatter; inserting or formatting a callout, code
9
+ block, table, image, tabs, accordion, cards, video, badge, icon, or any other
10
+ Markdoc block or inline tag; or when you need the exact syntax, attributes,
11
+ defaults, or allowed values for a DeveloperHub Markdoc tag.
12
+ ---
13
+
14
+ # Writing DeveloperHub Markdoc
15
+
16
+ DeveloperHub pages are authored in **Markdoc** — a superset of Markdown with a set
17
+ of typed custom tags. The same format is used when editing a page through the
18
+ editor, an API/AI edit, or a Git-synced repository: what you write is parsed into
19
+ the editor's document model, and what the editor produces is serialised back to
20
+ this exact syntax. Getting the shape right keeps that round-trip **lossless** —
21
+ emit a non-canonical shape and the edit churns on the first save or silently drops
22
+ content.
23
+
24
+ This skill is the ground truth for the syntax. For the full per-tag reference
25
+ (every attribute, default, and allowed value), read
26
+ [`references/blocks.md`](references/blocks.md).
27
+
28
+ ## Core dialect rules
29
+
30
+ These hold for every tag. Break one and the round-trip breaks.
31
+
32
+ 1. **Block tags sit on their own lines.** The open tag, the body, and the close
33
+ tag are each on separate lines. Content on the same line as `{% tag %}`
34
+ silently downgrades the tag to a plain paragraph.
35
+ ```markdoc
36
+ {% callout title="Note" %}
37
+ Body goes here.
38
+ {% /callout %}
39
+ ```
40
+ 2. **Inline tags are self-closing:** `{% icon classes="fas fa-star" /%}`.
41
+ 3. **Booleans are bare, never quoted.** `open=true`, `showLineNumbers=false`. A
42
+ quoted boolean (`open="false"`) becomes the truthy *string* `"false"` and
43
+ silently inverts the setting.
44
+ 4. **Numbers are bare too:** `width=357`, `colspan=2`. Strings are quoted:
45
+ `title="Warning"`.
46
+ 5. **Omit any attribute that equals its default.** The serialiser drops them, so
47
+ adding them back only makes diffs noisier. The defaults are listed per tag in
48
+ `references/blocks.md`. It is never *wrong* to include one; just match the
49
+ surrounding page.
50
+ 6. **Internal links carry real text:** `[Getting started](/support-center/start)`.
51
+ A text-less link round-trips to nothing.
52
+ 7. **Asset references stay verbatim.** An image `url="asset:zvlxyph4f37t"` is kept
53
+ as-is; never resolve it to a URL. (Full `https://uploads.developerhub.io/…`
54
+ URLs are also valid — use whichever the surrounding pages use.)
55
+ 8. **Reserve `#` (H1) for the page title.** Body headings start at `##`.
56
+
57
+ ## Standard Markdown
58
+
59
+ Plain Markdown works and is preferred wherever a custom tag is not needed:
60
+
61
+ - Headings `##` … `######`
62
+ - `**bold**`, `*italic*`, `~~strikethrough~~`, `` `inline code` ``
63
+ - `[links](/support-center/slug)` — root-relative for internal links
64
+ - Bullet (`-`) and ordered (`1.`) lists, including nested
65
+ - `> blockquote`
66
+ - `---` horizontal rule
67
+ - Fenced code blocks (see the reference)
68
+
69
+ Two things have no Markdown equivalent, so they are tags:
70
+
71
+ - **Underline:** `{% u %}underlined{% /u %}`
72
+ - **A link whose URL contains spaces or parentheses:**
73
+ `{% link href="https://x.com/a (b)" %}text{% /link %}`
74
+
75
+ ## The blocks at a glance
76
+
77
+ | Tag | Form | Purpose |
78
+ |---|---|---|
79
+ | `callout` | block | Coloured note / warning box |
80
+ | `code` + fences | block | Multi-language code tabs |
81
+ | ` ```lang ` fence | fence | A single code block |
82
+ | `code-steps` | block | Stepped code walkthrough |
83
+ | `table` / `row` / `cell` | block | Rich table (spans, alignment, backgrounds) |
84
+ | `image` | block | Image, with optional caption |
85
+ | `video` | block | Embedded / raw video |
86
+ | `tabs` / `tab` | block | Tabbed content |
87
+ | `accordion-group` / `accordion` | block | Collapsible sections |
88
+ | `cards` / `card` | block | Card grid or list |
89
+ | `conditional` | block | Audience-gated content |
90
+ | `html` | block | Raw custom HTML |
91
+ | `github-code` | block | Code pulled live from a GitHub URL |
92
+ | `index-list` | block | Auto index of child pages |
93
+ | `synced` | block | Reusable synced block, by id |
94
+ | `badge` | inline | Small coloured label |
95
+ | `key` | inline | Keyboard key |
96
+ | `icon` | inline | Font Awesome icon |
97
+ | `glossary` | inline | Glossary term |
98
+ | `inline-image` | inline | Inline image |
99
+ | `%KEY%` | inline | Variable placeholder |
100
+
101
+ ## Cheat sheet
102
+
103
+ One of (almost) every block. Full attribute lists in
104
+ [`references/blocks.md`](references/blocks.md).
105
+
106
+ ````markdoc
107
+ ## A heading
108
+
109
+ A paragraph with **bold**, *italic*, and a [link](/support-center/blocks).
110
+
111
+ {% callout type="warning" title="Heads up" %}
112
+ A coloured note box. `type` is one of info, warning, success, error.
113
+ {% /callout %}
114
+
115
+ {% code %}
116
+ ```php {% title="PHP" %}
117
+ echo 'first tab';
118
+ ```
119
+
120
+ ```javascript {% title="JS" %}
121
+ console.log('second tab');
122
+ ```
123
+ {% /code %}
124
+
125
+ ```js {% showLineNumbers=true title="Example" %}
126
+ const ready = true;
127
+ ```
128
+
129
+ {% table layout="auto" %}
130
+ {% row %}
131
+ {% cell header=true %}
132
+ Header
133
+ {% /cell %}
134
+ {% cell header=true %}
135
+ Header
136
+ {% /cell %}
137
+ {% /row %}
138
+ {% row %}
139
+ {% cell %}
140
+ Cell
141
+ {% /cell %}
142
+ {% cell %}
143
+ Cell
144
+ {% /cell %}
145
+ {% /row %}
146
+ {% /table %}
147
+
148
+ {% image url="asset:zvlxyph4f37t" width=357 %}
149
+ Optional caption.
150
+ {% /image %}
151
+
152
+ {% video provider="raw" videoId="https://uploads.developerhub.io/example.mp4" /%}
153
+
154
+ {% tabs %}
155
+ {% tab title="Android" %}
156
+ Android content.
157
+ {% /tab %}
158
+ {% tab title="iOS" %}
159
+ iOS content.
160
+ {% /tab %}
161
+ {% /tabs %}
162
+
163
+ {% accordion-group %}
164
+ {% accordion title="Getting started" open=true %}
165
+ Collapsible content. Add `open=true` to expand it on load.
166
+ {% /accordion %}
167
+ {% accordion title="Advanced" %}
168
+ Second section.
169
+ {% /accordion %}
170
+ {% /accordion-group %}
171
+
172
+ {% cards %}
173
+ {% card title="Card 1" text="Description" link="/support-center/videos" /%}
174
+ {% card title="Card 2" text="Description" link="/support-center/callouts" /%}
175
+ {% /cards %}
176
+
177
+ {% conditional audience="enterprise" %}
178
+ Shown only to the enterprise audience.
179
+ {% /conditional %}
180
+
181
+ {% github-code url="https://github.com/acme/repo/blob/main/Makefile" /%}
182
+
183
+ {% index-list /%}
184
+
185
+ {% synced id="open-block-menu" /%}
186
+
187
+ {% html %}
188
+ <div style="background:red; height:50px">Custom widget</div>
189
+ {% /html %}
190
+
191
+ ---
192
+
193
+ Inline: %product% and {% glossary term="coding" /%} and
194
+ {% badge text="Done" type="success" /%} and {% icon classes="fas fa-adjust" /%}
195
+ and {% key key="F1" /%} and {% inline-image url="asset:02grc2jqacve" width=14 /%}.
196
+ ````
197
+
198
+ ## When to use which block
199
+
200
+ | You want to… | Use | Not |
201
+ |---|---|---|
202
+ | Highlight a note, tip, or warning | `callout` (`type=`) | bold text |
203
+ | Show the same code in several languages | `code` with fenced tabs | one fence with mixed code |
204
+ | Walk through code step by step | `code-steps` | a numbered list of fences |
205
+ | A table with spans, alignment, or cell colours | `table` / `row` / `cell` | a GFM pipe table (it is rewritten) |
206
+ | Group alternative content the reader switches between | `tabs` / `tab` | headings |
207
+ | Collapse optional / long detail | `accordion-group` / `accordion` | `tabs` |
208
+ | A grid of navigation links | `cards` / `card` | a bullet list of links |
209
+ | Content for one audience only | `conditional` (`audience=`) | duplicating the page |
210
+ | A reusable snippet shared across pages | `synced` (`id=`) | copy-paste |
211
+
212
+ ## Round-trip gotchas
213
+
214
+ These follow from the two-way sync. Ignoring them drops or mangles content on the
215
+ next save.
216
+
217
+ - **Keep block open/close tags on their own lines** (rule 1). This is the most
218
+ common mistake.
219
+ - **Tables are `row` / `cell` trees**, not Markdown pipes. A pipe table is
220
+ accepted on input but rewritten into the `{% table %}` form on the next save,
221
+ so don't be surprised when your pipe grid comes back expanded.
222
+ - **Multi-language code is `{% code %}` wrapping bare fences**, each annotated with
223
+ `{% title="…" %}`. There is **no** `{% tab %}` inside `{% code %}` — that tag is
224
+ only for generic content tabs.
225
+ - **Line-number / wrap settings ride on the `{% code %}` open tag**, once, shared
226
+ by every tab — not per fence.
227
+ - **Accordions collapse by default.** Add a bare `open=true` to expand one on load.
228
+ - **An intentionally empty paragraph** serialises as `{% p /%}`. You rarely write
229
+ this by hand.
230
+ - **Custom HTML is best kept to a single logical line.** Multi-line *indented* HTML
231
+ in `{% html %}` can lose its indentation on round-trip.
232
+ - **`conditional audience="…"` must name a real project audience.** Omit the
233
+ attribute for public / all-audiences content.
234
+
235
+ ## Page frontmatter
236
+
237
+ A page stored as a `.md` file (a Git-synced repo, or an export) opens with a YAML
238
+ `--- … ---` block, then a blank line, then the Markdoc body. The field set is
239
+ **fixed** — the same keys on every page, not project-specific — so keep the block
240
+ intact and edit the values in place; put the Markdoc body after it.
241
+
242
+ ```yaml
243
+ ---
244
+ type: page
245
+ title: Getting started
246
+ listed: true
247
+ slug: getting-started
248
+ description:
249
+ index_title: Getting started
250
+ hidden:
251
+ keywords:
252
+ tags:
253
+ ---
254
+ ```
255
+
256
+ | Field | Meaning |
257
+ |---|---|
258
+ | `type` | `page` for a content page. An index entry may instead be `category`, `label`, `separator`, or `link`; those carry fewer fields (`category`/`label` → `type` + `title`; `separator` → `type`; `link` → `type` + `title` + `url`). |
259
+ | `title` | The page title (its `#` H1). |
260
+ | `listed` | `true` / `false` — whether the page appears in navigation. Defaults to `true` when omitted. |
261
+ | `slug` | URL slug. For a Git-synced page the file path *is* the slug, so this field is informational; renaming the file is what changes the URL. |
262
+ | `description` | Meta / SEO description. Left empty when unset. |
263
+ | `index_title` | Overrides the title shown in the sidebar / index; falls back to `title`. |
264
+ | `hidden` | `true` / `false` — hide the page from the auto-generated index. Defaults to `false`. |
265
+ | `keywords` | Comma-separated search keywords. |
266
+ | `tags` | Comma-separated page tags. |
267
+
268
+ **Preserve every key, even when its value is empty** — the format writes them all.
269
+ This skill covers the body; leave the frontmatter block in place and change only the
270
+ values you mean to change.
@@ -0,0 +1,467 @@
1
+ # DeveloperHub Markdoc — full block & tag reference
2
+
3
+ Every DeveloperHub Markdoc tag, its form, attributes, defaults, and allowed
4
+ values. Read [`../SKILL.md`](../SKILL.md) first for the core dialect rules.
5
+
6
+ **Conventions used below**
7
+
8
+ - **Form** — `block` (`{% x %}…{% /x %}`), `inline` (`{% x /%}`, self-closing), or
9
+ `fence` (a fenced code block with `{% … %}` annotations).
10
+ - **Default** — omit the attribute when its value equals this; the serialiser does.
11
+ - Booleans are **bare** (`open=true`); numbers are bare (`width=357`); strings are
12
+ quoted (`title="Note"`).
13
+
14
+ ---
15
+
16
+ ## Text & inline marks
17
+
18
+ Prefer plain Markdown for all of these.
19
+
20
+ | Construct | Syntax |
21
+ |---|---|
22
+ | Bold | `**bold**` |
23
+ | Italic | `*italic*` |
24
+ | Strikethrough | `~~struck~~` |
25
+ | Inline code | `` `code` `` |
26
+ | Underline | `{% u %}underlined{% /u %}` (no Markdown equivalent) |
27
+ | Link | `[text](/support-center/slug)` |
28
+ | Link with spaces/parens in URL | `{% link href="https://x.com/a (b)" %}text{% /link %}` |
29
+ | Heading | `##` … `######` (reserve `#` for the page title) |
30
+ | Bullet list | `- item` (nest by indenting) |
31
+ | Ordered list | `1. item` |
32
+ | Blockquote | `> quoted` |
33
+ | Horizontal rule | `---` |
34
+ | Empty paragraph | `{% p /%}` (rarely authored by hand) |
35
+
36
+ Links must carry real text — a text-less link round-trips to nothing.
37
+
38
+ ---
39
+
40
+ ## Blocks
41
+
42
+ ### `callout`
43
+
44
+ Coloured note / warning box. Body holds arbitrary block content.
45
+
46
+ ```markdoc
47
+ {% callout type="warning" title="Changing hosting settings" %}
48
+ Redirect any external links if you change hosting after publishing.
49
+ {% /callout %}
50
+ ```
51
+
52
+ | Attr | Type | Default | Values |
53
+ |---|---|---|---|
54
+ | `type` | enum | `info` | `info`, `warning`, `success`, `error` |
55
+ | `title` | string | `""` | rendered as a header row when set |
56
+
57
+ > The type for a red/error box is **`error`**, not "danger".
58
+
59
+ ### `code` (multi-language tabs)
60
+
61
+ Wraps one or more **bare fenced blocks**, each a language tab. There is **no**
62
+ `{% tab %}` inside `{% code %}`. Line-number and wrap settings ride on the open
63
+ tag, once, shared by every tab.
64
+
65
+ ````markdoc
66
+ {% code showLineNumbers=true %}
67
+ ```javascript {% title="JavaScript" %}
68
+ console.log("hi");
69
+ ```
70
+
71
+ ```python {% title="Python" %}
72
+ print("hi")
73
+ ```
74
+ {% /code %}
75
+ ````
76
+
77
+ | Attr | Type | Default | Notes |
78
+ |---|---|---|---|
79
+ | `showLineNumbers` | boolean | `false` | shared by all tabs |
80
+ | `wrapLine` | boolean | `true` | shared by all tabs |
81
+
82
+ Content model: **only** bare code fences.
83
+
84
+ ### Fenced code block (single)
85
+
86
+ A single code block. Language on the info string; annotations in `{% … %}` after
87
+ it.
88
+
89
+ ````markdoc
90
+ ```javascript {% title="fibonacci.js" showLineNumbers=true highlightLines="2,4-6" %}
91
+ function fibonacci(num) {
92
+ return num <= 1 ? 1 : fibonacci(num - 1) + fibonacci(num - 2);
93
+ }
94
+ ```
95
+ ````
96
+
97
+ | Annotation | Type | Default | Notes |
98
+ |---|---|---|---|
99
+ | `title` | string | `Code` | shown in the block header |
100
+ | `showLineNumbers` | boolean | `false` | omit inside `{% code %}` (set it there) |
101
+ | `wrapLine` | boolean | `true` | omit inside `{% code %}` |
102
+ | `highlightLines` | string | `""` | e.g. `"1,3-5"` |
103
+ | `id` | string | — | only inside `code-steps` (see below) |
104
+
105
+ The language comes from the fence token itself (` ```js `); default `plaintext`.
106
+
107
+ ### `code-steps`
108
+
109
+ A stepped walkthrough: a lead description, then `{% step %}` tags, then one fenced
110
+ language block per step. Each fence carries an `id` that a step's `codeId` points
111
+ at.
112
+
113
+ ````markdoc
114
+ {% code-steps title="Set up the client" %}
115
+ Intro prose describing the walkthrough.
116
+
117
+ {% step title="Install" codeId="install" %}
118
+ Install the package.
119
+ {% /step %}
120
+
121
+ ```bash {% id="install" title="Terminal" %}
122
+ npm install @acme/sdk
123
+ ```
124
+ {% /code-steps %}
125
+ ````
126
+
127
+ `code-steps` attrs:
128
+
129
+ | Attr | Type | Default |
130
+ |---|---|---|
131
+ | `title` | string | `Code Steps` |
132
+
133
+ `step` attrs:
134
+
135
+ | Attr | Type | Default | Notes |
136
+ |---|---|---|---|
137
+ | `title` | string | `""` | |
138
+ | `codeId` | string | `""` | matches a fence `id` in this block |
139
+ | `highlightLines` | string | `""` | |
140
+
141
+ Inside `code-steps`, a language fence's `showLineNumbers` defaults to `true`.
142
+
143
+ ### `table` / `row` / `cell`
144
+
145
+ An explicit `row` / `cell` tree. Each cell body is itself block content. Use this
146
+ (not a pipe table) whenever you need cell spans, alignment, backgrounds, or the
147
+ fluid `auto` layout.
148
+
149
+ ```markdoc
150
+ {% table layout="auto" %}
151
+ {% row %}
152
+ {% cell header=true %}
153
+ Parameter
154
+ {% /cell %}
155
+ {% cell header=true align="right" %}
156
+ Type
157
+ {% /cell %}
158
+ {% /row %}
159
+ {% row %}
160
+ {% cell %}
161
+ user_id
162
+ {% /cell %}
163
+ {% cell %}
164
+ int
165
+ {% /cell %}
166
+ {% /row %}
167
+ {% /table %}
168
+ ```
169
+
170
+ `table` attrs:
171
+
172
+ | Attr | Type | Default | Values |
173
+ |---|---|---|---|
174
+ | `layout` | enum | `fixed` | `fixed`, `auto` (last column fluid) |
175
+
176
+ `row` — no attributes; contains `cell` children only.
177
+
178
+ `cell` attrs:
179
+
180
+ | Attr | Type | Default | Values |
181
+ |---|---|---|---|
182
+ | `header` | boolean | `false` | `true` renders as a header cell |
183
+ | `align` | enum | (none) | `left`, `center`, `right` |
184
+ | `bg` | string | (none) | CSS colour, e.g. `#f6f6f6` |
185
+ | `colspan` | number | `1` | |
186
+ | `rowspan` | number | `1` | |
187
+ | `colwidth` | number | (none) | column width in px |
188
+
189
+ > A plain GFM pipe table is accepted on input but is rewritten into this
190
+ > `{% table %}` form on the next save. Don't be surprised when it comes back
191
+ > expanded.
192
+
193
+ ### `image`
194
+
195
+ Block image. Self-closing when there is no caption; body form when there is one
196
+ (the body is the caption, inline content only).
197
+
198
+ ```markdoc
199
+ {% image url="asset:zvlxyph4f37t" width=357 /%}
200
+ ```
201
+
202
+ ```markdoc
203
+ {% image url="https://uploads.developerhub.io/prod/02/example.png" %}
204
+ A caption sits in the body.
205
+ {% /image %}
206
+ ```
207
+
208
+ | Attr | Type | Default | Values |
209
+ |---|---|---|---|
210
+ | `url` | string | `""` | `asset:<id>` (kept verbatim) or a full upload URL |
211
+ | `width` | number | `0` | px; `0` = natural/full width |
212
+ | `align` | enum | `center` | `left`, `center`, `right` |
213
+
214
+ ### `video`
215
+
216
+ Embedded or raw video. Self-closing.
217
+
218
+ ```markdoc
219
+ {% video provider="loom" videoId="e5b8c04bca094dd8a5507925ab887002" /%}
220
+ {% video provider="raw" videoId="https://uploads.developerhub.io/example.mp4" /%}
221
+ ```
222
+
223
+ | Attr | Type | Default | Values |
224
+ |---|---|---|---|
225
+ | `provider` | enum | `youtube` | `youtube`, `vimeo`, `loom`, `raw` |
226
+ | `videoId` | string | `""` | the id for embeds; the full URL for `raw` (mp4/webm/ogg) |
227
+ | `params` | string | `""` | e.g. `start=30` |
228
+
229
+ ### `tabs` / `tab`
230
+
231
+ Generic tabbed content the reader switches between. Distinct from `{% code %}`.
232
+
233
+ ```markdoc
234
+ {% tabs %}
235
+ {% tab title="Android" %}
236
+ Android content.
237
+ {% /tab %}
238
+ {% tab title="iOS" %}
239
+ iOS content.
240
+ {% /tab %}
241
+ {% /tabs %}
242
+ ```
243
+
244
+ `tabs` — no attributes; contains `tab` children only.
245
+
246
+ `tab` attrs:
247
+
248
+ | Attr | Type | Default |
249
+ |---|---|---|
250
+ | `title` | string | `Tab` |
251
+
252
+ ### `accordion-group` / `accordion`
253
+
254
+ Collapsible sections. Each section is **collapsed on load** by default; add a bare
255
+ `open=true` to expand it initially.
256
+
257
+ ```markdoc
258
+ {% accordion-group %}
259
+ {% accordion title="Getting started" open=true %}
260
+ Any block content can go inside an accordion.
261
+ {% /accordion %}
262
+ {% accordion title="Advanced features" %}
263
+ Second section.
264
+ {% /accordion %}
265
+ {% /accordion-group %}
266
+ ```
267
+
268
+ `accordion-group` — no attributes; contains `accordion` children only.
269
+
270
+ `accordion` attrs:
271
+
272
+ | Attr | Type | Default | Notes |
273
+ |---|---|---|---|
274
+ | `title` | string | `Accordion` | |
275
+ | `open` | boolean | `false` | `open=true` = expanded on page load |
276
+
277
+ ### `cards` / `card`
278
+
279
+ A grid (default) or list of link cards. Each card is a self-closing `{% card /%}`.
280
+
281
+ ```markdoc
282
+ {% cards view="grid" %}
283
+ {% card title="Getting Started" text="Set up your first project." link="/support-center/getting-started" /%}
284
+ {% card title="Hosting" text="Custom domains and SSL." link="/support-center/hosting" /%}
285
+ {% /cards %}
286
+ ```
287
+
288
+ `cards` attrs:
289
+
290
+ | Attr | Type | Default | Values |
291
+ |---|---|---|---|
292
+ | `view` | enum | `grid` | `grid`, `list` |
293
+
294
+ `card` attrs:
295
+
296
+ | Attr | Type | Default |
297
+ |---|---|---|
298
+ | `title` | string | `""` |
299
+ | `text` | string | `""` |
300
+ | `link` | string | `/` |
301
+
302
+ ### `conditional`
303
+
304
+ Wraps content shown only to a given audience (personalised / private docs).
305
+ Resolved server-side.
306
+
307
+ ```markdoc
308
+ {% conditional audience="beta-users" %}
309
+ Content only beta users see.
310
+ {% /conditional %}
311
+ ```
312
+
313
+ | Attr | Type | Default | Notes |
314
+ |---|---|---|---|
315
+ | `audience` | string | `""` | a real project audience id; omit for public content |
316
+
317
+ ### `html`
318
+
319
+ Raw custom HTML. The HTML is the body, not an attribute. Keep it to a single
320
+ logical line where practical — multi-line indented HTML can lose indentation on
321
+ round-trip.
322
+
323
+ ```markdoc
324
+ {% html %}
325
+ <div class="promo">Custom widget</div>
326
+ {% /html %}
327
+ ```
328
+
329
+ No attributes.
330
+
331
+ ### `github-code`
332
+
333
+ Embeds code straight from a GitHub file URL (optionally a line range in the
334
+ fragment). Self-closing.
335
+
336
+ ```markdoc
337
+ {% github-code url="https://github.com/torvalds/linux/blob/master/kernel/signal.c#L152-L170" /%}
338
+ ```
339
+
340
+ | Attr | Type | Default |
341
+ |---|---|---|
342
+ | `url` | string | `""` |
343
+
344
+ ### `index-list`
345
+
346
+ Auto-generates an index of the current page's children. Self-closing, no
347
+ attributes.
348
+
349
+ ```markdoc
350
+ {% index-list /%}
351
+ ```
352
+
353
+ ### `synced`
354
+
355
+ Inserts a reusable synced block by its id (managed in the project, not in the
356
+ file). Self-closing.
357
+
358
+ ```markdoc
359
+ {% synced id="open-block-menu" /%}
360
+ ```
361
+
362
+ | Attr | Type | Default |
363
+ |---|---|---|
364
+ | `id` | string | `welcome` |
365
+
366
+ ---
367
+
368
+ ## Inline tags
369
+
370
+ All self-closing, all sit inside a line of text.
371
+
372
+ ### `badge`
373
+
374
+ Small coloured label.
375
+
376
+ ```markdoc
377
+ {% badge text="Done" type="success" /%}
378
+ ```
379
+
380
+ | Attr | Type | Default | Values |
381
+ |---|---|---|---|
382
+ | `text` | string | `OK` | the label |
383
+ | `type` | enum | `info` | `info`, `success`, `warning`, `error`, `primary`, `custom` |
384
+
385
+ ### `key`
386
+
387
+ A keyboard key. Multi-key strings like `cmd shift k` are prettified to glyphs
388
+ (`⌘ ⇧ k`) on render.
389
+
390
+ ```markdoc
391
+ {% key key="F1" /%} {% key key="⌘" /%}
392
+ ```
393
+
394
+ | Attr | Type | Default |
395
+ |---|---|---|
396
+ | `key` | string | `Esc` |
397
+
398
+ ### `icon`
399
+
400
+ A Font Awesome icon.
401
+
402
+ ```markdoc
403
+ {% icon classes="fas fa-adjust" /%}
404
+ ```
405
+
406
+ | Attr | Type | Default |
407
+ |---|---|---|
408
+ | `classes` | string | `fas fa-star` |
409
+
410
+ ### `glossary`
411
+
412
+ Links a word to its glossary entry (the definition is resolved live from the
413
+ project glossary).
414
+
415
+ ```markdoc
416
+ {% glossary term="coding" /%}
417
+ ```
418
+
419
+ | Attr | Type | Default |
420
+ |---|---|---|
421
+ | `term` | string | `term` |
422
+
423
+ ### `inline-image`
424
+
425
+ An image sized to sit inline with text.
426
+
427
+ ```markdoc
428
+ {% inline-image url="asset:02grc2jqacve" width=14 /%}
429
+ ```
430
+
431
+ | Attr | Type | Default | Values |
432
+ |---|---|---|---|
433
+ | `url` | string | `""` | `asset:<id>` (verbatim) or a full URL |
434
+ | `width` | number | `0` | px; applies in `resizable` mode |
435
+ | `mode` | enum | `resizable` | `resizable`, `w100` (fit width), `fit` |
436
+
437
+ ---
438
+
439
+ ## Variables
440
+
441
+ Variables are written in **percent form**, not as a tag:
442
+
443
+ ```markdoc
444
+ %product% renders the project product name (e.g. "DeveloperHub")
445
+ %user.name% dotted keys resolve into nested values
446
+ ```
447
+
448
+ - Keys are dotted identifiers (`api.base`).
449
+ - Never echo `%product%` back to a user as literal text, and never replace it in
450
+ source.
451
+ - A `%…%` inside inline code or a code fence stays literal.
452
+ - An undefined key renders as `{{key}}`.
453
+
454
+ ---
455
+
456
+ ## Nesting rules
457
+
458
+ - `accordion-group` → `accordion` only; `accordion` → any block.
459
+ - `tabs` → `tab` only; `tab` → any block.
460
+ - `code` → bare code fences only (no `{% tab %}`).
461
+ - `code-steps` → lead blocks, then `step` tags, then language fences.
462
+ - `table` → `row` → `cell`; a `cell` holds any block content.
463
+ - `callout`, `conditional` → any block (they can nest tabs, callouts, etc.).
464
+ - `image` body → caption (inline content only).
465
+ - `video`, `cards`, `github-code`, `index-list`, `synced`, `html` → no children.
466
+ - Inline tags (`badge`, `glossary`, `icon`, `key`, `inline-image`) and `%KEY%`
467
+ variables live inside a line of text.