blume 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/cli/index.js +2429 -792
  2. package/dist/cli/index.js.map +63 -44
  3. package/dist/types/core/data.d.ts +16 -0
  4. package/dist/types/core/define-components.d.ts +9 -2
  5. package/dist/types/core/diagnostics.d.ts +5 -0
  6. package/dist/types/core/schema.d.ts +313 -778
  7. package/dist/types/core/types.d.ts +2 -2
  8. package/dist/types/migrate/mintlify/assets.d.ts +8 -0
  9. package/docs/01-quickstart.mdx +5 -16
  10. package/docs/02-deployment.mdx +26 -40
  11. package/docs/advanced/api-reference.mdx +10 -37
  12. package/docs/advanced/blog.mdx +9 -25
  13. package/docs/advanced/changelog.mdx +10 -33
  14. package/docs/advanced/custom-pages.mdx +66 -61
  15. package/docs/configuration/ai.mdx +47 -91
  16. package/docs/configuration/analytics.mdx +20 -38
  17. package/docs/configuration/customization.mdx +92 -27
  18. package/docs/configuration/export.mdx +9 -34
  19. package/docs/configuration/index.mdx +78 -85
  20. package/docs/configuration/search.mdx +17 -54
  21. package/docs/configuration/seo.mdx +18 -44
  22. package/docs/configuration/theming.mdx +20 -42
  23. package/docs/content/components.mdx +42 -101
  24. package/docs/content/i18n.mdx +21 -72
  25. package/docs/content/index.mdx +18 -48
  26. package/docs/content/islands.mdx +79 -33
  27. package/docs/content/meta.mdx +23 -50
  28. package/docs/content/navigation.mdx +42 -56
  29. package/docs/content/sources.mdx +20 -83
  30. package/docs/content/syntax.mdx +37 -105
  31. package/docs/index.mdx +13 -51
  32. package/docs/reference/cli.mdx +49 -18
  33. package/docs/reference/frontmatter.mdx +2 -5
  34. package/package.json +3 -1
  35. package/src/ai/ask-context.ts +131 -0
  36. package/src/ai/ask-data.ts +25 -0
  37. package/src/astro/component-slots.ts +165 -0
  38. package/src/astro/generate.ts +132 -13
  39. package/src/astro/integration.ts +85 -3
  40. package/src/astro/islands.ts +6 -2
  41. package/src/astro/markdown-negotiation.ts +17 -3
  42. package/src/astro/pages.ts +11 -13
  43. package/src/astro/static-assets.ts +117 -0
  44. package/src/astro/templates.ts +120 -50
  45. package/src/blume-modules.d.ts +25 -0
  46. package/src/cli/args.ts +23 -0
  47. package/src/cli/commands/build.ts +209 -1
  48. package/src/cli/commands/check.ts +62 -0
  49. package/src/cli/commands/dev.ts +32 -3
  50. package/src/cli/commands/doctor.ts +32 -6
  51. package/src/cli/commands/eject.ts +3 -1
  52. package/src/cli/commands/init.ts +184 -16
  53. package/src/cli/commands/preview.ts +2 -1
  54. package/src/cli/commands/validate.ts +27 -2
  55. package/src/cli/dev-lock.ts +84 -0
  56. package/src/cli/index.ts +15 -0
  57. package/src/cli/internal-error.ts +63 -0
  58. package/src/cli/log.ts +41 -1
  59. package/src/cli/prepare.ts +17 -3
  60. package/src/cli/required-secrets.ts +44 -0
  61. package/src/components/BlumePage.astro +109 -0
  62. package/src/components/content/YouTube.astro +35 -0
  63. package/src/components/content/youtube.ts +46 -0
  64. package/src/components/index.ts +3 -3
  65. package/src/components/islands/ask-ai.tsx +29 -15
  66. package/src/components/islands/hooks.ts +188 -0
  67. package/src/components/layout/Empty.astro +6 -0
  68. package/src/components/layout/Header.astro +24 -39
  69. package/src/components/layout/Logo.astro +50 -0
  70. package/src/components/layout/NavSelector.astro +75 -0
  71. package/src/components/layout/PageLayout.astro +38 -2
  72. package/src/components/layout/RootLayout.astro +70 -4
  73. package/src/components/layout/hydration-hint.ts +30 -0
  74. package/src/components/layout/overrides.ts +6 -4
  75. package/src/components/props.ts +71 -0
  76. package/src/core/assets.ts +31 -0
  77. package/src/core/bridge.ts +10 -0
  78. package/src/core/builtin-tags.ts +40 -0
  79. package/src/core/component-diagnostics.ts +44 -0
  80. package/src/core/component-overrides.ts +478 -0
  81. package/src/core/config.ts +8 -0
  82. package/src/core/data.ts +14 -0
  83. package/src/core/define-components.ts +9 -2
  84. package/src/core/diagnostics.ts +95 -1
  85. package/src/core/gitignore.ts +30 -0
  86. package/src/core/graph.ts +7 -0
  87. package/src/core/links.ts +60 -19
  88. package/src/core/nav-diagnostics.ts +205 -0
  89. package/src/core/project-graph.ts +40 -1
  90. package/src/core/schema.ts +35 -96
  91. package/src/core/sources/mdx-remote.ts +54 -8
  92. package/src/core/sources/normalize.ts +57 -1
  93. package/src/core/sources/notion.ts +49 -5
  94. package/src/core/sources/sanity.ts +5 -1
  95. package/src/core/types.ts +2 -2
  96. package/src/deploy/redirects.ts +43 -0
  97. package/src/deploy/rss.ts +1 -8
  98. package/src/deploy/sitemap.ts +20 -1
  99. package/src/deploy/xml.ts +8 -0
  100. package/src/markdown/directives.ts +15 -7
  101. package/src/markdown/package-commands.ts +26 -4
  102. package/src/migrate/fumadocs/content.ts +14 -1
  103. package/src/migrate/fumadocs/groups.ts +7 -0
  104. package/src/migrate/fumadocs/index.ts +5 -2
  105. package/src/migrate/mintlify/assets.ts +46 -0
  106. package/src/migrate/mintlify/config.ts +1 -176
  107. package/src/migrate/mintlify/index.ts +53 -45
  108. package/src/migrate/shared.ts +12 -27
  109. package/src/migrate/starlight/config.ts +0 -4
  110. package/src/og/card.ts +175 -38
  111. package/src/registry/eject.ts +52 -12
  112. package/src/registry/registry.ts +172 -0
  113. package/src/registry/rewrite-imports.ts +31 -19
  114. package/src/runtime/index.ts +61 -0
  115. package/src/search/documents.ts +23 -5
  116. package/src/search/sync/algolia.ts +5 -1
  117. package/src/search/sync/typesense.ts +24 -16
  118. package/src/theme/palette.ts +26 -7
  119. package/src/vite-env.d.ts +14 -0
@@ -3,15 +3,11 @@ title: Islands
3
3
  description: Drop an interactive component into islands/ and use it in any MDX page — hydrated automatically, no per-page import.
4
4
  ---
5
5
 
6
- Blume renders your docs as static HTML with **zero JavaScript** by default. When
7
- you need something interactive — a live demo, a chart, a playground — you add an
8
- **island**: a framework component that ships JS only for itself, only on the
9
- pages that use it.
6
+ Blume renders your docs as static HTML with **zero JavaScript** by default. When you need something interactive — a live demo, a chart, a playground — you add an **island**: a framework component that ships JS only for itself, only on the pages that use it.
10
7
 
11
8
  ## The `islands/` convention
12
9
 
13
- Drop a component into an `islands/` folder at your project root. Its filename
14
- becomes a component you can use in **any** `.mdx` page, with no import:
10
+ Drop a component into an `islands/` folder at your project root. Its filename becomes a component you can use in **any** `.mdx` page, with no import:
15
11
 
16
12
  ```tsx islands/Counter.tsx lineNumbers
17
13
  import { useState } from "react";
@@ -26,21 +22,38 @@ export default function Counter() {
26
22
  Here's a live counter: <Counter />
27
23
  ```
28
24
 
29
- The filename is the component name, so it **must be PascalCase**
30
- (`Counter.tsx` → `<Counter />`). Lowercase filenames and two islands that
31
- resolve to the same name are skipped with a build warning.
25
+ The filename is the component name, so it **must be a PascalCase identifier** — letters, digits, and underscores only (`Counter.tsx` → `<Counter />`). Lowercase filenames, names with dashes/dots/spaces (like `Time-Picker.tsx`), and two islands that resolve to the same name are skipped with a build warning.
32
26
 
33
- :::note
34
- Islands are for **interactive** UI. For a static component you reuse across
35
- pages (a styled callout, a pricing table), use an [MDX
36
- override](/docs/configuration/customization) instead — it ships no JavaScript.
37
- :::
27
+ :::note Islands are for **interactive** UI. For a static component you reuse across pages (a styled callout, a pricing table), use an [MDX override](/docs/configuration/customization) instead — it ships no JavaScript. :::
28
+
29
+ ## Registering islands in `components.ts`
30
+
31
+ If you'd rather keep islands next to the rest of your components — or give them a different name than the file — register them with `defineComponents`. The `islands` group is exactly like the `islands/` folder: each entry is available in every MDX page and hydrates (defaulting to `client: "visible"`).
32
+
33
+ ```ts components.ts
34
+ import { defineComponents } from "blume";
35
+ import Counter from "./widgets/Counter.tsx";
36
+
37
+ export default defineComponents({
38
+ islands: {
39
+ Counter, // <Counter /> in any MDX page, hydrated
40
+ },
41
+ });
42
+ ```
43
+
44
+ Reference the component by import or by a path string, and set a hydration mode per island with the descriptor form:
45
+
46
+ ```ts components.ts
47
+ export default defineComponents({
48
+ islands: {
49
+ Chart: { component: "./widgets/Chart.tsx", client: "only" },
50
+ },
51
+ });
52
+ ```
38
53
 
39
54
  ## Hydration
40
55
 
41
- By default an island uses `client:visible`: it hydrates when the reader scrolls
42
- it into view, so a page full of islands still loads instantly. Opt into a
43
- different strategy with an `export const client` in the island file:
56
+ By default an island uses `client:visible`: it hydrates when the reader scrolls it into view, so a page full of islands still loads instantly. Opt into a different strategy with an `export const client` in the island file:
44
57
 
45
58
  ```tsx islands/Chart.tsx lineNumbers
46
59
  // Skip server rendering entirely — for components that touch the DOM/window.
@@ -51,20 +64,18 @@ export default function Chart() {
51
64
  }
52
65
  ```
53
66
 
54
- | `client` value | Hydrates | Use it for |
55
- | ----------------------- | ---------------------------------- | --------------------------------------------------------- |
56
- | `"visible"` _(default)_ | When scrolled into view | Most islands |
57
- | `"load"` | Immediately on page load | Above-the-fold, must-be-instant UI |
58
- | `"idle"` | When the main thread is idle | Non-urgent interactivity |
59
- | `"only"` | Client only, never server-rendered | Libraries that need `window`/`document` (charts, editors) |
67
+ | `client` value | Hydrates | Use it for |
68
+ | --- | --- | --- |
69
+ | `"visible"` _(default)_ | When scrolled into view | Most islands |
70
+ | `"load"` | Immediately on page load | Above-the-fold, must-be-instant UI |
71
+ | `"idle"` | When the main thread is idle | Non-urgent interactivity |
72
+ | `"only"` | Client only, never server-rendered | Libraries that need `window`/`document` (charts, editors) |
60
73
 
61
74
  ## Frameworks
62
75
 
63
- **React works out of the box** — Blume turns it on automatically the moment your
64
- project contains a `.tsx`/`.jsx` island.
76
+ **React works out of the box** — Blume turns it on automatically the moment your project contains a `.tsx`/`.jsx` island.
65
77
 
66
- **Vue and Svelte** are supported too; install the matching Astro integration and
67
- Blume wires up the renderer when it sees a `.vue` or `.svelte` island:
78
+ **Vue and Svelte** are supported too; install the matching Astro integration and Blume wires up the renderer when it sees a `.vue` or `.svelte` island:
68
79
 
69
80
  ```bash
70
81
  # Vue
@@ -85,10 +96,45 @@ const on = ref(false);
85
96
  </template>
86
97
  ```
87
98
 
88
- Props you pass in MDX (`<Counter start={5} />`) are forwarded to the component,
89
- and children (`<Counter>label</Counter>`) arrive as the default slot.
99
+ Props you pass in MDX (`<Counter start={5} />`) are forwarded to the component, and children (`<Counter>label</Counter>`) arrive as the default slot.
100
+
101
+ :::tip Islands hydrate on the client, so anything you pass as a prop must be serializable — strings, numbers, plain objects, not functions. :::
102
+
103
+ ## Hooks
90
104
 
91
- :::tip
92
- Islands hydrate on the client, so anything you pass as a prop must be
93
- serializable strings, numbers, plain objects, not functions.
94
- :::
105
+ Islands hydrate on their own, so there's no React context to thread project data through. Instead, `blume/hooks` reads a small snapshot the layout serializes into the page — no props to drill:
106
+
107
+ ```tsx islands/PageInfo.tsx lineNumbers
108
+ import { useBlume, usePage } from "blume/hooks";
109
+
110
+ export default function PageInfo() {
111
+ const blume = useBlume();
112
+ const page = usePage();
113
+ if (!(blume && page)) {
114
+ return null;
115
+ }
116
+ return (
117
+ <p>
118
+ You're reading <strong>{page.title}</strong> on {blume.config.title}.
119
+ </p>
120
+ );
121
+ }
122
+ ```
123
+
124
+ | Hook | Returns |
125
+ | --- | --- |
126
+ | `useBlume()` | `{ config, navigation }` for the site, or `null` before mount |
127
+ | `usePage()` | `{ route, title }` for the current page, or `null` before mount |
128
+ | `useSearch()` | `{ search, results, loading }` — query the configured search provider |
129
+ | `useAskAI()` | `{ ask, messages, loading, reset }` — stream from the Ask AI endpoint |
130
+
131
+ `useBlume()` and `usePage()` return `null` until the island mounts (so server and client render the same first frame) — guard for it. The snapshot is emitted only on pages that ship React, so a fully static site pays nothing.
132
+
133
+ On [custom pages](/docs/advanced/custom-pages) built with `PageLayout`, pass `clientData` so islands there can read it:
134
+
135
+ ```astro
136
+ <PageLayout
137
+ clientData={{ config: data.config, navigation: data.navigation, page: { route: "/", title: "Home" } }}
138
+ {/* …other props… */}
139
+ />
140
+ ```
@@ -3,16 +3,11 @@ title: Folder meta
3
3
  description: Configure a sidebar group — its title, icon, order, display mode, and page order — with a meta.ts file.
4
4
  ---
5
5
 
6
- Every folder in your content tree becomes a sidebar group. Drop a `meta.ts`
7
- beside its pages to control how that group looks and how its children are
8
- ordered. It's entirely optional: without one, the group's label is the humanized
9
- folder name and its pages sort by [index, numeric prefix, then
10
- alphabetically](/docs/content/navigation#ordering).
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).
11
7
 
12
8
  ## Defining meta
13
9
 
14
- Export a `defineMeta` object for a fully typed config. Place the file at the root
15
- of the folder it configures — `guides/meta.ts` configures the **Guides** group:
10
+ Export a `defineMeta` object for a fully typed config. Place the file at the root of the folder it configures — `guides/meta.ts` configures the **Guides** group:
16
11
 
17
12
  ```ts meta.ts lineNumbers
18
13
  import { defineMeta } from "blume";
@@ -31,45 +26,32 @@ Every field is optional — set only what you want to override.
31
26
 
32
27
  ## Fields
33
28
 
34
- | Field | Type | Description |
35
- | ----------- | ----------------------------- | -------------------------------------------------------------------------------------- |
36
- | `title` | `string` | The group's label. Defaults to the humanized folder name. |
37
- | `icon` | `string` | Icon shown next to the label. |
38
- | `order` | `number` | Position among sibling groups and pages. Lower numbers sort first. |
39
- | `display` | `"flat" \| "group" \| "page"` | How the group renders in the sidebar. Defaults to `flat`. See [below](#display-modes). |
40
- | `collapsed` | `boolean` | For `display: "group"`, whether the group starts collapsed. |
41
- | `pages` | `string[]` | Explicit order for the group's children, by slug. |
29
+ | Field | Type | Description |
30
+ | --- | --- | --- |
31
+ | `title` | `string` | The group's label. Defaults to the humanized folder name. |
32
+ | `icon` | `string` | Icon shown next to the label. |
33
+ | `order` | `number` | Position among sibling groups and pages. Lower numbers sort first. |
34
+ | `display` | `"flat" \| "group" \| "page"` | How the group renders in the sidebar. Defaults to `flat`. See [below](#display-modes). |
35
+ | `collapsed` | `boolean` | For `display: "group"`, whether the group starts collapsed. |
36
+ | `pages` | `string[]` | Explicit order for the group's children, by slug. |
42
37
 
43
- The `pages` array lists children by slug — the folder or file name with its
44
- numeric prefix and any parentheses stripped (so `01-quickstart.mdx` is
45
- `"quickstart"`). Children you leave out still appear, after the listed ones.
38
+ 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.
46
39
 
47
40
  ## Display modes
48
41
 
49
42
  `display` controls how a group and its items appear in the sidebar:
50
43
 
51
44
  - **`flat`** (default) — a non-collapsible header with its pages listed beneath.
52
- - **`group`** — a collapsible `<details>` disclosure. Pair it with `collapsed`
53
- to set the initial state; a group containing the current page always starts
54
- open.
55
- - **`page`** — a single row that, when clicked, slides the sidebar into a
56
- sub-panel showing only that group's items, with a back arrow at the top. The
57
- panel is route-aware, so landing directly on a page inside the group opens
58
- straight to it.
59
-
60
- :::tip
61
- `page` mode keeps deep sections tidy — reach for it when a group has many
62
- children and you'd rather drill into it than scroll past it.
63
- :::
64
-
65
- `display` works the same on a group in an [explicit
66
- sidebar](/docs/content/navigation#explicit-sidebar) config.
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 `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. :::
49
+
50
+ `display` works the same on a group in an [explicit sidebar](/docs/content/navigation#explicit-sidebar) config.
67
51
 
68
52
  ## Computed meta
69
53
 
70
- Because `meta.ts` is a real module, you can compute the meta — pass a function
71
- (sync or `async`) instead of an object to build it at scan time. Handy for
72
- ordering pages from an external source:
54
+ Because `meta.ts` is a real module, you can compute the meta — pass a function (sync or `async`) instead of an object to build it at scan time. Handy for ordering pages from an external source:
73
55
 
74
56
  ```ts meta.ts
75
57
  import { defineMeta } from "blume";
@@ -82,30 +64,21 @@ export default defineMeta(async () => ({
82
64
 
83
65
  ## Ordering within a group
84
66
 
85
- The `pages` array sets the order of a group's children. Anything it omits falls
86
- back to each page's frontmatter `sidebar.order`, then the file system (an `index`
87
- page first, then numeric prefixes, then alphabetical). For the full sidebar
88
- precedence — including an explicit config sidebar — see [Navigation ›
89
- Ordering](/docs/content/navigation#ordering).
67
+ The `pages` array sets the order of a group's children. Anything it omits falls back to each page's frontmatter `sidebar.order`, then the file system (an `index` page first, then numeric prefixes, then alphabetical). For the full sidebar precedence — including an explicit config sidebar — see [Navigation › Ordering](/docs/content/navigation#ordering).
90
68
 
91
- To group pages _without_ adding a URL segment, you don't need a `meta.ts` at all:
92
- use a parenthesized folder name — see [Pages › Group
93
- folders](/docs/content#group-folders).
69
+ To group pages _without_ adding a URL segment, you don't need a `meta.ts` at all: use a parenthesized folder name — see [Pages › Group folders](/docs/content#group-folders).
94
70
 
95
71
  ## Internationalization
96
72
 
97
- Under [i18n](/docs/content/i18n), folder meta resolves per locale: put a
98
- `meta.ts` under `fr/guides/` to order the French group independently.
73
+ Under [i18n](/docs/content/i18n), folder meta resolves per locale: put a `meta.ts` under `fr/guides/` to order the French group independently.
99
74
 
100
- For folder meta that's identical in every language, add a `$` marker so one file
101
- serves all locales without duplication:
75
+ For folder meta that's identical in every language, add a `$` marker so one file serves all locales without duplication:
102
76
 
103
77
  ```txt
104
78
  docs/guides/meta.$.ts (folder meta applied to every locale)
105
79
  ```
106
80
 
107
- A locale-specific `meta.ts` still overrides the shared `meta.$.ts` for that
108
- language.
81
+ A locale-specific `meta.ts` still overrides the shared `meta.$.ts` for that language.
109
82
 
110
83
  ## Where to next
111
84
 
@@ -3,27 +3,21 @@ title: Navigation
3
3
  description: Build the sidebar from your files, then refine it with frontmatter, folder meta, or config.
4
4
  ---
5
5
 
6
- Blume builds your sidebar from the file system, then lets you refine it as much —
7
- or as little — as you want: page by page, folder by folder, or with one explicit
8
- config. Breadcrumbs, previous/next links, and the on-page outline all follow from
9
- the same model, with nothing to wire up.
6
+ Blume builds your sidebar from the file system, then lets you refine it as much — or as little — as you want: page by page, folder by folder, or with one explicit config. Breadcrumbs, previous/next links, and the on-page outline all follow from the same model, with nothing to wire up.
10
7
 
11
8
  ## The generated sidebar
12
9
 
13
10
  By default the sidebar mirrors your content tree:
14
11
 
15
12
  - folders become **groups**, files become **pages**
16
- - a page's label is its frontmatter `title`; a group's label is the humanized
17
- folder name
18
- - items sort by [numeric prefix](/docs/content), then alphabetically, and a folder's
19
- `index` page comes first
13
+ - a page's label is its frontmatter `title`; a group's label is the humanized folder name
14
+ - items sort by [numeric prefix](/docs/content), then alphabetically, and a folder's `index` page comes first
20
15
 
21
16
  That's enough for many sites — everything below is opt-in.
22
17
 
23
18
  ## Page label, icon, and badge
24
19
 
25
- Tune how a single page appears in the sidebar from its frontmatter, under
26
- `sidebar`:
20
+ Tune how a single page appears in the sidebar from its frontmatter, under `sidebar`:
27
21
 
28
22
  ```yaml lineNumbers
29
23
  sidebar:
@@ -37,9 +31,7 @@ See [Frontmatter](/docs/reference/frontmatter) for the full page schema.
37
31
 
38
32
  ## Folder groups
39
33
 
40
- Each folder becomes a sidebar group. Drop a [`meta.ts`](/docs/content/meta)
41
- beside its pages to set the group's title, icon, order, [display
42
- mode](/docs/content/meta#display-modes), and the order of its children:
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, [display mode](/docs/content/meta#display-modes), and the order of its children:
43
35
 
44
36
  ```ts meta.ts
45
37
  import { defineMeta } from "blume";
@@ -51,11 +43,9 @@ export default defineMeta({
51
43
  });
52
44
  ```
53
45
 
54
- See [Folder meta](/docs/content/meta) for every field, the three display modes,
55
- and computing meta at scan time.
46
+ See [Folder meta](/docs/content/meta) for every field, the three display modes, and computing meta at scan time.
56
47
 
57
- To group pages _without_ adding a URL segment, use a parenthesized folder name —
58
- see [Pages](/docs/content#group-folders).
48
+ To group pages _without_ adding a URL segment, use a parenthesized folder name — see [Pages](/docs/content#group-folders).
59
49
 
60
50
  ## Ordering
61
51
 
@@ -76,8 +66,7 @@ When the sidebar is generated, order is resolved highest priority first:
76
66
 
77
67
  ## Hidden pages
78
68
 
79
- Hide a page from the sidebar — and from previous/next pagination — while keeping
80
- it built and reachable by its URL:
69
+ Hide a page from the sidebar — and from previous/next pagination — while keeping it built and reachable by its URL:
81
70
 
82
71
  ```yaml
83
72
  sidebar:
@@ -86,9 +75,7 @@ sidebar:
86
75
 
87
76
  ## Tabs
88
77
 
89
- Render top-level sections as tabs in the header, useful for splitting a large
90
- site into distinct areas — say adapters, an API, and AI guides. A tab is
91
- highlighted when the current route falls under its `path`:
78
+ Render top-level sections as tabs in the header, useful for splitting a large site into distinct areas — say adapters, an API, and AI guides. A tab is highlighted when the current route falls under its `path`:
92
79
 
93
80
  ```ts blume.config.ts lineNumbers
94
81
  navigation: {
@@ -100,23 +87,34 @@ navigation: {
100
87
  }
101
88
  ```
102
89
 
103
- Tabs also **scope the sidebar**: when the current route falls under a tab's
104
- `path`, the sidebar shows only that section's pages — so `/adapters/*` lists the
105
- adapters and nothing else. The folder at a tab's `path` becomes the section, so
106
- this needs no extra config beyond the tabs themselves; structure your content
107
- into a folder per tab and point each tab at it.
90
+ Tabs also **scope the sidebar**: when the current route falls under a tab's `path`, the sidebar shows only that section's pages — so `/adapters/*` lists the adapters and nothing else. The folder at a tab's `path` becomes the section, so this needs no extra config beyond the tabs themselves; structure your content into a folder per tab and point each tab at it.
108
91
 
109
- On a route under no tab (or a tab whose `path` is `/`), the sidebar shows the
110
- pages that _don't_ belong to a tab — each tab's folder is hidden from it, since
111
- that section already has its own tab in the header. So a root landing page lists
112
- your loose top-level pages while the sectioned content stays behind its tab,
113
- mirroring Fumadocs' root folders. If a route has no pages of its own to show
114
- this way, the full tree is shown instead, so the sidebar is never left blank.
92
+ On a route under no tab (or a tab whose `path` is `/`), the sidebar shows the pages that _don't_ belong to a tab — each tab's folder is hidden from it, since that section already has its own tab in the header. So a root landing page lists your loose top-level pages while the sectioned content stays behind its tab, mirroring Fumadocs' root folders. If a route has no pages of its own to show this way, the full tree is shown instead, so the sidebar is never left blank.
93
+
94
+ ## Selectors
95
+
96
+ For switching between whole partitions of a site a product, a version, or any grouped set of destinations add a `selector`. Each renders as a dropdown in the header, showing the option whose `path` matches the current route:
97
+
98
+ ```ts blume.config.ts lineNumbers
99
+ navigation: {
100
+ selectors: [
101
+ {
102
+ kind: "version",
103
+ label: "Version",
104
+ items: [
105
+ { label: "v2 (latest)", path: "/v2", icon: "rocket" },
106
+ { label: "v1", path: "/v1" },
107
+ ],
108
+ },
109
+ ],
110
+ }
111
+ ```
112
+
113
+ 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.
115
114
 
116
115
  ## Explicit sidebar
117
116
 
118
- For full control, define `navigation.sidebar` in config. When it's set, Blume
119
- uses it verbatim and skips file-system generation:
117
+ For full control, define `navigation.sidebar` in config. When it's set, Blume uses it verbatim and skips file-system generation:
120
118
 
121
119
  ```ts blume.config.ts lineNumbers
122
120
  navigation: {
@@ -132,15 +130,11 @@ navigation: {
132
130
  }
133
131
  ```
134
132
 
135
- Each item is a page route (a string), a group (`label` + `items`), or a link
136
- (`label` + `href`). Groups can nest, take a [`display`
137
- mode](/docs/content/meta#display-modes), and start `collapsed`.
133
+ Each item is a page route (a string), a group (`label` + `items`), or a link (`label` + `href`). Groups can nest, take a [`display` mode](/docs/content/meta#display-modes), and start `collapsed`.
138
134
 
139
135
  ## Repository link
140
136
 
141
- When you set [`github`](/docs/configuration) in your config, Blume shows a GitHub
142
- icon in the header — beside the theme toggle — that links to your repository. It's
143
- on by default; hide it with `navigation.repo`:
137
+ When you set [`github`](/docs/configuration) in your config, Blume shows a GitHub icon in the header — beside the theme toggle — that links to your repository. It's on by default; hide it with `navigation.repo`:
144
138
 
145
139
  ```ts blume.config.ts lineNumbers
146
140
  navigation: {
@@ -148,35 +142,27 @@ navigation: {
148
142
  }
149
143
  ```
150
144
 
151
- The link only appears when `github` is configured, so projects without a repo are
152
- unaffected either way.
145
+ The link only appears when `github` is configured, so projects without a repo are unaffected either way.
153
146
 
154
147
  ## Breadcrumbs and pagination
155
148
 
156
149
  These come for free from the sidebar tree — no configuration:
157
150
 
158
151
  - **Breadcrumbs** show the current page's parent group above the title.
159
- - **Previous and next** links at the foot of each page follow sidebar order,
160
- skipping hidden pages.
152
+ - **Previous and next** links at the foot of each page follow sidebar order, skipping hidden pages.
161
153
 
162
154
  ## On this page
163
155
 
164
- A right-rail outline is generated automatically from each page's `##` and `###`
165
- headings, so long pages stay scannable. On narrower screens, where the right rail
166
- is hidden, it collapses into an “On this page” dropdown above the content.
156
+ A right-rail outline is generated automatically from each page's `##` and `###` headings, so long pages stay scannable. On narrower screens, where the right rail is hidden, it collapses into an “On this page” dropdown above the content.
167
157
 
168
158
  ## Page actions
169
159
 
170
160
  Below the table of contents, every page shows a set of quick actions:
171
161
 
172
- - **Edit this page on GitHub** — links straight to the source file. Appears once
173
- you set [`github`](/docs/configuration) in your config.
162
+ - **Edit this page on GitHub** — links straight to the source file. Appears once you set [`github`](/docs/configuration) in your config.
174
163
  - **Scroll to top** — smoothly returns to the top of long pages.
175
- - **Give feedback** — opens a prefilled GitHub issue with an optional reaction and
176
- note (also requires `github`).
164
+ - **Give feedback** — opens a prefilled GitHub issue with an optional reaction and note (also requires `github`).
177
165
 
178
- Three more hand the page to AI tools — **Copy as Markdown**, **Open in chat**, and
179
- **Ask AI about this page** — covered in [AI](/docs/configuration/ai#copy-as-markdown).
166
+ Three more hand the page to AI tools — **Copy as Markdown**, **Open in chat**, and **Ask AI about this page** — covered in [AI](/docs/configuration/ai#copy-as-markdown).
180
167
 
181
- With [`export`](/docs/configuration/export) on, an **Export** action also lets
182
- readers download the page as a PDF or EPUB.
168
+ With [`export`](/docs/configuration/export) on, an **Export** action also lets readers download the page as a PDF or EPUB.
@@ -3,16 +3,11 @@ title: Content sources
3
3
  description: Pull docs from local files, a remote repo, or any custom backend — and mix several sources into one site.
4
4
  ---
5
5
 
6
- By default Blume reads a folder of `.md`/`.mdx` files. **Content sources** let
7
- you pull pages from somewhere else — a remote repository, a CMS, or any custom
8
- backend — and mix several sources into a single site. Sources are read at
9
- build time; Blume stays static-first.
6
+ By default Blume reads a folder of `.md`/`.mdx` files. **Content sources** let you pull pages from somewhere else — a remote repository, a CMS, or any custom backend — and mix several sources into a single site. Sources are read at build time; Blume stays static-first.
10
7
 
11
8
  ## The default
12
9
 
13
- With no configuration, Blume scans your content root (`docs` by default) as one
14
- implicit filesystem source. The top-level `content.root`/`include`/`exclude`
15
- options still work exactly as before — nothing to change.
10
+ With no configuration, Blume scans your content root (`docs` by default) as one implicit filesystem source. The top-level `content.root`/`include`/`exclude` options still work exactly as before — nothing to change.
16
11
 
17
12
  ```ts blume.config.ts
18
13
  import { defineConfig } from "blume";
@@ -24,10 +19,7 @@ export default defineConfig({
24
19
 
25
20
  ## Multiple sources
26
21
 
27
- Add a `content.sources` array to compose sources. Each entry is namespaced by an
28
- optional `prefix`, so its routes nest under `/<prefix>/…`. When `sources` is
29
- present it replaces the implicit default, so include a `filesystem` entry for
30
- your local docs.
22
+ Add a `content.sources` array to compose sources. Each entry is namespaced by an optional `prefix`, so its routes nest under `/<prefix>/…`. When `sources` is present it replaces the implicit default, so include a `filesystem` entry for your local docs.
31
23
 
32
24
  ```ts blume.config.ts
33
25
  import { defineConfig } from "blume";
@@ -49,14 +41,11 @@ export default defineConfig({
49
41
  });
50
42
  ```
51
43
 
52
- If two sources resolve to the same route, Blume reports a `BLUME_DUPLICATE_ROUTE`
53
- build error — give each source a distinct `prefix`.
44
+ If two sources resolve to the same route, Blume reports a `BLUME_DUPLICATE_ROUTE` build error — give each source a distinct `prefix`.
54
45
 
55
46
  ## Remote MDX
56
47
 
57
- The built-in `mdx-remote` source fetches raw `.md`/`.mdx` over HTTP. Enumerate
58
- files either from a GitHub repo subtree (`github`) or explicitly against a raw
59
- base URL (`url` + `files`):
48
+ The built-in `mdx-remote` source fetches raw `.md`/`.mdx` over HTTP. Enumerate files either from a GitHub repo subtree (`github`) or explicitly against a raw base URL (`url` + `files`):
60
49
 
61
50
  ```ts blume.config.ts
62
51
  {
@@ -67,34 +56,19 @@ base URL (`url` + `files`):
67
56
  }
68
57
  ```
69
58
 
70
- A private repo's token is read from the `GITHUB_TOKEN` environment variable — it
71
- is never inlined into your config or generated output.
59
+ A private repo's token is read from the `GITHUB_TOKEN` environment variable — it is never inlined into your config or generated output.
72
60
 
73
- Remote pages are rendered with full MDX-plus-component fidelity: their bodies are
74
- materialized into a hidden staging directory and rendered through Astro
75
- alongside your local docs, so callouts, tabs, and every other Blume component
76
- keep working.
61
+ Remote pages are rendered with full MDX-plus-component fidelity: their bodies are materialized into a hidden staging directory and rendered through Astro alongside your local docs, so callouts, tabs, and every other Blume component keep working.
77
62
 
78
63
  ### Caching and offline builds
79
64
 
80
- Each remote source keeps a snapshot under `.blume/cache/<source>/`. If a fetch
81
- fails — a network blip or a CMS outage — Blume serves the last-known-good
82
- snapshot with a warning rather than failing the build. The cache lives inside
83
- `.blume/` and is regenerated, never committed.
65
+ Each remote source keeps a snapshot under `.blume/cache/<source>/`. If a fetch fails — a network blip or a CMS outage — Blume serves the last-known-good snapshot with a warning rather than failing the build. The cache lives inside `.blume/` and is regenerated, never committed.
84
66
 
85
- In dev, remote content is fetched once and frozen for the session; restart the
86
- dev server to refresh it. Local filesystem sources hot-reload as usual. To poll
87
- a remote source for changes instead, set `pollInterval` (seconds) on it — the
88
- dev server re-fetches on that interval and reloads only when the content
89
- actually changes. Leave it unset to avoid hitting the API while you work.
67
+ In dev, remote content is fetched once and frozen for the session; restart the dev server to refresh it. Local filesystem sources hot-reload as usual. To poll a remote source for changes instead, set `pollInterval` (seconds) on it — the dev server re-fetches on that interval and reloads only when the content actually changes. Leave it unset to avoid hitting the API while you work.
90
68
 
91
69
  ## GitHub Releases
92
70
 
93
- The built-in `github-releases` source turns a repo's releases into a changelog:
94
- each release becomes a `type: changelog` entry, so your release notes _are_ your
95
- changelog — nothing to write twice. Combined with the generated
96
- [changelog timeline](/docs/advanced/changelog), publishing a GitHub release
97
- ships a changelog entry.
71
+ The built-in `github-releases` source turns a repo's releases into a changelog: each release becomes a `type: changelog` entry, so your release notes _are_ your changelog — nothing to write twice. Combined with the generated [changelog timeline](/docs/advanced/changelog), publishing a GitHub release ships a changelog entry.
98
72
 
99
73
  ```ts blume.config.ts
100
74
  import { defineConfig } from "blume";
@@ -117,25 +91,13 @@ export default defineConfig({
117
91
  });
118
92
  ```
119
93
 
120
- Each release maps to the changelog fields automatically: its name (or tag)
121
- becomes the title, its published date drives the timeline order, the tag becomes
122
- `changelog.version`, and prereleases are tagged `Prerelease` (others `Release`).
123
- The notes render as the entry body. Give the source a `prefix` so its release
124
- pages nest under a route like `/changelog/v1-2-0`.
94
+ Each release maps to the changelog fields automatically: its name (or tag) becomes the title, its published date drives the timeline order, the tag becomes `changelog.version`, and prereleases are tagged `Prerelease` (others `Release`). The notes render as the entry body. Give the source a `prefix` so its release pages nest under a route like `/changelog/v1-2-0`.
125
95
 
126
- A private repo authenticates with the `GITHUB_TOKEN` environment variable — the
127
- same token the other GitHub features use, never inlined into your config. Like
128
- every remote source it's cached under `.blume/cache/<source>/` and served
129
- offline if the API is unreachable. Because a changelog is supplementary, a fetch
130
- failure with no cache (say a CI build without a token) degrades to an empty
131
- changelog with a warning rather than failing the build — set `GITHUB_TOKEN` in
132
- your CI and deploy environments to populate it.
96
+ A private repo authenticates with the `GITHUB_TOKEN` environment variable — the same token the other GitHub features use, never inlined into your config. Like every remote source it's cached under `.blume/cache/<source>/` and served offline if the API is unreachable. Because a changelog is supplementary, a fetch failure with no cache (say a CI build without a token) degrades to an empty changelog with a warning rather than failing the build — set `GITHUB_TOKEN` in your CI and deploy environments to populate it.
133
97
 
134
98
  ## Sanity
135
99
 
136
- The built-in `sanity` source runs a GROQ query and maps each document's fields
137
- to frontmatter and its Portable Text body to Markdown. The `@sanity/client`
138
- package is an optional peer dependency — install it only if you use this source.
100
+ The built-in `sanity` source runs a GROQ query and maps each document's fields to frontmatter and its Portable Text body to Markdown. The `@sanity/client` package is an optional peer dependency — install it only if you use this source.
139
101
 
140
102
  ```ts blume.config.ts
141
103
  import { defineConfig } from "blume";
@@ -158,17 +120,11 @@ export default defineConfig({
158
120
  });
159
121
  ```
160
122
 
161
- A read token for a private dataset comes from the `SANITY_TOKEN` environment
162
- variable. Custom Portable Text block types map to Blume components through the
163
- adapter's `serializers` option, available when you construct `sanitySource`
164
- directly via a [custom source](#custom-sources).
123
+ A read token for a private dataset comes from the `SANITY_TOKEN` environment variable. Custom Portable Text block types map to Blume components through the adapter's `serializers` option, available when you construct `sanitySource` directly via a [custom source](#custom-sources).
165
124
 
166
125
  ## Notion
167
126
 
168
- The built-in `notion` source turns a Notion database into a collection: each row
169
- becomes a page, its properties become frontmatter, and its block tree becomes
170
- MDX. Callouts, toggles, columns, and code blocks map to the matching Blume
171
- components. `@notionhq/client` is an optional peer dependency.
127
+ The built-in `notion` source turns a Notion database into a collection: each row becomes a page, its properties become frontmatter, and its block tree becomes MDX. Callouts, toggles, columns, and code blocks map to the matching Blume components. `@notionhq/client` is an optional peer dependency.
172
128
 
173
129
  ```ts blume.config.ts
174
130
  import { defineConfig } from "blume";
@@ -190,29 +146,14 @@ export default defineConfig({
190
146
  });
191
147
  ```
192
148
 
193
- The integration token comes from the `NOTION_TOKEN` environment variable (share
194
- the database with your integration). By default every page is imported; set
195
- `publishedValue` to make the `Status` property a publish gate — any other value
196
- then maps to `draft: true`, which production builds drop. **Notion image URLs
197
- are signed and expire**, so the adapter downloads them at build time into the
198
- site's assets and rewrites the references — a CMS image never rots a static
199
- build.
149
+ The integration token comes from the `NOTION_TOKEN` environment variable (share the database with your integration). By default every page is imported; set `publishedValue` to make the `Status` property a publish gate — any other value then maps to `draft: true`, which production builds drop. **Notion image URLs are signed and expire**, so the adapter downloads them at build time into the site's assets and rewrites the references — a CMS image never rots a static build.
200
150
 
201
151
  ## Preview and sync
202
152
 
203
153
  Two flags control how remote content is fetched and what's included:
204
154
 
205
- - **`--preview`** on `blume dev` or `blume build` renders drafts and pulls
206
- unpublished CMS content Sanity switches to its `previewDrafts` perspective,
207
- and Notion stops filtering by `Status`. Production builds without the flag
208
- exclude drafts as usual, so a preview build is a safe way to review
209
- unpublished work before it ships.
210
- - **`blume sync`** re-fetches every remote source and regenerates the runtime.
211
- Dev is cache-first — a remote source is fetched once and served from
212
- `.blume/cache` on restart (fast and offline-tolerant), so `blume sync` is how
213
- you pull the latest CMS content without restarting the dev server (a running
214
- server hot-reloads). Add `--force` to drop the cache first, or set
215
- `pollInterval` on a source to refresh automatically.
155
+ - **`--preview`** on `blume dev` or `blume build` renders drafts and pulls unpublished CMS content — Sanity switches to its `previewDrafts` perspective, and Notion stops filtering by `Status`. Production builds without the flag exclude drafts as usual, so a preview build is a safe way to review unpublished work before it ships.
156
+ - **`blume sync`** re-fetches every remote source and regenerates the runtime. Dev is cache-first — a remote source is fetched once and served from `.blume/cache` on restart (fast and offline-tolerant), so `blume sync` is how you pull the latest CMS content without restarting the dev server (a running server hot-reloads). Add `--force` to drop the cache first, or set `pollInterval` on a source to refresh automatically.
216
157
 
217
158
  ```sh
218
159
  blume dev --preview # author workflow: see drafts live
@@ -223,9 +164,7 @@ blume sync --force # ...ignoring any cached snapshot
223
164
 
224
165
  ## Custom sources
225
166
 
226
- Any object implementing the `ContentSource` interface can be passed directly,
227
- which is how an adapter with custom serializers — or any backend not built in —
228
- plugs in without its SDK touching the core install:
167
+ Any object implementing the `ContentSource` interface can be passed directly, which is how an adapter with custom serializers — or any backend not built in — plugs in without its SDK touching the core install:
229
168
 
230
169
  ```ts blume.config.ts
231
170
  import { defineConfig } from "blume";
@@ -254,6 +193,4 @@ export default defineConfig({
254
193
  });
255
194
  ```
256
195
 
257
- A source normalizes its native shape (Portable Text, Notion blocks, remote HTML)
258
- to Markdown/MDX text, so the same components and markdown features apply no
259
- matter where a page comes from.
196
+ A source normalizes its native shape (Portable Text, Notion blocks, remote HTML) to Markdown/MDX text, so the same components and markdown features apply no matter where a page comes from.