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,392 @@
1
+ ---
2
+ name: dogsbay:taxonomy-config
3
+ description: Configure user-defined taxonomies (tags, types, audience, status) in dogsbay.config.yml. Dogsbay provides the slot, the user defines the categories. Use when adding tagging schemes, custom badges, or facet UI.
4
+ ---
5
+
6
+ # Taxonomy configuration
7
+
8
+ Dogsbay does **not** prescribe what categories a doc site uses
9
+ for tagging. It provides:
10
+
11
+ - A frontmatter slot for tag values
12
+ - A config slot (`taxonomies:`) for declaring tag schemes
13
+ - Audit rules + display components that consume the schemes
14
+ - Browse pages (`/tags/`, `/by-type/`, etc.) per declared
15
+ taxonomy
16
+
17
+ What goes IN the schemes is the user's call. Dogsbay's role is
18
+ to make the slot work consistently.
19
+
20
+ ## Where taxonomy values live
21
+
22
+ Per-page in frontmatter:
23
+
24
+ ```yaml
25
+ ---
26
+ title: Configuring environments
27
+ description: …
28
+ type: how-to # built-in field (treated as a taxonomy)
29
+ status: stable # built-in
30
+ audience: [developer, ops] # built-in
31
+ tags: # free-form tag list
32
+ - concept/environments
33
+ - difficulty/intermediate
34
+ - kind/configuration
35
+ ---
36
+ ```
37
+
38
+ Five built-in field names (`tags`, `type`, `status`, `audience`,
39
+ `category`) are treated as taxonomies if declared in
40
+ `dogsbay.config.yml`. Custom names work too — just declare them
41
+ in the config.
42
+
43
+ ## Declaring a taxonomy
44
+
45
+ In `dogsbay.config.yml`:
46
+
47
+ ```yaml
48
+ taxonomies:
49
+ tags:
50
+ indexPath: /tags # browse page (default: /<name>)
51
+ prefixes: # display config per top-level slug prefix
52
+ concept:
53
+ label: Concept
54
+ color: blue
55
+ difficulty:
56
+ label: Difficulty
57
+ color: amber
58
+ kind:
59
+ label: Kind
60
+ color: emerald
61
+ labels: # display label overrides per slug
62
+ "concept/a11y": Accessibility # /tags/concept/a11y → "Concept: Accessibility"
63
+ "difficulty/intermediate": Intermediate
64
+ type:
65
+ indexPath: /by-type
66
+ values: # closed list (audit rule fails on unknown values)
67
+ - tutorial
68
+ - how-to
69
+ - reference
70
+ - explanation
71
+ status:
72
+ indexPath: /by-status
73
+ values: [draft, preview, stable, deprecated]
74
+ audience:
75
+ indexPath: /by-audience
76
+ values: [developer, ops, security, end-user]
77
+ ```
78
+
79
+ Each taxonomy declaration produces:
80
+
81
+ - A browse index at `/tags/`, `/by-type/`, etc.
82
+ - Per-term pages (`/tags/concept/a11y`)
83
+ - A faceted-search checkbox group in the `Cmd+K` search dialog,
84
+ emitted automatically as `data-pagefind-filter="<name>:<value>"`
85
+ divs on every page that declares the taxonomy
86
+
87
+ ## Discovery model — chips vs. facets vs. browse
88
+
89
+ Taxonomies play two distinct roles in a doc site:
90
+
91
+ - **Associative tags** — answer *"what other pages are like this
92
+ one?"*. Primary mode: chips on the article header, link to
93
+ related pages. The `tags:` taxonomy is the canonical example.
94
+ - **Descriptive metadata** — answers *"what kind of doc is this,
95
+ is it for me?"*. Primary mode: faceted search + browse-by-axis
96
+ pages. `audience`, `difficulty`, `team`, `status`, `type` all
97
+ fit here.
98
+
99
+ Dogsbay surfaces them as follows:
100
+
101
+ | Surface | Built-ins (tags, type, status) | Custom taxonomies (difficulty, team, …) |
102
+ |---|---|---|
103
+ | Chips on article header | ✅ rendered | ❌ not rendered (use a custom layout to add) |
104
+ | Pagefind facet checkbox | ✅ automatic | ✅ automatic |
105
+ | Browse pages (`/tags/`, `/by-difficulty/`) | ✅ from `indexPath` | ✅ from `indexPath` |
106
+ | Sidebar nav entry | ❌ writer adds via `nav.yml` | ❌ writer adds via `nav.yml` |
107
+
108
+ The chip cluster on the page header deliberately stays small —
109
+ adding every taxonomy as a chip would crowd the article. Search
110
+ facets are the discovery path for descriptive metadata, and
111
+ browse pages are the catalogue path. Add browse pages to the
112
+ sidebar nav (see "Surfacing browse pages" below) when discovery
113
+ matters.
114
+
115
+ ## Faceted search comes free
116
+
117
+ Every taxonomy declared in `dogsbay.config.yml` automatically
118
+ appears as a checkbox group in the `Cmd+K` search dialog. No
119
+ extra config — declaring it produces the `<div
120
+ data-pagefind-filter="<name>:<value>">` markers on every
121
+ matching page, Pagefind discovers them at index time, and the
122
+ search UI renders one column per taxonomy with checkboxes per
123
+ distinct value, sorted by frequency.
124
+
125
+ Display labels for the checkboxes flow from
126
+ `taxonomies.<name>.prefixes` and `taxonomies.<name>.labels` (the
127
+ same display config that drives chips). When unset, raw slugs
128
+ appear ("intermediate", "developer").
129
+
130
+ ```yaml
131
+ taxonomies:
132
+ difficulty:
133
+ indexPath: /by-difficulty
134
+ values: [beginner, intermediate, advanced]
135
+ prefixes:
136
+ beginner: { label: Beginner, color: emerald }
137
+ intermediate: { label: Intermediate, color: amber }
138
+ advanced: { label: Advanced, color: rose }
139
+ ```
140
+
141
+ Above declaration ⇒ search dialog grows a "Difficulty" column
142
+ with three labelled checkboxes, with counts.
143
+
144
+ ## Surfacing browse pages
145
+
146
+ Browse pages (`/by-difficulty/`, `/by-audience/`, etc.) exist
147
+ the moment you declare a taxonomy with an `indexPath`, but
148
+ nothing links to them by default. The standard pattern is a
149
+ "Browse" group at the bottom of `nav.yml`:
150
+
151
+ ```yaml
152
+ # nav.yml
153
+ - Guides:
154
+ - Getting started: /getting-started
155
+ - Configuration: /config
156
+ - Browse:
157
+ - By type: /by-type/
158
+ - By audience: /by-audience/
159
+ - By difficulty: /by-difficulty/
160
+ - Tags: /tags/
161
+ ```
162
+
163
+ Nav entries accept absolute paths to internal pages. The
164
+ "Browse" group is conventional, not enforced — pick the IA that
165
+ fits the site.
166
+
167
+ ## Hierarchical tags via slug prefixes
168
+
169
+ Tags can have slash-separated prefixes that the display layer
170
+ treats as namespaces:
171
+
172
+ ```yaml
173
+ tags:
174
+ - concept/accessibility
175
+ - concept/observability
176
+ - difficulty/beginner
177
+ - difficulty/advanced
178
+ ```
179
+
180
+ Renders as two-part chips: `Concept: Accessibility`,
181
+ `Difficulty: Beginner`. The prefix maps to a colour via
182
+ `taxonomies.tags.prefixes.<prefix>.color`; the leaf maps to a
183
+ human label via `taxonomies.tags.labels.<full-slug>`.
184
+
185
+ This is the slot for "we want categories of tags but don't want
186
+ to invent five separate frontmatter fields." Slash separation
187
+ is the convention; the parser doesn't enforce it.
188
+
189
+ ### `hierarchical:` controls prefix browsing
190
+
191
+ When `hierarchical: true`, declaring tag `concept/rag` also
192
+ emits a `/tags/concept/` browse page that aggregates every
193
+ `concept/*` tag. Term-page breadcrumbs (`Tags / Concept / RAG`)
194
+ become clickable.
195
+
196
+ When `hierarchical: false`, only leaf-tag pages (`/tags/concept/rag/`)
197
+ exist; the prefix is purely visual styling on chips.
198
+
199
+ **The default is auto-derived:** when you declare `prefixes:`,
200
+ `hierarchical` defaults to `true` (the writer signalled they
201
+ want prefixes to be real browsable categories). When
202
+ `prefixes:` is absent, `hierarchical` defaults to `false`. Set
203
+ the flag explicitly to override.
204
+
205
+ ```yaml
206
+ # Auto-hierarchical — prefixes declared, /tags/concept/ exists
207
+ taxonomies:
208
+ tags:
209
+ prefixes:
210
+ concept: { color: blue }
211
+
212
+ # Flat — prefixes are pure styling, /tags/concept/ does NOT exist
213
+ taxonomies:
214
+ tags:
215
+ hierarchical: false
216
+ prefixes:
217
+ concept: { color: blue }
218
+ ```
219
+
220
+ ## Closed-list taxonomies
221
+
222
+ `values:` makes a taxonomy closed — pages declaring a value
223
+ outside the list fire the `taxonomy/unknown-value` audit rule.
224
+ Use for taxonomies where you want type-safety:
225
+
226
+ ```yaml
227
+ taxonomies:
228
+ status:
229
+ values: [draft, preview, stable, deprecated]
230
+ ```
231
+
232
+ A page with `status: production` fails `dogsbay site check`.
233
+
234
+ Without `values:`, the taxonomy is open — any string is
235
+ accepted. Useful for free-form tags.
236
+
237
+ ### `status: draft` is special-cased
238
+
239
+ The `draft` value of the `status` taxonomy is **also** a
240
+ build-time visibility gate — equivalent to `draft: true` in
241
+ frontmatter. A page with `status: draft` is dropped from
242
+ `dogsbay site build` (production) and the build line prints
243
+ "(N drafts excluded)". `dogsbay site dev` always includes
244
+ drafts so the writer can preview; `--include-drafts` is the
245
+ escape hatch for production builds.
246
+
247
+ The other three values (`preview`, `stable`, `deprecated`) are
248
+ pure taxonomy — they render as a `<StatusBadge>` chip and
249
+ populate `/by-status/`, but don't gate visibility.
250
+
251
+ This is the only built-in taxonomy with overloaded semantics.
252
+ If a writer wants `draft` to be only-a-taxonomy (no
253
+ exclusion), the workaround is to use a different value name
254
+ (e.g. `status: in-progress`) and customize the badge labels via
255
+ `taxonomies.status.labels`.
256
+
257
+ **Agents should not proactively stamp `status: draft` on new
258
+ pages.** In a git-backed site (the common case), in-progress
259
+ work is already isolated by branch — the page is a draft
260
+ because the PR isn't merged, not because of frontmatter. Adding
261
+ `status: draft` then layers an invisible build-time gate on
262
+ top of the branch isolation, so when the PR merges the page
263
+ *still* doesn't ship until someone notices and removes the
264
+ frontmatter. That's a confusion trap.
265
+
266
+ Use `status:` only when:
267
+
268
+ 1. The writer explicitly asked for it ("mark this draft").
269
+ 2. There's no git workflow (single-author site editing
270
+ directly on `main`).
271
+ 3. The page is a long-lived `preview` / `deprecated` /
272
+ `stable` lifecycle marker (not draft).
273
+
274
+ Default to no `status` field on new pages. Branches handle
275
+ draft state.
276
+
277
+ ## Display palette
278
+
279
+ The closed colour palette for prefix chips and badges:
280
+
281
+ `blue`, `amber`, `emerald`, `violet`, `rose`, `slate`
282
+
283
+ These resolve to theme tokens — they look correct in any theme
284
+ the site uses. Don't pass arbitrary hex codes here; the palette
285
+ is intentional.
286
+
287
+ ## Audit rule integration
288
+
289
+ `dogsbay site check` ships a few taxonomy-related rules
290
+ (currently in the `seo/` and (planned) `taxonomy/` categories):
291
+
292
+ - `seo/missing-tags` — page has no tags (configurable threshold)
293
+ - `taxonomy/unknown-value` — frontmatter value not in declared
294
+ `values:`
295
+ - `taxonomy/orphan-term` — taxonomy term page exists but no page
296
+ references it
297
+ - `taxonomy/duplicate-display-label` — two slugs map to the same
298
+ display string
299
+
300
+ Plugins and patterns can register additional taxonomy-aware
301
+ rules.
302
+
303
+ ## Browse + facet UI
304
+
305
+ `<TaxonomyIndex>` and `<TaxonomyTerm>` components render the
306
+ browse pages — they read `siteConfig.taxonomyDisplay` and
307
+ `siteConfig.taxonomyIndexPaths` (both populated from the config
308
+ at build time).
309
+
310
+ `<TagList>` renders chip-style tags in page headers. Reads the
311
+ prefix colours and label overrides.
312
+
313
+ `<SearchFacets>` renders facet checkboxes ("Concept:
314
+ Accessibility", "How-to") — reads the same display config.
315
+
316
+ ## Per-page taxonomy values flow
317
+
318
+ ```
319
+ content/page.md frontmatter
320
+ ↓ (parsed by importer)
321
+ ExportPage.meta.taxonomies = { tags: [...], type: ..., ... }
322
+ ↓ (rendered into page)
323
+ data-pagefind-filter attributes (search facets)
324
+ TagList component (visual chips)
325
+ TaxonomyIndex pages (browse)
326
+ ```
327
+
328
+ Each step is platform-mechanical. The categories (concept,
329
+ difficulty, audience values, etc.) are user-defined.
330
+
331
+ ## Common patterns
332
+
333
+ ### Diátaxis-style content typing
334
+
335
+ When `@dogsbay/pattern-diataxis` is installed, it pre-declares:
336
+
337
+ ```yaml
338
+ taxonomies:
339
+ type:
340
+ values: [tutorial, how-to, reference, explanation]
341
+ prefixes:
342
+ tutorial: { color: emerald }
343
+ "how-to": { color: blue }
344
+ reference: { color: amber }
345
+ explanation: { color: violet }
346
+ ```
347
+
348
+ The pattern owns the values; the user can extend with their own.
349
+
350
+ ### Custom team taxonomies
351
+
352
+ ```yaml
353
+ taxonomies:
354
+ team:
355
+ values: [platform, billing, identity, search]
356
+ indexPath: /by-team
357
+ release:
358
+ indexPath: /releases
359
+ ```
360
+
361
+ Each team owns a subset of pages. The `/by-team/platform/` page
362
+ lists every page that declares `team: platform` in frontmatter.
363
+
364
+ ### Free-form tags only
365
+
366
+ ```yaml
367
+ taxonomies:
368
+ tags:
369
+ indexPath: /tags
370
+ prefixes:
371
+ concept: { color: blue }
372
+ kind: { color: emerald }
373
+ ```
374
+
375
+ No `values:` — any tag is valid. Just hierarchical display
376
+ config for the prefixes the writer uses.
377
+
378
+ ## Common mistakes
379
+
380
+ - ❌ Putting tag display config (colours, labels) in
381
+ per-page frontmatter — that's site-wide, belongs in
382
+ `dogsbay.config.yml`. Frontmatter just lists which taxonomies
383
+ apply to this page.
384
+ - ❌ Using `tagPrefixes` directly in `dogsbay.config.yml` —
385
+ legacy field, deprecated. Use
386
+ `taxonomies.tags.prefixes` instead.
387
+ - ❌ Mixing prefix-based and free-form tags inconsistently —
388
+ pick one approach per taxonomy. If `tags` uses `concept/...`
389
+ prefixes, every tag should follow that pattern.
390
+ - ❌ Declaring `values: [draft, …]` and then using `Draft` (with
391
+ capital) in frontmatter — values match exactly; case
392
+ matters.
@@ -0,0 +1,276 @@
1
+ ---
2
+ name: dogsbay:theme-tokens
3
+ description: The 60 design tokens Dogsbay ships, how theme.css works, and how to override colours, fonts, radius, or swap themes wholesale. Use when customising visual design or troubleshooting theming issues.
4
+ ---
5
+
6
+ # Theme tokens
7
+
8
+ Dogsbay ships ~60 design tokens grouped by purpose. They live
9
+ in `astro/src/styles/theme.css` (scaffold-once — yours to edit
10
+ freely) and are consumed by the 72 components via Tailwind's
11
+ `@theme` directive.
12
+
13
+ Two themes ship out of the box:
14
+
15
+ - `default` — shadcn-zinc-style (clean, neutral)
16
+ - `material` — Material-Design-inspired (denser, more chromed)
17
+
18
+ Set in `dogsbay.config.yml`:
19
+
20
+ ```yaml
21
+ theme: default # or "material"
22
+ ```
23
+
24
+ ## The token groups
25
+
26
+ ### Shadcn baseline (25 tokens)
27
+
28
+ Standard shadcn UI tokens. Every component reads from these:
29
+
30
+ ```css
31
+ :root {
32
+ --background: oklch(1 0 0);
33
+ --foreground: oklch(0.145 0.014 285.823);
34
+ --card: oklch(1 0 0);
35
+ --card-foreground: …;
36
+ --popover, --popover-foreground;
37
+ --primary, --primary-foreground;
38
+ --secondary, --secondary-foreground;
39
+ --muted, --muted-foreground;
40
+ --accent, --accent-foreground;
41
+ --destructive, --destructive-foreground;
42
+ --border;
43
+ --input;
44
+ --ring;
45
+ --radius: 0.625rem;
46
+ }
47
+ ```
48
+
49
+ Override any of these to reskin the whole site. Want emerald
50
+ buttons? Set `--primary` to an emerald oklch value; every
51
+ component using `bg-primary` updates.
52
+
53
+ ### Sidebar (8 tokens)
54
+
55
+ `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`,
56
+ `--sidebar-primary-foreground`, `--sidebar-accent`,
57
+ `--sidebar-accent-foreground`, `--sidebar-border`,
58
+ `--sidebar-ring`.
59
+
60
+ These let the sidebar diverge from the main content panel —
61
+ useful for sites with a darker nav bar over a lighter content
62
+ area.
63
+
64
+ ### Admonitions (13 tokens)
65
+
66
+ One per callout type:
67
+
68
+ ```css
69
+ --note: oklch(0.637 0.174 259.126);
70
+ --abstract: …;
71
+ --info: …;
72
+ --tip: …;
73
+ --success: …;
74
+ --question: …;
75
+ --warning: …;
76
+ --failure: …;
77
+ --danger: …;
78
+ --bug: …;
79
+ --example: …;
80
+ --quote: oklch(0.65 0 0);
81
+ ```
82
+
83
+ `--note-foreground` is also exposed for callouts that need
84
+ explicit text contrast.
85
+
86
+ ### Code (3 tokens)
87
+
88
+ `--code-background`, `--code-foreground`, `--font-code`.
89
+
90
+ The `<CodeBlock>` and inline `<code>` components read these.
91
+ Default to a slightly-different background from `--background`
92
+ so code stands out.
93
+
94
+ ### API panel (2 tokens)
95
+
96
+ `--api-panel-bg`, `--api-panel-fg`.
97
+
98
+ The dark right-column panel on OpenAPI endpoint pages.
99
+ **Always dark**, regardless of light/dark mode — it's a stylistic
100
+ choice that matches Stripe / Mintlify API doc convention.
101
+
102
+ ### API semantic (17 tokens)
103
+
104
+ HTTP methods, type badges, status codes, role indicators —
105
+ everything `<MethodBadge>`, `<EndpointCard>`, `<ParameterTable>`,
106
+ `<ResponseTabs>`, `<SchemaViewer>`, etc. consume:
107
+
108
+ ```css
109
+ --api-get: oklch(0.723 0.15 162); /* green */
110
+ --api-post: oklch(0.637 0.174 259); /* blue */
111
+ --api-put: oklch(0.74 0.175 63); /* orange */
112
+ --api-patch: oklch(0.7 0.18 45); /* amber */
113
+ --api-delete: oklch(0.614 0.194 22); /* red */
114
+ --api-head: oklch(0.534 0.222 286); /* violet */
115
+ --api-required: oklch(0.614 0.194 22); /* red */
116
+ --api-deprecated: oklch(0.74 0.175 63); /* orange */
117
+ --api-type-string: oklch(0.723 0.15 162);
118
+ --api-type-number: oklch(0.637 0.174 259);
119
+ --api-type-boolean: oklch(0.74 0.175 63);
120
+ --api-type-object: oklch(0.534 0.222 286);
121
+ --api-type-array: oklch(0.7 0.18 45);
122
+ --api-status-2xx: oklch(0.723 0.15 162);
123
+ --api-status-3xx: oklch(0.637 0.174 259);
124
+ --api-status-4xx: oklch(0.74 0.175 63);
125
+ --api-status-5xx: oklch(0.614 0.194 22);
126
+ ```
127
+
128
+ These are present in the scaffold even if the user isn't
129
+ shipping OpenAPI yet — adding an OpenAPI source later doesn't
130
+ require theme.css changes.
131
+
132
+ ### Fonts (3 tokens)
133
+
134
+ `--font-sans`, `--font-heading`, `--font-code`.
135
+
136
+ Defaults are system fonts. Override at the top of `theme.css`:
137
+
138
+ ```css
139
+ :root {
140
+ --font-sans: "Inter", system-ui, sans-serif;
141
+ --font-heading: "Inter Display", "Inter", sans-serif;
142
+ --font-code: "JetBrains Mono", ui-monospace, monospace;
143
+ }
144
+ ```
145
+
146
+ ## Light vs dark
147
+
148
+ `theme.css` defines `:root { ... }` for light mode and `.dark
149
+ { ... }` for dark mode. The user's OS preference + the
150
+ `<ThemeToggle>` component switch which class is applied to
151
+ `<html>`.
152
+
153
+ Dark-mode tokens override the same names with darker values.
154
+ Both modes share radius, fonts, and the dark API panel
155
+ (intentionally constant — see "API panel" above).
156
+
157
+ ## Override patterns
158
+
159
+ ### Tweak one colour
160
+
161
+ Edit `theme.css`:
162
+
163
+ ```css
164
+ :root {
165
+ --primary: oklch(0.5 0.18 145); /* greenish primary */
166
+ }
167
+
168
+ .dark {
169
+ --primary: oklch(0.65 0.16 145); /* lighter green for dark mode */
170
+ }
171
+ ```
172
+
173
+ ### Swap themes wholesale
174
+
175
+ `global.css` imports `theme.css`. To switch themes, change the
176
+ import:
177
+
178
+ ```css
179
+ /* Was: */
180
+ @import "./theme.css";
181
+
182
+ /* Now: */
183
+ @import "./theme-material.css";
184
+ ```
185
+
186
+ Both files ship by default. `theme-material.css` lives in the
187
+ scaffold alongside `theme.css` for easy A/B-ing.
188
+
189
+ ### Custom theme from scratch
190
+
191
+ Copy `theme.css` to `theme-mybrand.css`, edit, swap the import.
192
+
193
+ The recommended editing path: keep the structure (light root +
194
+ dark class + same token names), change values. Components find
195
+ the tokens by name — renaming a token breaks the components.
196
+
197
+ ## Tailwind config consumption
198
+
199
+ `@theme inline { --color-primary: var(--primary); ... }` in
200
+ `theme.css` makes every token usable as a Tailwind utility:
201
+
202
+ - `bg-primary` → `var(--primary)`
203
+ - `text-api-get` → `var(--api-get)`
204
+ - `border-sidebar-border`
205
+ - etc.
206
+
207
+ This is why the components use `class="bg-api-get/20
208
+ text-api-get"` and Just Work in any theme. The tokens are the
209
+ contract; the colours are the override surface.
210
+
211
+ ## Typography
212
+
213
+ Two opt-in typography enhancements (pulled from theme tokens):
214
+
215
+ - `font-heading` — applied to `<h1>` / `<h2>` / `<h3>` if
216
+ `--font-heading` differs from `--font-sans`. Set both to
217
+ the same value for "headings look like body".
218
+ - `font-code` — applied to `<pre>`, `<code>`, syntax-highlighted
219
+ blocks.
220
+
221
+ ## Common patterns
222
+
223
+ ### Default theme (no edits)
224
+
225
+ ```yaml
226
+ theme: default
227
+ ```
228
+
229
+ `theme.css` ships, you don't touch it. Works for 80% of sites.
230
+
231
+ ### Brand-tinted defaults
232
+
233
+ Edit just `--primary` + `--accent` to brand colours. Leave the
234
+ rest alone. The site looks "yours" without 50 lines of CSS.
235
+
236
+ ### Dark-only or light-only
237
+
238
+ The scaffold supports both modes via the `.dark` class. To
239
+ force dark-only:
240
+
241
+ ```css
242
+ :root,
243
+ .dark {
244
+ /* dark token values */
245
+ }
246
+ ```
247
+
248
+ (The `<ThemeToggle>` component still exists; it just toggles
249
+ between two identical themes. Hide it via DocsHeader prop if
250
+ preferred.)
251
+
252
+ ### Swap to Material
253
+
254
+ ```yaml
255
+ theme: material
256
+ ```
257
+
258
+ Replace `default` with `material` in the config; `dogsbay site
259
+ init` re-emits `global.css` with the right import.
260
+
261
+ ## Common mistakes
262
+
263
+ - ❌ Using arbitrary Tailwind colours (`bg-emerald-500`) in
264
+ scaffolded components — bypasses the token system, breaks
265
+ theme switching. Use `bg-primary` / `bg-api-get` / etc.
266
+ - ❌ Editing tokens INSIDE `:root` that are meant to be derived
267
+ from another (`--ring` is supposed to follow `--primary` in
268
+ the default theme; overriding directly produces inconsistent
269
+ state).
270
+ - ❌ Setting tokens via inline `style="..."` on individual
271
+ elements — works but defeats the cascade. Update `theme.css`
272
+ instead.
273
+ - ❌ Forgetting that the API panel is always dark — overriding
274
+ `--api-panel-bg` to a light value makes white text on white
275
+ background. The panel is intentionally a constant; reskin
276
+ individual API tokens (`--api-get`, etc.) instead.