@takazudo/zudo-doc 7.1.0 → 8.0.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,28 @@ The format is based on Keep a Changelog, and release notes are generated from th
8
8
 
9
9
  No unreleased changes.
10
10
 
11
+ ## [8.0.0] - 2026-10-24
12
+
13
+ ### Breaking Changes
14
+
15
+ - Remove the `@takazudo/zudo-doc/plugins/optional-peers` export. Remove any explicit registration of that workaround plugin; ZFB now handles rejection-handled optional imports directly.
16
+ - Require ZFB 5.0.1 or later in the 5.x line. Upgrade the ZFB engine, runtime, and Markdown WASM packages together. When using document history, upgrade `@takazudo/zudo-doc-history-server` to 8.x as well. Node.js 24 remains the minimum.
17
+
18
+ ### Features
19
+
20
+ - Add `siteTreeNavInitiallyCollapsed` to start exact nested category slugs collapsed in home-page and MDX site trees while preserving links and toggles. (fb4a6e3cf)
21
+
22
+ ### Bug Fixes
23
+
24
+ - Adopt ZFB 5.0.1 to fix lazy island chunks requesting an unpublished `islands.js` asset.
25
+ - Restore prose spacing inside tab panels. (5c2f52b92)
26
+ - Clarify adjacent heading spacing and heading hierarchy across theme packs. (2969bd803)
27
+
28
+ ### Other Changes
29
+
30
+ - Accept Node.js 24 or later without an upper version cap. Node 24 LTS remains the CI-verified line. (d5336a24b)
31
+ - Update the MCP SDK to 1.32.2.
32
+
11
33
  ## [7.1.0] - 2026-10-23
12
34
 
13
35
  ### Bug Fixes
package/README.md CHANGED
@@ -109,7 +109,7 @@ If your project renders **semi-trusted or user-submitted** HTML in a preview, ov
109
109
 
110
110
  **6.0.0** is the coordinated major release of `@takazudo/zudo-doc`,
111
111
  `@takazudo/zudo-doc-history-server` and `create-zudo-doc`. The current workspace
112
- targets the published **zfb 4.7.0** family (peer floor **^4.7.0**).
112
+ targets the published **zfb 5.0.1** family (peer floor **^5.0.1**).
113
113
  [Upstream #4097](https://github.com/Takazudo/zudo-front-builder/issues/4097) is
114
114
  resolved; its strict published 4.3.1 consumer control passed 3/3 with no skips or
115
115
  retries in the [source-bound verification](../../docs/findings/zfb-4.3.1/verification.md).
@@ -322,6 +322,7 @@ function deriveMdxComponents(ctx) {
322
322
  getCategoryOrder: ctx.getCategoryOrder,
323
323
  versionedDocsUrl: ctx.versionedDocsUrl,
324
324
  categoryIgnore: ctx.settings.siteTreeNavIgnore,
325
+ initiallyCollapsedCategorySlugs: ctx.settings.siteTreeNavInitiallyCollapsed,
325
326
  dateFormat: ctx.settings.dateFormat
326
327
  });
327
328
  const NoteTrayIndexWrapper = createNoteTrayIndexWrapper({
package/dist/compiled.css CHANGED
@@ -630,11 +630,15 @@ body {
630
630
  }
631
631
 
632
632
  @layer zd-flow {
633
- .zd-content > :where(* + *) {
633
+ .zd-content > :where(* + *), .zd-content :where(.tab-panel) > :where(* + *) {
634
634
  margin-top: var(--flow-space, var(--spacing-vsp-md));
635
635
  }
636
636
  }
637
637
 
638
+ .zd-content :where(.tab-panel) {
639
+ --flow-space: var(--spacing-vsp-md);
640
+ }
641
+
638
642
  .zd-content :where(h2) {
639
643
  --flow-space: var(--spacing-vsp-2xl);
640
644
  }
@@ -658,11 +662,15 @@ body {
658
662
  --flow-space: var(--spacing-vsp-xs);
659
663
  }
660
664
 
665
+ .zd-content :where(h2) + :where(h3) {
666
+ --flow-space: var(--spacing-vsp-md);
667
+ }
668
+
661
669
  .zd-content :where(h2, h3, h4, h5, h6) + :where(:not(h2, h3, h4, h5, h6)) {
662
670
  --flow-space: var(--spacing-vsp-xs);
663
671
  }
664
672
 
665
- .zd-content > :first-child {
673
+ .zd-content > :first-child, .zd-content :where(.tab-panel) > :first-child {
666
674
  margin-top: 0;
667
675
  }
668
676
 
package/dist/config.d.ts CHANGED
@@ -158,6 +158,15 @@ export interface ZudoDocConfig {
158
158
  * @default []
159
159
  */
160
160
  siteTreeNavIgnore?: string[];
161
+ /**
162
+ * Exact full `node.slug` values of categories that start collapsed at any
163
+ * depth in package-owned home pages and `<SiteTreeNav />` / `<SiteTreeNavDemo />`
164
+ * MDX tags. Use slugs such as `"zudo-wind/utilities"`, without `/docs`, a
165
+ * locale prefix, or leading/trailing slashes. Visitors can toggle them normally.
166
+ * Does not affect sidebar expansion.
167
+ * @default []
168
+ */
169
+ siteTreeNavInitiallyCollapsed?: string[];
161
170
  /**
162
171
  * Top-level category slugs moved OUT of the package-owned home-page category
163
172
  * grid on `/` and locale homes and rendered instead as one row of small
package/dist/content.css CHANGED
@@ -74,11 +74,17 @@
74
74
  * layer-independent, so the per-heading flow gaps are unaffected.
75
75
  * See zudolab/zudo-doc#2082 / #2109. */
76
76
  @layer zd-flow {
77
- .zd-content > :where(* + *) {
77
+ .zd-content > :where(* + *),
78
+ .zd-content :where(.tab-panel) > :where(* + *) {
78
79
  margin-top: var(--flow-space, var(--spacing-vsp-md));
79
80
  }
80
81
  }
81
82
 
83
+ /* Tabs can inherit a heading-following gap; start a new prose flow inside. */
84
+ .zd-content :where(.tab-panel) {
85
+ --flow-space: var(--spacing-vsp-md);
86
+ }
87
+
82
88
  /* ── Headings ── */
83
89
 
84
90
  /* Heading flow-space (structural — consumed by flow spacing system) */
@@ -107,13 +113,18 @@
107
113
  --flow-space: var(--spacing-vsp-xs);
108
114
  }
109
115
 
116
+ .zd-content :where(h2) + :where(h3) {
117
+ --flow-space: var(--spacing-vsp-md);
118
+ }
119
+
110
120
  /* Content immediately after a heading: tight gap (replaces heading margin-bottom) */
111
121
  .zd-content :where(h2, h3, h4, h5, h6) + :where(:not(h2, h3, h4, h5, h6)) {
112
122
  --flow-space: var(--spacing-vsp-xs);
113
123
  }
114
124
 
115
125
  /* First child never needs top margin */
116
- .zd-content > :first-child {
126
+ .zd-content > :first-child,
127
+ .zd-content :where(.tab-panel) > :first-child {
117
128
  margin-top: 0;
118
129
  }
119
130
 
@@ -43,7 +43,7 @@ export interface HomePageViewProps {
43
43
  tree: DocNavNode[];
44
44
  /** Ordered category prefixes, passed through to `SiteTreeNav`. */
45
45
  categoryOrder: string[];
46
- /** Root-category slugs that should start collapsed in `SiteTreeNav`. */
46
+ /** Exact full category slugs at any depth that should start collapsed in `SiteTreeNav`. Overrides `settings.siteTreeNavInitiallyCollapsed`, including when `[]`. */
47
47
  initiallyCollapsedCategorySlugs?: string[];
48
48
  /** Unique tag count for the current locale — gates the "Tags" section
49
49
  * together with `settings.docTags`. Part of the documented public
@@ -87,6 +87,7 @@ function createHomePageView(ctx) {
87
87
  wide
88
88
  }) {
89
89
  const prefix = locale === defaultLocale ? "" : `/${locale}`;
90
+ const effectiveInitiallyCollapsedCategorySlugs = initiallyCollapsedCategorySlugs ?? settings.siteTreeNavInitiallyCollapsed;
90
91
  const rawDescription = settings.locales[locale]?.description ?? settings.siteDescription;
91
92
  const description = rawDescription?.trim() ? rawDescription : void 0;
92
93
  const ctaNav = settings.headerNav[0] ?? null;
@@ -193,7 +194,7 @@ function createHomePageView(ctx) {
193
194
  tree,
194
195
  categoryOrder,
195
196
  categoryIgnore: [...categoryIgnore, ...movedNodes.map((n) => n.slug)],
196
- ...initiallyCollapsedCategorySlugs !== void 0 ? { initiallyCollapsedCategorySlugs } : {},
197
+ ...effectiveInitiallyCollapsedCategorySlugs !== void 0 ? { initiallyCollapsedCategorySlugs: effectiveInitiallyCollapsedCategorySlugs } : {},
197
198
  locale,
198
199
  updatedLabel: t("doc.updated", locale),
199
200
  dateFormats: dateFormatsFor(locale)
package/dist/preset.js CHANGED
@@ -130,16 +130,6 @@ function buildMarkdownFeatures(settings, directiveVocabulary) {
130
130
  headingIds: { strategy: "hierarchical" }
131
131
  };
132
132
  }
133
- function optionalPeerShadows(settings) {
134
- const peers = [
135
- ["katex", Boolean(settings.math)],
136
- ["diff", Boolean(settings.docHistory)]
137
- ];
138
- return {
139
- shadow: peers.filter(([, on]) => !on).map(([name]) => name),
140
- shadowIfMissing: peers.filter(([, on]) => on).map(([name]) => name)
141
- };
142
- }
143
133
  function buildPlugins(settings, routeContext) {
144
134
  const localeArray = Object.entries(settings.locales).map(([code, locale]) => ({
145
135
  code,
@@ -347,10 +337,7 @@ function buildPlugins(settings, routeContext) {
347
337
  // `design-token-panel-bootstrap` (#4261). Preset-level rather than inside
348
338
  // the routes plugin because the bootstrap is reachable from chrome even
349
339
  // when packageOwnedRoutes is off.
350
- ...resolvesBundleZdtp(settings) ? [] : [{ name: "@takazudo/zudo-doc/plugins/zdtp-loader", options: {} }],
351
- // Absent or disabled katex/diff → throwing shadows, so they cannot fail a
352
- // Rolldown build (Takazudo/zudo-front-builder#4429).
353
- { name: "@takazudo/zudo-doc/plugins/optional-peers", options: optionalPeerShadows(settings) }
340
+ ...resolvesBundleZdtp(settings) ? [] : [{ name: "@takazudo/zudo-doc/plugins/zdtp-loader", options: {} }]
354
341
  ];
355
342
  }
356
343
  export {
@@ -14,6 +14,7 @@ const DEFAULT_SETTINGS = {
14
14
  trailingSlash: false,
15
15
  home: { wide: false, introMarkdown: "", sitemapHeading: "" },
16
16
  siteTreeNavIgnore: [],
17
+ siteTreeNavInitiallyCollapsed: [],
17
18
  siteTreeNavSecondary: [],
18
19
  minifyHtml: true,
19
20
  docsDir: "src/content/docs",
@@ -347,6 +347,8 @@ export interface Settings {
347
347
  home?: HomeConfig;
348
348
  /** Top-level category slugs hidden from the package home-page grid and the `<SiteTreeNav />` MDX tag. Nothing hidden when omitted. */
349
349
  siteTreeNavIgnore?: string[];
350
+ /** Exact full category slugs to start collapsed at any depth in home and MDX SiteTreeNav. Defaults to `[]`. */
351
+ siteTreeNavInitiallyCollapsed?: string[];
350
352
  /** Top-level category slugs moved from the package home-page grid into the secondary link row under it. Nothing moved when omitted. */
351
353
  siteTreeNavSecondary?: string[];
352
354
  /** Minify production HTML output from `zfb build`. Defaults to `true` when omitted. */
@@ -82,6 +82,8 @@ export interface SiteTreeNavDeps {
82
82
  * package chrome passes `settings.siteTreeNavIgnore`.
83
83
  */
84
84
  categoryIgnore?: string[];
85
+ /** Exact full category slugs to start collapsed at any depth. Optional for existing factory callers. */
86
+ initiallyCollapsedCategorySlugs?: string[];
85
87
  /**
86
88
  * The raw `dateFormat` setting (`ctx.settings.dateFormat`). Resolved to
87
89
  * per-role patterns for the render locale and forwarded to the island as
@@ -13,6 +13,7 @@ function createSiteTreeNavWrapper(deps) {
13
13
  getCategoryOrder,
14
14
  versionedDocsUrl,
15
15
  categoryIgnore,
16
+ initiallyCollapsedCategorySlugs,
16
17
  dateFormat
17
18
  } = deps;
18
19
  function SiteTreeNavWrapper({
@@ -43,6 +44,7 @@ function createSiteTreeNavWrapper(deps) {
43
44
  tree,
44
45
  categoryOrder,
45
46
  ...categoryIgnore !== void 0 ? { categoryIgnore } : {},
47
+ ...initiallyCollapsedCategorySlugs !== void 0 ? { initiallyCollapsedCategorySlugs } : {},
46
48
  ...ariaLabel !== void 0 ? { ariaLabel } : {},
47
49
  locale,
48
50
  dateFormats: resolveDateFormats(dateFormat, locale)
@@ -4,6 +4,7 @@ export interface SiteTreeNavIslandProps {
4
4
  tree: SidebarNavNode[];
5
5
  categoryOrder: string[];
6
6
  categoryIgnore?: string[];
7
+ initiallyCollapsedCategorySlugs?: string[];
7
8
  ariaLabel?: string;
8
9
  locale: string;
9
10
  dateFormats: ResolvedDateFormats;
@@ -12,6 +12,12 @@ function normalizeSiteTreeNavProps(input) {
12
12
  "categoryIgnore"
13
13
  );
14
14
  }
15
+ if (input.initiallyCollapsedCategorySlugs !== void 0) {
16
+ normalized.initiallyCollapsedCategorySlugs = normalizeStringArray(
17
+ input.initiallyCollapsedCategorySlugs,
18
+ "initiallyCollapsedCategorySlugs"
19
+ );
20
+ }
15
21
  if (input.ariaLabel !== void 0) {
16
22
  normalized.ariaLabel = requireString(input.ariaLabel, "ariaLabel");
17
23
  }
@@ -5,7 +5,7 @@ export interface SiteTreeNavProps {
5
5
  ariaLabel?: string;
6
6
  categoryOrder?: string[];
7
7
  categoryIgnore?: string[];
8
- /** Root-category slugs that should start collapsed. */
8
+ /** Exact full category slugs at any depth that should start collapsed. */
9
9
  initiallyCollapsedCategorySlugs?: string[];
10
10
  /** Locale used by dated note-tray rows. */
11
11
  locale?: string;
@@ -91,7 +91,7 @@ function SiteTreeNav({
91
91
  node,
92
92
  depth: 0,
93
93
  isLast,
94
- initiallyCollapsed: initiallyCollapsed.has(node.value.slug),
94
+ initiallyCollapsedCategorySlugs: initiallyCollapsed,
95
95
  locale,
96
96
  dateFormats,
97
97
  updatedLabel
@@ -108,7 +108,8 @@ function SiteTreeNav({
108
108
  SiteTreeNav.displayName = "SiteTreeNav";
109
109
  function NodeList({
110
110
  nodes,
111
- depth
111
+ depth,
112
+ initiallyCollapsedCategorySlugs
112
113
  }) {
113
114
  return /* @__PURE__ */ jsx(For, { each: nodes, by: (node) => node.slug, children: (node, index) => {
114
115
  const isLast = computed(() => index.value === nodes.value.length - 1);
@@ -117,7 +118,7 @@ function NodeList({
117
118
  {
118
119
  when: computed(() => node.value.children.length > 0),
119
120
  fallback: () => /* @__PURE__ */ jsx(LeafNode, { node, depth, isLast }),
120
- children: () => /* @__PURE__ */ jsx(CategoryNode, { node, depth, isLast })
121
+ children: () => /* @__PURE__ */ jsx(CategoryNode, { node, depth, isLast, initiallyCollapsedCategorySlugs })
121
122
  }
122
123
  );
123
124
  } });
@@ -126,12 +127,12 @@ function CategoryNode({
126
127
  node,
127
128
  depth,
128
129
  isLast,
129
- initiallyCollapsed = false,
130
+ initiallyCollapsedCategorySlugs,
130
131
  locale = "en",
131
132
  dateFormats,
132
133
  updatedLabel = "Updated"
133
134
  }) {
134
- const open = signal(initialCategoryOpenState(initiallyCollapsed));
135
+ const open = signal(initialCategoryOpenState(initiallyCollapsedCategorySlugs.has(node.value.slug)));
135
136
  const toggle = () => {
136
137
  open.value = toggleCategoryOpenState(open.value);
137
138
  };
@@ -234,7 +235,7 @@ function CategoryNode({
234
235
  when: computed(
235
236
  () => node.value.shape === "note-tray" && depth === 0
236
237
  ),
237
- fallback: () => /* @__PURE__ */ jsx(NodeList, { nodes: children, depth: depth + 1 }),
238
+ fallback: () => /* @__PURE__ */ jsx(NodeList, { nodes: children, depth: depth + 1, initiallyCollapsedCategorySlugs }),
238
239
  children: () => /* @__PURE__ */ jsx(
239
240
  NoteTrayNodeList,
240
241
  {
@@ -314,6 +314,9 @@ html[data-theme-pack="drift"] a[data-nav-active] {
314
314
  html[data-theme-pack="drift"] .zd-content h1 {
315
315
  text-shadow: 0 0 34px color-mix(in oklch, var(--zd-accent) 32%, transparent);
316
316
  }
317
+ html[data-theme-pack="drift"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
318
+ font-size: 1.6rem;
319
+ }
317
320
  html[data-theme-pack="drift"] .zd-content h2 {
318
321
  border-top-width: 1px;
319
322
  border-image: linear-gradient(
@@ -323,6 +326,9 @@ html[data-theme-pack="drift"] .zd-content h2 {
323
326
  )
324
327
  1 !important;
325
328
  }
329
+ html[data-theme-pack="drift"] .zd-content h3:where(:not(.zd-compact-prose *)) {
330
+ border-top-width: 1px;
331
+ }
326
332
 
327
333
  /* links: airy underline that brightens on approach */
328
334
  html[data-theme-pack="drift"] .zd-content a {
@@ -107,11 +107,17 @@ html[data-theme-pack="foundry"] .zd-content h1 {
107
107
  padding-bottom: 0.55rem;
108
108
  border-bottom: 1px solid color-mix(in srgb, var(--zd-muted) 42%, transparent);
109
109
  }
110
+ html[data-theme-pack="foundry"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
111
+ font-size: 1.6rem;
112
+ }
110
113
  html[data-theme-pack="foundry"] .zd-content h2 {
111
114
  border-image: none !important;
112
115
  border-top-width: 1px;
113
116
  border-top-color: color-mix(in srgb, var(--zd-muted) 42%, transparent);
114
117
  }
118
+ html[data-theme-pack="foundry"] .zd-content h3:where(:not(.zd-compact-prose *)) {
119
+ border-top-width: 1px;
120
+ }
115
121
 
116
122
  /* inline code: neutral wash that holds up in both modes */
117
123
  html[data-theme-pack="foundry"] .zd-content code:not(pre code) {
@@ -216,11 +216,17 @@ html[data-theme-pack="futura-editorial"] .zd-content > p[data-doc-description] {
216
216
  fade (signature move #2). The h2 rule needs !important to defeat the
217
217
  SSR-inlined `style="border-image:linear-gradient(...)"` (Decision
218
218
  6.6's sole allowlisted carve-out). */
219
+ html[data-theme-pack="futura-editorial"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
220
+ font-size: 1.6rem;
221
+ }
219
222
  html[data-theme-pack="futura-editorial"] .zd-content h2 {
220
223
  border-image: none !important;
221
224
  border-top-width: 1px;
222
225
  border-top-color: var(--zd-fg);
223
226
  }
227
+ html[data-theme-pack="futura-editorial"] .zd-content h3:where(:not(.zd-compact-prose *)) {
228
+ border-top-width: 1px;
229
+ }
224
230
  html[data-theme-pack="futura-editorial"] .zd-content h3 {
225
231
  font-family: var(--font-display);
226
232
  letter-spacing: 0.01em;
@@ -264,6 +264,9 @@ html[data-theme-pack="hollow"] .zd-content h1 {
264
264
  /* h2: the SSR-inlined gradient bar is re-pointed at a fading pink
265
265
  rule (Decision 6.6's sole allowlisted !important carve-out —
266
266
  border-image only) */
267
+ html[data-theme-pack="hollow"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
268
+ font-size: 1.5rem;
269
+ }
267
270
  html[data-theme-pack="hollow"] .zd-content h2 {
268
271
  border-image: linear-gradient(
269
272
  to right,
@@ -281,6 +281,9 @@ html[data-theme-pack="ledger"] .zd-content h2 {
281
281
  font-style: italic;
282
282
  font-size: 1.6rem;
283
283
  }
284
+ html[data-theme-pack="ledger"] .zd-content h3:where(:not(.zd-compact-prose *)) {
285
+ border-top-width: 1px;
286
+ }
284
287
  html[data-theme-pack="ledger"] .zd-content h3 {
285
288
  font-style: italic;
286
289
  font-size: 1.3rem;
@@ -229,6 +229,9 @@ html[data-theme-pack="matcha"] .zd-content h2 {
229
229
  }
230
230
 
231
231
  /* h3: quiet, slightly tracked (weight 600 rides --zdc-doc-h3-weight) */
232
+ html[data-theme-pack="matcha"] .zd-content h3:where(:not(.zd-compact-prose *)) {
233
+ border-top-width: 1px;
234
+ }
232
235
  html[data-theme-pack="matcha"] .zd-content h3 {
233
236
  letter-spacing: 0.01em;
234
237
  }
@@ -422,6 +422,9 @@ html[data-theme-pack="observatory"] .zd-content > p[data-doc-description] {
422
422
  allowlisted carve-out) + a zeroed border-top; the node-to-line rule
423
423
  is then painted as a top-anchored background gradient, with the two
424
424
  glowing star nodes as pseudo-element dots. */
425
+ html[data-theme-pack="observatory"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
426
+ font-size: 1.75rem;
427
+ }
425
428
  html[data-theme-pack="observatory"] .zd-content h2 {
426
429
  border-image: none !important;
427
430
  border-top-width: 0;
@@ -284,6 +284,9 @@ html[data-theme-pack="sakura"] a[data-nav-active] {
284
284
  into drifting transparency. Re-points the SSR-inlined
285
285
  `style="border-image:linear-gradient(...)"` gradient — the
286
286
  border-image !important carve-out (Decision 6.6). */
287
+ html[data-theme-pack="sakura"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
288
+ font-size: 1.6rem;
289
+ }
287
290
  html[data-theme-pack="sakura"] .zd-content h2 {
288
291
  border-top-width: 2px;
289
292
  border-image: linear-gradient(
@@ -225,6 +225,9 @@ html[data-theme-pack="scandi"] .zd-content h2 {
225
225
  border-top-width: 1px;
226
226
  border-top-color: color-mix(in srgb, var(--zd-accent) 38%, transparent);
227
227
  }
228
+ html[data-theme-pack="scandi"] .zd-content h3:where(:not(.zd-compact-prose *)) {
229
+ border-top-width: 1px;
230
+ }
228
231
 
229
232
  /* links: sage ink with a soft sage underline that firms on hover */
230
233
  html[data-theme-pack="scandi"] .zd-content a {
@@ -215,6 +215,9 @@ html[data-theme-pack="solar"] main nav[aria-label="Breadcrumb"] li > span {
215
215
  bar is defeated with border-image none !important (Decision 6.6's
216
216
  sole allowlisted carve-out) + a zeroed border-top; the fade is then
217
217
  painted as a 2px top-anchored background gradient. */
218
+ html[data-theme-pack="solar"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
219
+ font-size: 1.5rem;
220
+ }
218
221
  html[data-theme-pack="solar"] .zd-content h2 {
219
222
  border-image: none !important;
220
223
  border-top-width: 0;
@@ -219,6 +219,9 @@ html[data-theme-pack="tidepool"] a[data-nav-active] {
219
219
  html[data-theme-pack="tidepool"] .zd-content h1 {
220
220
  color: color-mix(in srgb, var(--zd-accent) 12%, var(--zd-fg));
221
221
  }
222
+ html[data-theme-pack="tidepool"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
223
+ font-size: 1.5rem;
224
+ }
222
225
  html[data-theme-pack="tidepool"] .zd-content h2 {
223
226
  border-image: none !important;
224
227
  border-top-width: 0;
@@ -314,6 +314,9 @@ html[data-theme-pack="washi"] .zd-content > p[data-doc-description] {
314
314
  SSR-inlined `style="border-image:linear-gradient(...)"` — the
315
315
  border-image !important carve-out (Decision 6.6), here re-pointing
316
316
  the gradient rather than clearing it. */
317
+ html[data-theme-pack="washi"] .zd-content h2:where(:not(.zd-home-heading)):where(:not(.zd-compact-prose *)) {
318
+ font-size: 1.6rem;
319
+ }
317
320
  html[data-theme-pack="washi"] .zd-content h2 {
318
321
  border-top-width: 2px;
319
322
  border-image: linear-gradient(
@@ -57,7 +57,7 @@ export interface SiteTreeNavProps {
57
57
  ariaLabel?: string;
58
58
  categoryOrder?: string[];
59
59
  categoryIgnore?: string[];
60
- /** Root-category slugs that should start collapsed. */
60
+ /** Exact full category slugs at any depth that should start collapsed. */
61
61
  initiallyCollapsedCategorySlugs?: string[];
62
62
  /** Locale used by dated note-tray rows. */
63
63
  locale?: string;
@@ -129,7 +129,7 @@ export function SiteTreeNav({
129
129
  node={node}
130
130
  depth={0}
131
131
  isLast={isLast}
132
- initiallyCollapsed={initiallyCollapsed.has(node.value.slug)}
132
+ initiallyCollapsedCategorySlugs={initiallyCollapsed}
133
133
  locale={locale}
134
134
  dateFormats={dateFormats}
135
135
  updatedLabel={updatedLabel}
@@ -150,9 +150,11 @@ SiteTreeNav.displayName = "SiteTreeNav";
150
150
  function NodeList({
151
151
  nodes,
152
152
  depth,
153
+ initiallyCollapsedCategorySlugs,
153
154
  }: {
154
155
  nodes: ReadonlySignal<readonly SidebarNavNode[]>;
155
156
  depth: number;
157
+ initiallyCollapsedCategorySlugs: ReadonlySet<string>;
156
158
  }) {
157
159
  return (
158
160
  <For each={nodes} by={(node) => node.slug}>
@@ -166,7 +168,7 @@ function NodeList({
166
168
  )}
167
169
  >
168
170
  {() => (
169
- <CategoryNode node={node} depth={depth} isLast={isLast} />
171
+ <CategoryNode node={node} depth={depth} isLast={isLast} initiallyCollapsedCategorySlugs={initiallyCollapsedCategorySlugs} />
170
172
  )}
171
173
  </Show>
172
174
  );
@@ -179,7 +181,7 @@ function CategoryNode({
179
181
  node,
180
182
  depth,
181
183
  isLast,
182
- initiallyCollapsed = false,
184
+ initiallyCollapsedCategorySlugs,
183
185
  locale = "en",
184
186
  dateFormats,
185
187
  updatedLabel = "Updated",
@@ -187,12 +189,12 @@ function CategoryNode({
187
189
  node: ReadonlySignal<SidebarNavNode>;
188
190
  depth: number;
189
191
  isLast: ReadonlySignal<boolean>;
190
- initiallyCollapsed?: boolean;
192
+ initiallyCollapsedCategorySlugs: ReadonlySet<string>;
191
193
  locale?: string;
192
194
  dateFormats?: ResolvedDateFormats;
193
195
  updatedLabel?: string;
194
196
  }) {
195
- const open = signal(initialCategoryOpenState(initiallyCollapsed));
197
+ const open = signal(initialCategoryOpenState(initiallyCollapsedCategorySlugs.has(node.value.slug)));
196
198
  const toggle = () => {
197
199
  open.value = toggleCategoryOpenState(open.value);
198
200
  };
@@ -294,7 +296,7 @@ function CategoryNode({
294
296
  () => node.value.shape === "note-tray" && depth === 0,
295
297
  )}
296
298
  fallback={() => (
297
- <NodeList nodes={children} depth={depth + 1} />
299
+ <NodeList nodes={children} depth={depth + 1} initiallyCollapsedCategorySlugs={initiallyCollapsedCategorySlugs} />
298
300
  )}
299
301
  >
300
302
  {() => (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takazudo/zudo-doc",
3
- "version": "7.1.0",
3
+ "version": "8.0.0",
4
4
  "type": "module",
5
5
  "description": "zudo-doc framework primitives layer that sits on top of zfb's engine — sidebar, theme, TOC, breadcrumb, layouts, head injection, View Transitions, SSR-skip wrappers (per ADR-003).",
6
6
  "license": "MIT",
@@ -15,7 +15,7 @@
15
15
  "url": "https://github.com/zudolab/zudo-doc/issues"
16
16
  },
17
17
  "engines": {
18
- "node": ">=24 <25"
18
+ "node": ">=24"
19
19
  },
20
20
  "keywords": [
21
21
  "zudo-doc",
@@ -206,10 +206,6 @@
206
206
  "types": "./dist/plugins/zdtp-loader.d.ts",
207
207
  "default": "./dist/plugins/zdtp-loader.js"
208
208
  },
209
- "./plugins/optional-peers": {
210
- "types": "./dist/plugins/optional-peers.d.ts",
211
- "default": "./dist/plugins/optional-peers.js"
212
- },
213
209
  "./content-admonition": {
214
210
  "types": "./dist/content-admonition/index.d.ts",
215
211
  "default": "./dist/content-admonition/index.js"
@@ -738,12 +734,12 @@
738
734
  "CHANGELOG.md"
739
735
  ],
740
736
  "peerDependencies": {
741
- "@modelcontextprotocol/sdk": "1.32.1",
737
+ "@modelcontextprotocol/sdk": "1.32.2",
742
738
  "@takazudo/zdtp": "^0.5.2 || ^0.6.0 || ^0.7.0 || ^0.8.0",
743
- "@takazudo/zfb": "^4.7.0",
744
- "@takazudo/zfb-md-wasm": "^4.7.0",
745
- "@takazudo/zfb-runtime": "^4.7.0",
746
- "@takazudo/zudo-doc-history-server": "^7.0.0",
739
+ "@takazudo/zfb": "^5.0.1",
740
+ "@takazudo/zfb-md-wasm": "^5.0.1",
741
+ "@takazudo/zfb-runtime": "^5.0.1",
742
+ "@takazudo/zudo-doc-history-server": "^8.0.0",
747
743
  "diff": "^8.0.0",
748
744
  "katex": "^0.18.2 || ^0.19.0",
749
745
  "zod": "^4.3.6"
@@ -781,11 +777,11 @@
781
777
  "yaml": "^2.9.0"
782
778
  },
783
779
  "devDependencies": {
784
- "@modelcontextprotocol/sdk": "1.32.1",
780
+ "@modelcontextprotocol/sdk": "1.32.2",
785
781
  "@takazudo/mdx-formatter": "1.3.0",
786
- "@takazudo/zfb": "4.7.0",
787
- "@takazudo/zfb-md-wasm": "4.7.0",
788
- "@takazudo/zfb-runtime": "4.7.0",
782
+ "@takazudo/zfb": "5.0.1",
783
+ "@takazudo/zfb-md-wasm": "5.0.1",
784
+ "@takazudo/zfb-runtime": "5.0.1",
789
785
  "@types/fs-extra": "^11.0.4",
790
786
  "@types/minimist": "^1.2.5",
791
787
  "@types/node": "^25.3.5",
@@ -795,7 +791,7 @@
795
791
  "typescript": "^5.0.0",
796
792
  "vitest": "^4.1.0",
797
793
  "zod": "^4.3.6",
798
- "@takazudo/zudo-doc-history-server": "7.1.0"
794
+ "@takazudo/zudo-doc-history-server": "8.0.0"
799
795
  },
800
796
  "scripts": {
801
797
  "gen:search-widget-script": "node scripts/gen-search-widget-script.mjs",
@@ -1,3 +0,0 @@
1
- import type { ZfbPlugin } from "@takazudo/zfb/plugins";
2
- declare const plugin: ZfbPlugin;
3
- export default plugin;
@@ -1,34 +0,0 @@
1
- import { createRequire } from "node:module";
2
- function shadowSource(specifier) {
3
- const message = `Optional peer "${specifier}" is shadowed: it is not installed or the zudo-doc feature that uses it is off.`;
4
- return `throw new Error(${JSON.stringify(message)});
5
- export {};
6
- `;
7
- }
8
- function stringList(value) {
9
- return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
10
- }
11
- function isResolvable(specifier) {
12
- try {
13
- createRequire(import.meta.url).resolve(specifier);
14
- return true;
15
- } catch {
16
- return false;
17
- }
18
- }
19
- const plugin = {
20
- name: "optional-peers",
21
- setup(ctx) {
22
- const shadow = [
23
- ...stringList(ctx.options["shadow"]),
24
- ...stringList(ctx.options["shadowIfMissing"]).filter((s) => !isResolvable(s))
25
- ];
26
- for (const specifier of shadow) {
27
- ctx.addVirtualModule(specifier, () => shadowSource(specifier));
28
- }
29
- }
30
- };
31
- var optional_peers_default = plugin;
32
- export {
33
- optional_peers_default as default
34
- };