create-zudo-doc 3.2.0 → 3.3.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 (122) hide show
  1. package/dist/claude-md-gen.d.ts +8 -0
  2. package/dist/claude-md-gen.js +43 -26
  3. package/dist/compose.d.ts +14 -20
  4. package/dist/compose.js +15 -25
  5. package/dist/features/body-foot-util.d.ts +8 -4
  6. package/dist/features/body-foot-util.js +8 -4
  7. package/dist/features/claude-resources.d.ts +12 -0
  8. package/dist/features/claude-resources.js +13 -6
  9. package/dist/features/design-token-panel.d.ts +0 -12
  10. package/dist/features/design-token-panel.js +32 -93
  11. package/dist/features/doc-history.d.ts +18 -3
  12. package/dist/features/doc-history.js +45 -60
  13. package/dist/features/doc-tags.d.ts +9 -13
  14. package/dist/features/doc-tags.js +10 -26
  15. package/dist/features/dynamic-page-transition.d.ts +19 -30
  16. package/dist/features/dynamic-page-transition.js +21 -209
  17. package/dist/features/footer-taglist.d.ts +1 -1
  18. package/dist/features/footer-taglist.js +1 -1
  19. package/dist/features/footer.d.ts +3 -2
  20. package/dist/features/footer.js +3 -2
  21. package/dist/features/i18n.d.ts +13 -8
  22. package/dist/features/i18n.js +14 -9
  23. package/dist/features/image-enlarge.d.ts +7 -26
  24. package/dist/features/image-enlarge.js +7 -26
  25. package/dist/features/llms-txt.d.ts +3 -5
  26. package/dist/features/llms-txt.js +3 -5
  27. package/dist/features/search.d.ts +7 -3
  28. package/dist/features/search.js +7 -3
  29. package/dist/features/sidebar-resizer.d.ts +4 -8
  30. package/dist/features/sidebar-resizer.js +4 -8
  31. package/dist/features/sidebar-toggle.d.ts +7 -7
  32. package/dist/features/sidebar-toggle.js +7 -7
  33. package/dist/features/tag-governance.d.ts +14 -7
  34. package/dist/features/tag-governance.js +52 -8
  35. package/dist/features/tauri.d.ts +13 -10
  36. package/dist/features/tauri.js +16 -52
  37. package/dist/features/versioning.d.ts +12 -24
  38. package/dist/features/versioning.js +13 -39
  39. package/dist/index.js +2 -3
  40. package/dist/scaffold.d.ts +13 -6
  41. package/dist/scaffold.js +64 -45
  42. package/dist/utils.d.ts +10 -0
  43. package/dist/utils.js +14 -0
  44. package/dist/zfb-config-gen.d.ts +32 -20
  45. package/dist/zfb-config-gen.js +400 -53
  46. package/package.json +2 -2
  47. package/templates/base/pages/docs/[[...slug]].tsx +59 -0
  48. package/templates/base/pages/index.tsx +6 -41
  49. package/templates/base/src/styles/global.css +15 -340
  50. package/templates/base/tsconfig.json +3 -26
  51. package/templates/features/i18n/files/pages/[locale]/docs/[[...slug]].tsx +88 -0
  52. package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +3 -1
  53. package/dist/settings-gen.d.ts +0 -2
  54. package/dist/settings-gen.js +0 -320
  55. package/templates/base/.htmlvalidate.json +0 -5
  56. package/templates/base/.zfb/doc-history-meta.json +0 -1
  57. package/templates/base/pages/_data.ts +0 -160
  58. package/templates/base/pages/lib/_body-end-islands.tsx +0 -165
  59. package/templates/base/pages/lib/_chrome.ts +0 -167
  60. package/templates/base/pages/lib/_details.tsx +0 -29
  61. package/templates/base/pages/lib/_doc-route-entries.ts +0 -10
  62. package/templates/base/pages/lib/_extract-headings.ts +0 -37
  63. package/templates/base/pages/lib/_frontmatter-preview-data.ts +0 -27
  64. package/templates/base/pages/lib/_nav-source-cache.ts +0 -100
  65. package/templates/base/pages/lib/_nav-source-docs.ts +0 -17
  66. package/templates/base/pages/lib/_preset-generator.tsx +0 -81
  67. package/templates/base/pages/lib/_route-context.ts +0 -32
  68. package/templates/base/pages/lib/_search-widget.tsx +0 -17
  69. package/templates/base/pages/lib/doc-page-props.ts +0 -30
  70. package/templates/base/pages/lib/locale-merge.ts +0 -59
  71. package/templates/base/scripts/run-b4push.sh +0 -102
  72. package/templates/base/src/components/ai-chat-modal.tsx +0 -18
  73. package/templates/base/src/components/content/code-group.tsx +0 -3
  74. package/templates/base/src/components/content/content-admonition.tsx +0 -4
  75. package/templates/base/src/components/desktop-sidebar-toggle.tsx +0 -15
  76. package/templates/base/src/components/doc-history.tsx +0 -21
  77. package/templates/base/src/components/image-enlarge.tsx +0 -24
  78. package/templates/base/src/components/preset-generator.tsx +0 -14
  79. package/templates/base/src/components/sidebar-toggle.tsx +0 -6
  80. package/templates/base/src/components/sidebar-tree.tsx +0 -6
  81. package/templates/base/src/config/color-scheme-utils.ts +0 -69
  82. package/templates/base/src/config/color-schemes.ts +0 -165
  83. package/templates/base/src/config/docs-schema.ts +0 -95
  84. package/templates/base/src/config/frontmatter-preview-defaults.ts +0 -27
  85. package/templates/base/src/config/frontmatter-preview-renderers.tsx +0 -46
  86. package/templates/base/src/config/i18n.ts +0 -239
  87. package/templates/base/src/config/settings-types.ts +0 -45
  88. package/templates/base/src/config/sidebars.ts +0 -66
  89. package/templates/base/src/config/tag-vocabulary-types.ts +0 -4
  90. package/templates/base/src/config/tag-vocabulary.ts +0 -20
  91. package/templates/base/src/config/z-index-tokens.ts +0 -128
  92. package/templates/base/src/types/docs-entry.ts +0 -28
  93. package/templates/base/src/types/heading.ts +0 -5
  94. package/templates/base/src/types/locale.ts +0 -10
  95. package/templates/base/src/utils/base.ts +0 -164
  96. package/templates/base/src/utils/docs.ts +0 -446
  97. package/templates/base/src/utils/git-info.ts +0 -70
  98. package/templates/base/src/utils/github.ts +0 -22
  99. package/templates/base/src/utils/nav-scope.ts +0 -34
  100. package/templates/base/src/utils/sidebar.ts +0 -36
  101. package/templates/base/src/utils/slug.ts +0 -10
  102. package/templates/base/src/utils/smart-break.tsx +0 -12
  103. package/templates/base/src/utils/tags.ts +0 -83
  104. package/templates/base/zfb-shim.d.ts +0 -183
  105. package/templates/features/bodyFootUtil/files/src/utils/github.ts +0 -22
  106. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +0 -42
  107. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +0 -752
  108. package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +0 -97
  109. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +0 -735
  110. package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +0 -15
  111. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +0 -435
  112. package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +0 -174
  113. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +0 -30
  114. package/templates/features/docHistory/files/src/components/doc-history.tsx +0 -10
  115. package/templates/features/docHistory/files/src/types/doc-history.ts +0 -7
  116. package/templates/features/dynamicPageTransition/files/src/components/client-router-bootstrap.tsx +0 -72
  117. package/templates/features/i18n/files/pages/[locale]/index.tsx +0 -72
  118. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +0 -11
  119. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +0 -6
  120. package/templates/features/tauri/files/src/components/find-bar.tsx +0 -122
  121. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +0 -59
  122. package/templates/features/tauri/files/src/utils/find-in-page.ts +0 -175
@@ -1,320 +0,0 @@
1
- import { capitalize, getLangLabel, getSecondaryLang } from "./utils.js";
2
- export function generateSettingsFile(choices) {
3
- const lines = [];
4
- // Import types from settings-types (copied from template src/config/)
5
- lines.push(`export type {`);
6
- lines.push(` HeaderNavChildItem,`);
7
- lines.push(` HeaderNavItem,`);
8
- lines.push(` HeaderRightItem,`);
9
- lines.push(` ColorModeConfig,`);
10
- lines.push(` HtmlPreviewConfig,`);
11
- lines.push(` LocaleConfig,`);
12
- lines.push(` VersionConfig,`);
13
- lines.push(` FooterConfig,`);
14
- lines.push(` FrontmatterPreviewConfig,`);
15
- lines.push(` BodyFootUtilAreaConfig,`);
16
- lines.push(` TagPlacement,`);
17
- lines.push(` TagGovernanceMode,`);
18
- lines.push(` TagVocabularyEntry,`);
19
- lines.push(` MetaTagsConfig,`);
20
- lines.push(`} from "./settings-types";`);
21
- lines.push(`import type {`);
22
- lines.push(` HeaderNavItem,`);
23
- lines.push(` HeaderRightItem,`);
24
- lines.push(` ColorModeConfig,`);
25
- lines.push(` HtmlPreviewConfig,`);
26
- lines.push(` LocaleConfig,`);
27
- lines.push(` VersionConfig,`);
28
- lines.push(` FooterConfig,`);
29
- lines.push(` FrontmatterPreviewConfig,`);
30
- lines.push(` BodyFootUtilAreaConfig,`);
31
- lines.push(` TagPlacement,`);
32
- lines.push(` TagGovernanceMode,`);
33
- lines.push(` MetaTagsConfig,`);
34
- lines.push(`} from "./settings-types";`);
35
- lines.push(``);
36
- lines.push(`export const settings = {`);
37
- if (choices.colorSchemeMode === "single") {
38
- lines.push(` colorScheme: ${JSON.stringify(choices.singleScheme ?? "Default Dark")},`);
39
- lines.push(` colorMode: false as ColorModeConfig | false,`);
40
- }
41
- else {
42
- lines.push(` colorScheme: ${JSON.stringify(choices.darkScheme ?? "Default Dark")},`);
43
- lines.push(` colorMode: {`);
44
- lines.push(` defaultMode: ${JSON.stringify(choices.defaultMode ?? "dark")},`);
45
- lines.push(` lightScheme: ${JSON.stringify(choices.lightScheme ?? "Default Light")},`);
46
- lines.push(` darkScheme: ${JSON.stringify(choices.darkScheme ?? "Default Dark")},`);
47
- lines.push(` respectPrefersColorScheme: ${choices.respectPrefersColorScheme ?? true},`);
48
- lines.push(` } satisfies ColorModeConfig as ColorModeConfig | false,`);
49
- }
50
- lines.push(` siteName: ${JSON.stringify(capitalize(choices.projectName.replace(/-/g, " ")))},`);
51
- lines.push(` siteDescription: "" as string,`);
52
- lines.push(` base: "/",`);
53
- lines.push(` trailingSlash: false as boolean,`);
54
- lines.push(` minifyHtml: ${choices.minifyHtml ?? true} as boolean,`);
55
- lines.push(` noindex: ${choices.features.includes("noindex")} as boolean,`);
56
- lines.push(` editUrl: false as string | false,`);
57
- const rawGithubUrl = (choices.githubUrl ?? "").trim();
58
- if (rawGithubUrl) {
59
- lines.push(` githubUrl: ${JSON.stringify(rawGithubUrl)} as string | false,`);
60
- }
61
- else {
62
- lines.push(` githubUrl: false as string | false,`);
63
- }
64
- lines.push(` siteUrl: "" as string,`);
65
- lines.push(` metaTags: {`);
66
- if (choices.metaTags) {
67
- const mt = choices.metaTags;
68
- lines.push(` description: ${mt.description !== undefined ? mt.description : true},`);
69
- lines.push(` keywords: ${mt.keywords !== undefined ? JSON.stringify(mt.keywords) : false},`);
70
- lines.push(` ogImage: ${mt.ogImage !== undefined ? JSON.stringify(mt.ogImage) : false},`);
71
- lines.push(` ogSiteName: ${mt.ogSiteName !== undefined ? mt.ogSiteName : true},`);
72
- if (mt.twitterCard) {
73
- lines.push(` twitterCard: ${JSON.stringify(mt.twitterCard)},`);
74
- if (mt.twitterSite) {
75
- lines.push(` twitterSite: ${JSON.stringify(mt.twitterSite)},`);
76
- }
77
- if (mt.twitterCreator) {
78
- lines.push(` twitterCreator: ${JSON.stringify(mt.twitterCreator)},`);
79
- }
80
- }
81
- else {
82
- lines.push(` twitterCard: false,`);
83
- }
84
- }
85
- else {
86
- lines.push(` description: true,`);
87
- lines.push(` keywords: false,`);
88
- lines.push(` ogImage: false,`);
89
- lines.push(` ogSiteName: true,`);
90
- lines.push(` twitterCard: false,`);
91
- }
92
- lines.push(` } satisfies MetaTagsConfig as MetaTagsConfig,`);
93
- lines.push(` docsDir: "src/content/docs",`);
94
- lines.push(` defaultLocale: ${JSON.stringify(choices.defaultLang ?? "en")} as const,`);
95
- if (choices.features.includes("i18n")) {
96
- const secondaryLang = getSecondaryLang(choices.defaultLang);
97
- const secondaryLabel = getLangLabel(secondaryLang);
98
- lines.push(` locales: {`);
99
- lines.push(` ${secondaryLang}: { label: ${JSON.stringify(secondaryLabel)}, dir: "src/content/docs-${secondaryLang}" },`);
100
- lines.push(` } satisfies Record<string, LocaleConfig>,`);
101
- }
102
- else {
103
- // `as`, not `satisfies`: satisfies keeps the inferred type at literal {},
104
- // so Object.entries(settings.locales) in the generated zfb.config.ts
105
- // yields unknown values and `zfb check` fails with TS18046 (#2053).
106
- lines.push(` locales: {} as Record<string, LocaleConfig>,`);
107
- }
108
- // mermaid is controlled by the markdown.features block in zfb.config.ts
109
- // (zfb next.12+). This field is retained for compatibility with framework
110
- // components that still read settings.mermaid. See the markdown.features
111
- // block in the generated zfb.config.ts for the canonical opt-in.
112
- lines.push(` mermaid: true,`);
113
- lines.push(` sitemap: false,`);
114
- lines.push(` docMetainfo: false,`);
115
- lines.push(` docTags: ${choices.features.includes("docTags")},`);
116
- lines.push(` tagPlacement: "after-title" as TagPlacement,`);
117
- if (choices.features.includes("tagGovernance")) {
118
- lines.push(` tagGovernance: "warn" as TagGovernanceMode,`);
119
- lines.push(` tagVocabulary: true as boolean,`);
120
- }
121
- else {
122
- lines.push(` tagGovernance: "off" as TagGovernanceMode,`);
123
- lines.push(` tagVocabulary: false as boolean,`);
124
- }
125
- // Default false — fresh scaffolds typically don't need live frontmatter preview;
126
- // users opt in once they're ready to wire up the preview panel.
127
- lines.push(` frontmatterPreview: false as FrontmatterPreviewConfig | false,`);
128
- if (choices.features.includes("llmsTxt")) {
129
- lines.push(` llmsTxt: true,`);
130
- }
131
- else {
132
- lines.push(` llmsTxt: false,`);
133
- }
134
- lines.push(` changelogs: false,`);
135
- lines.push(` math: false,`);
136
- lines.push(` cjkFriendly: ${choices.cjkFriendly ?? false} as boolean,`);
137
- lines.push(` onBrokenMarkdownLinks: "warn" as "warn" | "error" | "ignore",`);
138
- lines.push(` aiAssistant: false as boolean,`);
139
- // When the user wires up `pages/api/ai-chat.tsx` (not shipped in any
140
- // scaffold variant — W6A spec-lock Decision 5), this toggle short-circuits
141
- // the endpoint with a fixed "disabled" reply. Default `false` here — the
142
- // showcase ships with `true` because it deploys without an Anthropic key,
143
- // but a fresh scaffold has `aiAssistant: false` and the user only enables
144
- // the chat once they're wiring up a real `ANTHROPIC_API_KEY`. Defaulting
145
- // demo mode off avoids silently disabling chat for them.
146
- lines.push(` aiChatDemoMode: false as boolean,`);
147
- lines.push(` aiChatAllowedOrigins: [] as string[],`);
148
- lines.push(` aiChatGlobalDailyLimit: false as number | false,`);
149
- if (choices.features.includes("docHistory")) {
150
- lines.push(` docHistory: true,`);
151
- }
152
- else {
153
- lines.push(` docHistory: false,`);
154
- }
155
- // Package-owned route injection is on by default since the fast-follow (#2372).
156
- // Generated projects include the field explicitly so settings.ts stays in sync
157
- // with src/config/settings.ts (checked by check-fixture-settings-drift.mjs).
158
- lines.push(` packageOwnedRoutes: true,`);
159
- if (choices.features.includes("bodyFootUtil")) {
160
- lines.push(` bodyFootUtilArea: {`);
161
- lines.push(` docHistory: ${choices.features.includes("docHistory")},`);
162
- lines.push(` viewSourceLink: ${Boolean(rawGithubUrl)},`);
163
- lines.push(` } satisfies BodyFootUtilAreaConfig as BodyFootUtilAreaConfig | false,`);
164
- }
165
- else {
166
- lines.push(` bodyFootUtilArea: false as BodyFootUtilAreaConfig | false,`);
167
- }
168
- if (choices.features.includes("designTokenPanel")) {
169
- lines.push(` designTokenPanel: true as boolean,`);
170
- }
171
- else {
172
- lines.push(` designTokenPanel: false as boolean,`);
173
- }
174
- lines.push(` tocMinDepth: 2 as number,`);
175
- lines.push(` tocMaxDepth: 4 as number,`);
176
- // Heading-ID (anchor) strategy — single source of truth shared by
177
- // zfb.config.ts (markdown.features.headingIds) and the host TOC builder
178
- // (pages/lib/_extract-headings.ts). "hierarchical" emits ancestor-prefixed
179
- // anchors (foo / foo-moo / foo-moo-mew); "flat" is zfb's legacy scheme.
180
- // Default to "hierarchical": safe for greenfield (no existing deep links to
181
- // break) and the recommended scheme (upstream zfb#871).
182
- lines.push(` headingIdStrategy: "hierarchical" as "flat" | "hierarchical",`);
183
- if (choices.features.includes("sidebarResizer")) {
184
- lines.push(` sidebarResizer: true as boolean,`);
185
- }
186
- else {
187
- lines.push(` sidebarResizer: false as boolean,`);
188
- }
189
- if (choices.features.includes("sidebarToggle")) {
190
- lines.push(` sidebarToggle: true as boolean,`);
191
- }
192
- else {
193
- lines.push(` sidebarToggle: false as boolean,`);
194
- }
195
- if (choices.features.includes("imageEnlarge")) {
196
- lines.push(` imageEnlarge: true as boolean,`);
197
- }
198
- else {
199
- lines.push(` imageEnlarge: false as boolean,`);
200
- }
201
- if (choices.features.includes("dynamicPageTransition")) {
202
- lines.push(` dynamicPageTransition: true as boolean,`);
203
- }
204
- else {
205
- lines.push(` dynamicPageTransition: false as boolean,`);
206
- }
207
- lines.push(` htmlPreview: undefined as HtmlPreviewConfig | undefined,`);
208
- if (choices.features.includes("versioning")) {
209
- lines.push(` versions: [] satisfies VersionConfig[] as VersionConfig[] | false,`);
210
- }
211
- else {
212
- lines.push(` versions: false as VersionConfig[] | false,`);
213
- }
214
- if (choices.features.includes("claudeResources")) {
215
- lines.push(` claudeResources: {`);
216
- lines.push(` claudeDir: ".claude",`);
217
- lines.push(` } as { claudeDir: string; projectRoot?: string; scanRoot?: string } | false,`);
218
- }
219
- else {
220
- lines.push(` claudeResources: false as { claudeDir: string; projectRoot?: string; scanRoot?: string } | false,`);
221
- }
222
- if (choices.features.includes("claudeResources")) {
223
- lines.push(` defaultLocaleOnlyPrefixes: [`);
224
- lines.push(` "/docs/claude-md/",`);
225
- lines.push(` "/docs/claude-skills/",`);
226
- lines.push(` "/docs/claude-agents/",`);
227
- lines.push(` "/docs/claude-commands/",`);
228
- lines.push(` ] as string[],`);
229
- }
230
- else {
231
- lines.push(` defaultLocaleOnlyPrefixes: [] as string[],`);
232
- }
233
- if (choices.features.includes("footerNavGroup") ||
234
- choices.features.includes("footerCopyright") ||
235
- choices.features.includes("footerTaglist")) {
236
- lines.push(` footer: {`);
237
- if (choices.features.includes("footerNavGroup")) {
238
- lines.push(` links: [`);
239
- lines.push(` {`);
240
- lines.push(` title: "Docs",`);
241
- lines.push(` items: [`);
242
- lines.push(` { label: "Getting Started", href: "/docs/getting-started" },`);
243
- lines.push(` ],`);
244
- lines.push(` },`);
245
- lines.push(` ],`);
246
- }
247
- else {
248
- lines.push(` links: [],`);
249
- }
250
- if (choices.features.includes("footerCopyright")) {
251
- lines.push(` copyright: "Copyright © ${new Date().getFullYear()} Your Name. Built with zudo-doc.",`);
252
- }
253
- if (choices.features.includes("footerTaglist")) {
254
- lines.push(` taglist: {`);
255
- lines.push(` enabled: true,`);
256
- lines.push(` groupBy: "group",`);
257
- lines.push(` },`);
258
- }
259
- lines.push(` } satisfies FooterConfig as FooterConfig | false,`);
260
- }
261
- else {
262
- lines.push(` footer: false as FooterConfig | false,`);
263
- }
264
- lines.push(` headerNav: [`);
265
- lines.push(` { label: "Getting Started", path: "/docs/getting-started", categoryMatch: "getting-started" },`);
266
- // The "claude" categoryMatch is load-bearing beyond the header link: getCategoryOrder()
267
- // derives the satellite-grouping prefixes from headerNav, so without this entry
268
- // groupSatelliteNodes() never nests claude-md/claude-skills/... under the "claude"
269
- // overview node and they spread out as separate top-level cards on the index sitemap.
270
- if (choices.features.includes("claudeResources")) {
271
- lines.push(` { label: "Claude", path: "/docs/claude", categoryMatch: "claude" },`);
272
- }
273
- if (choices.features.includes("changelog")) {
274
- lines.push(` { label: "Changelog", path: "/docs/changelog", categoryMatch: "changelog" },`);
275
- }
276
- lines.push(` ] satisfies HeaderNavItem[] as HeaderNavItem[],`);
277
- lines.push(` headerRightItems: [`);
278
- if (choices.headerRightItems !== undefined) {
279
- // User-supplied override (including empty array): emit each entry verbatim,
280
- // in the chosen order. An empty array means "no header-right items" — honor it.
281
- // DEFENSIVE STRIP: drop any "design-token-panel" trigger when the
282
- // designTokenPanel feature is off — the type "design-token-panel" is absent
283
- // from HeaderRightTriggerName in a feature-off scaffold (gated via
284
- // @slot:settings-types:trigger-names), so emitting it would cause a TS error
285
- // (zudolab/zudo-doc#2162).
286
- for (const item of choices.headerRightItems) {
287
- if (item.type === "trigger" &&
288
- item.trigger === "design-token-panel" &&
289
- !choices.features.includes("designTokenPanel")) {
290
- continue;
291
- }
292
- if (item.type === "trigger") {
293
- lines.push(` { type: "trigger", trigger: ${JSON.stringify(item.trigger)} },`);
294
- }
295
- else {
296
- lines.push(` { type: "component", component: ${JSON.stringify(item.component)} },`);
297
- }
298
- }
299
- }
300
- else {
301
- // Default fallback: hardcoded order, gated on selected features.
302
- if (choices.features.includes("designTokenPanel")) {
303
- lines.push(` { type: "trigger", trigger: "design-token-panel" },`);
304
- }
305
- if (choices.features.includes("versioning")) {
306
- lines.push(` { type: "component", component: "version-switcher" },`);
307
- }
308
- lines.push(` { type: "component", component: "github-link" },`);
309
- lines.push(` { type: "component", component: "theme-toggle" },`);
310
- if (choices.features.includes("search")) {
311
- lines.push(` { type: "component", component: "search" },`);
312
- }
313
- if (choices.features.includes("i18n")) {
314
- lines.push(` { type: "component", component: "language-switcher" },`);
315
- }
316
- }
317
- lines.push(` ] satisfies HeaderRightItem[] as HeaderRightItem[],`);
318
- lines.push(`};`);
319
- return lines.join("\n") + "\n";
320
- }
@@ -1,5 +0,0 @@
1
- {
2
- "rules": {
3
- "element-permitted-content": "error"
4
- }
5
- }
@@ -1 +0,0 @@
1
- {}
@@ -1,160 +0,0 @@
1
- // pages/_data.ts — zfb-compatible data helpers for doc page modules.
2
- //
3
- // Provides the bridge between zfb's CollectionEntry (from "zfb/content") and
4
- // the utility functions in @/utils/docs that expect DocsEntry (which carries
5
- // an `id` field mirroring Astro's collection entry id).
6
- //
7
- // Sync convention (ADR-004):
8
- // getCollection() resolves from the pre-loaded ContentSnapshot without an
9
- // async boundary. paths() exports call getDocs() without await. The Promise
10
- // wrapper on the type is a v0 artefact — the synchronous snapshot path is
11
- // the production contract.
12
-
13
- import { getCollection } from "zfb/content";
14
- import type { CollectionEntry } from "zfb/content";
15
- import type { DocsData } from "@/config/docs-schema";
16
- import type { DocsEntry } from "@/types/docs-entry";
17
- import type { DocPageEntry } from "./lib/doc-page-props";
18
- import { toRouteSlug } from "@/utils/slug";
19
-
20
- // ---------------------------------------------------------------------------
21
- // Types
22
- // ---------------------------------------------------------------------------
23
-
24
- /**
25
- * Frontmatter shape shared by all docs collections (EN, locale, versioned).
26
- *
27
- * Re-exported alias for `DocsData` (the `z.infer`-derived type from
28
- * `src/config/docs-schema.ts`) so call sites that import `ZfbDocsData` from
29
- * `pages/_data` continue to work without changes.
30
- *
31
- * The `[key: string]: unknown` index signature comes from `.passthrough()` on
32
- * the zod schema — custom frontmatter keys remain accessible downstream (e.g.
33
- * for frontmatter-preview) without extra casting.
34
- */
35
- export type ZfbDocsData = DocsData;
36
-
37
- /**
38
- * zfb collection entry augmented with the `id` and `collection` fields that
39
- * @/utils/docs utility functions (buildNavTree, buildBreadcrumbs, …) expect
40
- * from DocsEntry.
41
- *
42
- * `id` is bridged from `slug` — in Astro, `id` was the file-path identifier
43
- * (e.g. "getting-started/intro"); in zfb, the same role is played by `slug`.
44
- * Mapping them keeps the utility functions working without modification.
45
- */
46
- export type ZfbDocsEntry = CollectionEntry<ZfbDocsData> & {
47
- /** Bridged from `slug` for @/utils/docs compat. */
48
- id: string;
49
- /** Collection name, e.g. "docs", "docs-ja", "docs-v-1.0". */
50
- collection: string;
51
- };
52
-
53
- // ---------------------------------------------------------------------------
54
- // Loaders
55
- // ---------------------------------------------------------------------------
56
-
57
- /**
58
- * Load docs from a named collection synchronously (ADR-004 sync contract).
59
- *
60
- * `getCollection` resolves from the ContentSnapshot when called inside a
61
- * paths() evaluation. The `as unknown as` cast converts the nominal Promise
62
- * wrapper to a plain array — safe because the snapshot path is synchronous.
63
- *
64
- * The returned entries include:
65
- * - All CollectionEntry fields (slug, data, body, module_specifier, Content)
66
- * - `id` — same value as `slug`, for @/utils/docs compat
67
- * - `collection` — the collection name, for DocsEntry compat
68
- */
69
- export function getDocs(collectionName: string): ZfbDocsEntry[] {
70
- const entries = getCollection<ZfbDocsData>(collectionName);
71
- return entries.map((e) => ({
72
- ...e,
73
- // Astro-compat: strip a trailing `/index` from the entry id so
74
- // `getting-started/index.mdx` → id "getting-started" (matching
75
- // Astro 5's `glob()` collection loader). Downstream nav helpers
76
- // (`buildNavTree`, `buildBreadcrumbs`, …) keyed off the stripped
77
- // form long before zfb existed; emitting the unstripped slug here
78
- // produces ambiguous-URL collisions at paths()-expansion time.
79
- id: stripIndexSuffix(e.slug),
80
- collection: collectionName,
81
- }));
82
- }
83
-
84
- // The `id` field bridged onto every entry is the canonical route slug, so it
85
- // routes through the one shared rule (`toRouteSlug` in @/utils/slug) — bare
86
- // root `index` → "" (URL /docs/), nested `x/index` → "x". Previously this was
87
- // a standalone copy of the strip logic (the lone "" dissenter of the five
88
- // index-stripping sites); consolidating it here means there is one source of
89
- // truth. See @/utils/slug for the canonical-root rationale (#1891 / #1873).
90
- function stripIndexSuffix(slug: string): string {
91
- return toRouteSlug(slug);
92
- }
93
-
94
- /**
95
- * Augment a raw zfb collection result with the Astro-style
96
- * `id`/`collection` fields that downstream `@/utils/docs` helpers
97
- * (and the `DocPageEntry` extender shape used by `[...slug].tsx`
98
- * pages) expect. Use this when a page needs a typed array more
99
- * specific than `DocsEntry` — pages that only need `DocsEntry[]`
100
- * can use [`loadDocs`] / [`getDocs`] directly.
101
- */
102
- export function bridgeEntries<T = ZfbDocsData>(
103
- entries: ReadonlyArray<CollectionEntry<T>>,
104
- collectionName: string,
105
- ): Array<CollectionEntry<T> & { id: string; collection: string }> {
106
- return entries.map((e) => ({
107
- ...e,
108
- id: stripIndexSuffix(e.slug),
109
- collection: collectionName,
110
- }));
111
- }
112
-
113
- /**
114
- * Typed bridge from a raw zfb collection result to `DocPageEntry[]`.
115
- *
116
- * This is the **single, justified** cast at the zfb/DocsEntry boundary.
117
- * `CollectionEntry<ZfbDocsData> & { id, collection }` structurally satisfies
118
- * `DocPageEntry` because:
119
- * - `id` and `collection` are added by `bridgeEntries`
120
- * - `data` (ZfbDocsData) structurally satisfies `DocsEntry.data` (all
121
- * required/optional fields are present; the index signature is wider)
122
- * - `body`, `slug`, `module_specifier`, `Content` are provided by
123
- * `CollectionEntry<ZfbDocsData>`
124
- * The plain `as DocPageEntry[]` (not `as unknown as`) is intentional — it
125
- * expresses that this is a well-understood structural subtype relationship,
126
- * not an escape from the type system. The zfb type is the source of truth;
127
- * DocsEntry/DocPageEntry are local compatibility shapes for @/utils/docs.
128
- */
129
- export function bridgeDocsEntries(
130
- entries: ReadonlyArray<CollectionEntry<ZfbDocsData>>,
131
- collectionName: string,
132
- ): DocPageEntry[] {
133
- return bridgeEntries(entries, collectionName) as DocPageEntry[];
134
- }
135
-
136
- /**
137
- * One-shot helper for paths()/render-time pages that just need a
138
- * `DocsEntry[]` for `@/utils/docs` consumption — wraps `getDocs` so
139
- * call sites stay one-line. Use this from any page that previously did
140
- * `getCollection("docs") as unknown as DocsEntry[]` — that idiom
141
- * silently dropped the `id`/`collection` fields the utility helpers
142
- * read, which threw `Cannot read properties of undefined` at runtime.
143
- *
144
- * `ZfbDocsEntry` structurally satisfies `DocsEntry`: it carries `id`,
145
- * `collection`, `data` (ZfbDocsData satisfies DocsEntry.data field-for-
146
- * field), `body`, plus the zfb-specific extras (`slug`, `Content`, etc.)
147
- * that DocsEntry does not require.
148
- */
149
- export function loadDocs(collectionName: string): DocsEntry[] {
150
- return getDocs(collectionName);
151
- }
152
-
153
- /**
154
- * Filter out draft entries.
155
- * Drafts are always excluded in static-build paths() context.
156
- */
157
- export function filterDrafts(entries: ZfbDocsEntry[]): ZfbDocsEntry[] {
158
- return entries.filter((e) => !e.data.draft);
159
- }
160
-
@@ -1,165 +0,0 @@
1
- /** @jsxRuntime automatic */
2
- /** @jsxImportSource preact */
3
- // Host-side body-end islands helper.
4
- //
5
- // Wave 8 (Path A — super-epic #1333 / child epic #1355) drops the local
6
- // SSR-skip wrapper functions in `@takazudo/zudo-doc/ssr-skip` and uses
7
- // zfb's native `<Island ssrFallback={...}>` API directly with the real
8
- // component constructors imported by the host.
9
- //
10
- // The previous indirection (page → wrapper → placeholder div) created an
11
- // orphan-component bug: the real components were `"use client"` modules
12
- // that no page module ever imported transitively, so zfb's island scanner
13
- // never walked page → real-component, the manifest never bound the marker
14
- // to the real constructor, and the bundle never contained the real
15
- // component body. PR #150 to zfb fixed the marker-name alignment but the
16
- // orphan problem stayed — see issue zudolab/zudo-doc#1355 Wave 7 report.
17
- //
18
- // This helper is the page → real-component import chain the scanner
19
- // needs. Each island below is composed with zfb's `<Island>` wrapper,
20
- // which emits `<div data-zfb-island-skip-ssr="<ComponentName>">…</div>`
21
- // at SSR (zfb's `captureComponentName` derives the marker from
22
- // `child.displayName ?? child.name`). Because the page imports this
23
- // file, and this file imports the real components, the scanner walks
24
- // page → helper → real component and registers the constructor under
25
- // the SSR marker name.
26
- //
27
- // Pattern mirrors `_header-with-defaults.tsx`: the JSX-shim widens
28
- // `Island`'s return type to `unknown`, so call-sites cast through
29
- // `as unknown as VNode` at the boundary.
30
-
31
- import type { VNode, JSX } from "preact";
32
- import { Island } from "@takazudo/zfb";
33
- import { settings } from "@/config/settings";
34
-
35
- import { AiChatModal } from "@takazudo/zudo-doc/ai-chat-modal";
36
- import { ImageEnlarge, ImageEnlargeSsrFallback } from "@takazudo/zudo-doc/image-enlarge";
37
- import { MermaidEnlarge, MermaidEnlargeSsrFallback } from "@takazudo/zudo-doc/mermaid-enlarge";
38
- // @slot:body-end-islands:imports
39
-
40
- // AiChatModal, ImageEnlarge, MermaidEnlarge pin displayName internally in the
41
- // package. Optional feature islands that still need explicit call-site pinning
42
- // inject their displayName assignment at the slot below only when selected.
43
- // @slot:body-end-islands:display-names
44
-
45
- /**
46
- * Default sr-only label rendered as the AiChatModal SSR fallback. This
47
- * mirrors the body-label string the deleted `AiChatModalIsland` wrapper
48
- * produced verbatim so assistive tech can discover the chat entrypoint
49
- * in the static HTML before JS hydration. English-only for now — the
50
- * previous default was also English-only; pass `aiChatBodyLabel` to
51
- * localise.
52
- */
53
- const DEFAULT_AI_CHAT_BODY_LABEL = "Ask a question about the documentation.";
54
-
55
- /** Props for {@link BodyEndIslands}. */
56
- export interface BodyEndIslandsProps {
57
- /** Base path the AI chat modal uses to construct API URLs. */
58
- basePath: string;
59
- /**
60
- * Sr-only label rendered as the AiChatModal SSR fallback. Defaults to
61
- * the English string. Pass a locale-translated string for non-default
62
- * locales so screen readers announce the chat entrypoint correctly
63
- * before hydration.
64
- */
65
- aiChatBodyLabel?: string;
66
- }
67
-
68
- /**
69
- * The default body-end islands a doc page may mount: the AI chat modal
70
- * (`<dialog>` overlay) and the image-enlarge dialog (mounted lazily based
71
- * on viewport scan). Each is feature-gated — the AI chat modal (and its
72
- * sr-only landmark heading) on `settings.aiAssistant`, and image-enlarge
73
- * on `settings.imageEnlarge` — so a feature-off consumer ships neither the
74
- * island marker nor a misleading landmark (zudolab/zudo-doc#2058).
75
- *
76
- * Optional feature islands (e.g. the design token panel bootstrap) are not
77
- * listed here: they are injected at the body-end-islands composition
78
- * anchors only when their feature is selected, so a feature-off scaffold
79
- * carries no trace of them.
80
- *
81
- * Each island is wrapped in `<Island ssrFallback>` so the heavy
82
- * component is NOT evaluated server-side — they depend on
83
- * `dialog.showModal()`, `localStorage`, `ResizeObserver`, runtime
84
- * fetch, etc. The hydration runtime swaps each placeholder on the
85
- * client.
86
- *
87
- * When `settings.aiAssistant` is enabled, the
88
- * `<h2 class="sr-only">AI Assistant</h2>` heading is emitted in the SSG
89
- * output so screen readers and crawlers can discover the chat section
90
- * landmark before JS hydration.
91
- */
92
- export function BodyEndIslands({
93
- basePath,
94
- aiChatBodyLabel = DEFAULT_AI_CHAT_BODY_LABEL,
95
- }: BodyEndIslandsProps): JSX.Element {
96
- // Gated on `settings.aiAssistant` (zudolab/zudo-doc#2058): when the AI
97
- // assistant feature is off, neither the AiChatModal island marker nor the
98
- // sr-only "AI Assistant" landmark heading should reach the SSG output —
99
- // otherwise feature-off consumers ship a dead island marker plus a
100
- // misleading screen-reader landmark for a section that never hydrates.
101
- // Same feature-gating pattern as the other optional body-end islands.
102
- //
103
- // KNOWN CAVEAT: zfb's island scanner walks the static `"use client"`
104
- // import chain, so gating this JSX removes the SSR marker and heading but
105
- // may NOT strip the AiChatModal bundle from the build output. Marker
106
- // removal is the agreed first fix (#2058); bundle stripping is out of scope.
107
- //
108
- // The sr-only <p> fallback keeps the body label in static HTML for screen
109
- // readers before JS hydration; sr-only keeps it invisible to sighted users.
110
- const aiAssistant = settings.aiAssistant ? (
111
- <>
112
- {/* Emits the "AI Assistant" heading in the SSG output so screen
113
- readers can discover the chat section landmark before JS
114
- hydration. */}
115
- <h2 class="sr-only">AI Assistant</h2>
116
- {
117
- Island({
118
- ssrFallback: <p class="sr-only">{aiChatBodyLabel}</p>,
119
- children: <AiChatModal basePath={basePath} />,
120
- }) as unknown as VNode
121
- }
122
- </>
123
- ) : null;
124
-
125
- // Gated on `settings.imageEnlarge` (zudolab/zudo-doc#2058). Same caveat as
126
- // the AI assistant gating: removing this JSX drops the SSR dialog shell and
127
- // island marker, but the bundle may persist via the static import scan.
128
- //
129
- // Wave 11 (zudolab/zudo-doc#1355): the SSR fallback is the empty, closed
130
- // `<dialog class="zd-enlarge-dialog ...">` shell so the dist HTML carries
131
- // one dialog from the start. Without this the smoke "exactly one
132
- // zd-enlarge-dialog element" assertion sees zero (skip-ssr placeholders are
133
- // empty divs) and the no-JS path has no dialog at all. Hydration replaces
134
- // this shell with the real ImageEnlarge component when the page goes idle.
135
- const imageEnlarge = settings.imageEnlarge
136
- ? (Island({
137
- when: "idle",
138
- ssrFallback: <ImageEnlargeSsrFallback />,
139
- children: <ImageEnlarge />,
140
- }) as unknown as VNode)
141
- : null;
142
-
143
- // Gated on `settings.mermaid`. Mirrors the imageEnlarge block: the SSR
144
- // fallback is an empty, closed `<dialog class="zd-mermaid-dialog ...">` so
145
- // the dist HTML carries one dialog from the start and hydration (when="idle")
146
- // swaps in the real component. Unlike images (SSR-wrapped by the MDX paragraph
147
- // override), mermaid renders client-side, so this island injects the enlarge
148
- // button into each rendered diagram container itself.
149
- const mermaidEnlarge = settings.mermaid
150
- ? (Island({
151
- when: "idle",
152
- ssrFallback: <MermaidEnlargeSsrFallback />,
153
- children: <MermaidEnlarge />,
154
- }) as unknown as VNode)
155
- : null;
156
-
157
- return (
158
- <>
159
- {aiAssistant}
160
- {imageEnlarge}
161
- {mermaidEnlarge}
162
- {/* @slot:body-end-islands:extra-islands */}
163
- </>
164
- );
165
- }