blume 0.5.3 → 0.5.4

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 (113) hide show
  1. package/dist/cli/index.js +2985 -6883
  2. package/dist/cli/index.js.map +27 -58
  3. package/dist/types/core/config.d.ts +0 -8
  4. package/dist/types/core/data.d.ts +2 -2
  5. package/dist/types/core/schema.d.ts +58 -453
  6. package/dist/types/core/types.d.ts +1 -7
  7. package/docs/advanced/meta.ts +1 -8
  8. package/docs/advanced/skills.mdx +28 -0
  9. package/docs/configuration/ai.mdx +2 -0
  10. package/docs/configuration/index.mdx +13 -17
  11. package/docs/configuration/theming.mdx +2 -10
  12. package/docs/content/components.mdx +2 -53
  13. package/docs/content/i18n.mdx +0 -4
  14. package/docs/content/syntax.mdx +1 -1
  15. package/docs/index.mdx +0 -1
  16. package/docs/reference/cli.mdx +12 -13
  17. package/package.json +4 -6
  18. package/skills/blume/SKILL.md +71 -0
  19. package/skills/blume-update-docs/SKILL.md +52 -0
  20. package/skills/blume-update-docs/references/audit-checklist.md +46 -0
  21. package/src/astro/generate.ts +15 -12
  22. package/src/astro/integration.ts +0 -21
  23. package/src/astro/templates.ts +9 -16
  24. package/src/cli/commands/dev.ts +0 -6
  25. package/src/cli/commands/validate.ts +0 -2
  26. package/src/cli/index.ts +0 -2
  27. package/src/components/BlumePage.astro +0 -6
  28. package/src/components/Icon.astro +1 -12
  29. package/src/components/content/AccordionItem.astro +3 -6
  30. package/src/components/content/Badge.astro +1 -3
  31. package/src/components/content/Callout.astro +3 -9
  32. package/src/components/content/Card.astro +2 -3
  33. package/src/components/content/ColorItem.astro +2 -2
  34. package/src/components/content/Column.astro +1 -1
  35. package/src/components/content/Prompt.astro +1 -1
  36. package/src/components/content/Step.astro +3 -4
  37. package/src/components/content/Tab.astro +2 -3
  38. package/src/components/content/TypeTable.astro +5 -5
  39. package/src/components/content/Update.astro +1 -1
  40. package/src/components/layout/Header.astro +26 -3
  41. package/src/components/layout/Logo.astro +5 -1
  42. package/src/components/layout/NavSelector.astro +1 -1
  43. package/src/components/layout/NavTree.astro +14 -14
  44. package/src/components/layout/PageLayout.astro +42 -0
  45. package/src/components/layout/ReferenceLayout.astro +1 -0
  46. package/src/components/layout/RootLayout.astro +31 -2
  47. package/src/components/layout/Search.astro +5 -5
  48. package/src/components/layout/nav-utils.ts +9 -4
  49. package/src/components/openapi/ApiOverview.astro +4 -50
  50. package/src/components/openapi/ApiTagOperations.astro +42 -0
  51. package/src/core/builtin-tags.ts +1 -3
  52. package/src/core/config.ts +5 -28
  53. package/src/core/data.ts +2 -2
  54. package/src/core/graph.ts +1 -4
  55. package/src/core/links.ts +5 -19
  56. package/src/core/meta.ts +1 -1
  57. package/src/core/navigation.ts +0 -5
  58. package/src/core/project-graph.ts +0 -5
  59. package/src/core/schema.ts +13 -90
  60. package/src/core/sources/resolve.ts +0 -13
  61. package/src/core/sources/watch.ts +3 -2
  62. package/src/core/types.ts +1 -8
  63. package/src/openapi/parse.ts +197 -14
  64. package/src/openapi/render-mdx.ts +44 -10
  65. package/src/openapi/source.ts +19 -2
  66. package/src/theme/entry.ts +45 -17
  67. package/src/theme/icons.ts +18 -109
  68. package/src/theme/palette.ts +4 -37
  69. package/src/theme/twoslash.ts +6 -1
  70. package/dist/types/core/bridge.d.ts +0 -24
  71. package/dist/types/core/package-json.d.ts +0 -12
  72. package/dist/types/migrate/mintlify/assets.d.ts +0 -8
  73. package/dist/types/migrate/mintlify/config.d.ts +0 -16
  74. package/dist/types/migrate/mintlify/i18n.d.ts +0 -7
  75. package/dist/types/migrate/shared.d.ts +0 -153
  76. package/docs/advanced/bridge.mdx +0 -76
  77. package/docs/advanced/migrate.mdx +0 -124
  78. package/src/astro/static-assets.ts +0 -124
  79. package/src/cli/commands/migrate.ts +0 -39
  80. package/src/components/content/ApiField.astro +0 -75
  81. package/src/components/content/ParamField.astro +0 -39
  82. package/src/components/content/RequestField.astro +0 -23
  83. package/src/components/content/ResponseField.astro +0 -23
  84. package/src/components/content/Warning.astro +0 -9
  85. package/src/core/assets.ts +0 -31
  86. package/src/core/bridge.ts +0 -102
  87. package/src/core/sources/mintlify.ts +0 -190
  88. package/src/migrate/fumadocs/config.ts +0 -155
  89. package/src/migrate/fumadocs/content.ts +0 -376
  90. package/src/migrate/fumadocs/frontmatter.ts +0 -18
  91. package/src/migrate/fumadocs/groups.ts +0 -237
  92. package/src/migrate/fumadocs/index.ts +0 -355
  93. package/src/migrate/fumadocs/meta.ts +0 -244
  94. package/src/migrate/migrate.ts +0 -53
  95. package/src/migrate/mintlify/assets.ts +0 -46
  96. package/src/migrate/mintlify/config.ts +0 -954
  97. package/src/migrate/mintlify/content.ts +0 -120
  98. package/src/migrate/mintlify/frontmatter.ts +0 -126
  99. package/src/migrate/mintlify/i18n.ts +0 -51
  100. package/src/migrate/mintlify/icons.ts +0 -128
  101. package/src/migrate/mintlify/index.ts +0 -459
  102. package/src/migrate/mintlify/snippets.ts +0 -315
  103. package/src/migrate/mintlify/transform.ts +0 -82
  104. package/src/migrate/nextra/content.ts +0 -46
  105. package/src/migrate/nextra/frontmatter.ts +0 -40
  106. package/src/migrate/nextra/index.ts +0 -389
  107. package/src/migrate/nextra/meta.ts +0 -266
  108. package/src/migrate/shared.ts +0 -801
  109. package/src/migrate/starlight/config.ts +0 -455
  110. package/src/migrate/starlight/content.ts +0 -75
  111. package/src/migrate/starlight/frontmatter.ts +0 -111
  112. package/src/migrate/starlight/i18n.ts +0 -54
  113. package/src/migrate/starlight/index.ts +0 -131
@@ -69,6 +69,8 @@ interface LoadedSpec {
69
69
  slug: string;
70
70
  spec: ApiSpecData;
71
71
  entries: SourceEntry[];
72
+ /** Non-fatal notes from the load (e.g. an offline cache fallback). */
73
+ diagnostics: Diagnostic[];
72
74
  }
73
75
 
74
76
  export const openApiSource = (
@@ -81,7 +83,11 @@ export const openApiSource = (
81
83
  reference: ReferenceSource
82
84
  ): Promise<LoadedSpec | Diagnostic> => {
83
85
  try {
84
- const { document } = await parseSpec(reference.spec, ctx.projectRoot);
86
+ const { document, warnings } = await parseSpec(
87
+ reference.spec,
88
+ ctx.projectRoot,
89
+ { cacheDir: ctx.cacheDir, refresh: ctx.refresh }
90
+ );
85
91
  const { operations, tags } = extractOperations(document, reference.route);
86
92
  const info = document.info ?? { title: reference.label, version: "" };
87
93
  const spec: ApiSpecData = {
@@ -100,6 +106,11 @@ export const openApiSource = (
100
106
  version: info.version ?? "",
101
107
  };
102
108
  return {
109
+ diagnostics: warnings.map((message) => ({
110
+ code: "BLUME_OPENAPI_STALE",
111
+ message,
112
+ severity: "warning" as const,
113
+ })),
103
114
  entries: specEntries(spec, operations),
104
115
  slug: reference.slug,
105
116
  spec,
@@ -108,7 +119,12 @@ export const openApiSource = (
108
119
  return {
109
120
  code: "BLUME_OPENAPI_UNAVAILABLE",
110
121
  message: `Could not load OpenAPI spec "${reference.spec}" for ${reference.route} (${(error as Error).message}); its reference pages were skipped.`,
111
- severity: "warning",
122
+ // A configured-but-unloadable spec ships a dead nav tab (a 404 route),
123
+ // so fail loudly in build (blocks under --strict) while staying a warning
124
+ // in dev so offline work still runs.
125
+ severity: ctx.mode === "build" ? "error" : "warning",
126
+ suggestion:
127
+ "Check the spec URL/path is reachable from the build environment; behind a proxy, set HTTP(S)_PROXY.",
112
128
  };
113
129
  }
114
130
  };
@@ -125,6 +141,7 @@ export const openApiSource = (
125
141
  }
126
142
  data[result.slug] = result.spec;
127
143
  entries.push(...result.entries);
144
+ diagnostics.push(...result.diagnostics);
128
145
  }
129
146
  parsed = data;
130
147
  return { diagnostics, entries };
@@ -32,9 +32,6 @@ ${options.sources.map((source) => `@source "${source}";`).join("\n")}
32
32
 
33
33
  :root {
34
34
  --blume-background: oklch(1 0 0);
35
- --blume-background-decoration: none;
36
- --blume-background-decoration-repeat: no-repeat;
37
- --blume-background-decoration-size: auto;
38
35
  --blume-background-image: none;
39
36
  --blume-background-image-repeat: no-repeat;
40
37
  --blume-background-image-size: cover;
@@ -90,9 +87,6 @@ ${options.sources.map((source) => `@source "${source}";`).join("\n")}
90
87
 
91
88
  :root[data-theme="dark"] {
92
89
  --blume-background: oklch(0.085 0 0);
93
- --blume-background-decoration: none;
94
- --blume-background-decoration-repeat: no-repeat;
95
- --blume-background-decoration-size: auto;
96
90
  --blume-background-image: none;
97
91
  --blume-background-image-repeat: no-repeat;
98
92
  --blume-background-image-size: cover;
@@ -134,9 +128,22 @@ ${options.sources.map((source) => `@source "${source}";`).join("\n")}
134
128
  }
135
129
 
136
130
  @layer base {
131
+ /* Nothing refuses to shrink below its intrinsic content width. This global
132
+ min-width reset defuses the classic flex/grid overflow — a long or
133
+ truncating child forcing its container (and the page) past the viewport
134
+ edge — so components don't need per-element min-w-0 overrides. */
135
+ * {
136
+ min-width: 0;
137
+ }
138
+ /* Interactive controls get a pointer cursor unless disabled. */
139
+ button:not(:disabled),
140
+ [role="button"]:not(:disabled) {
141
+ cursor: pointer;
142
+ }
137
143
  html {
138
144
  scroll-behavior: smooth;
139
145
  scroll-padding-top: 4.5rem;
146
+ text-rendering: optimizeLegibility;
140
147
  }
141
148
  /* Headings use the display font (defaults to the body font when unset). */
142
149
  h1,
@@ -153,16 +160,11 @@ ${options.sources.map((source) => `@source "${source}";`).join("\n")}
153
160
  border-radius: 2px;
154
161
  }
155
162
  body {
156
- background-attachment: fixed, fixed;
157
- background-image:
158
- var(--blume-background-image), var(--blume-background-decoration);
159
- background-position: center top, top center;
160
- background-repeat:
161
- var(--blume-background-image-repeat),
162
- var(--blume-background-decoration-repeat);
163
- background-size:
164
- var(--blume-background-image-size),
165
- var(--blume-background-decoration-size);
163
+ background-attachment: fixed;
164
+ background-image: var(--blume-background-image);
165
+ background-position: center top;
166
+ background-repeat: var(--blume-background-image-repeat);
167
+ background-size: var(--blume-background-image-size);
166
168
  }
167
169
  @media (prefers-reduced-motion: reduce) {
168
170
  html {
@@ -298,7 +300,7 @@ ${options.sources.map((source) => `@source "${source}";`).join("\n")}
298
300
  line-height: 1.55;
299
301
  margin: 1.5rem 0;
300
302
  overflow-x: auto;
301
- padding: 1rem 1.25rem;
303
+ padding: 1rem 0;
302
304
  position: relative;
303
305
  }
304
306
 
@@ -395,6 +397,32 @@ blume-diff {
395
397
  padding: 0;
396
398
  }
397
399
 
400
+ /* Long lines scroll inside the code element, not the pre: the pre stays static
401
+ so its absolute header bar (::before) and copy button don't drift with the
402
+ scroll. The pre's horizontal padding lives here so content still scrolls
403
+ edge-to-edge past it. Two contexts opt out: twoslash blocks (popups must
404
+ escape any scroll container — see theme/twoslash.ts), and the API request
405
+ panel, which owns its code layout and keeps the copy control in the panel
406
+ header. Every other component that hosts a code block — Tabs, CodeGroup,
407
+ Steps, Callout, Card, Accordion — is real prose content and keeps the inset,
408
+ even though its chrome wrapper is not-prose. */
409
+ .prose :where(pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code) {
410
+ display: block;
411
+ overflow-x: auto;
412
+ padding: 0 1.25rem;
413
+ /* The scroller is only as tall as the code, so an overlay scrollbar would
414
+ draw on top of the last line; hide it (wheel/trackpad/keyboard scrolling
415
+ still works). */
416
+ scrollbar-width: none;
417
+ }
418
+
419
+ .prose
420
+ :where(
421
+ pre:not(.twoslash, .twoslash pre, blume-panel-tabs *) > code
422
+ )::-webkit-scrollbar {
423
+ display: none;
424
+ }
425
+
398
426
  /* Word wrap (markdown.code.wrap): long lines wrap instead of scrolling. The
399
427
  attribute is set on <body> from config; default code keeps \`white-space: pre\`. */
400
428
  [data-blume-code-wrap] pre,
@@ -1,16 +1,11 @@
1
1
  /**
2
- * Icon resolution backed by the open Iconify icon sets Font Awesome (free),
3
- * Lucide, and Tabler — the three libraries Mintlify exposes. Resolution runs at
2
+ * Icon resolution backed by the open Iconify Lucide set. Resolution runs at
4
3
  * **build time, server-side**, and returns ready-to-inline SVG, so icons stay
5
- * zero-JS and fully self-contained (no runtime CDN fetch, unlike Mintlify).
4
+ * zero-JS and fully self-contained (no runtime CDN fetch).
6
5
  *
7
6
  * Because the Iconify set data is large, this module must only be imported from
8
7
  * server contexts (`.astro` frontmatter, the CLI). Client scripts use the tiny
9
8
  * hand-inlined set in `./chrome-icons.ts` instead.
10
- *
11
- * Coverage vs Mintlify: full parity for every Font Awesome *free* name, Lucide,
12
- * and Tabler. Font Awesome Pro styles (`light`/`thin`/`duotone`/`sharp-solid`)
13
- * aren't in the open data, so they fall back to `solid`.
14
9
  */
15
10
  import { createRequire } from "node:module";
16
11
 
@@ -25,61 +20,22 @@ const requireJson = createRequire(import.meta.url);
25
20
  const loadSet = (pkg: string): IconifyJSON => requireJson(pkg) as IconifyJSON;
26
21
 
27
22
  const SETS: Record<string, IconifyJSON> = {
28
- "fa6-brands": loadSet("@iconify-json/fa6-brands/icons.json"),
29
- "fa6-regular": loadSet("@iconify-json/fa6-regular/icons.json"),
30
- "fa6-solid": loadSet("@iconify-json/fa6-solid/icons.json"),
31
23
  lucide: loadSet("@iconify-json/lucide/icons.json"),
32
- tabler: loadSet("@iconify-json/tabler/icons.json"),
33
24
  };
34
25
 
35
- /** Blume's default library when a project sets no `icons.library`. */
26
+ /** Blume's only icon library. */
36
27
  const DEFAULT_SET = "lucide";
37
28
 
38
- /** `icons.library` config value Iconify set. */
39
- const LIBRARY_SETS: Record<string, string> = {
40
- fa: "fa6-solid",
41
- "font-awesome": "fa6-solid",
42
- fontawesome: "fa6-solid",
43
- lucide: "lucide",
44
- tabler: "tabler",
45
- };
46
-
47
- /**
48
- * Mintlify Font Awesome `iconType` → Iconify set. The Pro-only styles
49
- * (`light`/`thin`/`duotone`/`sharp-solid`) aren't in the free data, so they map
50
- * to `solid` rather than render nothing.
51
- */
52
- const ICON_TYPE_SETS: Record<string, string> = {
53
- brands: "fa6-brands",
54
- duotone: "fa6-solid",
55
- light: "fa6-solid",
56
- regular: "fa6-regular",
57
- "sharp-solid": "fa6-solid",
58
- solid: "fa6-solid",
59
- thin: "fa6-solid",
60
- };
61
-
62
- /** Explicit `prefix:name` prefixes (Iconify prefixes + common FA aliases). */
29
+ /** Explicit `prefix:name` prefixes. Lucide is the only bundled set. */
63
30
  const PREFIX_SETS: Record<string, string> = {
64
- ...LIBRARY_SETS,
65
- fa: "fa6-solid",
66
- "fa-brands": "fa6-brands",
67
- "fa-regular": "fa6-regular",
68
- "fa-solid": "fa6-solid",
69
- "fa6-brands": "fa6-brands",
70
- "fa6-regular": "fa6-regular",
71
- "fa6-solid": "fa6-solid",
72
- fab: "fa6-brands",
73
- far: "fa6-regular",
74
- fas: "fa6-solid",
75
- ti: "tabler",
31
+ lucide: "lucide",
76
32
  };
77
33
 
78
34
  /**
79
- * Own-property map lookup. Icon names and library/iconType hints come from
80
- * content and config, so a value like `constructor:x` would otherwise resolve
81
- * an Object.prototype member (a function) and crash resolution deep in the
82
- * build with no pointer to the offending page.
35
+ * Own-property map lookup. Icon names come from content and config, so a value
36
+ * like `constructor:x` would otherwise resolve an Object.prototype member (a
37
+ * function) and crash resolution deep in the build with no pointer to the
38
+ * offending page.
83
39
  */
84
40
  const ownEntry = <T>(map: Record<string, T>, key: string): T | undefined =>
85
41
  Object.hasOwn(map, key) ? map[key] : undefined;
@@ -89,40 +45,16 @@ export interface ResolvedIcon {
89
45
  body: string;
90
46
  /** The resolved icon name. */
91
47
  name: string;
92
- /** The icon's viewBox, which varies per library (24×24, 512-height, …). */
48
+ /** The icon's viewBox (Lucide is 24×24). */
93
49
  viewBox: string;
94
50
  }
95
51
 
96
- export interface ResolveIconOptions {
97
- /** Font Awesome style selector (`solid`, `regular`, `brands`, …). */
98
- iconType?: string;
99
- /** Default library for a bare name (`fontawesome` | `lucide` | `tabler`). */
100
- library?: string;
101
- }
102
-
103
52
  const normalize = (name: string): string =>
104
53
  name
105
54
  .trim()
106
55
  .toLowerCase()
107
56
  .replaceAll(/[\s_]+/gu, "-");
108
57
 
109
- /** Which set a bare name resolves against, given library/iconType hints. */
110
- const setFor = (options: ResolveIconOptions): string => {
111
- if (options.iconType) {
112
- const set = ownEntry(ICON_TYPE_SETS, normalize(options.iconType));
113
- if (set) {
114
- return set;
115
- }
116
- }
117
- if (options.library) {
118
- const set = ownEntry(LIBRARY_SETS, normalize(options.library));
119
- if (set) {
120
- return set;
121
- }
122
- }
123
- return DEFAULT_SET;
124
- };
125
-
126
58
  const fromSet = (setName: string, iconName: string): ResolvedIcon | null => {
127
59
  const set = ownEntry(SETS, setName);
128
60
  const data = set && getIconData(set, iconName);
@@ -133,46 +65,23 @@ const fromSet = (setName: string, iconName: string): ResolvedIcon | null => {
133
65
  return { body, name: iconName, viewBox: attributes.viewBox };
134
66
  };
135
67
 
136
- // Font Awesome splits brands into their own set, so a bare `github` under a
137
- // solid/regular default still resolves.
138
- const fromFaSet = (setName: string, name: string): ResolvedIcon | null =>
139
- fromSet(setName, name) ?? fromSet("fa6-brands", name);
140
-
141
- const resolveInSet = (setName: string, name: string): ResolvedIcon | null =>
142
- setName.startsWith("fa6-")
143
- ? fromFaSet(setName, name)
144
- : fromSet(setName, name);
145
-
146
68
  /**
147
- * Resolve an icon name to inline SVG. Honors an explicit `prefix:name`
148
- * (`lucide:rocket`, `fa6-brands:github`), then `iconType`, then the configured
149
- * `library`, falling back to Lucide.
69
+ * Resolve an icon name to inline SVG. Honors an explicit `lucide:name` prefix;
70
+ * a bare name resolves against Lucide.
150
71
  */
151
- export const resolveIcon = (
152
- name: string,
153
- options: ResolveIconOptions = {}
154
- ): ResolvedIcon | null => {
72
+ export const resolveIcon = (name: string): ResolvedIcon | null => {
155
73
  const normalized = normalize(name);
156
74
  const colon = normalized.indexOf(":");
157
75
  if (colon > 0) {
158
76
  const setName = ownEntry(PREFIX_SETS, normalized.slice(0, colon));
159
- if (setName) {
160
- return resolveInSet(setName, normalized.slice(colon + 1));
161
- }
77
+ return setName ? fromSet(setName, normalized.slice(colon + 1)) : null;
162
78
  }
163
- return resolveInSet(setFor(options), normalized);
79
+ return fromSet(DEFAULT_SET, normalized);
164
80
  };
165
81
 
166
- /**
167
- * Whether a name resolves to a known icon. Without a library hint this checks
168
- * every bundled set, so a valid Font Awesome/Tabler name isn't flagged as a typo
169
- * just because the project's default library is Lucide.
170
- */
171
- export const hasIcon = (
172
- name: string,
173
- options: ResolveIconOptions = {}
174
- ): boolean => {
175
- if (resolveIcon(name, options)) {
82
+ /** Whether a name resolves to a known Lucide icon. */
83
+ export const hasIcon = (name: string): boolean => {
84
+ if (resolveIcon(name)) {
176
85
  return true;
177
86
  }
178
87
  const normalized = normalize(name);
@@ -52,36 +52,11 @@ const backgroundImageCss = (image: string): string =>
52
52
  const cssToken = (name: string, value?: string | null): string[] =>
53
53
  value ? [` ${name}: ${value};`] : [];
54
54
 
55
- const backgroundDecorationCss = (
56
- decoration: ResolvedConfig["theme"]["backgroundDecoration"]
57
- ): string => {
58
- if (decoration === "gradient") {
59
- return ` --blume-background-decoration: radial-gradient(circle at top left, color-mix(in oklab, var(--blume-accent) 18%, transparent), transparent 28rem), radial-gradient(circle at top right, color-mix(in oklab, var(--blume-action) 12%, transparent), transparent 24rem);
60
- --blume-background-decoration-repeat: no-repeat, no-repeat;
61
- --blume-background-decoration-size: auto, auto;
62
- `;
63
- }
64
- if (decoration === "grid") {
65
- return ` --blume-background-decoration: linear-gradient(var(--blume-border) 1px, transparent 1px), linear-gradient(90deg, var(--blume-border) 1px, transparent 1px);
66
- --blume-background-decoration-repeat: repeat, repeat;
67
- --blume-background-decoration-size: 2rem 2rem, 2rem 2rem;
68
- `;
69
- }
70
- if (decoration === "windows") {
71
- return ` --blume-background-decoration: linear-gradient(90deg, color-mix(in oklab, var(--blume-border) 70%, transparent) 1px, transparent 1px), linear-gradient(var(--blume-border) 1px, transparent 1px);
72
- --blume-background-decoration-repeat: repeat, repeat;
73
- --blume-background-decoration-size: 7rem 4.5rem, 7rem 4.5rem;
74
- `;
75
- }
76
- return "";
77
- };
78
-
79
55
  const themeRootCss = (
80
56
  theme: ResolvedConfig["theme"],
81
57
  options: {
82
58
  accent: string;
83
59
  action: string | null;
84
- backgroundDecoration: string;
85
60
  radius: string;
86
61
  }
87
62
  ): string =>
@@ -97,7 +72,6 @@ const themeRootCss = (
97
72
  "--blume-background-image",
98
73
  theme.backgroundImage ? backgroundImageCss(theme.backgroundImage) : null
99
74
  ),
100
- options.backgroundDecoration.trimEnd(),
101
75
  ` --blume-radius: ${options.radius};`,
102
76
  ]
103
77
  .filter(Boolean)
@@ -108,13 +82,12 @@ const themeDarkCss = (
108
82
  options: {
109
83
  accent: string;
110
84
  action: string | null;
111
- backgroundDecoration: string;
112
85
  }
113
86
  ): string => {
114
- // Mode-shared tokens (accent, action, decoration) must be re-declared here:
115
- // the base stylesheet's own `:root[data-theme="dark"]` block outranks the
116
- // `:root` config tokens on specificity, so without this block dark mode
117
- // would silently keep its neutral defaults and ignore the config.
87
+ // Mode-shared tokens (accent, action) must be re-declared here: the base
88
+ // stylesheet's own `:root[data-theme="dark"]` block outranks the `:root`
89
+ // config tokens on specificity, so without this block dark mode would
90
+ // silently keep its neutral defaults and ignore the config.
118
91
  const tokens = [
119
92
  ` --blume-accent: ${options.accent};`,
120
93
  " --blume-accent-foreground: oklch(1 0 0);",
@@ -130,7 +103,6 @@ const themeDarkCss = (
130
103
  ? backgroundImageCss(theme.backgroundImageDark)
131
104
  : null
132
105
  ),
133
- options.backgroundDecoration.trimEnd(),
134
106
  ].filter(Boolean);
135
107
  return `:root[data-theme="dark"] {
136
108
  ${tokens.join("\n")}
@@ -159,20 +131,15 @@ export const buildThemeCss = (theme: ResolvedConfig["theme"]): string => {
159
131
  const accent = presetOrColor(theme.accent);
160
132
  const accentDark = theme.accentDark ? presetOrColor(theme.accentDark) : null;
161
133
  const action = theme.action ? presetOrColor(theme.action) : null;
162
- const backgroundDecoration = backgroundDecorationCss(
163
- theme.backgroundDecoration
164
- );
165
134
  const radius = RADII[theme.radius];
166
135
  const root = themeRootCss(theme, {
167
136
  accent,
168
137
  action,
169
- backgroundDecoration,
170
138
  radius,
171
139
  });
172
140
  const dark = themeDarkCss(theme, {
173
141
  accent: accentDark ?? accent,
174
142
  action,
175
- backgroundDecoration,
176
143
  });
177
144
 
178
145
  return `/* Generated by Blume from theme config. */
@@ -33,9 +33,14 @@ const OVERRIDES = `
33
33
 
34
34
  /* Popups are absolutely positioned and must escape the pre's scroll container.
35
35
  The base prose pre rule (from the typography layer) wins the cascade here
36
- despite lower specificity, so !important is needed to force visibility. */
36
+ despite lower specificity, so !important is needed to force visibility.
37
+ Regular code blocks scroll their inner code element and carry the horizontal
38
+ padding there; twoslash code opts out of that scroller (popups again), so the
39
+ padding is restored on the pre. */
37
40
  .prose pre.twoslash {
38
41
  overflow: visible !important;
42
+ padding-left: 1.25rem;
43
+ padding-right: 1.25rem;
39
44
  }
40
45
 
41
46
  /* The rich renderer renders each popup's type signature as a nested Shiki
@@ -1,24 +0,0 @@
1
- import type { BlumeConfig } from "./schema.ts";
2
- /** A detected docs-tool config that Blume can serve without a migration. */
3
- export interface BridgeDetection {
4
- /** Which foreign docs tool was detected. */
5
- tool: "mintlify";
6
- /** Absolute path of the detected config file (`docs.json`/`mint.json`). */
7
- configFile: string;
8
- /** A Blume config synthesized from the foreign config, ready to validate. */
9
- raw: BlumeConfig;
10
- }
11
- /**
12
- * Detect a Mintlify project at `root` and synthesize an equivalent Blume config.
13
- *
14
- * "Bridge mode" lets a team swap `mintlify dev` for `blume dev` with no file
15
- * changes: `docs.json` is translated to Blume config (`loadMintlifyConfig`) and
16
- * its content block is rewired to a single `mintlify` content source, which
17
- * transforms each MDX page to Blume idiom at scan time. The original
18
- * `content.root`/`exclude` and `variables` move onto the source (Blume has no
19
- * runtime variable substitution, so globals are inlined into content there).
20
- *
21
- * Returns `null` when no Mintlify config is present. Only called when no
22
- * `blume.config.*` exists, so an explicit Blume config always takes precedence.
23
- */
24
- export declare const detectMintlifyBridge: (root: string) => Promise<BridgeDetection | null>;
@@ -1,12 +0,0 @@
1
- /**
2
- * Derive a valid npm package name from a directory name, falling back to
3
- * `docs` when nothing usable remains.
4
- */
5
- export declare const toPackageName: (raw: string) => string;
6
- /**
7
- * A minimal, runnable `package.json` body for a Blume project: the `blume`
8
- * dependency pinned to the installed version plus `dev`/`build`/`doctor`
9
- * scripts, so `npm install && npm run dev` works immediately. Shared by
10
- * `blume init` and the migrators, which scaffold one when a project has none.
11
- */
12
- export declare const blumePackageJson: (name: string) => string;
@@ -1,8 +0,0 @@
1
- import type { BlumeConfig } from "../../core/schema.ts";
2
- /**
3
- * Top-level path segments referenced as static assets by a Mintlify config
4
- * (the conventional `/images`, plus logo/favicon/background paths). These are
5
- * the root-served folders Mintlify exposes at the site root; Blume serves them
6
- * via `content.assets` (bridge) or relocates them under `public/` (migrator).
7
- */
8
- export declare const assetSegments: (config: BlumeConfig) => string[];
@@ -1,16 +0,0 @@
1
- import type { BlumeConfig } from "../../core/schema.ts";
2
- type JsonObject = Record<string, unknown>;
3
- export interface MintlifyRedirectPartition {
4
- /** Static redirects Blume can honor, translated to Blume's `from`/`to` shape. */
5
- kept: NonNullable<BlumeConfig["redirects"]>;
6
- /** Source paths of dynamic redirects dropped because Blume can't model them. */
7
- dropped: string[];
8
- }
9
- /**
10
- * Split a spec's redirects into the static ones Blume emits and the dynamic
11
- * (wildcard/param) ones it drops. Keeping a dynamic redirect crashes the Astro
12
- * build, so the migrator surfaces the dropped sources as a warning instead.
13
- */
14
- export declare const partitionMintlifyRedirects: (spec: JsonObject) => MintlifyRedirectPartition;
15
- export declare const loadMintlifyConfig: (root: string, file: string) => Promise<BlumeConfig>;
16
- export {};
@@ -1,7 +0,0 @@
1
- import type { BlumeConfig } from "../../core/schema.ts";
2
- /**
3
- * Map a Mintlify `navigation.languages[]` array to a Blume `i18n` config. The
4
- * entry marked `default: true` becomes `defaultLocale`; translated content
5
- * already lives in ISO-code directories, which match Blume's `dir` parser.
6
- */
7
- export declare const mintlifyI18n: (spec: Record<string, unknown>) => BlumeConfig["i18n"] | null;