create-zudo-doc 0.2.0 → 0.2.2

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 (83) hide show
  1. package/dist/api.js +4 -1
  2. package/dist/cli.js +4 -6
  3. package/dist/compose.d.ts +2 -3
  4. package/dist/compose.js +7 -4
  5. package/dist/features/tauri.d.ts +10 -5
  6. package/dist/features/tauri.js +49 -6
  7. package/dist/preset.js +11 -0
  8. package/dist/prompts.js +2 -6
  9. package/dist/scaffold.js +15 -9
  10. package/dist/settings-gen.js +9 -6
  11. package/dist/utils.d.ts +8 -0
  12. package/dist/utils.js +25 -0
  13. package/dist/zfb-config-gen.js +11 -50
  14. package/package.json +1 -1
  15. package/templates/base/pages/_data.ts +10 -23
  16. package/templates/base/pages/docs/[[...slug]].tsx +27 -168
  17. package/templates/base/pages/lib/_body-end-islands.tsx +3 -0
  18. package/templates/base/pages/lib/_doc-content-header.tsx +24 -4
  19. package/templates/base/pages/lib/_doc-history-area.tsx +21 -5
  20. package/templates/base/pages/lib/_doc-metainfo-area.tsx +22 -2
  21. package/templates/base/pages/lib/_doc-page-renderer.tsx +192 -0
  22. package/templates/base/pages/lib/_doc-page-shell.tsx +3 -2
  23. package/templates/base/pages/lib/_doc-route-entries.ts +188 -0
  24. package/templates/base/pages/lib/_doc-tags-area.tsx +7 -2
  25. package/templates/base/pages/lib/_footer-with-defaults.tsx +38 -27
  26. package/templates/base/pages/lib/_head-with-defaults.tsx +7 -10
  27. package/templates/base/pages/lib/_header-with-defaults.tsx +54 -89
  28. package/templates/base/pages/lib/_inline-version-switcher.tsx +5 -4
  29. package/templates/base/pages/lib/_nav-data-prep.ts +137 -0
  30. package/templates/base/pages/lib/_nav-source-docs.ts +10 -6
  31. package/templates/base/pages/lib/_search-widget-script.ts +32 -9
  32. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +15 -60
  33. package/templates/base/pages/lib/locale-merge.ts +1 -1
  34. package/templates/base/pages/lib/route-enumerators.ts +11 -7
  35. package/templates/base/plugins/connect-adapter.mjs +30 -1
  36. package/templates/base/plugins/copy-public-plugin.mjs +10 -2
  37. package/templates/base/plugins/search-index-plugin.mjs +20 -8
  38. package/templates/base/src/components/ai-chat-modal.tsx +2 -0
  39. package/templates/base/src/components/doc-history.tsx +2 -0
  40. package/templates/base/src/components/image-enlarge.tsx +2 -0
  41. package/templates/base/src/components/sidebar-toggle.tsx +1 -1
  42. package/templates/base/src/components/sidebar-tree.tsx +11 -5
  43. package/templates/base/src/components/theme-toggle.tsx +18 -102
  44. package/templates/base/src/config/color-schemes.ts +4 -0
  45. package/templates/base/src/config/docs-schema.ts +94 -0
  46. package/templates/base/src/config/i18n.ts +10 -3
  47. package/templates/base/src/styles/global.css +14 -0
  48. package/templates/base/src/types/docs-entry.ts +8 -26
  49. package/templates/base/src/utils/base.ts +5 -3
  50. package/templates/base/src/utils/docs.ts +144 -169
  51. package/templates/base/zfb-shim.d.ts +167 -0
  52. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +20 -110
  53. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +62 -38
  54. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +34 -8
  55. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +27 -45
  56. package/templates/features/docHistory/files/src/components/doc-history.tsx +30 -8
  57. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +6 -74
  58. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +6 -77
  59. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +7 -69
  60. package/templates/features/docTags/files/pages/docs/tags/index.tsx +6 -76
  61. package/templates/features/docTags/files/pages/lib/_tag-pages.tsx +201 -0
  62. package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +41 -179
  63. package/templates/features/i18n/files/pages/[locale]/index.tsx +5 -5
  64. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +2 -0
  65. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +33 -21
  66. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +1 -1
  67. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +9 -3
  68. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +5 -59
  69. package/templates/features/versioning/files/pages/docs/versions.tsx +8 -66
  70. package/templates/features/versioning/files/pages/lib/_versions-page.tsx +79 -0
  71. package/templates/features/versioning/files/pages/v/[version]/[locale]/docs/[[...slug]].tsx +46 -191
  72. package/templates/features/versioning/files/pages/v/[version]/docs/[[...slug]].tsx +31 -173
  73. package/templates/base/src/components/content/heading-h3.tsx +0 -20
  74. package/templates/base/src/hooks/use-active-heading.ts +0 -133
  75. package/templates/base/src/plugins/docs-source-map.ts +0 -103
  76. package/templates/base/src/plugins/hast-utils.ts +0 -10
  77. package/templates/base/src/plugins/rehype-code-title.ts +0 -50
  78. package/templates/base/src/plugins/rehype-heading-links.ts +0 -53
  79. package/templates/base/src/plugins/rehype-mermaid.ts +0 -41
  80. package/templates/base/src/plugins/url-utils.ts +0 -4
  81. package/templates/base/src/utils/dedent.ts +0 -24
  82. package/templates/features/docHistory/files/src/utils/doc-history.ts +0 -180
  83. package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +0 -198
@@ -23,29 +23,18 @@
23
23
  // - Non-default locales emit /{locale}/docs/{slug}.
24
24
  // - Locale-first merge: locale docs take priority; base EN docs fill in
25
25
  // pages not translated yet (shown with a fallback notice).
26
+ //
27
+ // Enumeration + per-entry derived data are built by the shared, memoized
28
+ // buildDocRouteEntries (#2010); rendering by the shared renderDocPage. This
29
+ // file owns only the route's nav source and the param/prop shapes.
26
30
 
27
31
  import { settings } from "@/config/settings";
28
- import { docsUrl, absoluteUrl } from "@/utils/base";
29
- import {
30
- buildNavTree,
31
- buildBreadcrumbs,
32
- collectAutoIndexNodes,
33
- type NavNode,
34
- } from "@/utils/docs";
35
- import { getNavSectionForSlug, getNavSubtree } from "@/utils/nav-scope";
36
- import { toRouteSlug, toSlugParams } from "@/utils/slug";
37
- // Shared MDX components bag — see `pages/_mdx-components.ts`.
38
- import { createMdxComponents } from "../../_mdx-components";
32
+ import { getLocaleConfig, type Locale } from "@/config/i18n";
39
33
  import type { JSX } from "preact";
40
34
  import { resolveNavSource } from "../../lib/_nav-source-docs";
41
- import { extractHeadings } from "../../lib/_extract-headings";
42
- import type { DocPageEntry, AutoIndexNode, DocPageEntryProps, DocPageAutoIndexProps } from "../../lib/doc-page-props";
43
- import { DocHistoryArea } from "../../lib/_doc-history-area";
44
- import { DocMetainfoArea } from "../../lib/_doc-metainfo-area";
45
- import { buildInlineVersionSwitcher } from "../../lib/_inline-version-switcher";
46
- import { DocContentHeader } from "../../lib/_doc-content-header";
47
- import { DocPageShell } from "../../lib/_doc-page-shell";
48
- import { resolveDocPrevNext, flattenSubtree } from "../../lib/_doc-route-paths";
35
+ import type { DocPageEntryProps, DocPageAutoIndexProps } from "../../lib/doc-page-props";
36
+ import { buildDocRouteEntries } from "../../lib/_doc-route-entries";
37
+ import { renderDocPage } from "../../lib/_doc-page-renderer";
49
38
 
50
39
  export const frontmatter = { title: "Docs" };
51
40
 
@@ -53,8 +42,6 @@ export const frontmatter = { title: "Docs" };
53
42
  // Types
54
43
  // ---------------------------------------------------------------------------
55
44
 
56
- // DocPageEntry, AutoIndexNode imported from pages/lib/doc-page-props.ts
57
-
58
45
  /** Route-specific extra fields — present on both branches of the union. */
59
46
  interface LocaleDocPageExtra {
60
47
  /** Content directory for the active locale (or base EN for fallbacks). */
@@ -81,8 +68,9 @@ type DocPageProps =
81
68
  * 4. Track fallback slugs for the fallback-notice banner.
82
69
  * 5. Build nav tree, compute breadcrumbs and prev/next for each entry.
83
70
  *
84
- * Fallback slug set drives `isFallback` which the component uses to show
85
- * the "not yet translated" notice (matching the Astro original).
71
+ * Fallback detection (`isFallback`) comes from the merge's localeSlugSet
72
+ * the component uses it to show the "not yet translated" notice (matching
73
+ * the Astro original).
86
74
  */
87
75
  export function paths(): Array<{
88
76
  params: { locale: string; slug: string[] };
@@ -94,85 +82,36 @@ export function paths(): Array<{
94
82
  }> = [];
95
83
 
96
84
  for (const locale of Object.keys(settings.locales) as string[]) {
97
- const localeConfig = settings.locales[locale];
85
+ const localeConfig = getLocaleConfig(locale);
98
86
  const contentDir = localeConfig?.dir ?? settings.docsDir;
99
87
 
100
88
  // Identity-stable, locale-first merge with EN fallback. The same `docs` /
101
89
  // `navDocs` / `categoryMeta` instances are reused across this route's many
102
- // per-page paths() invocations so buildNavTree's identity fast-path skips
103
- // the key recomputation — see pages/lib/_nav-source-docs.ts (#1902).
104
- const { docs: allDocs, navDocs, categoryMeta, localeSlugSet } = resolveNavSource(
105
- locale,
106
- undefined,
107
- { applyDefaultLocaleOnlyFilter: true, keepUnlisted: true },
108
- );
109
- // isFallback: page came from base docs, not the locale collection.
110
- const fallbackSlugs = new Set(
111
- allDocs
112
- .filter((d) => !localeSlugSet.has(d.data.slug ?? d.id))
113
- .map((d) => d.data.slug ?? d.id),
114
- );
115
-
116
- const tree = buildNavTree(navDocs, locale, categoryMeta);
117
- const fullTree = buildNavTree(allDocs, locale, categoryMeta);
118
-
119
- // Regular doc pages
120
- for (const entry of allDocs) {
121
- // A `category_no_page` index.mdx is metadata-only — kept in the nav tree
122
- // for breadcrumbs but emits no route (zfb retains every .mdx as a
123
- // collection entry, so the skip must be explicit).
124
- if (entry.data.category_no_page === true) continue;
125
- // Canonical route slug via the one shared rule (@/utils/slug). `entry.id`
126
- // is already `toRouteSlug(entry.slug)` (bridgeEntries → stripIndexSuffix →
127
- // toRouteSlug), so this is identical to the previous `entry.id` form for
128
- // every entry — but stating it explicitly removes the historical id-vs-
129
- // toRouteSlug asymmetry with the EN route and the component below, all of
130
- // which now yield "" for a root index (URL /{locale}/docs/ — #1891).
131
- const slug = entry.data.slug ?? toRouteSlug(entry.slug);
132
- const isFallback = fallbackSlugs.has(slug);
133
- const entryContentDir = isFallback ? settings.docsDir : contentDir;
134
-
135
- const navSection = getNavSectionForSlug(slug);
136
- const subtree = getNavSubtree(tree, navSection);
137
-
138
- // Prev/next + pagination overrides against THIS locale's own `tree`.
139
- // Latest content (no version) — hrefs stay unversioned (no rewrite).
140
- const { prev: prevNode, next: nextNode } = resolveDocPrevNext(
141
- tree,
142
- flattenSubtree(subtree),
143
- slug,
144
- entry.data,
145
- );
146
-
147
- result.push({
148
- params: { locale, slug: toSlugParams(slug) },
149
- props: {
150
- kind: "entry",
151
- entry,
152
- contentDir: entryContentDir,
153
- isFallback,
154
- breadcrumbs: buildBreadcrumbs(fullTree, slug, locale),
155
- prev: prevNode,
156
- next: nextNode,
157
- headings: extractHeadings(entry.body ?? ""),
158
- },
159
- });
160
- }
161
-
162
- // Auto-generated index pages for categories without index.mdx
163
- for (const node of collectAutoIndexNodes(tree)) {
90
+ // per-page paths() invocations so both buildNavTree's identity fast-path
91
+ // and the buildDocRouteEntries memo key on them — see
92
+ // pages/lib/_nav-source-docs.ts (#1902).
93
+ const source = resolveNavSource(locale as Locale, undefined, {
94
+ applyDefaultLocaleOnlyFilter: true,
95
+ keepUnlisted: true,
96
+ });
97
+
98
+ for (const item of buildDocRouteEntries({
99
+ source,
100
+ locale: locale as Locale,
101
+ routeSig: `locale-docs;${locale}`,
102
+ })) {
103
+ // isFallback: page came from base docs, not the locale collection.
104
+ // Always false for autoIndex items (item.isFallback already is).
105
+ const extra: LocaleDocPageExtra = {
106
+ contentDir: item.isFallback ? settings.docsDir : contentDir,
107
+ isFallback: item.isFallback,
108
+ };
164
109
  result.push({
165
- params: { locale, slug: toSlugParams(node.slug) },
166
- props: {
167
- kind: "autoIndex",
168
- autoIndex: node as AutoIndexNode,
169
- contentDir,
170
- isFallback: false,
171
- breadcrumbs: buildBreadcrumbs(fullTree, node.slug, locale),
172
- prev: null,
173
- next: null,
174
- headings: [],
175
- },
110
+ params: { locale, slug: item.slugParams },
111
+ props:
112
+ item.props.kind === "entry"
113
+ ? { ...item.props, ...extra }
114
+ : { ...item.props, ...extra },
176
115
  });
177
116
  }
178
117
  }
@@ -187,86 +126,9 @@ export function paths(): Array<{
187
126
  type PageArgs = DocPageProps & { params: { locale: string; slug: string[] } };
188
127
 
189
128
  export default function LocaleDocsPage(props: PageArgs): JSX.Element {
190
- const { breadcrumbs, prev, next, headings, contentDir, isFallback } = props;
191
- const locale = props.params.locale;
192
-
193
- const slug = props.kind === "autoIndex"
194
- ? props.autoIndex.slug
195
- : (props.entry.data.slug ?? toRouteSlug(props.entry.slug));
196
-
197
- const title = props.kind === "autoIndex" ? props.autoIndex.label : props.entry.data.title;
198
- const description = props.kind === "autoIndex" ? props.autoIndex.description : props.entry.data.description;
199
-
200
- // Locale-aware components bag — creates nav wrappers bound to the active
201
- // locale so CategoryNav/CategoryTreeNav/SiteTreeNav query the right collection.
202
- const components = createMdxComponents(locale);
203
-
204
- // Latest content (no version) — keep the nav node's own docsUrl href.
205
- const autoIndexChildren = props.kind === "autoIndex"
206
- ? props.autoIndex.children
207
- .filter((c: NavNode) => c.hasPage || c.children.length > 0)
208
- .map((c: NavNode) => ({
209
- ...c,
210
- href: c.href ?? docsUrl(c.slug, locale),
211
- }))
212
- : [];
213
-
214
- // Canonical URL — base-prefixed locale page path, absolutized against siteUrl.
215
- const currentPath = docsUrl(slug, locale);
216
- const canonical = absoluteUrl(currentPath);
217
-
218
- // Persist key: locale + nav-section so the sidebar DOM node is reused
219
- // across same-locale + same-section navigations only. No sanitizer needed —
220
- // both lang (BCP-47 locale string) and navSection (filesystem-derived
221
- // kebab-case slug) come from controlled, trusted sources.
222
- const navSection = getNavSectionForSlug(slug);
223
- const hideSidebar = props.kind === "entry" ? props.entry.data.hide_sidebar : undefined;
224
- const sidebarPersistKey = hideSidebar
225
- ? undefined
226
- : `sidebar-${locale}-${navSection ?? "default"}`;
227
-
228
- return (
229
- <DocPageShell
230
- kind={props.kind}
231
- locale={locale}
232
- slug={slug}
233
- title={title}
234
- description={description}
235
- canonical={canonical}
236
- breadcrumbs={breadcrumbs}
237
- prev={prev}
238
- next={next}
239
- headings={headings}
240
- navSection={navSection}
241
- sidebarPersistKey={sidebarPersistKey}
242
- hideSidebar={hideSidebar}
243
- hideToc={props.kind === "entry" ? props.entry.data.hide_toc : undefined}
244
- currentPath={currentPath}
245
- versionSwitcher={buildInlineVersionSwitcher(slug, locale)}
246
- autoIndexLabel={props.kind === "autoIndex" ? props.autoIndex.label : undefined}
247
- autoIndexChildren={autoIndexChildren}
248
- metainfoSlot={
249
- props.kind === "autoIndex" ? <DocMetainfoArea slug={slug} locale={locale} /> : null
250
- }
251
- contentHeaderSlot={
252
- props.kind === "entry" ? (
253
- <DocContentHeader entry={props.entry} slug={slug} locale={locale} isFallback={isFallback} />
254
- ) : undefined
255
- }
256
- contentSlot={
257
- props.kind === "entry" ? <props.entry.Content components={components} /> : undefined
258
- }
259
- docHistorySlot={
260
- props.kind === "entry" && !props.entry.data.unlisted ? (
261
- <DocHistoryArea
262
- slug={slug}
263
- locale={locale}
264
- entrySlug={props.entry.slug}
265
- contentDir={contentDir}
266
- isFallback={isFallback}
267
- />
268
- ) : null
269
- }
270
- />
271
- );
129
+ return renderDocPage(props, {
130
+ locale: props.params.locale as Locale,
131
+ isFallback: props.isFallback,
132
+ docHistoryContentDir: props.contentDir,
133
+ });
272
134
  }
@@ -16,7 +16,7 @@
16
16
  // → collectTags() → tag section
17
17
 
18
18
  import { settings } from "@/config/settings";
19
- import { t } from "@/config/i18n";
19
+ import { t, getLocaleConfig, type Locale } from "@/config/i18n";
20
20
  import { withBase } from "@/utils/base";
21
21
  import {
22
22
  buildNavTree,
@@ -71,16 +71,16 @@ export default function LocaleIndexPage({ params }: PageArgs): JSX.Element {
71
71
  // instance). categoryMeta is intentionally locale-dir-only here — this page
72
72
  // historically did NOT merge in base meta (unlike the locale doc route), so
73
73
  // we keep that exact behavior to preserve output.
74
- const { navDocs } = resolveNavSource(locale, undefined, {
74
+ const { navDocs } = resolveNavSource(locale as Locale, undefined, {
75
75
  applyDefaultLocaleOnlyFilter: true,
76
76
  keepUnlisted: true,
77
77
  });
78
- const localeConfig = settings.locales[locale];
78
+ const localeConfig = getLocaleConfig(locale);
79
79
  const categoryMeta = localeConfig
80
80
  ? loadCategoryMeta(localeConfig.dir)
81
81
  : loadCategoryMeta(settings.docsDir);
82
82
 
83
- const tree = buildNavTree(navDocs, locale, categoryMeta);
83
+ const tree = buildNavTree(navDocs, locale as Locale, categoryMeta);
84
84
  const categoryOrder = getCategoryOrder();
85
85
  const groupedTree = groupSatelliteNodes(tree, categoryOrder);
86
86
 
@@ -103,7 +103,7 @@ export default function LocaleIndexPage({ params }: PageArgs): JSX.Element {
103
103
  noindex={settings.noindex}
104
104
  hideSidebar={true}
105
105
  hideToc={true}
106
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/`)} />}
106
+ headerOverride={<HeaderWithDefaults lang={locale as Locale} currentPath={withBase(`/${locale}/`)} />}
107
107
  footerOverride={<FooterWithDefaults lang={locale} />}
108
108
  bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
109
109
  >
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { useState, useEffect, useRef } from "preact/compat";
2
4
  import type { JSX } from "preact";
3
5
 
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  // zfb plugin module: llms-txt.
2
3
  //
3
4
  // Wires two lifecycle hooks for the llms-txt integration:
@@ -20,35 +21,35 @@
20
21
  // Inline functions are not supported by zfb's plugin runtime; see the
21
22
  // sibling `doc-history-plugin.mjs` for the rationale.
22
23
 
24
+ /** @import { ZfbBuildHookContext, ZfbDevMiddlewareContext, ZfbPlugin } from "@takazudo/zfb/plugins" */
25
+ /** @import { LlmsTxtEmitOptions, LlmsTxtDevMiddlewareOptions } from "@takazudo/zudo-doc/integrations/llms-txt" */
26
+
23
27
  import { emitLlmsTxt, createLlmsTxtDevMiddleware } from "@takazudo/zudo-doc/integrations/llms-txt";
24
28
  import { connectToZfbHandler } from "./connect-adapter.mjs";
25
29
 
30
+ /** @type {ZfbPlugin} */
26
31
  export default {
27
32
  name: "llms-txt",
28
33
 
34
+ /** @param {ZfbBuildHookContext} ctx */
29
35
  postBuild(ctx) {
30
- const {
31
- siteName,
32
- siteDescription,
33
- base,
34
- siteUrl,
35
- defaultLocaleDir,
36
- locales,
37
- } = ctx.options;
38
- emitLlmsTxt({
36
+ emitLlmsTxt(/** @type {LlmsTxtEmitOptions} */ (/** @type {unknown} */ ({
37
+ ...ctx.options,
39
38
  outDir: ctx.outDir,
40
- siteName,
41
- siteDescription,
42
- base,
43
- siteUrl: siteUrl || undefined,
44
- defaultLocaleDir,
45
- locales,
39
+ // siteUrl is normalised to undefined when falsy because the runner
40
+ // switches between absolute and root-relative URLs based on its
41
+ // presence (matches legacy Astro behaviour).
42
+ siteUrl: ctx.options["siteUrl"] || undefined,
46
43
  logger: ctx.logger,
47
- });
44
+ })));
48
45
  },
49
46
 
47
+ /** @param {ZfbDevMiddlewareContext} ctx */
50
48
  devMiddleware(ctx) {
51
- const middleware = createLlmsTxtDevMiddleware(ctx.options, ctx.logger);
49
+ const middleware = createLlmsTxtDevMiddleware(
50
+ /** @type {LlmsTxtDevMiddlewareOptions} */ (/** @type {unknown} */ (ctx.options)),
51
+ ctx.logger,
52
+ );
52
53
  const handler = connectToZfbHandler(middleware);
53
54
 
54
55
  // zfb's `register(path, handler)` matches against the FULL request
@@ -58,16 +59,27 @@ export default {
58
59
  // empty and routes are `/llms.txt` etc. as expected. The middleware
59
60
  // accepts base-prefixed URLs via the matcher (see `matchLlmsRoute`
60
61
  // in `dev-middleware.ts`).
61
- const basePrefix = stripTrailingSlash(ctx.options.base ?? "");
62
+ const basePrefix = stripTrailingSlash(
63
+ typeof ctx.options["base"] === "string" ? ctx.options["base"] : "",
64
+ );
62
65
  ctx.register(`${basePrefix}/llms.txt`, handler);
63
66
  ctx.register(`${basePrefix}/llms-full.txt`, handler);
64
- for (const locale of ctx.options.locales ?? []) {
65
- ctx.register(`${basePrefix}/${locale.code}/llms.txt`, handler);
66
- ctx.register(`${basePrefix}/${locale.code}/llms-full.txt`, handler);
67
+ const locales = ctx.options["locales"];
68
+ if (Array.isArray(locales)) {
69
+ for (const locale of locales) {
70
+ if (locale && typeof locale === "object" && typeof locale.code === "string") {
71
+ ctx.register(`${basePrefix}/${locale.code}/llms.txt`, handler);
72
+ ctx.register(`${basePrefix}/${locale.code}/llms-full.txt`, handler);
73
+ }
74
+ }
67
75
  }
68
76
  },
69
77
  };
70
78
 
79
+ /**
80
+ * @param {string} s
81
+ * @returns {string}
82
+ */
71
83
  function stripTrailingSlash(s) {
72
84
  if (typeof s !== "string" || s.length === 0) return "";
73
85
  return s.endsWith("/") ? s.slice(0, -1) : s;
@@ -29,7 +29,7 @@ export default function DesktopSidebarToggle() {
29
29
  // to <html> from localStorage *before* this island mounts, so the
30
30
  // visual state stays correct; we only need to sync this island's
31
31
  // React state to the persisted preference after hydration. Same
32
- // pattern as src/components/theme-toggle.tsx (commit 9aebd8e).
32
+ // pattern as packages/zudo-doc/src/theme-toggle/index.tsx (commit 9aebd8e).
33
33
  const [visible, setVisible] = useState<boolean>(true);
34
34
  // Tracks whether the hydration sync (below) has run. The persistence
35
35
  // effect below skips the very first mount so we don't overwrite the
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { useState, useEffect, useRef } from "preact/compat";
2
4
  import { FindBar } from "./find-bar";
3
5
  import { createFindInPage } from "@/utils/find-in-page";
@@ -30,14 +32,18 @@ export default function FindInPageInit() {
30
32
  return () => document.removeEventListener("keydown", handler);
31
33
  }, [isTauri]);
32
34
 
33
- // Clear search on zfb page navigation
35
+ // Clear search on zfb page navigation. zfb navigates via SPA body swap and
36
+ // fires "zfb:before-preparation" on document before nav — it never fires the
37
+ // native "pagehide" (full-unload) event. The literal is inlined because
38
+ // downstream scaffolds do not depend on @takazudo/zudo-doc as a runtime dep
39
+ // (same reason as the designTokenPanel bootstrap).
34
40
  useEffect(() => {
35
41
  const handler = () => {
36
42
  findInPageRef.current.stop();
37
43
  setVisible(false);
38
44
  };
39
- document.addEventListener("pagehide", handler);
40
- return () => document.removeEventListener("pagehide", handler);
45
+ document.addEventListener("zfb:before-preparation", handler);
46
+ return () => document.removeEventListener("zfb:before-preparation", handler);
41
47
  }, []);
42
48
 
43
49
  if (!isTauri) return null;
@@ -6,22 +6,16 @@
6
6
  // settings.locales. Locale string is passed as a prop to drive label
7
7
  // translation in the component.
8
8
  //
9
+ // Rendering is shared with the default-locale route via
10
+ // pages/lib/_versions-page.tsx (#2010).
11
+ //
9
12
  // paths() contract (zfb ADR-004 — synchronous):
10
13
  // params: { locale: string }
11
14
  // props: { locale }
12
15
 
13
16
  import { settings } from "@/config/settings";
14
- import { t } from "@/config/i18n";
15
- import { withBase } from "@/utils/base";
16
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
17
- import { VersionsPageContent } from "@takazudo/zudo-doc/nav-indexing";
18
- import type { VersionPageEntry, VersionsPageLabels } from "@takazudo/zudo-doc/nav-indexing";
19
17
  import type { JSX } from "preact";
20
- import { FooterWithDefaults } from "../../lib/_footer-with-defaults";
21
- import { HeaderWithDefaults } from "../../lib/_header-with-defaults";
22
- import { HeadWithDefaults } from "../../lib/_head-with-defaults";
23
- import { composeMetaTitle } from "../../lib/_compose-meta-title";
24
- import { BodyEndIslands } from "../../lib/_body-end-islands";
18
+ import { VersionsPageView } from "../../lib/_versions-page";
25
19
 
26
20
  export const frontmatter = { title: "Versions" };
27
21
 
@@ -50,53 +44,5 @@ interface PageArgs {
50
44
  }
51
45
 
52
46
  export default function LocaleVersionsPage({ params }: PageArgs): JSX.Element {
53
- const locale = params.locale;
54
- const pageTitle = t("version.page.title", locale);
55
-
56
- const labels: VersionsPageLabels = {
57
- pageTitle,
58
- latestTitle: t("version.page.latest.title", locale),
59
- latestDescription: t("version.page.latest.description", locale),
60
- latestLink: t("version.page.latest.link", locale),
61
- pastTitle: t("version.page.past.title", locale),
62
- pastDescription: t("version.page.past.description", locale),
63
- unmaintained: t("version.page.unmaintained", locale),
64
- unreleased: t("version.page.unreleased", locale),
65
- versionCol: t("version.switcher.label", locale),
66
- statusCol: t("version.page.status", locale),
67
- docsCol: t("version.page.docs", locale),
68
- };
69
-
70
- const latestHref = withBase(`/${locale}/docs/getting-started`);
71
-
72
- const versions: VersionPageEntry[] = settings.versions
73
- ? settings.versions.map((v) => ({
74
- slug: v.slug,
75
- label: v.label ?? v.slug,
76
- // Version prefix comes BEFORE the locale — the only routed shape is
77
- // pages/v/[version]/{locale}/docs/...; /{locale}/v/... has no route.
78
- docsHref: withBase(`/v/${v.slug}/${locale}/docs/getting-started/`),
79
- banner: v.banner as "unmaintained" | "unreleased" | undefined,
80
- }))
81
- : [];
82
-
83
- return (
84
- <DocLayoutWithDefaults
85
- title={composeMetaTitle(pageTitle)}
86
- head={<HeadWithDefaults title={pageTitle} />}
87
- lang={locale}
88
- noindex={settings.noindex}
89
- hideSidebar={true}
90
- hideToc={true}
91
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase(`/${locale}/docs/versions`)} />}
92
- footerOverride={<FooterWithDefaults lang={locale} />}
93
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
94
- >
95
- <VersionsPageContent
96
- latestHref={latestHref}
97
- versions={versions}
98
- labels={labels}
99
- />
100
- </DocLayoutWithDefaults>
101
- );
47
+ return <VersionsPageView locale={params.locale} />;
102
48
  }
@@ -2,77 +2,19 @@
2
2
  /** @jsxImportSource preact */
3
3
  // Page module for the default-locale versions index route.
4
4
  //
5
- // Default-locale (EN) documentation versions page. Static route — no
6
- // paths() export needed. Lists the latest version and any past versions
7
- // configured in settings.versions.
5
+ // Default-locale documentation versions page. Static route — no paths()
6
+ // export needed. Lists the latest version and any past versions configured
7
+ // in settings.versions.
8
8
  //
9
- // Data flow:
10
- // settings.versions → build version entry list
11
- // t() for locale strings → labels bag passed to VersionsPageContent
9
+ // Rendering is shared with the locale-prefixed route via
10
+ // pages/lib/_versions-page.tsx (#2010).
12
11
 
13
- import { settings } from "@/config/settings";
14
- import { defaultLocale, t } from "@/config/i18n";
15
- import { withBase } from "@/utils/base";
16
- import { DocLayoutWithDefaults } from "@takazudo/zudo-doc/doclayout";
17
- import { VersionsPageContent } from "@takazudo/zudo-doc/nav-indexing";
18
- import type { VersionPageEntry, VersionsPageLabels } from "@takazudo/zudo-doc/nav-indexing";
12
+ import { defaultLocale } from "@/config/i18n";
19
13
  import type { JSX } from "preact";
20
- import { FooterWithDefaults } from "../lib/_footer-with-defaults";
21
- import { HeaderWithDefaults } from "../lib/_header-with-defaults";
22
- import { HeadWithDefaults } from "../lib/_head-with-defaults";
23
- import { composeMetaTitle } from "../lib/_compose-meta-title";
24
- import { BodyEndIslands } from "../lib/_body-end-islands";
14
+ import { VersionsPageView } from "../lib/_versions-page";
25
15
 
26
16
  export const frontmatter = { title: "Versions" };
27
17
 
28
18
  export default function VersionsPage(): JSX.Element {
29
- const locale = defaultLocale;
30
- const pageTitle = t("version.page.title", locale);
31
-
32
- const labels: VersionsPageLabels = {
33
- pageTitle,
34
- latestTitle: t("version.page.latest.title", locale),
35
- latestDescription: t("version.page.latest.description", locale),
36
- latestLink: t("version.page.latest.link", locale),
37
- pastTitle: t("version.page.past.title", locale),
38
- pastDescription: t("version.page.past.description", locale),
39
- unmaintained: t("version.page.unmaintained", locale),
40
- unreleased: t("version.page.unreleased", locale),
41
- versionCol: t("version.switcher.label", locale),
42
- statusCol: t("version.page.status", locale),
43
- docsCol: t("version.page.docs", locale),
44
- };
45
-
46
- // Latest docs href — points to the default docs entry point
47
- const latestHref = withBase("/docs/getting-started");
48
-
49
- // Past version entries from settings
50
- const versions: VersionPageEntry[] = settings.versions
51
- ? settings.versions.map((v) => ({
52
- slug: v.slug,
53
- label: v.label ?? v.slug,
54
- docsHref: withBase(`/v/${v.slug}/docs/getting-started/`),
55
- banner: v.banner as "unmaintained" | "unreleased" | undefined,
56
- }))
57
- : [];
58
-
59
- return (
60
- <DocLayoutWithDefaults
61
- title={composeMetaTitle(pageTitle)}
62
- head={<HeadWithDefaults title={pageTitle} />}
63
- lang={locale}
64
- noindex={settings.noindex}
65
- hideSidebar={true}
66
- hideToc={true}
67
- headerOverride={<HeaderWithDefaults lang={locale} currentPath={withBase("/docs/versions")} />}
68
- footerOverride={<FooterWithDefaults lang={locale} />}
69
- bodyEndComponents={<BodyEndIslands basePath={settings.base ?? "/"} />}
70
- >
71
- <VersionsPageContent
72
- latestHref={latestHref}
73
- versions={versions}
74
- labels={labels}
75
- />
76
- </DocLayoutWithDefaults>
77
- );
19
+ return <VersionsPageView locale={defaultLocale} />;
78
20
  }