@takazudo/zudo-doc 4.5.0 → 5.0.1

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 (86) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/category-nav/index.d.ts +31 -0
  3. package/dist/category-nav/index.js +15 -3
  4. package/dist/category-tree-nav/index.d.ts +32 -0
  5. package/dist/category-tree-nav/index.js +16 -4
  6. package/dist/chrome/derive.d.ts +32 -3
  7. package/dist/chrome/derive.js +26 -7
  8. package/dist/code-syntax/mermaid-init-script.d.ts +6 -7
  9. package/dist/config.d.ts +22 -5
  10. package/dist/config.js +11 -0
  11. package/dist/content.css +1 -0
  12. package/dist/desktop-toc-toggle-island/index.d.ts +7 -0
  13. package/dist/desktop-toc-toggle-island/index.js +64 -0
  14. package/dist/doc-page-renderer/index.d.ts +12 -1
  15. package/dist/doc-page-renderer/index.js +10 -3
  16. package/dist/doc-page-shell/index.d.ts +13 -0
  17. package/dist/doc-page-shell/index.js +37 -3
  18. package/dist/doclayout/doc-layout.d.ts +14 -0
  19. package/dist/doclayout/doc-layout.js +4 -2
  20. package/dist/features.css +82 -10
  21. package/dist/head-with-defaults/index.js +1 -0
  22. package/dist/header/header.d.ts +1 -1
  23. package/dist/header/header.js +2 -2
  24. package/dist/header/types.d.ts +7 -0
  25. package/dist/header-with-defaults/index.d.ts +2 -0
  26. package/dist/header-with-defaults/index.js +9 -2
  27. package/dist/i18n-version/version-switcher.d.ts +36 -0
  28. package/dist/i18n-version/version-switcher.js +43 -3
  29. package/dist/inline-version-switcher/index.d.ts +18 -0
  30. package/dist/inline-version-switcher/index.js +3 -1
  31. package/dist/mdx-components/index.d.ts +11 -0
  32. package/dist/mdx-components/index.js +4 -4
  33. package/dist/nav-data-prep/index.d.ts +18 -3
  34. package/dist/nav-data-prep/index.js +3 -3
  35. package/dist/plugins/internal/claude-resources/generate.d.ts +11 -0
  36. package/dist/plugins/internal/claude-resources/generate.js +16 -13
  37. package/dist/plugins/internal/claude-resources/index.js +6 -1
  38. package/dist/preset.d.ts +16 -7
  39. package/dist/preset.js +8 -6
  40. package/dist/routes/_context.d.ts +1 -1
  41. package/dist/safelist.css +1 -1
  42. package/dist/settings.d.ts +17 -2
  43. package/dist/site-tree-nav/index.d.ts +21 -0
  44. package/dist/site-tree-nav/index.js +26 -7
  45. package/dist/theme-packs/academia/meta.json +1 -1
  46. package/dist/theme-packs/academia/pack.css +13 -3
  47. package/dist/theme-packs/blueprint/meta.json +1 -1
  48. package/dist/theme-packs/blueprint/pack.css +9 -0
  49. package/dist/theme-packs/broadsheet/meta.json +1 -1
  50. package/dist/theme-packs/broadsheet/pack.css +9 -0
  51. package/dist/theme-packs/drift/meta.json +1 -1
  52. package/dist/theme-packs/drift/pack.css +9 -0
  53. package/dist/theme-packs/fjord/meta.json +1 -1
  54. package/dist/theme-packs/fjord/pack.css +9 -0
  55. package/dist/theme-packs/hearth/meta.json +1 -1
  56. package/dist/theme-packs/hearth/pack.css +9 -0
  57. package/dist/theme-packs/hollow/meta.json +1 -1
  58. package/dist/theme-packs/hollow/pack.css +9 -0
  59. package/dist/theme-packs/matcha/meta.json +1 -1
  60. package/dist/theme-packs/matcha/pack.css +9 -0
  61. package/dist/theme-packs/nocturne/meta.json +1 -1
  62. package/dist/theme-packs/nocturne/pack.css +9 -0
  63. package/dist/theme-packs/observatory/meta.json +1 -1
  64. package/dist/theme-packs/observatory/pack.css +18 -0
  65. package/dist/theme-packs/onyx/meta.json +1 -1
  66. package/dist/theme-packs/onyx/pack.css +9 -0
  67. package/dist/theme-packs/sakura/meta.json +1 -1
  68. package/dist/theme-packs/sakura/pack.css +9 -0
  69. package/dist/theme-packs/sumi/meta.json +1 -1
  70. package/dist/theme-packs/sumi/pack.css +9 -0
  71. package/dist/theme-packs/timberline/meta.json +1 -1
  72. package/dist/theme-packs/timberline/pack.css +9 -0
  73. package/dist/theme-packs-registry/validator.js +38 -6
  74. package/dist/toc-prepaint/index.d.ts +60 -0
  75. package/dist/toc-prepaint/index.js +44 -0
  76. package/dist/url-helpers/index.d.ts +1 -1
  77. package/dist/url-helpers/index.js +2 -2
  78. package/dist/version-availability/index.d.ts +75 -0
  79. package/dist/version-availability/index.js +56 -0
  80. package/dist/versions-page/index.d.ts +9 -1
  81. package/dist/versions-page/index.js +6 -2
  82. package/eject/header/header.tsx +3 -2
  83. package/eject/header/types.ts +7 -0
  84. package/package.json +17 -10
  85. package/tsconfig.base.json +2 -1
  86. package/zfb-config-shim.d.ts +22 -204
package/CHANGELOG.md CHANGED
@@ -4,6 +4,60 @@ All notable changes to `@takazudo/zudo-doc` are documented in this file.
4
4
 
5
5
  The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
6
6
 
7
+ ## [5.0.1] - 2026-08-04
8
+
9
+ ### Bug Fixes
10
+
11
+ - `setup-doc-skill.sh` no longer skips a tracked skill whose global symlink is already correct. The idempotency check compared the literal string stored in the existing symlink against a target built from `MAIN_PROJECT_DIR`, which is derived from `git worktree list` and is therefore always the **physical** path. When the project or any parent directory sits behind a symlink — a symlinked `$HOME`, a symlinked checkout, or macOS's `$TMPDIR` (`/var` → `/private/var`) — the two forms name the same directory but differ as strings, so the script mis-read its own correct link as foreign and printed a spurious `already links to ...` warning instead of no-opping. It now compares where the link actually lands, via a portable `physical_dir` helper (`cd` + `pwd -P`, since stock macOS ships no `realpath(1)` and BSD `readlink` has no `-f`). The dangling-symlink replacement path and the D4 safe-link policy are unchanged — nothing the script does not own is ever claimed or deleted (b6a226c, 76a39c0)
12
+ - Harden that same helper against unresolvable directories: it now falls back to its raw input on any resolution failure. An unreadable directory made `cd` fail and the subshell yield an empty string, so two different unreadable paths both collapsed to `""` and compared equal, which would have silently skipped a link that should have been created. `cd`'s permission-denied output is suppressed (76a39c0)
13
+
14
+ ### Other Changes
15
+
16
+ - Add a regression test that reaches the skill directory through a deliberate symlinked alias, so the defect above reproduces on Linux as well. The original bug was invisible to CI because only macOS symlinks `$TMPDIR`; the test was verified to fail against the pre-fix script (b6a226c)
17
+
18
+ ## [5.0.0] - 2026-08-04
19
+
20
+ ### Breaking Changes
21
+
22
+ - The `@takazudo/zfb` family peer range moves from `^1.1.0` to `^2.1.0`. Consumers must upgrade `@takazudo/zfb`, `@takazudo/zfb-md-wasm`, and `@takazudo/zfb-runtime` in lockstep; `@takazudo/design-token-lint` also moves from 1.x to 2.x (d23e47a8, fb91919f)
23
+ - The `githubAutolinks` feature and its `githubAutolinksRepo` setting are removed. A project still passing `githubAutolinksRepo` now fails at runtime with an explicit rejection rather than silently ignoring it, and the config shim likewise rejects zfb 2.0.0's removed `githubAutolinks` key (3d4a8ca7, 4c03befc, 255676da)
24
+ - `strictContentBridge` is enabled by default in this repo's config and re-points the b4push/CI wiring — a content-bridge fallback now fails a plain `pnpm build` instead of only tripping the allowlist-gated b4push step (#3234, d3035f68)
25
+ - The shared base `tsconfig` flips its JSX mode to `react-jsx` with `jsxImportSource: preact`, and `@types/react` is dropped from the scaffolded devDependencies. Projects that relied on the React typings being present must add them back explicitly (#3182, 705c3957, be91c050)
26
+
27
+ ### Features
28
+
29
+ - Desktop TOC visibility toggle — a new `tocToggle` setting adds a prepaint-backed show/hide control for the desktop table of contents, wired through the generator, the showcase, and the sidebar fixture, with e2e coverage. The claim is scoped to default-TOC pages (c5a26102, 3f851196, ed1f6887, 304ce68d, 409b0bfe, 5301b011)
30
+ - New `entryDocSlug` and `headerNav` versioned config fields, letting a versioned site name its entry document and carry version context through header navigation (#3217, 0d42ffbd, e56c4dee)
31
+ - Version switcher now recomputes its disabled state across SPA navigation — each page emits an unavailable-version payload into the swapped content and the switcher rewires on `after-swap` (faa0e8aa, d2a3fccf)
32
+ - GFM task lists and footnotes are now enabled as a preset default (31c1f253)
33
+ - `strictContentBridge` added as a shell passthrough field so downstream projects can opt into the strict content bridge from their own config (725febec, c1432ffa)
34
+ - Theme packs gain a catalog-wide `(pointer: coarse)` scroll fallback for fixed-attachment layers, and the pack validator now accepts top-level `@media`-wrapped pack-scoped rules (ee973b27, 594b0486)
35
+ - `create-zudo-doc` ships a default `public/` favicon set in the base template, with an SVG favicon link emitted ahead of the `.ico`/`.png` entries (a9d100bc, 19d11f8d)
36
+ - The safelist checker gains a `safelist-ok` marker escape hatch plus honest remediation text (150925a4)
37
+
38
+ ### Bug Fixes
39
+
40
+ - Restore desktop TOC sticky scroll-follow, with an e2e regression guard (ab25912a, ac7d3fc0)
41
+ - Version availability is now computed from real route generation rather than an approximation, and `unavailableVersions` is computed for both version switchers (01409a94, a820304b)
42
+ - An absent version-availability payload now leaves the disabled state untouched instead of clearing it, and the switcher rewire is deferred to `DOMContentLoaded` (6593e0c0, d5d6b586)
43
+ - Thread `currentVersion` into the `CategoryNav` / `CategoryTreeNav` / `SiteTreeNav` wrappers so versioned sites link correctly from nav cards (ddeaa6da)
44
+ - `entryDocSlug` is optional and locale-aware in the versions page (20890f21)
45
+ - The zfb config shim re-exports `zfb/config` instead of hand-copying its shape, and shim-shape validation is now AST-based rather than substring matching, closing three false-negative paths (#3239, #3241, 213f4e5a, ea109c66, 70ad667c)
46
+ - Restore public API back-compat on the versioning deps/node shapes (416baef2)
47
+ - `claude-resources` excludes build directories by basename at any depth rather than only at the top level (6e830dc4)
48
+ - Long delimiter-free inline code identifiers now wrap instead of overflowing (62679947)
49
+ - Rename the TOC island helpers to avoid an island marker-name collision (7c29a6ca)
50
+ - Resolve package-only dependencies in the packed-tarball slow fixtures (df23d982)
51
+
52
+ ### Other Changes
53
+
54
+ - Academia theme pack readability pass: low-opacity rest-state link underline and 0.9rem code blocks (8dbb8f46)
55
+ - Restructure the `CLAUDE.md` hierarchy and fix seven accuracy defects surfaced by the refactor self-review (#3167, fd2124ba, ed8917a4)
56
+ - Repository cleanup — remove six unused dependencies and two dead files, delete host-side source superseded by package-owned equivalents, drop finished-epic scratch/report directories, and correct stale references in the README, config, and b4push skill (332c8406, 6d2f90dd, a40bc5fd, c035c76d, 7869d45b)
57
+ - Record the Tauri Mode 1 CSP verification result and put the icon prerequisite before the build command in the Tauri docs (4a45c00f, 532eb81e, 8a93286e)
58
+ - Test coverage: dogfood snapshot plus e2e fixture/spec coverage for the version-link family, an SPA-navigation guard for the version-switcher disabled state, a wider A2 fixture covering headings/task lists/footnotes/directives, and the locked favicon set mirrored in the target manifest fixture (#3219, #3245, 1a73989b, fab8568d, 0310c8fb, 94e09ceb)
59
+ - Documentation corrections for the content-fallback guard wiring, the e2e fixture-setup fast path, and the safelist-ok example (9047c389, 2bb9a1e1, 70d69842, 66ad3101, 100761b3)
60
+
7
61
  ## [4.5.0] - 2026-08-02
8
62
 
9
63
  ### Features
@@ -3,6 +3,16 @@
3
3
  import type { JSX } from "preact";
4
4
  /** Minimal nav node shape required by the category-nav factory. */
5
5
  export interface CategoryNavNode {
6
+ /**
7
+ * Content slug — used by remapVersionedHrefs to rebuild a versioned href.
8
+ *
9
+ * Optional — `./category-nav` is a documented frozen-1.0 public subpath
10
+ * (`packages/zudo-doc/CLAUDE.md`/`API.md`), so a pre-#3218 caller whose
11
+ * `buildNavTree`/`findNode` implementations emit the older slug-less node
12
+ * shape must keep compiling. A node without a slug cannot be rebuilt into
13
+ * its versioned form, so `remapVersionedHrefs` leaves its href untouched.
14
+ */
15
+ slug?: string;
6
16
  label: string;
7
17
  description?: string;
8
18
  href?: string;
@@ -38,6 +48,14 @@ export interface CategoryNavWrapperProps {
38
48
  * Defaults to defaultLocale when not provided.
39
49
  */
40
50
  lang?: string;
51
+ /**
52
+ * Active version slug for a `/v/{version}` route. Injected via
53
+ * createMdxComponents() closure alongside `lang` (#3218). When set, the nav
54
+ * source resolves the version's doc collection and emitted hrefs are
55
+ * remapped to their versioned form; omitted (latest/unversioned pages)
56
+ * leaves behavior unchanged.
57
+ */
58
+ currentVersion?: string;
41
59
  /** Optional extra CSS classes forwarded to the <nav> element. */
42
60
  class?: string;
43
61
  }
@@ -68,6 +86,19 @@ export interface CategoryNavDeps {
68
86
  * Host passes `firstRoutedHref` from `@/utils/docs`.
69
87
  */
70
88
  firstRoutedHref: (node: CategoryNavNode) => string | undefined;
89
+ /**
90
+ * Build a versioned docs URL: `versionedDocsUrl(slug, versionSlug, lang)`.
91
+ * Host passes `ctx.versionedDocsUrl`. Used to remap the resolved tree's
92
+ * hrefs into their versioned form when `currentVersion` is set (#3218) —
93
+ * mirrors the sidebar's `buildSidebarNodes` two-step in `chrome/derive.tsx`.
94
+ *
95
+ * Optional — `./category-nav` is a documented frozen-1.0 public subpath
96
+ * (`packages/zudo-doc/CLAUDE.md`/`API.md`), so a pre-#3218 caller that hand-
97
+ * constructs `CategoryNavDeps` without this field must keep compiling.
98
+ * Omitting it while passing `currentVersion` leaves hrefs unversioned (a
99
+ * loud dev warning is emitted) rather than throwing.
100
+ */
101
+ versionedDocsUrl?: (slug: string, versionSlug: string, lang: string) => string;
71
102
  }
72
103
  /**
73
104
  * Create the `CategoryNavWrapper` component bound to the host's nav source
@@ -1,18 +1,30 @@
1
1
  import { jsx } from "preact/jsx-runtime";
2
2
  import { CategoryNav as CategoryNavV2 } from "../nav-indexing/index.js";
3
+ import { remapVersionedHrefs } from "../nav-data-prep/index.js";
3
4
  function createCategoryNavWrapper(deps) {
4
- const { defaultLocale, resolveNavSource, buildNavTree, findNode, firstRoutedHref } = deps;
5
+ const { defaultLocale, resolveNavSource, buildNavTree, findNode, firstRoutedHref, versionedDocsUrl } = deps;
5
6
  function CategoryNavWrapper({
6
7
  category,
7
8
  categories,
8
9
  lang = defaultLocale,
10
+ currentVersion,
9
11
  class: className
10
12
  }) {
11
13
  const locale = lang;
12
- const { navDocs, categoryMeta } = resolveNavSource(locale, void 0, {
14
+ const { navDocs, categoryMeta } = resolveNavSource(locale, currentVersion, {
13
15
  keepUnlisted: true
14
16
  });
15
- const tree = buildNavTree(navDocs, locale, categoryMeta);
17
+ const rawTree = buildNavTree(navDocs, locale, categoryMeta);
18
+ let tree = rawTree;
19
+ if (currentVersion) {
20
+ if (versionedDocsUrl) {
21
+ tree = remapVersionedHrefs(rawTree, currentVersion, locale, versionedDocsUrl);
22
+ } else {
23
+ console.warn(
24
+ "[zudo-doc] CategoryNavWrapper: currentVersion is set but CategoryNavDeps.versionedDocsUrl was not provided \u2014 nav card hrefs will NOT be remapped into the version."
25
+ );
26
+ }
27
+ }
16
28
  let children;
17
29
  if (categories !== void 0) {
18
30
  children = categories.map((slug) => {
@@ -3,6 +3,17 @@
3
3
  import type { JSX } from "preact";
4
4
  /** Minimal nav node shape required by the category-tree-nav factory. */
5
5
  export interface CategoryTreeNavNode {
6
+ /**
7
+ * Content slug — used by remapVersionedHrefs to rebuild a versioned href.
8
+ *
9
+ * Optional — `./category-tree-nav` is a documented frozen-1.0 public
10
+ * subpath (`packages/zudo-doc/CLAUDE.md`/`API.md`), so a pre-#3218 caller
11
+ * whose `buildNavTree`/`findNode` implementations emit the older slug-less
12
+ * node shape must keep compiling. A node without a slug cannot be rebuilt
13
+ * into its versioned form, so `remapVersionedHrefs` leaves its href
14
+ * untouched.
15
+ */
16
+ slug?: string;
6
17
  label: string;
7
18
  description?: string;
8
19
  href?: string;
@@ -26,6 +37,14 @@ export interface CategoryTreeNavWrapperProps {
26
37
  * Defaults to defaultLocale when not provided.
27
38
  */
28
39
  lang?: string;
40
+ /**
41
+ * Active version slug for a `/v/{version}` route. Injected via
42
+ * createMdxComponents() closure alongside `lang` (#3218). When set, the nav
43
+ * source resolves the version's doc collection and emitted hrefs are
44
+ * remapped to their versioned form; omitted (latest/unversioned pages)
45
+ * leaves behavior unchanged.
46
+ */
47
+ currentVersion?: string;
29
48
  }
30
49
  /** Injected dependencies for {@link createCategoryTreeNavWrapper}. */
31
50
  export interface CategoryTreeNavDeps {
@@ -54,6 +73,19 @@ export interface CategoryTreeNavDeps {
54
73
  * Host passes `findNode` from `@/utils/docs`.
55
74
  */
56
75
  findNode: (tree: CategoryTreeNavNode[], slug: string) => CategoryTreeNavNode | undefined;
76
+ /**
77
+ * Build a versioned docs URL: `versionedDocsUrl(slug, versionSlug, lang)`.
78
+ * Host passes `ctx.versionedDocsUrl`. Used to remap the resolved tree's
79
+ * hrefs into their versioned form when `currentVersion` is set (#3218) —
80
+ * mirrors the sidebar's `buildSidebarNodes` two-step in `chrome/derive.tsx`.
81
+ *
82
+ * Optional — `./category-tree-nav` is a documented frozen-1.0 public
83
+ * subpath (`packages/zudo-doc/CLAUDE.md`/`API.md`), so a pre-#3218 caller
84
+ * that hand-constructs `CategoryTreeNavDeps` without this field must keep
85
+ * compiling. Omitting it while passing `currentVersion` leaves hrefs
86
+ * unversioned (a loud dev warning is emitted) rather than throwing.
87
+ */
88
+ versionedDocsUrl?: (slug: string, versionSlug: string, lang: string) => string;
57
89
  }
58
90
  /**
59
91
  * Create the `CategoryTreeNavWrapper` component bound to the host's nav source
@@ -1,17 +1,29 @@
1
1
  import { jsx } from "preact/jsx-runtime";
2
2
  import { CategoryTreeNav as CategoryTreeNavV2 } from "../nav-indexing/index.js";
3
+ import { remapVersionedHrefs } from "../nav-data-prep/index.js";
3
4
  function createCategoryTreeNavWrapper(deps) {
4
- const { defaultLocale, resolveNavSource, buildNavTree, groupSatelliteNodes, findNode } = deps;
5
+ const { defaultLocale, resolveNavSource, buildNavTree, groupSatelliteNodes, findNode, versionedDocsUrl } = deps;
5
6
  function CategoryTreeNavWrapper({
6
7
  category,
7
- lang = defaultLocale
8
+ lang = defaultLocale,
9
+ currentVersion
8
10
  }) {
9
11
  const locale = lang;
10
- const { navDocs, categoryMeta } = resolveNavSource(locale, void 0, {
12
+ const { navDocs, categoryMeta } = resolveNavSource(locale, currentVersion, {
11
13
  keepUnlisted: true
12
14
  });
13
15
  const rawTree = buildNavTree(navDocs, locale, categoryMeta);
14
- const tree = groupSatelliteNodes(rawTree, [category]);
16
+ const groupedTree = groupSatelliteNodes(rawTree, [category]);
17
+ let tree = groupedTree;
18
+ if (currentVersion) {
19
+ if (versionedDocsUrl) {
20
+ tree = remapVersionedHrefs(groupedTree, currentVersion, locale, versionedDocsUrl);
21
+ } else {
22
+ console.warn(
23
+ "[zudo-doc] CategoryTreeNavWrapper: currentVersion is set but CategoryTreeNavDeps.versionedDocsUrl was not provided \u2014 nav card hrefs will NOT be remapped into the version."
24
+ );
25
+ }
26
+ }
15
27
  const categoryNode = findNode(tree, category);
16
28
  const children = categoryNode?.children.filter((c) => c.hasPage || c.children.length > 0) ?? [];
17
29
  if (children.length === 0) return null;
@@ -64,8 +64,28 @@ export declare function deriveBodyEndIslands(ctx: ChromeContext): (props: import
64
64
  /** Derive the DocHistory island: `ctx.hostBindings.DocHistory` when supplied,
65
65
  * else the package no-op stub. */
66
66
  export declare function deriveDocHistorySlot(ctx: ChromeContext): typeof DocHistoryStub;
67
- /** Derive the inline version-switcher builder bound to the context. */
68
- export declare function deriveInlineVersionSwitcher(ctx: ChromeContext): (slug: string, locale: string, currentVersion?: string) => JSX.Element | undefined;
67
+ /**
68
+ * Derive `getUnavailableVersions(slug, locale)`, shared by the inline
69
+ * breadcrumb switcher (`deriveInlineVersionSwitcher` below) and the header
70
+ * dropdown switcher (`createHeaderWithDefaults`) — the genuinely
71
+ * multi-factory piece of #3215's availability computation, so both callers
72
+ * wire the SAME implementation instead of re-deriving the recipe.
73
+ */
74
+ export declare function deriveGetUnavailableVersions(ctx: ChromeContext): (slug: string | undefined, locale: string) => ReadonlySet<string> | undefined;
75
+ /**
76
+ * Derive the inline version-switcher builder bound to the context.
77
+ *
78
+ * `getUnavailableVersions` is optional so a caller that already built one
79
+ * (e.g. `createRenderDocPage`, which also emits it as the SPA-rewire client
80
+ * payload — #3243) can pass that SAME instance instead of a second one being
81
+ * derived here. `createGetUnavailableVersions` owns a per-(locale, version)
82
+ * nav-source cache, so two independently-derived instances would each
83
+ * resolve the nav source and rebuild the auto-index tree from scratch on
84
+ * their first call per (locale, version) — doubling that work on every
85
+ * versioned route. Defaults to deriving its own when omitted, so this stays
86
+ * backward-compatible with a bare `deriveInlineVersionSwitcher(ctx)` call.
87
+ */
88
+ export declare function deriveInlineVersionSwitcher(ctx: ChromeContext, getUnavailableVersions?: (slug: string | undefined, locale: string) => ReadonlySet<string> | undefined): (slug: string, locale: string, currentVersion?: string) => JSX.Element | undefined;
69
89
  /**
70
90
  * Derive the locale-aware `createMdxComponents` factory plus the
71
91
  * `SiteTreeNavWrapper` (also exposed by `createChrome`). The nav wrappers
@@ -75,9 +95,18 @@ export declare function deriveInlineVersionSwitcher(ctx: ChromeContext): (slug:
75
95
  * for the injected package path. The content overrides (Details / HtmlPreview /
76
96
  * Island / PresetGenerator + any host extras) come from `ctx.hostBindings.mdxExtras`
77
97
  * merged over the package defaults.
98
+ *
99
+ * `createMdxComponentsBound(lang, currentVersion?)` (#3218) threads the active
100
+ * version through to the nav wrappers as a `currentVersion` prop — see
101
+ * `createMdxComponents()`'s `CategoryNavBound`/etc — so `resolveNavSource`
102
+ * resolves the version's doc collection and emitted hrefs get remapped via
103
+ * `versionedDocsUrl`, mirroring `buildSidebarNodes`' two-step above. This works
104
+ * for BOTH the package-default wrapper (built from `versionedDocsUrl` below)
105
+ * and a host-supplied `ctx.components` override, since the prop injection
106
+ * happens one layer up in `createMdxComponents()`, after the wrapper choice.
78
107
  */
79
108
  export declare function deriveMdxComponents(ctx: ChromeContext): {
80
- createMdxComponentsBound: (lang?: string) => Record<string, unknown>;
109
+ createMdxComponentsBound: (lang?: string, currentVersion?: string) => Record<string, unknown>;
81
110
  SiteTreeNavWrapper: FactoryComponent;
82
111
  };
83
112
  export type { Settings };
@@ -21,6 +21,7 @@ import {
21
21
  HtmlPreviewWrapper
22
22
  } from "../html-preview-wrapper/index.js";
23
23
  import { createInlineVersionSwitcher } from "../inline-version-switcher/index.js";
24
+ import { createGetUnavailableVersions } from "../version-availability/index.js";
24
25
  import {
25
26
  buildRootMenuItems as buildRootMenuItemsBase,
26
27
  buildLocaleLinksForNav as buildLocaleLinksForNavBase,
@@ -103,7 +104,7 @@ function deriveNavDataPrep(ctx) {
103
104
  currentVersion,
104
105
  ctx.settings.headerNav,
105
106
  (key, l) => ctx.t(key, l),
106
- (path, l, v) => ctx.navHref(path, l, v)
107
+ (path, l, v, versioned) => ctx.navHref(path, l, v, versioned)
107
108
  );
108
109
  }
109
110
  function buildLocaleLinksForNav(currentPath, lang, localeCount) {
@@ -204,14 +205,27 @@ function deriveBodyEndIslands(ctx) {
204
205
  function deriveDocHistorySlot(ctx) {
205
206
  return ctx.hostBindings.DocHistory ?? DocHistoryStub;
206
207
  }
207
- function deriveInlineVersionSwitcher(ctx) {
208
+ function deriveGetUnavailableVersions(ctx) {
209
+ return createGetUnavailableVersions({
210
+ versions: ctx.settings.versions,
211
+ resolveNavSource: ctx.resolveNavSource,
212
+ toRouteSlug: ctx.toRouteSlug,
213
+ // Hrefs are irrelevant to availability — bound to docsUrl only because
214
+ // ctx.buildNavTree's buildHref param is required, mirroring the same
215
+ // throwaway binding route-enumerators/doc-route-entries use.
216
+ buildNavTree: (docs, locale, categoryMeta) => ctx.buildNavTree(docs, locale, categoryMeta, (slug, loc) => ctx.docsUrl(slug, loc)),
217
+ collectAutoIndexNodes: ctx.collectAutoIndexNodes
218
+ });
219
+ }
220
+ function deriveInlineVersionSwitcher(ctx, getUnavailableVersions = deriveGetUnavailableVersions(ctx)) {
208
221
  return createInlineVersionSwitcher({
209
222
  settings: ctx.settings,
210
223
  defaultLocale: ctx.defaultLocale,
211
224
  t: ctx.t,
212
225
  docsUrl: ctx.docsUrl,
213
226
  versionedDocsUrl: ctx.versionedDocsUrl,
214
- withBase: ctx.withBase
227
+ withBase: ctx.withBase,
228
+ getUnavailableVersions
215
229
  });
216
230
  }
217
231
  function deriveMdxComponents(ctx) {
@@ -225,7 +239,8 @@ function deriveMdxComponents(ctx) {
225
239
  (slug, loc) => ctx.docsUrl(slug, loc)
226
240
  )),
227
241
  findNode: ctx.findNode,
228
- firstRoutedHref: ctx.firstRoutedHref
242
+ firstRoutedHref: ctx.firstRoutedHref,
243
+ versionedDocsUrl: ctx.versionedDocsUrl
229
244
  });
230
245
  const CategoryTreeNavWrapper = ctx.components.CategoryTreeNav ?? createCategoryTreeNavWrapper({
231
246
  defaultLocale: ctx.defaultLocale,
@@ -237,7 +252,8 @@ function deriveMdxComponents(ctx) {
237
252
  (slug, loc) => ctx.docsUrl(slug, loc)
238
253
  )),
239
254
  groupSatelliteNodes: ctx.groupSatelliteNodes,
240
- findNode: ctx.findNode
255
+ findNode: ctx.findNode,
256
+ versionedDocsUrl: ctx.versionedDocsUrl
241
257
  });
242
258
  const SiteTreeNavWrapper = ctx.components.SiteTreeNav ?? createSiteTreeNavWrapper({
243
259
  defaultLocale: ctx.defaultLocale,
@@ -249,7 +265,8 @@ function deriveMdxComponents(ctx) {
249
265
  (slug, loc) => ctx.docsUrl(slug, loc)
250
266
  )),
251
267
  groupSatelliteNodes: ctx.groupSatelliteNodes,
252
- getCategoryOrder: ctx.getCategoryOrder
268
+ getCategoryOrder: ctx.getCategoryOrder,
269
+ versionedDocsUrl: ctx.versionedDocsUrl
253
270
  });
254
271
  function HtmlPreviewBound(props) {
255
272
  return HtmlPreviewWrapper({
@@ -266,10 +283,11 @@ function deriveMdxComponents(ctx) {
266
283
  PresetGenerator: (_props) => null
267
284
  };
268
285
  const mdxExtras = { ...mdxExtrasDefault, ...ctx.hostBindings.mdxExtras ?? {} };
269
- function createMdxComponentsBound(lang = ctx.defaultLocale) {
286
+ function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion) {
270
287
  return createMdxComponents({
271
288
  settings: ctx.settings,
272
289
  locale: lang,
290
+ currentVersion,
273
291
  navData: {
274
292
  CategoryNav: CategoryNavWrapper,
275
293
  CategoryTreeNav: CategoryTreeNavWrapper,
@@ -289,6 +307,7 @@ export {
289
307
  deriveColorSchemeGenerators,
290
308
  deriveComposeMetaTitle,
291
309
  deriveDocHistorySlot,
310
+ deriveGetUnavailableVersions,
292
311
  deriveInlineVersionSwitcher,
293
312
  deriveMdxComponents,
294
313
  deriveNavDataPrep,
@@ -4,13 +4,12 @@
4
4
  *
5
5
  * Pinned to a specific minor.patch (not the bare `@11` major) so the
6
6
  * runtime version is byte-stable across deploys; floating on the major
7
- * would silently pull in upstream theme-API or behaviour changes the
8
- * SSR-time mermaid in package.json hasn't been pinned to. Bump policy:
9
- * raise this string in the same PR that bumps `mermaid` in
10
- * package.json so the SSR-time and runtime mermaid versions stay
11
- * matched. Cross-major jumps need an extra parity smoke (`pnpm build`
12
- * and a manual diagram render) since esm.sh's exports drift between
13
- * majors.
7
+ * would silently pull in upstream theme-API or behaviour changes. Bump
8
+ * policy: there is no `mermaid` package.json dependency to keep in sync
9
+ * with this constant is the sole source of truth for the mermaid
10
+ * version this project ships. Cross-major jumps need an extra parity
11
+ * smoke (`pnpm build` and a manual diagram render) since esm.sh's
12
+ * exports drift between majors.
14
13
  *
15
14
  * Exported (rather than baked into the template) so consumers and
16
15
  * tests can override the URL — e.g. self-hosted mirrors, version-
package/dist/config.d.ts CHANGED
@@ -127,6 +127,15 @@ export interface ZudoDocConfig {
127
127
  * @default "src/content/docs"
128
128
  */
129
129
  docsDir?: string;
130
+ /**
131
+ * Route slug of the doc page the versions page links to as the "latest
132
+ * docs" entry point (and as each past version's docs link). Convention: no
133
+ * leading/trailing slashes, e.g. `"getting-started"` or
134
+ * `"overview/getting-started"`. Not validated at runtime — an invalid slug
135
+ * simply 404s.
136
+ * @default "getting-started"
137
+ */
138
+ entryDocSlug?: string;
130
139
  /**
131
140
  * Default locale code (unprefixed routes).
132
141
  * @default "en"
@@ -158,11 +167,6 @@ export interface ZudoDocConfig {
158
167
  * @default false
159
168
  */
160
169
  githubUrl?: string | false;
161
- /**
162
- * "owner/repo" — enables `#123` / SHA autolinks in markdown. Omit to disable.
163
- * @default undefined
164
- */
165
- githubAutolinksRepo?: string;
166
170
  /**
167
171
  * Canonical site origin (e.g. `"https://example.com"`) for sitemap /
168
172
  * canonical / og:url. Empty = not set.
@@ -286,6 +290,11 @@ export interface ZudoDocConfig {
286
290
  * @default false
287
291
  */
288
292
  sidebarToggle?: boolean;
293
+ /**
294
+ * Enable the desktop TOC collapse toggle.
295
+ * @default false
296
+ */
297
+ tocToggle?: boolean;
289
298
  /**
290
299
  * Enable click-to-enlarge for content images.
291
300
  * @default false
@@ -464,6 +473,14 @@ export interface ZudoDocConfig {
464
473
  * @default undefined
465
474
  */
466
475
  bundle?: BundleConfig;
476
+ /**
477
+ * Build-only gate that fails `zfb build` when a collection entry falls
478
+ * back to `<pre data-zfb-content-fallback>` (mirrors zfb's
479
+ * `Config::strict_content_bridge`, zfb 2.0.0). Omit to leave zfb's own
480
+ * default (`false`) in effect.
481
+ * @default undefined (zfb's own default, `false`, applies)
482
+ */
483
+ strictContentBridge?: boolean;
467
484
  }
468
485
  /**
469
486
  * Build a complete `ZfbConfig` from user config, defaulting every field.
package/dist/config.js CHANGED
@@ -3,6 +3,7 @@ import { buildDocsSchema as defaultBuildDocsSchema } from "./docs-schema/index.j
3
3
  import { defaultDirectiveVocabulary } from "./directive-vocabulary-defaults/index.js";
4
4
  import { defaultTranslations } from "./i18n-defaults/index.js";
5
5
  import { defaultColorSchemes } from "./color-schemes-defaults/index.js";
6
+ import { assertNoCommaInVersionSlugs } from "./version-availability/index.js";
6
7
  const DEFAULT_SETTINGS = {
7
8
  colorScheme: "Default Dark",
8
9
  colorMode: {
@@ -19,6 +20,7 @@ const DEFAULT_SETTINGS = {
19
20
  home: { wide: false },
20
21
  minifyHtml: true,
21
22
  docsDir: "src/content/docs",
23
+ entryDocSlug: "getting-started",
22
24
  defaultLocale: "en",
23
25
  locales: {},
24
26
  mermaid: true,
@@ -55,6 +57,7 @@ const DEFAULT_SETTINGS = {
55
57
  tocMaxDepth: 4,
56
58
  sidebarResizer: false,
57
59
  sidebarToggle: false,
60
+ tocToggle: false,
58
61
  imageEnlarge: false,
59
62
  findInPage: false,
60
63
  dynamicPageTransition: false,
@@ -80,10 +83,16 @@ function zudoDoc(user = {}) {
80
83
  "headingIdStrategy is no longer supported; heading IDs are always hierarchical"
81
84
  );
82
85
  }
86
+ if ("githubAutolinksRepo" in user) {
87
+ throw new TypeError(
88
+ "githubAutolinksRepo is no longer supported; the githubAutolinks markdown feature was removed"
89
+ );
90
+ }
83
91
  const {
84
92
  port,
85
93
  adapter,
86
94
  bundle,
95
+ strictContentBridge,
87
96
  buildDocsSchema: userBuildDocsSchema,
88
97
  colorSchemes: userColorSchemes,
89
98
  translations: userTranslations,
@@ -92,6 +101,7 @@ function zudoDoc(user = {}) {
92
101
  ...settingsOverrides
93
102
  } = user;
94
103
  const settings = { ...DEFAULT_SETTINGS, ...settingsOverrides };
104
+ assertNoCommaInVersionSlugs(settings.versions);
95
105
  const fragment = zudoDocPreset({
96
106
  settings,
97
107
  buildDocsSchema: userBuildDocsSchema ?? (() => defaultBuildDocsSchema({
@@ -111,6 +121,7 @@ function zudoDoc(user = {}) {
111
121
  base: settings.base,
112
122
  ...adapter ? { adapter } : {},
113
123
  ...bundle ? { bundle } : {},
124
+ ...strictContentBridge !== void 0 ? { strictContentBridge } : {},
114
125
  // ── Preset-owned fields (collections, plugins, markdown, …) ──────────
115
126
  ...fragment,
116
127
  // The preset's `markdown.features` intentionally uses the loose Record
package/dist/content.css CHANGED
@@ -187,6 +187,7 @@
187
187
  color: var(--color-code-fg);
188
188
  border-radius: var(--radius-DEFAULT);
189
189
  padding: 2px var(--spacing-hsp-xs);
190
+ overflow-wrap: anywhere;
190
191
  }
191
192
 
192
193
  /* ── Code blocks (pre) ── */
@@ -0,0 +1,7 @@
1
+ export declare const TOC_STORAGE_KEY = "zudo-doc-toc-visible";
2
+ export declare function readTocState(): boolean;
3
+ export declare function setTocDataAttribute(isVisible: boolean): void;
4
+ export declare function DesktopTocToggle(): import("preact").JSX.Element;
5
+ export declare namespace DesktopTocToggle {
6
+ var displayName: string;
7
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { jsx } from "preact/jsx-runtime";
3
+ import { useState, useEffect, useRef } from "preact/hooks";
4
+ import { ChevronRight, ChevronLeft } from "../icons/index.js";
5
+ import { AFTER_NAVIGATE_EVENT } from "../transitions/index.js";
6
+ const TOC_STORAGE_KEY = "zudo-doc-toc-visible";
7
+ function readTocState() {
8
+ if (typeof window === "undefined") return true;
9
+ try {
10
+ return localStorage.getItem(TOC_STORAGE_KEY) !== "false";
11
+ } catch {
12
+ return true;
13
+ }
14
+ }
15
+ function setTocDataAttribute(isVisible) {
16
+ if (isVisible) {
17
+ document.documentElement.removeAttribute("data-toc-hidden");
18
+ } else {
19
+ document.documentElement.setAttribute("data-toc-hidden", "");
20
+ }
21
+ }
22
+ function DesktopTocToggle() {
23
+ const [visible, setVisible] = useState(true);
24
+ const hydrated = useRef(false);
25
+ useEffect(() => {
26
+ if (!hydrated.current) return;
27
+ setTocDataAttribute(visible);
28
+ try {
29
+ localStorage.setItem(TOC_STORAGE_KEY, String(visible));
30
+ } catch {
31
+ }
32
+ }, [visible]);
33
+ useEffect(() => {
34
+ hydrated.current = true;
35
+ const actual = readTocState();
36
+ if (actual !== visible) {
37
+ setVisible(actual);
38
+ }
39
+ }, []);
40
+ useEffect(() => {
41
+ const handler = () => setTocDataAttribute(readTocState());
42
+ document.addEventListener(AFTER_NAVIGATE_EVENT, handler);
43
+ return () => document.removeEventListener(AFTER_NAVIGATE_EVENT, handler);
44
+ }, []);
45
+ return /* @__PURE__ */ jsx(
46
+ "button",
47
+ {
48
+ type: "button",
49
+ onClick: () => setVisible((v) => !v),
50
+ className: "zd-desktop-toc-toggle hidden xl:flex fixed bottom-vsp-xl z-sidebar items-center justify-center w-[1.5rem] h-[3rem] bg-surface border border-muted border-r-0 rounded-l-DEFAULT text-muted cursor-pointer transition-[right,color] duration-200 ease-in-out hover:text-fg",
51
+ "aria-label": visible ? "Hide table of contents" : "Show table of contents",
52
+ "aria-pressed": visible,
53
+ "data-zfb-transition-persist": "desktop-toc-toggle",
54
+ children: visible ? /* @__PURE__ */ jsx(ChevronRight, { className: "h-icon-sm w-icon-sm" }) : /* @__PURE__ */ jsx(ChevronLeft, { className: "h-icon-sm w-icon-sm" })
55
+ }
56
+ );
57
+ }
58
+ DesktopTocToggle.displayName = "DesktopTocToggle";
59
+ export {
60
+ DesktopTocToggle,
61
+ TOC_STORAGE_KEY,
62
+ readTocState,
63
+ setTocDataAttribute
64
+ };
@@ -47,8 +47,11 @@ export interface DocPageRendererDeps {
47
47
  /**
48
48
  * Build locale-aware MDX components bag.
49
49
  * Host passes `createMdxComponents` (from `pages/_mdx-components.ts`).
50
+ * `currentVersion` (#3218) threads the active `/v/{version}` slug into the
51
+ * nav-card wrappers (CategoryNav/CategoryTreeNav/SiteTreeNav) so their hrefs
52
+ * resolve within the version — undefined on latest/unversioned pages.
50
53
  */
51
- createMdxComponents: (locale: string) => Record<string, unknown>;
54
+ createMdxComponents: (locale: string, currentVersion?: string) => Record<string, unknown>;
52
55
  /** Translate a UI string key for a locale. */
53
56
  t: (key: string, locale: string) => string;
54
57
  /**
@@ -86,6 +89,14 @@ export interface DocPageRendererDeps {
86
89
  currentPath: string;
87
90
  currentVersion?: string;
88
91
  versionSwitcher: JSX.Element | undefined;
92
+ /**
93
+ * This page's unavailable-version slugs, straight from
94
+ * `getUnavailableVersions(slug, locale)` (`../version-availability`) —
95
+ * see `DocPageShellProps.unavailableVersions` (`../doc-page-shell`) for
96
+ * the full absent/empty/populated contract this rides through to the
97
+ * emitted `data-doc-unavailable-versions` attribute.
98
+ */
99
+ unavailableVersions?: ReadonlySet<string>;
89
100
  versionBanner?: "unmaintained" | "unreleased";
90
101
  versionBannerLatestUrl?: string;
91
102
  versionBannerLabels?: VersionBannerLabels;