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,196 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // SSR-friendly search widget for the zfb host header.
4
+ //
5
+ // Mirrors the Astro baseline `src/components/search.astro` (deleted in
6
+ // commit a4d9956) for the zfb host pages. The key SSR requirement is that
7
+ // the placeholder text ("Type to search..." / 「検索したい単語を入力」) and
8
+ // the keyboard-shortcut hint ("to open search from anywhere" /
9
+ // 「いつでも検索バーを開ける」) appear in the static HTML so no-JS users
10
+ // and crawlers can see them.
11
+ //
12
+ // Architecture:
13
+ // - Pure SSR markup for the dialog structure + placeholder text.
14
+ // - A `<site-search>` custom element wraps everything; the inline script
15
+ // at the bottom of this module registers the element and handles:
16
+ // * Dialog open / close (button click, backdrop click, Escape key)
17
+ // * Platform keyboard-shortcut label (⌘K / Ctrl+K) written into
18
+ // `[data-kbd-shortcut]` on first `connectedCallback`
19
+ // * Search-index loading + MiniSearch query dispatch (lazy-loaded
20
+ // after the user types the first character)
21
+ // - Locale-aware strings are passed as props from the caller so the
22
+ // Japanese placeholder / shortcut-hint copy appears in JA-locale SSR
23
+ // without JS execution.
24
+ //
25
+ // Coordination note: this file is inserted into the header via
26
+ // `_header-with-defaults.tsx`'s `search` slot prop. It is self-contained
27
+ // so B-10-2 (version-switcher) can touch the header file without conflict.
28
+
29
+ import type { JSX } from "preact";
30
+ import { withBase } from "@/utils/base";
31
+ import { SEARCH_WIDGET_SCRIPT } from "./_search-widget-script.js";
32
+
33
+ export interface SearchWidgetProps {
34
+ /** Locale-aware placeholder: "Type to search..." / 「検索したい単語を入力」 */
35
+ placeholderText: string;
36
+ /** Locale-aware shortcut hint: "to open search from anywhere" / 「いつでも検索バーを開ける」 */
37
+ shortcutHint: string;
38
+ /** Locale-aware result count template, e.g. "{count} results" */
39
+ resultCountTemplate: string;
40
+ /** Accessible label for the search trigger button */
41
+ searchLabel: string;
42
+ }
43
+
44
+ /**
45
+ * Search trigger button + dialog widget.
46
+ *
47
+ * Pure SSR — renders the full dialog markup including the placeholder text
48
+ * so static HTML contains the required copy even before JS runs.
49
+ * The `<site-search>` custom element registers itself via the inline script
50
+ * and activates interactive behaviour (open/close/keyboard shortcut/MiniSearch)
51
+ * only on the client.
52
+ */
53
+ export function SearchWidget(props: SearchWidgetProps): JSX.Element {
54
+ const { placeholderText, shortcutHint, resultCountTemplate, searchLabel } = props;
55
+ const base = withBase("/");
56
+
57
+ return (
58
+ <>
59
+ {/* @ts-expect-error site-search is a custom element not in JSX intrinsics */}
60
+ <site-search
61
+ data-base={base}
62
+ data-result-count-template={resultCountTemplate}
63
+ >
64
+ {/* Header trigger button — search magnifier icon */}
65
+ <button
66
+ data-open-search
67
+ type="button"
68
+ class="flex items-center justify-center text-muted transition-colors hover:text-fg"
69
+ aria-label={searchLabel}
70
+ >
71
+ <svg
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ width="22"
74
+ height="22"
75
+ viewBox="0 0 24 24"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ stroke-width="2"
79
+ stroke-linecap="round"
80
+ stroke-linejoin="round"
81
+ aria-hidden="true"
82
+ >
83
+ <circle cx="11" cy="11" r="8" />
84
+ <path d="m21 21-4.3-4.3" />
85
+ </svg>
86
+ </button>
87
+
88
+ {/* Search dialog — rendered in SSR so the placeholder text is in
89
+ static HTML for no-JS users and crawlers. The browser treats
90
+ it as a closed <dialog> until the custom element calls
91
+ showModal(). */}
92
+ <dialog
93
+ data-search-dialog
94
+ class="m-0 h-full w-full max-w-none border-none bg-transparent p-0 backdrop:bg-overlay/60 sm:mx-auto sm:my-[10vh] sm:h-auto sm:max-h-[80vh] sm:max-w-[52rem] sm:rounded-lg"
95
+ >
96
+ <div class="flex h-full flex-col overflow-hidden bg-surface sm:rounded-lg sm:border sm:border-muted">
97
+ {/* ── Dialog header (input row) ─────────────────────────── */}
98
+ <div class="flex items-center gap-hsp-sm border-b border-muted px-hsp-lg py-vsp-sm">
99
+ {/* Small search icon inside the input area */}
100
+ <svg
101
+ xmlns="http://www.w3.org/2000/svg"
102
+ width="16"
103
+ height="16"
104
+ viewBox="0 0 24 24"
105
+ fill="none"
106
+ stroke="currentColor"
107
+ stroke-width="2"
108
+ stroke-linecap="round"
109
+ stroke-linejoin="round"
110
+ class="shrink-0 text-muted"
111
+ aria-hidden="true"
112
+ >
113
+ <circle cx="11" cy="11" r="8" />
114
+ <path d="m21 21-4.3-4.3" />
115
+ </svg>
116
+ <input
117
+ data-search-input
118
+ type="text"
119
+ placeholder={placeholderText}
120
+ class="w-full bg-transparent text-body text-fg outline-none placeholder:text-muted"
121
+ autocomplete="off"
122
+ spellcheck={false}
123
+ />
124
+ {/* Wide-viewport hit count (hidden until results arrive) */}
125
+ <span
126
+ data-search-count
127
+ class="hidden shrink-0 text-caption text-muted"
128
+ aria-live="polite"
129
+ />
130
+ <button
131
+ data-close-search
132
+ type="button"
133
+ class="shrink-0 text-muted hover:text-fg"
134
+ aria-label="Close search"
135
+ >
136
+ <svg
137
+ xmlns="http://www.w3.org/2000/svg"
138
+ width="20"
139
+ height="20"
140
+ viewBox="0 0 24 24"
141
+ fill="none"
142
+ stroke="currentColor"
143
+ stroke-width="2"
144
+ stroke-linecap="round"
145
+ stroke-linejoin="round"
146
+ >
147
+ <path d="M18 6 6 18" />
148
+ <path d="m6 6 12 12" />
149
+ </svg>
150
+ </button>
151
+ </div>
152
+
153
+ {/* Narrow-viewport hit count (below header, hidden on sm+) */}
154
+ <span
155
+ data-search-count-narrow
156
+ class="hidden border-b border-muted px-hsp-lg py-vsp-xs text-caption text-muted"
157
+ aria-live="polite"
158
+ />
159
+
160
+ {/* ── Results area ──────────────────────────────────────── */}
161
+ {/* aria-live="polite" so screen readers announce result changes */}
162
+ <div
163
+ class="flex-1 overflow-y-auto px-hsp-lg pb-vsp-md"
164
+ data-search-results
165
+ aria-live="polite"
166
+ >
167
+ {/* Placeholder text — always in SSR HTML so no-JS users and
168
+ crawlers see the placeholderText and shortcutHint strings
169
+ in the static page source. */}
170
+ <div class="text-small text-muted" data-search-placeholder>
171
+ <p>{placeholderText}</p>
172
+ <p class="mt-vsp-md text-caption">
173
+ {/* data-kbd-shortcut is populated by the custom element on
174
+ connectedCallback() with the platform shortcut string
175
+ (⌘K on Mac, Ctrl+K elsewhere). Empty in SSR. */}
176
+ <kbd
177
+ class="rounded border border-muted bg-bg px-hsp-2xs py-[2px] font-mono text-caption"
178
+ data-kbd-shortcut
179
+ />{" "}
180
+ {shortcutHint}
181
+ </p>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </dialog>
186
+ {/* @ts-expect-error closing custom element tag */}
187
+ </site-search>
188
+
189
+ {/* Inline script registers the SiteSearch custom element. Emitted once
190
+ per page — the browser deduplicates same-id custom elements
191
+ automatically; the `customElements.define` call below guards against
192
+ double-registration with the `!customElements.get(...)` check. */}
193
+ <script dangerouslySetInnerHTML={{ __html: SEARCH_WIDGET_SCRIPT }} />
194
+ </>
195
+ );
196
+ }
@@ -0,0 +1,176 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // Locale-/version-aware Sidebar wrapper for the zfb doc pages.
4
+ //
5
+ // Mirrors the data-prep that lived in src/components/sidebar.astro
6
+ // (deleted in commit a4d9956): build root-menu items from
7
+ // settings.headerNav, load the locale's docs collection (with EN
8
+ // fallback for non-default locales), build the nav tree for the active
9
+ // section, optionally remap hrefs for versioned routes, and feed the
10
+ // result into the host's <SidebarTree> Preact island.
11
+ //
12
+ // Why this wrapper exists: the data prep is host-only (it imports
13
+ // @/config/* and @/utils/*), so it cannot live in the published v2
14
+ // package. Without this wrapper the zfb doc pages fall through to a
15
+ // <SidebarTree nodes={[]} /> default and the SSG output emits an empty
16
+ // `<div data-zfb-island="SidebarTree" data-when="load"></div>` marker.
17
+
18
+ import type { JSX } from "preact";
19
+ // `<Island>` wraps `<SidebarTree>` directly here (rather than going through
20
+ // the v2 `<Sidebar>` shell with `treeComponent`) so the zfb island bundle's
21
+ // hydrate pass targets the actual stateful tree component. Mirrors the
22
+ // mobile `<SidebarToggle>` shape in `pages/lib/_header-with-defaults.tsx`:
23
+ // the hydration target owns its own data props directly so they ride the
24
+ // SSR → hydrate boundary inside the Island marker's `data-props` attribute.
25
+ //
26
+ // Background: zfb's `Island.captureSerializableProps` runs `JSON.stringify`
27
+ // on the wrapped component's own props bag, which silently drops function
28
+ // values. With the previous `<Sidebar treeComponent={SidebarTree} ...>`
29
+ // shape the `treeComponent` function was dropped during serialisation, so
30
+ // at hydration the v2 Sidebar shell mounted with `treeComponent=undefined`,
31
+ // returned `null`, and Preact's `hydrate(null, element)` left the SSR-
32
+ // rendered tree DOM in place WITHOUT attaching the input's `onChange`
33
+ // handler — typing into the filter input had no DOM effect.
34
+ // zudolab/zudo-doc#1459 (Wave 1 #1445 wired the input but not the wiring
35
+ // path; this wave routes the hydration target so the wiring actually
36
+ // reaches the rendered tree).
37
+ import { Island } from "@takazudo/zfb";
38
+ import SidebarTree from "@/components/sidebar-tree";
39
+ import { settings } from "@/config/settings";
40
+ import { defaultLocale, locales, t, type Locale } from "@/config/i18n";
41
+ import { buildLocaleLinks, navHref, versionedDocsUrl } from "@/utils/base";
42
+ import {
43
+ isNavVisible,
44
+ type NavNode,
45
+ } from "@/utils/docs";
46
+ import { buildSidebarForSection } from "@/utils/sidebar";
47
+ import { loadNavSourceDocs } from "./_nav-source-docs";
48
+
49
+ export interface SidebarWithDefaultsProps {
50
+ /** Slug of the active doc page, used to highlight the current entry. */
51
+ currentSlug?: string;
52
+ /** Active locale; defaults to the configured defaultLocale. */
53
+ lang?: Locale;
54
+ /** Header-nav category matcher used to scope the tree (e.g. "guides"). */
55
+ navSection?: string;
56
+ /** Active version slug, when rendering inside `/v/{version}/...`. */
57
+ currentVersion?: string;
58
+ /**
59
+ * Current page URL path used to build the locale-switcher links shown in
60
+ * the mobile sidebar footer. The Astro template read this from
61
+ * `Astro.url.pathname`; in zfb the page module passes it explicitly.
62
+ */
63
+ currentPath?: string;
64
+ }
65
+
66
+ /**
67
+ * Walk the nav tree and rewrite each node's `href` to its versioned form.
68
+ *
69
+ * `buildNavTree` always emits hrefs via `docsUrl()`; when the active route
70
+ * lives under `/v/{version}/...` we need the same nodes pointing at the
71
+ * versioned URL so internal nav clicks stay inside the version. Skips
72
+ * nodes without an href (link-only or category placeholders).
73
+ */
74
+ function remapVersionedHrefs(
75
+ nodes: NavNode[],
76
+ version: string,
77
+ nodeLang: Locale,
78
+ ): NavNode[] {
79
+ return nodes.map((node) => {
80
+ const children =
81
+ node.children.length > 0
82
+ ? remapVersionedHrefs(node.children, version, nodeLang)
83
+ : node.children;
84
+
85
+ if (!node.href || node.slug.startsWith("__link__")) {
86
+ return children !== node.children ? { ...node, children } : node;
87
+ }
88
+
89
+ const newHref = versionedDocsUrl(node.slug, version, nodeLang);
90
+ return { ...node, href: newHref, children };
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Default-bearing host wrapper that performs the data prep the deleted
96
+ * `sidebar.astro` template did, then wraps the project's `<SidebarTree>`
97
+ * Preact island in `<Island when="load">` so the SSG output ships a
98
+ * populated `<div data-zfb-island="SidebarTree" data-when="load">…tree…
99
+ * </div>` marker for the hydration runtime to pick up.
100
+ *
101
+ * The v2 `<Sidebar>` shell is intentionally NOT used as the hydration
102
+ * target here. Its `treeComponent` prop is a function, and zfb's
103
+ * `Island.captureSerializableProps` drops function values during
104
+ * `JSON.stringify`, so a `<Sidebar treeComponent={SidebarTree} ...>`
105
+ * island would hydrate with `treeComponent=undefined` and the shell
106
+ * would return `null`, silently breaking the filter input's hydration
107
+ * (zudolab/zudo-doc#1459). Wrapping `<SidebarTree>` directly mirrors the
108
+ * mobile `<SidebarToggle>` shape (see `_header-with-defaults.tsx`) and
109
+ * keeps all data props serializable.
110
+ */
111
+ export function SidebarWithDefaults(
112
+ props: SidebarWithDefaultsProps,
113
+ ): JSX.Element {
114
+ const {
115
+ currentSlug,
116
+ lang = defaultLocale,
117
+ navSection,
118
+ currentVersion,
119
+ currentPath = "",
120
+ } = props;
121
+
122
+ // Root-menu items derived from headerNav (mobile back-to-menu list).
123
+ // The Astro template fed labelKey through `t(...)` and computed hrefs
124
+ // with `navHref()`; mirror that exactly so the rendered list stays
125
+ // identical between the A and B sites.
126
+ const rootMenuItems = settings.headerNav.map((item) => ({
127
+ label: item.labelKey
128
+ ? t(item.labelKey as Parameters<typeof t>[0], lang)
129
+ : item.label,
130
+ href: navHref(item.path, lang, currentVersion),
131
+ children: item.children?.map((child) => ({
132
+ label: child.labelKey
133
+ ? t(child.labelKey as Parameters<typeof t>[0], lang)
134
+ : child.label,
135
+ href: navHref(child.path, lang, currentVersion),
136
+ })),
137
+ }));
138
+
139
+ const backToMenuLabel = navSection ? t("nav.backToMenu", lang) : undefined;
140
+
141
+ const { docs, categoryMeta } = loadNavSourceDocs(lang, currentVersion);
142
+ const navDocs = docs.filter(isNavVisible);
143
+ const rawNodes = buildSidebarForSection(navDocs, lang, navSection, categoryMeta);
144
+ const nodes = currentVersion
145
+ ? remapVersionedHrefs(rawNodes, currentVersion, lang)
146
+ : rawNodes;
147
+
148
+ // Locale-switcher links are only meaningful when more than one locale is
149
+ // configured — matches the Astro template's guard.
150
+ const localeLinks =
151
+ locales.length > 1 ? buildLocaleLinks(currentPath, lang) : undefined;
152
+
153
+ // Wrap <SidebarTree> directly in <Island when="load">. SSR emits the
154
+ // `data-zfb-island="SidebarTree"` marker around the rendered tree, with
155
+ // all data props serialised into `data-props` (every prop is plain data:
156
+ // arrays of objects + strings). At hydration the runtime finds the
157
+ // marker, looks up "SidebarTree" in the islands manifest (registered via
158
+ // the host's `"use client"` directive on `src/components/sidebar-tree.tsx`),
159
+ // and mounts the real component in-place — re-attaching the filter
160
+ // input's `onChange` handler to the existing SSR DOM.
161
+ return Island({
162
+ when: "load",
163
+ children: (
164
+ <SidebarTree
165
+ nodes={nodes}
166
+ currentSlug={currentSlug}
167
+ rootMenuItems={rootMenuItems}
168
+ backToMenuLabel={backToMenuLabel}
169
+ localeLinks={localeLinks}
170
+ themeDefaultMode={
171
+ settings.colorMode ? settings.colorMode.defaultMode : undefined
172
+ }
173
+ />
174
+ ),
175
+ }) as unknown as JSX.Element;
176
+ }
@@ -0,0 +1,128 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ // Host-side MDX wrapper for <SiteTreeNav /> and <SiteTreeNavDemo />.
4
+ //
5
+ // The original Astro project had two files:
6
+ // - src/components/site-tree-nav.astro — interactive Preact island
7
+ // - src/components/site-tree-nav-demo.astro — Astro wrapper that loaded
8
+ // collection data and rendered the island
9
+ //
10
+ // Restored to use the interactive SiteTreeNav island (refs #1453):
11
+ // Both <SiteTreeNav> and <SiteTreeNavDemo> MDX tags are mapped to this
12
+ // wrapper which does the same data loading that site-tree-nav-demo.astro did:
13
+ //
14
+ // 1. Load the full docs collection for the active locale.
15
+ // 2. Build nav tree via buildNavTree().
16
+ // 3. Group satellite nodes via groupSatelliteNodes().
17
+ // 4. Wrap the interactive SiteTreeNav in Island({when:"idle"}) so the MDX
18
+ // page gets the same collapsible grid the reference renders at
19
+ // /docs/components/site-tree-nav/ (refs #1453/#1442).
20
+ //
21
+ // All data access is synchronous (ADR-004 zfb content snapshot contract).
22
+ // The `lang` prop is injected by createMdxComponents() in
23
+ // pages/_mdx-components.ts so locale routes get locale-aware nav data.
24
+ //
25
+ // categoryIgnore defaults to ["inbox", "develop"] — same as the original index page
26
+ // and site-tree-nav-demo.astro.
27
+
28
+ import type { JSX } from "preact";
29
+ import { Island } from "@takazudo/zfb";
30
+ import SiteTreeNav from "@/components/site-tree-nav";
31
+ import {
32
+ buildNavTree,
33
+ groupSatelliteNodes,
34
+ loadCategoryMeta,
35
+ isNavVisible,
36
+ } from "@/utils/docs";
37
+ import { settings } from "@/config/settings";
38
+ import { defaultLocale, type Locale } from "@/config/i18n";
39
+ import { isDefaultLocaleOnlyPath } from "@/utils/base";
40
+ import { getCategoryOrder } from "@/utils/nav-scope";
41
+ import { loadDocs } from "../_data";
42
+
43
+ export interface SiteTreeNavWrapperProps {
44
+ /**
45
+ * Active locale. Injected via createMdxComponents() closure.
46
+ * Defaults to defaultLocale when not provided.
47
+ */
48
+ lang?: Locale | string;
49
+ /**
50
+ * Optional aria-label for the wrapping <nav> element.
51
+ * Forwarded to the v2 SiteTreeNavDemo component.
52
+ */
53
+ ariaLabel?: string;
54
+ }
55
+
56
+ /**
57
+ * Load merged docs + categoryMeta for the given locale.
58
+ * Matches the locale-merge strategy used by _category-nav.tsx.
59
+ */
60
+ function loadNavSource(
61
+ locale: string,
62
+ ): { docs: ReturnType<typeof loadDocs>; categoryMeta: Map<string, import("@/utils/docs").CategoryMeta> } {
63
+ if (locale === defaultLocale) {
64
+ return {
65
+ docs: loadDocs("docs").filter((d) => !d.data.draft),
66
+ categoryMeta: loadCategoryMeta(settings.docsDir),
67
+ };
68
+ }
69
+
70
+ const localeDocs = loadDocs(`docs-${locale}`).filter((d) => !d.data.draft);
71
+ const baseDocs = loadDocs("docs").filter((d) => !d.data.draft);
72
+ const localeSlugSet = new Set(localeDocs.map((d) => d.data.slug ?? d.id));
73
+ const fallbackDocs = baseDocs
74
+ .filter((d) => !localeSlugSet.has(d.data.slug ?? d.id))
75
+ .filter((d) => {
76
+ const slug = d.data.slug ?? d.id;
77
+ return !isDefaultLocaleOnlyPath(`/docs/${slug}/`);
78
+ });
79
+
80
+ const localeDir =
81
+ (settings.locales as Record<string, { dir?: string }>)[locale]?.dir ??
82
+ settings.docsDir;
83
+ const categoryMeta = new Map([
84
+ ...loadCategoryMeta(settings.docsDir),
85
+ ...loadCategoryMeta(localeDir),
86
+ ]);
87
+
88
+ return { docs: [...localeDocs, ...fallbackDocs], categoryMeta };
89
+ }
90
+
91
+ /**
92
+ * MDX wrapper shared by both <SiteTreeNav> and <SiteTreeNavDemo> tags.
93
+ *
94
+ * Builds the full site nav tree and renders it via the interactive SiteTreeNav
95
+ * island (wrapped in Island({when:"idle"})) — restoring byte-parity with the
96
+ * Astro reference at /docs/components/site-tree-nav/ (refs #1453/#1442).
97
+ *
98
+ * The island renders the collapsible multi-column grid the reference shows.
99
+ * SiteTreeNavDemo (static <details> list) is no longer used for MDX content.
100
+ *
101
+ * Returns null when the tree is empty after filtering.
102
+ */
103
+ export function SiteTreeNavWrapper({
104
+ lang = defaultLocale,
105
+ ariaLabel,
106
+ }: SiteTreeNavWrapperProps): JSX.Element | null {
107
+ const locale = lang as Locale;
108
+
109
+ const { docs, categoryMeta } = loadNavSource(locale);
110
+ const navDocs = docs.filter(isNavVisible);
111
+ const tree = buildNavTree(navDocs, locale, categoryMeta);
112
+ const categoryOrder = getCategoryOrder();
113
+ const groupedTree = groupSatelliteNodes(tree, categoryOrder);
114
+
115
+ if (groupedTree.length === 0) return null;
116
+
117
+ return Island({
118
+ when: "idle",
119
+ children: (
120
+ <SiteTreeNav
121
+ tree={groupedTree}
122
+ categoryOrder={categoryOrder}
123
+ categoryIgnore={["inbox", "develop"]}
124
+ ariaLabel={ariaLabel}
125
+ />
126
+ ),
127
+ }) as unknown as JSX.Element;
128
+ }
@@ -0,0 +1,58 @@
1
+ // Shared utility for merging locale docs with base-locale fallbacks.
2
+ //
3
+ // Used by the locale tag pages to replicate the locale-first + base-fallback
4
+ // strategy from the Astro src/components/tag-nav.astro non-default-locale
5
+ // branch. Extracted here to avoid duplicating the logic across multiple
6
+ // [locale] page modules.
7
+ //
8
+ // Strategy:
9
+ // 1. Load `docs-${locale}` collection.
10
+ // 2. Load base "docs" collection.
11
+ // 3. Merge: locale docs take priority; base docs fill in slugs not present
12
+ // in the locale collection.
13
+ // 4. Filter out drafts and unlisted pages.
14
+ //
15
+ // This is a zfb-only module (uses synchronous getCollection from zfb/content).
16
+ // Do not import from Astro page code.
17
+
18
+ import { loadDocs } from "../_data";
19
+ import type { DocsEntry } from "@/types/docs-entry";
20
+
21
+ /**
22
+ * Merge locale docs with base-locale fallbacks.
23
+ *
24
+ * Locale docs take priority; base docs fill in slugs not covered by the
25
+ * locale collection. Drafts and unlisted docs are filtered out.
26
+ */
27
+ export function mergeLocaleDocs(locale: string): DocsEntry[] {
28
+ // zfb's CollectionEntry uses `slug`/`collection` only; @/utils/docs
29
+ // utilities expect Astro-style `id` and `collection` fields. Map them
30
+ // here so the rest of this module (and its callers) get the
31
+ // DocsEntry-shaped objects they assume. Bridging on read keeps the
32
+ // schema discrepancy contained to the I/O boundary.
33
+ // Use `loadDocs` so the Astro-compat `id`/`collection` augmentation
34
+ // (and the `index` slug stripping) stays in one place — the inline
35
+ // `id: e.slug` map dropped the `/index` suffix that
36
+ // `buildNavTree`/`buildBreadcrumbs` etc. assume Astro 5 strips, which
37
+ // produced ambiguous-URL collisions at paths()-expansion time.
38
+ const localeDocs = loadDocs(`docs-${locale}`);
39
+ const baseDocs = loadDocs("docs");
40
+
41
+ const filteredLocale = localeDocs.filter(
42
+ (d) => !d.data.draft && !d.data.unlisted,
43
+ );
44
+ const filteredBase = baseDocs.filter(
45
+ (d) => !d.data.draft && !d.data.unlisted,
46
+ );
47
+
48
+ const localeSlugSet = new Set(
49
+ filteredLocale.map((d) => d.data.slug ?? d.id),
50
+ );
51
+
52
+ return [
53
+ ...filteredLocale,
54
+ ...filteredBase.filter(
55
+ (d) => !localeSlugSet.has(d.data.slug ?? d.id),
56
+ ),
57
+ ];
58
+ }