create-zudo-doc 0.2.0-next.5 → 0.2.0-next.7

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 (33) hide show
  1. package/dist/constants.d.ts +2 -0
  2. package/dist/constants.js +26 -0
  3. package/dist/scaffold.js +11 -5
  4. package/dist/settings-gen.js +9 -0
  5. package/dist/zfb-config-gen.js +15 -0
  6. package/package.json +1 -1
  7. package/templates/base/pages/_data.ts +28 -11
  8. package/templates/base/pages/docs/[[...slug]].tsx +52 -129
  9. package/templates/base/pages/lib/_doc-metainfo-area.tsx +6 -6
  10. package/templates/base/pages/lib/_doc-page-shell.tsx +229 -0
  11. package/templates/base/pages/lib/_doc-route-paths.ts +101 -0
  12. package/templates/base/pages/lib/_extract-headings.ts +263 -33
  13. package/templates/base/pages/lib/_head-with-defaults.tsx +12 -14
  14. package/templates/base/pages/lib/_nav-source-cache.ts +3 -3
  15. package/templates/base/pages/lib/_nav-source-docs.ts +9 -9
  16. package/templates/base/pages/lib/locale-merge.ts +15 -8
  17. package/templates/base/src/config/color-scheme-utils.ts +5 -3
  18. package/templates/base/src/utils/base.ts +13 -1
  19. package/templates/base/src/utils/dedent.ts +1 -1
  20. package/templates/base/src/utils/docs.ts +21 -4
  21. package/templates/base/src/utils/smart-break.tsx +2 -2
  22. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +2 -3
  23. package/templates/features/docTags/files/pages/docs/tags/index.tsx +2 -3
  24. package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +52 -126
  25. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +4 -1
  26. package/templates/features/versioning/files/pages/v/[version]/[locale]/docs/[[...slug]].tsx +61 -125
  27. package/templates/features/versioning/files/pages/v/[version]/docs/[[...slug]].tsx +62 -131
  28. package/templates/base/src/components/html-preview/highlighted-code.tsx +0 -74
  29. package/templates/base/src/components/html-preview/html-preview.tsx +0 -108
  30. package/templates/base/src/components/html-preview/preflight.ts +0 -112
  31. package/templates/base/src/components/html-preview/preview-base.tsx +0 -159
  32. package/templates/base/src/components/mobile-toc.tsx +0 -94
  33. package/templates/base/src/components/toc.tsx +0 -63
@@ -5,6 +5,7 @@ export interface LightDarkPairing {
5
5
  }
6
6
  export declare const LIGHT_DARK_PAIRINGS: LightDarkPairing[];
7
7
  export declare const SINGLE_SCHEMES: string[];
8
+ export declare const LIGHT_SCHEMES: string[];
8
9
  export interface SupportedLang {
9
10
  value: string;
10
11
  label: string;
@@ -18,3 +19,4 @@ export interface Feature {
18
19
  cliFlag: string;
19
20
  }
20
21
  export declare const FEATURES: Feature[];
22
+ export declare const HEADER_RIGHT_LABELS: Record<string, string>;
package/dist/constants.js CHANGED
@@ -55,6 +55,19 @@ export const SINGLE_SCHEMES = [
55
55
  "Gruvbox Light",
56
56
  "Ayu Light",
57
57
  ];
58
+ // Light-only subset of SINGLE_SCHEMES. Used by the preset generator to populate
59
+ // the "Light scheme" dropdown (dark schemes are derived as SINGLE_SCHEMES minus these).
60
+ export const LIGHT_SCHEMES = [
61
+ "Default Light",
62
+ "GitHub Light",
63
+ "Catppuccin Latte",
64
+ "Solarized Light",
65
+ "Rose Pine Dawn",
66
+ "Atom One Light",
67
+ "Everforest Light",
68
+ "Gruvbox Light",
69
+ "Ayu Light",
70
+ ];
58
71
  export const SUPPORTED_LANGS = [
59
72
  { value: "en", label: "English" },
60
73
  { value: "ja", label: "Japanese" },
@@ -222,3 +235,16 @@ export const FEATURES = [
222
235
  cliFlag: "footer-taglist",
223
236
  },
224
237
  ];
238
+ // Display labels for header-right items. Keys are canonical component/trigger
239
+ // names from HeaderRightComponentName / HeaderRightTriggerName
240
+ // (src/config/settings-types.ts in the host); they are deliberately not imported
241
+ // here so constants.ts stays pure data with no cross-package dependencies.
242
+ export const HEADER_RIGHT_LABELS = {
243
+ "version-switcher": "Version switcher",
244
+ "design-token-panel": "Design token panel (trigger)",
245
+ "ai-chat": "AI chat (trigger)",
246
+ "github-link": "GitHub link",
247
+ "theme-toggle": "Theme toggle",
248
+ search: "Search",
249
+ "language-switcher": "Language switcher",
250
+ };
package/dist/scaffold.js CHANGED
@@ -258,16 +258,22 @@ function generatePackageJson(choices) {
258
258
  // disabled, reproducible CSS-Modules scoped names (project-relative paths),
259
259
  // dev-mode git-restore detection, Tailwind temp-file cleanup, and a
260
260
  // near-miss `"use client"` directive scanner.
261
- "@takazudo/zfb": "0.1.0-next.31",
262
- "@takazudo/zfb-runtime": "0.1.0-next.31",
261
+ // next.33 added the opt-in hierarchical heading-ID strategy
262
+ // (Takazudo/zudo-front-builder#871): `markdown.features.headingIds.strategy`.
263
+ // The generated config + TOC builder use it via settings.headingIdStrategy.
264
+ // next.35 fixes resolve_links rewriting bare same-page `[text](#anchor)` /
265
+ // `[text](?query)` links to `/<parent-dir>/#anchor` (zudolab/zudo-doc#1948,
266
+ // upstream Takazudo/zudo-front-builder#875).
267
+ "@takazudo/zfb": "0.1.0-next.35",
268
+ "@takazudo/zfb-runtime": "0.1.0-next.35",
263
269
  // zfb-adapter-cloudflare — required for any route with `prerender = false`.
264
270
  // Pinned in lockstep with @takazudo/zfb.
265
- "@takazudo/zfb-adapter-cloudflare": "0.1.0-next.31",
271
+ "@takazudo/zfb-adapter-cloudflare": "0.1.0-next.35",
266
272
  // @takazudo/zudo-doc — published from this monorepo via
267
273
  // .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
268
274
  // lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
269
275
  // version moves, so a fresh scaffold pulls the version we just published.
270
- "@takazudo/zudo-doc": "^0.2.0-next.5",
276
+ "@takazudo/zudo-doc": "^0.2.0-next.7",
271
277
  // zod — used by the generated zfb.config.ts. zfb-config-gen emits
272
278
  // `import { z } from "zod"` for the content-collection schema +
273
279
  // `z.toJSONSchema(...)` conversion. Without this dep, the consumer
@@ -325,7 +331,7 @@ function generatePackageJson(choices) {
325
331
  // @takazudo/zudo-doc/integrations/doc-history which in turn imports
326
332
  // @takazudo/zudo-doc-history-server/git-history. Without this dep the
327
333
  // plugin host fails at init with ERR_MODULE_NOT_FOUND — W8A (#1739).
328
- deps["@takazudo/zudo-doc-history-server"] = "^0.2.0-next.5";
334
+ deps["@takazudo/zudo-doc-history-server"] = "^0.2.0-next.7";
329
335
  // W7A (#1736): doc-history-plugin.mjs spawns `tsx -e <inline-script>` to
330
336
  // run the v2 runtime in a TS-aware Node subprocess; without tsx the
331
337
  // plugin's preBuild step exits with ENOENT before zfb finishes config
@@ -130,6 +130,15 @@ export function generateSettingsFile(choices) {
130
130
  else {
131
131
  lines.push(` designTokenPanel: false as boolean,`);
132
132
  }
133
+ lines.push(` tocMinDepth: 2 as number,`);
134
+ lines.push(` tocMaxDepth: 4 as number,`);
135
+ // Heading-ID (anchor) strategy — single source of truth shared by
136
+ // zfb.config.ts (markdown.features.headingIds) and the host TOC builder
137
+ // (pages/lib/_extract-headings.ts). "hierarchical" emits ancestor-prefixed
138
+ // anchors (foo / foo-moo / foo-moo-mew); "flat" is zfb's legacy scheme.
139
+ // Default to "hierarchical": safe for greenfield (no existing deep links to
140
+ // break) and the recommended scheme (upstream zfb#871).
141
+ lines.push(` headingIdStrategy: "hierarchical" as "flat" | "hierarchical",`);
133
142
  if (choices.features.includes("sidebarResizer")) {
134
143
  lines.push(` sidebarResizer: true as boolean,`);
135
144
  }
@@ -152,6 +152,7 @@ export function generateZfbConfig(choices) {
152
152
  lines.push(` options: {`);
153
153
  lines.push(` docsDir: settings.docsDir,`);
154
154
  lines.push(` locales: localeRecord,`);
155
+ lines.push(` base: settings.base,`);
155
156
  lines.push(` },`);
156
157
  lines.push(` },`);
157
158
  lines.push(` ]`);
@@ -211,6 +212,16 @@ export function generateZfbConfig(choices) {
211
212
  lines.push(` dir: locale.dir,`);
212
213
  lines.push(` routePrefix: \`/\${code}/docs/\`,`);
213
214
  lines.push(` })),`);
215
+ lines.push(` // Versioned collections: each version's EN dir + per-locale dirs.`);
216
+ lines.push(` ...(settings.versions`);
217
+ lines.push(` ? settings.versions.flatMap((version) => [`);
218
+ lines.push(` { dir: version.docsDir, routePrefix: \`/v/\${version.slug}/docs/\` },`);
219
+ lines.push(` ...Object.entries(version.locales ?? {}).map(([code, locale]) => ({`);
220
+ lines.push(` dir: locale.dir,`);
221
+ lines.push(` routePrefix: \`/v/\${version.slug}/\${code}/docs/\`,`);
222
+ lines.push(` })),`);
223
+ lines.push(` ])`);
224
+ lines.push(` : []),`);
214
225
  lines.push(` ],`);
215
226
  lines.push(` onBrokenLinks: "warn",`);
216
227
  lines.push(` },`);
@@ -271,6 +282,10 @@ export function generateZfbConfig(choices) {
271
282
  lines.push(` imageDimensions: {},`);
272
283
  lines.push(` // warn-only link validation — failOnBroken: false never fails the build.`);
273
284
  lines.push(` linkValidation: { failOnBroken: false },`);
285
+ lines.push(` // Heading-ID (anchor) strategy — single source of truth in`);
286
+ lines.push(` // settings.headingIdStrategy, also mirrored by the host TOC builder`);
287
+ lines.push(` // (pages/lib/_extract-headings.ts) so TOC anchors match rendered ids.`);
288
+ lines.push(` headingIds: { strategy: settings.headingIdStrategy },`);
274
289
  lines.push(` },`);
275
290
  lines.push(` },`);
276
291
  lines.push(` plugins: integrationPlugins,`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "0.2.0-next.5",
3
+ "version": "0.2.0-next.7",
4
4
  "description": "Create a new zudo-doc documentation site",
5
5
  "license": "MIT",
6
6
  "author": "Takeshi Takatsudo",
@@ -13,6 +13,7 @@
13
13
  import { getCollection } from "zfb/content";
14
14
  import type { CollectionEntry } from "zfb/content";
15
15
  import type { DocsEntry } from "@/types/docs-entry";
16
+ import type { DocPageEntry } from "./lib/doc-page-props";
16
17
  import { toRouteSlug } from "@/utils/slug";
17
18
 
18
19
  // ---------------------------------------------------------------------------
@@ -79,7 +80,7 @@ export type ZfbDocsEntry = CollectionEntry<ZfbDocsData> & {
79
80
  * - `collection` — the collection name, for DocsEntry compat
80
81
  */
81
82
  export function getDocs(collectionName: string): ZfbDocsEntry[] {
82
- const entries = getCollection(collectionName) as unknown as CollectionEntry<ZfbDocsData>[];
83
+ const entries = getCollection<ZfbDocsData>(collectionName);
83
84
  return entries.map((e) => ({
84
85
  ...e,
85
86
  // Astro-compat: strip a trailing `/index` from the entry id so
@@ -123,27 +124,43 @@ export function bridgeEntries<T = ZfbDocsData>(
123
124
  }
124
125
 
125
126
  /**
126
- * Cast ZfbDocsEntry[] to DocsEntry[] for passing to @/utils/docs utilities.
127
+ * Typed bridge from a raw zfb collection result to `DocPageEntry[]`.
127
128
  *
128
- * The types are structurally compatible: ZfbDocsEntry has every required field
129
- * of DocsEntry (id, collection, data, body). The optional `rendered` and
130
- * `filePath` fields of DocsEntry are absent but not required.
129
+ * This is the **single, justified** cast at the zfb/DocsEntry boundary.
130
+ * `CollectionEntry<ZfbDocsData> & { id, collection }` structurally satisfies
131
+ * `DocPageEntry` because:
132
+ * - `id` and `collection` are added by `bridgeEntries`
133
+ * - `data` (ZfbDocsData) structurally satisfies `DocsEntry.data` (all
134
+ * required/optional fields are present; the index signature is wider)
135
+ * - `body`, `slug`, `module_specifier`, `Content` are provided by
136
+ * `CollectionEntry<ZfbDocsData>`
137
+ * The plain `as DocPageEntry[]` (not `as unknown as`) is intentional — it
138
+ * expresses that this is a well-understood structural subtype relationship,
139
+ * not an escape from the type system. The zfb type is the source of truth;
140
+ * DocsEntry/DocPageEntry are local compatibility shapes for @/utils/docs.
131
141
  */
132
- export function asDocsEntries(entries: ZfbDocsEntry[]): DocsEntry[] {
133
- return entries as unknown as DocsEntry[];
142
+ export function bridgeDocsEntries(
143
+ entries: ReadonlyArray<CollectionEntry<ZfbDocsData>>,
144
+ collectionName: string,
145
+ ): DocPageEntry[] {
146
+ return bridgeEntries(entries, collectionName) as DocPageEntry[];
134
147
  }
135
148
 
136
149
  /**
137
150
  * One-shot helper for paths()/render-time pages that just need a
138
- * `DocsEntry[]` for `@/utils/docs` consumption — wraps `getDocs` and
139
- * the `asDocsEntries` cast so call sites stay one-line. Use this from
140
- * any page that previously did
151
+ * `DocsEntry[]` for `@/utils/docs` consumption — wraps `getDocs` so
152
+ * call sites stay one-line. Use this from any page that previously did
141
153
  * `getCollection("docs") as unknown as DocsEntry[]` — that idiom
142
154
  * silently dropped the `id`/`collection` fields the utility helpers
143
155
  * read, which threw `Cannot read properties of undefined` at runtime.
156
+ *
157
+ * `ZfbDocsEntry` structurally satisfies `DocsEntry`: it carries `id`,
158
+ * `collection`, `data` (ZfbDocsData satisfies DocsEntry.data field-for-
159
+ * field), `body`, plus the zfb-specific extras (`slug`, `Content`, etc.)
160
+ * that DocsEntry does not require.
144
161
  */
145
162
  export function loadDocs(collectionName: string): DocsEntry[] {
146
- return asDocsEntries(getDocs(collectionName));
163
+ return getDocs(collectionName);
147
164
  }
148
165
 
149
166
  /**
@@ -22,44 +22,32 @@
22
22
  // Locale: defaultLocale (EN). Non-default locales are handled by
23
23
  // pages/[locale]/docs/[[...slug]].tsx.
24
24
 
25
- import type { DocsEntry } from "@/types/docs-entry";
26
25
  import { settings } from "@/config/settings";
27
26
  import { defaultLocale } from "@/config/i18n";
28
- import { docsUrl } from "@/utils/base";
27
+ import { docsUrl, absoluteUrl } from "@/utils/base";
29
28
  import {
30
29
  buildNavTree,
31
30
  buildBreadcrumbs,
32
- flattenTree,
33
- findNode,
34
31
  collectAutoIndexNodes,
35
32
  type NavNode,
36
33
  } from "@/utils/docs";
37
34
  import { getNavSectionForSlug, getNavSubtree } from "@/utils/nav-scope";
38
35
  import { toRouteSlug, toSlugParams } 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
36
  // Shared MDX-tag → Preact-component bag. Includes htmlOverrides
43
37
  // (native typography), HtmlPreviewWrapper (Island), and stub bindings
44
38
  // for every other custom tag the MDX corpus references — see
45
39
  // `pages/_mdx-components.ts` for the full list and rationale.
46
40
  import { createMdxComponents } from "../_mdx-components";
47
- import { FooterWithDefaults } from "../lib/_footer-with-defaults";
48
41
  import { DocHistoryArea } from "../lib/_doc-history-area";
49
42
  import { DocMetainfoArea } from "../lib/_doc-metainfo-area";
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 { composeMetaTitle } from "../lib/_compose-meta-title";
54
43
  import { buildInlineVersionSwitcher } from "../lib/_inline-version-switcher";
55
44
  import type { JSX } from "preact";
56
45
  import { resolveNavSource } from "../lib/_nav-source-docs";
57
46
  import { extractHeadings } from "../lib/_extract-headings";
58
47
  import type { DocPageEntry, AutoIndexNode, DocPageEntryProps, DocPageAutoIndexProps } from "../lib/doc-page-props";
59
- import { DocPager } from "../lib/_doc-pager";
60
48
  import { DocContentHeader } from "../lib/_doc-content-header";
61
- import { SidebarPrepaint } from "../lib/_sidebar-prepaint";
62
- import { DocBodyEnd } from "../lib/_doc-body-end";
49
+ import { DocPageShell } from "../lib/_doc-page-shell";
50
+ import { resolveDocPrevNext, flattenSubtree } from "../lib/_doc-route-paths";
63
51
 
64
52
  export const frontmatter = { title: "Docs" };
65
53
 
@@ -94,9 +82,9 @@ export function paths(): Array<{
94
82
  const { docs, navDocs, categoryMeta } = resolveNavSource(locale, undefined);
95
83
 
96
84
  // Nav docs: exclude unlisted (for sidebar/prev-next) but keep for breadcrumbs
97
- const tree = buildNavTree(navDocs as unknown as DocsEntry[], locale, categoryMeta);
85
+ const tree = buildNavTree(navDocs, locale, categoryMeta);
98
86
  // Full tree (including unlisted) for accurate breadcrumbs
99
- const fullTree = buildNavTree(docs as unknown as DocsEntry[], locale, categoryMeta);
87
+ const fullTree = buildNavTree(docs, locale, categoryMeta);
100
88
 
101
89
  const result: Array<{ params: { slug: string[] }; props: DocPageProps }> = [];
102
90
 
@@ -105,29 +93,15 @@ export function paths(): Array<{
105
93
  const slug = entry.data.slug ?? toRouteSlug(entry.slug);
106
94
  const navSection = getNavSectionForSlug(slug);
107
95
  const subtree = getNavSubtree(tree, navSection);
108
- const flat = flattenTree(subtree);
109
- const idx = flat.findIndex((n) => n.slug === slug);
110
96
 
111
- let prevNode = idx > 0 ? flat[idx - 1] ?? null : null;
112
- let nextNode = idx >= 0 && idx < flat.length - 1 ? flat[idx + 1] ?? null : null;
113
-
114
- // Frontmatter pagination overrides
115
- if (entry.data.pagination_prev !== undefined) {
116
- if (entry.data.pagination_prev === null) {
117
- prevNode = null;
118
- } else {
119
- const found = findNode(tree, entry.data.pagination_prev);
120
- prevNode = found ?? prevNode;
121
- }
122
- }
123
- if (entry.data.pagination_next !== undefined) {
124
- if (entry.data.pagination_next === null) {
125
- nextNode = null;
126
- } else {
127
- const found = findNode(tree, entry.data.pagination_next);
128
- nextNode = found ?? nextNode;
129
- }
130
- }
97
+ // Prev/next + frontmatter pagination overrides resolved against THIS
98
+ // route's own `tree`. Latest route hrefs stay unversioned (no rewrite).
99
+ const { prev: prevNode, next: nextNode } = resolveDocPrevNext(
100
+ tree,
101
+ flattenSubtree(subtree),
102
+ slug,
103
+ entry.data,
104
+ );
131
105
 
132
106
  result.push({
133
107
  params: { slug: toSlugParams(slug) },
@@ -181,7 +155,8 @@ export default function DocsPage(props: PageArgs): JSX.Element {
181
155
  // locale so CategoryNav/CategoryTreeNav/SiteTreeNav query the right collection.
182
156
  const components = createMdxComponents(locale);
183
157
 
184
- // Resolve child hrefs for auto-index pages
158
+ // Resolve child hrefs for auto-index pages — latest route keeps the nav
159
+ // node's own docsUrl href (fallback for a noPage parent without an href).
185
160
  const autoIndexChildren = props.kind === "autoIndex"
186
161
  ? props.autoIndex.children
187
162
  .filter((c: NavNode) => c.hasPage || c.children.length > 0)
@@ -191,12 +166,9 @@ export default function DocsPage(props: PageArgs): JSX.Element {
191
166
  }))
192
167
  : [];
193
168
 
194
- // Canonical URL — only when siteUrl is configured. pageUrl is the
195
- // base-prefixed path for this page without the siteUrl origin.
196
- const pageUrl = docsUrl(slug, locale);
197
- const canonical = settings.siteUrl
198
- ? settings.siteUrl.replace(/\/$/, "") + pageUrl
199
- : undefined;
169
+ // Canonical URL — base-prefixed page path, absolutized against siteUrl.
170
+ const currentPath = docsUrl(slug, locale);
171
+ const canonical = absoluteUrl(currentPath);
200
172
 
201
173
  // Persist key: locale + nav-section so the sidebar DOM node is reused
202
174
  // across same-locale + same-section navigations only. No sanitizer needed —
@@ -209,95 +181,46 @@ export default function DocsPage(props: PageArgs): JSX.Element {
209
181
  : `sidebar-${locale}-${navSection ?? "default"}`;
210
182
 
211
183
  return (
212
- <DocLayoutWithDefaults
213
- title={composeMetaTitle(title)}
184
+ <DocPageShell
185
+ kind={props.kind}
186
+ locale={locale}
187
+ slug={slug}
188
+ title={title}
214
189
  description={description}
215
- head={<HeadWithDefaults title={title} description={description} canonical={canonical} />}
216
- lang={locale}
217
- noindex={settings.noindex}
218
- hideSidebar={hideSidebar}
219
- hideToc={props.kind === "entry" ? props.entry.data.hide_toc : undefined}
220
- headings={headings}
221
190
  canonical={canonical}
191
+ breadcrumbs={breadcrumbs}
192
+ prev={prev}
193
+ next={next}
194
+ headings={headings}
195
+ navSection={navSection}
222
196
  sidebarPersistKey={sidebarPersistKey}
223
- headerOverride={
224
- <HeaderWithDefaults
225
- lang={locale}
226
- currentSlug={slug}
227
- navSection={getNavSectionForSlug(slug)}
228
- currentPath={docsUrl(slug, locale)}
229
- />
197
+ hideSidebar={hideSidebar}
198
+ hideToc={props.kind === "entry" ? props.entry.data.hide_toc : undefined}
199
+ currentPath={currentPath}
200
+ versionSwitcher={buildInlineVersionSwitcher(slug, locale)}
201
+ autoIndexLabel={props.kind === "autoIndex" ? props.autoIndex.label : undefined}
202
+ autoIndexChildren={autoIndexChildren}
203
+ metainfoSlot={
204
+ props.kind === "autoIndex" ? <DocMetainfoArea slug={slug} locale={locale} /> : null
230
205
  }
231
- breadcrumbOverride={
232
- breadcrumbs.length > 0 ? (
233
- <Breadcrumb
234
- items={breadcrumbs}
235
- rightSlot={buildInlineVersionSwitcher(slug, locale)}
236
- />
206
+ contentHeaderSlot={
207
+ props.kind === "entry" ? (
208
+ <DocContentHeader entry={props.entry} slug={slug} locale={locale} />
237
209
  ) : undefined
238
210
  }
239
- sidebarOverride={
240
- <SidebarWithDefaults
241
- currentSlug={slug}
242
- lang={locale}
243
- navSection={getNavSectionForSlug(slug)}
244
- currentPath={docsUrl(slug, locale)}
245
- />
211
+ contentSlot={
212
+ props.kind === "entry" ? <props.entry.Content components={components} /> : undefined
246
213
  }
247
- afterSidebar={<SidebarPrepaint />}
248
- footerOverride={<FooterWithDefaults lang={locale} />}
249
- bodyEndComponents={<DocBodyEnd />}
250
- >
251
- {props.kind === "autoIndex" ? (
252
- /* Auto-index page: category without an index.mdx.
253
- Fragment (not <div>) so children become direct children of
254
- <article class="zd-content">, picking up the flow-space rule
255
- (.zd-content > :where(* + *) { margin-top: var(--flow-space) }).
256
- Wrapping in <div> would make h1/description p children-of-children
257
- and the flow gap (~24px) would never apply — see #1460. */
258
- <>
259
- <h1 class="text-heading font-bold mb-vsp-xs">{props.autoIndex.label}</h1>
260
-
261
- {/* Build-time date block — chrome parity (#1461). Auto-index pages
262
- previously rendered without doc-meta; reference site shows it on
263
- every docs page. The component returns null when no manifest
264
- entry exists for this slug. */}
265
- <DocMetainfoArea slug={slug} locale={locale} />
266
-
267
- {props.autoIndex.description && (
268
- <p class="mb-vsp-lg text-title text-muted">
269
- {props.autoIndex.description}
270
- </p>
271
- )}
272
- <NavCardGrid children={autoIndexChildren} />
273
- </>
274
- ) : (
275
- /* Regular doc page. Fragment (not <div>) for the same reason as
276
- the auto-index branch above — see #1460. */
277
- <>
278
- <DocContentHeader entry={props.entry} slug={slug} locale={locale} />
279
-
280
- {/* MDX content rendered via zfb's Content bridge */}
281
- <props.entry.Content components={components} />
282
-
283
- {/* Prev / Next pagination — placed before the document utilities
284
- section to match the Astro reference order: content → pager →
285
- view-source / history. In the Astro layout, BodyFootUtilArea was
286
- rendered by the doc-layout wrapper after the <slot /> content,
287
- so the pager (inside the slot) came first. Fixes #1535. */}
288
- <DocPager prev={prev} next={next} locale={locale} />
289
-
290
- {/* Document utilities (revision history + view-source link) — skipped for unlisted pages */}
291
- {!props.entry.data.unlisted && (
292
- <DocHistoryArea
293
- slug={slug}
294
- locale={locale}
295
- entrySlug={props.entry.slug}
296
- contentDir={settings.docsDir}
297
- />
298
- )}
299
- </>
300
- )}
301
- </DocLayoutWithDefaults>
214
+ docHistorySlot={
215
+ props.kind === "entry" && !props.entry.data.unlisted ? (
216
+ <DocHistoryArea
217
+ slug={slug}
218
+ locale={locale}
219
+ entrySlug={props.entry.slug}
220
+ contentDir={settings.docsDir}
221
+ />
222
+ ) : null
223
+ }
224
+ />
302
225
  );
303
226
  }
@@ -13,9 +13,10 @@
13
13
  // (b11-2 pattern).
14
14
  //
15
15
  // Date formatting uses Intl.DateTimeFormat (browser-safe). We do NOT
16
- // import `formatDate` from `src/utils/git-info.ts` because that module
17
- // has top-level Node.js imports (`execFileSync`, `existsSync`) that
18
- // would be dragged into the client bundle the B-11 lesson.
16
+ // import the old `formatDate` from `src/utils/git-info.ts` that module
17
+ // carried top-level Node.js imports (`execFileSync`, `existsSync`) that
18
+ // would be dragged into the client bundle (the B-11 lesson). That file
19
+ // was removed in S1 cleanup (#1928); the mirror below is the canonical copy.
19
20
  //
20
21
  // Labels are resolved from the project's i18n table so non-default
21
22
  // locales (e.g. /ja/) get translated "作成" / "更新" strings.
@@ -36,9 +37,8 @@ import { toHistorySlug } from "@/utils/slug";
36
37
  import docHistoryMeta from "#doc-history-meta";
37
38
 
38
39
  // BCP-47 locale tag mapping used by Intl.DateTimeFormat.
39
- // Kept in sync with `src/utils/git-info.ts` manually; we cannot import
40
- // that module here because it carries top-level Node.js imports
41
- // (`execFileSync`, `existsSync`) — the B-11 lesson applies here too.
40
+ // Originally mirrored from `src/utils/git-info.ts` (removed in S1 #1928).
41
+ // The formatDate function below is the stable copy; kept in sync manually.
42
42
  const LOCALE_TO_BCP47: Record<string, string> = {
43
43
  en: "en-US",
44
44
  ja: "ja-JP",