nimbus-docs 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { o as validateLintOptions, r as suggest, t as IMPLEMENTED_CODES } from "./rules-DDDvKkyJ.js";
1
+ import { o as validateLintOptions, r as suggest, t as IMPLEMENTED_CODES } from "./rules-CzB-afEb.js";
2
2
  import { i as toRouteKey, n as isAbsoluteUrl, r as toBrowserHref, t as withStrictKeys } from "./strict-keys-fbKKxxKL.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { execFile } from "node:child_process";
@@ -118,28 +118,16 @@ function slug(value, maintainCase) {
118
118
  * Mirror of Astro's content-layer slug normalization, used by every
119
119
  * framework URL builder that derives a URL from an `entry.id`.
120
120
  *
121
- * Astro's `glob` content loader (used by Nimbus's `docsCollection` /
122
- * `partialsCollection` factories) runs each path segment through
121
+ * Astro's `glob` content loader runs each path segment through
123
122
  * `github-slugger.slug()` and strips a trailing `/index`. That output is
124
- * what `entry.id` becomes inside `getCollection`, and it's what `params.slug`
125
- * substitutes into `[...slug].astro` routes. Anything that constructs a
126
- * URL from the raw filesystem path *must* apply the same normalization or
127
- * the URL won't match what Astro actually serves.
128
- *
129
- * Why mirror instead of asking Astro: framework URL builders run at page
130
- * render time (sidebar hrefs) and during the integration's
131
- * `astro:config:setup` (sitemap, llms.txt). At neither point do we have a
132
- * clean way to read Astro's resolved routes. The honest architectural fix
133
- * is to refactor those builders to consume the route manifest at
134
- * `astro:routes:resolved` (sitemap/llms) or via a build-emitted lookup
135
- * table (sidebar). Until that work lands, this helper keeps the URLs
136
- * correct.
137
- *
138
- * Mirror caveat: this matches `github-slugger.slug()` and the trailing-
139
- * /index strip — the documented public-library behaviors Astro inherits
140
- * — not Astro's private routing internals. If a user supplies a custom
141
- * `generateId` to the content loader, or a `data.slug` override in
142
- * frontmatter, this helper doesn't see it. Both are uncommon.
123
+ * what `entry.id` becomes and what `params.slug` substitutes into
124
+ * `[...slug].astro` routes, so anything building a URL from the raw
125
+ * filesystem path must apply the same normalization to match what Astro
126
+ * serves.
127
+ *
128
+ * Caveat: this matches `github-slugger.slug()` and the trailing-/index
129
+ * strip, not a custom loader `generateId` or a `data.slug` frontmatter
130
+ * override neither of which this helper sees.
143
131
  */
144
132
  /** Canonicalize one entry id the way Astro's content layer does. */
145
133
  function canonicalSlug(entryId) {
@@ -489,12 +477,9 @@ function hasActivePage(item, currentPath) {
489
477
  * URL prefix* — e.g. `Components` mounted at `/components/` —
490
478
  * rather than a sub-directory of the primary docs collection.
491
479
  *
492
- * Why this matters: under the previous unconditional behavior, every
493
- * top-level group in the sidebar (including `wip/`, `lab/`, and other
494
- * docs-collection subdirectories) was promoted to a header tab. The
495
- * header rail is meant for "other collections" navigation, not for
496
- * sub-sections of the default collection — those belong in the
497
- * sidebar's own collapsible tree.
480
+ * Sub-directories of the primary collection (`wip/`, `lab/`, etc.) are
481
+ * deliberately excluded the header rail is for cross-collection
482
+ * navigation; sub-sections belong in the sidebar's own tree.
498
483
  *
499
484
  * Caller must pass the *un-scoped* tree (the result of
500
485
  * `buildSidebarTree`, not `getSidebar`); otherwise only the current
@@ -557,16 +542,13 @@ function applyDefaultCollapsed(items) {
557
542
  }
558
543
  }
559
544
  /**
560
- * @deprecated Effective no-op under structural separation. Pre-2026
561
- * Nimbus rendered the group's index as the first child link and used
562
- * this to rename that link to "Overview". The index is now exposed via
563
- * `SidebarGroupItem.indexHref` (the group label IS the link), so there's
564
- * no first-child index to rename. The function is kept so older configs
565
- * that set `sidebar.overviewLabel` don't blow up; future major can drop it.
566
- *
567
- * Renamed only when the first link IS the group's index (matched via the
568
- * `sortKeyByItem` WeakMap) — under structural separation that condition
569
- * never holds, so this silently returns the input unchanged.
545
+ * Relabel a section's landing link to the `overviewLabel` string (default
546
+ * "Overview"). Applies to a `directory:` autogenerate's leading landing
547
+ * link wherever it surfaces (tracked via `directoryIndexLinks`), and to a
548
+ * group whose first child link IS the group's own index (matched via the
549
+ * `sortKeyByItem` WeakMap). Config groups expose their index as the group
550
+ * label itself (`SidebarGroupItem.indexHref`), so those aren't relabelled
551
+ * here — there's no separate child link to rename.
570
552
  */
571
553
  function applyOverviewLabel(items, label) {
572
554
  for (const item of items) if (item.type === "link" && directoryIndexLinks.has(item)) item.label = label;
@@ -1025,7 +1007,7 @@ async function getLastUpdatedFromGit(filePath) {
1025
1007
 
1026
1008
  //#endregion
1027
1009
  //#region src/_internal/admonition-transform.ts
1028
- /** Built-in MyST / Docusaurus / CF admonition types and their Aside mapping. */
1010
+ /** Built-in MyST / Docusaurus admonition types and their Aside mapping. */
1029
1011
  const BUILTIN_TYPES = {
1030
1012
  note: "note",
1031
1013
  info: "note",
@@ -1856,9 +1838,9 @@ async function validateMdxContent(options) {
1856
1838
  * Format a list of failures into a single multi-line error message
1857
1839
  * suitable for `throw new Error(...)`.
1858
1840
  */
1859
- function formatFailures(failures, globalsCount) {
1841
+ function formatFailures(failures) {
1860
1842
  const lines = failures.map((f) => {
1861
- const fix = f.hint ? `Did you mean <${f.hint} />?` : globalsCount === 0 ? `Register it in src/components.ts, or add an explicit \`import\` at the top of this file.` : `Register it in src/components.ts, or add an explicit \`import\` at the top of this file.`;
1843
+ const fix = f.hint ? `Did you mean <${f.hint} />?` : `Register it in src/components.ts, or add an explicit \`import\` at the top of this file.`;
1862
1844
  return ` ${f.filePath}:${f.line}:${f.column} <${f.tag} /> → ${fix}`;
1863
1845
  });
1864
1846
  return `[nimbus-docs] Unknown MDX component ${failures.length === 1 ? "tag" : "tags"}:\n` + lines.join("\n") + "\n\nA PascalCase tag in MDX must either be registered in src/components.ts (the global registry) or imported at the top of the file. Without either, MDX renders the tag as literal text on the page — a silent failure this validator turns into a build error.";
@@ -2086,7 +2068,7 @@ function validateNimbusConfig(input) {
2086
2068
  const tail = received === null ? "" : `\n received: ${received}`;
2087
2069
  return ` - ${display}: ${issue.message}${tail}`;
2088
2070
  }).join("\n");
2089
- throw new Error(`Invalid nimbus.config — fix these issues:\n${issues}\n\nSee https://nimbus-docs.dev/config for the full config schema.`);
2071
+ throw new Error(`Invalid nimbus.config — fix these issues:\n${issues}\n\nSee https://nimbus-docs.com/config for the full config schema.`);
2090
2072
  }
2091
2073
  /**
2092
2074
  * Resolve the value at `path` inside the raw input and format it for an
@@ -2134,18 +2116,11 @@ function virtualConfigPlugin(config, extras) {
2134
2116
  * inside `.md` / `.mdx` files. Output feeds `shikiConfig.langs` so Shiki
2135
2117
  * eager-loads every grammar at startup instead of lazy-loading on first use.
2136
2118
  *
2137
- * Why this matters: Shiki's lazy load assumes every MDX file gets processed
2138
- * during a build. Layer 2 (incremental builds' Vite MDX-skip plugin) breaks
2139
- * that assumption cached MDX files never enter the markdown pipeline, so
2140
- * languages that only appear in cached files would never trigger a grammar
2141
- * load, and any non-cached file using those languages would silently render
2142
- * without highlighting.
2143
- *
2144
- * Eager loading also gives non-incremental users a small predictability win:
2145
- * the highlighter behaves the same regardless of which file is processed
2146
- * first.
2147
- *
2148
- * Cost: ~1s on a 7k-file bench. Acceptable.
2119
+ * Needed because incremental builds skip cached MDX files: those never
2120
+ * enter the markdown pipeline, so a language appearing only in cached
2121
+ * files would never trigger Shiki's lazy grammar load, and a non-cached
2122
+ * file using it would render without highlighting. Eager loading also
2123
+ * keeps highlighting independent of which file is processed first.
2149
2124
  */
2150
2125
  const FENCE_RE = /^[ \t]*```([a-zA-Z][a-zA-Z0-9_+\-]*)/gm;
2151
2126
  async function* walkMdx(dir) {
@@ -2200,10 +2175,10 @@ async function scanCodeBlockLanguages(projectRoot, langAlias = {}) {
2200
2175
  * pages/<aa>/<full-hash>.html — cached HTML body for a page, sharded
2201
2176
  * by the first 2 hex chars of the hash
2202
2177
  *
2203
- * Phase 2 MVP — atomic per-file writes. v2 adds a manifest-level
2204
- * `namespace` field for PR-vs-main isolation; resolution lives in
2205
- * `namespace.ts`. Framework/Node version is folded into `globalHash` via
2206
- * `computeGlobalHash` already, so it doesn't need a separate field.
2178
+ * Atomic per-file writes. A manifest-level `namespace` field provides
2179
+ * PR-vs-main isolation; resolution lives in `namespace.ts`. Framework/Node
2180
+ * version is folded into `globalHash` via `computeGlobalHash` already, so
2181
+ * it doesn't need a separate field.
2207
2182
  */
2208
2183
  const SCHEMA_VERSION = 2;
2209
2184
  var Cache = class {
@@ -2272,14 +2247,13 @@ var Cache = class {
2272
2247
  /**
2273
2248
  * Snapshot a *bounded subset* of `dist/_astro/` into the cache.
2274
2249
  *
2275
- * Naive snapshot was unbounded: every warm build accumulated new
2276
- * bundle hashes (vite produces different hashes when the module graph
2277
- * differs between builds) and we kept everything forever. Caller passes
2278
- * the set of asset rel-paths that some cached HTML actually references —
2250
+ * Bounded so the cache doesn't grow forever: Vite emits new bundle
2251
+ * hashes whenever the module graph differs between builds. The caller
2252
+ * passes the asset rel-paths that some cached HTML actually references;
2279
2253
  * anything outside that set gets dropped.
2280
2254
  *
2281
2255
  * `referencedRelPaths` should be the union of every `/_astro/...` URL
2282
- * extracted from cached HTML — see `parseReferencedAssets` in index.ts.
2256
+ * extracted from cached HTML — see `collectReferencedAssets` in index.ts.
2283
2257
  */
2284
2258
  async snapshotAssets(distAstroDir, referencedRelPaths) {
2285
2259
  const target = resolve(this.root, "assets");
@@ -2448,10 +2422,9 @@ async function writeAtomic(path, data) {
2448
2422
  * - pageHash: sha256(page bytes + globalHash). Determines whether a
2449
2423
  * given page's cached HTML is still valid.
2450
2424
  *
2451
- * Phase 2 MVP — deliberately no partial tracking, no data-collection tracking,
2452
- * no component-graph tracking. Phase 3 wires the partial registry into the
2453
- * page hash; that work depends on `validate-mdx-content.ts` being extended
2454
- * to capture `<Render file="…">` references.
2425
+ * Current scope deliberately omits data-collection tracking and
2426
+ * component-graph tracking. Partial-dependency tracking folds the partial
2427
+ * registry into the page hash (see `partial-refs.ts`).
2455
2428
  */
2456
2429
  const TRACKED_DIRS = ["src", "public"];
2457
2430
  const TRACKED_FILES = [
@@ -2514,7 +2487,7 @@ async function walk$1(dir, root) {
2514
2487
  * - Node major version (minor diffs occasionally affect bundling)
2515
2488
  * - Platform + arch (some asset emission is platform-sensitive)
2516
2489
  *
2517
- * Including provenance closes BUG-002 / BUG-003: a framework upgrade
2490
+ * Including provenance closes a class of staleness bug: a framework upgrade
2518
2491
  * (or Node bump, or OS change) silently changed rendered output but the
2519
2492
  * old global hash matched, so warm builds served stale entries from a
2520
2493
  * different version of the world.
@@ -2583,7 +2556,7 @@ async function readDepVersion(projectRoot, dep) {
2583
2556
  }
2584
2557
  }
2585
2558
  /**
2586
- * Phase 3 — per-page hash with transitive partial dependencies folded in.
2559
+ * Per-page hash with transitive partial dependencies folded in.
2587
2560
  *
2588
2561
  * Same shape as `computePageHash` but additionally absorbs the bytes of
2589
2562
  * every partial the page transitively embeds. Sorted by path so two
@@ -2667,14 +2640,14 @@ async function resolveCacheNamespace(projectRoot) {
2667
2640
  //#endregion
2668
2641
  //#region src/_internal/incremental/partial-refs.ts
2669
2642
  /**
2670
- * Phase 3 — partial dependency tracking.
2643
+ * Partial dependency tracking.
2671
2644
  *
2672
2645
  * Walks MDX content to find `<Render file="…" />` and `<Render slug="…" />`
2673
2646
  * references, then builds a per-page transitive closure: "pathname X
2674
2647
  * embeds partials A, B, C — where A in turn embeds D, and B in turn
2675
2648
  * embeds E and F." Folding all of those partials' bytes into the page's
2676
- * hash gives us the property the spec promises: edit one partial, exactly
2677
- * the pages that transitively embed it re-render.
2649
+ * hash gives us the property we want: edit one partial, exactly the pages
2650
+ * that transitively embed it re-render.
2678
2651
  *
2679
2652
  * Scope (v1):
2680
2653
  * - Only string-literal `file` / `slug` props get captured. Dynamic
@@ -2683,16 +2656,17 @@ async function resolveCacheNamespace(projectRoot) {
2683
2656
  * v1 limitation; the `partialResolver` hook (deferred) gives sites
2684
2657
  * an escape valve.
2685
2658
  * - Default resolver: `<Render file="topic/slug" />` resolves to
2686
- * `src/content/partials/topic/slug.mdx`. Matches the bench, apps/www,
2687
- * and mvvmm's PR shape for cloudflare-docs (their resolver also
2688
- * prepends a `product` prop — that needs a custom resolver).
2659
+ * `src/content/partials/topic/slug.mdx`. Sites with a multi-prop
2660
+ * convention (e.g. a resolver that prepends a `product` prop) need a
2661
+ * custom resolver.
2689
2662
  * - Cycles in the partial graph are handled (visited set).
2690
2663
  */
2691
2664
  /**
2692
2665
  * Check `candidate` is a normalised path under `rootWithSep`. Cheap
2693
2666
  * defense against `../` traversal escaping the partials root. We use a
2694
2667
  * trailing-sep marker on root to avoid false-matching `partialsRoot` with
2695
- * `partialsRoot-evil/` style siblings.
2668
+ * sibling directories that share its name as a prefix (e.g.
2669
+ * `partialsRoot-shared/`).
2696
2670
  */
2697
2671
  function isInside(candidate, rootWithSep) {
2698
2672
  return candidate.startsWith(rootWithSep) || candidate === rootWithSep.slice(0, -1);
@@ -2715,7 +2689,7 @@ const ATTR_RE = /([a-zA-Z][a-zA-Z0-9_]*)\s*=\s*["']([^"']*)["']/g;
2715
2689
  * extensions or use plain Markdown for partials.
2716
2690
  *
2717
2691
  * `partialsBase` lets callers point the resolver at a non-default partials
2718
- * collection base (closes BUG-040). Default: `src/content/partials`.
2692
+ * collection base. Default: `src/content/partials`.
2719
2693
  */
2720
2694
  function makeDefaultPartialResolver(projectRoot, partialsBase = "src/content/partials") {
2721
2695
  const partialsRoot = resolve(projectRoot, partialsBase);
@@ -2884,7 +2858,7 @@ async function partialsDirExists(projectRoot, partialsBase = "src/content/partia
2884
2858
  //#endregion
2885
2859
  //#region src/_internal/incremental/index.ts
2886
2860
  /**
2887
- * Incremental builds — Phase 2 MVP.
2861
+ * Incremental builds.
2888
2862
  *
2889
2863
  * Wires the cache layer into Astro's prerenderer. On warm build, pages whose
2890
2864
  * source bytes (and the global hash) haven't changed since the last build
@@ -2893,16 +2867,14 @@ async function partialsDirExists(projectRoot, partialsBase = "src/content/partia
2893
2867
  *
2894
2868
  * Astro sees every route in `getStaticPaths` either way — cache hits flow
2895
2869
  * through `astro:build:generated`, adapter writers, route-headers accounting
2896
- * exactly like fresh renders. This is the spec's design, *not* mvvmm's
2897
- * `getStaticPaths`-filtering approach, because the latter hides cached
2898
- * routes from downstream hooks.
2870
+ * exactly like fresh renders. This is by design rather than filtering
2871
+ * cached routes out of `getStaticPaths`, which would hide them from
2872
+ * downstream hooks.
2899
2873
  *
2900
- * Anti-goals for this MVP (deferred to Phase 3+):
2901
- * - Partial-dependency tracking. Edit a partial → still full rebuild today.
2874
+ * Out of scope for now:
2902
2875
  * - Data-collection scoping.
2903
2876
  * - Component-graph tracking. Any tracked-file change → full rebuild.
2904
- * - Provenance / namespacing / trust boundary. Hardening track.
2905
- * - `nimbus build --explain` and structured build reports. Console log only.
2877
+ * - `nimbus build --explain` and structured build reports.
2906
2878
  */
2907
2879
  /**
2908
2880
  * Normalise a request URL to its canonical pathname (no trailing slash,
@@ -2921,7 +2893,7 @@ function canonicalisePathname(input) {
2921
2893
  }
2922
2894
  /**
2923
2895
  * Build a map from pathname → MDX file bytes by walking the docs collection
2924
- * directory. Phase 2 MVP only handles the primary `docs` collection.
2896
+ * directory. Only the primary `docs` collection is handled.
2925
2897
  *
2926
2898
  * Pathname derivation: `src/content/docs/<entry.id>.mdx` → `/<entry.id>`,
2927
2899
  * mirroring `getDocsStaticPaths` which uses `entry.id` verbatim as slug.
@@ -3019,9 +2991,9 @@ async function collectDocsPages(projectRoot, docsBase = "src/content/docs") {
3019
2991
  * Computes per-page hashes, reads prior manifest, determines which pages
3020
2992
  * are cache-hits.
3021
2993
  *
3022
- * Phase 3 — the page hash includes the bytes of every partial the page
3023
- * transitively embeds, so editing a partial invalidates exactly the pages
3024
- * that reference it (directly or transitively) and nothing else.
2994
+ * The page hash includes the bytes of every partial the page transitively
2995
+ * embeds, so editing a partial invalidates exactly the pages that reference
2996
+ * it (directly or transitively) and nothing else.
3025
2997
  */
3026
2998
  async function setupIncrementalContext(projectRoot, cacheDir, logger, partialResolver) {
3027
2999
  const cache = new Cache(cacheDir ? resolve(cacheDir, "nimbus") : resolve(projectRoot, ".nimbus/cache"));
@@ -3081,7 +3053,7 @@ async function setupIncrementalContext(projectRoot, cacheDir, logger, partialRes
3081
3053
  /**
3082
3054
  * Wrap an Astro prerenderer with the cache.
3083
3055
  *
3084
- * Strategy (mvvmm-style — chosen empirically over the "wrap Response" approach
3056
+ * Strategy (chosen empirically over the "wrap Response" approach
3085
3057
  * because Astro's per-route work outside `render` is the actual dominant cost,
3086
3058
  * not MDX→HTML conversion):
3087
3059
  *
@@ -3094,12 +3066,12 @@ async function setupIncrementalContext(projectRoot, cacheDir, logger, partialRes
3094
3066
  * `dist/<pathname>/index.html` for the filtered cached routes — Astro
3095
3067
  * never wrote them, so we do.
3096
3068
  *
3097
- * Trade-off vs. the spec's "wrap Response in render" design: downstream
3069
+ * Trade-off vs. the "wrap Response in render" design: downstream
3098
3070
  * Astro hooks (`astro:build:generated`, adapter writers, route accounting)
3099
3071
  * don't see cached routes. For Cloudflare adapter sites or anything that
3100
3072
  * depends on every route being visible to those hooks, this matters.
3101
3073
  * For static SSG sites where the rendered HTML *is* the output, it's fine.
3102
- * Documented as a limitation in Phase 5 user-facing notes.
3074
+ * Documented as a known limitation.
3103
3075
  */
3104
3076
  function wrapPrerenderer(defaultPrerenderer, ctx) {
3105
3077
  return {
@@ -3176,7 +3148,7 @@ async function restoreCachedPagesToDist(ctx, outDir) {
3176
3148
  * (so the snapshot is the union of fresh + previously-cached assets the
3177
3149
  * cached HTML still references).
3178
3150
  *
3179
- * BUG-007 fix: bounded to assets actually referenced by cached HTML. We
3151
+ * Bounded to assets actually referenced by cached HTML. We
3180
3152
  * walk every cached page's bytes, regex-extract `/_astro/...` URLs,
3181
3153
  * dedupe — and only persist those. Without this the snapshot grew
3182
3154
  * unboundedly because vite produces new bundle hashes on every warm
@@ -3193,7 +3165,7 @@ const ASSET_REF_RE = /\/_astro\/([^"')\s>]+)/g;
3193
3165
  * Strip query string and hash from an extracted asset path. Without
3194
3166
  * this, `/_astro/foo.js?v=1` would record `foo.js?v=1` as the file
3195
3167
  * name — the snapshot would skip it because no such file exists in
3196
- * `_astro/`, leaving the warm build with a broken reference (BUG-108).
3168
+ * `_astro/`, leaving the warm build with a broken reference.
3197
3169
  */
3198
3170
  function normaliseAssetRef(raw) {
3199
3171
  if (!raw) return null;
@@ -3213,10 +3185,9 @@ function normaliseAssetRef(raw) {
3213
3185
  *
3214
3186
  * The single regex matches `/_astro/...` anywhere in the HTML —
3215
3187
  * straightforward for `href="..."`, `src="..."`, `url(...)` in inline
3216
- * styles, and individual `srcset` URLs alike. (BUG-107 was about the
3217
- * earlier regex anchoring on a quote/paren prefix and missing the
3218
- * second+nth URL inside a `srcset` value; the unanchored form here
3219
- * catches them all.)
3188
+ * styles, and individual `srcset` URLs alike. (An earlier regex
3189
+ * anchored on a quote/paren prefix and missed the second+nth URL
3190
+ * inside a `srcset` value; the unanchored form here catches them all.)
3220
3191
  *
3221
3192
  * We scan the dist output rather than the in-memory cache because dist
3222
3193
  * is the source of truth for what's currently referenced — after the
@@ -3335,8 +3306,8 @@ function mdxSkipPlugin(ctx) {
3335
3306
  * - sitemap-0.xml carries all entries (we don't split until >45k urls)
3336
3307
  * - sitemap-index.xml lists sitemap-0.xml only
3337
3308
  *
3338
- * v1 scope no custom `serialize` yet (deferred; cloudflare-docs case),
3339
- * no `lastmod`, `changefreq`, `priority`, no image/video sitemaps. Matches
3309
+ * Scope: an optional `serialize` hook per URL, but no `lastmod`,
3310
+ * `changefreq`, `priority`, and no image/video sitemaps. Matches
3340
3311
  * `@astrojs/sitemap` *default* output for sites that don't override.
3341
3312
  */
3342
3313
  const URLSET_XMLNS = "xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"";
@@ -3487,8 +3458,7 @@ function extractFrontmatter(source) {
3487
3458
  const rest = source.slice(afterFirstMarker + 1);
3488
3459
  const closingMatch = rest.match(/(^|\n)---\s*(\n|$)/);
3489
3460
  if (!closingMatch || closingMatch.index === void 0) return null;
3490
- const endIndex = closingMatch[1] === "\n" ? closingMatch.index : closingMatch.index;
3491
- return rest.slice(0, endIndex);
3461
+ return rest.slice(0, closingMatch.index);
3492
3462
  }
3493
3463
  /**
3494
3464
  * Find a top-level boolean field in YAML frontmatter. Returns the
@@ -3519,9 +3489,8 @@ function parseBoolField(yaml, field) {
3519
3489
  * - `string[]` for either array form
3520
3490
  * - `undefined` if absent
3521
3491
  *
3522
- * The multiline form is the canonical YAML list syntax; the scanner
3523
- * previously accepted only scalar and inline-array, which silently
3524
- * dropped valid block lists at build time. The schema validates the
3492
+ * All three forms are accepted: scalar, inline array, and the multiline
3493
+ * block list (canonical YAML list syntax). The schema validates the
3525
3494
  * post-parse shape; the scanner has to match it.
3526
3495
  */
3527
3496
  function parsePreviousSlugField(yaml) {
@@ -3824,7 +3793,7 @@ function nimbus(rawConfig, options = {}) {
3824
3793
  skip: validateOpts.skip,
3825
3794
  projectRoot
3826
3795
  });
3827
- if (failures.length > 0) throw new Error(formatFailures(failures, globals.length));
3796
+ if (failures.length > 0) throw new Error(formatFailures(failures));
3828
3797
  logger.info(`MDX validation passed — ${globals.length} global component${globals.length === 1 ? "" : "s"} registered, ${contentDirs.length} content dir${contentDirs.length === 1 ? "" : "s"} scanned.`);
3829
3798
  }
3830
3799
  }
@@ -4075,9 +4044,9 @@ function runPagefind(siteDir) {
4075
4044
  /**
4076
4045
  * Main entry for `nimbus-docs`.
4077
4046
  *
4078
- * Exports the Astro integration (default), config helper, and the four
4079
- * data helpers (sidebar, prev/next, breadcrumbs, TOC). Phase 6 will
4080
- * add page composition helpers (`getDocsStaticPaths`, `getDocsPageProps`).
4047
+ * Exports the Astro integration (default), config helper, the data helpers
4048
+ * (sidebar, prev/next, breadcrumbs, TOC), and the page composition helpers
4049
+ * (`getDocsStaticPaths`, `getDocsPageProps`).
4081
4050
  *
4082
4051
  * Helpers read the user's config from `virtual:nimbus/config` (provided
4083
4052
  * by our Vite plugin) and content entries from `astro:content`. Both
@@ -4326,7 +4295,7 @@ async function getPrevNext(currentSlug, options) {
4326
4295
  /**
4327
4296
  * Build breadcrumb trail from "/" to the current page.
4328
4297
  *
4329
- * Phase 5: simple URL-segment derivation. Later phases may enrich with
4298
+ * Simple URL-segment derivation. A later iteration may enrich this with
4330
4299
  * sidebar-aware labels.
4331
4300
  */
4332
4301
  async function getBreadcrumbs(currentSlug, options) {