docpensieve 0.4.0-beta.2 → 0.5.0-beta.1

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.
Files changed (33) hide show
  1. package/bin/docpensieve.js +5 -2
  2. package/package.json +5 -5
  3. package/src/commands/build.js +9 -1
  4. package/src/commands/dev.js +9 -1
  5. package/src/commands/init.js +19 -3
  6. package/starter/01-guide/01-installation.md +1 -1
  7. package/starter/01-guide/02-first-site.md +7 -1
  8. package/starter/01-guide/03-writing-pages.md +4 -0
  9. package/starter/01-guide/04-versions.md +4 -0
  10. package/starter/01-guide/05-themes.mdx +47 -13
  11. package/starter/01-guide/07-migrate-to-beta.md +21 -7
  12. package/starter/01-guide/08-navigation.mdx +12 -0
  13. package/starter/01-guide/09-when-it-breaks.md +176 -0
  14. package/starter/01-guide/10-languages.md +160 -0
  15. package/starter/02-components/01-card.mdx +2 -2
  16. package/starter/02-components/02-columns.mdx +3 -0
  17. package/starter/02-components/03-time-timer.mdx +4 -0
  18. package/starter/02-components/04-tooltip.mdx +40 -0
  19. package/starter/02-components/05-tree.mdx +4 -0
  20. package/starter/02-components/06-scroll-to-top.mdx +3 -0
  21. package/starter/02-components/07-skill.mdx +30 -0
  22. package/starter/02-components/08-logo-icon.mdx +24 -0
  23. package/starter/02-components/09-for-theme.mdx +3 -0
  24. package/starter/02-components/11-menu.mdx +129 -0
  25. package/starter/02-components/12-admonition.mdx +154 -0
  26. package/starter/03-reference/02-configuration.md +50 -32
  27. package/starter/03-reference/03-frontmatter.md +3 -0
  28. package/starter/03-reference/04-theme.md +80 -69
  29. package/starter/03-reference/05-api.md +158 -12
  30. package/starter/03-reference/index.mdx +3 -0
  31. package/starter/04-architecture.md +3 -0
  32. package/starter/05-whats-new.md +13 -21
  33. package/starter/examples.css +15 -0
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { createRequire } from 'node:module';
10
10
 
11
- import { DocPensieveError, NotImplementedError } from '@docpensieve/shared';
11
+ import { DOCUMENTATION_URL, DocPensieveError, NotImplementedError } from '@docpensieve/shared';
12
12
  import { Command } from 'commander';
13
13
  import chalk from 'chalk';
14
14
 
@@ -21,7 +21,10 @@ const program = new Command();
21
21
  program
22
22
  .name('docpensieve')
23
23
  .description('Static documentation site generator')
24
- .version(version, '-v, --version');
24
+ .version(version, '-v, --version')
25
+ // Someone typing --help is usually stuck: the list of commands alone sends
26
+ // them back where they started.
27
+ .addHelpText('after', `\nDocumentation: ${DOCUMENTATION_URL}`);
25
28
 
26
29
  program
27
30
  .command('init')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docpensieve",
3
- "version": "0.4.0-beta.2",
3
+ "version": "0.5.0-beta.1",
4
4
  "description": "DocPensieve command-line interface (init, build, check, dev, serve)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,10 +24,10 @@
24
24
  "types"
25
25
  ],
26
26
  "dependencies": {
27
- "@docpensieve/components": "0.4.0-beta.2",
28
- "@docpensieve/core": "0.4.0-beta.2",
29
- "@docpensieve/shared": "0.4.0-beta.2",
30
- "@docpensieve/theme": "0.4.0-beta.2",
27
+ "@docpensieve/components": "0.5.0-beta.1",
28
+ "@docpensieve/core": "0.5.0-beta.1",
29
+ "@docpensieve/shared": "0.5.0-beta.1",
30
+ "@docpensieve/theme": "0.5.0-beta.1",
31
31
  "chalk": "^6.0.0",
32
32
  "chokidar": "^5.0.0",
33
33
  "commander": "^15.0.0"
@@ -6,7 +6,12 @@
6
6
 
7
7
  import path from 'node:path';
8
8
 
9
- import { componentsCss, createRegistry, setSiteContext } from '@docpensieve/components';
9
+ import {
10
+ componentsCss,
11
+ createRegistry,
12
+ setSiteContext,
13
+ setAdmonitionKinds,
14
+ } from '@docpensieve/components';
10
15
  import { SiteGenerator, loadConfig, resolveVersion } from '@docpensieve/core';
11
16
 
12
17
  import { createTheme } from '../theme.js';
@@ -21,6 +26,9 @@ export async function build(versionSlug, options = {}) {
21
26
  const config = await loadConfig(cwd);
22
27
  const outDir = path.resolve(cwd, options.out ?? config.outDir);
23
28
 
29
+ // The kinds a project declares, beside the ones shipped.
30
+ setAdmonitionKinds(config.admonitions);
31
+
24
32
  const generator = new SiteGenerator(config, {
25
33
  // `globalComponents: false` removes the shipped components: a project that
26
34
  // defines its own thus avoids a name collision. The option was declared
@@ -7,7 +7,12 @@
7
7
  import { existsSync } from 'node:fs';
8
8
  import path from 'node:path';
9
9
 
10
- import { componentsCss, createRegistry, setSiteContext } from '@docpensieve/components';
10
+ import {
11
+ componentsCss,
12
+ createRegistry,
13
+ setSiteContext,
14
+ setAdmonitionKinds,
15
+ } from '@docpensieve/components';
11
16
  import { SiteGenerator, loadConfig } from '@docpensieve/core';
12
17
  import { CONFIG_FILENAME, DocPensieveError, THEME_FOLDER } from '@docpensieve/shared';
13
18
  import chokidar from 'chokidar';
@@ -54,6 +59,9 @@ export async function dev(options = {}) {
54
59
  // The configuration is read again every time: changing it must show
55
60
  // without restarting the command.
56
61
  const current = await loadConfig(cwd);
62
+ // Read again with the rest: a kind added to the configuration must show
63
+ // without restarting the command.
64
+ setAdmonitionKinds(current.admonitions);
57
65
  const generator = new SiteGenerator(current, {
58
66
  // `globalComponents: false` removes the shipped components: a project
59
67
  // that defines its own thus avoids a name collision. The option was
@@ -14,6 +14,7 @@ import { fileURLToPath } from 'node:url';
14
14
  import {
15
15
  CONFIG_FILENAME,
16
16
  CONFIG_FILENAMES,
17
+ DOCUMENTATION_URL,
17
18
  DocPensieveError,
18
19
  THEME_FOLDER,
19
20
  THEME_FRAMEWORKS,
@@ -64,9 +65,6 @@ const NOT_INSTALLED = new Set([
64
65
  /** Starting point of the project's own stylesheet, under the custom theme. */
65
66
  const CUSTOM_CSS = fileURLToPath(new URL('../templates/custom.css', import.meta.url));
66
67
 
67
- /** Where the generated configuration sends readers for every field. */
68
- const DOCUMENTATION_URL = 'https://docpensieve.com/';
69
-
70
68
  /**
71
69
  * Asks a question, with a default value shown between brackets.
72
70
  *
@@ -228,12 +226,16 @@ function renderConfig({ name, theme, siteUrl, version }) {
228
226
  ' // current the version the site root leads to — at most one',
229
227
  ' // prerelease in preparation: a banner on every page, kept out of search',
230
228
  ' // archived no longer maintained: a banner, still indexed',
229
+ ' // translations a folder per language, served under its code',
231
230
  ' versions: [',
232
231
  ' {',
233
232
  ` slug: ${quote(slug)}, // URL segment, and name of the version's branch`,
234
233
  ` name: ${quote(version)}, // label in the version switcher`,
235
234
  ` folder: ${quote(`docs/${slug}`)},`,
236
235
  ' current: true,',
236
+ ' // Pages of this version in another language, served under /fr/.',
237
+ ' // Your own language stays where it is, and keeps its addresses.',
238
+ ` // translations: { fr: ${quote(`docs/${slug}-fr`)} },`,
237
239
  ' },',
238
240
  ' ],',
239
241
  '',
@@ -288,6 +290,16 @@ function renderConfig({ name, theme, siteUrl, version }) {
288
290
  ' // read. Useful once the documentation is long.',
289
291
  ' foldedSidebar: false,',
290
292
  '',
293
+ ' // Wording of the shell — the menu, the notices, the search field.',
294
+ ' // English and French ship with the tool and follow `lang`; this field',
295
+ " // corrects a word or adds a language: { de: { search: 'Suchen' } }.",
296
+ ' ui: {},',
297
+ '',
298
+ ' // Kinds of admonition, beside the six shipped — note, info, tip,',
299
+ ' // attention, alert and danger. A kind is a label and a tone, which',
300
+ " // carries its colour: { review: { label: 'Review', tone: 'info' } }.",
301
+ ' admonitions: {},',
302
+ '',
291
303
  ' // The shipped components — Card, Columns, Tooltip… — usable in any .mdx',
292
304
  ' // page without an import. false removes them, to use your own names.',
293
305
  ' globalComponents: true,',
@@ -295,6 +307,10 @@ function renderConfig({ name, theme, siteUrl, version }) {
295
307
  ' // Back-to-top button on every page.',
296
308
  ' scrollToTop: true,',
297
309
  '',
310
+ ' // The header stays at the top of the screen. false lets it scroll away',
311
+ ' // with the page, and gives its height back to the text.',
312
+ ' stickyHeader: true,',
313
+ '',
298
314
  ' // Structured data (JSON-LD) generated from the frontmatter of each page.',
299
315
  ' jsonld: { enabled: true },',
300
316
  '',
@@ -99,7 +99,7 @@ The documentation `init` installed in `99-docpensieve` stays at the version it
99
99
  came with. To refresh it, run `init` in a scratch folder and copy that folder
100
100
  over.
101
101
 
102
- Coming from the 0.3, [Migrate from latest to beta](./migrate-to-beta/) says
102
+ Coming from the 0.4, [Migrate from latest to beta](./migrate-to-beta/) says
103
103
  what changes on its own and what to check first. To try this beta, install
104
104
  `docpensieve@beta`, or run `npx docpensieve@beta` alone.
105
105
 
@@ -72,6 +72,11 @@ actually used in order to emit only those.
72
72
  **The `versions.json` at the root** describes the available versions. It is
73
73
  what lets versions built long ago stay online without ever rebuilding them.
74
74
 
75
+ The folder itself is `dist/`, which `outDir` changes — to `public/`, or to
76
+ whatever your host expects. The build empties the folders it writes and leaves
77
+ the rest of that folder alone, so an output folder shared with something else
78
+ keeps what is not its own.
79
+
75
80
  ## Reading the output back
76
81
 
77
82
  A successful build says nothing of a dead link: nothing in the chain looks at
@@ -94,7 +99,8 @@ online.
94
99
  ```
95
100
 
96
101
  It is the check to run after any change of `baseUrl`, of folder structure or of
97
- URL.
102
+ URL. When something does not add up, [When it breaks](./when-it-breaks/) lists
103
+ the failures this tool produces, by the symptom you see.
98
104
 
99
105
  ## Searching the site
100
106
 
@@ -197,6 +197,10 @@ In an `.mdx` page, the shipped components are used **without an import**:
197
197
  They are rendered at build time: the delivered HTML only holds their result.
198
198
  The list is in [Components](../components/).
199
199
 
200
+ `globalComponents: false` removes them from every page. It is meant for a
201
+ project that would rather bring its own: a page still using one then stops the
202
+ build, naming it.
203
+
200
204
  ## A trap to know
201
205
 
202
206
  The content of a JSX tag **left alone on its own line** becomes a paragraph:
@@ -199,3 +199,7 @@ today's generator or today's sources. Rebuilding it is never necessary, and
199
199
  nothing would guarantee it gives the same result.
200
200
 
201
201
  The sources stay on the working branch. The two never mix.
202
+
203
+ What the day of a release looks like from the outside — the addresses, the
204
+ pipeline, the check that stops a bad publication — is in
205
+ [Deployment](./deployment/).
@@ -26,12 +26,43 @@ styles the site, with no styling dependency.
26
26
  The templates are the same in both cases. Switching values requires touching
27
27
  no page.
28
28
 
29
+ ## The images of the site
30
+
31
+ Three images carry the identity of a project, and none of them is a stylesheet
32
+ matter — they are declared once, from the root of the project:
33
+
34
+ ```js
35
+ logo: 'branding/logo.png',
36
+ favicon: 'branding/favicon.png',
37
+ socialImage: 'branding/social.jpg',
38
+ ```
39
+
40
+ | Field | Where it shows | What to give it |
41
+ | ------------- | ------------------------------ | ------------------------------------------------------------------------------------- |
42
+ | `logo` | In the header, beside the name | Any web image. A detailed logo reads badly at header size: crop the mark alone |
43
+ | `favicon` | In the browser tab | `.ico`, `.png` or `.svg` — nothing else is shown by browsers |
44
+ | `socialImage` | The preview of a shared link | A PNG or JPEG, 1200 × 630 pixels as a rule. Social networks read neither SVG nor AVIF |
45
+
46
+ A version may carry its own `logo` and `favicon`, which is how a beta tells
47
+ itself apart at a glance in the tab.
48
+
49
+ `socialImage` needs `siteUrl`, and the build refuses the pair when it is
50
+ missing rather than publish a preview nobody can fetch:
51
+
52
+ ```
53
+ socialImage needs siteUrl.
54
+ Social networks only read an absolute address: set siteUrl, the public address of the site.
55
+ ```
56
+
57
+ A preview is fetched by another machine, from the outside: a path that works in
58
+ your browser means nothing to it.
59
+
29
60
  ## The principle: slots, not classes
30
61
 
31
62
  The templates **hard-code no class**. They ask the theme for the class of each
32
63
  slot — the header, the menu, a navigation link — and the theme answers.
33
64
 
34
- The twenty-four slots are listed in the [reference](../reference/theme/).
65
+ The fifty slots are listed in the [reference](../reference/theme/).
35
66
 
36
67
  That is what lets a utility styling and a classic styling share the same HTML:
37
68
  one answers `dp-nav-link`, the other a handful of utilities. The template
@@ -43,7 +74,7 @@ they therefore follow the active palette without knowing anything about it.
43
74
 
44
75
  ## Changing the colours
45
76
 
46
- The fifteen tokens are listed in the [reference](../reference/theme/). They are
77
+ The tokens are listed in the [reference](../reference/theme/). They are
47
78
  redefined from the configuration:
48
79
 
49
80
  ```js
@@ -56,19 +87,22 @@ theme: {
56
87
  },
57
88
  ```
58
89
 
59
- | Token | What it sets |
60
- | -------------------------------------- | -------------------------------------------------------------- |
61
- | `--dp-bg`, `--dp-bg-soft` | Backgrounds |
62
- | `--dp-text`, `--dp-text-soft` | Text |
63
- | `--dp-border`, `--dp-rule` | Borders and rules |
64
- | `--dp-accent`, `--dp-accent-soft` | Accent colour |
65
- | `--dp-radius` | Corner rounding |
66
- | `--dp-font`, `--dp-font-mono` | Font families |
67
- | `--dp-content-width` | Reading width. `none` by default: the content fills its column |
68
- | `--dp-sidebar-width`, `--dp-toc-width` | Side columns |
90
+ | Token | What it sets |
91
+ | --------------------------------------------------------------- | -------------------------------------------------------------- |
92
+ | `--dp-bg`, `--dp-bg-soft` | Backgrounds |
93
+ | `--dp-text`, `--dp-text-soft` | Text |
94
+ | `--dp-border`, `--dp-rule` | Borders and rules |
95
+ | `--dp-accent`, `--dp-accent-soft` | Accent colour |
96
+ | `--dp-tip`, `--dp-attention`, `--dp-danger` (and their `-soft`) | Tones of the blocks set apart from the text |
97
+ | `--dp-radius` | Corner rounding |
98
+ | `--dp-font`, `--dp-font-mono` | Font families |
99
+ | `--dp-content-width` | Reading width. `none` by default: the content fills its column |
100
+ | `--dp-sidebar-width`, `--dp-toc-width` | Side columns |
69
101
 
70
102
  A redefined token propagates everywhere: components included, without any of
71
- them having to know.
103
+ them having to know. It sets the **light** palette; the dark one is a set of
104
+ tokens of its own, redefined in the `theme/` folder — see below. A site held
105
+ dark by `darkMode: 'dark'` therefore shows nothing of a token set here.
72
106
 
73
107
  ## Adding CSS
74
108
 
@@ -1,13 +1,17 @@
1
1
  ---
2
2
  title: Migrate from latest to beta
3
- description: Move a project from the latest version, the 0.3, to the 0.4 beta — what changes on its own, and what to check.
3
+ description: Move a project from the latest version, the 0.4, to the 0.5 beta — what changes on its own, and what to check.
4
4
  tags: [guide, migration]
5
+
6
+ jsonld:
7
+ type: TechArticle
8
+ breadcrumbs: true
5
9
  ---
6
10
 
7
11
  # Migrate from latest to beta
8
12
 
9
- A project on the latest version — the 0.3 — builds with the 0.4 beta as it is:
10
- every new field is optional.
13
+ A project on the latest version — the 0.4 — builds with the 0.5 beta as it is:
14
+ every field the 0.5 adds is optional.
11
15
 
12
16
  ## Update
13
17
 
@@ -18,10 +22,20 @@ npm install docpensieve@beta
18
22
  Through `npx` alone, `npx docpensieve@beta` runs the beta. Going back is
19
23
  `npm install docpensieve@latest`.
20
24
 
25
+ Read your site back once after the update — it is the cheapest check there is:
26
+
27
+ ```bash
28
+ npx docpensieve build
29
+ npx docpensieve check
30
+ ```
31
+
21
32
  ## What changes on its own
22
33
 
23
- Nothing yet: the 0.4 has just opened. Each change is listed here as it lands,
24
- beside what it asks of a project already built on the 0.3.
34
+ Nothing yet: the 0.5 has just opened. Each change is listed here as it lands,
35
+ beside what it asks of a project already built on the 0.4.
36
+
37
+ ## What to check afterwards
25
38
 
26
- One thing changes on its own: on a narrow screen, the menu of the documentation
27
- now folds above the content instead of standing open. Nothing to configure.
39
+ - **Your own CSS**, if the theme folder styles anything the beta touches.
40
+ - **`npx docpensieve check`**, which reads the built site back and reports dead
41
+ links and invalid markup.
@@ -17,6 +17,18 @@ other, or both.
17
17
 
18
18
  Neither loads a script. Everything here folds and unfolds with native elements.
19
19
 
20
+ ## The header, held or not
21
+
22
+ The header stays at the top of the screen. To let it scroll away with the page:
23
+
24
+ ```js
25
+ stickyHeader: false,
26
+ ```
27
+
28
+ Held, it keeps the search field and the version switcher within reach. Let go,
29
+ it gives back its height — a third of a phone screen — and anchors stop
30
+ reserving room for it.
31
+
20
32
  ## The menu of the documentation
21
33
 
22
34
  It follows the file tree, or the file you describe — that is
@@ -0,0 +1,176 @@
1
+ ---
2
+ title: When it breaks
3
+ description: The failures this tool actually produces, what causes each one, and the fix — by the symptom you see.
4
+ tags: [guide, troubleshooting]
5
+
6
+ jsonld:
7
+ type: TechArticle
8
+ breadcrumbs: true
9
+ ---
10
+
11
+ # When it breaks
12
+
13
+ Every expected error carries a message **and a hint** saying what is expected
14
+ instead, and the command exits with code 1 without a stack trace. The failures
15
+ below are the ones that say nothing, or say it somewhere you were not looking:
16
+ the site builds, the suite is green, and something is still wrong.
17
+
18
+ Each heading is the symptom as you would describe it.
19
+
20
+ ## `init` asked me nothing and built a site I did not choose
21
+
22
+ The dialogue needs a real terminal. Run from a script, a pipeline or through a
23
+ pipe, `init` takes the options given on the command line and the defaults for
24
+ everything else — it announces this before starting, unless `--yes` says you
25
+ meant it.
26
+
27
+ Pass what matters explicitly:
28
+
29
+ ```bash
30
+ npx docpensieve init my-site --name "Acme docs" --theme custom --site-url https://acme.example.com
31
+ ```
32
+
33
+ ## `npx` runs a version older than the one I just published
34
+
35
+ A freshly published version takes a few minutes to reach every mirror of the
36
+ registry, and `npx` keeps what it already has. During those minutes,
37
+ installing can even fail outright with `ETARGET`, because the packages pin each
38
+ other to the exact same version and one of the five is not there yet.
39
+
40
+ ```bash
41
+ npx --prefer-online docpensieve@latest init my-site
42
+ ```
43
+
44
+ Wait rather than republish: a version number burned is not recoverable, and
45
+ nothing is wrong with the one being propagated.
46
+
47
+ ## The site works on my machine and every link is dead online
48
+
49
+ The site is served from a subfolder and `baseUrl` was not set. Locally,
50
+ `serve` and `dev` answer at the root, so nothing shows.
51
+
52
+ ```js
53
+ siteUrl: 'https://acme.example.com/docs',
54
+ baseUrl: '/docs/',
55
+ ```
56
+
57
+ `baseUrl` prefixes every internal link. Without it, a link written
58
+ `/guide/installation/` points one level above the site.
59
+
60
+ ## `check` says a link "ignores the deployment prefix"
61
+
62
+ The file exists, but the link was written without the prefix — it will lead
63
+ nowhere once online, and hitting it locally hides that.
64
+
65
+ ```
66
+ index.html
67
+ /guide/installation/
68
+ → ignores the deployment prefix "/docs/"
69
+ ```
70
+
71
+ Write the link the way the documentation does — relative to the page
72
+ (`../components/card/`) or from the root of the version (`/components/card/`,
73
+ which the build rewrites) — rather than typing the deployed path by hand.
74
+
75
+ ## My page does not appear anywhere
76
+
77
+ Three causes, in the order worth checking:
78
+
79
+ - **`draft: true`** in its frontmatter keeps it out of the output, on purpose.
80
+ - **The file is outside a version folder.** Only what lives under a `folder`
81
+ declared in `versions` is read.
82
+ - **The extension.** `.md` and `.mdx` are read; nothing else is.
83
+
84
+ The build says nothing in these three cases, because none of them is an error:
85
+ a folder you did not declare is simply not documentation.
86
+
87
+ ## A colour I set in `tokens` changes nothing
88
+
89
+ `theme.tokens` sets the **light** palette. A site carrying
90
+ `theme.darkMode: 'dark'` is always dark, so it never shows those values — the
91
+ dark ones come from the theme's stylesheet.
92
+
93
+ Redefine them in the `theme/` folder, for both ways of being dark:
94
+
95
+ ```css
96
+ @media (prefers-color-scheme: dark) {
97
+ :root:not(.light) {
98
+ --dp-accent: #a78bfa;
99
+ }
100
+ }
101
+
102
+ :root.dark {
103
+ --dp-accent: #a78bfa;
104
+ }
105
+ ```
106
+
107
+ ## Node warns at every build, or refuses the configuration
108
+
109
+ A `.js` configuration file is read as a module only when the nearest
110
+ `package.json` says `"type": "module"` — and the one `npm init -y` writes says
111
+ the opposite, which makes Node refuse the file outright.
112
+
113
+ Name it `docpensieve.config.mjs`. The extension settles it whatever the
114
+ `package.json` says.
115
+
116
+ The build also stops when **both** files are present:
117
+
118
+ ```
119
+ Two configuration files in /path/to/project: docpensieve.config.mjs and docpensieve.config.js.
120
+ ```
121
+
122
+ Keep one. Guessing which you meant would sooner or later publish a site built
123
+ from the wrong settings.
124
+
125
+ ## A component stops the build, or renders nothing
126
+
127
+ The components that need a parent check for it: a `Column` outside `Columns`,
128
+ a `MenuLink` outside a `Menu` stop the build rather than render an element
129
+ with no effect. That is deliberate — a tag that quietly does nothing survives
130
+ review, an error does not.
131
+
132
+ `globalComponents: false` removes the shipped components from every page. A
133
+ page still using one then stops the build, naming it:
134
+
135
+ ```
136
+ Error Unknown component "Cards" in docs/v1.0/99-docpensieve/01-guide/index.mdx.
137
+ No global component is registered for this compilation.
138
+ ```
139
+
140
+ Either turn them back on, or remove the installed documentation with the
141
+ components it uses — `--minimal` at `init` leaves it out from the start.
142
+
143
+ A `.md` page using a component works, but its extension no longer says what the
144
+ page does. Rename it `.mdx`; the two compile identically.
145
+
146
+ ## A utility class in a page has no effect
147
+
148
+ Under the `tailwind` theme, the stylesheet is compiled from the classes found
149
+ in the **rendered** pages. A class whose name does not survive being written
150
+ into HTML is never emitted, and nothing reports it — an `&` becomes `&`,
151
+ so `[&_.x]:underline` produces no rule.
152
+
153
+ Pass through a variable of the component instead:
154
+
155
+ ```mdx
156
+ <Skill name="Coverage" level={97} color="#10b981" />
157
+ ```
158
+
159
+ ## Nothing above matches
160
+
161
+ Run the two commands that look at different things, in this order:
162
+
163
+ ```bash
164
+ npx docpensieve build
165
+ npx docpensieve check
166
+ ```
167
+
168
+ The build compiles pages and stops on what it cannot do. `check` reads the
169
+ produced site back and follows its links — a renamed page compiles perfectly
170
+ and leaves every link to it dead.
171
+
172
+ An unexpected failure — one without a hint — comes out with its stack trace,
173
+ and is worth reporting with the version printed by `npx docpensieve --version`.
174
+
175
+ Each message comes from a command, and the [CLI reference](../reference/cli/)
176
+ lists what each one does, with the exit codes a pipeline reads.