@takazudo/zudo-doc 5.5.3 → 5.7.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 (83) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +39 -0
  3. package/dist/chrome/derive.d.ts +46 -0
  4. package/dist/chrome/derive.js +6 -2
  5. package/dist/config-assertions/index.d.ts +31 -0
  6. package/dist/config-assertions/index.js +24 -0
  7. package/dist/config.d.ts +23 -2
  8. package/dist/config.js +3 -0
  9. package/dist/current-path/index.d.ts +27 -0
  10. package/dist/current-path/index.js +11 -0
  11. package/dist/design-token-panel-bootstrap.d.ts +89 -23
  12. package/dist/design-token-panel-bootstrap.js +19 -6
  13. package/dist/doc-page-props/index.d.ts +5 -67
  14. package/dist/doc-route-entries/index.d.ts +10 -95
  15. package/dist/doc-route-entries/index.js +1 -78
  16. package/dist/doc-route-paths/index.d.ts +1 -1
  17. package/dist/head-with-defaults/index.d.ts +3 -1
  18. package/dist/head-with-defaults/index.js +76 -4
  19. package/dist/header/nav-active.d.ts +31 -0
  20. package/dist/header/nav-active.js +2 -1
  21. package/dist/header/nav-overflow-generated-script.d.ts +11 -0
  22. package/dist/header/nav-overflow-generated-script.js +302 -0
  23. package/dist/header/nav-overflow-script.d.ts +1 -1
  24. package/dist/header/nav-overflow-script.js +1 -286
  25. package/dist/header-with-defaults/index.js +11 -2
  26. package/dist/i18n-version/language-switcher.d.ts +6 -0
  27. package/dist/i18n-version/language-switcher.js +3 -1
  28. package/dist/i18n-version/version-switcher.d.ts +6 -0
  29. package/dist/i18n-version/version-switcher.js +3 -1
  30. package/dist/md-utils/index.js +33 -1
  31. package/dist/nav-source-docs/index.d.ts +7 -11
  32. package/dist/plugins/route-pages-candidates.d.ts +19 -0
  33. package/dist/plugins/route-pages-candidates.js +17 -0
  34. package/dist/plugins/routes.d.ts +46 -0
  35. package/dist/plugins/routes.js +72 -18
  36. package/dist/preset.d.ts +12 -1
  37. package/dist/preset.js +2 -0
  38. package/dist/route-context/index.js +2 -2
  39. package/dist/routes/_chrome.d.ts +1 -1
  40. package/dist/routes/_chrome.js +4 -0
  41. package/dist/routes/_context.d.ts +3 -3
  42. package/dist/routes/_design-token-panel-bootstrap.d.ts +18 -0
  43. package/dist/routes/_design-token-panel-bootstrap.js +11 -0
  44. package/dist/routes/_docs-helpers.d.ts +1 -36
  45. package/dist/routes/_docs-helpers.js +0 -138
  46. package/dist/safelist.css +1 -1
  47. package/dist/search-widget-script/generated-script.d.ts +8 -0
  48. package/dist/search-widget-script/generated-script.js +465 -0
  49. package/dist/search-widget-script/index.d.ts +1 -18
  50. package/dist/search-widget-script/index.js +1 -443
  51. package/dist/settings.d.ts +82 -1
  52. package/dist/sidebar-toggle-island/index.js +2 -1
  53. package/dist/sidebar-tree/category-meta.d.ts +9 -0
  54. package/dist/sidebar-tree/category-meta.js +21 -12
  55. package/dist/sidebar-tree-island/index.d.ts +8 -1
  56. package/dist/sidebar-tree-island/index.js +16 -14
  57. package/dist/site-schema/doc-route-entries.d.ts +89 -0
  58. package/dist/site-schema/doc-route-entries.js +83 -0
  59. package/dist/site-schema/index.d.ts +17 -0
  60. package/dist/site-schema/index.js +46 -0
  61. package/dist/site-schema/nav-tree.d.ts +28 -0
  62. package/dist/site-schema/nav-tree.js +138 -0
  63. package/dist/site-schema/types.d.ts +97 -0
  64. package/dist/site-schema/types.js +0 -0
  65. package/dist/theme/theme-pack-provider.d.ts +34 -3
  66. package/dist/theme/theme-pack-provider.js +30 -2
  67. package/dist/transitions/index.d.ts +1 -0
  68. package/dist/transitions/index.js +2 -0
  69. package/dist/transitions/nested-island-props-refresh.d.ts +19 -0
  70. package/dist/transitions/nested-island-props-refresh.js +104 -0
  71. package/eject/header/header.tsx +13 -5
  72. package/eject/header/nav-active.ts +16 -1
  73. package/eject/header/nav-class-tokens.ts +9 -5
  74. package/eject/header/nav-overflow-generated-script.ts +29 -0
  75. package/eject/header/nav-overflow-script.ts +26 -304
  76. package/eject/sidebar-toggle-island/index.tsx +13 -1
  77. package/eject/sidebar-tree-island/index.tsx +44 -20
  78. package/package.json +25 -12
  79. package/routes-src/_chrome.tsx +21 -9
  80. package/routes-src/_design-token-panel-bootstrap.tsx +63 -0
  81. package/routes-src/_docs-helpers.ts +18 -225
  82. package/routes-src/_virtual.d.ts +5 -2
  83. package/virtual-modules.d.ts +5 -2
@@ -1,5 +1,6 @@
1
1
  import { jsx, jsxs } from "preact/jsx-runtime";
2
2
  import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
3
+ import { CURRENT_PATH_SCRIPT_PRELUDE } from "../current-path/index.js";
3
4
  import { UNAVAILABLE_VERSIONS_ATTR } from "../version-availability/index.js";
4
5
  function computeVersionSwitcherState(pathname, config, versionSlugs) {
5
6
  const normalizedBase = config.base;
@@ -235,6 +236,7 @@ var FLAG="__zdVersionSwitcherRewire";
235
236
  if(window[FLAG])return;
236
237
  window[FLAG]=true;
237
238
  var ATTR=${JSON.stringify(UNAVAILABLE_VERSIONS_ATTR)};
239
+ ${CURRENT_PATH_SCRIPT_PRELUDE}
238
240
  var computeVersionSwitcherState=${computeVersionSwitcherState.toString()};
239
241
  function setActive(a,active){
240
242
  a.classList.toggle("font-bold",active);
@@ -276,7 +278,7 @@ for(var j=0;j<versionAnchors.length;j++){
276
278
  var s=versionAnchors[j].getAttribute("data-version-slug");
277
279
  if(s)slugs.push(s);
278
280
  }
279
- var state=computeVersionSwitcherState(window.location.pathname,config,slugs);
281
+ var state=computeVersionSwitcherState(readCurrentPath(CURRENT_PATH_DATASET_KEY),config,slugs);
280
282
  var latest=c.querySelector("[data-version-latest]");
281
283
  if(latest){
282
284
  latest.setAttribute("href",state.latestHref);
@@ -2,8 +2,40 @@ import { readFileSync, readdirSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
  import matter from "gray-matter";
4
4
  import { toRouteSlug } from "../slug/index.js";
5
+ const NAMED_CHARACTER_REFERENCES = {
6
+ lt: "<",
7
+ gt: ">",
8
+ quot: '"',
9
+ apos: "'",
10
+ nbsp: "\xA0"
11
+ };
12
+ function decodeCharacterReferences(value) {
13
+ return value.replace(
14
+ /&#(?:([0-9]+)|[xX]([0-9a-fA-F]+));/g,
15
+ (reference, decimal, hexadecimal) => {
16
+ const digits = decimal ?? hexadecimal;
17
+ if (!digits) return reference;
18
+ const codePoint = Number.parseInt(digits, decimal ? 10 : 16);
19
+ if (codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343) {
20
+ return reference;
21
+ }
22
+ try {
23
+ return String.fromCodePoint(codePoint);
24
+ } catch {
25
+ return reference;
26
+ }
27
+ }
28
+ ).replace(
29
+ /&(lt|gt|quot|apos|nbsp);/g,
30
+ (reference, name) => NAMED_CHARACTER_REFERENCES[name] ?? reference
31
+ ).replace(/&amp;/g, "&");
32
+ }
5
33
  function stripMarkdown(md) {
6
- return md.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<[^>]+>/g, "").replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}([^*]+)\*{1,3}/g, "$1").replace(/_{1,3}([^_]+)_{1,3}/g, "$1").replace(/!\[[^\]]*\]\([^)]+\)/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/^[\s]*[-*+]\s+/gm, "").replace(/^[\s]*\d+\.\s+/gm, "").replace(/^import\s+.*$/gm, "").replace(/^export\s+.*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
34
+ const withoutCodeCommentsAndTags = md.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<[^>]+>/g, "");
35
+ return decodeCharacterReferences(withoutCodeCommentsAndTags).replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}([^*]+)\*{1,3}/g, "$1").replace(
36
+ /(^|[^\p{L}\p{N}])(_{1,3})(?=\S)([^_\n]*?\S)\2(?=$|[^\p{L}\p{N}])/gmu,
37
+ "$1$3"
38
+ ).replace(/!\[[^\]]*\]\([^)]+\)/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/^[\s]*[-*+]\s+/gm, "").replace(/^[\s]*\d+\.\s+/gm, "").replace(/^import\s+.*$/gm, "").replace(/^export\s+.*$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
7
39
  }
8
40
  function collectMdFiles(dir) {
9
41
  const results = [];
@@ -1,18 +1,14 @@
1
1
  import type { CategoryMeta } from "../sidebar-tree/index.js";
2
2
  import type { DocPageEntry } from "../doc-page-props/index.js";
3
+ import type { NavSourceDocs as NavSourceDocsShape } from "../site-schema/types.js";
3
4
  import type { Settings } from "../settings.js";
4
5
  export type { CategoryMeta, DocPageEntry };
5
- export interface NavSourceDocs {
6
- /** Full doc list (merged + draft-filtered; unlisted retained per options). */
7
- docs: DocPageEntry[];
8
- /** `docs.filter(isNavVisible)` — stable instance for buildNavTree. */
9
- navDocs: DocPageEntry[];
10
- /** Stable category-meta Map for the active (locale, version). */
11
- categoryMeta: Map<string, CategoryMeta>;
12
- /** Slugs that came from the locale collection (for isFallback). Empty for
13
- * default-locale / single-collection cases. */
14
- localeSlugSet: ReadonlySet<string>;
15
- }
6
+ /**
7
+ * The resolved nav source for one (locale, version) context, carrying zfb
8
+ * collection entries. The shape itself is browser-safe and declared in
9
+ * `../site-schema/types.js` (#3395); this alias pins it to `DocPageEntry`.
10
+ */
11
+ export type NavSourceDocs = NavSourceDocsShape<DocPageEntry>;
16
12
  /**
17
13
  * How to filter the merged doc list.
18
14
  */
@@ -0,0 +1,19 @@
1
+ /**
2
+ * File extensions zfb's router accepts as `pages/` route sources. Mirrors
3
+ * the Rust-side single source of truth `zfb_types::ROUTABLE_PAGE_EXTENSIONS`
4
+ * (zfb crate `crates/zfb-types/src/page_extensions.rs`) — that constant is
5
+ * not importable from JS, so it is copied here deliberately; keep in sync if
6
+ * zfb ever changes its accepted extension list.
7
+ */
8
+ export declare const ROUTABLE_PAGE_EXTENSIONS: readonly ["tsx", "ts", "jsx", "js", "mdx", "md", "html"];
9
+ /**
10
+ * Derive every `pages/`-relative path that would shadow the given injected
11
+ * route pattern (a zfb `injectRoute` pattern string, e.g.
12
+ * `"/docs/tags/[tag]"` or `"/[locale]"`). Pure — no filesystem I/O; the
13
+ * caller probes each candidate against the host's real `pages/` dir.
14
+ *
15
+ * Returns `[]` for the empty/root pattern (`"/"`) — never produced by this
16
+ * plugin's route catalog (zfb rejects `injectRoute("/")` outright), so there
17
+ * is nothing meaningful to derive.
18
+ */
19
+ export declare function derivePagesCandidates(pattern: string): string[];
@@ -0,0 +1,17 @@
1
+ const ROUTABLE_PAGE_EXTENSIONS = ["tsx", "ts", "jsx", "js", "mdx", "md", "html"];
2
+ function derivePagesCandidates(pattern) {
3
+ const segments = pattern.split("/").filter(Boolean);
4
+ if (segments.length === 0) return [];
5
+ const lastSegment = segments[segments.length - 1];
6
+ const dirPrefix = segments.length > 1 ? `${segments.slice(0, -1).join("/")}/` : "";
7
+ const candidates = [];
8
+ for (const ext of ROUTABLE_PAGE_EXTENSIONS) {
9
+ candidates.push(`${dirPrefix}${lastSegment}.${ext}`);
10
+ candidates.push(`${dirPrefix}${lastSegment}/index.${ext}`);
11
+ }
12
+ return candidates;
13
+ }
14
+ export {
15
+ ROUTABLE_PAGE_EXTENSIONS,
16
+ derivePagesCandidates
17
+ };
@@ -1,2 +1,48 @@
1
+ /** One injected route: a zfb route pattern + the package entrypoint specifier
2
+ * that renders it, with optional zfb `injectRoute` opts. */
3
+ interface RouteSpec {
4
+ pattern: string;
5
+ entrypoint: string;
6
+ opts?: {
7
+ prerender?: boolean;
8
+ };
9
+ /**
10
+ * Whether this route counts toward the DTP shadow diagnostic's denominator
11
+ * (#3434). REQUIRED, deliberately — a newly added route must state its
12
+ * membership, so forgetting one is a type error instead of the silent
13
+ * miss #3434 *is*.
14
+ *
15
+ * The name is scoped to the DIAGNOSTIC, not to the route's own nature,
16
+ * because that is all the flag actually asserts. In particular it is NOT
17
+ * `rendersPanel`: `/404` genuinely DOES render the configured DTP bootstrap
18
+ * (`routes/404.tsx` passes `bodyEndComponents={<BodyEndIslands …/>}`, and
19
+ * `routes/_chrome.tsx` feeds that chrome the configured wrapper), so a
20
+ * `rendersPanel: false` tag on it would be a false claim in the source. It
21
+ * is likewise not `kind: "doc-content"` — the counted set includes the
22
+ * locale home, the tag pages and the version pages.
23
+ *
24
+ * `false` for the four routes a reader never browses as documentation:
25
+ * `/sitemap.xml` and `/robots.txt` cannot render an HTML panel at all (they
26
+ * would hold the check at "not fully shadowed" forever), `/api/ai-chat` is a
27
+ * JSON endpoint, and `/404` is an error page — losing the panel there says
28
+ * nothing about whether the site's docs still have one. The never-injected
29
+ * `/` (see the note in `deriveRoutes`) is absent from the catalog entirely
30
+ * and so cannot be counted either.
31
+ */
32
+ includedInDtpShadowDiagnostic: boolean;
33
+ }
34
+ /**
35
+ * The DTP shadow diagnostic's warn/silent decision, extracted from `setup()`
36
+ * so the vacuity guard below is directly unit-testable (#3434).
37
+ *
38
+ * Returns `true` only when there is at least one reader-facing route AND every
39
+ * one of them is shadowed. The emptiness check is the whole reason this is a
40
+ * named function: `[].every(…)` is vacuously `true`, so an empty denominator
41
+ * would otherwise fire the warning for every host. That state is unreachable
42
+ * through `deriveRoutes` today (`/docs/[[...slug]]` is always emitted and
43
+ * always counted), which is exactly why it needs a test that calls this
44
+ * directly — a build-driven test cannot construct it.
45
+ */
46
+ export declare function shouldWarnDtpFullyShadowed(routes: ReadonlyArray<Pick<RouteSpec, "pattern" | "includedInDtpShadowDiagnostic">>, isShadowed: (pattern: string) => boolean): boolean;
1
47
  declare const plugin: import("@takazudo/zfb").ZfbPlugin;
2
48
  export default plugin;
@@ -1,46 +1,61 @@
1
1
  import { createRequire } from "node:module";
2
- import { existsSync, statSync, cpSync, rmSync, mkdirSync } from "node:fs";
2
+ import { existsSync, statSync, readFileSync, cpSync, rmSync, mkdirSync } from "node:fs";
3
3
  import { dirname, basename, join } from "node:path";
4
4
  import { definePlugin } from "@takazudo/zfb/plugins";
5
5
  import {
6
6
  loadThemePackRegistry,
7
7
  resolveEnabledPacks
8
8
  } from "../theme-packs-registry/index.js";
9
+ import { derivePagesCandidates } from "./route-pages-candidates.js";
10
+ function shouldWarnDtpFullyShadowed(routes, isShadowed) {
11
+ const counted = routes.filter((route) => route.includedInDtpShadowDiagnostic);
12
+ if (counted.length === 0) return false;
13
+ return counted.every((route) => isShadowed(route.pattern));
14
+ }
15
+ function isExactDefaultReExport(source, entrypoint) {
16
+ const withoutComments = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
17
+ const specifier = entrypoint.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ const reExportPattern = new RegExp(
19
+ `export\\s*\\{[^}]*\\bdefault\\b(?!\\s+as\\s)[^}]*\\}\\s*from\\s*(["'])${specifier}\\1`
20
+ );
21
+ return reExportPattern.test(withoutComments);
22
+ }
9
23
  function deriveRoutes(settings) {
10
24
  const routes = [];
11
25
  const localeCodes = Object.keys(settings.locales ?? {});
12
26
  const hasVersions = Array.isArray(settings.versions) && settings.versions.length > 0;
13
27
  const docTags = settings.docTags === true;
14
28
  const aiAssistant = settings.aiAssistant === true;
15
- routes.push({ pattern: "/404", entrypoint: "@takazudo/zudo-doc/routes/404" });
16
- routes.push({ pattern: "/sitemap.xml", entrypoint: "@takazudo/zudo-doc/routes/sitemap.xml" });
17
- routes.push({ pattern: "/robots.txt", entrypoint: "@takazudo/zudo-doc/routes/robots.txt" });
18
- routes.push({ pattern: "/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/docs-slug" });
29
+ routes.push({ pattern: "/404", entrypoint: "@takazudo/zudo-doc/routes/404", includedInDtpShadowDiagnostic: false });
30
+ routes.push({ pattern: "/sitemap.xml", entrypoint: "@takazudo/zudo-doc/routes/sitemap.xml", includedInDtpShadowDiagnostic: false });
31
+ routes.push({ pattern: "/robots.txt", entrypoint: "@takazudo/zudo-doc/routes/robots.txt", includedInDtpShadowDiagnostic: false });
32
+ routes.push({ pattern: "/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/docs-slug", includedInDtpShadowDiagnostic: true });
19
33
  if (docTags) {
20
- routes.push({ pattern: "/docs/tags", entrypoint: "@takazudo/zudo-doc/routes/docs-tags-index" });
21
- routes.push({ pattern: "/docs/tags/[tag]", entrypoint: "@takazudo/zudo-doc/routes/docs-tags-tag" });
34
+ routes.push({ pattern: "/docs/tags", entrypoint: "@takazudo/zudo-doc/routes/docs-tags-index", includedInDtpShadowDiagnostic: true });
35
+ routes.push({ pattern: "/docs/tags/[tag]", entrypoint: "@takazudo/zudo-doc/routes/docs-tags-tag", includedInDtpShadowDiagnostic: true });
22
36
  }
23
37
  if (aiAssistant) {
24
38
  routes.push({
25
39
  pattern: "/api/ai-chat",
26
40
  entrypoint: "@takazudo/zudo-doc/routes/api-ai-chat",
27
- opts: { prerender: false }
41
+ opts: { prerender: false },
42
+ includedInDtpShadowDiagnostic: false
28
43
  });
29
44
  }
30
45
  if (hasVersions) {
31
- routes.push({ pattern: "/docs/versions", entrypoint: "@takazudo/zudo-doc/routes/docs-versions" });
32
- routes.push({ pattern: "/v/[version]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/v-docs-slug" });
33
- routes.push({ pattern: "/v/[version]/[locale]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/v-locale-docs-slug" });
46
+ routes.push({ pattern: "/docs/versions", entrypoint: "@takazudo/zudo-doc/routes/docs-versions", includedInDtpShadowDiagnostic: true });
47
+ routes.push({ pattern: "/v/[version]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/v-docs-slug", includedInDtpShadowDiagnostic: true });
48
+ routes.push({ pattern: "/v/[version]/[locale]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/v-locale-docs-slug", includedInDtpShadowDiagnostic: true });
34
49
  }
35
50
  if (localeCodes.length > 0) {
36
- routes.push({ pattern: "/[locale]", entrypoint: "@takazudo/zudo-doc/routes/locale-index" });
37
- routes.push({ pattern: "/[locale]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-slug" });
51
+ routes.push({ pattern: "/[locale]", entrypoint: "@takazudo/zudo-doc/routes/locale-index", includedInDtpShadowDiagnostic: true });
52
+ routes.push({ pattern: "/[locale]/docs/[[...slug]]", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-slug", includedInDtpShadowDiagnostic: true });
38
53
  if (docTags) {
39
- routes.push({ pattern: "/[locale]/docs/tags", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-tags-index" });
40
- routes.push({ pattern: "/[locale]/docs/tags/[tag]", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-tags-tag" });
54
+ routes.push({ pattern: "/[locale]/docs/tags", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-tags-index", includedInDtpShadowDiagnostic: true });
55
+ routes.push({ pattern: "/[locale]/docs/tags/[tag]", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-tags-tag", includedInDtpShadowDiagnostic: true });
41
56
  }
42
57
  if (hasVersions) {
43
- routes.push({ pattern: "/[locale]/docs/versions", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-versions" });
58
+ routes.push({ pattern: "/[locale]/docs/versions", entrypoint: "@takazudo/zudo-doc/routes/locale-docs-versions", includedInDtpShadowDiagnostic: true });
44
59
  }
45
60
  }
46
61
  return routes;
@@ -74,6 +89,7 @@ const plugin = definePlugin({
74
89
  const translations = options.translations ?? {};
75
90
  const tagVocabulary = options.tagVocabulary ?? [];
76
91
  const colorSchemes = options.colorSchemes ?? null;
92
+ const derivedRoutes = deriveRoutes(settings);
77
93
  const themePacksDir = new URL("../theme-packs/", import.meta.url);
78
94
  const themePackRegistry = resolveEnabledPacks(
79
95
  loadThemePackRegistry(themePacksDir),
@@ -120,6 +136,43 @@ const plugin = definePlugin({
120
136
  ` : `export { buildDesignTokenPanelConfig } from "@takazudo/zudo-doc/design-token-panel-config";
121
137
  `
122
138
  );
139
+ if (settings.designTokenPanel === true && designTokenPanelConfigAbsPath) {
140
+ const pagesDir = join(ctx.projectRoot, "pages");
141
+ const routesByPattern = new Map(derivedRoutes.map((route) => [route.pattern, route]));
142
+ const readerRoutesAllShadowed = shouldWarnDtpFullyShadowed(derivedRoutes, (pattern) => {
143
+ const route = routesByPattern.get(pattern);
144
+ return derivePagesCandidates(pattern).some((rel) => {
145
+ const abs = join(pagesDir, rel);
146
+ if (!existsSync(abs)) return false;
147
+ let source;
148
+ try {
149
+ source = readFileSync(abs, "utf8");
150
+ } catch {
151
+ return true;
152
+ }
153
+ if (route && isExactDefaultReExport(source, route.entrypoint)) {
154
+ return false;
155
+ }
156
+ return true;
157
+ });
158
+ });
159
+ if (readerRoutesAllShadowed) {
160
+ let chromeBindingsSource;
161
+ if (chromeBindingsAbsPath !== void 0) {
162
+ try {
163
+ chromeBindingsSource = readFileSync(chromeBindingsAbsPath, "utf8");
164
+ } catch {
165
+ chromeBindingsSource = void 0;
166
+ }
167
+ }
168
+ const workaroundLikelyApplied = chromeBindingsSource !== void 0 && chromeBindingsSource.includes("DesignTokenPanelBootstrap");
169
+ if (!workaroundLikelyApplied) {
170
+ ctx.logger.warn(
171
+ 'zudo-doc: settings.designTokenPanelConfigModule is set, but every reader-facing injected route\'s URL is shadowed by a kept user pages/ file \u2014 the configured Design Token Panel builder can never apply on any documentation page a reader browses on this site (zudolab/zudo-doc#3420). Thread your builder through chromeBindings.DesignTokenPanelBootstrap instead \u2014 that binding wins everywhere, including stub-rendered pages (see the designTokenPanelConfigModule docblock in settings.ts). If you already did this via a chromeBindingsModule that composes the bootstrap indirectly (never spelling "DesignTokenPanelBootstrap" literally in the resolved file), this warning is safe to ignore. A pages/ file that cleanly re-exports a shadowed route\'s own entrypoint (`export { default, paths, frontmatter } from "@takazudo/zudo-doc/routes/\u2026"`) does not count as a real shadow either \u2014 but that check is also a best-effort text scan, not module evaluation, so a file that reaches the same entrypoint some other way may still trigger this warning even though it is safe.'
172
+ );
173
+ }
174
+ }
175
+ }
123
176
  const require2 = createRequire(import.meta.url);
124
177
  let stagedRoutesDir;
125
178
  const ensureStaged = (routesSrcDir) => {
@@ -131,7 +184,7 @@ const plugin = definePlugin({
131
184
  stagedRoutesDir = dest;
132
185
  return dest;
133
186
  };
134
- for (const route of deriveRoutes(settings)) {
187
+ for (const route of derivedRoutes) {
135
188
  let resolvedEntrypoint;
136
189
  try {
137
190
  const compiledPath = require2.resolve(route.entrypoint);
@@ -159,5 +212,6 @@ const plugin = definePlugin({
159
212
  });
160
213
  var routes_default = plugin;
161
214
  export {
162
- routes_default as default
215
+ routes_default as default,
216
+ shouldWarnDtpFullyShadowed
163
217
  };
package/dist/preset.d.ts CHANGED
@@ -34,7 +34,7 @@
34
34
  */
35
35
  import { z } from "zod";
36
36
  import type { ColorScheme } from "./color-scheme-utils.js";
37
- import type { TagVocabularyEntry } from "./settings.js";
37
+ import type { TagVocabularyEntry, FaviconConfig } from "./settings.js";
38
38
  import type { DirectiveSpec } from "@takazudo/zfb/config";
39
39
  /** A single locale's content directory (`settings.locales[code]`). */
40
40
  export interface PresetLocaleConfig {
@@ -76,6 +76,17 @@ export interface PresetSettings {
76
76
  base: string;
77
77
  siteName: string;
78
78
  siteDescription: string;
79
+ /**
80
+ * Home-hero logo. `zudoDocPreset()` doesn't otherwise consume this field —
81
+ * rendering happens in `home-page/index.tsx` against the full `Settings`
82
+ * object — it is carried here only so `assertNoEmptyStringFaviconOrLogo`
83
+ * (`config-assertions/index.ts`) can validate a direct `zudoDocPreset()`
84
+ * call the same way `zudoDoc()` does (#3474).
85
+ */
86
+ logo?: string | false;
87
+ /** Favicon links. Same rationale as {@link PresetSettings.logo} — carried
88
+ * only for `assertNoEmptyStringFaviconOrLogo` (#3474). */
89
+ favicon?: string | FaviconConfig | false;
79
90
  siteUrl: string;
80
91
  trailingSlash: boolean;
81
92
  minifyHtml?: boolean;
package/dist/preset.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { assertNoCommaInVersionSlugs } from "./version-availability/index.js";
3
+ import { assertNoEmptyStringFaviconOrLogo } from "./config-assertions/index.js";
3
4
  function zudoDocPreset({
4
5
  settings,
5
6
  buildDocsSchema,
@@ -9,6 +10,7 @@ function zudoDocPreset({
9
10
  colorSchemes
10
11
  }) {
11
12
  assertNoCommaInVersionSlugs(settings.versions);
13
+ assertNoEmptyStringFaviconOrLogo(settings);
12
14
  const docsSchemaJson = z.toJSONSchema(buildDocsSchema());
13
15
  return {
14
16
  collections: buildCollections(settings, docsSchemaJson),
@@ -16,8 +16,8 @@ import {
16
16
  import {
17
17
  createRouteEnumerators
18
18
  } from "../route-enumerators/index.js";
19
+ import { stableDocs as defaultStableDocs } from "../routes/_docs-helpers.js";
19
20
  import {
20
- stableDocs as defaultStableDocs,
21
21
  isNavVisible,
22
22
  buildNavTree,
23
23
  buildBreadcrumbs,
@@ -25,7 +25,7 @@ import {
25
25
  groupSatelliteNodes,
26
26
  findNode,
27
27
  firstRoutedHref
28
- } from "../routes/_docs-helpers.js";
28
+ } from "../site-schema/nav-tree.js";
29
29
  function createRouteContext(payload, options = {}) {
30
30
  const settings = payload.settings;
31
31
  const translations = payload.translations;
@@ -1,4 +1,4 @@
1
- import type { DocNavNode } from "./_docs-helpers.js";
1
+ import type { DocNavNode } from "../site-schema/types.js";
2
2
  export declare const composeMetaTitle: (title: string) => string, HeadWithDefaults: (props: import("../head-with-defaults/index.js").HeadWithDefaultsProps) => import("preact").JSX.Element, HeaderWithDefaults: (props: import("../header-with-defaults/index.js").HeaderWithDefaultsProps) => import("preact").JSX.Element, FooterWithDefaults: (props: {
3
3
  lang?: string;
4
4
  }) => import("preact").VNode, SidebarWithDefaults: (props: import("../sidebar-with-defaults/index.js").SidebarWithDefaultsProps) => import("preact").JSX.Element, renderDocPage: (props: import("../doc-page-props/index.js").DocPageBaseProps, opts: import("../doc-page-renderer/index.js").RenderDocPageOptions) => import("preact").JSX.Element, VersionsPageView: (props: import("../versions-page/index.js").VersionsPageViewProps) => import("preact").JSX.Element, collectTagMapForLocale: (locale: string) => Map<string, import("../tag-pages/index.js").TagInfo>, TagDetailPageView: (props: {
@@ -3,7 +3,11 @@ import { createChrome } from "../chrome/index.js";
3
3
  import { DocHistory } from "../doc-history/index.js";
4
4
  import { defineChromeBindings } from "@takazudo/zudo-doc/chrome-bindings";
5
5
  import { chromeBindings } from "virtual:zudo-doc-chrome-bindings";
6
+ import { ConfiguredDesignTokenPanelBootstrap } from "./_design-token-panel-bootstrap.js";
6
7
  const chrome = createChrome(routeCtx, {
8
+ ...defineChromeBindings({
9
+ DesignTokenPanelBootstrap: ConfiguredDesignTokenPanelBootstrap
10
+ }),
7
11
  ...chromeBindings,
8
12
  ...defineChromeBindings({ DocHistory })
9
13
  });
@@ -17,7 +17,7 @@ export declare const themePackRegistry: ThemePackRegistry | null;
17
17
  export declare const i18n: import("../factory-context/index.js").FactoryI18n, defaultLocale: string, locales: readonly string[], getLocaleConfig: (locale: string) => {
18
18
  label: string;
19
19
  dir: string;
20
- } | undefined, getLocaleLabel: (locale: string) => string, t: (key: string, locale?: string) => string, urlHelpers: import("../url-helpers/index.js").UrlHelpers, withBase: (path: string) => string, stripBase: (path: string) => string, docsUrl: (slug: string, lang?: string) => string, versionedDocsUrl: (slug: string, versionSlug: string, lang?: string) => string, navHref: (path: string, lang: string | undefined, currentVersion: string | undefined, versioned?: boolean) => string, isDefaultLocaleOnlyPath: (path: string) => boolean, absoluteUrl: (pageUrl: string) => string | undefined, isExternal: (href: string) => boolean, resolveHref: (href: string) => string, buildLocaleLinks: (currentPath: string, currentLang: string) => import("../url-helpers/index.js").LocaleLink[], getCategoryOrder: () => string[], getNavSectionForSlug: (slug: string) => string | undefined, getNavSubtree: (tree: import("./_docs-helpers.js").DocNavNode[], categoryMatch?: string) => import("./_docs-helpers.js").DocNavNode[], extractHeadings: (body: string) => import("../extract-headings/index.js").HeadingItem[], resolveTagBound: (raw: string) => import("../tag-helpers/index.js").ResolvedTag, collectTags: (entries: ReadonlyArray<{
20
+ } | undefined, getLocaleLabel: (locale: string) => string, t: (key: string, locale?: string) => string, urlHelpers: import("../url-helpers/index.js").UrlHelpers, withBase: (path: string) => string, stripBase: (path: string) => string, docsUrl: (slug: string, lang?: string) => string, versionedDocsUrl: (slug: string, versionSlug: string, lang?: string) => string, navHref: (path: string, lang: string | undefined, currentVersion: string | undefined, versioned?: boolean) => string, isDefaultLocaleOnlyPath: (path: string) => boolean, absoluteUrl: (pageUrl: string) => string | undefined, isExternal: (href: string) => boolean, resolveHref: (href: string) => string, buildLocaleLinks: (currentPath: string, currentLang: string) => import("../url-helpers/index.js").LocaleLink[], getCategoryOrder: () => string[], getNavSectionForSlug: (slug: string) => string | undefined, getNavSubtree: (tree: import("./_chrome.js").DocNavNode[], categoryMatch?: string) => import("./_chrome.js").DocNavNode[], extractHeadings: (body: string) => import("../extract-headings/index.js").HeadingItem[], resolveTagBound: (raw: string) => import("../tag-helpers/index.js").ResolvedTag, collectTags: (entries: ReadonlyArray<{
21
21
  slug: string;
22
22
  data: {
23
23
  slug?: string;
@@ -27,6 +27,6 @@ export declare const i18n: import("../factory-context/index.js").FactoryI18n, de
27
27
  };
28
28
  }>, slugFn: (entrySlug: string, data: {
29
29
  slug?: string;
30
- }) => string) => Map<string, import("./_context.js").TagInfo>, resolveNavSource: (lang: string, currentVersion: string | undefined, options?: import("../nav-source-docs/index.js").NavSourceOptions) => import("../nav-source-docs/index.js").NavSourceDocs, resolveVersionedLocaleSource: (versionSlug: string, versionDocsDir: string | undefined, lang: string, localeDir: string | undefined, options?: import("../nav-source-docs/index.js").NavSourceOptions) => import("../nav-source-docs/index.js").NavSourceDocs, loadNavSourceDocs: (lang: string, currentVersion: string | undefined) => import("../nav-source-docs/index.js").NavSourceDocs, buildDocRouteEntries: (args: import("../doc-route-entries/index.js").BuildDocRouteEntriesArgs) => import("../doc-route-entries/index.js").DocRouteEntry[], enumerateDocsRoutes: (locale: string) => string[], enumerateTagsRoutes: (locale: string) => string[], enumerateVersionedRoutes: (version: import("../route-enumerators/index.js").VersionConfigForEnum, locale: string) => string[], enumerateAllRoutes: () => Map<string, string>, buildNavTree: (docs: import("./_docs-helpers.js").DocPageEntry[], locale: string, categoryMeta: Map<string, import("./_docs-helpers.js").CategoryMeta> | undefined, buildHref: import("../factory-context/index.js").RouteHrefBuilder, options?: {
30
+ }) => string) => Map<string, import("./_context.js").TagInfo>, resolveNavSource: (lang: string, currentVersion: string | undefined, options?: import("../nav-source-docs/index.js").NavSourceOptions) => import("../nav-source-docs/index.js").NavSourceDocs, resolveVersionedLocaleSource: (versionSlug: string, versionDocsDir: string | undefined, lang: string, localeDir: string | undefined, options?: import("../nav-source-docs/index.js").NavSourceOptions) => import("../nav-source-docs/index.js").NavSourceDocs, loadNavSourceDocs: (lang: string, currentVersion: string | undefined) => import("../nav-source-docs/index.js").NavSourceDocs, buildDocRouteEntries: (args: import("../site-schema/doc-route-entries.js").BuildDocRouteEntriesArgs<import("../doc-page-props/index.js").DocPageEntry>) => import("../site-schema/doc-route-entries.js").DocRouteEntry<import("../doc-page-props/index.js").DocPageEntry>[], enumerateDocsRoutes: (locale: string) => string[], enumerateTagsRoutes: (locale: string) => string[], enumerateVersionedRoutes: (version: import("../route-enumerators/index.js").VersionConfigForEnum, locale: string) => string[], enumerateAllRoutes: () => Map<string, string>, buildNavTree: (docs: import("../doc-page-props/index.js").DocPageEntry[], locale: string, categoryMeta: Map<string, import("../sidebar-tree/types.js").CategoryMeta> | undefined, buildHref: import("../factory-context/index.js").RouteHrefBuilder, options?: {
31
31
  buildHref?: import("../factory-context/index.js").RouteHrefBuilder;
32
- }) => import("./_docs-helpers.js").DocNavNode[], groupSatelliteNodes: (tree: import("./_docs-helpers.js").DocNavNode[], prefixes: string[]) => import("./_docs-helpers.js").DocNavNode[], findNode: (nodes: import("./_docs-helpers.js").DocNavNode[], slug: string) => import("./_docs-helpers.js").DocNavNode | undefined, firstRoutedHref: (node: import("./_docs-helpers.js").DocNavNode) => string | undefined, collectAutoIndexNodes: (nodes: import("./_docs-helpers.js").DocNavNode[]) => import("./_docs-helpers.js").DocNavNode[], isNavVisible: (doc: import("./_docs-helpers.js").DocPageEntry) => boolean, stableDocs: (collectionName: string) => import("./_docs-helpers.js").DocPageEntry[], toRouteSlug: (entrySlug: string) => string, toSlugParams: (routeSlug: string) => string[];
32
+ }) => import("./_chrome.js").DocNavNode[], groupSatelliteNodes: (tree: import("./_chrome.js").DocNavNode[], prefixes: string[]) => import("./_chrome.js").DocNavNode[], findNode: (nodes: import("./_chrome.js").DocNavNode[], slug: string) => import("./_chrome.js").DocNavNode | undefined, firstRoutedHref: (node: import("./_chrome.js").DocNavNode) => string | undefined, collectAutoIndexNodes: (nodes: import("./_chrome.js").DocNavNode[]) => import("./_chrome.js").DocNavNode[], isNavVisible: (doc: import("../doc-page-props/index.js").DocPageEntry) => boolean, stableDocs: (collectionName: string) => import("../doc-page-props/index.js").DocPageEntry[], toRouteSlug: (entrySlug: string) => string, toSlugParams: (routeSlug: string) => string[];
@@ -0,0 +1,18 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ import type { JSX } from "preact";
4
+ /**
5
+ * The design-token-panel island injected routes mount instead of the package
6
+ * default: identical behavior, except the mode-scoped `PanelConfig` builder is
7
+ * the one the routes plugin resolved — the host's
8
+ * `settings.designTokenPanelConfigModule` when set, the package default
9
+ * otherwise.
10
+ *
11
+ * Renders `null` on both SSR and client (the panel self-mounts as a side
12
+ * effect of `bootstrapDesignTokenPanel`), matching the package default's
13
+ * non-`ssrFallback` `Island({ when: "load" })` shape.
14
+ */
15
+ export declare function ConfiguredDesignTokenPanelBootstrap(): JSX.Element | null;
16
+ export declare namespace ConfiguredDesignTokenPanelBootstrap {
17
+ var displayName: string;
18
+ }
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { runDesignTokenPanelBootstrapOnce } from "../design-token-panel-bootstrap.js";
3
+ import { buildDesignTokenPanelConfig } from "virtual:zudo-doc-design-token-panel-config";
4
+ function ConfiguredDesignTokenPanelBootstrap() {
5
+ runDesignTokenPanelBootstrapOnce(buildDesignTokenPanelConfig, "configured");
6
+ return null;
7
+ }
8
+ ConfiguredDesignTokenPanelBootstrap.displayName = "ConfiguredDesignTokenPanelBootstrap";
9
+ export {
10
+ ConfiguredDesignTokenPanelBootstrap
11
+ };
@@ -1,6 +1,4 @@
1
- import { type CategoryMeta } from "../sidebar-tree/index.js";
2
- import type { DocNavNode, DocPageEntry } from "../doc-page-props/index.js";
3
- export type { CategoryMeta, DocNavNode, DocPageEntry };
1
+ import type { DocPageEntry } from "../doc-page-props/index.js";
4
2
  /**
5
3
  * Identity-stable, draft-filtered `DocPageEntry[]` for a collection. Returns the
6
4
  * SAME array instance on every call within one build (anchored on the snapshot
@@ -8,36 +6,3 @@ export type { CategoryMeta, DocNavNode, DocPageEntry };
8
6
  * deliberately unmemoized. Passed as `stableDocs` to `createNavSourceDocs`.
9
7
  */
10
8
  export declare function stableDocs(collectionName: string): DocPageEntry[];
11
- /** Filter predicate: true when a doc should appear in navigation. */
12
- export declare function isNavVisible(doc: DocPageEntry): boolean;
13
- /** A docs-href builder: `(slug, locale) => url`. */
14
- export type BuildHref = (slug: string, locale: string) => string;
15
- export interface BuildNavTreeOptions {
16
- buildHref?: BuildHref;
17
- }
18
- /**
19
- * Build a recursive navigation tree from a flat doc collection. Delegates tree
20
- * construction to the shared `buildSidebarTree`; keeps the host-side concerns
21
- * (DocNavNode shape, root-index node synthesis). `buildHref` parameterizes the
22
- * href space (default: the injected `docsUrl`). Unlike the host copy this drops
23
- * the LRU / identity caches — the snapshot-anchored `stableDocs` already makes
24
- * the input arrays identity-stable so the factory-level `memoizeDerived` short-
25
- * circuits per build.
26
- */
27
- export declare function buildNavTree(docs: DocPageEntry[], locale: string, categoryMeta: Map<string, CategoryMeta> | undefined, buildHref: BuildHref, options?: BuildNavTreeOptions): DocNavNode[];
28
- /** Group "satellite" nodes (slug-prefix siblings) under their primary node. */
29
- export declare function groupSatelliteNodes(tree: DocNavNode[], prefixes: string[]): DocNavNode[];
30
- /** Find a node by slug anywhere in the tree. */
31
- export declare function findNode(nodes: DocNavNode[], slug: string): DocNavNode | undefined;
32
- /** Href of the first routed descendant, walking children depth-first. */
33
- export declare function firstRoutedHref(node: DocNavNode): string | undefined;
34
- /** Collect all category nodes that have children but no page (no index.mdx). */
35
- export declare function collectAutoIndexNodes(nodes: DocNavNode[]): DocNavNode[];
36
- export interface BreadcrumbItem {
37
- label: string;
38
- href?: string;
39
- }
40
- /** Build breadcrumb trail by walking the nav tree. `homeHref` is the locale
41
- * docs root (injected). `hrefFor` optionally remaps intermediate crumbs into
42
- * a versioned URL space. */
43
- export declare function buildBreadcrumbs(tree: DocNavNode[], slug: string, homeHref: string, hrefFor?: (slug: string) => string): BreadcrumbItem[];