@waveso/docs 0.1.0 → 0.3.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.
- package/CHANGELOG.md +194 -0
- package/README.md +592 -88
- package/dist/code-frame.d.ts +29 -0
- package/dist/code-frame.js +41 -0
- package/dist/code-meta.d.ts +48 -0
- package/dist/code-meta.js +72 -0
- package/dist/docs-content-id.d.ts +19 -0
- package/dist/docs-content-id.js +19 -0
- package/dist/docs-error.d.ts +19 -0
- package/dist/docs-error.js +28 -0
- package/dist/errors.d.ts +94 -0
- package/dist/errors.js +45 -0
- package/dist/frontmatter.d.ts +39 -7
- package/dist/frontmatter.js +51 -24
- package/dist/highlighter.d.ts +2 -2
- package/dist/highlighter.js +3 -2
- package/dist/map-pooled.d.ts +26 -0
- package/dist/map-pooled.js +45 -0
- package/dist/meta.d.ts +7 -3
- package/dist/meta.js +61 -15
- package/dist/next.d.ts +182 -35
- package/dist/next.js +177 -49
- package/dist/plugins/rehype-capture-toc.js +52 -20
- package/dist/plugins/rehype-code-frame.d.ts +10 -0
- package/dist/plugins/rehype-code-frame.js +88 -0
- package/dist/plugins/rehype-code-language.d.ts +24 -0
- package/dist/plugins/rehype-code-language.js +54 -0
- package/dist/plugins/rehype-fallback-heading-ids.d.ts +6 -0
- package/dist/plugins/rehype-fallback-heading-ids.js +51 -0
- package/dist/plugins/rehype-flatten-roots.d.ts +7 -0
- package/dist/plugins/rehype-flatten-roots.js +39 -0
- package/dist/plugins/remark-doc-links.d.ts +12 -1
- package/dist/plugins/remark-doc-links.js +147 -20
- package/dist/react/code-runtime.d.ts +14 -0
- package/dist/react/code-runtime.js +161 -0
- package/dist/react/doc-content.d.ts +39 -2
- package/dist/react/doc-content.js +42 -10
- package/dist/react/layout.d.ts +44 -0
- package/dist/react/layout.js +65 -0
- package/dist/react/markdown-components.js +71 -6
- package/dist/react/nav.d.ts +28 -0
- package/dist/react/nav.js +70 -0
- package/dist/react/nearest-scroll-top.d.ts +45 -0
- package/dist/react/nearest-scroll-top.js +44 -0
- package/dist/react/next-link.d.ts +34 -0
- package/dist/react/next-link.js +30 -0
- package/dist/react/next-nav.d.ts +11 -0
- package/dist/react/next-nav.js +32 -0
- package/dist/react/next-search.d.ts +22 -0
- package/dist/react/next-search.js +52 -0
- package/dist/react/search-dialog.d.ts +35 -7
- package/dist/react/search-dialog.js +55 -33
- package/dist/react/shell-labels.d.ts +43 -0
- package/dist/react/shell-labels.js +27 -0
- package/dist/react/sidebar.d.ts +38 -3
- package/dist/react/sidebar.js +104 -12
- package/dist/react/skip-link.d.ts +1 -9
- package/dist/react/skip-link.js +6 -5
- package/dist/react/toc.d.ts +12 -4
- package/dist/react/toc.js +46 -12
- package/dist/react/youtube.d.ts +31 -5
- package/dist/react/youtube.js +76 -52
- package/dist/render.d.ts +78 -10
- package/dist/render.js +137 -54
- package/dist/route-path.d.ts +46 -0
- package/dist/route-path.js +51 -0
- package/dist/search-index.d.ts +22 -21
- package/dist/search-index.js +27 -78
- package/dist/search-options.d.ts +32 -1
- package/dist/search-options.js +66 -3
- package/dist/section-boundary.d.ts +17 -0
- package/dist/section-boundary.js +43 -0
- package/dist/sitemap-limit.d.ts +34 -0
- package/dist/sitemap-limit.js +37 -0
- package/dist/source.d.ts +12 -22
- package/dist/source.js +165 -72
- package/dist/styles.css +1117 -125
- package/dist/types.d.ts +52 -29
- package/package.json +70 -34
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# @waveso/docs
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3ba341f: A docs site is now four files and a folder of markdown. Search works out of the box, there is a real shell with a mobile drawer, and the public surface is frozen. Several changes are breaking; those come first.
|
|
8
|
+
|
|
9
|
+
**The page's content is a `main` landmark.** It was an `<article>`, so the shell rendered a banner, a navigation and a complementary and no `main` at all — a screen-reader user navigating by landmark, which is how you skip a hundred-link sidebar without tabbing, had nothing to jump to. **Breaking in rendered output:** a selector or an assertion targeting `article.wave-docs-layout__main` needs to say `main`.
|
|
10
|
+
|
|
11
|
+
**`docs.Layout` renders the whole shell**, as one line in your layout file:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// app/docs/layout.tsx
|
|
15
|
+
import "@waveso/docs/styles.css";
|
|
16
|
+
import { docs } from "@/lib/docs";
|
|
17
|
+
|
|
18
|
+
export default docs.Layout;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Skip link, sticky header, sidebar column, mobile drawer and the grid that arranges them. It is a Server Component and your layout stays one — the two pieces that need a client carry their own boundaries inside the package — and it reads the navigation tree and the search index URL itself, so there is nothing to fetch and nothing to pass. It replaces four files every consumer used to write by hand, including a `'use client'` wrapper around `usePathname` that the README shipped as a recipe. Four props: `title`, `actions`, `search`, `children`. `search` takes the dialog's own props as well as a boolean, which is what makes `miniSearchOptions` reachable at all — MiniSearch reads `tokenize` and `processTerm` when indexing _and_ when querying, so an index built with one and queried with another matches nothing, and while `search` was a bare boolean there was no channel for it. The route's own `miniSearchOptions` are forwarded by default, so the object that built the index is the object that queries it without anyone having to know that.
|
|
22
|
+
|
|
23
|
+
**There is a mobile navigation drawer**, which there was not before: on a 390px viewport a reader could previously reach exactly one other page. It is one `<dialog closedby="any">` opened by a server-rendered `<button command="show-modal">`, so it works on the first tap — before hydration, and with JavaScript disabled. Focus moves inside and Tab stays there, Escape closes and restores focus, the backdrop dismisses it, and the page behind does not scroll; all of that is the browser's. At 64rem the same element becomes the sticky sidebar column via `display: contents`, so one navigation serves both breakpoints — one landmark, one copy of the links in the payload.
|
|
24
|
+
|
|
25
|
+
**`docs.Page` returns two children now**, the `<article>` and the table of contents, rather than one. They land as direct children of the grid, which is what puts them in separate columns. If you wrapped `docs.Page` in an element expecting a single child, that wrapper needs to go. A page with no headings emits no `<aside>` at all rather than an empty one, because the grid reserves that column with `:has()` and would otherwise give 15rem to nothing.
|
|
26
|
+
|
|
27
|
+
**The pipeline has plugin slots**, which it did not before — it was frozen end to end, and both apparent escape hatches are useless (`frozen.use()` throws; `frozen().use(p)` appends, so a plugin runs after Shiki and sees token spans where the author's code was):
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
createDocsRoute({
|
|
31
|
+
contentDir: "content/docs",
|
|
32
|
+
remarkPlugins: [remarkMath],
|
|
33
|
+
rehypePlugins: [rehypeKatex],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`remarkPlugins` run before link resolution, so what they emit is folded, contained and asserted exactly like authored markdown. `rehypePlugins` run after heading ids exist and before Shiki, so a fence is still the author's text. **The table of contents is now captured last**, after your plugins and everything else, so it describes the same document the search index does — a plugin that adds or removes a heading changes both together, and there is no validation pass because there is nothing left to validate.
|
|
38
|
+
|
|
39
|
+
**Every size and speed claim is now measured and budgeted — including the README's own table.** The figures there are ceilings rather than snapshots, and `pnpm size` fails when a measurement passes one, when one promises better than `size-budget.json` enforces, or when a row goes missing. That axis exists because the table had already rotted: it published the sidebar at 1.24 KB against a real 1.67 KB, and a budget raised two commits earlier, under a sentence promising every number was one a build fails over.
|
|
40
|
+
|
|
41
|
+
`pnpm size` runs in CI and in `prepublishOnly` across three axes — client bytes per `'use client'` entry, hast-over-the-wire against HTML as a brotli ratio, and highlighting cost against the same corpus unhighlighted. A "What it costs" table sits above the quick start with a budget entry behind every figure. Three numbers in the tree were wrong and are fixed by measuring rather than by picking a side: positions are 33% of the JSON, not the 38% one comment claimed or the 44% the README claimed, and the documented Node floor was 20.19.0 against a real floor of 22.12.0.
|
|
42
|
+
|
|
43
|
+
**The shell is translatable.** `docs.Layout` takes a `labels` object with the four strings it renders itself — the navigation landmark's name, the drawer's open and close buttons, the skip link — each falling back on its own, so a partial map is not a half-English shell. `DocsNav` had declared `label` and `closeLabel` props, documented them and defaulted them, while the layout that is the only thing rendering it passed neither and `DocsLayoutProps` had no way to say them: configuration that could not be configured.
|
|
44
|
+
|
|
45
|
+
**Two rendering defects that only a screenshot could find.** A code frame's title bar drops its bottom border to join the code below it, and a `<pre>`'s user-agent margin then pushed the two 14px apart — a caption hovering over a gap, and a copy button mis-seated on untitled fences. And the shell painted its own containers but not the grid between them, so in dark mode the sidebar and the table of contents rendered as lighter panels floating on a darker page; light mode hid it because both were white. Both are fixed, both are now pinned by browser tests, and the README carries screenshots taken from a real build so the next one is visible in review.
|
|
46
|
+
|
|
47
|
+
**Smaller repairs a reader would notice.** The video facade asks the player to start, so watching a video is one click rather than two — it is still not an autoplaying embed, because the iframe does not exist until the `<details>` opens. A failed copy now _looks_ failed: `data-copied="false"` was written from the beginning and had no rule in the stylesheet, so a screen-reader user was told and a sighted user watched a button do nothing, on the ordinary path of `next dev` opened from a phone over http. The table's horizontal-scroll shadow was hardcoded black and therefore invisible in dark mode. An excluded fence had no `tabindex`, making it the one code block on a page a keyboard could not scroll sideways.
|
|
48
|
+
|
|
49
|
+
**The sidebar scrolls the current page into view** — properly. The first version measured the item with `offsetTop - port.offsetTop`, and the navigation column is `position: sticky`, which makes it the `offsetParent`: the offset was already relative to it, so the subtraction removed the header height twice and parked the current page below the fold. Measured in Chromium and fixed with rectangles, and a browser test now asserts the item is inside the visible box rather than that something scrolled. A back-to-top link ends the table of contents. Both move focus rather than only the viewport, and neither uses `scrollIntoView` — which scrolls every scrollable ancestor including the document, so it would jump the article a reader just navigated to. External links in the navigation carry a small icon.
|
|
50
|
+
|
|
51
|
+
**Errors are branchable, at `@waveso/docs/errors`.** Every failure already carried a `code` from a 19-member union, and the module documenting that taxonomy also declared itself private — so there was no supported way to use it, and nothing in the README mentioned it. `DocsErrorCode`, `DocsError` and `isDocsError` are exported now, with a troubleshooting table carrying one row per code, and a test that keeps the union, the table and the call sites in step.
|
|
52
|
+
|
|
53
|
+
**The YouTube embed ships no JavaScript, and the default component map is now provably server-only.** It was a `'use client'` component mapped unconditionally, so every page of every consumer carried a reference to it whether or not it embedded a video. It is a `<details>` with a lazy iframe now — measured: a closed one issues no request, an open one does — which keeps the click-to-load facade, gains native keyboard support, and removes the hydration root. `src/server-boundary.test.ts` fails the build if anything reachable from the markdown map ever carries the directive again. **Breaking in rendered output:** the facade is a `<details>`/`<summary>` rather than a `<button>`, so styles or assertions targeting the button change with it. The open state hides that summary, which cost the keyboard reader their focus indicator entirely — it comes back on `:focus-visible` as a real control, and its label reads "Hide video" once activating it would hide the video.
|
|
54
|
+
|
|
55
|
+
**Sidebar links near the reader prefetch now.** Every link carried `prefetch={false}`, on Pages-Router reasoning that does not hold in the App Router — there it disables the hover and touch paths as well as the viewport one, so the most-clicked control in a docs site made every navigation a cold round-trip. The list holding the current page and the heading of the group around it are warm; the rest of the tree stays off.
|
|
56
|
+
|
|
57
|
+
**The table of contents stops at `h3`.** Measured on a synthetic API reference — 8 methods, 3 overloads, 3 subsections each — capturing h2–h6 gave 104 entries against 32. Deeper headings keep their ids and permalinks, so they are still deep-linkable and still open their own sections in search; only the rail entry is dropped. If you want them back, `rehypePlugins` is the escape hatch. **`RenderedDoc.toc` therefore contains less data than before** — if you render it yourself and relied on h4+, that is the change.
|
|
58
|
+
|
|
59
|
+
**`gray-matter` is gone**, replaced by `vfile-matter`. It did a bare `require('fs')` for a method this package never calls — the only gratuitous Node requirement in the whole tree — dragged a second copy of js-yaml, and memoised every file body it ever saw in a cache that is never evicted. Frontmatter parsing is unchanged in behaviour, including the UTF-8 BOM, which this package now strips itself.
|
|
60
|
+
|
|
61
|
+
**Runtime requirements are documented and asserted.** `render`, `frontmatter`, `highlighter`, `search-index` and the React layer require **no Node builtins at all** — the markdown pipeline runs wherever JavaScript does, and Shiki is loaded through its JavaScript regex engine rather than WASM deliberately. `source` needs `node:fs/promises` and `node:path`; `next` adds `node:crypto`. Every one of those is asserted as an exact set, so a new builtin three modules deep fails CI instead of silently ruling out a runtime.
|
|
62
|
+
|
|
63
|
+
**Code blocks have a frame, a title bar and a copy button**, which they did not before — a live render was a bare `<pre>` with no wrapper and no control of any kind:
|
|
64
|
+
|
|
65
|
+
````md
|
|
66
|
+
```ts title="app/page.tsx"
|
|
67
|
+
export default function Page() {}
|
|
68
|
+
```
|
|
69
|
+
````
|
|
70
|
+
|
|
71
|
+
The title becomes the caption, the button's accessible name (`Copy code from app/page.tsx`, not eight controls called "Copy code"), and a search hit. Anything else in the meta string passes through to Shiki untouched, so `{1,3-5}` keeps working when transformers land; a `title=` without double quotes now fails the build naming the document, rather than silently truncating at the first space. Copy is **one delegated listener for the page**, mounted by `DocContent` and only when the page has a fence — not a client component per code block — and the button is `visibility: hidden` until that listener attaches, so a reader with JavaScript off sees no button and finds no dead tab stop.
|
|
72
|
+
|
|
73
|
+
**`excludeLangs` stops shipping as half a feature.** An excluded fence had no background, border, padding or horizontal scroll anywhere in the stylesheet, so it rendered as UA-default text bleeding out of the reading column. It now gets the same surface as a highlighted block, is deliberately left unframed, and the README carries the Mermaid recipe — including the trap that its `<code>` className is an array where Shiki's is a string.
|
|
74
|
+
|
|
75
|
+
**`DocContent` now carries `wave-docs-prose` itself.** Nearly every rule in the stylesheet is scoped under that class, and the hand-rolled route in the README made you type it; forgetting it left a page whose code blocks kept their syntax colours and lost everything else. If you were putting the class on your own `<article>`, drop it — it is emitted once, by the component.
|
|
76
|
+
|
|
77
|
+
**Search is on by default, and the quick start includes its route.** `docs.Layout` renders the trigger, so leaving the route file out gave a reader a control that opened onto "Search is unavailable" — a broken box on the flagship path, under a release note promising search out of the box. The route is one of the three files in the quick start now, and a 404 on the index no longer reads as a transient failure: it names the file to create.
|
|
78
|
+
|
|
79
|
+
**Search is two files now, and neither is a build script.** The index is a prerendered route handler on the object you already hold, so it is rebuilt by the same `next build` that builds your pages — and in `next dev` it re-reads the disk per request, so a page you add is searchable on the next keystroke instead of at the next time you remember to run a script:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// app/docs/search-index.json/route.ts — the whole file
|
|
83
|
+
import { docs } from "@/lib/docs";
|
|
84
|
+
|
|
85
|
+
export const GET = docs.searchIndex;
|
|
86
|
+
export const dynamic = "force-static";
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`docs.searchIndexUrl` is derived from your `basePath`, so it is right at `/`, at `/docs`, and under a nested prefix. **`writeSearchIndex` is removed** — it was the only documented way to build an index, and this replaces it; `buildSearchIndex` and `renderAll()` remain for an artifact the route cannot express, and `docs.searchIndex` is asserted byte-identical to them. Verified against a real `next build`: the body prerenders in both output modes and is byte-identical between them, response headers survive into the prerender manifest, and CI now runs that build on every pull request.
|
|
90
|
+
|
|
91
|
+
**`export const dynamic = 'force-static'` is not optional, and the handler enforces it.** Without it Next re-renders your entire corpus per request, from markdown that output tracing did not put in the deployment bundle — on a serverless host that throws, at the reader, inside the search dialog, and the build prints no warning. It now fails loudly with `code: 'search-index-dynamic'`, naming the file to edit.
|
|
92
|
+
|
|
93
|
+
**`DocsSearch`, at `@waveso/docs/react/next-search`**, is the `'use client'` wrapper around `useRouter()` and `next/link` that every consumer was writing by hand — and skipping `Link` silently cost hover prefetching on every result. `SearchDialog` is unchanged and still host-agnostic.
|
|
94
|
+
|
|
95
|
+
**`SearchDialog`'s `searchOptions` prop is now `miniSearchOptions`**, and `createDocsRoute` takes the same name for the same object. MiniSearch's own name for the query defaults is `searchOptions`, so the old prop produced `searchOptions={{ searchOptions: { fuzzy: 0.1 } }}` — a stutter nobody writes, which is why both README examples were written flat, did not compile, and would not have errored at runtime either.
|
|
96
|
+
|
|
97
|
+
**Your project is no longer traced into your server bundle.** `contentDir` is a string this package cannot resolve statically, so Turbopack fell back to tracing the whole project — every source file, your entire `public/` folder, your last build's output — into the server output for every docs route. Measured at 332 traced files for a three-page site, of which 39 were the project's own. Nothing here reads markdown at request time, so nothing needs tracing, and now nothing is.
|
|
98
|
+
|
|
99
|
+
**Six names are gone**, all pre-1.0. Three are subpaths: the `./react/*` wildcard is enumerated as explicit subpaths, and `./markdown-links` and `./search-options` are no longer exported. Three are options and exports that changed shape, and each needs an edit rather than a rename:
|
|
100
|
+
|
|
101
|
+
| Gone | What to do |
|
|
102
|
+
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `createDocsRoute({ contentId })` | Nothing. The main element always carries `id="docs-content"`, which is what `SkipLink` targets by default. The option could point the skip link at nothing, silently, because `SkipLink` had no matching option to follow it with |
|
|
104
|
+
| `createDocsRoute({ rescanPerRequest })` · `createDocsSitemap({ rescanPerRequest })` | Nothing, if you left it at its default: the content directory is re-scanned per request outside `NODE_ENV=production`, exactly as before. **If you set it to `false`, delete it and measure** — you will now pay a scan per non-production request, and `docs.source.invalidate()` is the explicit tool |
|
|
105
|
+
| `toAliasRoute` from `@waveso/docs/source` | Private. It is one line of string handling and was never documented |
|
|
106
|
+
|
|
107
|
+
Every subpath in `exports` is listed in the README, and so is every runtime name each one exports — `manifest.test.ts` enumerates both against the built output and fails the build on a name this README does not mention. That test is new in this release, and writing it immediately found five: `DOCS_ERROR_PREFIX`, `DEFAULT_DOCS_THEMES`, `CALLOUT_TYPES`, `defaultMarkdownComponents` and `DOCS_CONTENT_ID` were all public and documented nowhere.
|
|
108
|
+
|
|
109
|
+
**The default page is worth looking at.** A 46rem measure and a system font stack, a 1.2 minor-third type scale, tables that scroll instead of shredding the layout, one focus `outline` in place of five `box-shadow` rings (which also deletes the forced-colors block that existed to patch them), and a responsive shell with breakpoints at 64/80/100rem. The element tree, the five layout tokens and the breakpoints are frozen in `docs/adr/001-shell-contract.md`.
|
|
110
|
+
|
|
111
|
+
**Three new harnesses, because the old ones could not see these failures.** A browser tier running real Chromium — jsdom reports every width as `0`, so the measure, the type scale, reflow and the table floor were unassertable. A smoke build of a real Next application against the published `exports` map, in both output modes. And `pnpm check:readme`, which type-checks every example in this README as one project: it immediately found `app/docs/layout.tsx` defined twice with different bodies, and an `imageResolver` example calling `imageSize(path)` when `image-size` v2 takes a `Uint8Array`.
|
|
112
|
+
|
|
113
|
+
## 0.2.0
|
|
114
|
+
|
|
115
|
+
### Minor Changes
|
|
116
|
+
|
|
117
|
+
- 7fefb08: Fix 38 defects found in a pre-publish review. Several are behaviour changes; the ones you can notice are listed first.
|
|
118
|
+
|
|
119
|
+
**Dark mode is now opt-in.** The tokens used to switch on `prefers-color-scheme` alone, but the stylesheet styles the docs subtree rather than the page — so a light-only site with a `/docs` section served near-white text on the host's white background (1.23:1) to every visitor whose OS was in dark mode. Dark now requires `data-theme="dark"` or `class="dark"` on `<html>`; `data-theme="system"` opts back into following the OS. `color-scheme` is declared, so native scrollbars and form controls match.
|
|
120
|
+
|
|
121
|
+
**The `tailwindcss` peer dependency is gone.** Nothing in the package used Tailwind, and declaring it blocked `npm install` outright for any project on Tailwind 3 — npm range-checks an optional peer that happens to be installed. The `@source "./"` directive is gone with it; it was injecting 14 unrequested utilities into every Tailwind consumer's CSS.
|
|
122
|
+
|
|
123
|
+
**`zod` is a dependency now, not a peer**, for the same reason and a larger one: it is imported at module scope, and as a peer its `^4.4.3` range refused to install beside roughly 69% of the Zod in the ecosystem — 47% still on 3.x, plus every 4.x below 4.4.3. Your project's Zod is now irrelevant. `z` is re-exported from `@waveso/docs/frontmatter`, so extending `docFrontmatterSchema` needs no install and cannot pick up a second copy.
|
|
124
|
+
|
|
125
|
+
**Search indexes the full text of a section.** It previously truncated to 300 characters _before_ indexing, dropping ~80% of a normal corpus, and stored none of it for display either. Tokenisation now uses `Intl.Segmenter`, so CJK text is searchable at all — it previously returned zero hits. Both `buildSearchIndex` and `SearchDialog` accept MiniSearch overrides.
|
|
126
|
+
|
|
127
|
+
**Aliases are validated.** `aliases: ['v1:beta']` used to compile to a Next redirect _wildcard_: the build passed, then `/docs/v1-guide` — a real prerendered page — was permanently 308'd away. Metacharacters, relative segments and empty entries are now rejected when the page is read, naming the file. Linking to an alias also fails the build now, naming the page to link instead: an alias is never prerendered, so it was a green build and a hard 404.
|
|
128
|
+
|
|
129
|
+
**Relative images fail the build instead of shipping a broken `src`.** Nothing ever rewrote them, so the browser resolved them against the route and identical markdown requested a different file from every page. Absolute and external sources are unaffected.
|
|
130
|
+
|
|
131
|
+
**A custom `frontmatterSchema` can no longer drop the package's own fields.** All six — `title`, `description`, `label`, `draft`, `aliases`, `order` — are parsed from the raw YAML and laid back over your schema's output, so a custom schema can only ever _add_. A bare `z.object({ title, … })` type-checks but used to strip `draft` and `aliases`, publishing every draft, submitting them to Google, and returning no redirects. The price is that a `.default()`, `.transform()` or `.coerce` aimed at one of the six is not honoured: the YAML wins.
|
|
132
|
+
|
|
133
|
+
**Links to unusual URL schemes are dropped rather than rendered.** `javascript:`, `data:` and `vbscript:` never reach an `href` — including the obfuscated spellings a browser still navigates. The allowlist is GitHub's (`http`, `https`, `mailto`, `tel`, `sms`, `ftp`, `ftps`, `irc`, `ircs`, `xmpp`, `news`, `nntp`, `feed`, `git`, `matrix`); anything else keeps its text, loses its destination, and warns outside production.
|
|
134
|
+
|
|
135
|
+
**Absolute internal links are respelled before they are checked.** `/docs/café` and `/docs/caf%C3%A9` are the same page, and only the encoded form used to match — so the human-readable spelling every editor produces failed the build with "no such page exists" for a page that plainly exists.
|
|
136
|
+
|
|
137
|
+
Also fixed: `siteUrl` with a path silently truncated out of every canonical and the whole sitemap; ` ```JSON `/` ```Bash ` shipping unhighlighted; the search dialog's focus trap breaking on a click, its Close button navigating on Enter, and IME composition being consumed as "open result"; the search index cached forever against the first `indexUrl`; every focus indicator vanishing under Windows High Contrast; long tokens forcing horizontal scroll at 320px; a `draft: true` index page publishing its title as a public sidebar heading; symlinked and `.MD` files vanishing silently; percent-encoded and NFD filenames failing to resolve; `writeSearchIndex` truncating the served file in place; the TOC scrollspy dying permanently when headings mount late; YouTube re-stealing focus on every re-render; markdown images ignoring an author's `loading`; Shiki splicing an invalid `root` node into the tree; `renderAll` running unbounded; and `docs.source.nav()` being one request stale in dev while every page view scanned the filesystem twice over.
|
|
138
|
+
|
|
139
|
+
Every failure now throws with a `code` (`'broken-link'`, `'invalid-alias'`, `'invalid-frontmatter'`, …) and a message naming this package, so a host can branch on the kind of failure instead of matching message text. Where an underlying parser failed — js-yaml on frontmatter, `JSON.parse` on `meta.json` — its own error is attached as `cause`.
|
|
140
|
+
|
|
141
|
+
### API changes
|
|
142
|
+
|
|
143
|
+
- `extractSearchRecords(doc)` takes no options; `ExtractSearchRecordsOptions` and `excerptLength` are gone, since full section text is now indexed.
|
|
144
|
+
- `SearchRecord.id` is a slug (`page#anchor`), not an `href` — an href embedded `basePath`, so moving a site from `/docs` to `/reference` changed the identity of every record.
|
|
145
|
+
- `buildSearchIndex(records, options?)` and `<SearchDialog searchOptions={…}>` accept MiniSearch overrides. They must agree.
|
|
146
|
+
- `DocsSource.drafts()` is new, and `DocsRouteOptions` gains `excludeLangs`, which existed on the renderer and was reachable from nothing.
|
|
147
|
+
- Optional properties across the public option types are now spelled `?: T | undefined`, so a consumer with `exactOptionalPropertyTypes` can pass a possibly-undefined value — `siteUrl: process.env.SITE_URL` was previously a compile error.
|
|
148
|
+
- The CSS custom property `--wave-docs-header-height` is now `--wave-docs-scroll-padding`, which is what it actually controls.
|
|
149
|
+
- `engines.node` is `>=22.12.0`. Node 20 reached end of life in April 2026.
|
|
150
|
+
|
|
151
|
+
## 0.1.0
|
|
152
|
+
|
|
153
|
+
### Minor Changes
|
|
154
|
+
|
|
155
|
+
- adddaea: Initial release.
|
|
156
|
+
|
|
157
|
+
Markdown documentation for Next.js, from one content directory and one pipeline.
|
|
158
|
+
Markdown becomes hast in Node at build time, so the browser receives a tree of
|
|
159
|
+
nodes and a component map — never `unified`, `remark-parse` or Shiki.
|
|
160
|
+
|
|
161
|
+
- `createDocsRoute` wires a content directory to an App Router catch-all, with
|
|
162
|
+
`dynamicParams: false`, a real index route, awaited `params` and a canonical
|
|
163
|
+
URL on every page.
|
|
164
|
+
- Frontmatter is extensible through `frontmatterSchema`, typed as a
|
|
165
|
+
[Standard Schema](https://standardschema.dev) so Zod, Valibot and ArkType all
|
|
166
|
+
work and your fields are inferred with no type argument.
|
|
167
|
+
- Internal `.md` links are rewritten to routes and their targets checked, so a
|
|
168
|
+
link that works on GitHub cannot 404 once published.
|
|
169
|
+
- Table-of-contents ids come from the same `rehype-slug` pass that annotates the
|
|
170
|
+
document, so anchors match by construction rather than by a second parse.
|
|
171
|
+
- GitHub alert syntax, a click-to-load YouTube facade, section-scoped MiniSearch
|
|
172
|
+
records, a sidebar, a scrollspy TOC, a search dialog and a themeable
|
|
173
|
+
stylesheet.
|
|
174
|
+
|
|
175
|
+
- ed73890: Retheming now works from a plain `:root`, and config files highlight.
|
|
176
|
+
|
|
177
|
+
The stylesheet's own guidance — "redefine the tokens in your own `:root`" — could
|
|
178
|
+
not work against it. The dark tokens are `:root:not([data-theme='light'])`, which
|
|
179
|
+
is specificity (0,2,0), so an unlayered `:root` at (0,1,0) lost regardless of load
|
|
180
|
+
order; the cascade never reached source order. Overriding meant `:root:root:root`.
|
|
181
|
+
|
|
182
|
+
Every block now lives in a layer — `theme` for tokens, `base` for resets,
|
|
183
|
+
`components` for classes, declared in that order — and unlayered CSS outranks
|
|
184
|
+
every layer whatever its specificity. Inside the layer the dark blocks still beat
|
|
185
|
+
the light one, so OS following and `data-theme` are unchanged. The README gains a
|
|
186
|
+
Theming section, which it did not have.
|
|
187
|
+
|
|
188
|
+
Added the `ini` and `toml` grammars, and registered `cfg` and `conf` as aliases of
|
|
189
|
+
`ini`. Shiki resolves a fence against a grammar's own aliases rather than against
|
|
190
|
+
this package's loader keys, and `ini` ships only `properties` — so a `` cfg block
|
|
191
|
+
threw `Language 'cfg' not found` and `fallbackLanguage` rendered it as plain text.
|
|
192
|
+
The fence an author writes follows the filename: nobody types ``ini above a file
|
|
193
|
+
called `server.cfg`, and on a FiveM docs site that block is the most-read code on
|
|
194
|
+
the page.
|