create-zudo-doc 0.2.21 → 0.2.22

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 (71) hide show
  1. package/dist/features/image-enlarge.d.ts +10 -2
  2. package/dist/features/image-enlarge.js +10 -2
  3. package/dist/scaffold.js +20 -22
  4. package/dist/zfb-config-gen.d.ts +16 -10
  5. package/dist/zfb-config-gen.js +34 -239
  6. package/package.json +1 -1
  7. package/templates/base/pages/lib/_body-end-islands.tsx +6 -13
  8. package/templates/base/pages/lib/_category-nav.tsx +30 -115
  9. package/templates/base/pages/lib/_category-tree-nav.tsx +38 -65
  10. package/templates/base/pages/lib/_compose-meta-title.ts +2 -6
  11. package/templates/base/pages/lib/_doc-body-end.tsx +3 -35
  12. package/templates/base/pages/lib/_doc-content-header.tsx +10 -111
  13. package/templates/base/pages/lib/_doc-history-area.tsx +19 -211
  14. package/templates/base/pages/lib/_doc-metainfo-area.tsx +10 -113
  15. package/templates/base/pages/lib/_doc-page-renderer.tsx +22 -183
  16. package/templates/base/pages/lib/_doc-page-shell.tsx +17 -251
  17. package/templates/base/pages/lib/_doc-pager.tsx +4 -74
  18. package/templates/base/pages/lib/_doc-route-entries.ts +43 -177
  19. package/templates/base/pages/lib/_doc-route-paths.ts +16 -101
  20. package/templates/base/pages/lib/_doc-tags-area.tsx +14 -77
  21. package/templates/base/pages/lib/_footer-with-defaults.tsx +37 -225
  22. package/templates/base/pages/lib/_frontmatter-preview-data.ts +5 -31
  23. package/templates/base/pages/lib/_head-with-defaults.tsx +13 -138
  24. package/templates/base/pages/lib/_header-with-defaults.tsx +24 -324
  25. package/templates/base/pages/lib/_inline-version-switcher.tsx +16 -78
  26. package/templates/base/pages/lib/_nav-data-prep.ts +32 -51
  27. package/templates/base/pages/lib/_nav-source-cache.ts +12 -57
  28. package/templates/base/pages/lib/_nav-source-docs.ts +33 -233
  29. package/templates/base/pages/lib/_search-widget-script.ts +2 -470
  30. package/templates/base/pages/lib/_search-widget.tsx +9 -195
  31. package/templates/base/pages/lib/_sidebar-prepaint.tsx +6 -59
  32. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +16 -118
  33. package/templates/base/pages/lib/_site-tree-nav.tsx +29 -80
  34. package/templates/base/pages/lib/doc-page-props.ts +26 -44
  35. package/templates/base/pages/lib/locale-merge.ts +32 -145
  36. package/templates/base/pages/lib/route-enumerators.ts +52 -286
  37. package/templates/base/pages/robots.txt.tsx +2 -26
  38. package/templates/base/src/components/ai-chat-modal.tsx +9 -8
  39. package/templates/base/src/components/doc-history.tsx +9 -8
  40. package/templates/base/src/components/image-enlarge.tsx +11 -8
  41. package/templates/base/src/components/sidebar-toggle.tsx +6 -170
  42. package/templates/base/src/components/sidebar-tree.tsx +6 -548
  43. package/templates/base/src/components/site-tree-nav.tsx +6 -220
  44. package/templates/base/src/config/color-scheme-utils.ts +34 -158
  45. package/templates/base/src/config/i18n.ts +9 -0
  46. package/templates/base/src/config/z-index-tokens.ts +5 -4
  47. package/templates/base/src/styles/global.css +5 -579
  48. package/templates/base/src/utils/base.ts +1 -1
  49. package/templates/base/src/utils/docs.ts +47 -16
  50. package/templates/base/src/utils/github.ts +12 -9
  51. package/templates/base/src/utils/nav-scope.ts +13 -42
  52. package/templates/base/src/utils/sidebar.ts +18 -86
  53. package/templates/base/src/utils/slug.ts +10 -53
  54. package/templates/base/src/utils/smart-break.tsx +12 -120
  55. package/templates/base/src/utils/tags.ts +25 -68
  56. package/templates/features/bodyFootUtil/files/src/utils/github.ts +12 -9
  57. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +13 -39
  58. package/templates/features/docHistory/files/src/components/doc-history.tsx +8 -636
  59. package/templates/features/docHistory/files/src/types/doc-history.ts +7 -23
  60. package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +35 -201
  61. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +8 -269
  62. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +6 -99
  63. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +67 -515
  64. package/templates/features/versioning/files/pages/lib/_versions-page.tsx +21 -73
  65. package/templates/base/plugins/connect-adapter.mjs +0 -169
  66. package/templates/base/plugins/search-index-plugin.mjs +0 -66
  67. package/templates/base/scripts/gen-z-index.mjs +0 -157
  68. package/templates/base/src/components/mermaid-enlarge.tsx +0 -490
  69. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +0 -47
  70. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +0 -111
  71. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +0 -93
@@ -1,9 +1,10 @@
1
- // Shared nav data-prep utilities used by both _header-with-defaults.tsx
2
- // and _sidebar-with-defaults.tsx.
1
+ // Host thin-stub for nav data-prep utilities (epic #2344, S5).
2
+ // Imports core logic from @takazudo/zudo-doc and wires host singletons.
3
3
  //
4
- // Extracted to avoid maintaining four near-identical copies: the two host
5
- // modules above plus their template mirrors under
6
- // packages/create-zudo-doc/templates/base/pages/lib/.
4
+ // Package exports the pure parameterized functions; this stub binds
5
+ // them to the host's `settings`, `i18n`, and `base` singletons so
6
+ // existing call sites (_header-with-defaults.tsx, _sidebar-with-defaults.tsx)
7
+ // are unchanged.
7
8
 
8
9
  import { settings } from "@/config/settings";
9
10
  import { t, type Locale } from "@/config/i18n";
@@ -15,6 +16,12 @@ import {
15
16
  import { type NavNode } from "@/utils/docs";
16
17
  import { buildSidebarForSection } from "@/utils/sidebar";
17
18
  import { loadNavSourceDocs } from "./_nav-source-docs";
19
+ import {
20
+ remapVersionedHrefs as _remapVersionedHrefs,
21
+ buildRootMenuItems as _buildRootMenuItems,
22
+ buildLocaleLinksForNav as _buildLocaleLinksForNav,
23
+ getThemeDefaultMode as _getThemeDefaultMode,
24
+ } from "@takazudo/zudo-doc/nav-data-prep";
18
25
 
19
26
  // ---------------------------------------------------------------------------
20
27
  // remapVersionedHrefs
@@ -22,30 +29,18 @@ import { loadNavSourceDocs } from "./_nav-source-docs";
22
29
 
23
30
  /**
24
31
  * Walk the nav tree and rewrite each node's `href` to its versioned form.
25
- *
26
- * `buildNavTree` always emits hrefs via `docsUrl()`; when the active route
27
- * lives under `/v/{version}/...` we need the same nodes pointing at the
28
- * versioned URL so internal nav clicks stay inside the version. Skips
29
- * nodes without an href (link-only or category placeholders).
30
32
  */
31
33
  export function remapVersionedHrefs(
32
34
  nodes: NavNode[],
33
35
  version: string,
34
36
  nodeLang: Locale,
35
37
  ): NavNode[] {
36
- return nodes.map((node) => {
37
- const children =
38
- node.children.length > 0
39
- ? remapVersionedHrefs(node.children, version, nodeLang)
40
- : node.children;
41
-
42
- if (!node.href || node.slug.startsWith("__link__")) {
43
- return children !== node.children ? { ...node, children } : node;
44
- }
45
-
46
- const newHref = versionedDocsUrl(node.slug, version, nodeLang);
47
- return { ...node, href: newHref, children };
48
- });
38
+ return _remapVersionedHrefs(
39
+ nodes,
40
+ version,
41
+ nodeLang,
42
+ (slug, v, lang) => versionedDocsUrl(slug, v, lang as Locale),
43
+ );
49
44
  }
50
45
 
51
46
  // ---------------------------------------------------------------------------
@@ -54,26 +49,18 @@ export function remapVersionedHrefs(
54
49
 
55
50
  /**
56
51
  * Root-menu items derived from settings.headerNav (mobile "back to menu" list).
57
- *
58
- * Used by both header and sidebar wrappers — the same nav data feeds both the
59
- * mobile SidebarToggle (header) and the desktop SidebarTree (sidebar).
60
52
  */
61
53
  export function buildRootMenuItems(
62
54
  lang: Locale,
63
55
  currentVersion?: string,
64
56
  ) {
65
- return settings.headerNav.map((item) => ({
66
- label: item.labelKey
67
- ? t(item.labelKey as Parameters<typeof t>[0], lang)
68
- : item.label,
69
- href: navHref(item.path, lang, currentVersion),
70
- children: item.children?.map((child) => ({
71
- label: child.labelKey
72
- ? t(child.labelKey as Parameters<typeof t>[0], lang)
73
- : child.label,
74
- href: navHref(child.path, lang, currentVersion),
75
- })),
76
- }));
57
+ return _buildRootMenuItems(
58
+ lang,
59
+ currentVersion,
60
+ settings.headerNav,
61
+ (key, l) => t(key as Parameters<typeof t>[0], l as Locale),
62
+ (path, l, v) => navHref(path, l as Locale | undefined, v),
63
+ );
77
64
  }
78
65
 
79
66
  // ---------------------------------------------------------------------------
@@ -89,7 +76,12 @@ export function buildLocaleLinksForNav(
89
76
  lang: Locale,
90
77
  localeCount: number,
91
78
  ) {
92
- return localeCount > 1 ? buildLocaleLinks(currentPath, lang) : undefined;
79
+ return _buildLocaleLinksForNav(
80
+ currentPath,
81
+ lang,
82
+ localeCount,
83
+ (path, l) => buildLocaleLinks(path, l as Locale),
84
+ );
93
85
  }
94
86
 
95
87
  // ---------------------------------------------------------------------------
@@ -98,17 +90,6 @@ export function buildLocaleLinksForNav(
98
90
 
99
91
  /**
100
92
  * Build the resolved sidebar node list for a given section + version.
101
- *
102
- * Loads the nav source, filters to the active section, then optionally
103
- * remaps hrefs for versioned routes.
104
- *
105
- * `emptyWhenUnsectioned` controls the `navSection === undefined` case —
106
- * the two legacy call sites deliberately disagreed: the header's mobile
107
- * drawer returned `[]` (root menu only), while the desktop sidebar fell
108
- * through to `buildSidebarForSection(..., undefined)` = the FULL tree
109
- * (pages whose slug matches no headerNav categoryMatch still get a
110
- * sidebar). Collapsing both to `[]` shipped an empty desktop sidebar for
111
- * unsectioned pages — keep the divergence explicit here.
112
93
  */
113
94
  export function buildSidebarNodes(
114
95
  lang: Locale,
@@ -133,5 +114,5 @@ export function buildSidebarNodes(
133
114
  * Returns `undefined` when color mode is not configured (single-scheme projects).
134
115
  */
135
116
  export function getThemeDefaultMode() {
136
- return settings.colorMode ? settings.colorMode.defaultMode : undefined;
117
+ return _getThemeDefaultMode(settings.colorMode);
137
118
  }
@@ -32,11 +32,21 @@
32
32
  // HMR intent preserved: a content edit produces a NEW snapshot object (new
33
33
  // `collections[name]` array identity), so every memo here misses and
34
34
  // recomputes — matching the old content-keyed cache's change detection.
35
+ //
36
+ // PACKAGE SPLIT (epic #2344, S6):
37
+ // `memoizeDerived` — the generic two-level WeakMap memo — is now in the
38
+ // package at @takazudo/zudo-doc/nav-source-cache (no zfb imports needed).
39
+ // `stableDocs` stays here because it imports `getCollection` /
40
+ // `getContentSnapshot` from "zfb/content", which is a virtual module
41
+ // provided by the zfb SSG build system at compile time — not an npm package.
35
42
 
36
43
  import { getCollection, getContentSnapshot } from "zfb/content";
37
44
  import { bridgeDocsEntries, type ZfbDocsData } from "../_data";
38
45
  import type { DocPageEntry } from "./doc-page-props";
39
46
 
47
+ // Re-export memoizeDerived from the package (pure, no zfb dep).
48
+ export { memoizeDerived } from "@takazudo/zudo-doc/nav-source-cache";
49
+
40
50
  // ---------------------------------------------------------------------------
41
51
  // Snapshot anchor → stable bridged arrays
42
52
  // ---------------------------------------------------------------------------
@@ -70,6 +80,8 @@ function buildBridged(collectionName: string): DocPageEntry[] {
70
80
  * `buildNavTree` — can rely on reference equality. The entries carry the full
71
81
  * `DocPageEntry` shape (Content, body, module_specifier, id, collection) the
72
82
  * route `paths()` props need.
83
+ *
84
+ * Passed as `stableDocs` to `createNavSourceDocs` in `_nav-source-docs.ts`.
73
85
  */
74
86
  export function stableDocs(collectionName: string): DocPageEntry[] {
75
87
  const anchor = snapshotAnchor(collectionName);
@@ -86,60 +98,3 @@ export function stableDocs(collectionName: string): DocPageEntry[] {
86
98
  bridgedByAnchor.set(anchor, built);
87
99
  return built;
88
100
  }
89
-
90
- // ---------------------------------------------------------------------------
91
- // Generic derived-array memo (merge / filter results)
92
- // ---------------------------------------------------------------------------
93
-
94
- // Two-level memo: a WeakMap on the FIRST stable input array (so a new snapshot
95
- // drops the whole sub-map), then a string key combining the remaining stable
96
- // inputs' identities and the option signature.
97
- const derivedMemo = new WeakMap<object, Map<string, unknown>>();
98
- // Per-build incrementing ids for stable arrays, so a multi-array key can be a
99
- // cheap string. Lives in a WeakMap so it does not retain arrays past their
100
- // snapshot.
101
- const arrayId = new WeakMap<object, number>();
102
- let nextArrayId = 1;
103
- function idOf(arr: object): number {
104
- let id = arrayId.get(arr);
105
- if (id === undefined) {
106
- id = nextArrayId++;
107
- arrayId.set(arr, id);
108
- }
109
- return id;
110
- }
111
-
112
- // Anchor object for the rare case of an empty `inputs` array (e.g. an absent
113
- // locale collection). Lets the WeakMap still key the derived memo.
114
- const EMPTY_INPUT_ANCHOR: object = {};
115
-
116
- /**
117
- * Memoize a derived array (e.g. a locale merge or an `isNavVisible` filter)
118
- * on the identity of its stable inputs plus a caller-supplied option
119
- * signature.
120
- *
121
- * `inputs` MUST be the stable arrays returned by {@link stableDocs} (or arrays
122
- * derived from them through this same helper) — passing a fresh array defeats
123
- * the memo. `optionSig` distinguishes call sites that differ only in filter
124
- * options (e.g. `applyDefaultLocaleOnlyFilter`, `keepUnlisted`), so they never
125
- * collide on the same key.
126
- */
127
- export function memoizeDerived<T>(
128
- inputs: readonly object[],
129
- optionSig: string,
130
- compute: () => T,
131
- ): T {
132
- const primary = inputs[0] ?? EMPTY_INPUT_ANCHOR;
133
- let sub = derivedMemo.get(primary);
134
- if (!sub) {
135
- sub = new Map();
136
- derivedMemo.set(primary, sub);
137
- }
138
- const key = `${inputs.map((a) => idOf(a)).join("/")}::${optionSig}`;
139
- // Use has(): a legitimately-`undefined` computed result must still register
140
- // as a cache hit, otherwise it recomputes every call.
141
- if (sub.has(key)) return sub.get(key) as T;
142
- const computed = compute();
143
- sub.set(key, computed);
144
- return computed;
145
- }
@@ -1,237 +1,37 @@
1
- // Shared, identity-stable nav-source resolver.
2
- //
3
- // Picks the right collection(s) and category-meta dir for an active
4
- // (locale, version) pair, applying the locale-first + EN-fallback merge that
5
- // the doc-route `paths()` passes use, so every nav surface (desktop sidebar,
6
- // mobile SidebarToggle, route enumeration, MDX nav wrappers) sees the same
7
- // data the pages enumerate.
8
- //
9
- // IDENTITY STABILITY (why this is not a thin wrapper)
10
- // ---------------------------------------------------
11
- // Every returned `docs` / `navDocs` array and `categoryMeta` Map is memoized
12
- // so repeat callers within one build get the SAME instances. That is what lets
13
- // `buildNavTree`'s identity fast-path (`src/utils/docs.ts`) skip the O(n log n)
14
- // key computation on the ~900 calls a 251-page build makes. See
15
- // `_nav-source-cache.ts` for the snapshot-anchored memo and the original-issue
16
- // trace (zudolab/zudo-doc#1902 / #1882).
17
- //
18
- // Used by:
19
- // - _sidebar-with-defaults.tsx / _header-with-defaults.tsx (per-page nav)
20
- // - the 4 doc-route paths() files (route enumeration + nav tree)
21
- // - route-enumerators.ts (sitemap) and the MDX nav wrappers
22
- // each picking the `NavSourceVariant` matching its filter needs.
23
-
24
- import { defaultLocale, getLocaleConfig, type Locale } from "@/config/i18n";
1
+ // Thin stub nav-source-docs moved to the package (epic #2344, S6).
2
+ // Calls `createNavSourceDocs(ctx)` from @takazudo/zudo-doc/nav-source-docs
3
+ // with the host singletons injected, then re-exports the resulting resolver
4
+ // functions so all existing call sites continue to work unchanged.
5
+
6
+ import { createNavSourceDocs } from "@takazudo/zudo-doc/nav-source-docs";
7
+ export type { NavSourceDocs, NavSourceOptions } from "@takazudo/zudo-doc/nav-source-docs";
8
+ import { defaultLocale, getLocaleConfig } from "@/config/i18n";
25
9
  import { settings } from "@/config/settings";
26
- import {
10
+ import { loadCategoryMeta, isNavVisible } from "@/utils/docs";
11
+ import { isDefaultLocaleOnlyPath } from "@/utils/base";
12
+ import { stableDocs } from "./_nav-source-cache";
13
+
14
+ const {
15
+ resolveNavSource,
16
+ resolveVersionedLocaleSource,
17
+ loadNavSourceDocs,
18
+ stableMergeCategoryMeta,
19
+ stableNavDocs,
20
+ } = createNavSourceDocs({
21
+ defaultLocale,
22
+ docsDir: settings.docsDir,
23
+ getVersions: () => settings.versions,
24
+ getLocaleConfig,
27
25
  loadCategoryMeta,
28
26
  isNavVisible,
29
- type CategoryMeta,
30
- } from "@/utils/docs";
31
- import type { DocsEntry } from "@/types/docs-entry";
32
- import type { DocPageEntry } from "./doc-page-props";
33
- import { stableDocs, memoizeDerived } from "./_nav-source-cache";
34
- import { mergeLocaleDocs } from "./locale-merge";
35
-
36
- // ---------------------------------------------------------------------------
37
- // Stable category-meta merge
38
- // ---------------------------------------------------------------------------
39
-
40
- // `loadCategoryMeta(dir)` is already memoized by dir (returns the same Map).
41
- // The locale merge of two such Maps, however, was minted fresh on every call —
42
- // breaking the `categoryMeta === categoryMeta` identity check in the nav-tree
43
- // fast-path. Memoize the merge on the (baseDir, localeDir) pair.
44
- const mergedCategoryMetaCache = new Map<string, Map<string, CategoryMeta>>();
45
-
46
- /** Base metadata first, locale overrides win on overlapping keys — same merge
47
- * order the locale doc-route paths() use. Returns a STABLE Map per
48
- * (baseDir, localeDir) pair. */
49
- export function stableMergeCategoryMeta(
50
- baseDir: string,
51
- localeDir: string,
52
- ): Map<string, CategoryMeta> {
53
- const key = `${baseDir}\n${localeDir}`;
54
- const cached = mergedCategoryMetaCache.get(key);
55
- if (cached) return cached;
56
- const merged = new Map<string, CategoryMeta>([
57
- ...loadCategoryMeta(baseDir),
58
- ...loadCategoryMeta(localeDir),
59
- ]);
60
- mergedCategoryMetaCache.set(key, merged);
61
- return merged;
62
- }
63
-
64
- // ---------------------------------------------------------------------------
65
- // Stable navDocs (isNavVisible filter) derived from a stable docs array
66
- // ---------------------------------------------------------------------------
67
-
68
- /** `docs.filter(isNavVisible)`, memoized on the stable `docs` identity so the
69
- * filtered array also has stable identity for the nav-tree fast-path. */
70
- export function stableNavDocs<T extends DocsEntry>(docs: T[]): T[] {
71
- return memoizeDerived([docs], "navVisible", () => docs.filter(isNavVisible));
72
- }
73
-
74
- // ---------------------------------------------------------------------------
75
- // Resolved nav source
76
- // ---------------------------------------------------------------------------
77
-
78
- export type NavSourceDocs = {
79
- /** Full doc list (merged + draft-filtered; unlisted retained per options). */
80
- docs: DocPageEntry[];
81
- /** `docs.filter(isNavVisible)` — stable instance for buildNavTree. */
82
- navDocs: DocPageEntry[];
83
- /** Stable category-meta Map for the active (locale, version). */
84
- categoryMeta: Map<string, CategoryMeta>;
85
- /** Slugs that came from the locale collection (for isFallback). Empty for
86
- * default-locale / single-collection cases. */
87
- localeSlugSet: ReadonlySet<string>;
27
+ isDefaultLocaleOnlyPath,
28
+ stableDocs,
29
+ });
30
+
31
+ export {
32
+ resolveNavSource,
33
+ resolveVersionedLocaleSource,
34
+ loadNavSourceDocs,
35
+ stableMergeCategoryMeta,
36
+ stableNavDocs,
88
37
  };
89
-
90
- /**
91
- * How to filter the merged doc list. The locale merge takes different options
92
- * at different call sites; the option signature is folded into the memo key so
93
- * variants never collide on a shared cache entry.
94
- */
95
- export interface NavSourceOptions {
96
- /** Drop base docs matching `defaultLocaleOnlyPrefixes` (route/sidebar nav). */
97
- applyDefaultLocaleOnlyFilter?: boolean;
98
- /** Retain `unlisted: true` docs (route enumeration + nav). */
99
- keepUnlisted?: boolean;
100
- }
101
-
102
- function optionSig(o: NavSourceOptions): string {
103
- return `dlo=${o.applyDefaultLocaleOnlyFilter ? 1 : 0};ku=${o.keepUnlisted ? 1 : 0}`;
104
- }
105
-
106
- const EMPTY_SLUG_SET: ReadonlySet<string> = new Set();
107
-
108
- /**
109
- * Resolve the identity-stable nav source for an EN/locale (optionally
110
- * versioned) context. Every field is memoized so repeated calls with the same
111
- * logical inputs return the same instances.
112
- */
113
- export function resolveNavSource(
114
- lang: Locale,
115
- currentVersion: string | undefined,
116
- options: NavSourceOptions = {},
117
- ): NavSourceDocs {
118
- const sig = optionSig(options);
119
-
120
- // --- Versioned. For a non-default locale the version IS configured for,
121
- // delegate to resolveVersionedLocaleSource so every nav surface uses the
122
- // SAME version-scoped locale-first merge the page body / route
123
- // enumeration use (#1909) — keeping nav labels and locale-only version
124
- // pages in sync. Otherwise (default locale, or the version not configured
125
- // for this locale) fall back to the version's EN base collection.
126
- if (currentVersion) {
127
- // `versions` is `VersionConfig[] | false` — `false?.find` would throw
128
- // (optional chaining only short-circuits on null/undefined).
129
- const versionConfig = Array.isArray(settings.versions)
130
- ? settings.versions.find((v) => v.slug === currentVersion)
131
- : undefined;
132
- const localeDir = versionConfig?.locales?.[lang]?.dir;
133
- if (lang !== defaultLocale && localeDir) {
134
- return resolveVersionedLocaleSource(
135
- currentVersion,
136
- versionConfig?.docsDir ?? settings.docsDir,
137
- lang,
138
- localeDir,
139
- options,
140
- );
141
- }
142
- const docs = stableDocs(`docs-v-${currentVersion}`);
143
- const categoryMeta = loadCategoryMeta(versionConfig?.docsDir ?? settings.docsDir);
144
- const navDocs = stableNavDocs(docs);
145
- return { docs, navDocs, categoryMeta, localeSlugSet: EMPTY_SLUG_SET };
146
- }
147
-
148
- // --- Default locale: the "docs" collection directly.
149
- if (lang === defaultLocale) {
150
- const docs = stableDocs("docs");
151
- const categoryMeta = loadCategoryMeta(settings.docsDir);
152
- const navDocs = stableNavDocs(docs);
153
- return { docs, navDocs, categoryMeta, localeSlugSet: EMPTY_SLUG_SET };
154
- }
155
-
156
- // --- Non-default locale: locale-first merge with EN fallback.
157
- const baseDocs = stableDocs("docs");
158
- const localeDocs = stableDocs(`docs-${lang}`);
159
-
160
- const merged = memoizeDerived([baseDocs, localeDocs], `merge;${sig}`, () =>
161
- mergeLocaleDocs<DocPageEntry>({
162
- baseDocs,
163
- localeDocs,
164
- applyDefaultLocaleOnlyFilter: options.applyDefaultLocaleOnlyFilter,
165
- keepUnlisted: options.keepUnlisted,
166
- }),
167
- );
168
- const docs = merged.docs;
169
-
170
- const localeDir = getLocaleConfig(lang)?.dir ?? settings.docsDir;
171
- const categoryMeta = stableMergeCategoryMeta(settings.docsDir, localeDir);
172
- const navDocs = stableNavDocs(docs);
173
-
174
- return { docs, navDocs, categoryMeta, localeSlugSet: merged.localeSlugSet };
175
- }
176
-
177
- /**
178
- * Resolve the identity-stable nav source for a versioned non-default-locale
179
- * context — locale-specific version collection over the version's EN base.
180
- */
181
- export function resolveVersionedLocaleSource(
182
- versionSlug: string,
183
- versionDocsDir: string,
184
- lang: Locale,
185
- localeDir: string | undefined,
186
- options: NavSourceOptions = {},
187
- ): NavSourceDocs {
188
- const sig = optionSig(options);
189
- const baseDocs = stableDocs(`docs-v-${versionSlug}`);
190
- const localeDocs = localeDir ? stableDocs(`docs-v-${versionSlug}-${lang}`) : EMPTY_DOCS;
191
-
192
- const merged = memoizeDerived(
193
- localeDir ? [baseDocs, localeDocs] : [baseDocs],
194
- `vmerge;${lang};${sig}`,
195
- () =>
196
- mergeLocaleDocs<DocPageEntry>({
197
- baseDocs,
198
- localeDocs,
199
- applyDefaultLocaleOnlyFilter: options.applyDefaultLocaleOnlyFilter,
200
- keepUnlisted: options.keepUnlisted,
201
- }),
202
- );
203
- const docs = merged.docs;
204
-
205
- const categoryMeta = localeDir
206
- ? stableMergeCategoryMeta(versionDocsDir, localeDir)
207
- : loadCategoryMeta(versionDocsDir);
208
- const navDocs = stableNavDocs(docs);
209
-
210
- return { docs, navDocs, categoryMeta, localeSlugSet: merged.localeSlugSet };
211
- }
212
-
213
- // Shared empty array for the "no locale collection" branch — stable identity
214
- // keeps the merge memo consistent.
215
- const EMPTY_DOCS: DocPageEntry[] = [];
216
-
217
- // ---------------------------------------------------------------------------
218
- // Back-compat: loadNavSourceDocs (sidebar + header consumers)
219
- // ---------------------------------------------------------------------------
220
-
221
- /**
222
- * Per-page nav source for the desktop sidebar and mobile SidebarToggle.
223
- *
224
- * Thin adapter over {@link resolveNavSource} with the sidebar's filter options
225
- * (`applyDefaultLocaleOnlyFilter`, `keepUnlisted`). Returns `navDocs` already
226
- * filtered so callers pass it straight to `buildSidebarForSection` without a
227
- * fresh `.filter(isNavVisible)` that would defeat the nav-tree fast-path.
228
- */
229
- export function loadNavSourceDocs(
230
- lang: Locale,
231
- currentVersion: string | undefined,
232
- ): NavSourceDocs {
233
- return resolveNavSource(lang, currentVersion, {
234
- applyDefaultLocaleOnlyFilter: true,
235
- keepUnlisted: true,
236
- });
237
- }