@karimsa/mdxserve 0.0.0-stage → 0.2.0

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -2
  3. package/client/App.tsx +7 -0
  4. package/client/CodeBlock.tsx +395 -0
  5. package/client/CrossFade.tsx +72 -0
  6. package/client/DocContext.ts +14 -0
  7. package/client/DocView.tsx +107 -0
  8. package/client/ErrorBox.tsx +23 -0
  9. package/client/Heading.tsx +31 -0
  10. package/client/HomeEmptyState.tsx +101 -0
  11. package/client/HomeView.tsx +78 -0
  12. package/client/ListingView.tsx +663 -0
  13. package/client/MdSection.tsx +234 -0
  14. package/client/MdSectionEditor.tsx +233 -0
  15. package/client/Mermaid.tsx +435 -0
  16. package/client/RenderErrorBoundary.tsx +40 -0
  17. package/client/Table.tsx +14 -0
  18. package/client/TaskCheckbox.tsx +38 -0
  19. package/client/api.ts +138 -0
  20. package/client/app.css +372 -0
  21. package/client/builtins/Badge.tsx +109 -0
  22. package/client/builtins/Button.tsx +111 -0
  23. package/client/builtins/Callout.tsx +97 -0
  24. package/client/builtins/Card.tsx +111 -0
  25. package/client/builtins/Chart.tsx +875 -0
  26. package/client/builtins/Diff.tsx +722 -0
  27. package/client/builtins/Dropdown.tsx +417 -0
  28. package/client/builtins/FileTree.tsx +87 -0
  29. package/client/builtins/Kbd.tsx +18 -0
  30. package/client/builtins/Screenshot.tsx +209 -0
  31. package/client/builtins/Sparkline.tsx +63 -0
  32. package/client/builtins/Tabs.tsx +169 -0
  33. package/client/builtins/Tooltip.tsx +52 -0
  34. package/client/builtins/chart-data.ts +133 -0
  35. package/client/builtins/index.ts +167 -0
  36. package/client/design/base/editor.css +151 -0
  37. package/client/design/base/prose.css +143 -0
  38. package/client/design/base/reset.css +79 -0
  39. package/client/design/tokens/colors.css +188 -0
  40. package/client/design/tokens/elevation.css +42 -0
  41. package/client/design/tokens/fonts.css +6 -0
  42. package/client/design/tokens/motion.css +76 -0
  43. package/client/design/tokens/spacing.css +34 -0
  44. package/client/design/tokens/typography.css +56 -0
  45. package/client/doc-module-cache.ts +17 -0
  46. package/client/editor-link.ts +27 -0
  47. package/client/entry.tsx +51 -0
  48. package/client/export-doc.ts +80 -0
  49. package/client/export-save.ts +96 -0
  50. package/client/favicon.svg +1 -0
  51. package/client/file-system-access.d.ts +29 -0
  52. package/client/format.ts +17 -0
  53. package/client/hooks.ts +34 -0
  54. package/client/lucide-icons.d.ts +9 -0
  55. package/client/mdx-components-base.ts +32 -0
  56. package/client/mdx-components.ts +18 -0
  57. package/client/mermaid-chart.ts +109 -0
  58. package/client/mermaid-direction.ts +73 -0
  59. package/client/motion.ts +104 -0
  60. package/client/platform.ts +16 -0
  61. package/client/route-path.ts +15 -0
  62. package/client/router.ts +452 -0
  63. package/client/shell/AppShell.tsx +401 -0
  64. package/client/shell/Footer.tsx +33 -0
  65. package/client/shell/NotFoundView.tsx +22 -0
  66. package/client/shell/Sidebar.tsx +169 -0
  67. package/client/shell/StandaloneShell.tsx +65 -0
  68. package/client/shell/TocRail.tsx +53 -0
  69. package/client/shell/TopBar.tsx +117 -0
  70. package/client/shell/use-doc-width.ts +61 -0
  71. package/client/shell/useToc.ts +77 -0
  72. package/client/ssr-entry.tsx +22 -0
  73. package/client/standalone-entry.tsx +51 -0
  74. package/client/state.ts +90 -0
  75. package/client/theme.ts +65 -0
  76. package/client/ui/Breadcrumb.tsx +49 -0
  77. package/client/ui/ConfirmDeleteDialog.tsx +113 -0
  78. package/client/ui/ExpandModal.tsx +342 -0
  79. package/client/ui/Icon.tsx +114 -0
  80. package/client/ui/IconButton.tsx +63 -0
  81. package/client/ui/Kbd.tsx +17 -0
  82. package/client/ui/PageNav.tsx +77 -0
  83. package/client/ui/ResizeHandle.tsx +201 -0
  84. package/client/ui/SearchDialog.tsx +187 -0
  85. package/client/ui/Tag.tsx +44 -0
  86. package/client/ui/Toast.tsx +189 -0
  87. package/client/ui/TocList.tsx +71 -0
  88. package/client/ui/icon-set.ts +102 -0
  89. package/client/ui/toast-count.ts +28 -0
  90. package/dist/cli.js +5091 -0
  91. package/dist/registry.json +703 -0
  92. package/dist/render-worker.js +145 -0
  93. package/package.json +113 -5
  94. package/skills/mdxserve/SKILL.md +178 -0
@@ -0,0 +1,145 @@
1
+ // src/rendering/render-worker.ts
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { parentPort } from "node:worker_threads";
5
+ import { ESModulesEvaluator, ModuleRunner } from "vite/module-runner";
6
+ if (!parentPort) {
7
+ throw new Error("render-worker.ts must be run inside a node:worker_threads Worker");
8
+ }
9
+ var port = parentPort;
10
+ function toPosix(filePath) {
11
+ return filePath.split(path.sep).join("/");
12
+ }
13
+ var nextInvokeId = 0;
14
+ var pendingInvokes = /* @__PURE__ */ new Map();
15
+ var transport = {
16
+ invoke(data) {
17
+ const invokeId = nextInvokeId++;
18
+ return new Promise((resolve) => {
19
+ pendingInvokes.set(invokeId, { resolve });
20
+ const message = { type: "invoke", invokeId, payload: data };
21
+ port.postMessage(message);
22
+ });
23
+ }
24
+ };
25
+ var runner = new ModuleRunner(
26
+ {
27
+ transport,
28
+ hmr: false,
29
+ // Have Node remap `Error.stack` frames through each module's sourcemap
30
+ // as they're generated, so an error thrown while evaluating a doc
31
+ // already points at the original .md/.mdx source — no separate
32
+ // `vite.ssrFixStacktrace()` pass needed (that was a main-thread-only
33
+ // API tied to the main-thread module graph). "node" is what Vite's own
34
+ // `createServerModuleRunner` resolves to by default in a real Node
35
+ // process (see `resolveSourceMapOptions` in dist/node/chunks/node.js);
36
+ // passing it explicitly here just makes that intentional rather than
37
+ // incidental.
38
+ sourcemapInterceptor: "node"
39
+ },
40
+ new ESModulesEvaluator()
41
+ );
42
+ var USE_LAYOUT_EFFECT_WARNING = "useLayoutEffect does nothing on the server";
43
+ function withConsoleErrorFilter(fn) {
44
+ const original = console.error;
45
+ console.error = (...args) => {
46
+ const first = args[0];
47
+ if (typeof first === "string" && first.includes(USE_LAYOUT_EFFECT_WARNING)) return;
48
+ original.apply(console, args);
49
+ };
50
+ try {
51
+ return fn();
52
+ } finally {
53
+ console.error = original;
54
+ }
55
+ }
56
+ function firstFrameFor(stack, absPath) {
57
+ const escaped = absPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
58
+ const re = new RegExp(`${escaped}:(\\d+):(\\d+)`);
59
+ for (const rawLine of stack.split("\n")) {
60
+ const match = re.exec(rawLine);
61
+ if (match) {
62
+ return { line: Number(match[1]), column: Number(match[2]) };
63
+ }
64
+ }
65
+ return {};
66
+ }
67
+ function pathForms(docPath) {
68
+ let real = docPath;
69
+ try {
70
+ real = fs.realpathSync.native(docPath);
71
+ } catch {
72
+ }
73
+ return [.../* @__PURE__ */ new Set([docPath, toPosix(docPath), real, toPosix(real)])];
74
+ }
75
+ function outcomeFromError(err, docPath) {
76
+ if (!(err instanceof Error)) {
77
+ return { ok: false, message: String(err) };
78
+ }
79
+ const stack = err.stack;
80
+ let line;
81
+ let column;
82
+ if (stack) {
83
+ for (const form of pathForms(docPath)) {
84
+ ({ line, column } = firstFrameFor(stack, form));
85
+ if (line !== void 0) break;
86
+ }
87
+ }
88
+ return { ok: false, message: err.message, stack, line, column };
89
+ }
90
+ async function handleRender(msg) {
91
+ const { id, ssrEntryPath, docPath } = msg;
92
+ let outcome;
93
+ try {
94
+ invalidateDocModules();
95
+ const [ssrEntry, docModule] = await Promise.all([
96
+ runner.import(toPosix(ssrEntryPath)),
97
+ runner.import(toPosix(docPath))
98
+ ]);
99
+ if (!docModule.default) {
100
+ outcome = { ok: false, message: `${docPath} has no default export.` };
101
+ } else {
102
+ withConsoleErrorFilter(() => ssrEntry.renderDoc(docModule.default));
103
+ outcome = { ok: true };
104
+ }
105
+ } catch (err) {
106
+ outcome = outcomeFromError(err, docPath);
107
+ }
108
+ const result = { type: "result", id, outcome };
109
+ port.postMessage(result);
110
+ }
111
+ var sharedIds;
112
+ function invalidateDocModules() {
113
+ for (const [id, node] of runner.evaluatedModules.idToModuleMap) {
114
+ if (sharedIds?.has(id)) continue;
115
+ runner.evaluatedModules.invalidateModule(node);
116
+ }
117
+ }
118
+ async function handleWarm(msg) {
119
+ let outcome;
120
+ try {
121
+ await runner.import(toPosix(msg.ssrEntryPath));
122
+ sharedIds = new Set(runner.evaluatedModules.idToModuleMap.keys());
123
+ outcome = { ok: true };
124
+ } catch (err) {
125
+ outcome = err instanceof Error ? { ok: false, message: err.message, stack: err.stack } : { ok: false, message: String(err) };
126
+ }
127
+ const result = { type: "result", id: msg.id, outcome };
128
+ port.postMessage(result);
129
+ }
130
+ port.on("message", (msg) => {
131
+ if (msg.type === "invoke-response") {
132
+ const pending = pendingInvokes.get(msg.invokeId);
133
+ if (!pending) return;
134
+ pendingInvokes.delete(msg.invokeId);
135
+ pending.resolve(msg.response);
136
+ return;
137
+ }
138
+ if (msg.type === "warm") {
139
+ void handleWarm(msg);
140
+ return;
141
+ }
142
+ if (msg.type === "render") {
143
+ void handleRender(msg);
144
+ }
145
+ });
package/package.json CHANGED
@@ -1,6 +1,114 @@
1
1
  {
2
- "name": "@karimsa/mdxserve",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@karimsa/mdxserve",
3
+ "version": "0.2.0",
4
+ "description": "Serve a directory of Markdown/MDX files, like `serve` but for docs",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/karimsa/mdxserve.git"
9
+ },
10
+ "homepage": "https://github.com/karimsa/mdxserve#readme",
11
+ "bugs": "https://github.com/karimsa/mdxserve/issues",
12
+ "keywords": [
13
+ "markdown",
14
+ "mdx",
15
+ "docs",
16
+ "cli"
17
+ ],
18
+ "type": "module",
19
+ "bin": "dist/cli.js",
20
+ "files": [
21
+ "dist",
22
+ "client",
23
+ "!client/design-ref",
24
+ "skills"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsx scripts/build.ts",
28
+ "registry": "tsx scripts/build-registry.ts",
29
+ "dev": "tsx scripts/build-registry.ts && tsx src/index.ts serve -w example",
30
+ "typecheck": "tsc --noEmit",
31
+ "format": "prettier --write .",
32
+ "format:check": "prettier --check .",
33
+ "lint": "oxlint",
34
+ "test": "vitest run",
35
+ "prepack": "yarn build",
36
+ "smoke": "bash scripts/smoke-pack.sh"
37
+ },
38
+ "packageManager": "yarn@4.18.0",
39
+ "engines": {
40
+ "node": ">=22.12"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "dependencies": {
46
+ "@fontsource-variable/jetbrains-mono": "5.3.0",
47
+ "@fontsource-variable/manrope": "5.3.0",
48
+ "@mdx-js/mdx": "3.1.1",
49
+ "@mdx-js/react": "3.1.1",
50
+ "@mdx-js/rollup": "3.1.1",
51
+ "@tailwindcss/vite": "4.3.3",
52
+ "@tanstack/react-query": "5.101.4",
53
+ "@tippyjs/react": "4.2.6",
54
+ "@tiptap/core": "3.29.2",
55
+ "@tiptap/extension-image": "3.29.2",
56
+ "@tiptap/extension-list": "3.29.2",
57
+ "@tiptap/extension-table": "3.29.2",
58
+ "@tiptap/markdown": "3.29.2",
59
+ "@tiptap/pm": "3.29.2",
60
+ "@tiptap/react": "3.29.2",
61
+ "@tiptap/starter-kit": "3.29.2",
62
+ "@trpc/client": "11.18.0",
63
+ "@trpc/server": "11.18.0",
64
+ "@trpc/tanstack-react-query": "11.18.0",
65
+ "@vitejs/plugin-react": "6.0.5",
66
+ "commander": "15.0.0",
67
+ "date-fns": "4.4.0",
68
+ "diff": "9.0.0",
69
+ "framer-motion": "13.0.0",
70
+ "hast-util-to-html": "9.0.5",
71
+ "jotai": "2.20.2",
72
+ "lockfile": "1.0.4",
73
+ "lucide-react": "1.30.0",
74
+ "mdast-util-from-markdown": "2.0.3",
75
+ "mdast-util-mdx": "3.0.0",
76
+ "mdast-util-to-hast": "13.2.1",
77
+ "mdast-util-to-string": "4.0.0",
78
+ "mermaid": "10.9.8",
79
+ "micromark-extension-mdxjs": "3.0.0",
80
+ "minisearch": "7.2.0",
81
+ "node-sqlite3-wasm": "0.8.60",
82
+ "react": "19.2.8",
83
+ "react-dom": "19.2.8",
84
+ "react-hot-toast": "2.6.0",
85
+ "rehype-pretty-code": "0.14.5",
86
+ "rehype-slug": "6.0.0",
87
+ "remark-gfm": "4.0.1",
88
+ "shiki": "4.4.2",
89
+ "svg-pan-zoom": "3.6.2",
90
+ "tailwindcss": "4.3.3",
91
+ "tippy.js": "6.3.7",
92
+ "trash": "10.1.1",
93
+ "vfile-message": "4.0.3",
94
+ "vite": "8.2.1",
95
+ "zod": "4.4.3",
96
+ "zx": "8.8.5"
97
+ },
98
+ "devDependencies": {
99
+ "@types/lockfile": "1.0.4",
100
+ "@types/mdast": "4.0.4",
101
+ "@types/node": "26.2.0",
102
+ "@types/react": "19.2.18",
103
+ "@types/react-dom": "19.2.4",
104
+ "@types/svg-pan-zoom": "3.4.0",
105
+ "@yarnpkg/types": "4.0.1",
106
+ "esbuild": "0.28.2",
107
+ "fast-check": "4.9.0",
108
+ "oxlint": "1.77.0",
109
+ "prettier": "3.9.6",
110
+ "tsx": "4.23.12",
111
+ "typescript": "7.0.2",
112
+ "vitest": "4.1.10"
113
+ }
114
+ }
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: mdxserve
3
+ description: Use when writing or editing Markdown the user will read — docs, reports, plan files, notes — so the content renders as richly as possible in mdxserve while staying plain, portable Markdown.
4
+ allowed-tools: Bash, Read, Grep, Glob
5
+ ---
6
+
7
+ # Writing Markdown for mdxserve
8
+
9
+ mdxserve serves a folder of `.md` / `.mdx` files as a browsable site: a file listing,
10
+ rendered pages with syntax highlighting, mermaid diagrams, and a small library of builtin
11
+ components. `.md` and `.mdx` are treated identically — both go through MDX, so the builtin
12
+ components work in either.
13
+
14
+ Markdown files have many readers besides mdxserve (GitHub, editors, other tools). The goal
15
+ is the **simplest Markdown that is still visual**: reach for richer features only when they
16
+ add clarity, and prefer features that degrade gracefully everywhere else.
17
+
18
+ ## The ladder — prefer lower rungs
19
+
20
+ 1. **Plain GitHub-flavored Markdown.** Headings, short paragraphs, lists, task lists, tables,
21
+ links, bold for the one thing that matters. Works everywhere. Most content should stop here.
22
+ 2. **Fenced code with a language tag.** Always tag the language (`ts`, `bash`, `json`, …). Add
23
+ fence meta when it helps the reader: `title="path/to/file.ts"`, `showLineNumbers`,
24
+ `{3-5}` to highlight lines. mdxserve renders these richly; other tools ignore the meta.
25
+ 3. **Mermaid diagrams** for flows, sequences, states, and architecture. mdxserve renders them as
26
+ pan/zoomable diagrams (with a toggle to the source); GitHub renders them too; everywhere else
27
+ they are still readable text. Prefer `flowchart` and `sequenceDiagram`; keep a diagram to
28
+ roughly 15 nodes or fewer — split larger ones. Never use mermaid's `pie`, `xychart-beta`,
29
+ `quadrantChart`, or `sankey-beta` — mdxserve doesn't render them, `mdxserve validate` reports an
30
+ error on them, and they draw data, which is `<Chart>`'s job (or a table). Mermaid is for a
31
+ flow or a shape, not a dataset.
32
+ 4. **Builtin components** (`<Callout>`, `<Tabs>`, `<Badge>`, `<Tooltip>`, `<Button>`, `<Diff>`, `<Card>`,
33
+ `<Kbd>`, `<FileTree>`, `<Chart>` (bar, line, area, histogram), `<Sparkline>`, `<Screenshot>`) only when they make
34
+ the content clearer: a warning the reader must not miss, per-OS or per-language variants of the
35
+ same instructions, a status label, a small dataset that's clearer as a shape than a table.
36
+ Outside mdxserve these show as raw tags, so use them sparingly and never for decoration.
37
+
38
+ Don't: write raw HTML or inline styles; import or create custom components; use emoji as
39
+ icons where a `Badge` or `Callout` would do; nest components for layout; put a component in a
40
+ file the user will mainly read elsewhere (e.g. a README on GitHub) unless they asked.
41
+
42
+ MDX is stricter than Markdown: a bare `{`, `}` or an unclosed `<tag>` in prose is a compile
43
+ error, not literal text. Put such characters in backticks or escape them (`\{`).
44
+
45
+ ## Quick reference
46
+
47
+ A table beats a list of "X: Y" lines:
48
+
49
+ ```md
50
+ | Option | Default | Effect |
51
+ | -------- | ------- | ------------------------- |
52
+ | `--port` | `4040` | Port to listen on |
53
+ | `--host` | `127.0.0.1` | Interface to bind (`0.0.0.0` for LAN) |
54
+ ```
55
+
56
+ Code with a title, line numbers, and highlighted lines:
57
+
58
+ ````md
59
+ ```ts title="src/server.ts" showLineNumbers {3-4}
60
+ const server = http.createServer(handler);
61
+ server.listen(port);
62
+ // highlighted:
63
+ console.log(`http://localhost:${port}`);
64
+ ```
65
+ ````
66
+
67
+ A flow and a sequence:
68
+
69
+ ````md
70
+ ```mermaid
71
+ flowchart LR
72
+ Browser -->|GET /docs/intro.md| Server
73
+ Server -->|compile via Vite| Page[Rendered page]
74
+ ```
75
+ ````
76
+
77
+ ````md
78
+ ```mermaid
79
+ sequenceDiagram
80
+ participant U as User
81
+ participant S as Server
82
+ U->>S: click file
83
+ S-->>U: rendered page
84
+ ```
85
+ ````
86
+
87
+ Task lists for progress; keep them flat:
88
+
89
+ ```md
90
+ - [x] Read the existing router
91
+ - [ ] Add the listing endpoint
92
+ - [ ] Verify in the browser
93
+ ```
94
+
95
+ ## Builtin components
96
+
97
+ Available in any `.md` or `.mdx` with no import. The registry — not this file — is the
98
+ authoritative list of components and props. Before using a component for the first time in a
99
+ session, check it:
100
+
101
+ ```bash
102
+ mdxserve components search # list every builtin with a one-line description
103
+ mdxserve components search tab # search by name, description, or prop name
104
+ mdxserve components show Callout # props table (types, defaults) + when to use
105
+ mdxserve components show Callout --json
106
+ ```
107
+
108
+ If `mdxserve` is not on your PATH, install it with `npm install -g @karimsa/mdxserve`.
109
+
110
+ Minimal usage:
111
+
112
+ ```mdx
113
+ <Callout tone="warn" title="Before you deploy">
114
+ Rotate the key first; the old one stops working immediately.
115
+ </Callout>
116
+
117
+ <Tabs>
118
+ <Tab label="macOS">`brew install foo`</Tab>
119
+ <Tab label="Linux">`apt install foo`</Tab>
120
+ </Tabs>
121
+
122
+ Status: <Badge tone="ok" dot>Online</Badge>
123
+
124
+ The <Tooltip content="Mean time to recovery">MTTR</Tooltip> improved.
125
+
126
+ <Button href="./setup.md">Continue to setup</Button>
127
+
128
+ <Diff before={`port: 3000`} after={`port: 4000`} />
129
+
130
+ Press <Kbd>⌘</Kbd><Kbd>K</Kbd> to open search.
131
+ ```
132
+
133
+ Components contain ordinary Markdown: a `Tab` can hold lists, paragraphs, and code fences.
134
+ Leave a blank line between a component tag and a Markdown block inside it so the block is
135
+ parsed as Markdown.
136
+
137
+ ## Plan files
138
+
139
+ Plans are read in mdxserve and in plain text. Use headings for phases, a task list per phase,
140
+ a table for files-to-change, and at most one mermaid diagram (the architecture or the main
141
+ flow, never a chart). Skip components in plans unless the user views plans only in mdxserve.
142
+
143
+ ## After writing
144
+
145
+ After saving any `.md`/`.mdx` file, run `mdxserve validate <absolute path>`. It works even when
146
+ no `mdxserve serve` is running. Read the first line and every diagnostic under it: it is
147
+ `OK: <path>`, `OK with N warnings: <path>`, or a list of problems. Fix every `error` (MDX
148
+ compile errors, unknown components, `render-error`) and any `unknown-prop` warning where a real
149
+ prop was intended, then re-run until the first line is `OK` with only intended warnings. The
150
+ exit code is 1 when any error was reported. Compile errors and unknown components blank the
151
+ page or throw at render time — invisible until a human opens it — so this is not optional.
152
+
153
+ `validate` also renders the doc server-side and reports any throw as a `render-error` — this is
154
+ what catches a component that compiles fine but blanks the page (e.g. a stray identifier that
155
+ only breaks at render time). Read the line after the diagnostics: `Rendered OK`, or
156
+ `Not rendered (…)` when no `mdxserve serve` is reachable, meaning only the static checks
157
+ (compile, unknown component/prop) ran. Treat that as a weaker pass and mention it to the user
158
+ rather than treating `OK` alone as a full clean bill of health. Even with `Rendered OK`, errors
159
+ thrown inside a `useEffect`/`useLayoutEffect` and hydration mismatches are never caught — those
160
+ stay browser-only. `hint:` lines are advisory and never affect the result. When scripting, use
161
+ `--json` and check the `rendered` field.
162
+
163
+ If `validate` says the file is outside every served directory, or `mdxserve search` /
164
+ `mdxserve docs` report that no folders are served, run `mdxserve roots list`, then
165
+ `mdxserve roots add <absolute dir>` with the doc's folder.
166
+
167
+ If `mdxserve` is not installed, tell the user the file was not validated; don't skip this
168
+ silently.
169
+
170
+ ## Before saving
171
+
172
+ - Every code fence has a language tag; titles and highlights only where they help.
173
+ - Any diagram is a mermaid fence, small enough to read at a glance, and none of them draws a
174
+ chart (no `pie`, `xychart-beta`, `quadrantChart`, `sankey-beta` — use `<Chart>` or a table).
175
+ - No raw HTML, styles, imports, or custom components.
176
+ - Any builtin component used was checked with `components show` and genuinely clarifies.
177
+ - The file still reads well as plain text.
178
+ - `mdxserve validate` printed `OK`, ideally `Rendered OK`.