create-zudo-doc 0.1.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 (212) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/bin/create-zudo-doc.js +2 -0
  4. package/dist/api.d.ts +20 -0
  5. package/dist/api.js +13 -0
  6. package/dist/claude-md-gen.d.ts +2 -0
  7. package/dist/claude-md-gen.js +113 -0
  8. package/dist/cli.d.ts +39 -0
  9. package/dist/cli.js +157 -0
  10. package/dist/compose.d.ts +95 -0
  11. package/dist/compose.js +206 -0
  12. package/dist/constants.d.ts +20 -0
  13. package/dist/constants.js +224 -0
  14. package/dist/features/body-foot-util.d.ts +10 -0
  15. package/dist/features/body-foot-util.js +12 -0
  16. package/dist/features/claude-resources.d.ts +2 -0
  17. package/dist/features/claude-resources.js +6 -0
  18. package/dist/features/design-token-panel.d.ts +14 -0
  19. package/dist/features/design-token-panel.js +27 -0
  20. package/dist/features/doc-history.d.ts +9 -0
  21. package/dist/features/doc-history.js +11 -0
  22. package/dist/features/doc-tags.d.ts +19 -0
  23. package/dist/features/doc-tags.js +33 -0
  24. package/dist/features/footer-taglist.d.ts +14 -0
  25. package/dist/features/footer-taglist.js +17 -0
  26. package/dist/features/footer.d.ts +8 -0
  27. package/dist/features/footer.js +10 -0
  28. package/dist/features/i18n.d.ts +22 -0
  29. package/dist/features/i18n.js +41 -0
  30. package/dist/features/image-enlarge.d.ts +11 -0
  31. package/dist/features/image-enlarge.js +13 -0
  32. package/dist/features/index.d.ts +15 -0
  33. package/dist/features/index.js +53 -0
  34. package/dist/features/llms-txt.d.ts +11 -0
  35. package/dist/features/llms-txt.js +13 -0
  36. package/dist/features/search.d.ts +9 -0
  37. package/dist/features/search.js +11 -0
  38. package/dist/features/sidebar-resizer.d.ts +14 -0
  39. package/dist/features/sidebar-resizer.js +16 -0
  40. package/dist/features/sidebar-toggle.d.ts +13 -0
  41. package/dist/features/sidebar-toggle.js +15 -0
  42. package/dist/features/tag-governance.d.ts +14 -0
  43. package/dist/features/tag-governance.js +16 -0
  44. package/dist/features/tauri-dev.d.ts +2 -0
  45. package/dist/features/tauri-dev.js +25 -0
  46. package/dist/features/tauri.d.ts +11 -0
  47. package/dist/features/tauri.js +52 -0
  48. package/dist/features/versioning.d.ts +27 -0
  49. package/dist/features/versioning.js +43 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.js +150 -0
  52. package/dist/preset.d.ts +37 -0
  53. package/dist/preset.js +156 -0
  54. package/dist/prompts.d.ts +32 -0
  55. package/dist/prompts.js +248 -0
  56. package/dist/scaffold.d.ts +4 -0
  57. package/dist/scaffold.js +344 -0
  58. package/dist/settings-gen.d.ts +2 -0
  59. package/dist/settings-gen.js +237 -0
  60. package/dist/utils.d.ts +8 -0
  61. package/dist/utils.js +34 -0
  62. package/dist/zfb-config-gen.d.ts +19 -0
  63. package/dist/zfb-config-gen.js +222 -0
  64. package/package.json +65 -0
  65. package/templates/base/.htmlvalidate.json +5 -0
  66. package/templates/base/.zfb/doc-history-meta.json +1 -0
  67. package/templates/base/pages/404.tsx +55 -0
  68. package/templates/base/pages/_data.ts +179 -0
  69. package/templates/base/pages/_mdx-components.ts +249 -0
  70. package/templates/base/pages/docs/[...slug].tsx +448 -0
  71. package/templates/base/pages/index.tsx +158 -0
  72. package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
  73. package/templates/base/pages/lib/_category-nav.tsx +148 -0
  74. package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
  75. package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
  76. package/templates/base/pages/lib/_details.tsx +30 -0
  77. package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
  78. package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
  79. package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
  80. package/templates/base/pages/lib/_extract-headings.ts +81 -0
  81. package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
  82. package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
  83. package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
  84. package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
  85. package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
  86. package/templates/base/pages/lib/_math-block.tsx +63 -0
  87. package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
  88. package/templates/base/pages/lib/_preset-generator.tsx +81 -0
  89. package/templates/base/pages/lib/_search-widget-script.ts +388 -0
  90. package/templates/base/pages/lib/_search-widget.tsx +196 -0
  91. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
  92. package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
  93. package/templates/base/pages/lib/locale-merge.ts +58 -0
  94. package/templates/base/pages/lib/route-enumerators.ts +302 -0
  95. package/templates/base/pages/sitemap.xml.tsx +51 -0
  96. package/templates/base/plugins/connect-adapter.mjs +144 -0
  97. package/templates/base/plugins/copy-public-plugin.mjs +50 -0
  98. package/templates/base/plugins/search-index-plugin.mjs +54 -0
  99. package/templates/base/scripts/run-b4push.sh +102 -0
  100. package/templates/base/src/components/ai-chat-modal.tsx +15 -0
  101. package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
  102. package/templates/base/src/components/content/component-map.ts +25 -0
  103. package/templates/base/src/components/content/content-blockquote.tsx +16 -0
  104. package/templates/base/src/components/content/content-code.tsx +117 -0
  105. package/templates/base/src/components/content/content-link.tsx +83 -0
  106. package/templates/base/src/components/content/content-ol.tsx +19 -0
  107. package/templates/base/src/components/content/content-paragraph.tsx +10 -0
  108. package/templates/base/src/components/content/content-strong.tsx +16 -0
  109. package/templates/base/src/components/content/content-table.tsx +18 -0
  110. package/templates/base/src/components/content/content-ul.tsx +18 -0
  111. package/templates/base/src/components/content/heading-h2.tsx +26 -0
  112. package/templates/base/src/components/content/heading-h3.tsx +26 -0
  113. package/templates/base/src/components/content/heading-h4.tsx +26 -0
  114. package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
  115. package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
  116. package/templates/base/src/components/doc-history.tsx +18 -0
  117. package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
  118. package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
  119. package/templates/base/src/components/html-preview/preflight.ts +112 -0
  120. package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
  121. package/templates/base/src/components/image-enlarge.tsx +19 -0
  122. package/templates/base/src/components/mobile-toc.tsx +94 -0
  123. package/templates/base/src/components/preset-generator.tsx +14 -0
  124. package/templates/base/src/components/sidebar-toggle.tsx +98 -0
  125. package/templates/base/src/components/sidebar-tree.tsx +543 -0
  126. package/templates/base/src/components/site-tree-nav.tsx +233 -0
  127. package/templates/base/src/components/theme-toggle.tsx +93 -0
  128. package/templates/base/src/components/toc.tsx +63 -0
  129. package/templates/base/src/components/tree-nav-shared.tsx +71 -0
  130. package/templates/base/src/config/color-scheme-utils.ts +182 -0
  131. package/templates/base/src/config/color-schemes.ts +128 -0
  132. package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
  133. package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
  134. package/templates/base/src/config/i18n.ts +225 -0
  135. package/templates/base/src/config/settings-types.ts +162 -0
  136. package/templates/base/src/config/sidebars.ts +66 -0
  137. package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
  138. package/templates/base/src/config/tag-vocabulary.ts +20 -0
  139. package/templates/base/src/hooks/use-active-heading.ts +133 -0
  140. package/templates/base/src/plugins/docs-source-map.ts +103 -0
  141. package/templates/base/src/plugins/hast-utils.ts +10 -0
  142. package/templates/base/src/plugins/rehype-code-title.ts +50 -0
  143. package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
  144. package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
  145. package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
  146. package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
  147. package/templates/base/src/plugins/remark-admonitions.ts +99 -0
  148. package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
  149. package/templates/base/src/plugins/url-utils.ts +4 -0
  150. package/templates/base/src/styles/global.css +1066 -0
  151. package/templates/base/src/types/docs-entry.ts +39 -0
  152. package/templates/base/src/types/heading.ts +5 -0
  153. package/templates/base/src/types/locale.ts +10 -0
  154. package/templates/base/src/utils/base.ts +139 -0
  155. package/templates/base/src/utils/content-files.ts +106 -0
  156. package/templates/base/src/utils/dedent.ts +24 -0
  157. package/templates/base/src/utils/docs.ts +335 -0
  158. package/templates/base/src/utils/git-info.ts +70 -0
  159. package/templates/base/src/utils/github.ts +19 -0
  160. package/templates/base/src/utils/header-right-items.ts +38 -0
  161. package/templates/base/src/utils/nav-scope.ts +63 -0
  162. package/templates/base/src/utils/sidebar.ts +104 -0
  163. package/templates/base/src/utils/slug.ts +10 -0
  164. package/templates/base/src/utils/smart-break.tsx +126 -0
  165. package/templates/base/src/utils/tags.ts +126 -0
  166. package/templates/base/tsconfig.json +36 -0
  167. package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
  168. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
  169. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
  170. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
  171. package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
  172. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
  173. package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
  174. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
  175. package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
  176. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
  177. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
  178. package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
  179. package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
  180. package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
  181. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
  182. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
  183. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
  184. package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
  185. package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
  186. package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
  187. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
  188. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
  189. package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
  190. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
  191. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
  192. package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
  193. package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
  194. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
  195. package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
  196. package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
  197. package/templates/features/tauri/files/src-tauri/build.rs +3 -0
  198. package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
  199. package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
  200. package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
  201. package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
  202. package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
  203. package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
  204. package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
  205. package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
  206. package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
  207. package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
  208. package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
  209. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
  210. package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
  211. package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
  212. package/templates/features/versioning/files/pages/v/[version]/ja/docs/[...slug].tsx +490 -0
@@ -0,0 +1,451 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // Port of src/pages/v/[version]/docs/[...slug].astro → zfb page module.
4
+ //
5
+ // Versioned EN docs route. paths() enumerates one route per (version, slug)
6
+ // combination using the `docs-v-${version.slug}` collection for each version
7
+ // configured in settings.versions.
8
+ //
9
+ // paths() contract (zfb ADR-004 — synchronous):
10
+ // params: { version: string; slug: string[] }
11
+ // props: { entry, autoIndex, version, breadcrumbs, prev, next }
12
+ //
13
+ // Each version renders with its own nav tree (from the version's docsDir
14
+ // category metadata). Prev/next hrefs are pre-resolved to the versioned URL
15
+ // form (e.g. /v/1.0/docs/…) so the component needs no URL computation.
16
+ //
17
+ // Version banner: if version.banner is set ("unmaintained" | "unreleased"),
18
+ // the DocLayoutWithDefaults version-banner prop drives the banner display.
19
+
20
+ import { getCollection } from "zfb/content";
21
+ import type { CollectionEntry } from "zfb/content";
22
+ import type { DocsEntry } from "@/types/docs-entry";
23
+ import { settings } from "@/config/settings";
24
+ import type { VersionConfig } from "@/config/settings";
25
+ import { t } from "@/config/i18n";
26
+ import { docsUrl, versionedDocsUrl } from "@/utils/base";
27
+ import {
28
+ buildNavTree,
29
+ buildBreadcrumbs,
30
+ flattenTree,
31
+ findNode,
32
+ loadCategoryMeta,
33
+ collectAutoIndexNodes,
34
+ isNavVisible,
35
+ type NavNode,
36
+ type BreadcrumbItem,
37
+ } from "@/utils/docs";
38
+ import { getNavSectionForSlug, getNavSubtree } from "@/utils/nav-scope";
39
+ import { toRouteSlug } from "@/utils/slug";
40
+ import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
41
+ import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
42
+ import { NavCardGrid } from "@takazudo/zudo-doc/nav-indexing";
43
+ import { FrontmatterPreview } from "@takazudo/zudo-doc/metainfo";
44
+ import { frontmatterRenderers } from "@/config/frontmatter-preview-renderers";
45
+ // Locale-aware MDX components factory — see `pages/_mdx-components.ts`.
46
+ import { createMdxComponents } from "../../../_mdx-components";
47
+ import type { JSX } from "preact";
48
+ import { bridgeEntries } from "../../../_data";
49
+ import { extractHeadings } from "../../../lib/_extract-headings";
50
+ import { FooterWithDefaults } from "../../../lib/_footer-with-defaults";
51
+ import { SidebarWithDefaults } from "../../../lib/_sidebar-with-defaults";
52
+ import { HeaderWithDefaults } from "../../../lib/_header-with-defaults";
53
+ import { HeadWithDefaults } from "../../../lib/_head-with-defaults";
54
+ import { DocHistoryArea } from "../../../lib/_doc-history-area";
55
+ import { DocMetainfoArea } from "../../../lib/_doc-metainfo-area";
56
+ import { DocTagsArea } from "../../../lib/_doc-tags-area";
57
+ import { BodyEndIslands } from "../../../lib/_body-end-islands";
58
+ import { buildFrontmatterPreviewEntries } from "../../../lib/_frontmatter-preview-data";
59
+ import { composeMetaTitle } from "../../../lib/_compose-meta-title";
60
+ import { buildInlineVersionSwitcher } from "../../../lib/_inline-version-switcher";
61
+ import DesktopSidebarToggle from "@/components/desktop-sidebar-toggle";
62
+ import { SidebarResizerInit } from "@takazudo/zudo-doc/sidebar-resizer";
63
+ import type { VNode } from "preact";
64
+ import { Island } from "@takazudo/zfb";
65
+
66
+ export const frontmatter = { title: "Docs" };
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Types
70
+ // ---------------------------------------------------------------------------
71
+
72
+ interface DocPageEntry extends DocsEntry {
73
+ Content: CollectionEntry<unknown>["Content"];
74
+ module_specifier: string;
75
+ }
76
+
77
+ interface AutoIndexNode extends NavNode {
78
+ children: NavNode[];
79
+ }
80
+
81
+ interface DocPageProps {
82
+ entry: DocPageEntry | null;
83
+ autoIndex?: AutoIndexNode;
84
+ /** The version config for the active version. */
85
+ version: VersionConfig;
86
+ breadcrumbs: BreadcrumbItem[];
87
+ prev: NavNode | null;
88
+ next: NavNode | null;
89
+ /** Depth-2/3/4 headings extracted from the MDX body, for SSG TOC links. */
90
+ headings: ReturnType<typeof extractHeadings>;
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // paths() — synchronous (ADR-004)
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Emit one route per (version, slug) combination.
99
+ *
100
+ * For each version in settings.versions, loads docs from
101
+ * `docs-v-${version.slug}` and enumerates all pages plus
102
+ * auto-generated category index pages.
103
+ *
104
+ * Prev/next hrefs are pre-resolved to the versioned form.
105
+ */
106
+ export function paths(): Array<{
107
+ params: { version: string; slug: string[] };
108
+ props: DocPageProps;
109
+ }> {
110
+ if (!settings.versions) return [];
111
+
112
+ const result: Array<{
113
+ params: { version: string; slug: string[] };
114
+ props: DocPageProps;
115
+ }> = [];
116
+
117
+ for (const version of settings.versions) {
118
+ const collectionName = `docs-v-${version.slug}`;
119
+ const allDocs = ((bridgeEntries(getCollection(collectionName), collectionName) as unknown as DocPageEntry[])).filter(
120
+ (doc) => !doc.data.draft,
121
+ );
122
+
123
+ const categoryMeta = loadCategoryMeta(version.docsDir);
124
+ const navDocs = allDocs.filter(isNavVisible);
125
+ // Versioned docs always use EN locale for nav tree
126
+ const tree = buildNavTree(navDocs as unknown as DocsEntry[], "en", categoryMeta);
127
+
128
+ // Regular doc pages
129
+ for (const entry of allDocs) {
130
+ const slug = entry.data.slug ?? toRouteSlug(entry.slug);
131
+ const navSection = getNavSectionForSlug(slug);
132
+ const subtree = getNavSubtree(tree, navSection);
133
+ const flat = flattenTree(subtree);
134
+ const idx = flat.findIndex((n) => n.slug === slug);
135
+
136
+ let prevNode = idx > 0 ? flat[idx - 1] ?? null : null;
137
+ let nextNode = idx >= 0 && idx < flat.length - 1 ? flat[idx + 1] ?? null : null;
138
+
139
+ if (entry.data.pagination_prev !== undefined) {
140
+ if (entry.data.pagination_prev === null) {
141
+ prevNode = null;
142
+ } else {
143
+ const found = findNode(tree, entry.data.pagination_prev);
144
+ prevNode = found ?? prevNode;
145
+ }
146
+ }
147
+ if (entry.data.pagination_next !== undefined) {
148
+ if (entry.data.pagination_next === null) {
149
+ nextNode = null;
150
+ } else {
151
+ const found = findNode(tree, entry.data.pagination_next);
152
+ nextNode = found ?? nextNode;
153
+ }
154
+ }
155
+
156
+ result.push({
157
+ params: { version: version.slug, slug: slug.split("/") },
158
+ props: {
159
+ entry,
160
+ version,
161
+ breadcrumbs: buildBreadcrumbs(tree, slug, "en"),
162
+ // Pre-resolve prev/next hrefs to versioned URLs
163
+ prev: prevNode
164
+ ? { ...prevNode, href: versionedDocsUrl(prevNode.slug, version.slug) }
165
+ : null,
166
+ next: nextNode
167
+ ? { ...nextNode, href: versionedDocsUrl(nextNode.slug, version.slug) }
168
+ : null,
169
+ headings: extractHeadings(entry.body ?? ""),
170
+ },
171
+ });
172
+ }
173
+
174
+ // Auto-generated index pages for categories without index.mdx
175
+ for (const node of collectAutoIndexNodes(tree)) {
176
+ result.push({
177
+ params: { version: version.slug, slug: node.slug.split("/") },
178
+ props: {
179
+ entry: null,
180
+ autoIndex: {
181
+ ...node,
182
+ children: node.children.map((c: NavNode) => ({
183
+ ...c,
184
+ href: c.href ?? versionedDocsUrl(c.slug, version.slug),
185
+ })) as NavNode[],
186
+ } as AutoIndexNode,
187
+ version,
188
+ breadcrumbs: buildBreadcrumbs(tree, node.slug, "en"),
189
+ prev: null,
190
+ next: null,
191
+ headings: [],
192
+ },
193
+ });
194
+ }
195
+ }
196
+
197
+ return result;
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Page component
202
+ // ---------------------------------------------------------------------------
203
+
204
+ interface PageArgs {
205
+ params: { version: string; slug: string[] };
206
+ entry: DocPageProps["entry"];
207
+ autoIndex?: DocPageProps["autoIndex"];
208
+ version: DocPageProps["version"];
209
+ breadcrumbs: DocPageProps["breadcrumbs"];
210
+ prev: DocPageProps["prev"];
211
+ next: DocPageProps["next"];
212
+ headings: DocPageProps["headings"];
213
+ }
214
+
215
+ export default function VersionedDocsPage({ entry, autoIndex, version, breadcrumbs, prev, next, headings }: PageArgs): JSX.Element {
216
+ const locale = "en";
217
+
218
+ const slug = autoIndex
219
+ ? autoIndex.slug
220
+ : (entry!.data.slug ?? toRouteSlug(entry!.slug));
221
+
222
+ const title = autoIndex ? autoIndex.label : entry!.data.title;
223
+ const description = autoIndex ? autoIndex.description : entry!.data.description;
224
+
225
+ // Locale-aware components bag — creates nav wrappers bound to the active
226
+ // locale so CategoryNav/CategoryTreeNav/SiteTreeNav query the right collection.
227
+ const components = createMdxComponents(locale);
228
+
229
+ const autoIndexChildren = autoIndex
230
+ ? autoIndex.children.filter((c: NavNode) => c.hasPage || c.children.length > 0)
231
+ : [];
232
+
233
+ // Version banner: drives the `<VersionBanner>` element inside
234
+ // DocLayoutWithDefaults when `version.banner` is "unmaintained" or
235
+ // "unreleased". The banner links out to the latest version of the
236
+ // current page (slug-preserving — strips the /v/{version}/ prefix).
237
+ const versionBannerType = version.banner ? version.banner : undefined;
238
+ const versionBannerLatestUrl = versionBannerType
239
+ ? docsUrl(slug, locale)
240
+ : undefined;
241
+ const versionBannerLabels = versionBannerType
242
+ ? {
243
+ message:
244
+ versionBannerType === "unmaintained"
245
+ ? t("version.banner.unmaintained", locale)
246
+ : t("version.banner.unreleased", locale),
247
+ latestLink: t("version.banner.latestLink", locale),
248
+ }
249
+ : undefined;
250
+
251
+ // Canonical URL — versioned pages use the versioned URL as canonical.
252
+ const pageUrl = versionedDocsUrl(slug, version.slug, locale);
253
+ const canonical = settings.siteUrl
254
+ ? settings.siteUrl.replace(/\/$/, "") + pageUrl
255
+ : undefined;
256
+
257
+ // Persist key: locale + nav-section so the sidebar DOM node is reused
258
+ // across same-locale + same-section navigations only. No sanitizer needed —
259
+ // both lang (BCP-47 locale string) and navSection (filesystem-derived
260
+ // kebab-case slug) come from controlled, trusted sources.
261
+ const navSection = getNavSectionForSlug(slug);
262
+ const hideSidebar = entry?.data?.hide_sidebar;
263
+ const sidebarPersistKey = hideSidebar
264
+ ? undefined
265
+ : `sidebar-${locale}-${navSection ?? "default"}`;
266
+
267
+ return (
268
+ <DocLayoutWithDefaults
269
+ title={composeMetaTitle(title)}
270
+ description={description}
271
+ head={<HeadWithDefaults title={title} description={description} canonical={canonical} />}
272
+ lang={locale}
273
+ noindex={settings.noindex}
274
+ hideSidebar={hideSidebar}
275
+ hideToc={entry?.data?.hide_toc}
276
+ headings={headings}
277
+ canonical={canonical}
278
+ sidebarPersistKey={sidebarPersistKey}
279
+ versionBanner={versionBannerType ?? false}
280
+ versionBannerLatestUrl={versionBannerLatestUrl}
281
+ versionBannerLabels={versionBannerLabels}
282
+ headerOverride={
283
+ <HeaderWithDefaults
284
+ lang={locale}
285
+ currentSlug={slug}
286
+ navSection={getNavSectionForSlug(slug)}
287
+ currentVersion={version.slug}
288
+ currentPath={versionedDocsUrl(slug, version.slug, locale)}
289
+ />
290
+ }
291
+ breadcrumbOverride={
292
+ breadcrumbs.length > 0 ? (
293
+ <Breadcrumb
294
+ items={breadcrumbs}
295
+ rightSlot={buildInlineVersionSwitcher(slug, locale, version.slug)}
296
+ />
297
+ ) : undefined
298
+ }
299
+ sidebarOverride={
300
+ <SidebarWithDefaults
301
+ currentSlug={slug}
302
+ lang={locale}
303
+ navSection={getNavSectionForSlug(slug)}
304
+ currentVersion={version.slug}
305
+ currentPath={versionedDocsUrl(slug, version.slug, locale)}
306
+ />
307
+ }
308
+ afterSidebar={
309
+ settings.sidebarToggle ? (
310
+ <>
311
+ <script dangerouslySetInnerHTML={{
312
+ __html: `(function(){try{if(localStorage.getItem('zudo-doc-sidebar-visible')==='false'){document.documentElement.setAttribute('data-sidebar-hidden','');}}catch(e){}})();`,
313
+ }} />
314
+ {Island({
315
+ when: "load",
316
+ children: <DesktopSidebarToggle />,
317
+ }) as unknown as VNode}
318
+ </>
319
+ ) : undefined
320
+ }
321
+ footerOverride={<FooterWithDefaults lang={locale} />}
322
+ bodyEndComponents={
323
+ <>
324
+ <BodyEndIslands basePath={settings.base ?? "/"} />
325
+ {settings.sidebarResizer && <SidebarResizerInit />}
326
+ </>
327
+ }
328
+ >
329
+ {autoIndex ? (
330
+ /* Auto-index page: category without an index.mdx.
331
+ Fragment (not <div>) so children become direct children of
332
+ <article class="zd-content">, picking up the flow-space rule
333
+ (.zd-content > :where(* + *) { margin-top: var(--flow-space) }).
334
+ Wrapping in <div> would make h1/description p children-of-children
335
+ and the flow gap (~24px) would never apply — see #1460. */
336
+ <>
337
+ <h1 class="text-heading font-bold mb-vsp-xs">{autoIndex.label}</h1>
338
+
339
+ {/* Build-time date block — chrome parity (#1461). Auto-index pages
340
+ previously rendered without doc-meta; reference site shows it on
341
+ every docs page. The component returns null when no manifest
342
+ entry exists for this slug. */}
343
+ <DocMetainfoArea slug={slug} locale={locale} />
344
+
345
+ {autoIndex.description && (
346
+ <p class="mb-vsp-lg text-title text-muted">
347
+ {autoIndex.description}
348
+ </p>
349
+ )}
350
+ <NavCardGrid children={autoIndexChildren} />
351
+ </>
352
+ ) : (
353
+ /* Regular doc page. Fragment (not <div>) for the same reason as
354
+ the auto-index branch above — see #1460. */
355
+ <>
356
+ <h1 class="text-heading font-bold mb-vsp-xs">{entry!.data.title}</h1>
357
+
358
+ {/* Build-time date block (Created / Updated / Author). Mirrors the
359
+ Astro `doc-metainfo.astro` placement — between <h1> and description.
360
+ Data from `.zfb/doc-history-meta.json` (esbuild-inlined, no fs). */}
361
+ <DocMetainfoArea slug={slug} locale={locale} />
362
+
363
+ {/* Page-level tag chips — mirroring doc-tags.astro placement (#1658). */}
364
+ <DocTagsArea slug={slug} locale={locale} tags={entry!.data.tags} />
365
+
366
+ {entry!.data.description && (
367
+ <p class="mb-vsp-lg text-title text-muted">
368
+ {entry!.data.description}
369
+ </p>
370
+ )}
371
+
372
+ {/* Frontmatter preview — non-system, custom keys only. Returns
373
+ null when the entries array is empty, so pages without
374
+ custom frontmatter emit nothing. */}
375
+ <FrontmatterPreview
376
+ entries={buildFrontmatterPreviewEntries(entry!.data)}
377
+ title={t("frontmatter.preview.title", locale)}
378
+ keyColLabel={t("frontmatter.preview.keyCol", locale)}
379
+ valueColLabel={t("frontmatter.preview.valueCol", locale)}
380
+ renderers={frontmatterRenderers}
381
+ data={entry!.data as Record<string, unknown>}
382
+ locale={locale}
383
+ />
384
+
385
+ {entry && <entry.Content components={components} />}
386
+
387
+ {/* Prev / Next pagination — placed before the document utilities
388
+ section to match the Astro reference order: content → pager →
389
+ view-source / history. Fixes #1535. */}
390
+ <nav class="mt-vsp-2xl grid grid-cols-2 gap-hsp-xl">
391
+ {prev ? (
392
+ <a
393
+ href={prev.href}
394
+ class="group border border-muted rounded-lg p-hsp-lg hover:border-accent"
395
+ >
396
+ <div class="flex items-center gap-hsp-xs text-caption text-muted mb-vsp-2xs">
397
+ <svg
398
+ xmlns="http://www.w3.org/2000/svg"
399
+ class="h-[1.125rem] w-[1.125rem]"
400
+ fill="none"
401
+ viewBox="0 0 24 24"
402
+ stroke="currentColor"
403
+ stroke-width="2"
404
+ >
405
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
406
+ </svg>
407
+ <span class="no-underline">{t("nav.previous", locale)}</span>
408
+ </div>
409
+ <p class="text-small font-semibold underline group-hover:text-accent">
410
+ {prev.label}
411
+ </p>
412
+ </a>
413
+ ) : (
414
+ <div />
415
+ )}
416
+ {next ? (
417
+ <a
418
+ href={next.href}
419
+ class="group border border-muted rounded-lg p-hsp-lg hover:border-accent text-right"
420
+ >
421
+ <div class="flex items-center justify-end gap-hsp-xs text-caption text-muted mb-vsp-2xs">
422
+ <span class="no-underline">{t("nav.next", locale)}</span>
423
+ <svg
424
+ xmlns="http://www.w3.org/2000/svg"
425
+ class="h-[1.125rem] w-[1.125rem]"
426
+ fill="none"
427
+ viewBox="0 0 24 24"
428
+ stroke="currentColor"
429
+ stroke-width="2"
430
+ >
431
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
432
+ </svg>
433
+ </div>
434
+ <p class="text-small font-semibold underline group-hover:text-accent">
435
+ {next.label}
436
+ </p>
437
+ </a>
438
+ ) : (
439
+ <div />
440
+ )}
441
+ </nav>
442
+
443
+ {/* Document utilities (revision history) — gated on entry, matching regular slug page pattern */}
444
+ {entry && (
445
+ <DocHistoryArea slug={slug} locale={locale} />
446
+ )}
447
+ </>
448
+ )}
449
+ </DocLayoutWithDefaults>
450
+ );
451
+ }