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,490 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // Port of src/pages/v/[version]/ja/docs/[...slug].astro → zfb page module.
4
+ //
5
+ // Versioned JA docs route. paths() cross-products settings.versions with a
6
+ // locale-first merge strategy: locale-specific collection (`docs-v-${version.slug}-ja`)
7
+ // takes priority; the base EN collection (`docs-v-${version.slug}`) fills in
8
+ // pages not translated yet (shown with a fallback notice).
9
+ //
10
+ // If version.locales?.ja is not configured, only the base EN collection is used.
11
+ //
12
+ // paths() contract (zfb ADR-004 — synchronous):
13
+ // params: { version: string; slug: string[] }
14
+ // props: { entry, autoIndex, version, contentDir, isFallback, breadcrumbs, prev, next }
15
+ //
16
+ // Prev/next hrefs are pre-resolved to the versioned JA URL form
17
+ // (e.g. /v/1.0/ja/docs/…) so the component needs no URL computation.
18
+
19
+ import { getCollection } from "zfb/content";
20
+ import type { CollectionEntry } from "zfb/content";
21
+ import type { DocsEntry } from "@/types/docs-entry";
22
+ import { settings } from "@/config/settings";
23
+ import type { VersionConfig } from "@/config/settings";
24
+ import { t } from "@/config/i18n";
25
+ import { docsUrl, versionedDocsUrl, isDefaultLocaleOnlyPath } from "@/utils/base";
26
+ import {
27
+ buildNavTree,
28
+ buildBreadcrumbs,
29
+ flattenTree,
30
+ findNode,
31
+ loadCategoryMeta,
32
+ collectAutoIndexNodes,
33
+ isNavVisible,
34
+ type NavNode,
35
+ type BreadcrumbItem,
36
+ } from "@/utils/docs";
37
+ import { getNavSectionForSlug, getNavSubtree } from "@/utils/nav-scope";
38
+ import { toRouteSlug } from "@/utils/slug";
39
+ import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
40
+ import { Breadcrumb } from "@takazudo/zudo-doc/breadcrumb";
41
+ import { NavCardGrid } from "@takazudo/zudo-doc/nav-indexing";
42
+ import { FrontmatterPreview } from "@takazudo/zudo-doc/metainfo";
43
+ import { frontmatterRenderers } from "@/config/frontmatter-preview-renderers";
44
+ // Locale-aware MDX components factory — see `pages/_mdx-components.ts`.
45
+ import { createMdxComponents } from "../../../../_mdx-components";
46
+ import type { JSX } from "preact";
47
+ import { bridgeEntries } from "../../../../_data";
48
+ import { extractHeadings } from "../../../../lib/_extract-headings";
49
+ import { FooterWithDefaults } from "../../../../lib/_footer-with-defaults";
50
+ import { SidebarWithDefaults } from "../../../../lib/_sidebar-with-defaults";
51
+ import { HeaderWithDefaults } from "../../../../lib/_header-with-defaults";
52
+ import { HeadWithDefaults } from "../../../../lib/_head-with-defaults";
53
+ import { DocHistoryArea } from "../../../../lib/_doc-history-area";
54
+ import { DocMetainfoArea } from "../../../../lib/_doc-metainfo-area";
55
+ import { DocTagsArea } from "../../../../lib/_doc-tags-area";
56
+ import { BodyEndIslands } from "../../../../lib/_body-end-islands";
57
+ import { buildFrontmatterPreviewEntries } from "../../../../lib/_frontmatter-preview-data";
58
+ import { composeMetaTitle } from "../../../../lib/_compose-meta-title";
59
+ import { buildInlineVersionSwitcher } from "../../../../lib/_inline-version-switcher";
60
+ import DesktopSidebarToggle from "@/components/desktop-sidebar-toggle";
61
+ import { SidebarResizerInit } from "@takazudo/zudo-doc/sidebar-resizer";
62
+ import type { VNode } from "preact";
63
+ import { Island } from "@takazudo/zfb";
64
+
65
+ export const frontmatter = { title: "Docs" };
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Types
69
+ // ---------------------------------------------------------------------------
70
+
71
+ interface DocPageEntry extends DocsEntry {
72
+ Content: CollectionEntry<unknown>["Content"];
73
+ module_specifier: string;
74
+ }
75
+
76
+ interface AutoIndexNode extends NavNode {
77
+ children: NavNode[];
78
+ }
79
+
80
+ interface DocPageProps {
81
+ entry: DocPageEntry | null;
82
+ autoIndex?: AutoIndexNode;
83
+ /** The version config for the active version. */
84
+ version: VersionConfig;
85
+ /** Content directory for this page (locale dir if translated, version docsDir if fallback). */
86
+ contentDir: string;
87
+ /** True when this page falls back to the base EN collection for this version. */
88
+ isFallback: boolean;
89
+ breadcrumbs: BreadcrumbItem[];
90
+ prev: NavNode | null;
91
+ next: NavNode | null;
92
+ /** Depth-2/3/4 headings extracted from the MDX body, for SSG TOC links. */
93
+ headings: ReturnType<typeof extractHeadings>;
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // paths() — synchronous (ADR-004)
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Emit one route per (version, slug) combination for JA locale.
102
+ *
103
+ * Merge strategy per version:
104
+ * 1. Load locale docs (e.g. "docs-v-1.0-ja") if version.locales.ja is set.
105
+ * 2. Load base EN docs ("docs-v-1.0").
106
+ * 3. Locale docs take priority; base EN fills in slugs not translated.
107
+ * 4. Track fallback slugs for the fallback-notice banner.
108
+ * 5. Build nav tree with "ja" locale, compute breadcrumbs and prev/next.
109
+ *
110
+ * Prev/next hrefs are pre-resolved to the versioned JA URL form.
111
+ */
112
+ export function paths(): Array<{
113
+ params: { version: string; slug: string[] };
114
+ props: DocPageProps;
115
+ }> {
116
+ if (!settings.versions) return [];
117
+
118
+ const result: Array<{
119
+ params: { version: string; slug: string[] };
120
+ props: DocPageProps;
121
+ }> = [];
122
+
123
+ for (const version of settings.versions) {
124
+ const baseCollectionName = `docs-v-${version.slug}`;
125
+ const localeDir = (version.locales as Record<string, { dir: string }> | undefined)?.ja?.dir;
126
+ const localeCollectionName = localeDir ? `docs-v-${version.slug}-ja` : null;
127
+
128
+ const baseDocs = ((bridgeEntries(getCollection(baseCollectionName), baseCollectionName) as unknown as DocPageEntry[])).filter(
129
+ (doc) => !doc.data.draft,
130
+ );
131
+ const localeDocs = localeCollectionName
132
+ ? ((bridgeEntries(getCollection(localeCollectionName), localeCollectionName) as unknown as DocPageEntry[])).filter(
133
+ (doc) => !doc.data.draft,
134
+ )
135
+ : [];
136
+
137
+ // Build slug set from locale docs (locale takes priority)
138
+ const localeSlugSet = new Set(localeDocs.map((d) => d.data.slug ?? toRouteSlug(d.slug)));
139
+
140
+ // Merge: locale docs first, then base docs for missing pages
141
+ const fallbackDocs = baseDocs.filter(
142
+ (d) => !localeSlugSet.has(d.data.slug ?? toRouteSlug(d.slug)) && !isDefaultLocaleOnlyPath(`/docs/${d.data.slug ?? toRouteSlug(d.slug)}`),
143
+ );
144
+ const fallbackSlugs = new Set(fallbackDocs.map((d) => d.data.slug ?? toRouteSlug(d.slug)));
145
+ const allDocs = [...localeDocs, ...fallbackDocs];
146
+
147
+ // Merge category metadata: base first, locale overrides
148
+ const baseCategoryMeta = loadCategoryMeta(version.docsDir);
149
+ const localeCategoryMeta = localeDir ? loadCategoryMeta(localeDir) : new Map();
150
+ const categoryMeta = new Map([...baseCategoryMeta, ...localeCategoryMeta]);
151
+
152
+ const navDocs = allDocs.filter(isNavVisible);
153
+ const tree = buildNavTree(navDocs as unknown as DocsEntry[], "ja", categoryMeta);
154
+
155
+ // Regular doc pages
156
+ for (const entry of allDocs) {
157
+ const slug = entry.data.slug ?? toRouteSlug(entry.slug);
158
+ const isFallback = fallbackSlugs.has(slug);
159
+ const entryContentDir = isFallback ? version.docsDir : (localeDir ?? version.docsDir);
160
+
161
+ const navSection = getNavSectionForSlug(slug);
162
+ const subtree = getNavSubtree(tree, navSection);
163
+ const flat = flattenTree(subtree);
164
+ const idx = flat.findIndex((n) => n.slug === slug);
165
+
166
+ let prevNode = idx > 0 ? flat[idx - 1] ?? null : null;
167
+ let nextNode = idx >= 0 && idx < flat.length - 1 ? flat[idx + 1] ?? null : null;
168
+
169
+ if (entry.data.pagination_prev !== undefined) {
170
+ if (entry.data.pagination_prev === null) {
171
+ prevNode = null;
172
+ } else {
173
+ const found = findNode(tree, entry.data.pagination_prev);
174
+ prevNode = found ?? prevNode;
175
+ }
176
+ }
177
+ if (entry.data.pagination_next !== undefined) {
178
+ if (entry.data.pagination_next === null) {
179
+ nextNode = null;
180
+ } else {
181
+ const found = findNode(tree, entry.data.pagination_next);
182
+ nextNode = found ?? nextNode;
183
+ }
184
+ }
185
+
186
+ result.push({
187
+ params: { version: version.slug, slug: slug.split("/") },
188
+ props: {
189
+ entry,
190
+ version,
191
+ contentDir: entryContentDir,
192
+ isFallback,
193
+ breadcrumbs: buildBreadcrumbs(tree, slug, "ja"),
194
+ // Pre-resolve prev/next hrefs to versioned JA URLs
195
+ prev: prevNode
196
+ ? { ...prevNode, href: versionedDocsUrl(prevNode.slug, version.slug, "ja") }
197
+ : null,
198
+ next: nextNode
199
+ ? { ...nextNode, href: versionedDocsUrl(nextNode.slug, version.slug, "ja") }
200
+ : null,
201
+ headings: extractHeadings(entry.body ?? ""),
202
+ },
203
+ });
204
+ }
205
+
206
+ // Auto-generated index pages for categories without index.mdx
207
+ for (const node of collectAutoIndexNodes(tree)) {
208
+ result.push({
209
+ params: { version: version.slug, slug: node.slug.split("/") },
210
+ props: {
211
+ entry: null,
212
+ autoIndex: {
213
+ ...node,
214
+ children: node.children.map((c: NavNode) => ({
215
+ ...c,
216
+ href: c.href ?? versionedDocsUrl(c.slug, version.slug, "ja"),
217
+ })) as NavNode[],
218
+ } as AutoIndexNode,
219
+ version,
220
+ contentDir: localeDir ?? version.docsDir,
221
+ isFallback: false,
222
+ breadcrumbs: buildBreadcrumbs(tree, node.slug, "ja"),
223
+ prev: null,
224
+ next: null,
225
+ headings: [],
226
+ },
227
+ });
228
+ }
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Page component
236
+ // ---------------------------------------------------------------------------
237
+
238
+ interface PageArgs {
239
+ params: { version: string; slug: string[] };
240
+ entry: DocPageProps["entry"];
241
+ autoIndex?: DocPageProps["autoIndex"];
242
+ version: DocPageProps["version"];
243
+ isFallback: DocPageProps["isFallback"];
244
+ breadcrumbs: DocPageProps["breadcrumbs"];
245
+ prev: DocPageProps["prev"];
246
+ next: DocPageProps["next"];
247
+ headings: DocPageProps["headings"];
248
+ }
249
+
250
+ export default function VersionedJaDocsPage({ entry, autoIndex, version, isFallback, breadcrumbs, prev, next, headings }: PageArgs): JSX.Element {
251
+ const locale = "ja";
252
+
253
+ const slug = autoIndex
254
+ ? autoIndex.slug
255
+ : (entry!.data.slug ?? toRouteSlug(entry!.slug));
256
+
257
+ const title = autoIndex ? autoIndex.label : entry!.data.title;
258
+ const description = autoIndex ? autoIndex.description : entry!.data.description;
259
+
260
+ // Locale-aware components bag — creates nav wrappers bound to the active
261
+ // locale so CategoryNav/CategoryTreeNav/SiteTreeNav query the right collection.
262
+ const components = createMdxComponents(locale);
263
+
264
+ const autoIndexChildren = autoIndex
265
+ ? autoIndex.children.filter((c: NavNode) => c.hasPage || c.children.length > 0)
266
+ : [];
267
+
268
+ // Version banner: drives the `<VersionBanner>` element inside
269
+ // DocLayoutWithDefaults when `version.banner` is "unmaintained" or
270
+ // "unreleased". The banner links out to the latest version of the
271
+ // current page (slug-preserving — strips the /v/{version}/ prefix,
272
+ // keeps the /ja/ locale prefix).
273
+ const versionBannerType = version.banner ? version.banner : undefined;
274
+ const versionBannerLatestUrl = versionBannerType
275
+ ? docsUrl(slug, locale)
276
+ : undefined;
277
+ const versionBannerLabels = versionBannerType
278
+ ? {
279
+ message:
280
+ versionBannerType === "unmaintained"
281
+ ? t("version.banner.unmaintained", locale)
282
+ : t("version.banner.unreleased", locale),
283
+ latestLink: t("version.banner.latestLink", locale),
284
+ }
285
+ : undefined;
286
+
287
+ // Canonical URL — versioned JA pages use the versioned JA URL as canonical.
288
+ const pageUrl = versionedDocsUrl(slug, version.slug, locale);
289
+ const canonical = settings.siteUrl
290
+ ? settings.siteUrl.replace(/\/$/, "") + pageUrl
291
+ : undefined;
292
+
293
+ // Persist key: locale + nav-section so the sidebar DOM node is reused
294
+ // across same-locale + same-section navigations only. No sanitizer needed —
295
+ // both lang (BCP-47 locale string) and navSection (filesystem-derived
296
+ // kebab-case slug) come from controlled, trusted sources.
297
+ const navSection = getNavSectionForSlug(slug);
298
+ const hideSidebar = entry?.data?.hide_sidebar;
299
+ const sidebarPersistKey = hideSidebar
300
+ ? undefined
301
+ : `sidebar-${locale}-${navSection ?? "default"}`;
302
+
303
+ return (
304
+ <DocLayoutWithDefaults
305
+ title={composeMetaTitle(title)}
306
+ description={description}
307
+ head={<HeadWithDefaults title={title} description={description} canonical={canonical} />}
308
+ lang={locale}
309
+ noindex={settings.noindex}
310
+ hideSidebar={hideSidebar}
311
+ hideToc={entry?.data?.hide_toc}
312
+ headings={headings}
313
+ canonical={canonical}
314
+ sidebarPersistKey={sidebarPersistKey}
315
+ versionBanner={versionBannerType ?? false}
316
+ versionBannerLatestUrl={versionBannerLatestUrl}
317
+ versionBannerLabels={versionBannerLabels}
318
+ headerOverride={
319
+ <HeaderWithDefaults
320
+ lang={locale}
321
+ currentSlug={slug}
322
+ navSection={getNavSectionForSlug(slug)}
323
+ currentVersion={version.slug}
324
+ currentPath={versionedDocsUrl(slug, version.slug, locale)}
325
+ />
326
+ }
327
+ breadcrumbOverride={
328
+ breadcrumbs.length > 0 ? (
329
+ <Breadcrumb
330
+ items={breadcrumbs}
331
+ rightSlot={buildInlineVersionSwitcher(slug, locale, version.slug)}
332
+ />
333
+ ) : undefined
334
+ }
335
+ sidebarOverride={
336
+ <SidebarWithDefaults
337
+ currentSlug={slug}
338
+ lang={locale}
339
+ navSection={getNavSectionForSlug(slug)}
340
+ currentVersion={version.slug}
341
+ currentPath={versionedDocsUrl(slug, version.slug, locale)}
342
+ />
343
+ }
344
+ afterSidebar={
345
+ settings.sidebarToggle ? (
346
+ <>
347
+ <script dangerouslySetInnerHTML={{
348
+ __html: `(function(){try{if(localStorage.getItem('zudo-doc-sidebar-visible')==='false'){document.documentElement.setAttribute('data-sidebar-hidden','');}}catch(e){}})();`,
349
+ }} />
350
+ {Island({
351
+ when: "load",
352
+ children: <DesktopSidebarToggle />,
353
+ }) as unknown as VNode}
354
+ </>
355
+ ) : undefined
356
+ }
357
+ footerOverride={<FooterWithDefaults lang={locale} />}
358
+ bodyEndComponents={
359
+ <>
360
+ <BodyEndIslands basePath={settings.base ?? "/"} />
361
+ {settings.sidebarResizer && <SidebarResizerInit />}
362
+ </>
363
+ }
364
+ >
365
+ {autoIndex ? (
366
+ /* Auto-index page: category without an index.mdx.
367
+ Fragment (not <div>) so children become direct children of
368
+ <article class="zd-content">, picking up the flow-space rule
369
+ (.zd-content > :where(* + *) { margin-top: var(--flow-space) }).
370
+ Wrapping in <div> would make h1/description p children-of-children
371
+ and the flow gap (~24px) would never apply — see #1460. */
372
+ <>
373
+ <h1 class="text-heading font-bold mb-vsp-xs">{autoIndex.label}</h1>
374
+ {autoIndex.description && (
375
+ <p class="mb-vsp-lg text-title text-muted">
376
+ {autoIndex.description}
377
+ </p>
378
+ )}
379
+ <NavCardGrid children={autoIndexChildren} />
380
+ </>
381
+ ) : (
382
+ /* Regular doc page. Fragment (not <div>) for the same reason as
383
+ the auto-index branch above — see #1460. */
384
+ <>
385
+ <h1 class="text-heading font-bold mb-vsp-xs">{entry!.data.title}</h1>
386
+
387
+ {/* Build-time date block (Created / Updated / Author). Mirrors the
388
+ Astro `doc-metainfo.astro` placement — between <h1> and description.
389
+ Data from `.zfb/doc-history-meta.json` (esbuild-inlined, no fs). */}
390
+ <DocMetainfoArea slug={slug} locale={locale} />
391
+
392
+ {/* Page-level tag chips — mirroring doc-tags.astro placement (#1658). */}
393
+ <DocTagsArea slug={slug} locale={locale} tags={entry!.data.tags} />
394
+
395
+ {/* Fallback notice for non-translated pages */}
396
+ {isFallback && !entry!.data.generated && (
397
+ <div
398
+ class="mb-vsp-md border border-info/30 bg-info/5 px-hsp-lg py-vsp-sm text-small text-muted rounded"
399
+ role="note"
400
+ >
401
+ {t("doc.fallbackNotice", locale)}
402
+ </div>
403
+ )}
404
+
405
+ {entry!.data.description && (
406
+ <p class="mb-vsp-lg text-title text-muted">
407
+ {entry!.data.description}
408
+ </p>
409
+ )}
410
+
411
+ {/* Frontmatter preview — non-system, custom keys only. Returns
412
+ null when the entries array is empty, so pages without
413
+ custom frontmatter emit nothing. */}
414
+ <FrontmatterPreview
415
+ entries={buildFrontmatterPreviewEntries(entry!.data)}
416
+ title={t("frontmatter.preview.title", locale)}
417
+ keyColLabel={t("frontmatter.preview.keyCol", locale)}
418
+ valueColLabel={t("frontmatter.preview.valueCol", locale)}
419
+ renderers={frontmatterRenderers}
420
+ data={entry!.data as Record<string, unknown>}
421
+ locale={locale}
422
+ />
423
+
424
+ {entry && <entry.Content components={components} />}
425
+
426
+ {/* Prev / Next pagination — placed before the document utilities
427
+ section to match the Astro reference order: content → pager →
428
+ view-source / history. Fixes #1535. */}
429
+ <nav class="mt-vsp-2xl grid grid-cols-2 gap-hsp-xl">
430
+ {prev ? (
431
+ <a
432
+ href={prev.href}
433
+ class="group border border-muted rounded-lg p-hsp-lg hover:border-accent"
434
+ >
435
+ <div class="flex items-center gap-hsp-xs text-caption text-muted mb-vsp-2xs">
436
+ <svg
437
+ xmlns="http://www.w3.org/2000/svg"
438
+ class="h-[1.125rem] w-[1.125rem]"
439
+ fill="none"
440
+ viewBox="0 0 24 24"
441
+ stroke="currentColor"
442
+ stroke-width="2"
443
+ >
444
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
445
+ </svg>
446
+ <span class="no-underline">{t("nav.previous", locale)}</span>
447
+ </div>
448
+ <p class="text-small font-semibold underline group-hover:text-accent">
449
+ {prev.label}
450
+ </p>
451
+ </a>
452
+ ) : (
453
+ <div />
454
+ )}
455
+ {next ? (
456
+ <a
457
+ href={next.href}
458
+ class="group border border-muted rounded-lg p-hsp-lg hover:border-accent text-right"
459
+ >
460
+ <div class="flex items-center justify-end gap-hsp-xs text-caption text-muted mb-vsp-2xs">
461
+ <span class="no-underline">{t("nav.next", locale)}</span>
462
+ <svg
463
+ xmlns="http://www.w3.org/2000/svg"
464
+ class="h-[1.125rem] w-[1.125rem]"
465
+ fill="none"
466
+ viewBox="0 0 24 24"
467
+ stroke="currentColor"
468
+ stroke-width="2"
469
+ >
470
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
471
+ </svg>
472
+ </div>
473
+ <p class="text-small font-semibold underline group-hover:text-accent">
474
+ {next.label}
475
+ </p>
476
+ </a>
477
+ ) : (
478
+ <div />
479
+ )}
480
+ </nav>
481
+
482
+ {/* Document utilities (revision history) — gated on entry, matching regular slug page pattern */}
483
+ {entry && (
484
+ <DocHistoryArea slug={slug} locale={locale} />
485
+ )}
486
+ </>
487
+ )}
488
+ </DocLayoutWithDefaults>
489
+ );
490
+ }