blume 1.0.3 → 1.1.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 (126) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/cli/index.js +13784 -10579
  3. package/dist/cli/index.js.map +93 -61
  4. package/dist/types/core/config-input.d.ts +87 -8
  5. package/dist/types/core/data.d.ts +21 -0
  6. package/dist/types/core/deployment-env.d.ts +6 -0
  7. package/dist/types/core/diagnostics.d.ts +23 -0
  8. package/dist/types/core/i18n-ui.d.ts +140 -140
  9. package/dist/types/core/schema.d.ts +549 -370
  10. package/dist/types/core/sources/types.d.ts +3 -1
  11. package/dist/types/core/standard-schema.d.ts +41 -0
  12. package/dist/types/core/types.d.ts +23 -0
  13. package/dist/types/og/card.d.ts +63 -0
  14. package/dist/types/og/dimensions.d.ts +12 -0
  15. package/dist/types/openapi/references.d.ts +12 -7
  16. package/docs/01-quickstart.mdx +1 -1
  17. package/docs/02-deployment.mdx +9 -1
  18. package/docs/advanced/api-reference.mdx +22 -3
  19. package/docs/advanced/changelog.mdx +1 -1
  20. package/docs/advanced/skills.mdx +1 -1
  21. package/docs/configuration/ai.mdx +1 -1
  22. package/docs/configuration/customization.mdx +1 -1
  23. package/docs/configuration/export.mdx +1 -1
  24. package/docs/configuration/index.mdx +21 -1
  25. package/docs/configuration/search.mdx +28 -1
  26. package/docs/configuration/seo.mdx +40 -2
  27. package/docs/configuration/theming.mdx +1 -1
  28. package/docs/content/components.mdx +15 -2
  29. package/docs/content/index.mdx +1 -1
  30. package/docs/content/meta.mdx +1 -1
  31. package/docs/content/navigation.mdx +11 -1
  32. package/docs/content/sources.mdx +1 -1
  33. package/docs/content/syntax.mdx +116 -4
  34. package/docs/reference/cli.mdx +79 -1
  35. package/docs/reference/frontmatter.mdx +29 -1
  36. package/package.json +3 -3
  37. package/skills/blume-migrate/SKILL.md +170 -0
  38. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +20 -0
  39. package/skills/blume-migrate/references/docusaurus.md +95 -0
  40. package/skills/blume-migrate/references/fumadocs.md +95 -0
  41. package/skills/blume-migrate/references/mintlify.md +156 -0
  42. package/skills/blume-migrate/references/monorepo.md +224 -0
  43. package/skills/blume-migrate/references/nextra.md +76 -0
  44. package/skills/blume-migrate/references/starlight.md +116 -0
  45. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +478 -0
  46. package/src/ai/llms.ts +15 -0
  47. package/src/astro/adapter-root.ts +70 -0
  48. package/src/astro/component-slots.ts +3 -2
  49. package/src/astro/generate.ts +132 -42
  50. package/src/astro/index.ts +1 -0
  51. package/src/astro/pages.ts +18 -3
  52. package/src/astro/templates.ts +158 -56
  53. package/src/audit/agent.ts +114 -0
  54. package/src/audit/catalog.ts +826 -0
  55. package/src/audit/checks/assets.ts +177 -0
  56. package/src/audit/checks/content.ts +231 -0
  57. package/src/audit/checks/duplicates.ts +131 -0
  58. package/src/audit/checks/i18n.ts +246 -0
  59. package/src/audit/checks/indexability.ts +213 -0
  60. package/src/audit/checks/links.ts +223 -0
  61. package/src/audit/checks/llms.ts +135 -0
  62. package/src/audit/checks/network.ts +272 -0
  63. package/src/audit/checks/og-image.ts +113 -0
  64. package/src/audit/checks/redirects.ts +87 -0
  65. package/src/audit/checks/robots.ts +114 -0
  66. package/src/audit/checks/sitemap.ts +229 -0
  67. package/src/audit/checks/social.ts +238 -0
  68. package/src/audit/crawl.ts +259 -0
  69. package/src/audit/graph.ts +74 -0
  70. package/src/audit/html.ts +54 -0
  71. package/src/audit/image-size.ts +63 -0
  72. package/src/audit/locate.ts +33 -0
  73. package/src/audit/redirects.ts +74 -0
  74. package/src/audit/report.ts +278 -0
  75. package/src/audit/run.ts +198 -0
  76. package/src/audit/snapshot.ts +189 -0
  77. package/src/audit/types.ts +214 -0
  78. package/src/audit/url.ts +103 -0
  79. package/src/cli/commands/audit.ts +205 -0
  80. package/src/cli/commands/build.ts +51 -12
  81. package/src/cli/index.ts +2 -0
  82. package/src/components/content/Callout.astro +8 -2
  83. package/src/components/content/Prompt.astro +25 -13
  84. package/src/components/content/Tabs.astro +98 -15
  85. package/src/components/layout/Breadcrumbs.astro +1 -1
  86. package/src/components/layout/Header.astro +5 -8
  87. package/src/components/layout/Logo.astro +13 -1
  88. package/src/components/layout/PageFeedback.astro +2 -2
  89. package/src/components/layout/PageLayout.astro +9 -9
  90. package/src/components/layout/Pagination.astro +7 -7
  91. package/src/components/layout/RootLayout.astro +9 -11
  92. package/src/components/layout/Search.astro +36 -7
  93. package/src/components/layout/TableOfContents.astro +1 -1
  94. package/src/components/layout/nav-utils.ts +9 -7
  95. package/src/components/openapi/Authorization.astro +80 -0
  96. package/src/components/openapi/Operation.astro +19 -1
  97. package/src/components/openapi/ParametersTable.astro +1 -1
  98. package/src/components/openapi/security.ts +201 -0
  99. package/src/components/openapi/snippets.ts +42 -13
  100. package/src/core/config-input.ts +94 -8
  101. package/src/core/data.ts +18 -2
  102. package/src/core/deployment-env.ts +9 -0
  103. package/src/core/diagnostics.ts +59 -12
  104. package/src/core/links.ts +2 -91
  105. package/src/core/nav-diagnostics.ts +48 -4
  106. package/src/core/navigation.ts +55 -13
  107. package/src/core/probe.ts +136 -0
  108. package/src/core/project-graph.ts +8 -0
  109. package/src/core/schema.ts +100 -1
  110. package/src/core/sources/normalize.ts +198 -25
  111. package/src/core/sources/types.ts +3 -1
  112. package/src/core/sources/watch.ts +5 -0
  113. package/src/core/standard-schema.ts +54 -0
  114. package/src/core/types.ts +23 -0
  115. package/src/deploy/adapter-output.ts +27 -15
  116. package/src/deploy/headers.ts +66 -0
  117. package/src/deploy/redirects.ts +49 -9
  118. package/src/markdown/index.ts +2 -0
  119. package/src/markdown/language-icon.ts +2 -1
  120. package/src/markdown/table-wrap.ts +43 -0
  121. package/src/og/card.ts +128 -36
  122. package/src/og/index.ts +1 -1
  123. package/src/og/logo.ts +21 -0
  124. package/src/openapi/references.ts +19 -16
  125. package/src/search/popular.ts +33 -0
  126. package/src/theme/entry.ts +56 -6
@@ -1,4 +1,4 @@
1
- import type { ResolvedI18nConfig } from "../schema.ts";
1
+ import type { FrontmatterExtend, ResolvedI18nConfig } from "../schema.ts";
2
2
  import type { Diagnostic } from "../types.ts";
3
3
  /**
4
4
  * A single content item, normalized by a source adapter. Adapters lower their
@@ -111,5 +111,7 @@ export interface NormalizeContext {
111
111
  /** Site-wide route mount point (`""` or `/seg`), prepended to every route. */
112
112
  basePath?: string;
113
113
  defaultType: string;
114
+ /** Opt-in custom frontmatter keys (`frontmatter.extend`), schema per key. */
115
+ frontmatterExtend?: FrontmatterExtend;
114
116
  i18n?: ResolvedI18nConfig;
115
117
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The Standard Schema interface (https://standardschema.dev), the minimal
3
+ * `~standard` surface shared by Zod 3.24+, Zod 4, Valibot, ArkType, and
4
+ * friends. Blume accepts user-supplied validation schemas (e.g.
5
+ * `frontmatter.extend`) through this interface instead of Zod's own types:
6
+ * `blume.config.ts` imports zod from the *consumer's* node_modules, which may
7
+ * be a different major version than the zod Blume bundles, and calling Zod
8
+ * methods (`.extend()`, `.safeParse()`) across instances is unsupported. The
9
+ * `~standard.validate` contract is version- and library-agnostic.
10
+ */
11
+ /** One validation failure, with an optional path into the checked value. */
12
+ export interface StandardSchemaIssue {
13
+ readonly message: string;
14
+ readonly path?: readonly (PropertyKey | {
15
+ readonly key: PropertyKey;
16
+ })[] | undefined;
17
+ }
18
+ /** A passing validation: the (possibly transformed) output value. */
19
+ export interface StandardSchemaSuccess<Output> {
20
+ readonly value: Output;
21
+ readonly issues?: undefined;
22
+ }
23
+ /** A failing validation: one or more issues. */
24
+ export interface StandardSchemaFailure {
25
+ readonly issues: readonly StandardSchemaIssue[];
26
+ }
27
+ export type StandardSchemaResult<Output> = StandardSchemaSuccess<Output> | StandardSchemaFailure;
28
+ /** A validation schema exposing the Standard Schema `~standard` contract. */
29
+ export interface StandardSchema<Input = unknown, Output = Input> {
30
+ readonly "~standard": {
31
+ readonly version: 1;
32
+ readonly vendor: string;
33
+ readonly validate: (value: unknown) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
34
+ readonly types?: {
35
+ readonly input: Input;
36
+ readonly output: Output;
37
+ } | undefined;
38
+ };
39
+ }
40
+ /** Whether a config-supplied value implements the `~standard` contract. */
41
+ export declare const isStandardSchema: (value: unknown) => value is StandardSchema;
@@ -12,6 +12,13 @@ export interface Diagnostic {
12
12
  file?: string;
13
13
  line?: number;
14
14
  column?: number;
15
+ /**
16
+ * The built URL this diagnostic is about, for findings that are a property of
17
+ * the output rather than of a source file (`blume audit`). Set alongside
18
+ * `file`/`line` where the page maps back to authored content, so a finding can
19
+ * name both the URL that's wrong and the frontmatter line that fixes it.
20
+ */
21
+ url?: string;
15
22
  schemaPath?: string;
16
23
  suggestion?: string;
17
24
  docsUrl?: string;
@@ -110,6 +117,12 @@ export interface PageRecord {
110
117
  description?: string;
111
118
  contentType: string;
112
119
  meta: PageMeta;
120
+ /**
121
+ * Custom frontmatter values declared via `frontmatter.extend`, validated by
122
+ * the user-supplied schemas (schema output, so transforms apply). Present
123
+ * only when the project opts in and the page carries at least one value.
124
+ */
125
+ custom?: Record<string, unknown>;
113
126
  headings: Heading[];
114
127
  /** Whether the file is `.md`/`.mdx`. */
115
128
  format: "md" | "mdx";
@@ -150,7 +163,17 @@ export type NavNode = {
150
163
  /** Top-level tab/section. */
151
164
  export interface NavTab {
152
165
  label: string;
166
+ /**
167
+ * The tab's section prefix, used to scope the sidebar and match the active
168
+ * tab. Not necessarily a linkable route — a section may have no index page.
169
+ */
153
170
  path: string;
171
+ /**
172
+ * The clickable target. Equals `path` when the section has an index page;
173
+ * otherwise it's resolved to the section's first page so the tab never links
174
+ * to a 404. Absent when it matches `path`.
175
+ */
176
+ href?: string;
154
177
  icon?: string;
155
178
  items?: NavSelectorItem[];
156
179
  }
@@ -0,0 +1,63 @@
1
+ import type { RenderOptions } from "takumi-js";
2
+ /**
3
+ * A Google Font family to load into the OG card renderer. A bare string is the
4
+ * family name (weight 400, normal style); the object form pins weight and style.
5
+ * Handed straight to Takumi's `googleFonts` helper, which fetches the family
6
+ * from Google Fonts at build and returns per-glyph coverage subsets.
7
+ */
8
+ export type OgFont = string | {
9
+ /** Google Fonts family name, e.g. `"Noto Sans JP"`. */
10
+ name: string;
11
+ /** `400`, `[400, 700]`, or a variable range like `"100..900"`. */
12
+ weight?: number | number[] | string;
13
+ /** `"normal"`, `"italic"`, or both. */
14
+ style?: "normal" | "italic" | ("normal" | "italic")[];
15
+ };
16
+ export interface OgCardPalette {
17
+ accent?: string;
18
+ background?: string;
19
+ border?: string;
20
+ foreground?: string;
21
+ muted?: string;
22
+ }
23
+ export interface OgCardOptions {
24
+ /** Large headline — the page title. */
25
+ title: string;
26
+ /** Accent color (named preset or any CSS color) for the fallback brand mark. */
27
+ accent?: string;
28
+ /** Brand/site name shown in the top-left lockup. */
29
+ brand?: string;
30
+ /** Muted subtitle under the headline (usually the site description). */
31
+ description?: string;
32
+ /**
33
+ * Inlined SVG markup of the configured logo, painted into
34
+ * the brand lockup. Falls back to an accent mark when absent.
35
+ */
36
+ logo?: string;
37
+ /** Optional colors for the generated card. */
38
+ palette?: OgCardPalette;
39
+ /** Footer-left repository slug, e.g. `owner/repo`. */
40
+ repo?: string;
41
+ /** Footer-right site host, e.g. `docs.acme.com`. */
42
+ site?: string;
43
+ /**
44
+ * Pre-fetched image entries, or a group controlling how remote images (and
45
+ * emoji glyphs) are fetched. Blume merges in a shared glyph cache; see
46
+ * {@link resolveImages}.
47
+ */
48
+ images?: RenderOptions["images"];
49
+ /**
50
+ * Google Font families for non-Latin titles. Takumi's built-in font covers
51
+ * only Latin, so a CJK (etc.) title renders as tofu without a family that
52
+ * covers its script — see {@link loadFonts}.
53
+ */
54
+ fonts?: OgFont[];
55
+ }
56
+ /**
57
+ * Truncate to `max` code points with an ellipsis. Slices by code points, not
58
+ * UTF-16 units, so cutting mid-emoji doesn't leave a lone surrogate (a broken
59
+ * glyph) before the ellipsis.
60
+ */
61
+ export declare const truncate: (value: string, max: number) => string;
62
+ /** Render a 1200x630 Open Graph card to a PNG buffer. */
63
+ export declare const renderOgImage: (options: OgCardOptions) => Promise<Uint8Array>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Dimensions of a generated OG card, shared by the renderer (`card.ts`) and the
3
+ * layouts that declare them as `og:image:width`/`og:image:height` so a crawler
4
+ * can lay out the card without fetching the PNG first.
5
+ *
6
+ * This lives apart from `card.ts` because that module imports the Takumi native
7
+ * binding at load; a layout importing it would drag the renderer into every
8
+ * page render (and into the prerender/SSR bundles that externalize it).
9
+ */
10
+ export declare const OG_IMAGE_WIDTH = 1200;
11
+ export declare const OG_IMAGE_HEIGHT = 630;
12
+ export declare const OG_IMAGE_TYPE = "image/png";
@@ -1,11 +1,10 @@
1
1
  import type { ResolvedConfig } from "../core/schema.ts";
2
- import type { NavTab } from "../core/types.ts";
3
2
  /**
4
3
  * Pure resolution of the configured API reference blocks into concrete routes,
5
- * labels, and a renderer choice — no file IO, so the content source, the nav
6
- * tabs, the Scalar page generator, and the `blume:openapi` data module all share
7
- * one source of truth. Kept free of any Astro/template imports so `core` can
8
- * depend on it without a cycle.
4
+ * labels, and a renderer choice — no file IO, so the content source, the
5
+ * nav-target validation, the Scalar page generator, and the `blume:openapi`
6
+ * data module all share one source of truth. Kept free of any Astro/template
7
+ * imports so `core` can depend on it without a cycle.
9
8
  */
10
9
  export type ReferenceKind = "openapi" | "asyncapi";
11
10
  /** Who renders a reference: Blume's own UI, or the embedded Scalar SPA. */
@@ -52,8 +51,14 @@ export declare const normalizeRoute: (route: string) => string;
52
51
  * by default); AsyncAPI is always rendered by Scalar for now.
53
52
  */
54
53
  export declare const resolveReferences: (config: ResolvedConfig) => ReferenceSource[];
55
- /** Nav tabs (header links) for every reference, regardless of renderer. */
56
- export declare const referenceTabs: (config: ResolvedConfig) => NavTab[];
54
+ /**
55
+ * Mounted route for every reference, regardless of renderer. References no
56
+ * longer add a header tab automatically — authors point a `navigation.tabs`
57
+ * entry at one of these routes to surface it (and, for Blume-rendered specs, to
58
+ * scope its operations sidebar). These routes are whitelisted as valid nav
59
+ * targets so such a tab doesn't read as a broken link.
60
+ */
61
+ export declare const referenceRoutes: (config: ResolvedConfig) => string[];
57
62
  /** Blume-rendered OpenAPI references, deduped by route (first wins). */
58
63
  export declare const blumeReferences: (config: ResolvedConfig) => ReferenceSource[];
59
64
  /** Whether any reference is Scalar-rendered (gates the `@scalar/astro` dep + pages). */
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Quickstart
3
- description: Install Blume, scaffold a project, and ship your first page in minutes.
3
+ description: Install Blume, scaffold a new project, and ship your first documentation page in minutes — then grow it into a full site at your own pace.
4
4
  sidebar:
5
5
  label: Quickstart
6
6
  order: 1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Deployment
3
- description: Deploy static docs to any host, or switch to server rendering with an adapter.
3
+ description: Deploy static docs to any host with zero configuration, or switch to server-side rendering with an adapter when you need dynamic behavior.
4
4
  sidebar:
5
5
  label: Deployment
6
6
  order: 2
@@ -119,6 +119,14 @@ redirects: [{ from: "/old", to: "/new", status: 301 }];
119
119
  `from` is matched as an exact path — wildcards and pattern matching (e.g. `/blog/:slug` or `/old/*`) aren't supported. If you need pattern-based rules, handle them in an infrastructure file like `vercel.json` (which supports wildcard `source` patterns) or your host's redirect config instead. A `vercel.json` you ship in `public/` is preserved as-is.
120
120
  :::
121
121
 
122
+ :::note
123
+ Write both `from` and `to` as if mounted at root — under [`deployment.base`](#subpath-deploys) and [`basePath`](#mount-the-docs-under-a-path) alike, Blume rewrites both sides for you, so a redirect lands inside the base. A base you've already written into `to` by hand is preserved rather than doubled.
124
+ :::
125
+
126
+ ## Content types
127
+
128
+ A static build also emits a `_headers` file that pins `charset=utf-8` onto the raw AI-ready endpoints — `/<route>.md`, `/<route>.mdx`, and the `.txt` files (`llms.txt`, `llms-full.txt`). Those responses are valid UTF-8, but many static hosts serve them as `text/markdown` / `text/plain` with **no** charset, and browsers then fall back to Windows-1252 — so non-ASCII docs (Japanese, accented Latin, …) render as mojibake when the raw URL is opened directly. HTML pages are unaffected because they carry `<meta charset>`. Netlify and Cloudflare (Pages/Workers static assets) read `_headers`; hosts that don't (Vercel, S3) ignore the file harmlessly. A `_headers` you ship in `public/` is left untouched.
129
+
122
130
  ## Environment variables
123
131
 
124
132
  When a feature needs a runtime secret, Blume warns at `blume dev`/`build` if it's missing — so the problem surfaces early instead of at the first request:
@@ -3,7 +3,7 @@ title: OpenAPI / AsyncAPI
3
3
  description: Drop in an OpenAPI spec and get a native API reference — one real page per operation, in your sidebar and search.
4
4
  ---
5
5
 
6
- Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, and generated code samples. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. These docs ship one: the [Example API](/api) reference is generated from the Petstore spec by the config below.
6
+ Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, and generated code samples. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. The config below points Blume at the public Petstore spec as an example.
7
7
 
8
8
  ```ts blume.config.ts lineNumbers
9
9
  openapi: {
@@ -12,7 +12,15 @@ openapi: {
12
12
  }
13
13
  ```
14
14
 
15
- That mounts the reference at `/reference` (an overview page) with each operation at `/reference/<tag>/<operation>`, and adds a header tab. The `spec` is either an `http(s)` URL or a path to a local file in your project. Blume parses it with [Scalar's OpenAPI parser](https://github.com/scalar/scalar) — Swagger 2.0 and OpenAPI 3.0 specs are upgraded to 3.1 automatically.
15
+ That mounts the reference at `/reference` (an overview page) with each operation at `/reference/<tag>/<operation>`. The `spec` is either an `http(s)` URL or a path to a local file in your project. Blume parses it with [Scalar's OpenAPI parser](https://github.com/scalar/scalar) — Swagger 2.0 and OpenAPI 3.0 specs are upgraded to 3.1 automatically.
16
+
17
+ The reference doesn't add a header tab on its own. To surface it, point a [navigation tab](/docs/content/navigation#tabs) at its route — this also scopes the operations sidebar for the native renderer:
18
+
19
+ ```ts blume.config.ts
20
+ navigation: {
21
+ tabs: [{ label: "API", path: "/reference" }],
22
+ }
23
+ ```
16
24
 
17
25
  :::note
18
26
  Operations are indexed for search by their **summary and tag**. The rendered schema tables and code samples aren't full-text indexed; search matches an operation's title and section, then links to its own page.
@@ -31,7 +39,7 @@ openapi: {
31
39
 
32
40
  ## Route
33
41
 
34
- `route` controls where the reference mounts — the overview page and the prefix for every operation route (and the header tab's target):
42
+ `route` controls where the reference mounts — the overview page and the prefix for every operation route (and the route you point a navigation tab at):
35
43
 
36
44
  ```ts blume.config.ts lineNumbers
37
45
  openapi: {
@@ -70,6 +78,17 @@ openapi: {
70
78
 
71
79
  `spec` is shorthand for a single-entry `sources`, so you only reach for `sources` when you have more than one.
72
80
 
81
+ ## Authorization
82
+
83
+ Operations that declare [security requirements](https://spec.openapis.org/oas/v3.1.0#security-requirement-object) render an **Authorization** section above their parameters, and the generated code samples send a placeholder credential (`Authorization: Bearer YOUR_TOKEN`, an API-key header, or a query key — whatever the scheme calls for). There's nothing to configure: Blume reads `security` from the spec, so the reference always matches what the API actually enforces.
84
+
85
+ The OpenAPI semantics carry over as written:
86
+
87
+ - An operation's own `security` overrides the document's root default; `security: []` marks it **public** and renders no Authorization section.
88
+ - Multiple requirement entries are alternatives — rendered as "or" groups; every scheme inside one entry is required together. The first alternative feeds the code samples.
89
+ - An empty `{}` entry means auth is **optional** for that operation, and the section says so.
90
+ - OAuth2 scopes are listed per scheme; scheme `description`s from `components.securitySchemes` render inline.
91
+
73
92
  ## The Scalar renderer
74
93
 
75
94
  The native renderer is the default. If you'd rather embed [Scalar](https://scalar.com)'s self-contained API reference — its own sidebar, search, theme, and "Try it" playground on a single route — set `renderer: "scalar"`:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Changelog
3
- description: Author release notes as content, and Blume builds a timeline page and an RSS feed automatically.
3
+ description: Author release notes as ordinary content files or source them from GitHub Releases, and Blume builds a timeline page and an RSS feed automatically.
4
4
  ---
5
5
 
6
6
  Blume ships a changelog out of the box. Write each release as a normal content file, mark it `type: changelog`, and Blume collects every entry into a generated timeline page and an RSS feed — no layout to build, no list to maintain. Or skip the files entirely and [source your changelog from GitHub Releases](#from-github-releases).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Skills
3
- description: The agent skills Blume ships — playbooks that teach a coding agent to build and maintain a Blume docs site.
3
+ description: The agent skills Blume ships — playbooks that teach a coding agent like Claude Code, Codex, or Cursor to build and maintain a Blume docs site.
4
4
  ---
5
5
 
6
6
  Blume ships [agent skills](https://docs.claude.com/en/docs/claude-code/skills) — playbooks that teach a coding agent (Claude Code, Codex, Cursor) how to do a Blume-shaped job without you explaining it. They live on GitHub in the repo's `skills/` folder and are bundled in the package at `node_modules/blume/skills/` once Blume is installed, so any agent can be pointed at a `SKILL.md` directly.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: AI
3
- description: Machine-readable docs with llms.txt and an optional in-page Ask AI assistant.
3
+ description: Make your docs machine-readable with llms.txt, add an optional in-page Ask AI assistant, and expose a hosted MCP server for coding agents.
4
4
  ---
5
5
 
6
6
  Blume has a few AI features: machine-readable docs for external tools (`llms.txt`, on by default), an in-page **Ask AI** assistant, and a hosted **MCP server** for coding agents. Ask AI and MCP are opt-in, and static docs stay fully static until you turn a feature on.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Customization
3
- description: Override components, add interactive islands, mount custom pages, install registry components, or eject.
3
+ description: Override components, add interactive islands, mount custom pages, install registry components, or eject entirely when you need full control.
4
4
  ---
5
5
 
6
6
  ## Component overrides
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Export
3
- description: Let readers download any page as a PDF or EPUB — client-side, so static builds stay static.
3
+ description: Let readers download any page as a PDF or EPUB — rendered client-side, so your static builds stay static and need no server infrastructure.
4
4
  ---
5
5
 
6
6
  Blume can add an **Export** action to the [page actions](/docs/content/navigation#page-actions) beneath the table of contents, letting readers save the page they're on as a **PDF** or an **EPUB**. It's off by default and entirely client-side — no server, and [static](/docs/deployment) builds stay static.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Configuration file
3
- description: Every option in blume.config.ts site metadata, content, and links to each feature guide.
3
+ description: Every option in blume.config.ts, from site metadata and content sources to the links that lead into each individual feature configuration guide.
4
4
  sidebar:
5
5
  label: blume.config.ts
6
6
  ---
@@ -188,6 +188,26 @@ content: {
188
188
 
189
189
  Static assets live in `public/` — a file at `public/logo.png` is served at `/logo.png`, so a reference like `![](/images/create.png)` resolves against `public/images/create.png`.
190
190
 
191
+ ## Frontmatter
192
+
193
+ Page frontmatter is strictly validated — an unknown key fails the build, so typos are caught early. To carry project-specific metadata (an owner, a review date), declare the extra keys under `frontmatter.extend`, each mapped to a schema you supply:
194
+
195
+ ```ts blume.config.ts lineNumbers
196
+ import { defineConfig } from "blume";
197
+ import { z } from "zod";
198
+
199
+ export default defineConfig({
200
+ frontmatter: {
201
+ extend: {
202
+ owner: z.string(),
203
+ reviewedAt: z.coerce.date().optional(),
204
+ },
205
+ },
206
+ });
207
+ ```
208
+
209
+ Any [Standard Schema](https://standardschema.dev) library works — Zod (whichever version your project installs), Valibot, ArkType. Keys outside the extension stay strictly validated, so typo-catching is unchanged. See [Custom keys](/docs/reference/frontmatter#custom-keys) for the validation semantics.
210
+
191
211
  ## GitHub
192
212
 
193
213
  Point Blume at your repository with `github`. It powers the header [repository link](/docs/content/navigation#repository-link) and the **Edit on GitHub** and **Give feedback** [page actions](/docs/content/navigation#page-actions):
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Search
3
- description: Client-side search out of the box, with optional hosted and semantic backends.
3
+ description: Client-side search that works out of the box with no API keys, plus optional hosted and semantic backends you can switch to as your docs grow.
4
4
  ---
5
5
 
6
6
  Blume ships local search with no hosted infrastructure and no API keys. It runs in the browser, works in both `blume dev` and `blume build`, and indexes only your real content — navigation chrome and excluded pages are skipped. When you outgrow it, you can switch to a hosted or semantic backend without changing how search looks or behaves — only the `search.provider` you configure changes.
@@ -13,6 +13,33 @@ Open search with <Badge variant="accent">⌘K</Badge> (or `Ctrl K`), or press `/
13
13
 
14
14
  Queries match page **titles**, **descriptions**, and **body text**, with title matches ranked highest and descriptions above body.
15
15
 
16
+ ## Popular pages
17
+
18
+ Before a reader types a query, the search dialog shows a **Popular** list. By default it is the first six sidebar pages — which on multi-tab sites often surfaces the wrong section. Pin the links you want instead:
19
+
20
+ ```ts blume.config.ts lineNumbers
21
+ search: {
22
+ popular: [
23
+ { href: "/guides/getting-started", icon: "rocket", label: "Getting started" },
24
+ { href: "/guides/install", icon: "download", label: "Install" },
25
+ { href: "/concepts/overview", label: "Overview" },
26
+ ],
27
+ },
28
+ ```
29
+
30
+ Each entry takes an `href` (internal route or external URL) and a `label`, plus an optional `icon` — a built-in icon name shown beside the label, defaulting to a file glyph. Omit `popular` or leave it empty to keep the sidebar fallback.
31
+
32
+ Unlike icons elsewhere in Blume, `icon` here must be a built-in name: these rows render in a client island, so an image path or inline SVG isn't supported and falls back to the file glyph.
33
+
34
+ Write `href` as if the site were mounted at the root — a `basePath` is applied for you, the same as `navigation.featured`. External URLs pass through untouched.
35
+
36
+ <Callout type="warning">
37
+ A curated list is a single set of links shared by every language. On a site
38
+ with `i18n` configured, the sidebar fallback follows the reader's locale, but
39
+ `popular` entries point wherever their `href` says — so pin locale-prefixed
40
+ routes only if you want every reader sent to that one language.
41
+ </Callout>
42
+
16
43
  ## What's indexed
17
44
 
18
45
  For every indexable page, Blume indexes its title, description, and body reduced to plain text — code blocks, images, and markup are stripped, so results stay relevant. The index is built from your source files, so it's identical in dev and production.
@@ -106,7 +106,26 @@ seo: {
106
106
  }
107
107
  ```
108
108
 
109
- 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:
109
+ ### Brand the generated card
110
+
111
+ Set a local SVG and color palette to match the generated card to your brand. The logo can live in `public/` or at the project root. Omit any palette value to keep its default.
112
+
113
+ ```ts blume.config.ts lineNumbers
114
+ seo: {
115
+ og: {
116
+ logo: "/logo/og.svg",
117
+ palette: {
118
+ accent: "#ff5410",
119
+ background: "#1d1d1d",
120
+ foreground: "#fff6f2",
121
+ muted: "#a6a19f",
122
+ border: "#323232",
123
+ },
124
+ },
125
+ }
126
+ ```
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:
110
129
 
111
130
  | Page route | Image URL |
112
131
  | ------------------- | -------------------------- |
@@ -125,9 +144,28 @@ seo:
125
144
  ```
126
145
 
127
146
  :::note
128
- OG rendering uses hex internally, so an `oklch` custom accent falls back to the default. Use a named accent (`blue`, `teal`, …) or a hex value for custom cards.
147
+ Every palette color accepts any CSS color — hex, `oklch(…)`, `rgb(…)`, and so on. The accent also accepts a named preset (`blue`, `teal`, …), matching [`theme.accent`](/docs/configuration/theming#accent). A color the renderer can't parse fails the build rather than silently shipping a default-colored card.
129
148
  :::
130
149
 
150
+ Emoji in a page title or site title render as [Twemoji](https://github.com/jdecked/twemoji) glyphs, fetched from a CDN while the card renders — so a build whose titles contain emoji needs network access. Each glyph is fetched once per build, however many pages use it.
151
+
152
+ ### Non-Latin titles
153
+
154
+ The card's built-in font covers only Latin glyphs, so a title in another script (Japanese, Chinese, Korean, Arabic, …) renders as tofu — empty boxes. List one or more [Google Fonts](https://fonts.google.com) families in `og.fonts` that cover your script, by name:
155
+
156
+ ```ts blume.config.ts lineNumbers
157
+ seo: {
158
+ og: {
159
+ fonts: [
160
+ "Noto Sans JP",
161
+ { name: "Inter", weight: [400, 700] },
162
+ ],
163
+ },
164
+ }
165
+ ```
166
+
167
+ Each entry is a family name, or an object pinning its `weight` (a number, a list, or a variable range like `"100..900"`) and `style` (`"normal"`, `"italic"`, or both). The families are fetched from Google Fonts at build — so a build that uses them needs network access — and the renderer only pulls the glyph subsets each title actually uses. Fallback is per-glyph, so adding a family only affects glyphs the built-in font can't draw; Latin text renders exactly as before.
168
+
131
169
  `seo.image` is frontmatter, so it only covers Markdown and MDX content. To give a custom [`.astro` page](/docs/advanced/custom-pages) its own social image — a marketing home or landing page, and the way to give the home page alone a bespoke share image — pass the `ogImage` prop to `PageLayout`.
132
170
 
133
171
  ## RSS feeds
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Theming
3
- description: Tune the look with config tokens, override any CSS variable in theme.css, or use Tailwind utilities.
3
+ description: Tune the look with a handful of config tokens, override any CSS variable in theme.css, or drop down to Tailwind utilities for custom components.
4
4
  ---
5
5
 
6
6
  Blume's theme is token-driven and works in light and dark mode out of the box. Reach for as little or as much as you need: a few config tokens for the common cases, a `theme.css` to override any design token, or Tailwind utilities for custom components.
@@ -69,6 +69,20 @@ Switch between equivalent content in place — language variants, OS-specific co
69
69
  </Tabs>
70
70
  ```
71
71
 
72
+ Add `inline` to render borderless — a tab strip on a full-width rule with the content flowing beneath as prose — instead of the bordered box. Add `param` to sync the active tab to a URL query param instead of the hash, which makes the selection shareable: a link ending in `?install=windows` opens on the Windows tab. Each group syncs to its own `param`, so you can use several independent, deep-linkable groups on one page.
73
+
74
+ <Tabs inline param="install">
75
+ <Tab title="macOS">Use Homebrew to install the toolchain.</Tab>
76
+ <Tab title="Windows">Use winget to install the toolchain.</Tab>
77
+ </Tabs>
78
+
79
+ ```astro lineNumbers
80
+ <Tabs inline param="install">
81
+ <Tab title="macOS">Use Homebrew to install the toolchain.</Tab>
82
+ <Tab title="Windows">Use winget to install the toolchain.</Tab>
83
+ </Tabs>
84
+ ```
85
+
72
86
  ## Badge
73
87
 
74
88
  A small inline label for status or metadata — version tags, “new” or “beta” markers, stability levels. The `variant` tunes the color to the meaning.
@@ -619,7 +633,6 @@ An Astro example renders live with no client JavaScript:
619
633
 
620
634
  <CodeBlock
621
635
  lang="ts"
622
- icons={false}
623
636
  code={`export const greet = (name: string): string =>
624
637
  \`Hello, \${name}!\`;`}
625
638
  />
@@ -629,7 +642,7 @@ An Astro example renders live with no client JavaScript:
629
642
  import CodeBlock from "blume/components/content/CodeBlock.astro";
630
643
  ---
631
644
 
632
- <CodeBlock lang="ts" code={source} icons={false} />
645
+ <CodeBlock lang="ts" code={source} />
633
646
  ```
634
647
 
635
648
  To highlight to an HTML string yourself (e.g. inside your own component), import the underlying helper from `blume/markdown`:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Pages
3
- description: How files in your content folder become pages, and how to organize them.
3
+ description: How the files in your content folder become pages, and how to organize and name them so routing and navigation are inferred automatically.
4
4
  ---
5
5
 
6
6
  Your docs are just a folder of Markdown and MDX files. Blume turns each file into a page — routing, navigation, and metadata are inferred from the file system, so there's no manifest to keep in sync.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Folder meta
3
- description: Configure a sidebar group its title, icon, order, and page order with a meta.ts file.
3
+ description: Configure a sidebar group with a meta.ts file beside its pages — set the group's title, icon, order, and the order of the pages nested inside.
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).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Navigation
3
- description: Build the sidebar from your files, then refine it with frontmatter, folder meta, or config.
3
+ description: Blume builds the sidebar from your files, then lets you refine it with frontmatter, folder meta, or config — breadcrumbs and outlines follow along.
4
4
  ---
5
5
 
6
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.
@@ -109,6 +109,16 @@ navigation: {
109
109
  }
110
110
  ```
111
111
 
112
+ An enabled [OpenAPI or AsyncAPI reference](/docs/advanced/api-reference) mounts at its route but doesn't add a tab on its own — point a tab at that route to surface it in the header (and, for the native renderer, to scope its operations sidebar), with whatever label you like:
113
+
114
+ ```ts blume.config.ts
115
+ navigation: {
116
+ tabs: [
117
+ { label: "API", path: "/reference" },
118
+ ],
119
+ }
120
+ ```
121
+
112
122
  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.
113
123
 
114
124
  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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Content sources
3
- description: Pull docs from local files, a remote repo, or any custom backend — and mix several sources into one site.
3
+ description: Pull docs from local files, a remote repository, or any custom backend — and mix several sources into one static-first site read at build time.
4
4
  ---
5
5
 
6
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.