@waveso/docs 0.5.0 → 0.7.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 CHANGED
@@ -1,5 +1,256 @@
1
1
  # @waveso/docs
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - da25315: **A page becomes a landing page by declaring its actions.** `actions` in the
8
+ frontmatter turns `title` and `description` into a page header with those links
9
+ beneath them:
10
+
11
+ ```yaml
12
+ ---
13
+ title: Wave Docs
14
+ description: Markdown documentation for Next.js.
15
+ actions:
16
+ - label: Quick start
17
+ href: /getting-started
18
+ - label: GitHub
19
+ href: https://github.com/waveso/docs
20
+ variant: secondary
21
+ ---
22
+ ```
23
+
24
+ Leave it off and the page is exactly what it was: `description` stays a `<meta>`
25
+ tag and the title is the first thing in the prose. That is the whole of the
26
+ adaptation for the two shapes this package serves — documentation that is the
27
+ entire site puts a hero on its index, and documentation mounted at `/docs`
28
+ inside an application that already has a marketing page leaves `actions` off.
29
+ There is no mode, no `standalone` flag and nothing to configure.
30
+
31
+ Actions are primary-then-secondary by position, so the common case needs no
32
+ `variant`. Anything that leaves the site gets a plain `<a>` rather than the
33
+ router link, plus `target`, `rel` and a screen-reader suffix; `mailto:` and
34
+ `tel:` get none of that, because they open no tab. Unsafe hrefs fail the build
35
+ rather than reaching an `<a>` — frontmatter was the one door into an `href` that
36
+ bypassed `isSafeHref`.
37
+
38
+ `DocsHero` is exported at `@waveso/docs/react/hero`, and `DocAction` from
39
+ `@waveso/docs/types`.
40
+
41
+ ⚠️ A HERO PAGE MUST NOT ALSO WRITE ITS OWN `# Title`. `render` normally prepends
42
+ an `<h1>` from `frontmatter.title`; on a hero page the hero renders that heading
43
+ instead, because the tagline and the actions have to sit beneath it. Writing one
44
+ in the body as well ships two `h1`s — the same duplication `titleHeading` has
45
+ always warned about.
46
+
47
+ The background is a rotated line grid drawn with `repeating-linear-gradient`
48
+ rather than an inlined SVG: no data URI in the stylesheet, and the lines are
49
+ `--wave-docs-hero-grid` and `--wave-docs-hero-grid-strong` — Wave 200 and Wave
50
+ 300 from `@waveso/ui` in the light theme, Wave 900 and Wave 800 in the dark one
51
+ — so they follow the theme. They are tokens of their own rather than the border
52
+ colours, because a decorative tint must not be tied to a functional contrast
53
+ ratio.
54
+
55
+ - 915729f: **`docs.Layout` renders no header, and the navigation is one sidebar at every
56
+ width.** A full-width sticky bar is the one element that competes with a host
57
+ application's own for the viewport's top edge, and two of the sites using this
58
+ have one — measured against a fixed 64px host bar, ours landed on top of theirs
59
+ at every width. The chrome it held lives in the sidebar now:
60
+
61
+ ```
62
+ .wave-docs-shell the query container
63
+ └─ .wave-docs-layout the grid
64
+ ├─ .wave-docs-layout__sidebar paints nothing, and moves
65
+ │ ├─ …__sidebar-nav the surface, and the one border
66
+ │ └─ …__sidebar-trigger a 44px strip — paints nothing at rest
67
+ ├─ .wave-docs-layout__sidebar-scrim
68
+ ├─ .wave-docs-layout__main
69
+ └─ .wave-docs-layout__toc
70
+ ```
71
+
72
+ Pressing the trigger translates the sidebar by exactly the navigation's width,
73
+ so it leaves the page entirely and the trigger's outer edge lands on the inline
74
+ start edge. There is no drawer, no `<dialog>`, no second control and no second
75
+ copy of the tree.
76
+
77
+ **Nothing this package renders is anchored to the viewport.** The sidebar is a
78
+ grid item, the trigger is a flex child of it, and the scrim is `absolute` inside
79
+ `.wave-docs-layout` — so every one of them resolves against a box this package
80
+ owns and _your_ layout placed. `position: fixed` is the thing to avoid, and the
81
+ reason is specific: a fixed element is anchored to the viewport you share with
82
+ it, your navbar is in the same viewport, and neither can detect the other. The
83
+ search dialog is the one exception and always was.
84
+
85
+ **There is not one width-based `@media` query left.** Every breakpoint is
86
+ `@container`. `@media` asks how wide the _screen_ is, which is the wrong
87
+ question for a package mounted at `/docs` inside an application that owns the
88
+ rest of the page: put this in a 700px panel on a 1920px monitor and `@media`
89
+ says "wide", the sidebar takes its 16rem column, and the reading column comes
90
+ out around 60px. Two shapes fall out of the container width — beside the
91
+ article, or over it behind a scrim with `inert`, Escape, click-to-dismiss and
92
+ focus moved in and restored.
93
+
94
+ Migration, in full:
95
+
96
+ | Was | Becomes |
97
+ | ------------------------------------------------------------- | ------------------------------------------------------ |
98
+ | `<docs.Layout title={<Brand/>}>` | Delete it. The index page's title brands the docs |
99
+ | `<docs.Layout actions={<ThemeToggle/>}>` | Render it in your own layout, around `docs.Layout` |
100
+ | `--wave-docs-header-height` set so sticky columns clear a bar | `--wave-docs-chrome-offset`, same value |
101
+ | `--wave-docs-header-height` set to size the bar | Nothing sizes a bar. There is no bar |
102
+ | `--wave-docs-shell-width` set to cap the shell | Nothing. `--wave-docs-measure` caps the reading column |
103
+ | `actions` as the client-search escape hatch | `search={false}` plus your own `DocsSearch` |
104
+ | A `rootMargin` you relied on defaulting to `-80px` | Pass it explicitly |
105
+ | `.wave-docs-layout__header` / `__title` / `__actions` | Not rendered |
106
+ | `.wave-docs-layout__sidebar > .wave-docs-sidebar` | The tree is a grandchild now |
107
+
108
+ ⚠️ `--wave-docs-header-height` DID TWO JOBS AND ONLY ONE SURVIVES. It sized the
109
+ header, and it was the offset both sticky columns parked below. Nothing of this
110
+ package's sits above the content any more, so the sizing job is gone; the offset
111
+ job is `--wave-docs-chrome-offset`, and a host with their own fixed bar still
112
+ needs to set it or the sidebar and the table of contents park underneath it.
113
+
114
+ ⚠️ `--wave-docs-chrome-offset` DEFAULTS TO `0rem`, WITH A UNIT, AND THE UNIT IS
115
+ LOAD-BEARING. It is read inside `calc(100dvh - …)`, where a unitless `0` is
116
+ invalid at computed-value time — the height on the sidebar and the `max-height`
117
+ on the table of contents would die rather than resolve to no change.
118
+
119
+ ⚠️ `--wave-docs-shell-width` IS GONE RATHER THAN RENAMED. It capped the whole
120
+ shell and centred it, which pushed the sidebar's inline start 480px in from the
121
+ screen on a 2560px display — and left a _closed_ navigation parked in the
122
+ centring margin instead of off the page. The sidebar owns the page's inline
123
+ start edge at every width now, which is what makes "closed" mean off the screen
124
+ by construction, and the reading column is what is capped.
125
+
126
+ `DocsToc`'s `rootMargin` default changes from `'-80px 0px -60% 0px'` to
127
+ `'0px 0px -60% 0px'`. The 80px reserved room for a sticky header that is no
128
+ longer rendered; a host whose own chrome overlays the content passes the prop.
129
+
130
+ `DocsLayoutProps` is three props — `children`, `search` and `labels`.
131
+
132
+ Two behaviours are lost with the drawer and are worth knowing: the navigation no
133
+ longer opens before hydration (it was a server-rendered
134
+ `<button command="show-modal">`), and the client bundle grows about 500 bytes
135
+ for the containment work `<dialog>` used to give for free.
136
+
137
+ ### Patch Changes
138
+
139
+ - 1313e77: The shell no longer depends on the host shipping a CSS reset.
140
+
141
+ ⚠️ `box-sizing` WAS THE HOST'S TO SET, AND ALMOST EVERY HOST SETS IT.
142
+ `.wave-docs-sidebar__link` is `width: 100%` with `0.5rem` of inline padding, so
143
+ under `content-box` it is a 272px box in a 256px track — and the external-link
144
+ icon that `justify-content: space-between` pins to the far end renders 8px
145
+ outside the sidebar, clipped in half. Tailwind's preflight and every
146
+ normalize-style reset declare `border-box` globally, so this was invisible in
147
+ every project that has one, and visible on the only site here that ships no CSS
148
+ at all. `box-sizing: border-box` now applies to elements carrying a
149
+ `wave-docs-` class, scoped to this package's own namespace rather than to `*`,
150
+ because the prose renders a consumer's components too.
151
+
152
+ Three README claims were wrong and are corrected: `react/*` is ten subpaths and
153
+ not nine, three modules import from `next/*` and not two, and the twenty-two
154
+ chrome labels break down into six groups rather than the five that summed to
155
+ nineteen.
156
+
157
+ ## 0.6.0
158
+
159
+ ### Minor Changes
160
+
161
+ - 633f274: **Anchors are checked now.** A route was verified and its fragment thrown away, so `[setup](./install.md#setup)` built green with no `#setup` anywhere on the page. It is the more common of the two link failures — headings get renamed constantly and nothing renames the links into them — and it went unchecked while the rarer one did not.
162
+
163
+ `onBrokenAnchors` defaults to `'throw'`, and the error names the heading you probably meant:
164
+
165
+ ```
166
+ @waveso/docs: guide.md:12 links to '#instalation', and this page has no
167
+ '#instalation'. Did you mean 'installation'?
168
+ ```
169
+
170
+ Checked against every `id` in the rendered page, not against the table of contents — which captures `h2`–`h3` only, so a link to an `h4` is fine, and so is a link to an id one of your `rehypePlugins` added. Same-page anchors are checked as each page renders, so those errors carry a line number; cross-page anchors need the target's ids and are checked by `docs.renderAll()`, which runs in every build that serves search.
171
+
172
+ **`onUnverifiableLinks` is replaced by `externalRoutes`, and the default flipped.** It shipped in no release, so nothing to migrate.
173
+
174
+ The old option asked you to reason about _our_ inability to verify a link. The new one asks for a fact about _your_ application, which is the thing you actually know:
175
+
176
+ ```ts
177
+ createDocsRoute({
178
+ basePath: "/",
179
+ externalRoutes: ["/login", "/dashboard", "/api/"],
180
+ });
181
+ ```
182
+
183
+ And absolute links at a root mount are now checked by default rather than ignored. A root mount is what you choose when the origin serves documentation and nothing else — `docs.example.com` — so an unknown absolute link there is a typo, and silence was the wrong default. A site that serves something else names what is its own; `/api` covers `/api/keys` and not `/apiary`.
184
+
185
+ That also removes the `'warn'` level that made no sense: warning on every legitimate route in your application is not a diagnostic.
186
+
187
+ **New error code `broken-anchor`**, documented in the troubleshooting table and offered in the bug form.
188
+
189
+ - b6edd50: **New subpath `@waveso/docs/react/next-link`, exporting `DocsLink`** — `next/link` already adapted, so composing a shell by hand no longer needs a cast.
190
+
191
+ Passing `next/link` straight into `DocsSidebar` does not type-check under `exactOptionalPropertyTypes`: Next's `LinkProps` re-declares `onClick?`, `onMouseEnter?` and `onTouchStart?` _without_ `| undefined` while React's anchor props include it, so the two declaration files disagree over three props `next/link` accepts perfectly well at run time. It is a disagreement between dependencies, true of every `next/link` call site in a project with that flag on, and nothing the shape of `DocsLinkProps` can fix without breaking the plain-`<a>` fallback that keeps these components host-agnostic.
192
+
193
+ `docs.Layout` and `DocsSearch` have always absorbed it internally, so it only bit someone building their own shell — who was told in Troubleshooting to write `Link={Link as DocsLinkComponent}` and wait for a Next-wired component to ship. This is that component; the cast is retired and the note now shows the import.
194
+
195
+ ```tsx
196
+ "use client";
197
+ import { DocsLink } from "@waveso/docs/react/next-link";
198
+ import { DocsSidebar } from "@waveso/docs/react/sidebar";
199
+
200
+ <DocsSidebar nav={nav} pathname={pathname} Link={DocsLink} />;
201
+ ```
202
+
203
+ It carries `'use client'` — not for a hook, there is none, but because `DocsLink` is a function and a function cannot be handed from a Server Component to a Client one. Without the directive it would be a server reference and `next build` would refuse it, which is the same boundary this release fixed for MiniSearch options.
204
+
205
+ The private adapter factory it is built from is renamed `link-adapter.ts`, so the two are not one letter apart in the same directory. 180 bytes gzipped, with a 300-byte budget: it should stay the thinnest thing this package ships to a browser.
206
+
207
+ - 1fa4317: **Link checking has severity levels, and broken links now say what you probably meant.**
208
+
209
+ **BREAKING: `assertLinks: boolean` is replaced by `onBrokenLinks: 'throw' | 'warn' | 'ignore'`**, defaulting to `'throw'`. `assertLinks: false` becomes `onBrokenLinks: 'ignore'`; `assertLinks: true` was the default and can be dropped. The shape follows Docusaurus's `onBrokenLinks` for the same reason it exists there: the tool cannot know how much a given site cares, and guessing produces either a build that fails on somebody's legitimate URL or one that ships a dead link quietly.
210
+
211
+ **Broken-link errors now offer the closest published route** when the link looks like a typo of one:
212
+
213
+ ```
214
+ @waveso/docs: guide.md:12 links to './instalation.md', which resolves to
215
+ '/docs/instalation' — no such page exists. Did you mean '/docs/installation'?
216
+ ```
217
+
218
+ A typo is a near-miss by construction, which is what makes the suggestion safe to offer _and_ safe to withhold — the same trick `git`, `tsc`, `cargo` and Python 3.12 use. It decorates an error that was already being raised; it never decides whether to raise one. `/docs/instructions` is five edits from `/docs/installation` — a different word, not a typo — and gets no suggestion, because sending an author to rename a correct link is worse than saying nothing.
219
+
220
+ **New `onUnverifiableLinks`, defaulting to `'ignore'`, closes the root-mount gap.** To check `[x](/setup)` the package must first know it is a documentation link. Under `basePath: '/docs'` the prefix says so. Under `basePath: '/'` there is no prefix — `/setup` may be a page of yours, `/login` almost certainly is — so until now those links were dropped unrecorded and a typo in one shipped silently.
221
+
222
+ They are recorded and marked now, and the site decides:
223
+
224
+ ```ts
225
+ createDocsRoute({
226
+ contentDir: "content/docs",
227
+ basePath: "/",
228
+ onUnverifiableLinks: "throw", // this domain is documentation and nothing else
229
+ });
230
+ ```
231
+
232
+ The default stays `'ignore'` because a root mount inside a larger application genuinely cannot distinguish the two, and failing that build would be wrong. Relative links (`./other.md`) are resolved against the content tree, so they are verifiable at every mount and always governed by `onBrokenLinks`.
233
+
234
+ `docs.wave.so` runs with `onUnverifiableLinks: 'throw'`, which is the configuration this option was written for.
235
+
236
+ ### Patch Changes
237
+
238
+ - 102d6ae: **The README shows the live site instead of screenshots.** Three PNGs, a Playwright script to shoot them, a pinned tag and two tests to keep the pin honest — replaced by a link to [docs.wave.so](https://docs.wave.so), which is this package's documentation built with this package.
239
+
240
+ The screenshots were a photograph of the harness. The site _is_ the harness: the same `site/` that CI builds on every commit, whose acceptance test forbids it a single line of layout CSS of its own. A reader who wants to know what the shell looks like can now use it — open the search, resize to a phone, tab through the drawer — instead of looking at a picture of it taken on somebody's Mac.
241
+
242
+ It also removes a whole class of staleness. A pinned screenshot is wrong the moment the shell changes and right only if someone remembers to re-shoot and re-pin; the last one was pinned to `v0.3.0` while the images had been regenerated for 0.4.0, so npm showed a search dialog the release had already replaced. A URL cannot go stale.
243
+
244
+ `pnpm shoot` is gone. The regression it was meant to catch — a stylesheet change reflowing the shell — is the browser tier's, which asserts geometry rather than pixels and runs in the same Chromium everywhere.
245
+
246
+ - e2bbaf4: **docs.wave.so serves the documentation at its root**, so a page is `docs.wave.so/installation` rather than `docs.wave.so/docs/installation` — a host called `docs` should not say it twice.
247
+
248
+ Nothing in the package changed: `basePath` has always taken any prefix, and `'/'` is one of them. The default is still `/docs`, defined in one place, and every consumer gets it unless they say otherwise.
249
+
250
+ What did change is which configuration the harnesses cover. `smoke/` builds on the default `/docs` in both output modes on every CI run, so moving the site to the root mount loses nothing and covers the half that was thin: an empty base path is a distinct code path in `toHref`, `toRoute` and `isInternalAbsoluteLink`, and two unit assertions used to be all of it. The two harnesses now cover both mount points and both documented layout shapes — smoke keeps the README's one-line `export default docs.Layout`, the site composes `<docs.Layout>` inside a root layout.
251
+
252
+ **One behaviour differs at the root mount, and it is worth knowing.** With an empty base, an absolute link like `/installation` cannot be told apart from any other route in the application, so it is not checked against the published routes — under `/docs`, a typo in `/docs/instalation` fails the build; at the root it does not. Relative markdown links, which is what documentation should be written with, are unaffected.
253
+
3
254
  ## 0.5.0
4
255
 
5
256
  ### Minor Changes
@@ -307,7 +558,7 @@
307
558
 
308
559
  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.
309
560
 
310
- **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`.
561
+ **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.
311
562
 
312
563
  **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`.
313
564