create-zudo-doc 0.2.21 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/compose.d.ts +6 -3
  2. package/dist/compose.js +6 -4
  3. package/dist/features/image-enlarge.d.ts +22 -12
  4. package/dist/features/image-enlarge.js +23 -191
  5. package/dist/scaffold.d.ts +11 -0
  6. package/dist/scaffold.js +51 -24
  7. package/dist/settings-gen.js +4 -0
  8. package/dist/zfb-config-gen.d.ts +16 -10
  9. package/dist/zfb-config-gen.js +34 -239
  10. package/package.json +1 -1
  11. package/templates/base/pages/_mdx-components.ts +64 -185
  12. package/templates/base/pages/index.tsx +1 -1
  13. package/templates/base/pages/lib/_body-end-islands.tsx +6 -13
  14. package/templates/base/pages/lib/_category-nav.tsx +30 -115
  15. package/templates/base/pages/lib/_category-tree-nav.tsx +38 -65
  16. package/templates/base/pages/lib/_compose-meta-title.ts +2 -6
  17. package/templates/base/pages/lib/_doc-body-end.tsx +3 -35
  18. package/templates/base/pages/lib/_doc-content-header.tsx +10 -111
  19. package/templates/base/pages/lib/_doc-history-area.tsx +19 -211
  20. package/templates/base/pages/lib/_doc-metainfo-area.tsx +10 -113
  21. package/templates/base/pages/lib/_doc-page-renderer.tsx +22 -183
  22. package/templates/base/pages/lib/_doc-page-shell.tsx +17 -251
  23. package/templates/base/pages/lib/_doc-pager.tsx +4 -74
  24. package/templates/base/pages/lib/_doc-route-entries.ts +43 -177
  25. package/templates/base/pages/lib/_doc-route-paths.ts +16 -101
  26. package/templates/base/pages/lib/_doc-tags-area.tsx +14 -77
  27. package/templates/base/pages/lib/_extract-headings.ts +21 -295
  28. package/templates/base/pages/lib/_footer-with-defaults.tsx +37 -225
  29. package/templates/base/pages/lib/_frontmatter-preview-data.ts +5 -31
  30. package/templates/base/pages/lib/_head-with-defaults.tsx +13 -138
  31. package/templates/base/pages/lib/_header-with-defaults.tsx +24 -324
  32. package/templates/base/pages/lib/_inline-version-switcher.tsx +16 -78
  33. package/templates/base/pages/lib/_math-block.tsx +4 -63
  34. package/templates/base/pages/lib/_nav-data-prep.ts +32 -51
  35. package/templates/base/pages/lib/_nav-source-cache.ts +12 -57
  36. package/templates/base/pages/lib/_nav-source-docs.ts +33 -233
  37. package/templates/base/pages/lib/_search-widget-script.ts +2 -470
  38. package/templates/base/pages/lib/_search-widget.tsx +9 -195
  39. package/templates/base/pages/lib/_sidebar-prepaint.tsx +6 -59
  40. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +16 -118
  41. package/templates/base/pages/lib/_site-tree-nav.tsx +29 -80
  42. package/templates/base/pages/lib/doc-page-props.ts +26 -44
  43. package/templates/base/pages/lib/locale-merge.ts +32 -145
  44. package/templates/base/pages/lib/route-enumerators.ts +52 -286
  45. package/templates/base/src/components/ai-chat-modal.tsx +9 -8
  46. package/templates/base/src/components/content/code-group.tsx +3 -76
  47. package/templates/base/src/components/content/content-admonition.tsx +4 -56
  48. package/templates/base/src/components/doc-history.tsx +9 -8
  49. package/templates/base/src/components/image-enlarge.tsx +11 -8
  50. package/templates/base/src/components/sidebar-toggle.tsx +6 -170
  51. package/templates/base/src/components/sidebar-tree.tsx +6 -548
  52. package/templates/base/src/config/color-scheme-utils.ts +34 -158
  53. package/templates/base/src/config/i18n.ts +9 -0
  54. package/templates/base/src/config/settings-types.ts +34 -172
  55. package/templates/base/src/config/z-index-tokens.ts +5 -4
  56. package/templates/base/src/styles/global.css +40 -587
  57. package/templates/base/src/utils/base.ts +1 -1
  58. package/templates/base/src/utils/docs.ts +47 -16
  59. package/templates/base/src/utils/github.ts +12 -9
  60. package/templates/base/src/utils/nav-scope.ts +13 -42
  61. package/templates/base/src/utils/sidebar.ts +18 -86
  62. package/templates/base/src/utils/slug.ts +10 -53
  63. package/templates/base/src/utils/smart-break.tsx +12 -120
  64. package/templates/base/src/utils/tags.ts +25 -68
  65. package/templates/features/bodyFootUtil/files/src/utils/github.ts +12 -9
  66. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +13 -39
  67. package/templates/features/docHistory/files/src/components/doc-history.tsx +8 -636
  68. package/templates/features/docHistory/files/src/types/doc-history.ts +7 -23
  69. package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +35 -201
  70. package/templates/features/i18n/files/pages/[locale]/index.tsx +1 -1
  71. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +8 -269
  72. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +6 -99
  73. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +67 -515
  74. package/templates/features/versioning/files/pages/lib/_versions-page.tsx +21 -73
  75. package/templates/base/pages/404.tsx +0 -61
  76. package/templates/base/pages/robots.txt.tsx +0 -29
  77. package/templates/base/pages/sitemap.xml.tsx +0 -59
  78. package/templates/base/plugins/connect-adapter.mjs +0 -169
  79. package/templates/base/plugins/copy-public-plugin.mjs +0 -58
  80. package/templates/base/plugins/search-index-plugin.mjs +0 -66
  81. package/templates/base/scripts/gen-z-index.mjs +0 -157
  82. package/templates/base/src/components/mermaid-enlarge.tsx +0 -490
  83. package/templates/base/src/components/site-tree-nav.tsx +0 -220
  84. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +0 -47
  85. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +0 -111
  86. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +0 -59
  87. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +0 -39
  88. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +0 -43
  89. package/templates/features/docTags/files/pages/docs/tags/index.tsx +0 -20
  90. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +0 -93
  91. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +0 -48
  92. package/templates/features/versioning/files/pages/docs/versions.tsx +0 -20
@@ -1,64 +1,11 @@
1
1
  /** @jsxRuntime automatic */
2
2
  /** @jsxImportSource preact */
3
- // Shared afterSidebar block for doc pages.
4
- //
5
- // Extracted from the four doc-route render bodies (pages/docs/[...slug].tsx,
6
- // pages/[locale]/docs/[...slug].tsx, pages/v/[version]/docs/[...slug].tsx,
7
- // pages/v/[version]/[locale]/docs/[...slug].tsx) where the block was
8
- // duplicated verbatim across all four routes.
9
- //
10
- // Contains:
11
- // 1. A pre-paint inline <script> that restores the persisted sidebar
12
- // visibility flag from localStorage before first paint (avoids flash).
13
- // 2. A `DesktopSidebarToggle` Island that mounts on "load".
14
- //
15
- // Pattern mirrors `_body-end-islands.tsx`: the file imports the real
16
- // component so zfb's island scanner walks page → helper → DesktopSidebarToggle
17
- // and registers the constructor in the manifest. The Island call's return type
18
- // is widened to `unknown` (JSX shim gap) so call-sites cast through
19
- // `as unknown as VNode` at the boundary.
20
-
21
- import type { VNode, JSX } from "preact";
22
- import { Island } from "@takazudo/zfb";
3
+ // Host thin-stub see @takazudo/zudo-doc/sidebar-prepaint (epic #2344, S5).
23
4
  import { settings } from "@/config/settings";
24
- import DesktopSidebarToggle from "@/components/desktop-sidebar-toggle";
25
-
26
- /** Props for {@link SidebarPrepaint}. */
27
- export interface SidebarPrepaintProps {
28
- /**
29
- * Mirrors the page's `hide_sidebar: true` frontmatter. When true the desktop
30
- * toggle is skipped — there is no visible sidebar for it to collapse.
31
- */
32
- hideSidebar?: boolean;
33
- }
5
+ import { createSidebarPrepaint } from "@takazudo/zudo-doc/sidebar-prepaint";
34
6
 
35
- /**
36
- * The `afterSidebar` slot content shared by all four doc-route page components.
37
- *
38
- * Returns the pre-paint localStorage script + `DesktopSidebarToggle` Island
39
- * when `settings.sidebarToggle` is enabled AND the page actually shows a
40
- * sidebar; returns `undefined` when the toggle is disabled or the page hides
41
- * the sidebar (matching the conditional the route files used inline).
42
- */
43
- export function SidebarPrepaint({
44
- hideSidebar,
45
- }: SidebarPrepaintProps): JSX.Element | undefined {
46
- if (!settings.sidebarToggle || hideSidebar) return undefined;
7
+ export type { SidebarPrepaintProps } from "@takazudo/zudo-doc/sidebar-prepaint";
47
8
 
48
- return (
49
- <>
50
- {/* Pre-paint inline script: restore persisted sidebar visibility to
51
- <html data-sidebar-hidden> before first paint to avoid flash.
52
- Rendered only when the toggle is enabled and the page shows a
53
- sidebar; the attribute is only set when localStorage says "false"
54
- so the default (visible) needs no attribute and causes no layout shift. */}
55
- <script dangerouslySetInnerHTML={{
56
- __html: `(function(){try{if(localStorage.getItem('zudo-doc-sidebar-visible')==='false'){document.documentElement.setAttribute('data-sidebar-hidden','');}}catch(e){}})();`,
57
- }} />
58
- {Island({
59
- when: "load",
60
- children: <DesktopSidebarToggle />,
61
- }) as unknown as VNode}
62
- </>
63
- );
64
- }
9
+ export const SidebarPrepaint = createSidebarPrepaint({
10
+ sidebarToggle: Boolean(settings.sidebarToggle),
11
+ });
@@ -1,41 +1,8 @@
1
1
  /** @jsxRuntime automatic */
2
2
  /** @jsxImportSource preact */
3
- // Locale-/version-aware Sidebar wrapper for the zfb doc pages.
4
- //
5
- // Sidebar data-prep utilities — builds root-menu items from
6
- // settings.headerNav, loads the locale's docs collection (with EN
7
- // fallback for non-default locales), builds the nav tree for the active
8
- // section, optionally remaps hrefs for versioned routes, and feeds the
9
- // result into the host's <SidebarTree> Preact island.
10
- //
11
- // Why this wrapper exists: the data prep is host-only (it imports
12
- // @/config/* and @/utils/*), so it cannot live in the published v2
13
- // package. Without this wrapper the zfb doc pages fall through to a
14
- // <SidebarTree nodes={[]} /> default and the SSG output emits an empty
15
- // `<div data-zfb-island="SidebarTree" data-when="load"></div>` marker.
16
-
17
- import type { JSX } from "preact";
18
- // `<Island>` wraps `<SidebarTree>` directly here (rather than going through
19
- // the v2 `<Sidebar>` shell with `treeComponent`) so the zfb island bundle's
20
- // hydrate pass targets the actual stateful tree component. Mirrors the
21
- // mobile `<SidebarToggle>` shape in `pages/lib/_header-with-defaults.tsx`:
22
- // the hydration target owns its own data props directly so they ride the
23
- // SSR → hydrate boundary inside the Island marker's `data-props` attribute.
24
- //
25
- // Background: zfb's `Island.captureSerializableProps` runs `JSON.stringify`
26
- // on the wrapped component's own props bag, which silently drops function
27
- // values. With the previous `<Sidebar treeComponent={SidebarTree} ...>`
28
- // shape the `treeComponent` function was dropped during serialisation, so
29
- // at hydration the v2 Sidebar shell mounted with `treeComponent=undefined`,
30
- // returned `null`, and Preact's `hydrate(null, element)` left the SSR-
31
- // rendered tree DOM in place WITHOUT attaching the input's `onChange`
32
- // handler — typing into the filter input had no DOM effect.
33
- // zudolab/zudo-doc#1459 (Wave 1 #1445 wired the input but not the wiring
34
- // path; this wave routes the hydration target so the wiring actually
35
- // reaches the rendered tree).
36
- import { Island } from "@takazudo/zfb";
37
- import SidebarTree from "@/components/sidebar-tree";
3
+ // Host thin-stub see @takazudo/zudo-doc/sidebar-with-defaults (epic #2344, S5).
38
4
  import { defaultLocale, locales, t, type Locale } from "@/config/i18n";
5
+ import { createSidebarWithDefaults } from "@takazudo/zudo-doc/sidebar-with-defaults";
39
6
  import {
40
7
  buildRootMenuItems,
41
8
  buildLocaleLinksForNav,
@@ -43,86 +10,17 @@ import {
43
10
  getThemeDefaultMode,
44
11
  } from "./_nav-data-prep";
45
12
 
46
- export interface SidebarWithDefaultsProps {
47
- /** Slug of the active doc page, used to highlight the current entry. */
48
- currentSlug?: string;
49
- /** Active locale; defaults to the configured defaultLocale. */
50
- lang?: Locale;
51
- /** Header-nav category matcher used to scope the tree (e.g. "guides"). */
52
- navSection?: string;
53
- /** Active version slug, when rendering inside `/v/{version}/...`. */
54
- currentVersion?: string;
55
- /**
56
- * Current page URL path used to build the locale-switcher links shown in
57
- * the mobile sidebar footer. The Astro template read this from
58
- * `Astro.url.pathname`; in zfb the page module passes it explicitly.
59
- */
60
- currentPath?: string;
61
- }
62
-
63
- /**
64
- * Default-bearing host wrapper that performs sidebar data prep, then wraps
65
- * the project's `<SidebarTree>`
66
- * Preact island in `<Island when="load">` so the SSG output ships a
67
- * populated `<div data-zfb-island="SidebarTree" data-when="load">…tree…
68
- * </div>` marker for the hydration runtime to pick up.
69
- *
70
- * The v2 `<Sidebar>` shell is intentionally NOT used as the hydration
71
- * target here. Its `treeComponent` prop is a function, and zfb's
72
- * `Island.captureSerializableProps` drops function values during
73
- * `JSON.stringify`, so a `<Sidebar treeComponent={SidebarTree} ...>`
74
- * island would hydrate with `treeComponent=undefined` and the shell
75
- * would return `null`, silently breaking the filter input's hydration
76
- * (zudolab/zudo-doc#1459). Wrapping `<SidebarTree>` directly mirrors the
77
- * mobile `<SidebarToggle>` shape (see `_header-with-defaults.tsx`) and
78
- * keeps all data props serializable.
79
- */
80
- export function SidebarWithDefaults(
81
- props: SidebarWithDefaultsProps,
82
- ): JSX.Element {
83
- const {
84
- currentSlug,
85
- lang = defaultLocale,
86
- navSection,
87
- currentVersion,
88
- currentPath = "",
89
- } = props;
90
-
91
- // Root-menu items, sidebar nodes, locale links, and theme mode — all
92
- // delegated to the shared _nav-data-prep helpers so header and sidebar
93
- // wrappers stay in sync without duplicating the logic.
94
- const rootMenuItems = buildRootMenuItems(lang, currentVersion);
95
-
96
- const backToMenuLabel = navSection ? t("nav.backToMenu", lang) : undefined;
97
-
98
- // emptyWhenUnsectioned=false: the desktop sidebar falls back to the FULL
99
- // tree for pages whose slug matches no headerNav categoryMatch (legacy
100
- // behavior) — only the header's mobile drawer collapses to root menu.
101
- const nodes = buildSidebarNodes(lang, navSection, currentVersion, false);
102
-
103
- // Locale-switcher links are only meaningful when more than one locale is
104
- // configured — matches the Astro template's guard.
105
- const localeLinks = buildLocaleLinksForNav(currentPath, lang, locales.length);
106
-
107
- // Wrap <SidebarTree> directly in <Island when="load">. SSR emits the
108
- // `data-zfb-island="SidebarTree"` marker around the rendered tree, with
109
- // all data props serialised into `data-props` (every prop is plain data:
110
- // arrays of objects + strings). At hydration the runtime finds the
111
- // marker, looks up "SidebarTree" in the islands manifest (registered via
112
- // the host's `"use client"` directive on `src/components/sidebar-tree.tsx`),
113
- // and mounts the real component in-place — re-attaching the filter
114
- // input's `onChange` handler to the existing SSR DOM.
115
- return Island({
116
- when: "load",
117
- children: (
118
- <SidebarTree
119
- nodes={nodes}
120
- currentSlug={currentSlug}
121
- rootMenuItems={rootMenuItems}
122
- backToMenuLabel={backToMenuLabel}
123
- localeLinks={localeLinks}
124
- themeDefaultMode={getThemeDefaultMode()}
125
- />
126
- ),
127
- }) as unknown as JSX.Element;
128
- }
13
+ export type { SidebarWithDefaultsProps } from "@takazudo/zudo-doc/sidebar-with-defaults";
14
+
15
+ export const SidebarWithDefaults = createSidebarWithDefaults({
16
+ defaultLocale,
17
+ localeCount: locales.length,
18
+ buildRootMenuItems: (lang, currentVersion) =>
19
+ buildRootMenuItems(lang as Locale, currentVersion),
20
+ buildLocaleLinksForNav: (currentPath, lang, localeCount) =>
21
+ buildLocaleLinksForNav(currentPath, lang as Locale, localeCount),
22
+ buildSidebarNodes: (lang, navSection, currentVersion, emptyWhenUnsectioned) =>
23
+ buildSidebarNodes(lang as Locale, navSection, currentVersion, emptyWhenUnsectioned),
24
+ getThemeDefaultMode,
25
+ t: (key, lang) => t(key as Parameters<typeof t>[0], lang as Locale),
26
+ });
@@ -1,88 +1,37 @@
1
1
  /** @jsxRuntime automatic */
2
2
  /** @jsxImportSource preact */
3
- // Host-side MDX wrapper for <SiteTreeNav /> and <SiteTreeNavDemo />.
3
+ // Thin stub — site-tree-nav moved to the package (epic #2344, S8).
4
+ // Calls `createSiteTreeNavWrapper(deps)` from @takazudo/zudo-doc/site-tree-nav
5
+ // with host singletons injected, then re-exports the resulting component so
6
+ // all existing call sites continue to work unchanged.
4
7
  //
5
- // Both <SiteTreeNav> and <SiteTreeNavDemo> MDX tags are mapped to this
6
- // wrapper, which loads the full site nav tree and renders the interactive
7
- // SiteTreeNav island (refs #1453):
8
- //
9
- // 1. Load the full docs collection for the active locale.
10
- // 2. Build nav tree via buildNavTree().
11
- // 3. Group satellite nodes via groupSatelliteNodes().
12
- // 4. Wrap the interactive SiteTreeNav in Island({when:"idle"}) so the MDX
13
- // page gets the collapsible grid rendered at
14
- // /docs/components/site-tree-nav/ (refs #1453/#1442).
15
- //
16
- // All data access is synchronous (ADR-004 zfb content snapshot contract).
17
- // The `lang` prop is injected by createMdxComponents() in
18
- // pages/_mdx-components.ts so locale routes get locale-aware nav data.
19
- //
20
- // categoryIgnore defaults to ["inbox", "develop"] — matching the index page
21
- // and SiteTreeNavDemo defaults.
8
+ // IMPORTANT: Island({when:"idle"}) is PRESERVED in the package factory
9
+ // the island mounts after the page is idle for performance (refs #1453).
22
10
 
23
- import type { JSX } from "preact";
24
- import { Island } from "@takazudo/zfb";
25
- import SiteTreeNav from "@/components/site-tree-nav";
26
- import {
27
- buildNavTree,
28
- groupSatelliteNodes,
29
- } from "@/utils/docs";
30
- import { defaultLocale, type Locale } from "@/config/i18n";
11
+ import { createSiteTreeNavWrapper } from "@takazudo/zudo-doc/site-tree-nav";
12
+ import type { SidebarNavNode } from "@takazudo/zudo-doc/sidebar/types";
13
+ import { buildNavTree, groupSatelliteNodes } from "@/utils/docs";
14
+ import { defaultLocale } from "@/config/i18n";
31
15
  import { getCategoryOrder } from "@/utils/nav-scope";
32
16
  import { resolveNavSource } from "./_nav-source-docs";
33
17
 
34
- export interface SiteTreeNavWrapperProps {
35
- /**
36
- * Active locale. Injected via createMdxComponents() closure.
37
- * Defaults to defaultLocale when not provided.
38
- */
39
- lang?: Locale | string;
40
- /**
41
- * Optional aria-label for the wrapping <nav> element.
42
- * Forwarded to the v2 SiteTreeNavDemo component.
43
- */
44
- ariaLabel?: string;
45
- }
46
-
47
- /**
48
- * MDX wrapper shared by both <SiteTreeNav> and <SiteTreeNavDemo> tags.
49
- *
50
- * Builds the full site nav tree and renders it via the interactive SiteTreeNav
51
- * island (wrapped in Island({when:"idle"})) — restoring byte-parity with the
52
- * Astro reference at /docs/components/site-tree-nav/ (refs #1453/#1442).
53
- *
54
- * The island renders the collapsible multi-column grid the reference shows.
55
- * SiteTreeNavDemo (static <details> list) is no longer used for MDX content.
56
- *
57
- * Returns null when the tree is empty after filtering.
58
- */
59
- export function SiteTreeNavWrapper({
60
- lang = defaultLocale,
61
- ariaLabel,
62
- }: SiteTreeNavWrapperProps): JSX.Element | null {
63
- const locale = lang as Locale;
64
-
65
- // SiteTreeNav mirrors the route nav: applies the defaultLocaleOnly filter for
66
- // non-default locales (same options the sidebar/route enumeration use).
67
- const { navDocs, categoryMeta } = resolveNavSource(locale, undefined, {
68
- applyDefaultLocaleOnlyFilter: true,
69
- keepUnlisted: true,
70
- });
71
- const tree = buildNavTree(navDocs, locale, categoryMeta);
72
- const categoryOrder = getCategoryOrder();
73
- const groupedTree = groupSatelliteNodes(tree, categoryOrder);
74
-
75
- if (groupedTree.length === 0) return null;
18
+ export type { SiteTreeNavWrapperProps } from "@takazudo/zudo-doc/site-tree-nav";
76
19
 
77
- return Island({
78
- when: "idle",
79
- children: (
80
- <SiteTreeNav
81
- tree={groupedTree}
82
- categoryOrder={categoryOrder}
83
- categoryIgnore={["inbox", "develop"]}
84
- ariaLabel={ariaLabel}
85
- />
86
- ),
87
- }) as unknown as JSX.Element;
88
- }
20
+ export const SiteTreeNavWrapper = createSiteTreeNavWrapper({
21
+ defaultLocale,
22
+ resolveNavSource: resolveNavSource as (
23
+ lang: string,
24
+ currentVersion: string | undefined,
25
+ options?: { applyDefaultLocaleOnlyFilter?: boolean; keepUnlisted?: boolean },
26
+ ) => import("@takazudo/zudo-doc/site-tree-nav").SiteTreeNavSource,
27
+ buildNavTree: buildNavTree as (
28
+ docs: unknown[],
29
+ locale: string,
30
+ categoryMeta: Map<string, unknown>,
31
+ ) => SidebarNavNode[],
32
+ groupSatelliteNodes: groupSatelliteNodes as (
33
+ tree: SidebarNavNode[],
34
+ prefixes: string[],
35
+ ) => SidebarNavNode[],
36
+ getCategoryOrder,
37
+ });
@@ -1,48 +1,30 @@
1
- // Shared discriminated-union props types for all 4 doc-route pages.
1
+ // Thin stub types moved to the package (epic #2344, S6).
2
+ // Re-exports the shared discriminated-union props types for all 4 doc-route
3
+ // pages from @takazudo/zudo-doc/doc-page-props.
2
4
  //
3
- // Each route file has its own DocPageProps interface that extends one of these
4
- // two branches by merging in route-specific fields (locale, version, etc.).
5
- // Discriminating on `kind` lets TypeScript narrow `entry` / `autoIndex` to
6
- // non-null inside each branch — eliminating all `entry!` assertions.
5
+ // S5 and downstream (S7) consumers import `DocPageBaseProps`, `DocPageEntry`,
6
+ // `AutoIndexNode`, `DocPageEntryProps`, `DocPageAutoIndexProps` from this path
7
+ // unchanged all are re-exported below.
7
8
 
8
- import type { CollectionEntry } from "zfb/content";
9
- import type { DocsEntry } from "@/types/docs-entry";
10
- import type { NavNode, BreadcrumbItem } from "@/utils/docs";
11
- import type { extractHeadings } from "./_extract-headings";
9
+ export type {
10
+ DocNavNode,
11
+ DocPageEntry,
12
+ DocPageFrontmatter,
13
+ AutoIndexNode,
14
+ DocPageEntryProps,
15
+ DocPageAutoIndexProps,
16
+ DocPageBaseProps,
17
+ HeadingItem,
18
+ } from "@takazudo/zudo-doc/doc-page-props";
12
19
 
13
- export interface DocPageEntry extends DocsEntry {
14
- /** zfb content renderer. */
15
- Content: CollectionEntry<unknown>["Content"];
16
- /** zfb module specifier (for Content bridge). */
17
- module_specifier: string;
18
- }
20
+ // Backward-compatible alias: downstream callers that import `NavNode` from
21
+ // this path (e.g. _doc-route-entries.ts, route-enumerators.ts) continue to
22
+ // resolve. `DocNavNode` is structurally identical to the host's `NavNode` from
23
+ // src/utils/docs.ts (same required fields, same optional fields).
24
+ export type { DocNavNode as NavNode } from "@takazudo/zudo-doc/doc-page-props";
19
25
 
20
- export interface AutoIndexNode extends NavNode {
21
- children: NavNode[];
22
- }
23
-
24
- /** Shared fields present in every doc-page route. */
25
- interface DocPagePropsBase {
26
- breadcrumbs: BreadcrumbItem[];
27
- prev: NavNode | null;
28
- next: NavNode | null;
29
- /** Depth-2/3/4 headings extracted from the MDX body, for SSG TOC links. */
30
- headings: ReturnType<typeof extractHeadings>;
31
- }
32
-
33
- /** Branch: a real content entry. `autoIndex` is absent. */
34
- export interface DocPageEntryProps extends DocPagePropsBase {
35
- kind: "entry";
36
- entry: DocPageEntry;
37
- autoIndex?: undefined;
38
- }
39
-
40
- /** Branch: an auto-generated category index. `entry` is absent. */
41
- export interface DocPageAutoIndexProps extends DocPagePropsBase {
42
- kind: "autoIndex";
43
- autoIndex: AutoIndexNode;
44
- entry?: undefined;
45
- }
46
-
47
- /** Discriminated union for the `kind` prop. Narrow via `props.kind === "entry"`. */
48
- export type DocPageBaseProps = DocPageEntryProps | DocPageAutoIndexProps;
26
+ // Backward-compatible alias: `BreadcrumbItem` was previously imported from
27
+ // @/utils/docs in some files. The package's `DocPageBaseProps` uses
28
+ // `BreadcrumbItem` from @takazudo/zudo-doc/breadcrumb/types — re-export it
29
+ // here so callers importing from this path still resolve.
30
+ export type { BreadcrumbItem } from "@takazudo/zudo-doc/breadcrumb";
@@ -1,159 +1,46 @@
1
- // Shared utility for merging locale docs with base-locale fallbacks.
1
+ // Thin stub locale-merge moved to the package (epic #2344, S6).
2
+ // Re-exports the pure merge functions from @takazudo/zudo-doc/locale-merge
3
+ // and provides backward-compatible wrappers that inject the host singletons.
2
4
  //
3
- // Used by route enumerators, nav helpers, and locale page modules to implement
4
- // the locale-first + base-fallback merge strategy.
5
+ // `mergeLocaleDocs` is the main export: now the package function accepts an
6
+ // optional `isDefaultLocaleOnlyPath` injection in its options object. The host
7
+ // wrapper below injects the host's `isDefaultLocaleOnlyPath` automatically
8
+ // so all existing call sites — which pass `applyDefaultLocaleOnlyFilter: true`
9
+ // without explicitly passing the predicate — continue to work unchanged.
5
10
  //
6
- // Strategy:
7
- // 1. Caller loads the locale and base doc arrays (via loadDocs / bridgeEntries).
8
- // 2. mergeLocaleDocs({ baseDocs, localeDocs, ... }) builds the merged list.
9
- // 3. Locale docs take priority; base docs fill in slugs not present in locale.
10
- // 4. Optionally excludes base docs that are default-locale-only paths.
11
- // 5. Optionally filters out unlisted pages (for tag enumeration).
12
- //
13
- // WHY caller loads, helper merges:
14
- // Collection naming varies per context (regular docs, versioned, locale-versioned),
15
- // and the bridging (loadDocs vs. bridgeEntries) also varies per call site. Keeping
16
- // the helper as a pure merge over pre-loaded arrays avoids encoding that logic here
17
- // and lets each call site retain its own load pattern.
18
- //
19
- // This is a zfb-only module (synchronous, uses pre-loaded collections).
20
-
11
+ // `mergeCategoryMeta` is kept as a host-only helper (it wraps `loadCategoryMeta`
12
+ // from @/utils/docs which is a host import). The package's `nav-source-docs`
13
+ // factory now owns the merged-category-meta caching; this re-export is kept
14
+ // for any remaining host call sites.
15
+
16
+ export type {
17
+ MergeDocsEntry,
18
+ MergeLocaleDocsOptions,
19
+ MergeLocaleDocsResult,
20
+ } from "@takazudo/zudo-doc/locale-merge";
21
+ import { mergeLocaleDocs as _mergeLocaleDocs } from "@takazudo/zudo-doc/locale-merge";
22
+ import type { MergeLocaleDocsOptions, MergeLocaleDocsResult, MergeDocsEntry } from "@takazudo/zudo-doc/locale-merge";
21
23
  import { isDefaultLocaleOnlyPath } from "@/utils/base";
22
- import type { DocsEntry } from "@/types/docs-entry";
23
-
24
- // ---------------------------------------------------------------------------
25
- // mergeLocaleDocs
26
- // ---------------------------------------------------------------------------
27
-
28
- /**
29
- * Options for mergeLocaleDocs.
30
- *
31
- * The generic parameter `T` allows callers to pass arrays of a DocsEntry
32
- * subtype (e.g. `DocPageEntry[]`) and get back results of the same subtype,
33
- * eliminating the need for `as unknown as` casts at call sites.
34
- */
35
- export interface MergeLocaleDocsOptions<T extends DocsEntry = DocsEntry> {
36
- /** Pre-loaded base (EN/default-locale) docs array, already draft-filtered. */
37
- baseDocs: T[];
38
- /** Pre-loaded locale-specific docs array, already draft-filtered. */
39
- localeDocs: T[];
40
- /**
41
- * When true, base docs whose path matches a `defaultLocaleOnlyPrefixes`
42
- * entry are excluded from the merge result. This matches the behavior of the
43
- * inline copies in route-enumerators, nav helpers, and page paths() sections.
44
- *
45
- * Pass true for any call site that enumerates routes or builds nav trees for
46
- * non-default locales (where showing a default-locale-only page to a locale
47
- * user would be incorrect). Pass false (or omit) for call sites where the
48
- * filtered paths are harmless — e.g. category nav cards, taglist columns.
49
- *
50
- * @default false
51
- */
52
- applyDefaultLocaleOnlyFilter?: boolean;
53
- /**
54
- * Controls whether `unlisted: true` docs survive the merge.
55
- *
56
- * - `true` — unlisted docs are RETAINED (locale + base). Route/sitemap
57
- * enumeration uses this: unlisted pages have real HTML files, so
58
- * they must be built; nav callers rely on `isNavVisible`
59
- * downstream to hide them from the tree.
60
- * - `false` (default) — unlisted docs are DROPPED from both locale and base.
61
- * Tag aggregation uses this so hidden pages don't contribute tags.
62
- *
63
- * @default false
64
- */
65
- keepUnlisted?: boolean;
66
- }
67
-
68
- /**
69
- * Result of mergeLocaleDocs.
70
- *
71
- * **Array identity:** Each call returns a fresh array — no module-level
72
- * memoization is applied. If a caller keys a cache on the docs array identity
73
- * (e.g. a nav-tree cache), it should memoize the result itself rather than
74
- * relying on reference stability from this helper.
75
- *
76
- * This contract is intentionally unchanged. The identity-stable layer for
77
- * nav-source arrays lives ABOVE this helper: `pages/lib/_nav-source-docs.ts`
78
- * (`resolveNavSource` / `resolveVersionedLocaleSource`) memoizes the merge
79
- * result on the snapshot-anchored input arrays + option signature (see
80
- * `pages/lib/_nav-source-cache.ts`), so `mergeLocaleDocs` itself stays a pure,
81
- * memo-free function (#1902).
82
- *
83
- * The generic `T` mirrors the parameter on {@link MergeLocaleDocsOptions} so
84
- * the returned `docs` array preserves the subtype of the input arrays.
85
- */
86
- export interface MergeLocaleDocsResult<T extends DocsEntry = DocsEntry> {
87
- /**
88
- * Merged doc array: locale docs first, followed by base docs for slugs not
89
- * present in the locale collection (and not excluded by filter options).
90
- */
91
- docs: T[];
92
- /**
93
- * Set of slugs that came from the locale collection.
94
- * Useful for callers that need to determine whether a page is a fallback
95
- * (i.e. `isFallback = !localeSlugSet.has(slug)`).
96
- */
97
- localeSlugSet: ReadonlySet<string>;
98
- }
24
+ import { loadCategoryMeta } from "@/utils/docs";
25
+ import type { CategoryMeta } from "@/utils/docs";
99
26
 
100
27
  /**
101
28
  * Merge locale docs with base-locale fallbacks.
102
29
  *
103
- * Locale docs take priority; base docs fill in slugs not covered by the locale
104
- * collection. Optionally excludes default-locale-only paths and/or unlisted pages.
105
- *
106
- * **Slug keying**: slug identity uses `d.data.slug ?? d.id`. Since `loadDocs`
107
- * already strips the `/index` suffix (via `stripIndexSuffix` in `_data.ts`),
108
- * this key is consistent across all call sites regardless of whether they
109
- * loaded docs via `loadDocs` or `bridgeEntries`.
110
- *
111
- * **Array identity**: returns a fresh array on each call — see
112
- * {@link MergeLocaleDocsResult} for caching guidance.
30
+ * Thin host wrapper around the package's `mergeLocaleDocs` that injects
31
+ * `isDefaultLocaleOnlyPath` from `@/utils/base` automatically. Existing call
32
+ * sites that pass `applyDefaultLocaleOnlyFilter: true` continue to work
33
+ * without change the predicate is now injected rather than module-imported.
113
34
  */
114
- export function mergeLocaleDocs<T extends DocsEntry = DocsEntry>(
115
- options: MergeLocaleDocsOptions<T>,
35
+ export function mergeLocaleDocs<T extends MergeDocsEntry = MergeDocsEntry>(
36
+ options: Omit<MergeLocaleDocsOptions<T>, "isDefaultLocaleOnlyPath">,
116
37
  ): MergeLocaleDocsResult<T> {
117
- const {
118
- baseDocs,
119
- localeDocs,
120
- applyDefaultLocaleOnlyFilter = false,
121
- keepUnlisted = false,
122
- } = options;
123
-
124
- const filteredLocale = keepUnlisted
125
- ? localeDocs
126
- : localeDocs.filter((d) => !d.data.unlisted);
127
-
128
- const filteredBase = keepUnlisted
129
- ? baseDocs
130
- : baseDocs.filter((d) => !d.data.unlisted);
131
-
132
- const localeSlugSet = new Set(filteredLocale.map((d) => d.data.slug ?? d.id));
133
-
134
- let fallbackDocs = filteredBase.filter(
135
- (d) => !localeSlugSet.has(d.data.slug ?? d.id),
136
- );
137
-
138
- if (applyDefaultLocaleOnlyFilter) {
139
- fallbackDocs = fallbackDocs.filter(
140
- (d) => !isDefaultLocaleOnlyPath(`/docs/${d.data.slug ?? d.id}`),
141
- );
142
- }
143
-
144
- return {
145
- docs: [...filteredLocale, ...fallbackDocs],
146
- localeSlugSet,
147
- };
38
+ return _mergeLocaleDocs({
39
+ ...options,
40
+ isDefaultLocaleOnlyPath,
41
+ });
148
42
  }
149
43
 
150
- // ---------------------------------------------------------------------------
151
- // mergeCategoryMeta
152
- // ---------------------------------------------------------------------------
153
-
154
- import { loadCategoryMeta } from "@/utils/docs";
155
- import type { CategoryMeta } from "@/utils/docs";
156
-
157
44
  /**
158
45
  * Merge category metadata for a locale: base metadata first, locale overrides
159
46
  * win on overlapping keys.