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
@@ -7,7 +7,9 @@ import { useState, useCallback, useEffect, useMemo, useRef } from "preact/hooks"
7
7
  import type { NavNode } from "@/utils/docs";
8
8
  import type { LocaleLink } from "@/types/locale";
9
9
  import { INDENT, BASE_PAD, connectorLeft, ConnectorLines, CategoryLinkIcon } from "./tree-nav-shared";
10
- import ThemeToggle from "@/components/theme-toggle";
10
+ // BARE ThemeToggle (#2012 E2) — this footer toggle renders inside the
11
+ // SidebarToggle island, so it must NOT bring its own island wrapper.
12
+ import { ThemeToggle } from "@takazudo/zudo-doc/theme-toggle";
11
13
  import { smartBreakToHtml } from "@/utils/smart-break";
12
14
 
13
15
  function ToggleChevron({ isExpanded, className }: { isExpanded: boolean; className?: string }) {
@@ -61,7 +63,9 @@ function findActiveSlug(nodes: NavNode[], pathname: string): string | undefined
61
63
  for (const node of nodes) {
62
64
  if (node.href && normalizePath(node.href) === pathname) return node.slug;
63
65
  const found = findActiveSlug(node.children, pathname);
64
- if (found) return found;
66
+ // "" is the canonical root-index slug (#1891) a truthiness check
67
+ // would discard a legitimate root match.
68
+ if (found !== undefined) return found;
65
69
  }
66
70
  return undefined;
67
71
  }
@@ -246,8 +250,10 @@ export default function SidebarTree({ nodes, currentSlug, rootMenuItems, backToM
246
250
  }
247
251
 
248
252
  // Top page: show only header nav links, no doc tree or filter.
249
- // Derived from activeSlug (runtime-synced) so it stays correct across View Transitions.
250
- if (!activeSlug && rootMenuItems) {
253
+ // Derived from activeSlug (runtime-synced) so it stays correct across View
254
+ // Transitions. Must be an undefined check, not truthiness: "" is the
255
+ // canonical root-index doc slug (#1891) and gets the full tree.
256
+ if (activeSlug === undefined && rootMenuItems) {
251
257
  return (
252
258
  <nav>
253
259
  {rootMenuItems.map((item) => (
@@ -282,7 +288,7 @@ export default function SidebarTree({ nodes, currentSlug, rootMenuItems, backToM
282
288
  type="text"
283
289
  placeholder={filterPlaceholder}
284
290
  value={query}
285
- onChange={(e) => setQuery(e.target.value)}
291
+ onChange={(e) => setQuery(e.currentTarget.value)}
286
292
  className="bg-transparent text-small outline-none w-full text-fg placeholder:text-muted"
287
293
  />
288
294
  </div>
@@ -1,107 +1,23 @@
1
1
  "use client";
2
2
 
3
- // Use preact hook entrypoints directly the "react" → "preact/compat" alias
4
- // lets us consume React-typed components in this Preact app (configured
5
- // project-wide). Same pattern as packages/zudo-doc/src/theme/theme-toggle.tsx.
6
- import { useState, useEffect } from "preact/hooks";
7
-
8
- const STORAGE_KEY = "zudo-doc-theme";
9
-
10
- function SunIcon() {
11
- return (
12
- <svg
13
- aria-hidden="true"
14
- xmlns="http://www.w3.org/2000/svg"
15
- width="20"
16
- height="20"
17
- viewBox="0 0 24 24"
18
- fill="none"
19
- stroke="currentColor"
20
- strokeWidth="2"
21
- strokeLinecap="round"
22
- strokeLinejoin="round"
23
- >
24
- <circle cx="12" cy="12" r="5" />
25
- <line x1="12" y1="1" x2="12" y2="3" />
26
- <line x1="12" y1="21" x2="12" y2="23" />
27
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
28
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
29
- <line x1="1" y1="12" x2="3" y2="12" />
30
- <line x1="21" y1="12" x2="23" y2="12" />
31
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
32
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
33
- </svg>
34
- );
35
- }
36
-
37
- function MoonIcon() {
38
- return (
39
- <svg
40
- aria-hidden="true"
41
- xmlns="http://www.w3.org/2000/svg"
42
- width="20"
43
- height="20"
44
- viewBox="0 0 24 24"
45
- fill="none"
46
- stroke="currentColor"
47
- strokeWidth="2"
48
- strokeLinecap="round"
49
- strokeLinejoin="round"
50
- >
51
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
52
- </svg>
53
- );
54
- }
55
-
56
- interface ThemeToggleProps {
57
- defaultMode?: "light" | "dark";
3
+ // Scanner-visible ThemeToggle shim. zfb's island scanner does not register
4
+ // "use client" modules located under node_modules (Takazudo/zudo-front-builder#999),
5
+ // so importing the package ThemeToggle straight into the server-rendered header
6
+ // emits an island marker with no matching registry entry — the toggle renders
7
+ // but never hydrates, on every page (zudolab/zudo-doc#2048). This thin
8
+ // project-source wrapper gives the scanner a local binding to register; it
9
+ // re-wraps the bare (non-island-wrapped) package component unchanged.
10
+ import type { JSX } from "preact";
11
+ import {
12
+ ThemeToggle as ZudoDocThemeToggle,
13
+ type ThemeToggleProps,
14
+ } from "@takazudo/zudo-doc/theme-toggle";
15
+
16
+ export function ThemeToggle(props: ThemeToggleProps): JSX.Element {
17
+ return <ZudoDocThemeToggle {...props} />;
58
18
  }
19
+ ThemeToggle.displayName = "ThemeToggle";
59
20
 
60
- export default function ThemeToggle({ defaultMode = "dark" }: ThemeToggleProps) {
61
- // Initial state must match server render to avoid hydration mismatch.
62
- // Actual theme is synced from DOM in useEffect below.
63
- const [mode, setMode] = useState<"light" | "dark">(defaultMode);
64
-
65
- useEffect(() => {
66
- const actual =
67
- (document.documentElement.getAttribute("data-theme") as
68
- | "light"
69
- | "dark") || defaultMode;
70
- if (actual !== mode) {
71
- setMode(actual);
72
- }
73
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
74
-
75
- function toggle() {
76
- const next = mode === "dark" ? "light" : "dark";
77
- setMode(next);
78
- document.documentElement.setAttribute("data-theme", next);
79
- document.documentElement.style.colorScheme = next;
80
- localStorage.setItem(STORAGE_KEY, next);
81
- // Clear both v1 and v2 tweak state so the new scheme's palette takes effect.
82
- localStorage.removeItem("zudo-doc-tweak-state");
83
- localStorage.removeItem("zudo-doc-tweak-state-v2");
84
- window.dispatchEvent(new CustomEvent("color-scheme-changed"));
85
- }
86
-
87
- const nextMode = mode === "dark" ? "light" : "dark";
21
+ export type { ThemeToggleProps };
88
22
 
89
- return (
90
- <button
91
- onClick={toggle}
92
- aria-label={`Switch to ${nextMode} mode`}
93
- className="text-muted hover:text-fg transition-colors p-hsp-sm focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
94
- >
95
- {mode === "dark" ? <SunIcon /> : <MoonIcon />}
96
- </button>
97
- );
98
- }
99
- // Pin the island marker name to "ThemeToggle" regardless of esbuild's
100
- // identifier deduplication. Both this host component and the v2 package's
101
- // ThemeToggleInner share the plain name "ThemeToggle"; when both land in the
102
- // same SSR bundle esbuild renames one to "ThemeToggle2", making
103
- // captureComponentName() emit "ThemeToggle2" — a name that has no entry in
104
- // the island manifest. Setting displayName explicitly ensures Island() reads
105
- // the attribute-level name (displayName is preferred over .name) and emits
106
- // the correct data-zfb-island="ThemeToggle" marker. zudolab/zudo-doc#1446.
107
- ThemeToggle.displayName = "ThemeToggle";
23
+ export default ThemeToggle;
@@ -11,6 +11,10 @@ export interface ColorScheme {
11
11
  string, string, string, string, string, string, string, string,
12
12
  string, string, string, string, string, string, string, string,
13
13
  ];
14
+ /** Optional Shiki theme for the zdtp panel's client-side code-block preview.
15
+ * Falls back to the panel config's DEFAULT_SHIKI_THEME when omitted.
16
+ * Static highlighting (syntect via zfb's Rust pipeline) is unaffected. */
17
+ shikiTheme?: string;
14
18
  /** Optional semantic overrides — when omitted, defaults are used:
15
19
  * surface=p0, muted=p8, accent=p5, accentHover=p14
16
20
  * codeBg=p10, codeFg=p11, success=p2, danger=p1, warning=p3, info=p4
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ import { settings } from "./settings";
3
+ import { tagVocabulary } from "./tag-vocabulary";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Tags schema builder — governance-aware.
7
+ // ---------------------------------------------------------------------------
8
+
9
+ /**
10
+ * Build the `tags` schema based on governance mode. `"strict"` tightens to a
11
+ * `z.enum` of every canonical id plus every alias (content still uses
12
+ * aliases verbatim — resolution happens at the aggregation layer, after
13
+ * parsing).
14
+ */
15
+ function buildTagsSchema() {
16
+ const vocabularyActive =
17
+ settings.tagVocabulary && settings.tagGovernance === "strict";
18
+ if (!vocabularyActive) return z.array(z.string()).optional();
19
+ const allowed = new Set<string>();
20
+ for (const entry of tagVocabulary) {
21
+ allowed.add(entry.id);
22
+ for (const alias of entry.aliases ?? []) allowed.add(alias);
23
+ }
24
+ const allowedList = [...allowed];
25
+ if (allowedList.length === 0) return z.array(z.string()).optional();
26
+ const [first, ...rest] = allowedList;
27
+ return z
28
+ .array(z.enum([first, ...rest] as [string, ...string[]]))
29
+ .optional();
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Schema builder — single source of truth for the docs frontmatter shape.
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * Build the docs frontmatter zod schema.
38
+ *
39
+ * Returns a single `z.object(...).passthrough()` that is reused for every
40
+ * docs collection (default + per-locale + per-version + per-version-per-locale).
41
+ * The `tags` field is governance-aware: `buildTagsSchema()` returns a plain
42
+ * `z.array(z.string())` when governance is off, or a restricted `z.enum`
43
+ * when `tagGovernance: "strict"` + `tagVocabulary` is configured.
44
+ *
45
+ * `.passthrough()` keeps custom frontmatter keys (e.g. `author`, `status`)
46
+ * available downstream — the frontmatter-preview UI relies on this to
47
+ * surface arbitrary keys without declaring each one here.
48
+ */
49
+ export function buildDocsSchema() {
50
+ return z
51
+ .object({
52
+ title: z.string(),
53
+ description: z.string().optional(),
54
+ category: z.string().optional(),
55
+ sidebar_position: z.number().optional(),
56
+ sidebar_label: z.string().optional(),
57
+ tags: buildTagsSchema(),
58
+ search_exclude: z.boolean().optional(),
59
+ pagination_next: z.string().nullable().optional(),
60
+ pagination_prev: z.string().nullable().optional(),
61
+ draft: z.boolean().optional(),
62
+ unlisted: z.boolean().optional(),
63
+ hide_sidebar: z.boolean().optional(),
64
+ hide_toc: z.boolean().optional(),
65
+ doc_history: z.boolean().optional(),
66
+ standalone: z.boolean().optional(),
67
+ slug: z.string().optional(),
68
+ generated: z.boolean().optional(),
69
+ // Category metadata expressed as a directory index.mdx's frontmatter — the
70
+ // frontmatter form of `_category_.json`. `category_no_page` makes the index
71
+ // a non-linked sidebar header excluded from routes/sitemap/search;
72
+ // `category_sort_order` sets the child sort direction. Frontmatter wins
73
+ // over the sidecar.
74
+ category_no_page: z.boolean().optional(),
75
+ category_sort_order: z.enum(["asc", "desc"]).optional(),
76
+ })
77
+ .passthrough();
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Inferred type — single source of truth for the docs data shape.
82
+ // ---------------------------------------------------------------------------
83
+
84
+ /**
85
+ * TypeScript type inferred from the docs frontmatter zod schema.
86
+ *
87
+ * Import this type instead of hand-writing the field list in `pages/_data.ts`
88
+ * (`ZfbDocsData`) or `src/types/docs-entry.ts` (`DocsEntry.data`).
89
+ *
90
+ * The `[key: string]: unknown` index signature from `.passthrough()` is
91
+ * naturally present via `z.infer` — custom frontmatter keys remain accessible
92
+ * downstream (e.g. frontmatter-preview) without extra casting.
93
+ */
94
+ export type DocsData = z.infer<ReturnType<typeof buildDocsSchema>>;
@@ -1,4 +1,5 @@
1
1
  import { settings } from "./settings";
2
+ import type { LocaleConfig } from "./settings-types";
2
3
 
3
4
  // Collection name string used by zfb's content engine (`getCollection(...)`).
4
5
  // Kept as a structural string-literal alias so callers don't have to redeclare
@@ -15,9 +16,9 @@ export const locales = [
15
16
  ] as const;
16
17
  export type Locale = (typeof locales)[number];
17
18
 
18
- /** Safely look up a locale in settings.locales. */
19
- function getLocaleConfig(locale: string) {
20
- return settings.locales[locale];
19
+ /** Safely look up a locale in settings.locales by string key. */
20
+ export function getLocaleConfig(locale: string): LocaleConfig | undefined {
21
+ return (settings.locales as Record<string, LocaleConfig | undefined>)[locale];
21
22
  }
22
23
 
23
24
  /** Get the content directory for a locale. */
@@ -70,6 +71,8 @@ const translations: Record<string, Record<string, string>> = {
70
71
  "toc.title": "On this page",
71
72
  "docs.browseAll": "Browse all documentation sections.",
72
73
  "search.label": "Search",
74
+ "search.placeholder": "Type to search...",
75
+ "search.shortcutHint": "to open search from anywhere",
73
76
  "search.resultCount": "{count} results",
74
77
  "code.copy": "Copy code",
75
78
  "code.copied": "Copied!",
@@ -128,6 +131,8 @@ const translations: Record<string, Record<string, string>> = {
128
131
  "toc.title": "目次",
129
132
  "docs.browseAll": "すべてのドキュメントセクションを閲覧",
130
133
  "search.label": "検索",
134
+ "search.placeholder": "検索したい単語を入力",
135
+ "search.shortcutHint": "いつでも検索バーを開ける",
131
136
  "search.resultCount": "{count} 件",
132
137
  "code.copy": "コードをコピー",
133
138
  "code.copied": "コピーしました!",
@@ -186,6 +191,8 @@ const translations: Record<string, Record<string, string>> = {
186
191
  "toc.title": "Auf dieser Seite",
187
192
  "docs.browseAll": "Alle Dokumentationsabschnitte durchsuchen.",
188
193
  "search.label": "Suche",
194
+ "search.placeholder": "Suchbegriff eingeben...",
195
+ "search.shortcutHint": "Suche von überall öffnen",
189
196
  "search.resultCount": "{count} Ergebnisse",
190
197
  "code.copy": "Code kopieren",
191
198
  "code.copied": "Kopiert!",
@@ -1002,6 +1002,20 @@ pre[class^="syntect-"] .line .highlighted-word {
1002
1002
  html[data-sidebar-hidden] .zd-sidebar-content-wrapper {
1003
1003
  margin-left: 0;
1004
1004
  }
1005
+
1006
+ /* When hidden via the toggle, narrow the content band to the
1007
+ * hide_sidebar frontmatter width so it centers (the flex parent already
1008
+ * applies justify-content: center) instead of leaving a dead gap where
1009
+ * the sidebar was. 80rem must match the `max-w-[80rem]` hide_sidebar
1010
+ * branch in doc-layout.tsx. These rules are unlayered, so they win over
1011
+ * the Tailwind `max-w-[clamp(...)]` utility (utilities layer). (#2002) */
1012
+ .zd-doc-content-band {
1013
+ transition: max-width 200ms ease-in-out;
1014
+ }
1015
+
1016
+ html[data-sidebar-hidden] .zd-doc-content-band {
1017
+ max-width: 80rem;
1018
+ }
1005
1019
  }
1006
1020
 
1007
1021
  /* Sidebar toggle button — left position uses CSS variable, needs global rule */
@@ -1,3 +1,5 @@
1
+ import type { DocsData } from "@/config/docs-schema";
2
+
1
3
  /**
2
4
  * Concrete entry type for docs collections.
3
5
  *
@@ -6,6 +8,9 @@
6
8
  * but is defined locally now that the project runs on the zfb content
7
9
  * engine — collection-name-specific generics are not exposed by zfb, so
8
10
  * pages cast collection entries to this shape via `pages/_data.ts`.
11
+ *
12
+ * `data` is typed as `DocsData` — the `z.infer`-derived type from
13
+ * `src/config/docs-schema.ts` — so the field set is maintained in one place.
9
14
  */
10
15
  // Structural shape of zfb's optional rendered-content payload for a doc
11
16
  // entry (kept loose to stay engine-agnostic — pages do not rely on the
@@ -13,34 +18,11 @@
13
18
  type RenderedContent = unknown;
14
19
  export interface DocsEntry {
15
20
  id: string;
21
+ /** zfb content engine slug (filename without `.md`/`.mdx`; used by toRouteSlug). */
22
+ slug: string;
16
23
  body?: string;
17
24
  collection: string;
18
- data: {
19
- title: string;
20
- description?: string;
21
- category?: string;
22
- sidebar_position?: number;
23
- sidebar_label?: string;
24
- tags?: string[];
25
- search_exclude?: boolean;
26
- pagination_next?: string | null;
27
- pagination_prev?: string | null;
28
- draft?: boolean;
29
- unlisted?: boolean;
30
- hide_sidebar?: boolean;
31
- hide_toc?: boolean;
32
- doc_history?: boolean;
33
- standalone?: boolean;
34
- slug?: string;
35
- generated?: boolean;
36
- /** Category metadata on a directory's index.mdx (frontmatter form of
37
- * `_category_.json` `noPage`): non-linked sidebar header + excluded from
38
- * routes/sitemap/search. Frontmatter wins over the sidecar. */
39
- category_no_page?: boolean;
40
- /** Frontmatter form of `_category_.json` `sortOrder` — child sort
41
- * direction. Frontmatter wins over the sidecar. */
42
- category_sort_order?: "asc" | "desc";
43
- };
25
+ data: DocsData;
44
26
  rendered?: RenderedContent;
45
27
  filePath?: string;
46
28
  }
@@ -37,8 +37,10 @@ export function withBase(path: string): string {
37
37
  /** Strip the base prefix from a URL pathname. */
38
38
  export function stripBase(path: string): string {
39
39
  if (normalizedBase === "") return path;
40
- return path.startsWith(normalizedBase)
41
- ? path.slice(normalizedBase.length) || "/"
40
+ // Require a segment boundary so base "/app" doesn't strip "/application/...".
41
+ if (path === normalizedBase) return "/";
42
+ return path.startsWith(`${normalizedBase}/`)
43
+ ? path.slice(normalizedBase.length)
42
44
  : path;
43
45
  }
44
46
 
@@ -55,7 +57,7 @@ export function absoluteUrl(pageUrl: string): string | undefined {
55
57
  }
56
58
 
57
59
  /** Build a docs URL for the given slug and lang. */
58
- export function docsUrl(slug: string, lang: Locale = defaultLocale): string {
60
+ export function docsUrl(slug: string, lang: Locale | string = defaultLocale): string {
59
61
  const path = lang === defaultLocale ? `/docs/${slug}` : `/${lang}/docs/${slug}`;
60
62
  return withBase(path);
61
63
  }