dogsbay 0.2.0-beta.2 → 0.2.0-beta.21

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.
@@ -0,0 +1,310 @@
1
+ ---
2
+ name: dogsbay:frontmatter-fields
3
+ description: Reference for the frontmatter fields Dogsbay recognises and how the importers + audit rules use them. Use when adding YAML frontmatter to a content/*.md page or debugging why a field isn't taking effect.
4
+ ---
5
+
6
+ # Frontmatter fields
7
+
8
+ Every Dogsbay markdown page can declare YAML frontmatter at
9
+ the top, delimited by `---` lines. The importer parses it via
10
+ `gray-matter`; the recognised keys flow into `ExportPage` and
11
+ the rendered `.astro` page.
12
+
13
+ ```markdown
14
+ ---
15
+ title: Configuration
16
+ description: How to configure your Dogsbay site.
17
+ draft: false
18
+ ---
19
+
20
+ # Configuration
21
+
22
+
23
+ ```
24
+
25
+ ## Three categories of frontmatter
26
+
27
+ Dogsbay sees three distinct kinds of frontmatter, and they're
28
+ easy to confuse:
29
+
30
+ 1. **Built-in fields** with semantic meaning — Dogsbay's
31
+ importer (`parseMeta`) lifts these into `ExportPage` and the
32
+ layout reads them. Setting them produces visible effects
33
+ (chips, badges, build-time gates, SEO meta).
34
+ 2. **User-declared taxonomies** — keys you name in
35
+ `taxonomies:` config get lifted into `meta.taxonomies[name]`
36
+ and produce browse pages, faceted search, and (for the five
37
+ built-ins below) chips. See `dogsbay:taxonomy-config`.
38
+ 3. **Arbitrary frontmatter** — anything else is preserved
39
+ on `ExportPage.frontmatter` but not consumed. Suitable for
40
+ build-tool inputs, internal annotations, or fields that
41
+ future plugins might read.
42
+
43
+ The three tiers are mutually exclusive: `tags` is a built-in
44
+ (tier 1), not "user-defined" (tier 2). Custom `difficulty`
45
+ declared in your `taxonomies:` config is tier 2, not tier 3.
46
+
47
+ # Tier 1: Built-in fields
48
+
49
+ Each subsection groups built-ins by purpose. Setting any of
50
+ these produces a real effect — they aren't opaque slots.
51
+
52
+ ## Identification
53
+
54
+ | Field | Type | Purpose |
55
+ |---|---|---|
56
+ | `title` | string | Page title. Used in `<title>` tag, sidebar fallback, OG meta. Default: H1 of body, or filename. |
57
+ | `description` | string | Page description. Used in `<meta name="description">`, OG description, search snippets. Treated as load-bearing for SEO and AEO. |
58
+
59
+ ## Routing
60
+
61
+ | Field | Type | Purpose |
62
+ |---|---|---|
63
+ | `redirect` | string | Page is a redirect-only route. Renders no body; emits a 0-byte `<meta http-equiv="refresh">` page that points at the value. Use when you need a stable old URL to point at a new page. |
64
+ | `slug` | string | Override the URL slug (default: filename without `.md`). Use sparingly — file paths are usually the better source of truth. |
65
+
66
+ ## Visibility
67
+
68
+ | Field | Type | Default | Purpose |
69
+ |---|---|---|---|
70
+ | `draft` | boolean | `false` | When `true`, the page is excluded from production builds (`dogsbay site build`). It IS included in `dogsbay site dev` so the writer can preview. |
71
+ | `noindex` | boolean | `false` | Adds `<meta name="robots" content="noindex">`. Page is still public; just hidden from external search engines. |
72
+ | `excludeFromSearch` | boolean | `false` | Skip Pagefind indexing for this page. Use for legal pages, redirect stubs, etc. Independent of `noindex` — a page can be in-site-searchable but external-noindex, or vice versa. |
73
+
74
+ The escape hatch for production builds is `--include-drafts`
75
+ on `dogsbay site build` (matches the always-on behaviour of
76
+ `dogsbay site dev`). The build line prints "(N drafts excluded)"
77
+ so the writer notices.
78
+
79
+ `status: draft` is **also** a build-time visibility gate (see
80
+ the "`status` is dual-purpose" subsection below).
81
+
82
+ **Don't stamp `draft: true` or `status: draft` on new pages
83
+ proactively.** In a git-backed site, draftness is the branch
84
+ state — the page is a draft because the PR isn't merged, not
85
+ because of frontmatter. Layering frontmatter draft flags on top
86
+ creates an invisible second gate that survives the merge: the
87
+ PR ships, but the page silently doesn't appear in production
88
+ because nobody removed the flag. Default to no draft flag and
89
+ let branches do the gating. Only stamp draft when the writer
90
+ explicitly asks (or there's no git workflow at all).
91
+
92
+ ## Content classification (also taxonomies)
93
+
94
+ These five fields are first-class Dogsbay metadata AND drive
95
+ the taxonomy system when declared in `taxonomies:` config. They
96
+ have semantic effects regardless of whether you declare a
97
+ taxonomy:
98
+
99
+ | Field | Type | Purpose |
100
+ |---|---|---|
101
+ | `type` | string | Open string — `tutorial`, `how-to`, `reference`, `explanation`, or any custom value. Renders a `<TypeBadge>`; drives Schema.org JSON-LD selection (`HowTo` for how-tos, `TechArticle` for references). |
102
+ | `status` | enum | Closed enum — `draft \| preview \| stable \| deprecated`. Renders a `<StatusBadge>`. **`draft` is special-cased** (see below). |
103
+ | `audience` | string[] | Who the page is for. Single string is coerced to a one-element array. Surfaced as `<div data-pagefind-filter="audience:<value>">` for search facets. |
104
+ | `category` | string[] | Category segments. Auto-derived from path when absent (e.g. `/guides/auth/foo` → `["guides", "auth"]`). Hidden Pagefind facet. |
105
+ | `tags` | string[] | Free-form chip strip on the article header. Slash-nested values link to taxonomy term pages (`api/rest` → `/tags/api/rest/`). |
106
+
107
+ When **declared in `taxonomies:` config** (recommended), these
108
+ also light up:
109
+
110
+ - A browse index (`/tags/`, `/by-type/`, `/by-status/`, etc.)
111
+ - Per-term pages (`/tags/concept/auth`, `/by-type/tutorial`)
112
+ - A faceted-search checkbox group in `Cmd+K` search (one column
113
+ per declared taxonomy, sorted by frequency)
114
+ - Display-config consumption for human labels and prefix
115
+ colors
116
+
117
+ See `dogsbay:taxonomy-config` for the full taxonomy surface.
118
+
119
+ ### `status` is dual-purpose
120
+
121
+ `status: draft` is **both** a taxonomy value AND aliased to
122
+ `draft: true` as a build-time visibility gate. A page with
123
+ `status: draft` is excluded from production builds the same
124
+ way `draft: true` is. The other three values (`preview`,
125
+ `stable`, `deprecated`) are pure taxonomy — they render a
126
+ `<StatusBadge>` chip and populate the `/by-status/` browse
127
+ page, but don't gate visibility.
128
+
129
+ This is the only built-in taxonomy with overloaded semantics.
130
+ Implications:
131
+
132
+ - `status: draft` + `dogsbay site build` → page omitted from
133
+ output (the same N-drafts-excluded message fires).
134
+ - `status: preview` / `stable` / `deprecated` → page ships
135
+ normally; just renders the badge and gets bucketed into
136
+ `/by-status/<value>/`.
137
+ - A writer who wants `draft` to be a pure taxonomy value
138
+ (no exclusion) should rename to a different value (e.g.
139
+ `status: in-progress`) and customize the badge label via
140
+ `taxonomies.status.labels`.
141
+
142
+ The escape hatch on `dogsbay site build` is `--include-drafts`
143
+ (see the visibility section above).
144
+
145
+ ## SEO / social
146
+
147
+ | Field | Type | Purpose |
148
+ |---|---|---|
149
+ | `ogImage` | string | Per-page Open Graph image URL. Falls back to `siteConfig.ogImage` then no OG image. |
150
+ | `ogType` | string | Override `<meta property="og:type">` (default `article`). |
151
+ | `canonicalUrl` | string | Explicit `<link rel="canonical">` href. Default: build-time-computed from `siteUrl` + URL. |
152
+
153
+ ## Agent surface
154
+
155
+ | Field | Type | Purpose |
156
+ |---|---|---|
157
+ | `llmActions` | boolean / object | When `false`, suppress the per-page LLM-action UI cluster (Copy as markdown, Open in Claude, etc.). When an object, override placement / providers. See `dogsbay:agent-readiness`. |
158
+
159
+ ## Dates and ordering
160
+
161
+ | Field | Type | Purpose |
162
+ |---|---|---|
163
+ | `updated` | date | ISO date string for "last updated" displays. Date objects are coerced via `.toISOString()`. |
164
+ | `created` | date | ISO date string for "first published" displays. Same coercion. |
165
+ | `weight` | number | Sort hint for term/index pages (lower first). Non-numeric values are dropped. |
166
+
167
+ ## Auto-rendering hints
168
+
169
+ | Field | Type | Default | Purpose |
170
+ |---|---|---|---|
171
+ | `autoH1` | boolean | depends | When `true`, the layout renders `<h1>{title}</h1>` at the top of `<main>` if the body doesn't start with an H1. Computed per-page by the lede-detection step. |
172
+ | `autoLede` | boolean | `false` | When `true`, render `<p>{description}</p>` below the auto-H1 (or above the meta strip when there's no H1). |
173
+
174
+ ## Multi-source axis metadata (read-only, set by aggregator)
175
+
176
+ These get stamped by the multi-source aggregator when more than
177
+ one source / version / locale is in play. Setting them manually
178
+ is a mistake — the aggregator overwrites on every build.
179
+
180
+ | Field | Type | Purpose |
181
+ |---|---|---|
182
+ | `multiSource.namespace` | string | Set when ≥2 sources have distinct names. |
183
+ | `multiSource.version` | string | Set when version axis is active. |
184
+ | `multiSource.locale` | string | Set when locale axis is active. |
185
+ | `multiSource.originalSlug` | string | Slug as the importer produced it before any axis prefixing. Used by the version-switcher to compute cross-version equivalents. |
186
+
187
+ ## Custom props bag
188
+
189
+ | Field | Type | Purpose |
190
+ |---|---|---|
191
+ | `props` | object | Arbitrary key/value bag passed to the layout's per-page props slot. Useful for plugin or pattern integrations that need page-specific config without inventing a new top-level field. |
192
+
193
+ # Tier 2: User-declared taxonomies
194
+
195
+ Any frontmatter key you name in `taxonomies:` config gets
196
+ lifted into `meta.taxonomies[name]` as a string array. Example:
197
+ declaring `taxonomies.difficulty` in `dogsbay.config.yml` makes
198
+ `difficulty: intermediate` available as a Pagefind facet, a
199
+ browse page at `/by-difficulty/intermediate/`, and (with
200
+ `prefixes:` config) a chip color.
201
+
202
+ The five Tier 1 classification fields (`status`, `type`,
203
+ `audience`, `category`, `tags`) are **also** valid in
204
+ `taxonomies:` config — declaring them there extends their
205
+ existing built-in behavior (chip strips, badges) with the
206
+ taxonomy surface (browse pages, facets).
207
+
208
+ Keys NOT in `taxonomies:` config are tier 3 (arbitrary).
209
+
210
+ See `dogsbay:taxonomy-config` for the full taxonomy surface,
211
+ including `prefixes` (chip colors), `labels` (display
212
+ overrides), `values` (closed enum + audit), and `hierarchical`
213
+ (slash-nested browsing).
214
+
215
+ # Tier 3: Arbitrary frontmatter
216
+
217
+ Anything not in tiers 1–2 is preserved on
218
+ `ExportPage.frontmatter` and forwarded to the rendered page,
219
+ but Dogsbay's importer + layout don't consume it. No semantic
220
+ effect — no chips, no facets, no audit rules, no SEO meta.
221
+
222
+ Use cases:
223
+
224
+ - Internal annotations the writing team uses (e.g. a `reviewers:`
225
+ list rendered nowhere but tracked for editorial workflow).
226
+ - Custom plugin inputs (a plugin can add an `auditRule` that
227
+ reads any frontmatter key — see `dogsbay:plugin-api`).
228
+ - Importer-specific carry-through: Starlight's
229
+ `pcx_content_type`, MkDocs' `template`, etc. are preserved
230
+ but not lifted into Dogsbay's typed slots.
231
+
232
+ Plugins promote arbitrary frontmatter to semantic via the
233
+ `auditRules` and `componentWrappers` hooks. If you find
234
+ yourself wanting an arbitrary field to "do something", that's
235
+ the path.
236
+
237
+ ## Diátaxis (only if `@dogsbay/pattern-diataxis` is installed)
238
+
239
+ The Diátaxis pattern adds audit rules that match on `meta.type`
240
+ values:
241
+
242
+ | Value | Triggers |
243
+ |---|---|
244
+ | `tutorial` | `diataxis/tutorial-*` audit rules (verb-led headings, step structure, etc.) |
245
+ | `how-to` | `diataxis/how-to-*` rules |
246
+ | `reference` | `diataxis/reference-*` rules |
247
+ | `explanation` | `diataxis/explanation-*` rules |
248
+
249
+ Without the pattern installed, `meta.type` still works — it
250
+ renders a `<TypeBadge>` and powers `/by-type/` if you declare
251
+ the taxonomy. The pattern just adds opinionated audit rules.
252
+
253
+ # How importers + audit rules read frontmatter
254
+
255
+ - **format-dogsbay-md** parses via gray-matter; the typed
256
+ subset becomes `ExportPage.title`, `description`, `redirect`.
257
+ The full raw YAML lands in `ExportPage.frontmatter`.
258
+ - **format-mkdocs / format-obsidian / format-starlight** do
259
+ the same; importer-specific keys are preserved in
260
+ `frontmatter` but not lifted to typed fields.
261
+ - **format-astro** writes the typed fields into the generated
262
+ `.astro` page's frontmatter; the layout reads them as props.
263
+ - **dogsbay site check** rules can match on any frontmatter
264
+ key. `seo/description-missing` and `seo/description-length`
265
+ look at the `description` field directly.
266
+
267
+ # Cross-format `description` quirk
268
+
269
+ Three importers generate descriptions slightly differently:
270
+
271
+ - **dogsbay-md** uses whatever's in frontmatter. Empty if
272
+ missing.
273
+ - **MkDocs** falls back to the first paragraph if frontmatter
274
+ is empty (matching MkDocs Material convention).
275
+ - **Starlight** treats the typed subset as authoritative.
276
+
277
+ If the `seo/description-missing` audit fires unexpectedly,
278
+ check whether the importer is providing a default vs requiring
279
+ explicit declaration.
280
+
281
+ # Common mistakes
282
+
283
+ - ❌ Embedding YAML in the body (only the leading
284
+ `---`-delimited block is parsed; later `---` is rendered as
285
+ `<hr>`).
286
+ - ❌ Single-quoted multi-line strings without `>` or `|` block
287
+ scalars — multi-line strings need YAML's block syntax.
288
+ - ❌ Setting `multiSource.namespace` (or any `multiSource.*`)
289
+ manually — overwritten by the aggregator on every build.
290
+ - ❌ Using `draft: 'true'` (string) instead of `draft: true`
291
+ (boolean) — strings are truthy but the audit rules check for
292
+ the boolean.
293
+ - ❌ Adding `status: draft` (or `draft: true`) to a new page
294
+ by default. Branches handle draft state in a git workflow;
295
+ the frontmatter gate persists past merge and silently hides
296
+ the page in production. Only stamp draft when explicitly
297
+ asked.
298
+ - ❌ Treating `status`, `type`, `audience`, `category`, `tags`
299
+ as user-defined fields. They're built-in classification with
300
+ semantic effects, AND they're also the canonical taxonomies
301
+ to declare in `taxonomies:` config. Use the built-in name
302
+ rather than inventing a parallel field.
303
+ - ❌ Setting `status: beta` (or any value not in the closed
304
+ enum). The four values are `draft | preview | stable |
305
+ deprecated` — `parseMeta` silently drops anything else, so
306
+ the badge won't render and you'll waste time debugging.
307
+ - ❌ Custom `slug` that doesn't match the file path's parent
308
+ segments — the URL becomes inconsistent with the file
309
+ hierarchy. Stick with file-path-derived slugs unless there's
310
+ a specific reason.
@@ -0,0 +1,329 @@
1
+ ---
2
+ name: dogsbay:markdown-directives
3
+ description: Write Dogsbay markdown using the directive syntax for cards, steps, tabs, callouts, grids, details, and other components. Use when authoring or editing content/*.md pages.
4
+ ---
5
+
6
+ # Dogsbay markdown directives
7
+
8
+ Dogsbay markdown is a small superset of CommonMark with `:::name`
9
+ block directives for the components docs sites need most. The
10
+ directive syntax is parser-enforced — getting it wrong fails the
11
+ build or renders as literal text.
12
+
13
+ ## Block directive shape
14
+
15
+ ```
16
+ :::name{attr1="value" attr2="value"}
17
+ content
18
+ :::
19
+ ```
20
+
21
+ Use `::::` (four colons) when the content itself contains `:::`
22
+ (e.g., a directive nested inside another).
23
+
24
+ ## Callouts
25
+
26
+ Two equivalent forms — pick whichever reads cleaner.
27
+
28
+ ### GitHub-alert form (preferred for note/warning/tip/danger)
29
+
30
+ ```markdown
31
+ > [!NOTE]
32
+ > Helpful supplemental information.
33
+
34
+ > [!WARNING]
35
+ > Don't do the thing.
36
+
37
+ > [!TIP]
38
+ > Confirmation that you're on the right track.
39
+
40
+ > [!DANGER]
41
+ > Destructive or irreversible.
42
+ ```
43
+
44
+ The token after `[!` is the type — `NOTE`, `TIP`, `WARNING`,
45
+ `DANGER`, `IMPORTANT`, `CAUTION`.
46
+
47
+ ### Directive form (for custom titles or types outside the GitHub set)
48
+
49
+ ```markdown
50
+ :::callout{type="success" title="Build complete"}
51
+ The site published in 4.2 seconds.
52
+ :::
53
+ ```
54
+
55
+ Without `title`, the directive form behaves identically to the
56
+ GitHub form for the type.
57
+
58
+ ## Steps
59
+
60
+ Numbered steps for sequential procedures. Renders as `<Steps>` +
61
+ `<Step>` components with circle indicators.
62
+
63
+ ```markdown
64
+ :::steps
65
+ 1. **Install the CLI**
66
+ Run `npm install -g dogsbay`.
67
+
68
+ 2. **Initialise**
69
+ Run `dogsbay site init my-docs`.
70
+
71
+ 3. **Preview**
72
+ Run `dogsbay site dev` and open localhost:4321.
73
+ :::
74
+ ```
75
+
76
+ The numbered-list inside is plain markdown. Bold the step's
77
+ short verb, then a paragraph or code block of detail.
78
+
79
+ ## Tabs
80
+
81
+ Side-by-side content using a definition-list inside `:::tabs`.
82
+
83
+ ```markdown
84
+ :::tabs
85
+ TypeScript
86
+ : ```ts
87
+ const x = 1;
88
+ ```
89
+
90
+ Python
91
+ : ```py
92
+ x = 1
93
+ ```
94
+
95
+ Curl
96
+ : ```bash
97
+ curl https://example.com/api
98
+ ```
99
+ :::
100
+ ```
101
+
102
+ The tab label is the term; the tab content is the definition
103
+ (`: …` with three spaces or a tab). Multi-line content is
104
+ allowed; indent further lines to align with the first.
105
+
106
+ ## Cards
107
+
108
+ Bulleted list of bold-link entries inside `:::cards`. Renders as
109
+ a responsive grid.
110
+
111
+ ```markdown
112
+ :::cards
113
+ - **[Getting started](./getting-started)** {icon="rocket"}
114
+ Three-minute orientation to authoring.
115
+
116
+ - **[API reference](./api/)** {icon="book-open"}
117
+ Endpoint browser, schemas, code samples.
118
+
119
+ - **[GitHub](https://github.com/your-org/repo)** {icon="github"}
120
+ Star, browse, file an issue.
121
+ :::
122
+ ```
123
+
124
+ Each item: `- **[Title](href)**` optionally followed by an
125
+ attribute block (`{icon="..."}`), then the description on the
126
+ next line, indented two spaces. Optional blank line between
127
+ items improves readability.
128
+
129
+ ### Icons on cards
130
+
131
+ `icon` accepts any name from the platform Icon component
132
+ (see `dogsbay:theme-tokens` for the Icon component reference):
133
+
134
+ - **Bare names** default to Lucide:
135
+ `{icon="rocket"}`, `{icon="book-open"}`, `{icon="github"}`.
136
+ Browse the catalog at <https://lucide.dev/icons/>.
137
+ - **`pack:name`** routes to other Iconify packs:
138
+ `{icon="mdi:home"}` (Material Design Icons),
139
+ `{icon="simple-icons:github"}` (brand glyphs),
140
+ `{icon="lucide:book-open"}` (explicit Lucide).
141
+ - **Emoji shortcodes** map to Unicode characters when no
142
+ Iconify pack is given: `{icon="rocket"}` → 🚀,
143
+ `{icon="tada"}` → 🎉. The emoji map wins for bare names that
144
+ match it.
145
+
146
+ When the name doesn't resolve in any pack, the icon slot
147
+ renders nothing (silent fallback) — better than a broken-image
148
+ placeholder, since the title and description still convey the
149
+ card's intent.
150
+
151
+ ## Single card (with attrs or YAML body)
152
+
153
+ For a standalone card with rich props, use the singular `:::card`
154
+ directive:
155
+
156
+ ```markdown
157
+ :::card{title="Pro plan" href="/pricing/pro"}
158
+ For growing teams.
159
+ :::
160
+ ```
161
+
162
+ When prop count exceeds 3 OR any value contains a newline, the
163
+ serializer switches to YAML body form:
164
+
165
+ ```markdown
166
+ :::card
167
+ ---
168
+ title: Pro plan
169
+ description: |
170
+ For growing teams.
171
+ Includes priority support.
172
+ href: /pricing/pro
173
+ icon: rocket
174
+ ---
175
+ :::
176
+ ```
177
+
178
+ Both forms are accepted by the parser.
179
+
180
+ ## Link card (`:::link-card`)
181
+
182
+ A focused variant of `:::card` for "go here next" navigation
183
+ affordances. Renders a chevron arrow on the right and a
184
+ distinctive hover state — visually clearer that it's a link
185
+ rather than a content tile.
186
+
187
+ ```markdown
188
+ :::link-card{title="Configuration guide" href="/config/"}
189
+ Site name, theme, base path, and per-source settings live in
190
+ dogsbay.config.yml.
191
+ :::
192
+ ```
193
+
194
+ YAML body form when prop count or content grows:
195
+
196
+ ```markdown
197
+ :::link-card
198
+ ---
199
+ title: Configuration guide
200
+ description: |
201
+ Site name, theme, base path, and per-source settings live in
202
+ dogsbay.config.yml.
203
+ href: /config/
204
+ ---
205
+ :::
206
+ ```
207
+
208
+ Props: `title` (required), `href` (required), `description`
209
+ (optional — also accepted as the body paragraph for ergonomic
210
+ authoring). No `icon` slot — link-cards intentionally lean on
211
+ the chevron alone for the link affordance. When you need an
212
+ icon, use `:::card`.
213
+
214
+ ### When to pick which card directive
215
+
216
+ - **`:::cards`** (plural) — list of related links, displayed as
217
+ a responsive grid. Best for "browse next" clusters at the
218
+ end of a page or on a landing page. Items are bulleted-link
219
+ shorthand.
220
+ - **`:::card`** (singular) — one rich card with full prop
221
+ surface (`icon`, `variant`, custom classes, arbitrary body).
222
+ Use when you want a tile with deliberate visual weight.
223
+ - **`:::link-card`** — one card scoped to a single navigation
224
+ link, with chevron arrow. Use for callouts that lead the
225
+ reader somewhere specific ("Continue with Configuration
226
+ guide →").
227
+
228
+ ## Grid
229
+
230
+ Generic responsive grid container with `cols` and `gap` attrs:
231
+
232
+ ```markdown
233
+ :::grid{cols="3" gap="4"}
234
+
235
+ Cell A
236
+
237
+ Cell B
238
+
239
+ Cell C
240
+
241
+ :::
242
+ ```
243
+
244
+ Each cell is plain markdown content (a paragraph, a callout, a
245
+ code block, etc.). Empty lines between cells separate them.
246
+
247
+ ## Details (collapsible)
248
+
249
+ ```markdown
250
+ :::details{title="Show advanced configuration"}
251
+ The expanded content goes here.
252
+
253
+ Multi-paragraph + code blocks + nested directives all work.
254
+ :::
255
+ ```
256
+
257
+ Renders as `<details><summary>Title</summary>...</details>`.
258
+ Closed by default; pass `open="true"` to start expanded.
259
+
260
+ ## Code blocks
261
+
262
+ Triple-backtick fenced blocks with language identifiers. Always
263
+ specify the language for syntax highlighting:
264
+
265
+ ````markdown
266
+ ```bash
267
+ curl -X POST https://api.example.com/v1/pets
268
+ ```
269
+
270
+ ```yaml title="dogsbay.config.yml"
271
+ site:
272
+ name: Example
273
+ ```
274
+ ````
275
+
276
+ Supported attributes on the fence:
277
+
278
+ - `title="..."` — header bar above the code block
279
+ - `lineNumbers` — render line numbers
280
+ - `frame="terminal"` — terminal styling
281
+
282
+ ## Inline directives
283
+
284
+ For inline-level marks, use `:name[text]{attrs}`:
285
+
286
+ ```markdown
287
+ Press :kbd[Ctrl+C] to copy.
288
+ This is :badge[New]{tone="success"} content.
289
+ Click :icon[rocket] to launch, or :icon[mdi:home] to go home.
290
+ ```
291
+
292
+ `:icon[name]` accepts the same shorthand as the `{icon=...}`
293
+ attribute on cards — bare names default to Lucide,
294
+ `pack:name` routes to other Iconify packs, emoji shortcodes
295
+ render as Unicode. See "Icons on cards" above.
296
+
297
+ ## Frontmatter
298
+
299
+ Every page should start with YAML frontmatter delimited by `---`:
300
+
301
+ ```markdown
302
+ ---
303
+ title: Configuration
304
+ description: How to configure the site via dogsbay.config.yml.
305
+ ---
306
+
307
+ # Configuration
308
+
309
+
310
+ ```
311
+
312
+ See `dogsbay:frontmatter-fields` for the platform-recognised
313
+ fields.
314
+
315
+ ## Common mistakes
316
+
317
+ - ❌ Using `<Cards>` JSX — Dogsbay markdown is NOT MDX. Use
318
+ `:::cards` directives instead.
319
+ - ❌ Forgetting the language identifier on code fences — most
320
+ themes won't highlight without it.
321
+ - ❌ Mixing tab definition-list `:` with regular list `-` markers
322
+ — they're different syntactic structures.
323
+ - ❌ Writing card descriptions on the same line as the title —
324
+ must be on the next line, indented two spaces.
325
+ - ❌ Using a non-existent icon name. The icon slot fails
326
+ silently when the name doesn't resolve, so a typo leaves the
327
+ card looking wrong without any error. Verify Lucide names
328
+ against <https://lucide.dev/icons/> or use the `pack:name`
329
+ form to be explicit.