blume 1.6.0 → 1.6.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.
- package/CHANGELOG.md +15 -0
- package/dist/cli/index.js +434 -150
- package/dist/cli/index.js.map +59 -56
- package/dist/types/core/data.d.ts +10 -1
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +6 -0
- package/dist/types/openapi/references.d.ts +5 -0
- package/docs/07-faq.mdx +9 -9
- package/docs/advanced/api-reference.mdx +10 -1
- package/docs/advanced/custom-pages.mdx +3 -1
- package/docs/advanced/graphql.mdx +1 -1
- package/docs/configuration/ai.mdx +4 -0
- package/docs/configuration/seo.mdx +3 -3
- package/docs/configuration/theming.mdx +6 -0
- package/docs/content/components.mdx +8 -1
- package/package.json +53 -53
- package/src/astro/examples.ts +29 -2
- package/src/astro/generate.ts +99 -61
- package/src/astro/index.ts +7 -0
- package/src/astro/markdown-negotiation.ts +1 -1
- package/src/astro/runtime-modules.ts +196 -0
- package/src/astro/templates.ts +241 -38
- package/src/cli/commands/build.ts +7 -1
- package/src/cli/commands/dev.ts +6 -3
- package/src/cli/host-args.ts +18 -0
- package/src/cli/index.ts +2 -1
- package/src/components/copy-feedback.ts +93 -9
- package/src/components/islands/ask-ai.tsx +4 -1
- package/src/components/islands/hooks.ts +3 -1
- package/src/components/layout/PageActions.astro +25 -14
- package/src/core/data.ts +10 -1
- package/src/core/define-components.ts +2 -0
- package/src/core/i18n-ui.ts +1 -0
- package/src/core/includes.ts +2 -1
- package/src/core/manifest.ts +10 -0
- package/src/core/schema.ts +13 -5
- package/src/core/types.ts +6 -0
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +1 -0
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/core/version-cut.ts +5 -3
- package/src/deploy/vercel-negotiation.ts +49 -6
- package/src/og/card.ts +1 -1
- package/src/openapi/references.ts +8 -0
- package/src/openapi/render-mdx.ts +18 -4
- package/src/openapi/scalar.ts +0 -4
- package/src/registry/eject.ts +36 -17
- package/src/theme/entry.ts +2 -2
- package/src/theme/sources.ts +49 -0
|
@@ -82,6 +82,11 @@ export interface BlumeRoute {
|
|
|
82
82
|
alternates: RouteAlternate[];
|
|
83
83
|
/** Astro collection the entry renders through (`"docs"` | `"staged"`). */
|
|
84
84
|
collection: string;
|
|
85
|
+
/**
|
|
86
|
+
* Meta description as the page head renders it (`seo.description` over the
|
|
87
|
+
* front matter `description`), or `null` when the page declares neither.
|
|
88
|
+
*/
|
|
89
|
+
description: string | null;
|
|
85
90
|
draft: boolean;
|
|
86
91
|
/** "Edit this page" URL, or `null` when no repo/source provides one. */
|
|
87
92
|
editUrl: string | null;
|
|
@@ -176,7 +181,11 @@ export interface BlumeDataConfig {
|
|
|
176
181
|
* kept out of this snapshot, which pages serialize into HTML.
|
|
177
182
|
*/
|
|
178
183
|
og: {
|
|
179
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Site-wide card subtitle: `seo.og.description` (`false` omits it) over
|
|
186
|
+
* the site description. A page with its own description shows that
|
|
187
|
+
* instead; this is the fallback for pages without one.
|
|
188
|
+
*/
|
|
180
189
|
description?: string;
|
|
181
190
|
enabled: boolean;
|
|
182
191
|
/** Inlined SVG brand mark; `false` renders the card without any mark. */
|
|
@@ -17,6 +17,7 @@ declare const uiStringsObject: z.ZodObject<{
|
|
|
17
17
|
copyClaudeCode: z.ZodDefault<z.ZodString>;
|
|
18
18
|
copyCode: z.ZodDefault<z.ZodString>;
|
|
19
19
|
copyCodex: z.ZodDefault<z.ZodString>;
|
|
20
|
+
copyFailed: z.ZodDefault<z.ZodString>;
|
|
20
21
|
copyMarkdown: z.ZodDefault<z.ZodString>;
|
|
21
22
|
copyServerUrl: z.ZodDefault<z.ZodString>;
|
|
22
23
|
edit: z.ZodDefault<z.ZodString>;
|
|
@@ -129,6 +130,7 @@ export declare const uiStringsSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
129
130
|
copyClaudeCode: z.ZodDefault<z.ZodString>;
|
|
130
131
|
copyCode: z.ZodDefault<z.ZodString>;
|
|
131
132
|
copyCodex: z.ZodDefault<z.ZodString>;
|
|
133
|
+
copyFailed: z.ZodDefault<z.ZodString>;
|
|
132
134
|
copyMarkdown: z.ZodDefault<z.ZodString>;
|
|
133
135
|
copyServerUrl: z.ZodDefault<z.ZodString>;
|
|
134
136
|
edit: z.ZodDefault<z.ZodString>;
|
|
@@ -477,6 +477,7 @@ declare const openapiSourceSchema: z.ZodObject<{
|
|
|
477
477
|
label: z.ZodOptional<z.ZodString>;
|
|
478
478
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
479
479
|
route: z.ZodOptional<z.ZodString>;
|
|
480
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
480
481
|
spec: z.ZodString;
|
|
481
482
|
}, z.core.$strict>;
|
|
482
483
|
export type OpenApiSource = z.input<typeof openapiSourceSchema>;
|
|
@@ -492,6 +493,7 @@ declare const graphqlSourceSchema: z.ZodObject<{
|
|
|
492
493
|
label: z.ZodOptional<z.ZodString>;
|
|
493
494
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
494
495
|
route: z.ZodOptional<z.ZodString>;
|
|
496
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
495
497
|
spec: z.ZodString;
|
|
496
498
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
497
499
|
}, z.core.$strict>;
|
|
@@ -598,6 +600,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
598
600
|
label: z.ZodOptional<z.ZodString>;
|
|
599
601
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
600
602
|
route: z.ZodOptional<z.ZodString>;
|
|
603
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
601
604
|
spec: z.ZodString;
|
|
602
605
|
}, z.core.$strict>>>;
|
|
603
606
|
spec: z.ZodOptional<z.ZodString>;
|
|
@@ -795,6 +798,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
795
798
|
label: z.ZodOptional<z.ZodString>;
|
|
796
799
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
797
800
|
route: z.ZodOptional<z.ZodString>;
|
|
801
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
798
802
|
spec: z.ZodString;
|
|
799
803
|
endpoint: z.ZodOptional<z.ZodString>;
|
|
800
804
|
}, z.core.$strict>>>;
|
|
@@ -942,6 +946,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
942
946
|
label: z.ZodOptional<z.ZodString>;
|
|
943
947
|
noindex: z.ZodDefault<z.ZodBoolean>;
|
|
944
948
|
route: z.ZodOptional<z.ZodString>;
|
|
949
|
+
seoDescriptionSuffix: z.ZodDefault<z.ZodBoolean>;
|
|
945
950
|
spec: z.ZodString;
|
|
946
951
|
}, z.core.$strict>>>;
|
|
947
952
|
spec: z.ZodOptional<z.ZodString>;
|
|
@@ -347,6 +347,12 @@ export interface RouteManifestEntry {
|
|
|
347
347
|
/** Adapter-supplied "edit this page" URL (non-filesystem sources). */
|
|
348
348
|
editUrl?: string;
|
|
349
349
|
title: string;
|
|
350
|
+
/**
|
|
351
|
+
* The page's meta description as the head renders it: `seo.description`
|
|
352
|
+
* over the front matter `description`. Feeds the generated OG card's
|
|
353
|
+
* subtitle, so a shared link's image and its `og:description` agree.
|
|
354
|
+
*/
|
|
355
|
+
description?: string;
|
|
350
356
|
contentType: string;
|
|
351
357
|
hidden: boolean;
|
|
352
358
|
draft: boolean;
|
|
@@ -47,6 +47,11 @@ export interface ReferenceSource {
|
|
|
47
47
|
includeInSearch: boolean;
|
|
48
48
|
/** Whether generated pages emit noindex metadata and stay out of the sitemap. */
|
|
49
49
|
noindex: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Whether operation meta descriptions end with the generated English
|
|
52
|
+
* "Reference for …" sentence, or carry the spec's own prose alone.
|
|
53
|
+
*/
|
|
54
|
+
seoDescriptionSuffix: boolean;
|
|
50
55
|
/** Local path or `http(s)` URL, verbatim from config. */
|
|
51
56
|
spec: string;
|
|
52
57
|
/**
|
package/docs/07-faq.mdx
CHANGED
|
@@ -87,14 +87,14 @@ We reported it upstream in [oxc-project/oxc#24096](https://github.com/oxc-projec
|
|
|
87
87
|
|
|
88
88
|
Patch oxfmt so it preserves the line break that sits directly against a `:::` fence. Blume ships the same fix in its own repo, and you can apply it in any project.
|
|
89
89
|
|
|
90
|
-
1. Save the patch as `patches/oxfmt@0.
|
|
91
|
-
|
|
92
|
-
```diff patches/oxfmt@0.
|
|
93
|
-
diff --git a/dist/markdown-
|
|
94
|
-
index
|
|
95
|
-
--- a/dist/markdown-
|
|
96
|
-
+++ b/dist/markdown-
|
|
97
|
-
@@ -
|
|
90
|
+
1. Save the patch as `patches/oxfmt@0.66.0.patch`:
|
|
91
|
+
|
|
92
|
+
```diff patches/oxfmt@0.66.0.patch
|
|
93
|
+
diff --git a/dist/markdown-BgZGxhM2.js b/dist/markdown-BgZGxhM2.js
|
|
94
|
+
index 859231f9387a50df16419bc22b5268c4e446ddbc..dcc0ffda6f71adb27a03e3918a3db6fc7a58ffe9 100644
|
|
95
|
+
--- a/dist/markdown-BgZGxhM2.js
|
|
96
|
+
+++ b/dist/markdown-BgZGxhM2.js
|
|
97
|
+
@@ -4872,7 +4872,43 @@ function lu(e, t, r) {
|
|
98
98
|
case "sentence": return Oh(e, r);
|
|
99
99
|
case "word": return t.parser !== "mdx" ? zh(e, t) : Uh(e);
|
|
100
100
|
case "whitespace": {
|
|
@@ -146,7 +146,7 @@ Patch oxfmt so it preserves the line break that sits directly against a `:::` fe
|
|
|
146
146
|
```json package.json
|
|
147
147
|
{
|
|
148
148
|
"patchedDependencies": {
|
|
149
|
-
"oxfmt@0.
|
|
149
|
+
"oxfmt@0.66.0": "patches/oxfmt@0.66.0.patch"
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
```
|
|
@@ -139,6 +139,15 @@ openapi: {
|
|
|
139
139
|
- `includeInLlms: false` keeps them out of both `llms.txt` files.
|
|
140
140
|
- `noindex: true` adds crawler noindex metadata and removes the pages from the sitemap.
|
|
141
141
|
|
|
142
|
+
Each operation page's meta description is the operation's own `description` (or `summary`), followed by a generated sentence naming the endpoint — "Reference for the `GET /pets` endpoint in the Petstore API." — so a spec of terse one-line summaries still ships a distinct, snippet-length description per page. That sentence is English. On a site whose spec prose is written in another language, set `seoDescriptionSuffix: false` on the source to drop it and describe each page with the authored prose alone; an operation with neither a `description` nor a `summary` falls back to its title (`GET /pets`), so no page ships an empty description:
|
|
143
|
+
|
|
144
|
+
```ts blume.config.ts lineNumbers
|
|
145
|
+
openapi: {
|
|
146
|
+
enabled: true,
|
|
147
|
+
sources: [{ spec: "./openapi.de.json", seoDescriptionSuffix: false }],
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
142
151
|
With the [Scalar renderer](#the-scalar-renderer), only `noindex` applies — a Scalar-rendered reference already sits outside Blume's search and `llms.txt`, so the two `include*` settings have nothing to act on there.
|
|
143
152
|
|
|
144
153
|
## Authorization
|
|
@@ -202,7 +211,7 @@ AsyncAPI **2.x specs are normalized to 3.x automatically** with the official Asy
|
|
|
202
211
|
|
|
203
212
|
Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on the `openapi` block; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.
|
|
204
213
|
|
|
205
|
-
Everything documented above carries over, [`playground`](#try-it-for-events) included: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags, and search indexing by operation summary and tag.
|
|
214
|
+
Everything documented above carries over, [`playground`](#try-it-for-events) included: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags (`seoDescriptionSuffix` too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary and tag.
|
|
206
215
|
|
|
207
216
|
Setting `renderer: "scalar"` opts back into the embedded Scalar SPA, where — as with OpenAPI — only `noindex` applies. Scalar has no AsyncAPI playground of its own; its embed auto-detects the document type and renders channels, operations, messages, and a Models section, so that swap trades the composer away.
|
|
208
217
|
|
|
@@ -272,7 +272,9 @@ import data from "blume:data";
|
|
|
272
272
|
|
|
273
273
|
## 404 page
|
|
274
274
|
|
|
275
|
-
Blume ships a default **not found** page out of the box: a centered "404" message wrapped in the site chrome (header, search, theme), served for any unmatched URL. `blume build` writes it to `404.html`, which static hosts serve automatically, and `blume dev` shows it for unknown routes.
|
|
275
|
+
Blume ships a default **not found** page out of the box: a centered "404" message wrapped in the site chrome (header, search, theme), served for any unmatched URL. `blume build` writes it to `404.html`, which static hosts serve automatically, and `blume dev` shows it for unknown routes. Under the message, a **Where to look next** list links every top-level section plus the `sitemap.xml` and [`llms.txt`](/docs/configuration/ai#llmstxt) indexes when they exist, so a reader — or an agent that followed a stale URL — has a way back.
|
|
276
|
+
|
|
277
|
+
The page also has a Markdown twin at `/404.md` with the same recovery links (absolute URLs once [`deployment.site`](/docs/deployment) is set). On a [Vercel server build](/docs/deployment#server-rendering), a request for a missing page that sends [`Accept: text/markdown`](/docs/configuration/ai#content-negotiation), or asks for a `.md` URL no page backs, gets that Markdown body with the `404` status instead of the HTML shell — so an agent never has to parse a page of chrome to learn where to go next.
|
|
276
278
|
|
|
277
279
|
To replace it with your own, add a `pages/404.astro`. It owns the `/404` route the same way `pages/changelog.astro` takes over the changelog — your page wins and the default is dropped. Build it like any other custom page, in `PageLayout` or `RootLayout`:
|
|
278
280
|
|
|
@@ -66,7 +66,7 @@ graphql: {
|
|
|
66
66
|
}
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
Each source takes the same per-source controls
|
|
69
|
+
Each source takes the same [per-source controls](/docs/advanced/api-reference#per-source-indexing) as the OpenAPI block: `includeInSearch`, `includeInLlms`, `noindex`, and `seoDescriptionSuffix` (here the generated sentence names the query, mutation, or type — "Reference for the `pets` query in the GraphQL API.").
|
|
70
70
|
|
|
71
71
|
## Try it playground
|
|
72
72
|
|
|
@@ -81,6 +81,8 @@ The `.md` variant _downlevels_ components to plain Markdown for consumers that c
|
|
|
81
81
|
|
|
82
82
|
Agents don't need to know the `.md` convention: requesting a page's own URL with an [`Accept: text/markdown`](https://acceptmarkdown.com) header serves the Markdown variant at the same address, with `Vary: Accept` so caches keep the two apart. The dev server honors the header out of the box, and a [Vercel or Cloudflare server build](/docs/deployment#server-rendering) wires the same negotiation into the deploy automatically — routing rules on Vercel, a generated Worker on Cloudflare — no configuration needed. The homepage always negotiates, even when it's a custom landing page rather than a content page: its Markdown mirror falls back to the [`llms.txt`](#llmstxt) index, so an agent asking the site root for Markdown gets the machine-readable map of the site. Markdown responses also carry an `x-markdown-tokens` header — an estimated token count (~4 characters per token), following the convention of [Cloudflare's Markdown for Agents](https://developers.cloudflare.com/fundamentals/reference/markdown-for-agents/) — on every surface where Blume controls response headers: the dev server, server-rendered responses, and the negotiated homepage on Vercel and Cloudflare. Other deploy targets serve prerendered pages from a static layer with no request-time hook, so agents there fetch the `.md` URL directly; the [agent readability manifest](#agent-readability) advertises `contentNegotiation` only on deployments that honor the header.
|
|
83
83
|
|
|
84
|
+
Missing pages negotiate too. Every build emits a Markdown [404 page](/docs/advanced/custom-pages#404-page) at `/404.md` — the not-found message followed by recovery links to every top-level section, the sitemap, and `llms.txt` — and on Vercel a request for a nonexistent URL that prefers Markdown, or any `.md` URL with no page behind it, gets that body with a real `404` status rather than the HTML shell.
|
|
85
|
+
|
|
84
86
|
### Custom component serializers
|
|
85
87
|
|
|
86
88
|
Give your own components a Markdown form with `ai.markdownComponents` — a map of JSX name to serializer. Each serializer receives the component's `props` (statically evaluated from the MDX attributes, with the page's `frontmatter` in scope), its `children` (already downleveled to Markdown), and the page's `frontmatter` data, and returns the replacement — or `null` to leave the JSX as-is:
|
|
@@ -109,6 +111,8 @@ Serializers live in `blume.config.ts`, not `components.tsx`: the config file is
|
|
|
109
111
|
|
|
110
112
|
Every page carries a **Copy as Markdown** action — in the [page actions](/docs/content/navigation#page-actions) beneath the table of contents — that copies the page's raw Markdown to the clipboard. It's the same source served at the [`.md` URL](#raw-markdown) above, ready to paste into an LLM, an issue, or your notes. It's available on every page, in dev and production, with no configuration.
|
|
111
113
|
|
|
114
|
+
Where the Clipboard API is unavailable or the browser denies it — in-app browsers, WebViews, insecure origins — the action falls back to the legacy copy command, and if nothing lands on the clipboard the button reports **Copy failed** (localized via `actions.copyFailed`) rather than staying silent. The same fallback backs every copy button Blume renders.
|
|
115
|
+
|
|
112
116
|
## Open in chat
|
|
113
117
|
|
|
114
118
|
The **Open in chat** action opens the current page in an AI assistant — v0, ChatGPT, Claude, T3 Chat, Scira, or Cursor — pre-filled with a prompt that points it at the page's raw Markdown so it can answer questions about what you're reading:
|
|
@@ -125,7 +125,7 @@ seo: {
|
|
|
125
125
|
}
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
By default, each card is derived from your content and theme — the **page title** as the headline, your **site title** as the eyebrow, and your theme **accent** for the mark. Images are served at `/og/<slug>.png`, mirroring each route, and are prerendered as static files even in server mode:
|
|
128
|
+
By default, each card is derived from your content and theme — the **page title** as the headline, the **page description** as the subtitle (the same text as its `og:description`, so `seo.description` wins over `description`), your **site title** as the eyebrow, and your theme **accent** for the mark. Images are served at `/og/<slug>.png`, mirroring each route, and are prerendered as static files even in server mode:
|
|
129
129
|
|
|
130
130
|
| Page route | Image URL |
|
|
131
131
|
| ------------------- | -------------------------- |
|
|
@@ -151,13 +151,13 @@ Emoji in a page title or site title render as [Twemoji](https://github.com/jdeck
|
|
|
151
151
|
|
|
152
152
|
### Show, hide, or override card layers
|
|
153
153
|
|
|
154
|
-
Beyond the headline, the card carries three optional layers: the **brand mark** in the top-left (your logo, or an accent tile with the site title's initial), the **subtitle** under the headline (your site `description`), and a **footer** with your repo slug (from `github`) and the site's URL — the deployment site's host plus [`deployment.base`](/docs/deployment#subpath-deploys), so a GitHub Pages project site reads `user.github.io/repo`. Override any of them with a string of your own, or hide one with `false`:
|
|
154
|
+
Beyond the headline, the card carries three optional layers: the **brand mark** in the top-left (your logo, or an accent tile with the site title's initial), the **subtitle** under the headline (the page `description`, or your site `description` for pages without one), and a **footer** with your repo slug (from `github`) and the site's URL — the deployment site's host plus [`deployment.base`](/docs/deployment#subpath-deploys), so a GitHub Pages project site reads `user.github.io/repo`. Override any of them with a string of your own, or hide one with `false`:
|
|
155
155
|
|
|
156
156
|
```ts blume.config.ts lineNumbers
|
|
157
157
|
seo: {
|
|
158
158
|
og: {
|
|
159
159
|
site: "docs.acme.com", // footer URL text, or false to hide it
|
|
160
|
-
description: false, // hide the subtitle; a string
|
|
160
|
+
description: false, // hide the subtitle on every card; a string replaces the site fallback
|
|
161
161
|
logo: false, // no brand mark at all — not even the initial tile
|
|
162
162
|
},
|
|
163
163
|
}
|
|
@@ -206,6 +206,12 @@ Drop a `theme.css` in your project root to override any design token. It's the l
|
|
|
206
206
|
|
|
207
207
|
Set a token under `:root` for light mode and under `:root[data-theme="dark"]` for dark mode. Color tokens have distinct built-in dark values declared at the dark selector's higher specificity, so a `:root`-only override of `--blume-accent`, `--blume-background`, and friends applies to light mode only — declare the dark block too when both modes should change.
|
|
208
208
|
|
|
209
|
+
`theme.css` is inlined into the site's Tailwind entry, so Tailwind directives work in it too. The one to know for a monorepo is `@source`: Blume scans your project for utility classes, and a page that imports components from a sibling workspace package needs that package scanned as well. Point at it relative to `theme.css` — the standard Tailwind rule — and Blume carries the path into the generated sheet:
|
|
210
|
+
|
|
211
|
+
```css theme.css lineNumbers
|
|
212
|
+
@source "../../packages/ui/src";
|
|
213
|
+
```
|
|
214
|
+
|
|
209
215
|
### Design tokens
|
|
210
216
|
|
|
211
217
|
| Token | Controls |
|
|
@@ -567,7 +567,7 @@ The card reads the instance from [`github.host`](/docs/configuration#github-ente
|
|
|
567
567
|
|
|
568
568
|
`Component` renders an example file from your project's `examples/` directory as a live preview alongside its highlighted source, in tabs. Point it at a file with `path` — its location under `examples/`, without the extension (so `examples/counter.tsx` is `path="counter"`). React, Vue, Svelte, and Astro examples are all supported; framework examples hydrate, Astro ones render statically. It keeps the preview and the code in sync from a single file.
|
|
569
569
|
|
|
570
|
-
The preview renders in an isolated frame that the docs styles never reach — no prose margins, typography, or theme chrome bleed into your component. The frame gets Tailwind (preflight + utilities scanned from your
|
|
570
|
+
The preview renders in an isolated frame that the docs styles never reach — no prose margins, typography, or theme chrome bleed into your component. The frame gets Tailwind (preflight + utilities scanned from your project and your examples directory), Blume's design tokens so classes like `bg-background` follow the site palette by default, and it follows the site's light/dark toggle live. The pane sizes itself to the rendered example — and keeps tracking it if the example grows or shrinks after load — with the Preview and Code tabs sharing one height so toggling them never shifts the page.
|
|
571
571
|
|
|
572
572
|
To style previews with your own design system — say, shadcn variables — point `examples.css` at a stylesheet. It's injected into every preview frame after Blume's defaults, so your tokens win. Don't `@import "tailwindcss"` in it; the frame already provides Tailwind. Both `.dark` and `[data-theme="dark"]` work for dark-mode overrides:
|
|
573
573
|
|
|
@@ -623,6 +623,13 @@ export default defineConfig({
|
|
|
623
623
|
<Component path="file-list/examples/basic" />
|
|
624
624
|
```
|
|
625
625
|
|
|
626
|
+
In a monorepo, the components your examples import usually live in a sibling workspace package. Tailwind's `@source` scans files, not imports: Blume scans your project and the `examples` directory (wherever `source` points, even outside the project), so a class used only inside that sibling package isn't generated until you add the package to the scan. Do that with an `@source` directive in `examples.css` (for the preview frames) or `theme.css` (for the site), written relative to the file it sits in — the standard Tailwind rule — and Blume carries it into the generated sheet:
|
|
627
|
+
|
|
628
|
+
```css
|
|
629
|
+
/* examples/theme.css */
|
|
630
|
+
@source "../../../packages/ui/src";
|
|
631
|
+
```
|
|
632
|
+
|
|
626
633
|
<Component path="counter" />
|
|
627
634
|
|
|
628
635
|
```astro
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blume",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Documentation that's fast, AI-ready, and zero-config.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -66,107 +66,107 @@
|
|
|
66
66
|
"typecheck": "tsgo --noEmit && tsgo -p test/tsconfig.json --noEmit"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@astrojs/check": "^0.9.
|
|
70
|
-
"@astrojs/markdown-satteri": "^0.
|
|
71
|
-
"@astrojs/mdx": "^
|
|
72
|
-
"@astrojs/node": "^11.
|
|
73
|
-
"@astrojs/react": "^6.0.
|
|
74
|
-
"@astrojs/vercel": "^11.0.
|
|
69
|
+
"@astrojs/check": "^0.9.10",
|
|
70
|
+
"@astrojs/markdown-satteri": "^0.4.0",
|
|
71
|
+
"@astrojs/mdx": "^8.0.0",
|
|
72
|
+
"@astrojs/node": "^11.1.5",
|
|
73
|
+
"@astrojs/react": "^6.0.5",
|
|
74
|
+
"@astrojs/vercel": "^11.0.10",
|
|
75
75
|
"@asyncapi/converter": "^2.0.2",
|
|
76
76
|
"@clack/prompts": "^1.7.0",
|
|
77
|
-
"@iconify-json/lucide": "^1.2.
|
|
77
|
+
"@iconify-json/lucide": "^1.2.129",
|
|
78
78
|
"@iconify/types": "^2.0.0",
|
|
79
|
-
"@iconify/utils": "^3.1.
|
|
80
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
79
|
+
"@iconify/utils": "^3.1.5",
|
|
80
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
81
81
|
"@orama/orama": "^3.1.18",
|
|
82
|
-
"@pierre/diffs": "^1.
|
|
83
|
-
"@scalar/astro": "^0.4.
|
|
82
|
+
"@pierre/diffs": "^1.4.1",
|
|
83
|
+
"@scalar/astro": "^0.4.17",
|
|
84
84
|
"@scalar/openapi-parser": "^0.29.0",
|
|
85
|
-
"@scalar/openapi-types": "^0.9.
|
|
86
|
-
"@shikijs/transformers": "^4.
|
|
87
|
-
"@shikijs/twoslash": "^4.
|
|
85
|
+
"@scalar/openapi-types": "^0.9.5",
|
|
86
|
+
"@shikijs/transformers": "^4.4.3",
|
|
87
|
+
"@shikijs/twoslash": "^4.4.3",
|
|
88
88
|
"@tailwindcss/typography": "^0.5.20",
|
|
89
|
-
"@tailwindcss/vite": "^4",
|
|
89
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
90
90
|
"@types/mdast": "^4.0.4",
|
|
91
91
|
"@vercel/analytics": "^2.0.1",
|
|
92
|
-
"ai": "^7.0.
|
|
93
|
-
"astro": "^7.
|
|
92
|
+
"ai": "^7.0.93",
|
|
93
|
+
"astro": "^7.3.1",
|
|
94
94
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
95
95
|
"chokidar": "^5.0.0",
|
|
96
|
-
"citty": "^0.
|
|
97
|
-
"consola": "^3.4.
|
|
96
|
+
"citty": "^0.2.2",
|
|
97
|
+
"consola": "^3.4.2",
|
|
98
98
|
"cross-spawn": "^7.0.6",
|
|
99
|
-
"dompurify": "^3.4.
|
|
99
|
+
"dompurify": "^3.4.14",
|
|
100
100
|
"dotenv": "^17.4.2",
|
|
101
101
|
"epub-gen-memory": "^1.1.2",
|
|
102
|
-
"fast-xml-parser": "^5.
|
|
102
|
+
"fast-xml-parser": "^5.11.1",
|
|
103
103
|
"github-slugger": "^2.0.0",
|
|
104
104
|
"graphql": "^17.0.2",
|
|
105
105
|
"gray-matter": "^4.0.3",
|
|
106
106
|
"html-escaper": "^3.0.3",
|
|
107
107
|
"image-size": "^2.0.2",
|
|
108
|
-
"jiti": "^2.
|
|
108
|
+
"jiti": "^2.7.0",
|
|
109
109
|
"js-yaml": "^5.4.1",
|
|
110
|
-
"katex": "^0.18.
|
|
110
|
+
"katex": "^0.18.6",
|
|
111
111
|
"markdown-table": "^3.0.4",
|
|
112
|
-
"marked": "^18.0.
|
|
112
|
+
"marked": "^18.0.11",
|
|
113
113
|
"mdast-util-from-markdown": "^2.0.3",
|
|
114
114
|
"mdast-util-gfm": "^3.1.0",
|
|
115
115
|
"mdast-util-to-string": "^4.0.0",
|
|
116
116
|
"medium-zoom": "^1.1.0",
|
|
117
|
-
"mermaid": "^11.
|
|
117
|
+
"mermaid": "^11.17.2",
|
|
118
118
|
"micromark-extension-gfm": "^3.0.0",
|
|
119
119
|
"nanotar": "^0.3.0",
|
|
120
|
-
"node-html-parser": "^9.0.
|
|
121
|
-
"openapi-sampler": "^1.7.
|
|
122
|
-
"p-limit": "^7.3.
|
|
123
|
-
"p-map": "^7.0.
|
|
124
|
-
"p-retry": "^8.0.
|
|
120
|
+
"node-html-parser": "^9.0.3",
|
|
121
|
+
"openapi-sampler": "^1.7.5",
|
|
122
|
+
"p-limit": "^7.3.2",
|
|
123
|
+
"p-map": "^7.0.7",
|
|
124
|
+
"p-retry": "^8.0.1",
|
|
125
125
|
"package-manager-detector": "^1.8.0",
|
|
126
|
-
"pagefind": "^1.
|
|
127
|
-
"pathe": "^2.0.
|
|
126
|
+
"pagefind": "^1.5.2",
|
|
127
|
+
"pathe": "^2.0.3",
|
|
128
128
|
"perfect-debounce": "^2.1.0",
|
|
129
|
-
"picomatch": "^4.0.
|
|
129
|
+
"picomatch": "^4.0.7",
|
|
130
130
|
"react": "^19.2.8",
|
|
131
131
|
"react-dom": "^19.2.8",
|
|
132
132
|
"robots-parser": "^3.0.1",
|
|
133
133
|
"satteri": "^0.10.5",
|
|
134
134
|
"semver": "^7.8.5",
|
|
135
|
-
"sharp": "^0.35.
|
|
136
|
-
"shiki": "^4.
|
|
135
|
+
"sharp": "^0.35.4",
|
|
136
|
+
"shiki": "^4.4.3",
|
|
137
137
|
"simple-icons": "^16.29.0",
|
|
138
|
-
"string-width": "^8.
|
|
138
|
+
"string-width": "^8.2.2",
|
|
139
139
|
"sucrase": "^3.35.1",
|
|
140
140
|
"tailwindcss": "^4.3.3",
|
|
141
|
-
"takumi-js": "^2.
|
|
142
|
-
"tinyglobby": "^0.2.
|
|
141
|
+
"takumi-js": "^2.13.6",
|
|
142
|
+
"tinyglobby": "^0.2.17",
|
|
143
143
|
"twoslash": "^0.3.9",
|
|
144
144
|
"typescript": "^6.0.3",
|
|
145
145
|
"ufo": "^1.6.4",
|
|
146
|
-
"undici": "^8.
|
|
146
|
+
"undici": "^8.10.2",
|
|
147
147
|
"write-file-atomic": "^8.0.0",
|
|
148
|
-
"zod": "^4.
|
|
148
|
+
"zod": "^4.5.4"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
151
|
-
"@ai-sdk/openai-compatible": "^3.0.
|
|
152
|
-
"@mixedbread/sdk": "^0.
|
|
151
|
+
"@ai-sdk/openai-compatible": "^3.0.44",
|
|
152
|
+
"@mixedbread/sdk": "^0.77.0",
|
|
153
153
|
"@notionhq/client": "^5.26.0",
|
|
154
154
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
155
|
-
"@oramacloud/client": "^2.1.
|
|
156
|
-
"@sanity/client": "^8.
|
|
155
|
+
"@oramacloud/client": "^2.1.4",
|
|
156
|
+
"@sanity/client": "^8.5.0",
|
|
157
157
|
"@types/cross-spawn": "^6.0.6",
|
|
158
158
|
"@types/html-escaper": "^3.0.4",
|
|
159
|
-
"@types/node": "^22.
|
|
159
|
+
"@types/node": "^22.20.1",
|
|
160
160
|
"@types/picomatch": "^4.0.3",
|
|
161
161
|
"@types/react": "^19.2.18",
|
|
162
|
-
"@types/react-dom": "^19.
|
|
162
|
+
"@types/react-dom": "^19.2.7",
|
|
163
163
|
"@types/semver": "^7.8.0",
|
|
164
164
|
"@types/write-file-atomic": "^4.0.3",
|
|
165
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
166
|
-
"algoliasearch": "^5.
|
|
167
|
-
"bun-types": "^1.
|
|
168
|
-
"flexsearch": "^0.8.
|
|
169
|
-
"typesense": "^3.0.
|
|
165
|
+
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|
|
166
|
+
"algoliasearch": "^5.57.0",
|
|
167
|
+
"bun-types": "^1.4.2",
|
|
168
|
+
"flexsearch": "^0.8.212",
|
|
169
|
+
"typesense": "^3.0.6"
|
|
170
170
|
},
|
|
171
171
|
"peerDependencies": {
|
|
172
172
|
"@ai-sdk/openai-compatible": "^3.0.0",
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
"@astrojs/netlify": "^8.0.0",
|
|
175
175
|
"@astrojs/svelte": "^9.0.0",
|
|
176
176
|
"@astrojs/vue": "^7.0.0",
|
|
177
|
-
"@mixedbread/sdk": "^0.
|
|
177
|
+
"@mixedbread/sdk": "^0.77.0",
|
|
178
178
|
"@notionhq/client": "^5.0.0",
|
|
179
179
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
180
180
|
"@oramacloud/client": "^2.1.0",
|
package/src/astro/examples.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
|
|
3
3
|
import pMap from "p-map";
|
|
4
|
-
import { join, relative } from "pathe";
|
|
4
|
+
import { isAbsolute, join, relative } from "pathe";
|
|
5
5
|
import { glob } from "tinyglobby";
|
|
6
6
|
|
|
7
7
|
import type { ExampleLookup } from "../core/types.ts";
|
|
@@ -28,6 +28,11 @@ export interface ExampleSpec {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface ExampleDiscovery {
|
|
31
|
+
/**
|
|
32
|
+
* Absolute directory the examples were discovered under: the configured
|
|
33
|
+
* `examples.source` (or a glob's static prefix) resolved against the root.
|
|
34
|
+
*/
|
|
35
|
+
dir: string;
|
|
31
36
|
examples: ExampleSpec[];
|
|
32
37
|
warnings: string[];
|
|
33
38
|
}
|
|
@@ -56,6 +61,28 @@ const DEFAULT_EXAMPLE_GLOB = "**/*.{astro,jsx,svelte,tsx,vue}";
|
|
|
56
61
|
/** Ceiling on concurrent example-file reads; unbounded fan-out risks EMFILE. */
|
|
57
62
|
const READ_CONCURRENCY = 16;
|
|
58
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Files the preview-frame Tailwind entry scans for utility classes, appended
|
|
66
|
+
* to each directory from `exampleScanRoots`.
|
|
67
|
+
*/
|
|
68
|
+
export const EXAMPLE_SCAN_GLOB = "**/*.{astro,jsx,svelte,ts,tsx,vue}";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Directories the `<Component />` preview sheet scans for utility classes:
|
|
72
|
+
* the project root, plus the examples directory when it lives outside the
|
|
73
|
+
* root (a sibling workspace package, say). Tailwind's `@source` is a file
|
|
74
|
+
* glob, not an import graph, so an out-of-root examples directory would
|
|
75
|
+
* otherwise contribute no utilities and previews would render half-styled.
|
|
76
|
+
*/
|
|
77
|
+
export const exampleScanRoots = (
|
|
78
|
+
root: string,
|
|
79
|
+
examplesDir: string
|
|
80
|
+
): string[] => {
|
|
81
|
+
const path = relative(root, examplesDir);
|
|
82
|
+
const outside = path.startsWith("..") || isAbsolute(path);
|
|
83
|
+
return outside ? [root, examplesDir] : [root];
|
|
84
|
+
};
|
|
85
|
+
|
|
59
86
|
// Glob magic that turns `examples` from a plain directory into a pattern. `()`,
|
|
60
87
|
// `@`, and `+` are excluded so literal path segments (npm scopes, parens) keep
|
|
61
88
|
// resolving as directories; the extglob leads `*?!` still trigger here.
|
|
@@ -153,7 +180,7 @@ export const discoverExamples = async (
|
|
|
153
180
|
collectExample(file, sources[index] ?? "");
|
|
154
181
|
}
|
|
155
182
|
|
|
156
|
-
return { examples, warnings };
|
|
183
|
+
return { dir, examples, warnings };
|
|
157
184
|
};
|
|
158
185
|
|
|
159
186
|
/**
|