blume 0.5.4 → 0.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/dist/cli/index.js +759 -406
- package/dist/cli/index.js.map +27 -25
- package/dist/types/core/config-input.d.ts +759 -0
- package/dist/types/core/config.d.ts +126 -3
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +50 -0
- package/dist/types/core/schema.d.ts +334 -62
- package/dist/types/core/types.d.ts +8 -0
- package/dist/types/index.d.ts +2 -1
- package/docs/advanced/changelog.mdx +10 -2
- package/docs/configuration/ai.mdx +56 -0
- package/docs/configuration/index.mdx +0 -2
- package/docs/configuration/seo.mdx +59 -1
- package/docs/configuration/theming.mdx +14 -9
- package/docs/content/meta.mdx +3 -17
- package/docs/content/navigation.mdx +41 -4
- package/docs/content/syntax.mdx +4 -8
- package/package.json +3 -1
- package/src/ai/agent-readability.ts +97 -0
- package/src/ai/ask-context.ts +131 -8
- package/src/ai/ask-data.ts +4 -1
- package/src/astro/generate.ts +40 -11
- package/src/astro/templates.ts +90 -10
- package/src/cli/commands/build.ts +41 -1
- package/src/cli/commands/dev.ts +31 -14
- package/src/cli/dev-lock.ts +94 -21
- package/src/components/content/GithubInfo.astro +11 -10
- package/src/components/content/TypeTable.astro +8 -3
- package/src/components/content/Update.astro +12 -2
- package/src/components/content/changelog-element.ts +62 -0
- package/src/components/islands/AskAI.astro +66 -2
- package/src/components/islands/ask-ai.tsx +289 -53
- package/src/components/layout/Header.astro +1 -1
- package/src/components/layout/NavTree.astro +1 -1
- package/src/components/layout/PageActions.astro +73 -30
- package/src/components/layout/RootLayout.astro +79 -10
- package/src/core/config-input.ts +933 -0
- package/src/core/config.ts +126 -3
- package/src/core/data.ts +4 -0
- package/src/core/graph.ts +7 -2
- package/src/core/i18n-ui.ts +5 -0
- package/src/core/nav-diagnostics.ts +7 -0
- package/src/core/navigation.ts +38 -12
- package/src/core/schema.ts +130 -22
- package/src/core/sources/filesystem.ts +5 -1
- package/src/core/sources/watch.ts +43 -12
- package/src/core/types.ts +9 -0
- package/src/deploy/adapter-output.ts +82 -0
- package/src/deploy/robots.ts +37 -4
- package/src/index.ts +1 -1
- package/src/markdown/index.ts +28 -30
- package/src/markdown/math.ts +3 -2
- package/src/openapi/scalar.ts +1 -1
- package/src/registry/eject.ts +21 -14
- package/src/search/documents.ts +9 -2
- package/src/theme/entry.ts +7 -3
- package/src/theme/palette.ts +21 -14
|
@@ -168,11 +168,19 @@ export interface NavSelector {
|
|
|
168
168
|
kind: "dropdown" | "language" | "product" | "version";
|
|
169
169
|
items: NavSelectorItem[];
|
|
170
170
|
}
|
|
171
|
+
/** A pinned link rendered above the sidebar sections (external or internal). */
|
|
172
|
+
export interface FeaturedLink {
|
|
173
|
+
label: string;
|
|
174
|
+
href: string;
|
|
175
|
+
icon?: string;
|
|
176
|
+
}
|
|
171
177
|
/** The complete navigation model derived from the content graph. */
|
|
172
178
|
export interface Navigation {
|
|
173
179
|
tabs: NavTab[];
|
|
174
180
|
selectors: NavSelector[];
|
|
175
181
|
sidebar: NavNode[];
|
|
182
|
+
/** Pinned links shown above the sidebar sections, unscoped by tab. */
|
|
183
|
+
featured: FeaturedLink[];
|
|
176
184
|
/** Repo URL for the header link, or null when hidden (`navigation.repo`). */
|
|
177
185
|
repoUrl?: string | null;
|
|
178
186
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type { ComponentOverride, ComponentOverrides, IslandDescriptor, } from ".
|
|
|
5
5
|
export { defineMeta } from "./core/define-meta.ts";
|
|
6
6
|
export type { FolderMetaDefinition, FolderMetaFactory, } from "./core/define-meta.ts";
|
|
7
7
|
export type { UIStrings } from "./core/i18n-ui.ts";
|
|
8
|
-
export type { BlumeConfig
|
|
8
|
+
export type { BlumeConfig } from "./core/config-input.ts";
|
|
9
|
+
export type { FolderMeta, HydrationMode, ResolvedConfig, } from "./core/schema.ts";
|
|
9
10
|
export type { Diagnostic, Heading, NavNode, Navigation, NavTab, PageRecord, } from "./core/types.ts";
|
|
10
11
|
export { getBlumeVersion } from "./core/version.ts";
|
|
@@ -53,14 +53,22 @@ The optional `changelog` object adds richer metadata for the timeline and feed:
|
|
|
53
53
|
|
|
54
54
|
## The timeline page
|
|
55
55
|
|
|
56
|
-
Once you have at least one `type: changelog` entry, Blume generates a **`/changelog`** page automatically.
|
|
56
|
+
Once you have at least one `type: changelog` entry, Blume generates a **`/changelog`** page automatically. It renders as a focused, full-width timeline — no sidebar or table of contents — with each entry newest-first, showing its date, label, and `category` tag in a left rail beside its content:
|
|
57
57
|
|
|
58
|
-
- The entry **title** becomes its label — or `v{version}` when there's no title.
|
|
58
|
+
- The entry **title** becomes its label — or `v{version}` when there's no title. It links to that entry's own page, so a release is both a line in the timeline and a shareable permalink.
|
|
59
59
|
- The `category` renders as a tag next to the date.
|
|
60
60
|
- Drafts and `sidebar.hidden` entries are skipped.
|
|
61
61
|
|
|
62
62
|
The page appears only when nothing already occupies the `/changelog` route. To replace it with your own design, add a [custom page](/docs/advanced/custom-pages) at `pages/changelog.astro` — it takes over and Blume stops generating the default timeline.
|
|
63
63
|
|
|
64
|
+
### Grouped by major version
|
|
65
|
+
|
|
66
|
+
When your versions follow [semver](https://semver.org) and span more than one major, Blume paginates the timeline by major version. Only the newest major line is shown, with a **Show N.x releases** button at the bottom that reveals the next-oldest major one click at a time:
|
|
67
|
+
|
|
68
|
+
- Detection is automatic — no configuration. It kicks in only when every listed release parses as `major.minor.patch` and there is more than one major; otherwise the timeline stays flat.
|
|
69
|
+
- It tolerates the scoped tags monorepos publish, so `pkg@2.0.0` groups under `2.x` and `pkg@1.4.0` under `1.x`.
|
|
70
|
+
- It's progressive enhancement: every release is still in the page's HTML (and its RSS feed and search index), so readers without JavaScript — and crawlers — see the complete history. The button only collapses older majors once the page hydrates.
|
|
71
|
+
|
|
64
72
|
## From GitHub Releases
|
|
65
73
|
|
|
66
74
|
Rather than authoring entries by hand, point the built-in [`github-releases` source](/docs/content/sources#github-releases) at a repo and every release becomes a `type: changelog` entry — the same timeline and feed, fed straight from the releases you already publish:
|
|
@@ -62,6 +62,25 @@ ai: {
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
### Suggested questions
|
|
66
|
+
|
|
67
|
+
Seed the empty state with a few starter prompts. Each renders as a clickable suggestion — click one to send it — with an optional [Lucide icon](/docs/content/components#icon) beside the label:
|
|
68
|
+
|
|
69
|
+
```ts blume.config.ts lineNumbers
|
|
70
|
+
ai: {
|
|
71
|
+
ask: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
suggestions: [
|
|
74
|
+
{ label: "What is Blume?", icon: "rocket" },
|
|
75
|
+
{ label: "How do I write a docs page?", icon: "file-text" },
|
|
76
|
+
{ label: "How do I configure the theme?", icon: "settings" },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`label` is the question that gets asked; `icon` is optional. Leave `suggestions` unset (or empty) and the panel opens to a plain input.
|
|
83
|
+
|
|
65
84
|
### Grounding
|
|
66
85
|
|
|
67
86
|
Ask AI is **grounded in your docs**. For each question it retrieves the most relevant pages — using the same lexical [Orama](/docs/configuration/search) index that powers on-page search — and injects them into the model's system prompt, so answers come from your content instead of the model's own knowledge. The assistant is told to answer only from the retrieved pages, to say when something isn't covered, and to cite the pages it drew from.
|
|
@@ -175,6 +194,43 @@ A static build with `mcp.enabled` fails fast with a message telling you to set `
|
|
|
175
194
|
claude mcp add --transport http my-docs https://docs.example.com/mcp
|
|
176
195
|
```
|
|
177
196
|
|
|
197
|
+
## Agent readability
|
|
198
|
+
|
|
199
|
+
Blume writes an **`/agent-readability.json`** manifest at your site root that indexes the agent-facing surface described on this page — so an agent can discover it in a single fetch instead of guessing at conventions or scraping HTML. Unlike the features above, it's on by default:
|
|
200
|
+
|
|
201
|
+
```ts blume.config.ts lineNumbers
|
|
202
|
+
seo: {
|
|
203
|
+
agentReadability: true,
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The manifest lists only what you've enabled — the [raw Markdown](#raw-markdown) mirror pattern, [`llms.txt`](#llmstxt) and `llms-full.txt`, the [MCP server](#mcp-server) and its discovery document, the [Ask AI](#ask-ai) endpoint, the [sitemap](/docs/configuration/seo#sitemap), and [RSS feeds](/docs/configuration/seo#rss-feeds) — alongside your site name, description, source repository, and the [content-signal](/docs/configuration/seo#content-signals) usage policy. URLs are absolute when [`deployment.site`](/docs/deployment) is set and root-relative otherwise:
|
|
208
|
+
|
|
209
|
+
```json agent-readability.json
|
|
210
|
+
{
|
|
211
|
+
"artifacts": {
|
|
212
|
+
"markdown": {
|
|
213
|
+
"contentNegotiation": "text/markdown",
|
|
214
|
+
"pattern": "https://docs.example.com/{route}.md"
|
|
215
|
+
},
|
|
216
|
+
"llmsFullTxt": "https://docs.example.com/llms-full.txt",
|
|
217
|
+
"llmsTxt": "https://docs.example.com/llms.txt",
|
|
218
|
+
"mcp": {
|
|
219
|
+
"discovery": "https://docs.example.com/.well-known/mcp.json",
|
|
220
|
+
"url": "https://docs.example.com/mcp"
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"description": "Docs for the Acme API.",
|
|
224
|
+
"generator": "blume@1.0.0",
|
|
225
|
+
"name": "Acme Docs",
|
|
226
|
+
"site": "https://docs.example.com",
|
|
227
|
+
"contentUsage": { "search": true, "ai-input": true, "ai-train": true },
|
|
228
|
+
"repository": "https://github.com/acme/docs"
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Set `seo.agentReadability` to `false` to skip it, or ship your own `public/agent-readability.json` to take over — Blume never overwrites a file you place in `public/`.
|
|
233
|
+
|
|
178
234
|
## Agent skill
|
|
179
235
|
|
|
180
236
|
Building a Blume site with the help of a coding agent? Install the Blume [agent skill](https://docs.claude.com/en/docs/claude-code/skills) so it knows how Blume works without you explaining it:
|
|
@@ -49,11 +49,9 @@ export default defineConfig({
|
|
|
49
49
|
// Markdown features
|
|
50
50
|
markdown: {
|
|
51
51
|
imageZoom: true,
|
|
52
|
-
math: false,
|
|
53
52
|
code: {
|
|
54
53
|
icons: true, // language icon in the code-block header
|
|
55
54
|
wrap: false, // wrap long lines instead of scrolling
|
|
56
|
-
inline: false, // highlight inline `code{:lang}` snippets
|
|
57
55
|
},
|
|
58
56
|
},
|
|
59
57
|
|
|
@@ -154,7 +154,7 @@ Ship your own `public/sitemap.xml` to take over — Blume never overwrites a fil
|
|
|
154
154
|
|
|
155
155
|
## Robots
|
|
156
156
|
|
|
157
|
-
Blume writes a `robots.txt` that allows all crawlers and adds a `Sitemap:` line pointing to the sitemap when one is available. On by default:
|
|
157
|
+
Blume writes a `robots.txt` that allows all crawlers, declares your [content signals](#content-signals), and adds a `Sitemap:` line pointing to the sitemap when one is available. On by default:
|
|
158
158
|
|
|
159
159
|
```ts blume.config.ts lineNumbers
|
|
160
160
|
seo: {
|
|
@@ -164,9 +164,67 @@ seo: {
|
|
|
164
164
|
|
|
165
165
|
```txt robots.txt
|
|
166
166
|
User-agent: *
|
|
167
|
+
Content-Signal: search=yes, ai-input=yes, ai-train=yes
|
|
167
168
|
Allow: /
|
|
168
169
|
|
|
169
170
|
Sitemap: https://docs.example.com/sitemap.xml
|
|
170
171
|
```
|
|
171
172
|
|
|
173
|
+
### Content signals
|
|
174
|
+
|
|
175
|
+
The `Content-Signal` line — the emerging content-usage convention — declares how AI crawlers may reuse your docs. Blume emits it **on by default with every signal set to `yes`**, matching its stance that docs are open to humans and agents alike:
|
|
176
|
+
|
|
177
|
+
- `search` — traditional and AI search indexing
|
|
178
|
+
- `aiInput` — grounding / RAG at answer time
|
|
179
|
+
- `aiTrain` — model training
|
|
180
|
+
|
|
181
|
+
Restrict any signal by setting it to `false`; the ones you leave out stay `yes`:
|
|
182
|
+
|
|
183
|
+
```ts blume.config.ts lineNumbers
|
|
184
|
+
seo: {
|
|
185
|
+
contentSignals: {
|
|
186
|
+
aiTrain: false, // opt out of training, keep search + grounding
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```txt robots.txt
|
|
192
|
+
User-agent: *
|
|
193
|
+
Content-Signal: search=yes, ai-input=yes, ai-train=no
|
|
194
|
+
Allow: /
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Set `contentSignals: false` to drop the declaration entirely:
|
|
198
|
+
|
|
199
|
+
```ts blume.config.ts lineNumbers
|
|
200
|
+
seo: {
|
|
201
|
+
contentSignals: false,
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
<TypeTable
|
|
206
|
+
type={{
|
|
207
|
+
"seo.contentSignals": {
|
|
208
|
+
type: "boolean | object",
|
|
209
|
+
description:
|
|
210
|
+
"Content-Signal declaration. true or omitted emits all signals as yes; false drops the line; an object sets signals individually.",
|
|
211
|
+
},
|
|
212
|
+
"contentSignals.search": {
|
|
213
|
+
type: "boolean",
|
|
214
|
+
description: "Allow use for search indexing (search). Default true.",
|
|
215
|
+
},
|
|
216
|
+
"contentSignals.aiInput": {
|
|
217
|
+
type: "boolean",
|
|
218
|
+
description:
|
|
219
|
+
"Allow use for AI grounding / RAG at answer time (ai-input). Default true.",
|
|
220
|
+
},
|
|
221
|
+
"contentSignals.aiTrain": {
|
|
222
|
+
type: "boolean",
|
|
223
|
+
description: "Allow use for AI model training (ai-train). Default true.",
|
|
224
|
+
},
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
Content signals express a preference, not access control: they tell well-behaved crawlers how you'd like your content used, and it's on the crawler to honour them.
|
|
229
|
+
|
|
172
230
|
Ship your own `public/robots.txt` to take over.
|
|
@@ -34,6 +34,8 @@ theme: {
|
|
|
34
34
|
|
|
35
35
|
Named presets: `blue` (default), `green`, `orange`, `pink`, `purple`, `red`, and `teal`.
|
|
36
36
|
|
|
37
|
+
A string applies to both color modes; pass an object for [a different accent per mode](#dark-mode-colors).
|
|
38
|
+
|
|
37
39
|
### Radius
|
|
38
40
|
|
|
39
41
|
`radius` sets the corner rounding shared by cards, code blocks, callouts, and inputs — `none`, `sm`, `md` (default), or `lg`.
|
|
@@ -89,18 +91,19 @@ Need a font that isn't listed, or want to drop back to the system stack? Overrid
|
|
|
89
91
|
|
|
90
92
|
### Dark-mode colors
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
`accent` and `background` follow one rule: a string applies to both color modes, and a `{ light, dark }` object sets each mode individually:
|
|
93
95
|
|
|
94
96
|
```ts blume.config.ts lineNumbers
|
|
95
97
|
theme: {
|
|
96
|
-
accent: "blue",
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
accent: { light: "blue", dark: "teal" },
|
|
99
|
+
background: {
|
|
100
|
+
light: "#ffffff",
|
|
101
|
+
dark: "#0a0a0a",
|
|
102
|
+
},
|
|
100
103
|
}
|
|
101
104
|
```
|
|
102
105
|
|
|
103
|
-
Each takes a named preset or any CSS color
|
|
106
|
+
Each color takes a named preset or any CSS color. For `background` (and `backgroundImage`) either key can be omitted to override a single mode — `background: { dark: "#0a0a0a" }` keeps the default light background.
|
|
104
107
|
|
|
105
108
|
### Action color
|
|
106
109
|
|
|
@@ -114,12 +117,14 @@ theme: {
|
|
|
114
117
|
|
|
115
118
|
### Background image
|
|
116
119
|
|
|
117
|
-
Set a background image behind your content with `backgroundImage`
|
|
120
|
+
Set a background image behind your content with `backgroundImage` — a URL or a path under `public/`. Like the colors, a string applies to both modes and a `{ light, dark }` object sets each mode's image:
|
|
118
121
|
|
|
119
122
|
```ts blume.config.ts lineNumbers
|
|
120
123
|
theme: {
|
|
121
|
-
backgroundImage:
|
|
122
|
-
|
|
124
|
+
backgroundImage: {
|
|
125
|
+
light: "/bg-light.svg",
|
|
126
|
+
dark: "/bg-dark.svg",
|
|
127
|
+
},
|
|
123
128
|
}
|
|
124
129
|
```
|
|
125
130
|
|
package/docs/content/meta.mdx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Folder meta
|
|
3
|
-
description: Configure a sidebar group — its title, icon, order,
|
|
3
|
+
description: Configure a sidebar group — its title, icon, order, and page order — with a meta.ts file.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Every folder in your content tree becomes a sidebar group. Drop a `meta.ts` beside its pages to control how that group looks and how its children are ordered. It's entirely optional: without one, the group's label is the humanized folder name and its pages sort by [index, numeric prefix, then alphabetically](/docs/content/navigation#ordering).
|
|
@@ -16,7 +16,6 @@ export default defineMeta({
|
|
|
16
16
|
title: "Guides",
|
|
17
17
|
icon: "book-open",
|
|
18
18
|
order: 2,
|
|
19
|
-
display: "flat",
|
|
20
19
|
collapsed: false,
|
|
21
20
|
pages: ["configuration", "theming", "deployment"],
|
|
22
21
|
});
|
|
@@ -31,25 +30,12 @@ Every field is optional — set only what you want to override.
|
|
|
31
30
|
| `title` | `string` | The group's label. Defaults to the humanized folder name. |
|
|
32
31
|
| `icon` | `string` | Icon shown next to the label. |
|
|
33
32
|
| `order` | `number` | Position among sibling groups and pages. Lower numbers sort first. |
|
|
34
|
-
| `
|
|
35
|
-
| `collapsed` | `boolean` | For `display: "group"`, whether the group starts collapsed. |
|
|
33
|
+
| `collapsed` | `boolean` | Under the [`group` display mode](/docs/content/navigation#display-modes), whether the group starts collapsed. |
|
|
36
34
|
| `pages` | `string[]` | Explicit order for the group's children, by slug. |
|
|
37
35
|
|
|
38
36
|
The `pages` array lists children by slug — the folder or file name with its numeric prefix and any parentheses stripped (so `01-quickstart.mdx` is `"quickstart"`). Children you leave out still appear, after the listed ones.
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
`display` controls how a group and its items appear in the sidebar:
|
|
43
|
-
|
|
44
|
-
- **`flat`** (default) — a non-collapsible header with its pages listed beneath.
|
|
45
|
-
- **`group`** — a collapsible `<details>` disclosure. Pair it with `collapsed` to set the initial state; a group containing the current page always starts open.
|
|
46
|
-
- **`page`** — a single row that, when clicked, slides the sidebar into a sub-panel showing only that group's items, with a back arrow at the top. The panel is route-aware, so landing directly on a page inside the group opens straight to it.
|
|
47
|
-
|
|
48
|
-
:::tip
|
|
49
|
-
`page` mode keeps deep sections tidy — reach for it when a group has many children and you'd rather drill into it than scroll past it.
|
|
50
|
-
:::
|
|
51
|
-
|
|
52
|
-
`display` works the same on a group in an [explicit sidebar](/docs/content/navigation#explicit-sidebar) config.
|
|
38
|
+
How groups render — flat headers, collapsible disclosures, or drill-in panels — is a sidebar-wide setting, not per folder: see [display modes](/docs/content/navigation#display-modes).
|
|
53
39
|
|
|
54
40
|
## Computed meta
|
|
55
41
|
|
|
@@ -31,7 +31,7 @@ See [Frontmatter](/docs/reference/frontmatter) for the full page schema.
|
|
|
31
31
|
|
|
32
32
|
## Folder groups
|
|
33
33
|
|
|
34
|
-
Each folder becomes a sidebar group. Drop a [`meta.ts`](/docs/content/meta) beside its pages to set the group's title, icon, order,
|
|
34
|
+
Each folder becomes a sidebar group. Drop a [`meta.ts`](/docs/content/meta) beside its pages to set the group's title, icon, order, and the order of its children:
|
|
35
35
|
|
|
36
36
|
```ts meta.ts
|
|
37
37
|
import { defineMeta } from "blume";
|
|
@@ -43,10 +43,32 @@ export default defineMeta({
|
|
|
43
43
|
});
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
See [Folder meta](/docs/content/meta) for every field
|
|
46
|
+
See [Folder meta](/docs/content/meta) for every field and computing meta at scan time.
|
|
47
47
|
|
|
48
48
|
To group pages _without_ adding a URL segment, use a parenthesized folder name — see [Pages](/docs/content#group-folders).
|
|
49
49
|
|
|
50
|
+
## Display modes
|
|
51
|
+
|
|
52
|
+
`navigation.sidebar.display` sets how every sidebar group renders:
|
|
53
|
+
|
|
54
|
+
```ts blume.config.ts lineNumbers
|
|
55
|
+
navigation: {
|
|
56
|
+
sidebar: {
|
|
57
|
+
display: "flat", // "flat" | "group" | "page"
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **`flat`** (default) — a non-collapsible header with its pages listed beneath. Pages that aren't in any group always list first, above the group sections, so they can't be mistaken for a group's children.
|
|
63
|
+
- **`group`** — a collapsible `<details>` disclosure per group. Pair it with `collapsed` in [folder meta](/docs/content/meta) to set the initial state; a group containing the current page always starts open.
|
|
64
|
+
- **`page`** — each group is a single row that, when clicked, slides the sidebar into a sub-panel showing only that group's items, with a back arrow at the top. The panel is route-aware, so landing directly on a page inside the group opens straight to it.
|
|
65
|
+
|
|
66
|
+
:::tip
|
|
67
|
+
`page` mode keeps deep sections tidy — reach for it when groups have many children and you'd rather drill into them than scroll past them.
|
|
68
|
+
:::
|
|
69
|
+
|
|
70
|
+
A group in an [explicit sidebar](#explicit-sidebar) can override the global mode with its own `display`.
|
|
71
|
+
|
|
50
72
|
## Ordering
|
|
51
73
|
|
|
52
74
|
When the sidebar is generated, order is resolved highest priority first:
|
|
@@ -112,9 +134,24 @@ navigation: {
|
|
|
112
134
|
|
|
113
135
|
Each item takes a `label`, a `path`, and optional `icon`, `description`, and `tag`. `kind` (`dropdown`, `product`, `version`, or `language`) is a hint for how the selector is used; all render the same dropdown.
|
|
114
136
|
|
|
137
|
+
## Featured links
|
|
138
|
+
|
|
139
|
+
Pin links to the top of the sidebar, above every section — a blog, a changelog, a contact or support page that should always be one click away. Unlike the generated tree, featured links are **not scoped by tab**: they show on every route, on every breakpoint.
|
|
140
|
+
|
|
141
|
+
```ts blume.config.ts lineNumbers
|
|
142
|
+
navigation: {
|
|
143
|
+
featured: [
|
|
144
|
+
{ label: "Blog", href: "https://example.com/blog", icon: "newspaper" },
|
|
145
|
+
{ label: "Contact", href: "/contact", icon: "headphones" },
|
|
146
|
+
],
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Each link takes a `label`, an `href`, and an optional `icon` (a [built-in icon](/docs/configuration/customization) name, image path/URL, or inline SVG — the same as anywhere else). An `href` may point anywhere: an external URL opens in a new tab, while an internal route (`/contact`) is validated against your pages at build time, warning you if nothing matches.
|
|
151
|
+
|
|
115
152
|
## Explicit sidebar
|
|
116
153
|
|
|
117
|
-
For full control,
|
|
154
|
+
For full control, list explicit items in `navigation.sidebar` — a bare array is shorthand for `sidebar.items`, and the object form combines them with a global [`display`](#display-modes). When items are set, Blume uses them verbatim and skips file-system generation:
|
|
118
155
|
|
|
119
156
|
```ts blume.config.ts lineNumbers
|
|
120
157
|
navigation: {
|
|
@@ -130,7 +167,7 @@ navigation: {
|
|
|
130
167
|
}
|
|
131
168
|
```
|
|
132
169
|
|
|
133
|
-
Each item is a page route (a string), a group (`label` + `items`), or a link (`label` + `href`). Groups can nest,
|
|
170
|
+
Each item is a page route (a string), a group (`label` + `items`), or a link (`label` + `href`). Groups can nest, override the global [`display` mode](#display-modes), and start `collapsed`.
|
|
134
171
|
|
|
135
172
|
## Repository link
|
|
136
173
|
|
package/docs/content/syntax.mdx
CHANGED
|
@@ -139,7 +139,7 @@ export default defineConfig({
|
|
|
139
139
|
```
|
|
140
140
|
````
|
|
141
141
|
|
|
142
|
-
Inline code can be highlighted too: add a `{:lang}` marker inside a backtick span and it's colored like a tiny code block — `useState(){:js}` or `T extends object{:ts}`.
|
|
142
|
+
Inline code can be highlighted too: add a `{:lang}` marker inside a backtick span and it's colored like a tiny code block — `useState(){:js}` or `T extends object{:ts}`. It only kicks in when you add the marker, so plain inline code stays untouched — nothing to switch on.
|
|
143
143
|
|
|
144
144
|
### Line numbers
|
|
145
145
|
|
|
@@ -372,24 +372,20 @@ The names `caution`, `error`, `important`, and `warn` are accepted as aliases fo
|
|
|
372
372
|
|
|
373
373
|
## Math
|
|
374
374
|
|
|
375
|
-
Render LaTeX with KaTeX
|
|
376
|
-
|
|
377
|
-
The Pythagorean theorem is $a^2 + b^2 = c^2$.
|
|
375
|
+
Render LaTeX with KaTeX as centered blocks — useful for math-heavy or scientific docs. Wrap a formula in `$$…$$`:
|
|
378
376
|
|
|
379
377
|
$$
|
|
380
378
|
\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2}
|
|
381
379
|
$$
|
|
382
380
|
|
|
383
381
|
```md
|
|
384
|
-
The Pythagorean theorem is $a^2 + b^2 = c^2$.
|
|
385
|
-
|
|
386
382
|
$$
|
|
387
|
-
|
|
383
|
+
a^2 + b^2 = c^2
|
|
388
384
|
$$
|
|
389
385
|
```
|
|
390
386
|
|
|
391
387
|
:::note
|
|
392
|
-
Math is
|
|
388
|
+
Math is block-only and on automatically — write `$$…$$` and it renders; write none and KaTeX's stylesheet never ships. There's no inline `$…$` math: a lone `$` (currency, shell variables, code) is always left as literal text, so there's no delimiter to escape and no setting to toggle. Math is an MDX-only feature.
|
|
393
389
|
:::
|
|
394
390
|
|
|
395
391
|
## Smart punctuation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blume",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Documentation that's fast, AI-ready, and zero-config.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -92,12 +92,14 @@
|
|
|
92
92
|
"citty": "^0.1.6",
|
|
93
93
|
"consola": "^3.4.0",
|
|
94
94
|
"deepmerge": "^4.3.1",
|
|
95
|
+
"dompurify": "^3.4.11",
|
|
95
96
|
"epub-gen-memory": "^1.1.2",
|
|
96
97
|
"github-slugger": "^2.0.0",
|
|
97
98
|
"gray-matter": "^4.0.3",
|
|
98
99
|
"jiti": "^2.4.0",
|
|
99
100
|
"js-yaml": "^4.1.0",
|
|
100
101
|
"katex": "^0.17.0",
|
|
102
|
+
"marked": "^18.0.5",
|
|
101
103
|
"mermaid": "^11.15.0",
|
|
102
104
|
"pagefind": "^1.3.0",
|
|
103
105
|
"pathe": "^2.0.0",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { BlumeProject } from "../core/project-graph.ts";
|
|
2
|
+
import type { ContentSignalPolicy, ContentSignals } from "../core/schema.ts";
|
|
3
|
+
import { buildRssFeeds } from "../deploy/rss.ts";
|
|
4
|
+
|
|
5
|
+
/** Token map for the machine-readable content-usage echo. */
|
|
6
|
+
const USAGE_TOKENS: [keyof ContentSignalPolicy, string][] = [
|
|
7
|
+
["search", "search"],
|
|
8
|
+
["aiInput", "ai-input"],
|
|
9
|
+
["aiTrain", "ai-train"],
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The configured usage preferences as a `{ token: allowed }` object, or null
|
|
14
|
+
* when the declaration is disabled (`contentSignals: false`). Mirrors the
|
|
15
|
+
* robots.txt `Content-Signal` line so an agent that reads the manifest instead
|
|
16
|
+
* of robots.txt sees the same policy.
|
|
17
|
+
*/
|
|
18
|
+
const usagePolicy = (
|
|
19
|
+
signals: ContentSignals
|
|
20
|
+
): Record<string, boolean> | null => {
|
|
21
|
+
if (!signals) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return Object.fromEntries(
|
|
25
|
+
USAGE_TOKENS.map(([key, token]) => [token, signals[key]] as const)
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build `agent-readability.json`: a root manifest that indexes the project's
|
|
31
|
+
* agent-facing surface — llms.txt, the raw-Markdown mirrors, the MCP server,
|
|
32
|
+
* Ask AI, sitemap, and feeds — so agents can discover and cite the docs without
|
|
33
|
+
* scraping HTML. URLs are absolute when a `site` is configured and root-relative
|
|
34
|
+
* otherwise. Returns null when the manifest is disabled.
|
|
35
|
+
*/
|
|
36
|
+
export const buildAgentReadability = (
|
|
37
|
+
project: BlumeProject
|
|
38
|
+
): Record<string, unknown> | null => {
|
|
39
|
+
const { config } = project;
|
|
40
|
+
if (!config.seo.agentReadability) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const site = config.deployment.site ?? null;
|
|
45
|
+
// Concatenate rather than `new URL()` so a subpath deployment's base is kept.
|
|
46
|
+
const abs = (path: string): string =>
|
|
47
|
+
site ? `${site.replace(/\/+$/u, "")}${path}` : path;
|
|
48
|
+
|
|
49
|
+
const artifacts: Record<string, unknown> = {
|
|
50
|
+
markdown: {
|
|
51
|
+
contentNegotiation: "text/markdown",
|
|
52
|
+
pattern: abs("/{route}.md"),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
if (config.ai.llmsTxt) {
|
|
56
|
+
artifacts.llmsFullTxt = abs("/llms-full.txt");
|
|
57
|
+
artifacts.llmsTxt = abs("/llms.txt");
|
|
58
|
+
}
|
|
59
|
+
if (config.mcp.enabled) {
|
|
60
|
+
artifacts.mcp = {
|
|
61
|
+
discovery: abs("/.well-known/mcp.json"),
|
|
62
|
+
url: abs(config.mcp.route),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (config.ai.ask?.enabled) {
|
|
66
|
+
artifacts.askApi = abs("/api/ask");
|
|
67
|
+
}
|
|
68
|
+
if (site && config.seo.sitemap) {
|
|
69
|
+
artifacts.sitemap = abs("/sitemap.xml");
|
|
70
|
+
}
|
|
71
|
+
const feeds =
|
|
72
|
+
site && config.seo.rss.enabled
|
|
73
|
+
? buildRssFeeds(project).map((feed) => abs(feed.path))
|
|
74
|
+
: [];
|
|
75
|
+
if (feeds.length > 0) {
|
|
76
|
+
artifacts.feeds = feeds;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const version = project.manifest?.blumeVersion;
|
|
80
|
+
const manifest: Record<string, unknown> = {
|
|
81
|
+
artifacts,
|
|
82
|
+
description: config.description,
|
|
83
|
+
generator: version ? `blume@${version}` : undefined,
|
|
84
|
+
name: config.mcp.name ?? config.title,
|
|
85
|
+
site,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const usage = usagePolicy(config.seo.contentSignals);
|
|
89
|
+
if (usage) {
|
|
90
|
+
manifest.contentUsage = usage;
|
|
91
|
+
}
|
|
92
|
+
if (config.github) {
|
|
93
|
+
manifest.repository = `https://github.com/${config.github.owner}/${config.github.repo}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return manifest;
|
|
97
|
+
};
|