blume 0.1.3 → 0.1.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.
@@ -94,6 +94,19 @@ declare const uiStringsObject: z.ZodObject<{
94
94
  label?: string | undefined;
95
95
  untranslated?: string | undefined;
96
96
  }>>;
97
+ notFound: z.ZodDefault<z.ZodObject<{
98
+ description: z.ZodDefault<z.ZodString>;
99
+ home: z.ZodDefault<z.ZodString>;
100
+ title: z.ZodDefault<z.ZodString>;
101
+ }, "strip", z.ZodTypeAny, {
102
+ title: string;
103
+ description: string;
104
+ home: string;
105
+ }, {
106
+ title?: string | undefined;
107
+ description?: string | undefined;
108
+ home?: string | undefined;
109
+ }>>;
97
110
  page: z.ZodDefault<z.ZodObject<{
98
111
  lastUpdated: z.ZodDefault<z.ZodString>;
99
112
  next: z.ZodDefault<z.ZodString>;
@@ -188,6 +201,11 @@ declare const uiStringsObject: z.ZodObject<{
188
201
  toc: {
189
202
  title: string;
190
203
  };
204
+ notFound: {
205
+ title: string;
206
+ description: string;
207
+ home: string;
208
+ };
191
209
  }, {
192
210
  actions?: {
193
211
  addToCursor?: string | undefined;
@@ -237,6 +255,11 @@ declare const uiStringsObject: z.ZodObject<{
237
255
  toc?: {
238
256
  title?: string | undefined;
239
257
  } | undefined;
258
+ notFound?: {
259
+ title?: string | undefined;
260
+ description?: string | undefined;
261
+ home?: string | undefined;
262
+ } | undefined;
240
263
  }>;
241
264
  export declare const uiStringsSchema: z.ZodDefault<z.ZodObject<{
242
265
  actions: z.ZodDefault<z.ZodObject<{
@@ -324,6 +347,19 @@ export declare const uiStringsSchema: z.ZodDefault<z.ZodObject<{
324
347
  label?: string | undefined;
325
348
  untranslated?: string | undefined;
326
349
  }>>;
350
+ notFound: z.ZodDefault<z.ZodObject<{
351
+ description: z.ZodDefault<z.ZodString>;
352
+ home: z.ZodDefault<z.ZodString>;
353
+ title: z.ZodDefault<z.ZodString>;
354
+ }, "strip", z.ZodTypeAny, {
355
+ title: string;
356
+ description: string;
357
+ home: string;
358
+ }, {
359
+ title?: string | undefined;
360
+ description?: string | undefined;
361
+ home?: string | undefined;
362
+ }>>;
327
363
  page: z.ZodDefault<z.ZodObject<{
328
364
  lastUpdated: z.ZodDefault<z.ZodString>;
329
365
  next: z.ZodDefault<z.ZodString>;
@@ -418,6 +454,11 @@ export declare const uiStringsSchema: z.ZodDefault<z.ZodObject<{
418
454
  toc: {
419
455
  title: string;
420
456
  };
457
+ notFound: {
458
+ title: string;
459
+ description: string;
460
+ home: string;
461
+ };
421
462
  }, {
422
463
  actions?: {
423
464
  addToCursor?: string | undefined;
@@ -467,6 +508,11 @@ export declare const uiStringsSchema: z.ZodDefault<z.ZodObject<{
467
508
  toc?: {
468
509
  title?: string | undefined;
469
510
  } | undefined;
511
+ notFound?: {
512
+ title?: string | undefined;
513
+ description?: string | undefined;
514
+ home?: string | undefined;
515
+ } | undefined;
470
516
  }>>;
471
517
  /** A fully-resolved dictionary; every key present. */
472
518
  export type UIStrings = z.infer<typeof uiStringsObject>;
@@ -57,13 +57,13 @@ declare const pageMetaBaseSchema: z.ZodObject<{
57
57
  }, "strict", z.ZodTypeAny, {
58
58
  noindex: boolean;
59
59
  title?: string | undefined;
60
- canonical?: string | undefined;
61
60
  description?: string | undefined;
61
+ canonical?: string | undefined;
62
62
  image?: string | undefined;
63
63
  }, {
64
64
  title?: string | undefined;
65
- canonical?: string | undefined;
66
65
  description?: string | undefined;
66
+ canonical?: string | undefined;
67
67
  image?: string | undefined;
68
68
  noindex?: boolean | undefined;
69
69
  }>>;
@@ -106,8 +106,8 @@ declare const pageMetaBaseSchema: z.ZodObject<{
106
106
  seo: {
107
107
  noindex: boolean;
108
108
  title?: string | undefined;
109
- canonical?: string | undefined;
110
109
  description?: string | undefined;
110
+ canonical?: string | undefined;
111
111
  image?: string | undefined;
112
112
  };
113
113
  sidebar: {
@@ -119,8 +119,8 @@ declare const pageMetaBaseSchema: z.ZodObject<{
119
119
  };
120
120
  date?: string | undefined;
121
121
  title?: string | undefined;
122
- icon?: string | undefined;
123
122
  description?: string | undefined;
123
+ icon?: string | undefined;
124
124
  changelog?: {
125
125
  date?: string | undefined;
126
126
  category?: string | undefined;
@@ -146,9 +146,9 @@ declare const pageMetaBaseSchema: z.ZodObject<{
146
146
  exclude?: boolean | undefined;
147
147
  tags?: string[] | undefined;
148
148
  } | undefined;
149
+ description?: string | undefined;
149
150
  hidden?: boolean | undefined;
150
151
  icon?: string | undefined;
151
- description?: string | undefined;
152
152
  noindex?: boolean | undefined;
153
153
  changelog?: {
154
154
  date?: string | Date | undefined;
@@ -168,8 +168,8 @@ declare const pageMetaBaseSchema: z.ZodObject<{
168
168
  rss?: boolean | undefined;
169
169
  seo?: {
170
170
  title?: string | undefined;
171
- canonical?: string | undefined;
172
171
  description?: string | undefined;
172
+ canonical?: string | undefined;
173
173
  image?: string | undefined;
174
174
  noindex?: boolean | undefined;
175
175
  } | undefined;
@@ -238,13 +238,13 @@ export declare const pageMetaSchema: z.ZodObject<{
238
238
  }, "strict", z.ZodTypeAny, {
239
239
  noindex: boolean;
240
240
  title?: string | undefined;
241
- canonical?: string | undefined;
242
241
  description?: string | undefined;
242
+ canonical?: string | undefined;
243
243
  image?: string | undefined;
244
244
  }, {
245
245
  title?: string | undefined;
246
- canonical?: string | undefined;
247
246
  description?: string | undefined;
247
+ canonical?: string | undefined;
248
248
  image?: string | undefined;
249
249
  noindex?: boolean | undefined;
250
250
  }>>;
@@ -287,8 +287,8 @@ export declare const pageMetaSchema: z.ZodObject<{
287
287
  seo: {
288
288
  noindex: boolean;
289
289
  title?: string | undefined;
290
- canonical?: string | undefined;
291
290
  description?: string | undefined;
291
+ canonical?: string | undefined;
292
292
  image?: string | undefined;
293
293
  };
294
294
  sidebar: {
@@ -300,8 +300,8 @@ export declare const pageMetaSchema: z.ZodObject<{
300
300
  };
301
301
  date?: string | undefined;
302
302
  title?: string | undefined;
303
- icon?: string | undefined;
304
303
  description?: string | undefined;
304
+ icon?: string | undefined;
305
305
  changelog?: {
306
306
  date?: string | undefined;
307
307
  category?: string | undefined;
@@ -327,9 +327,9 @@ export declare const pageMetaSchema: z.ZodObject<{
327
327
  exclude?: boolean | undefined;
328
328
  tags?: string[] | undefined;
329
329
  } | undefined;
330
+ description?: string | undefined;
330
331
  hidden?: boolean | undefined;
331
332
  icon?: string | undefined;
332
- description?: string | undefined;
333
333
  noindex?: boolean | undefined;
334
334
  changelog?: {
335
335
  date?: string | Date | undefined;
@@ -349,8 +349,8 @@ export declare const pageMetaSchema: z.ZodObject<{
349
349
  rss?: boolean | undefined;
350
350
  seo?: {
351
351
  title?: string | undefined;
352
- canonical?: string | undefined;
353
352
  description?: string | undefined;
353
+ canonical?: string | undefined;
354
354
  image?: string | undefined;
355
355
  noindex?: boolean | undefined;
356
356
  } | undefined;
@@ -551,14 +551,14 @@ declare const contentSourceSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
551
551
  }, "strict", z.ZodTypeAny, {
552
552
  status?: string | undefined;
553
553
  title?: string | undefined;
554
- order?: string | undefined;
555
554
  description?: string | undefined;
555
+ order?: string | undefined;
556
556
  slug?: string | undefined;
557
557
  }, {
558
558
  status?: string | undefined;
559
559
  title?: string | undefined;
560
- order?: string | undefined;
561
560
  description?: string | undefined;
561
+ order?: string | undefined;
562
562
  slug?: string | undefined;
563
563
  }>>;
564
564
  /** Status value treated as published; others map to `draft`. Default `Published`. */
@@ -572,8 +572,8 @@ declare const contentSourceSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
572
572
  properties?: {
573
573
  status?: string | undefined;
574
574
  title?: string | undefined;
575
- order?: string | undefined;
576
575
  description?: string | undefined;
576
+ order?: string | undefined;
577
577
  slug?: string | undefined;
578
578
  } | undefined;
579
579
  publishedValue?: string | undefined;
@@ -585,8 +585,8 @@ declare const contentSourceSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<
585
585
  properties?: {
586
586
  status?: string | undefined;
587
587
  title?: string | undefined;
588
- order?: string | undefined;
589
588
  description?: string | undefined;
589
+ order?: string | undefined;
590
590
  slug?: string | undefined;
591
591
  } | undefined;
592
592
  publishedValue?: string | undefined;
@@ -1195,14 +1195,14 @@ export declare const blumeConfigSchema: z.ZodObject<{
1195
1195
  }, "strict", z.ZodTypeAny, {
1196
1196
  status?: string | undefined;
1197
1197
  title?: string | undefined;
1198
- order?: string | undefined;
1199
1198
  description?: string | undefined;
1199
+ order?: string | undefined;
1200
1200
  slug?: string | undefined;
1201
1201
  }, {
1202
1202
  status?: string | undefined;
1203
1203
  title?: string | undefined;
1204
- order?: string | undefined;
1205
1204
  description?: string | undefined;
1205
+ order?: string | undefined;
1206
1206
  slug?: string | undefined;
1207
1207
  }>>;
1208
1208
  /** Status value treated as published; others map to `draft`. Default `Published`. */
@@ -1216,8 +1216,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
1216
1216
  properties?: {
1217
1217
  status?: string | undefined;
1218
1218
  title?: string | undefined;
1219
- order?: string | undefined;
1220
1219
  description?: string | undefined;
1220
+ order?: string | undefined;
1221
1221
  slug?: string | undefined;
1222
1222
  } | undefined;
1223
1223
  publishedValue?: string | undefined;
@@ -1229,8 +1229,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
1229
1229
  properties?: {
1230
1230
  status?: string | undefined;
1231
1231
  title?: string | undefined;
1232
- order?: string | undefined;
1233
1232
  description?: string | undefined;
1233
+ order?: string | undefined;
1234
1234
  slug?: string | undefined;
1235
1235
  } | undefined;
1236
1236
  publishedValue?: string | undefined;
@@ -1322,8 +1322,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
1322
1322
  properties?: {
1323
1323
  status?: string | undefined;
1324
1324
  title?: string | undefined;
1325
- order?: string | undefined;
1326
1325
  description?: string | undefined;
1326
+ order?: string | undefined;
1327
1327
  slug?: string | undefined;
1328
1328
  } | undefined;
1329
1329
  publishedValue?: string | undefined;
@@ -1387,8 +1387,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
1387
1387
  properties?: {
1388
1388
  status?: string | undefined;
1389
1389
  title?: string | undefined;
1390
- order?: string | undefined;
1391
1390
  description?: string | undefined;
1391
+ order?: string | undefined;
1392
1392
  slug?: string | undefined;
1393
1393
  } | undefined;
1394
1394
  publishedValue?: string | undefined;
@@ -2123,14 +2123,14 @@ export declare const blumeConfigSchema: z.ZodObject<{
2123
2123
  }, "strict", z.ZodTypeAny, {
2124
2124
  path: string;
2125
2125
  label: string;
2126
- icon?: string | undefined;
2127
2126
  description?: string | undefined;
2127
+ icon?: string | undefined;
2128
2128
  tag?: string | undefined;
2129
2129
  }, {
2130
2130
  path: string;
2131
2131
  label: string;
2132
- icon?: string | undefined;
2133
2132
  description?: string | undefined;
2133
+ icon?: string | undefined;
2134
2134
  tag?: string | undefined;
2135
2135
  }>, "many">>;
2136
2136
  kind: z.ZodEnum<["dropdown", "language", "product", "version"]>;
@@ -2140,8 +2140,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2140
2140
  items: {
2141
2141
  path: string;
2142
2142
  label: string;
2143
- icon?: string | undefined;
2144
2143
  description?: string | undefined;
2144
+ icon?: string | undefined;
2145
2145
  tag?: string | undefined;
2146
2146
  }[];
2147
2147
  kind: "version" | "dropdown" | "language" | "product";
@@ -2151,8 +2151,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2151
2151
  items?: {
2152
2152
  path: string;
2153
2153
  label: string;
2154
- icon?: string | undefined;
2155
2154
  description?: string | undefined;
2155
+ icon?: string | undefined;
2156
2156
  tag?: string | undefined;
2157
2157
  }[] | undefined;
2158
2158
  }>, "many">>;
@@ -2179,14 +2179,14 @@ export declare const blumeConfigSchema: z.ZodObject<{
2179
2179
  }, "strict", z.ZodTypeAny, {
2180
2180
  path: string;
2181
2181
  label: string;
2182
- icon?: string | undefined;
2183
2182
  description?: string | undefined;
2183
+ icon?: string | undefined;
2184
2184
  tag?: string | undefined;
2185
2185
  }, {
2186
2186
  path: string;
2187
2187
  label: string;
2188
- icon?: string | undefined;
2189
2188
  description?: string | undefined;
2189
+ icon?: string | undefined;
2190
2190
  tag?: string | undefined;
2191
2191
  }>, "many">>;
2192
2192
  label: z.ZodString;
@@ -2198,8 +2198,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2198
2198
  items?: {
2199
2199
  path: string;
2200
2200
  label: string;
2201
- icon?: string | undefined;
2202
2201
  description?: string | undefined;
2202
+ icon?: string | undefined;
2203
2203
  tag?: string | undefined;
2204
2204
  }[] | undefined;
2205
2205
  }, {
@@ -2209,8 +2209,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2209
2209
  items?: {
2210
2210
  path: string;
2211
2211
  label: string;
2212
- icon?: string | undefined;
2213
2212
  description?: string | undefined;
2213
+ icon?: string | undefined;
2214
2214
  tag?: string | undefined;
2215
2215
  }[] | undefined;
2216
2216
  }>, "many">>;
@@ -2261,8 +2261,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2261
2261
  items: {
2262
2262
  path: string;
2263
2263
  label: string;
2264
- icon?: string | undefined;
2265
2264
  description?: string | undefined;
2265
+ icon?: string | undefined;
2266
2266
  tag?: string | undefined;
2267
2267
  }[];
2268
2268
  kind: "version" | "dropdown" | "language" | "product";
@@ -2279,8 +2279,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2279
2279
  items?: {
2280
2280
  path: string;
2281
2281
  label: string;
2282
- icon?: string | undefined;
2283
2282
  description?: string | undefined;
2283
+ icon?: string | undefined;
2284
2284
  tag?: string | undefined;
2285
2285
  }[] | undefined;
2286
2286
  }[] | undefined;
@@ -2333,8 +2333,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2333
2333
  items?: {
2334
2334
  path: string;
2335
2335
  label: string;
2336
- icon?: string | undefined;
2337
2336
  description?: string | undefined;
2337
+ icon?: string | undefined;
2338
2338
  tag?: string | undefined;
2339
2339
  }[] | undefined;
2340
2340
  }[] | undefined;
@@ -2349,8 +2349,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2349
2349
  items?: {
2350
2350
  path: string;
2351
2351
  label: string;
2352
- icon?: string | undefined;
2353
2352
  description?: string | undefined;
2353
+ icon?: string | undefined;
2354
2354
  tag?: string | undefined;
2355
2355
  }[] | undefined;
2356
2356
  }[] | undefined;
@@ -2812,8 +2812,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2812
2812
  properties?: {
2813
2813
  status?: string | undefined;
2814
2814
  title?: string | undefined;
2815
- order?: string | undefined;
2816
2815
  description?: string | undefined;
2816
+ order?: string | undefined;
2817
2817
  slug?: string | undefined;
2818
2818
  } | undefined;
2819
2819
  publishedValue?: string | undefined;
@@ -2986,8 +2986,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
2986
2986
  items: {
2987
2987
  path: string;
2988
2988
  label: string;
2989
- icon?: string | undefined;
2990
2989
  description?: string | undefined;
2990
+ icon?: string | undefined;
2991
2991
  tag?: string | undefined;
2992
2992
  }[];
2993
2993
  kind: "version" | "dropdown" | "language" | "product";
@@ -3004,8 +3004,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
3004
3004
  items?: {
3005
3005
  path: string;
3006
3006
  label: string;
3007
- icon?: string | undefined;
3008
3007
  description?: string | undefined;
3008
+ icon?: string | undefined;
3009
3009
  tag?: string | undefined;
3010
3010
  }[] | undefined;
3011
3011
  }[] | undefined;
@@ -3181,8 +3181,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
3181
3181
  properties?: {
3182
3182
  status?: string | undefined;
3183
3183
  title?: string | undefined;
3184
- order?: string | undefined;
3185
3184
  description?: string | undefined;
3185
+ order?: string | undefined;
3186
3186
  slug?: string | undefined;
3187
3187
  } | undefined;
3188
3188
  publishedValue?: string | undefined;
@@ -3412,8 +3412,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
3412
3412
  items?: {
3413
3413
  path: string;
3414
3414
  label: string;
3415
- icon?: string | undefined;
3416
3415
  description?: string | undefined;
3416
+ icon?: string | undefined;
3417
3417
  tag?: string | undefined;
3418
3418
  }[] | undefined;
3419
3419
  }[] | undefined;
@@ -3428,8 +3428,8 @@ export declare const blumeConfigSchema: z.ZodObject<{
3428
3428
  items?: {
3429
3429
  path: string;
3430
3430
  label: string;
3431
- icon?: string | undefined;
3432
3431
  description?: string | undefined;
3432
+ icon?: string | undefined;
3433
3433
  tag?: string | undefined;
3434
3434
  }[] | undefined;
3435
3435
  }[] | undefined;
@@ -129,6 +129,12 @@ export type NavNode = {
129
129
  display?: SidebarDisplay;
130
130
  icon?: string;
131
131
  route?: string;
132
+ /**
133
+ * The group's URL path (its folder route prefix), even when the folder
134
+ * has no index page to link. Used to scope the sidebar to a tab's section;
135
+ * not a clickable link (that's `route`).
136
+ */
137
+ path?: string;
132
138
  collapsed?: boolean;
133
139
  children: NavNode[];
134
140
  };
@@ -247,6 +247,44 @@ releases. When you want a layout that's fully yours,
247
247
  standard Astro project you own outright.
248
248
  :::
249
249
 
250
+ ## 404 page
251
+
252
+ Blume ships a default **not found** page out of the box: a centered "404" message
253
+ wrapped in the site chrome (header, search, theme), served for any unmatched URL.
254
+ `blume build` writes it to `404.html`, which static hosts serve automatically, and
255
+ `blume dev` shows it for unknown routes.
256
+
257
+ To replace it with your own, add a `pages/404.astro`. It owns the `/404` route the
258
+ same way `pages/changelog.astro` takes over the changelog — your page wins and the
259
+ default is dropped. Build it like any other custom page, in `PageLayout` or
260
+ `RootLayout`:
261
+
262
+ ```astro pages/404.astro lineNumbers
263
+ ---
264
+ import PageLayout from "blume/components/layout/PageLayout.astro";
265
+ import data from "blume:data";
266
+ ---
267
+
268
+ <PageLayout
269
+ site={{ title: data.config.title, description: data.config.description }}
270
+ logo={data.config.logo}
271
+ navigation={data.navigation}
272
+ themeMode={data.config.theme.mode}
273
+ searchEnabled={data.config.search.enabled}
274
+ page={{ title: "Page not found", route: "/404" }}
275
+ noindex={true}
276
+ >
277
+ <section class="mx-auto max-w-2xl px-6 py-24 text-center">
278
+ <h1>This page took a wrong turn</h1>
279
+ <a href="/">Back to home</a>
280
+ </section>
281
+ </PageLayout>
282
+ ```
283
+
284
+ To keep the default design but change its wording — including for other languages
285
+ — override the `notFound` [UI strings](/docs/content/i18n) (`title`, `description`,
286
+ `home`) via `i18n.ui`.
287
+
250
288
  ## Interactive pages
251
289
 
252
290
  Custom pages are ordinary Astro, so you can drop in React (or any framework)
@@ -86,19 +86,27 @@ sidebar:
86
86
 
87
87
  ## Tabs
88
88
 
89
- Render top-level sections as tabs in the header, useful for separating, say, docs
90
- from an API reference. A tab is highlighted when the current route falls under
91
- its `path`:
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`:
92
92
 
93
93
  ```ts blume.config.ts lineNumbers
94
94
  navigation: {
95
95
  tabs: [
96
- { label: "Docs", path: "/", icon: "book-open" },
96
+ { label: "Adapters", path: "/adapters", icon: "plug" },
97
97
  { label: "API", path: "/api", icon: "rocket" },
98
+ { label: "AI", path: "/ai", icon: "sparkles" },
98
99
  ],
99
100
  }
100
101
  ```
101
102
 
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. A route under no tab (or a tab
108
+ whose `path` is `/`) shows the full sidebar.
109
+
102
110
  ## Explicit sidebar
103
111
 
104
112
  For full control, define `navigation.sidebar` in config. When it's set, Blume
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -45,7 +45,7 @@ import { buildThemeCss } from "../theme/palette.ts";
45
45
  import { twoslashCss } from "../theme/twoslash.ts";
46
46
  import { discoverExamples } from "./examples.ts";
47
47
  import { discoverIslands } from "./islands.ts";
48
- import { customOgRoutes, discoverPages } from "./pages.ts";
48
+ import { customOgRoutes, discoverPages, routeIsTaken } from "./pages.ts";
49
49
  import {
50
50
  askEndpointTemplate,
51
51
  astroConfigTemplate,
@@ -61,6 +61,7 @@ import {
61
61
  mcpEndpointTemplate,
62
62
  mcpPageFile,
63
63
  mixedbreadSearchEndpointTemplate,
64
+ notFoundPageTemplate,
64
65
  ogEndpointTemplate,
65
66
  rawMarkdownEndpointTemplate,
66
67
  rssEndpointTemplate,
@@ -786,6 +787,25 @@ const writeMcpFiles = async (
786
787
  ]);
787
788
  };
788
789
 
790
+ /**
791
+ * Write the default 404 page at Astro's reserved `src/pages/404.astro` path so
792
+ * static builds emit `dist/404.html`. Skipped when the project already owns
793
+ * `/404` (a custom `pages/404.astro` or a `404.md` content page), letting it be
794
+ * fully overridden without a route collision; `pruneOrphans` then removes any
795
+ * previously-generated copy.
796
+ */
797
+ const writeNotFoundPage = async (
798
+ write: (path: string, content: string) => Promise<boolean>,
799
+ srcDir: string,
800
+ pages: { pattern: string }[],
801
+ contentPages: { route: string }[]
802
+ ): Promise<void> => {
803
+ if (routeIsTaken(pages, contentPages, "/404")) {
804
+ return;
805
+ }
806
+ await write(join(srcDir, "pages", "404.astro"), notFoundPageTemplate());
807
+ };
808
+
789
809
  export interface GenerateResult {
790
810
  /** Whether any structural file changed (config/page/content config). */
791
811
  structuralChange: boolean;
@@ -985,6 +1005,9 @@ export const generateRuntime = async (
985
1005
  );
986
1006
  }
987
1007
 
1008
+ // The default 404 page (`/404`), unless the project already owns the route.
1009
+ await writeNotFoundPage(write, srcDir, pages, project.graph.pages);
1010
+
988
1011
  // The provider-specific client loader behind the `blume:search-client` alias
989
1012
  // is always (re)generated so the alias resolves even when search is disabled.
990
1013
  await write(searchClientPath, searchClientTemplate(config));
@@ -27,6 +27,20 @@ export const discoverPages = async (
27
27
  });
28
28
  };
29
29
 
30
+ /**
31
+ * Whether the project already owns `route` — through a custom `.astro` page
32
+ * (injected, so matched on `pattern`) or a content page (matched on `route`).
33
+ * Used to skip a generated default page (e.g. `/404`, `/changelog`) so a
34
+ * user-authored page overrides it without a route collision.
35
+ */
36
+ export const routeIsTaken = (
37
+ pages: { pattern: string }[],
38
+ contentPages: { route: string }[],
39
+ route: string
40
+ ): boolean =>
41
+ pages.some((page) => page.pattern === route) ||
42
+ contentPages.some((page) => page.route === route);
43
+
30
44
  /** A custom-page route that should get a generated OG card. */
31
45
  export interface OgCustomRoute {
32
46
  /** `og/<slug>.png` path segment; `index` for the site root. */
@@ -1188,6 +1188,54 @@ const canonical = base ? base + "/changelog" : null;
1188
1188
  `;
1189
1189
  };
1190
1190
 
1191
+ /**
1192
+ * Generate `.blume/src/pages/404.astro`: the default not-found page. Rendered
1193
+ * through `PageLayout` (header + search, no sidebar) so it stays consistent with
1194
+ * the rest of the site, with copy pulled from the translatable `notFound` UI
1195
+ * strings. Written at Astro's reserved `src/pages/404.astro` path so static
1196
+ * builds emit `dist/404.html` and the dev server serves it for unmatched routes.
1197
+ * Skipped by the generator when a user `pages/404.astro` already occupies the
1198
+ * `/404` route, so projects can fully override it.
1199
+ */
1200
+ export const notFoundPageTemplate = (): string => `---
1201
+ // Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
1202
+ import PageLayout from "blume/components/layout/PageLayout.astro";
1203
+ import data from "../generated/data.json";
1204
+
1205
+ export const prerender = true;
1206
+
1207
+ const nf = data.ui.notFound;
1208
+ ---
1209
+
1210
+ <PageLayout
1211
+ site={{ title: data.config.title, description: data.config.description }}
1212
+ logo={data.config.logo}
1213
+ favicon={data.config.favicon}
1214
+ appleIcon={data.config.appleIcon}
1215
+ banner={data.config.banner}
1216
+ analytics={data.config.analytics}
1217
+ navigation={data.navigation}
1218
+ page={{ title: nf.title, route: "/404" }}
1219
+ themeMode={data.config.theme.mode}
1220
+ fontCssVars={data.fontCssVars}
1221
+ searchEnabled={data.config.search.enabled}
1222
+ ui={data.ui}
1223
+ noindex={true}
1224
+ >
1225
+ <div
1226
+ class="mx-auto flex min-h-[60vh] max-w-2xl flex-col items-center justify-center gap-4 px-6 py-24 text-center"
1227
+ >
1228
+ <p class="text-6xl font-bold text-muted-foreground">404</p>
1229
+ <h1 class="text-2xl font-semibold text-foreground">{nf.title}</h1>
1230
+ <p class="text-muted-foreground">{nf.description}</p>
1231
+ <a
1232
+ class="mt-2 rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-foreground"
1233
+ href="/">{nf.home}</a
1234
+ >
1235
+ </div>
1236
+ </PageLayout>
1237
+ `;
1238
+
1191
1239
  /**
1192
1240
  * Generate `.blume/src/generated/components.ts`, which re-exports the user's
1193
1241
  * component overrides (or empty maps when no `components.ts` exists). Importing