@takazudo/zudo-doc 8.1.0 → 8.1.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,30 @@ 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.1.1] - 2026-10-26
12
+
13
+ ### Bug Fixes
14
+
15
+ - Search-index bodies, llms.txt descriptions and agent-export fallback descriptions now read as plain prose.
16
+ - Inline code keeps its text and only the backticks are dropped. Before, `` `pnpm approve-builds` `` was deleted, leaving fragments like "by default ()".
17
+ - Whole-line directive fences are removed and their label or title text is kept. This covers `:::warning[Title]`, `:::note Title`, `:::name{attrs}`, nested `::::` and bare closing `:::`. (9d0bffb5, 64cae6a6)
18
+ - Sites that don't use math and don't install the optional `katex` peer no longer get a Rolldown `UNRESOLVED_IMPORT 'katex'` warning on every build.
19
+ - math-block now loads KaTeX through the package-owned `@takazudo/zudo-doc/katex-loader` subpath. With `math` off, the preset replaces that subpath with a stub.
20
+ - Bare `katex` imports in your own code are untouched.
21
+ - `` now requires `math: true`, as already documented. With `math` off it reports the missing-katex error even when `katex` happens to be installed. (82c24ea5, 64cae6a6)
22
+ - Sites with `docHistory` off that don't install the optional `diff` peer no longer get an `UNRESOLVED_IMPORT 'diff'` warning.
23
+ - The doc-history Compare view loads `diff` through the package-owned `@takazudo/zudo-doc/diff-loader` subpath. With `docHistory` off, the preset replaces that subpath with a stub.
24
+ - The dates-only mode (`docHistoryUi: false`) keeps the real loader. (6e6434ca)
25
+ - Some row descendants now follow their own row's hover and focus-visible accent, while keeping their resting colors and the inverted current-item style. This covers:
26
+ - numbered index ranks
27
+ - linked note-tray dates (index, compact and wide card stamps)
28
+ - sidebar tray and site-tree note-tray ranks and dates
29
+ - asset-tree folder and file icons, names and metadata (4c271316, 0efa5cbf)
30
+
31
+ ### Other Changes
32
+
33
+ - Requires zfb 5.1.3: the `@takazudo/zfb`, `zfb-runtime` and `zfb-md-wasm` peer floors move from `^5.1.2` to `^5.1.3`. zfb 5.1.3 rebuilds the native engine and WASM binaries; the JavaScript API is unchanged. Update the zfb family (and `zfb-adapter-cloudflare` when used) to 5.1.3 together with this release. (62648582)
34
+
11
35
  ## [8.1.0] - 2026-10-25
12
36
 
13
37
  ### Features
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 5.1.2** family (peer floor **^5.1.2**).
112
+ targets the published **zfb 5.1.3** family (peer floor **^5.1.3**).
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).
@@ -126,7 +126,7 @@ are feature-dependent. Package defaults for `agentExport`, `mcp`, and `llmsTxt`
126
126
  are all `false`. The create-zudo-doc initializer enables `llmsTxt` by default;
127
127
  agent export and MCP remain opt-in.
128
128
 
129
- For the panel, the installed **zdtp 0.8.6** manifest carries **Preact ^10.29.1** as
129
+ For the panel, the installed **zdtp 0.8.7** manifest carries **Preact ^10.29.1** as
130
130
  its own dependency (it was a peer through 0.8.5) and exports `./styles.css` (also `./styles`) to `dist/zdtp.css`. Its internal
131
131
  Tailwind browser dependency and Preact subtree are isolated from host JSX.
132
132
  Install `@takazudo/zdtp` and import `@takazudo/zdtp/styles.css` when enabling it.
@@ -48,15 +48,15 @@ function iconFor(asset) {
48
48
  function AssetTree({ node, base, routePrefix, locale, fileCountLabel, fileCountSingleLabel, linesLabel, root = false }) {
49
49
  return /* @__PURE__ */ jsxs("ul", { "data-zd-asset-tree": root ? true : void 0, children: [
50
50
  node.dirs.map((dir) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("details", { open: true, children: [
51
- /* @__PURE__ */ jsxs("summary", { class: "flex cursor-pointer items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small hover:bg-accent/10 focus-visible:bg-accent/10", children: [
52
- /* @__PURE__ */ jsx(ChevronRight, { class: "h-icon-xs w-icon-xs shrink-0 text-muted transition-transform" }),
53
- /* @__PURE__ */ jsx(Folder, { class: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
54
- /* @__PURE__ */ jsx(FolderOpen, { class: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
55
- /* @__PURE__ */ jsxs("span", { class: "min-w-0 truncate font-mono text-fg", children: [
51
+ /* @__PURE__ */ jsxs("summary", { class: "group flex cursor-pointer items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small hover:bg-accent/10 focus-visible:bg-accent/10", children: [
52
+ /* @__PURE__ */ jsx(ChevronRight, { class: "h-icon-xs w-icon-xs shrink-0 text-muted transition-transform group-hover:text-accent group-focus-visible:text-accent" }),
53
+ /* @__PURE__ */ jsx(Folder, { class: "h-icon-sm w-icon-sm shrink-0 text-muted group-hover:text-accent group-focus-visible:text-accent" }),
54
+ /* @__PURE__ */ jsx(FolderOpen, { class: "h-icon-sm w-icon-sm shrink-0 text-muted group-hover:text-accent group-focus-visible:text-accent" }),
55
+ /* @__PURE__ */ jsxs("span", { class: "min-w-0 truncate font-mono text-fg group-hover:text-accent group-focus-visible:text-accent", children: [
56
56
  dir.name,
57
57
  "/"
58
58
  ] }),
59
- /* @__PURE__ */ jsxs("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block", children: [
59
+ /* @__PURE__ */ jsxs("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block group-hover:text-accent group-focus-visible:text-accent", children: [
60
60
  countLabel(dir.fileCount, fileCountLabel, fileCountSingleLabel),
61
61
  " \xB7 ",
62
62
  formatAssetBytes(dir.bytes)
@@ -68,10 +68,10 @@ function AssetTree({ node, base, routePrefix, locale, fileCountLabel, fileCountS
68
68
  const Icon = iconFor(asset);
69
69
  const facet = facetLabel(asset, asset.lines !== void 0 ? linesLabel.replace("{count}", String(asset.lines)) : void 0);
70
70
  const meta = [kindLabel(asset), facet, formatAssetBytes(asset.bytes)].filter(Boolean).join(" \xB7 ");
71
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: assetViewerHref({ base, routePrefix, path: asset.path, locale }), title: asset.name, class: "flex min-w-0 items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small text-fg hover:bg-accent/10 hover:text-accent hover:underline focus-visible:bg-accent/10 focus-visible:text-accent focus-visible:underline", children: [
72
- /* @__PURE__ */ jsx(Icon, { class: "h-icon-sm w-icon-sm shrink-0 text-muted" }),
71
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: assetViewerHref({ base, routePrefix, path: asset.path, locale }), title: asset.name, class: "group flex min-w-0 items-center gap-hsp-xs rounded px-hsp-sm py-vsp-3xs text-small text-fg hover:bg-accent/10 hover:text-accent hover:underline focus-visible:bg-accent/10 focus-visible:text-accent focus-visible:underline", children: [
72
+ /* @__PURE__ */ jsx(Icon, { class: "h-icon-sm w-icon-sm shrink-0 text-muted group-hover:text-accent group-focus-visible:text-accent" }),
73
73
  /* @__PURE__ */ jsx("span", { class: "min-w-0 truncate font-mono", children: basename(asset.path) }),
74
- /* @__PURE__ */ jsx("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block", children: meta })
74
+ /* @__PURE__ */ jsx("span", { class: "ml-auto hidden whitespace-nowrap pl-hsp-lg text-caption text-muted sm:block group-hover:text-accent group-focus-visible:text-accent", children: meta })
75
75
  ] }) });
76
76
  })
77
77
  ] });
package/dist/compiled.css CHANGED
@@ -3608,11 +3608,6 @@ site-search [data-search-results] > :is([data-search-placeholder], p) {
3608
3608
  color: var(--zw-color-bg);
3609
3609
  }
3610
3610
  }
3611
- @media (hover: hover) {
3612
- :where(.group:hover) .group-hover\:text-fg {
3613
- color: var(--zw-color-fg);
3614
- }
3615
- }
3616
3611
  @media (hover: hover) {
3617
3612
  :where(.group:hover) .group-hover\:bg-fg {
3618
3613
  background-color: var(--zw-color-fg);
@@ -3624,9 +3619,6 @@ site-search [data-search-results] > :is([data-search-placeholder], p) {
3624
3619
  :where(.group:focus-visible) .group-focus-visible\:text-accent {
3625
3620
  color: var(--zw-color-accent);
3626
3621
  }
3627
- :where(.group:focus-visible) .group-focus-visible\:text-fg {
3628
- color: var(--zw-color-fg);
3629
- }
3630
3622
  @media (hover: hover) {
3631
3623
  :where(.peer:hover) ~ .peer-hover\:text-accent {
3632
3624
  color: var(--zw-color-accent);
@@ -0,0 +1,3 @@
1
+ export type DiffLines = typeof import("diff").diffLines;
2
+ /** diff's `diffLines`, or `null` when the optional peer is not installed. */
3
+ export declare const diffLines: DiffLines | null;
@@ -0,0 +1,13 @@
1
+ function pickDiffLines(m) {
2
+ let cur = m;
3
+ for (let i = 0; i < 3 && cur && typeof cur === "object"; i++) {
4
+ const fn = cur.diffLines;
5
+ if (typeof fn === "function") return fn;
6
+ cur = cur.default;
7
+ }
8
+ return null;
9
+ }
10
+ const diffLines = await import("diff").then(pickDiffLines, () => null);
11
+ export {
12
+ diffLines
13
+ };
@@ -95,12 +95,13 @@ async function getCachedDiff(olderHash, newerHash, olderContent, newerContent, i
95
95
  diffCache.set(key, hit);
96
96
  return hit;
97
97
  }
98
- const { diffLines } = await import("diff").then(
99
- (m) => m,
100
- () => {
101
- throw new Error('Compare requires the optional peer "diff": install it to use docHistory');
102
- }
98
+ const diffLines = await import("@takazudo/zudo-doc/diff-loader").then(
99
+ (m) => m.diffLines,
100
+ () => null
103
101
  );
102
+ if (!diffLines) {
103
+ throw new Error('Compare requires the optional peer "diff": install it to use docHistory');
104
+ }
104
105
  if (!isCurrent()) throw new Error("Comparison cancelled");
105
106
  const changes = diffLines(olderContent, newerContent);
106
107
  diffCache.set(key, changes);
@@ -0,0 +1,3 @@
1
+ export type KatexLike = typeof import("katex").default;
2
+ /** katex's API, or `null` when the optional peer is not installed. */
3
+ export declare const katex: KatexLike | null;
@@ -0,0 +1,12 @@
1
+ function pickKatex(m) {
2
+ let cur = m;
3
+ for (let i = 0; i < 3 && cur && typeof cur === "object"; i++) {
4
+ if (typeof cur.renderToString === "function") return cur;
5
+ cur = cur.default;
6
+ }
7
+ return null;
8
+ }
9
+ const katex = await import("katex").then(pickKatex, () => null);
10
+ export {
11
+ katex
12
+ };
@@ -1,13 +1,8 @@
1
1
  import { jsx } from "@takazudo/zfb/zudo-react/jsx-runtime";
2
- function pickKatex(m) {
3
- let cur = m;
4
- for (let i = 0; i < 3 && cur && typeof cur === "object"; i++) {
5
- if (typeof cur.renderToString === "function") return cur;
6
- cur = cur.default;
7
- }
8
- return null;
9
- }
10
- const katex = await import("katex").then(pickKatex, () => null);
2
+ const katex = await import("@takazudo/zudo-doc/katex-loader").then(
3
+ (m) => m.katex,
4
+ () => null
5
+ );
11
6
  const MISSING_KATEX_MESSAGE = 'MathBlock requires the optional peer "katex": install it and set math: true';
12
7
  function MathBlock({ latex, block = false }) {
13
8
  if (!katex) throw new Error(MISSING_KATEX_MESSAGE);
@@ -34,8 +34,14 @@ export interface MdDocFrontmatter {
34
34
  * search excerpts and llms-txt descriptions stay byte-equal across the
35
35
  * cutover, with one deliberate divergence: it also strips JSX/MDX block
36
36
  * comments (the `{/*`-delimited MDX comment form), which the legacy
37
- * pipeline leaked into descriptions (zudo-doc#2175). Do not add other new
38
- * rules without also updating the byte-equality fixtures (topic-plugin-audit).
37
+ * pipeline leaked into descriptions (zudo-doc#2175), and a second one
38
+ * (zudo-doc#4647): inline code keeps its text (only the backticks are
39
+ * dropped, contents protected from the later rules) instead of being deleted,
40
+ * and remark-directive container fence lines (`:::name[label]{attrs}`,
41
+ * `:::name Title`, bare `:::` closers) are removed while the label/title
42
+ * text is kept. Do not add
43
+ * other new rules without also updating the byte-equality fixtures
44
+ * (topic-plugin-audit).
39
45
  */
40
46
  export declare function stripMarkdown(md: string): string;
41
47
  /**
@@ -31,11 +31,18 @@ function decodeCharacterReferences(value) {
31
31
  ).replace(/&amp;/g, "&");
32
32
  }
33
33
  function stripMarkdown(md) {
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(
34
+ const inlineCode = [];
35
+ const withoutCodeCommentsAndTags = md.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, (_m, code) => {
36
+ inlineCode.push(code);
37
+ return `\uE000${inlineCode.length - 1}\uE001`;
38
+ }).replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/<[^>]+>/g, "");
39
+ return decodeCharacterReferences(withoutCodeCommentsAndTags).replace(
40
+ /^[ \t]*:{3,}(?:[A-Za-z][\w-]*(?:\[([^\]\n]*)\])?(?:\{[^}\n]*\})?(?:[ \t]+(\S[^\n]*?))?)?[ \t]*$/gm,
41
+ (_m, label, title) => label ?? title ?? ""
42
+ ).replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}([^*]+)\*{1,3}/g, "$1").replace(
36
43
  /(^|[^\p{L}\p{N}])(_{1,3})(?=\S)([^_\n]*?\S)\2(?=$|[^\p{L}\p{N}])/gmu,
37
44
  "$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();
45
+ ).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").replace(/\uE000(\d+)\uE001/g, (_m, i) => inlineCode[Number(i)] ?? "").trim();
39
46
  }
40
47
  function collectMdFiles(dir) {
41
48
  const results = [];
@@ -23,7 +23,8 @@ function CardBody({
23
23
  item,
24
24
  locale: props.locale,
25
25
  updatedLabel: props.updatedLabel,
26
- dateFormats: props.dateFormats
26
+ dateFormats: props.dateFormats,
27
+ linked
27
28
  }
28
29
  ) })
29
30
  ] }),
@@ -36,24 +37,23 @@ function DateStamp({
36
37
  locale,
37
38
  updatedLabel,
38
39
  positioned,
40
+ linked,
39
41
  dateFormats
40
42
  }) {
41
- return /* @__PURE__ */ jsxs(
42
- "time",
43
- {
44
- datetime: date,
45
- class: positioned ? "hidden sm:flex sm:col-start-2 sm:row-start-1 sm:row-span-2 w-[6.5rem] flex-col items-end self-stretch border-l border-muted pl-hsp-xl text-muted tabular-nums" : "hidden sm:flex w-[6.5rem] flex-col items-end self-stretch border-l border-muted pl-hsp-xl text-muted tabular-nums",
46
- children: [
47
- /* @__PURE__ */ jsx("span", { class: "block text-title leading-tight font-medium", children: formatMonthDayLabel(date, locale, dateFormats?.monthDay) }),
48
- /* @__PURE__ */ jsx("span", { class: "block text-caption", children: formatYear(date, locale, dateFormats?.year) }),
49
- updated && /* @__PURE__ */ jsxs("span", { class: "mt-vsp-3xs block text-micro", children: [
50
- updatedLabel,
51
- " ",
52
- formatMonthDayLabel(updated, locale, dateFormats?.monthDay)
53
- ] })
54
- ]
55
- }
56
- );
43
+ const stampClass = [
44
+ "hidden sm:flex w-[6.5rem] flex-col items-end self-stretch border-l border-muted pl-hsp-xl text-muted tabular-nums",
45
+ positioned && "sm:col-start-2 sm:row-start-1 sm:row-span-2",
46
+ linked && "group-hover:text-accent group-focus-visible:text-accent"
47
+ ].filter(Boolean).join(" ");
48
+ return /* @__PURE__ */ jsxs("time", { datetime: date, class: stampClass, children: [
49
+ /* @__PURE__ */ jsx("span", { class: "block text-title leading-tight font-medium", children: formatMonthDayLabel(date, locale, dateFormats?.monthDay) }),
50
+ /* @__PURE__ */ jsx("span", { class: "block text-caption", children: formatYear(date, locale, dateFormats?.year) }),
51
+ updated && /* @__PURE__ */ jsxs("span", { class: "mt-vsp-3xs block text-micro", children: [
52
+ updatedLabel,
53
+ " ",
54
+ formatMonthDayLabel(updated, locale, dateFormats?.monthDay)
55
+ ] })
56
+ ] });
57
57
  }
58
58
  function CardList(props) {
59
59
  return /* @__PURE__ */ jsx("div", { class: "grid grid-cols-1 gap-vsp-md", children: props.items.map((item) => /* @__PURE__ */ jsx(
@@ -84,7 +84,8 @@ function CardList(props) {
84
84
  locale: props.locale,
85
85
  updatedLabel: props.updatedLabel,
86
86
  dateFormats: props.dateFormats,
87
- positioned: true
87
+ positioned: true,
88
+ linked: true
88
89
  }
89
90
  )
90
91
  ]
@@ -107,7 +108,8 @@ function CardList(props) {
107
108
  locale: props.locale,
108
109
  updatedLabel: props.updatedLabel,
109
110
  dateFormats: props.dateFormats,
110
- positioned: true
111
+ positioned: true,
112
+ linked: false
111
113
  }
112
114
  )
113
115
  ] }),
@@ -135,7 +137,8 @@ function CardList(props) {
135
137
  locale: props.locale,
136
138
  updatedLabel: props.updatedLabel,
137
139
  dateFormats: props.dateFormats,
138
- positioned: false
140
+ positioned: false,
141
+ linked: true
139
142
  }
140
143
  )
141
144
  ]
@@ -158,7 +161,8 @@ function CardList(props) {
158
161
  locale: props.locale,
159
162
  updatedLabel: props.updatedLabel,
160
163
  dateFormats: props.dateFormats,
161
- positioned: false
164
+ positioned: false,
165
+ linked: false
162
166
  }
163
167
  )
164
168
  ] })
@@ -3,11 +3,13 @@ import type { JSX } from "@takazudo/zfb/zudo-react/jsx-runtime";
3
3
  import type { ResolvedDateFormats } from "../../settings.js";
4
4
  import type { NoteTrayIndexItem } from "../note-tray-index.js";
5
5
  import type { TagNavLabels } from "../types.js";
6
- export declare function DateLine({ item, locale, updatedLabel, dateFormats, }: {
6
+ export declare function DateLine({ item, locale, updatedLabel, dateFormats, linked, }: {
7
7
  item: NoteTrayIndexItem;
8
8
  locale: string;
9
9
  updatedLabel: string;
10
10
  dateFormats?: ResolvedDateFormats;
11
+ /** Inside a `group` link: follow that link's hover/focus-visible accent. */
12
+ linked?: boolean;
11
13
  }): JSX.Element | null;
12
14
  export declare function ItemTags({ item, labels }: {
13
15
  item: NoteTrayIndexItem;
@@ -5,18 +5,25 @@ function DateLine({
5
5
  item,
6
6
  locale,
7
7
  updatedLabel,
8
- dateFormats
8
+ dateFormats,
9
+ linked = false
9
10
  }) {
10
11
  if (!item.date && !item.updated) return null;
11
- return /* @__PURE__ */ jsxs("span", { class: "tabular-nums text-caption text-muted", children: [
12
- item.date && /* @__PURE__ */ jsx("time", { datetime: item.date, children: formatDate(item.date, locale, dateFormats?.full) }),
13
- item.date && item.updated && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " \xB7 " }),
14
- item.updated && /* @__PURE__ */ jsxs("span", { children: [
15
- updatedLabel,
16
- " ",
17
- /* @__PURE__ */ jsx("time", { datetime: item.updated, children: formatDate(item.updated, locale, dateFormats?.full) })
18
- ] })
19
- ] });
12
+ return /* @__PURE__ */ jsxs(
13
+ "span",
14
+ {
15
+ class: linked ? "tabular-nums text-caption text-muted group-hover:text-accent group-focus-visible:text-accent" : "tabular-nums text-caption text-muted",
16
+ children: [
17
+ item.date && /* @__PURE__ */ jsx("time", { datetime: item.date, children: formatDate(item.date, locale, dateFormats?.full) }),
18
+ item.date && item.updated && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " \xB7 " }),
19
+ item.updated && /* @__PURE__ */ jsxs("span", { children: [
20
+ updatedLabel,
21
+ " ",
22
+ /* @__PURE__ */ jsx("time", { datetime: item.updated, children: formatDate(item.updated, locale, dateFormats?.full) })
23
+ ] })
24
+ ]
25
+ }
26
+ );
20
27
  }
21
28
  function ItemTags({ item, labels }) {
22
29
  if (!labels || !item.tagLinks?.length) return null;
@@ -5,7 +5,7 @@ function IndexList(props) {
5
5
  const width = rankWidth(props.items);
6
6
  return /* @__PURE__ */ jsx("ol", { class: "zd-list-items-no-margin", children: props.items.map((item) => {
7
7
  const rowClass = item.href ? "relative -mt-px first:mt-0 border-y border-muted hover:z-local-1 hover:border-accent focus-within:z-local-1 focus-within:border-accent" : "relative -mt-px first:mt-0 border-y border-muted";
8
- const rankClass = item.href ? "whitespace-nowrap tabular-nums text-heading leading-none text-muted group-hover:text-fg group-focus-visible:text-fg" : "whitespace-nowrap tabular-nums text-heading leading-none text-muted";
8
+ const rankClass = item.href ? "whitespace-nowrap tabular-nums text-heading leading-none text-muted group-hover:text-accent group-focus-visible:text-accent" : "whitespace-nowrap tabular-nums text-heading leading-none text-muted";
9
9
  const labelClass = item.href ? "font-medium text-fg group-hover:text-accent group-hover:underline group-focus-visible:text-accent group-focus-visible:underline" : "font-medium text-fg";
10
10
  const detailClass = item.href ? "mt-vsp-2xs block text-small text-muted group-hover:text-accent group-hover:underline group-focus-visible:text-accent group-focus-visible:underline" : "mt-vsp-2xs block text-small text-muted";
11
11
  const dateClass = item.href ? "mt-vsp-2xs block text-caption text-muted group-hover:text-accent group-hover:underline group-focus-visible:text-accent group-focus-visible:underline" : "mt-vsp-2xs block text-caption text-muted";
@@ -20,7 +20,8 @@ function IndexList(props) {
20
20
  item,
21
21
  locale: props.locale,
22
22
  updatedLabel: props.updatedLabel,
23
- dateFormats: props.dateFormats
23
+ dateFormats: props.dateFormats,
24
+ linked: Boolean(item.href)
24
25
  }
25
26
  ) })
26
27
  ] })
@@ -0,0 +1,4 @@
1
+ import type { ZfbPlugin } from "@takazudo/zfb/plugins";
2
+ export declare const DIFF_LOADER_SPECIFIER = "@takazudo/zudo-doc/diff-loader";
3
+ declare const plugin: ZfbPlugin;
4
+ export default plugin;
@@ -0,0 +1,13 @@
1
+ const DIFF_LOADER_SPECIFIER = "@takazudo/zudo-doc/diff-loader";
2
+ const DISABLED_LOADER_SOURCE = 'throw new Error("diff is not bundled: docHistory is off, so this build shadows @takazudo/zudo-doc/diff-loader. To use the doc history Compare view, set docHistory: true and install diff.");\nexport {};\n';
3
+ const plugin = {
4
+ name: "diff-loader",
5
+ setup(ctx) {
6
+ ctx.addVirtualModule(DIFF_LOADER_SPECIFIER, () => DISABLED_LOADER_SOURCE);
7
+ }
8
+ };
9
+ var diff_loader_default = plugin;
10
+ export {
11
+ DIFF_LOADER_SPECIFIER,
12
+ diff_loader_default as default
13
+ };
@@ -0,0 +1,4 @@
1
+ import type { ZfbPlugin } from "@takazudo/zfb/plugins";
2
+ export declare const KATEX_LOADER_SPECIFIER = "@takazudo/zudo-doc/katex-loader";
3
+ declare const plugin: ZfbPlugin;
4
+ export default plugin;
@@ -0,0 +1,13 @@
1
+ const KATEX_LOADER_SPECIFIER = "@takazudo/zudo-doc/katex-loader";
2
+ const DISABLED_LOADER_SOURCE = 'throw new Error("katex is not bundled: math is off, so this build shadows @takazudo/zudo-doc/katex-loader. To render <MathBlock>, set math: true and install katex.");\nexport {};\n';
3
+ const plugin = {
4
+ name: "katex-loader",
5
+ setup(ctx) {
6
+ ctx.addVirtualModule(KATEX_LOADER_SPECIFIER, () => DISABLED_LOADER_SOURCE);
7
+ }
8
+ };
9
+ var katex_loader_default = plugin;
10
+ export {
11
+ KATEX_LOADER_SPECIFIER,
12
+ katex_loader_default as default
13
+ };
package/dist/preset.d.ts CHANGED
@@ -118,7 +118,7 @@ export interface PresetSettings {
118
118
  trailingSlash: boolean;
119
119
  minifyHtml?: boolean;
120
120
  mermaid: boolean;
121
- /** KaTeX math rendering; when off, the optional `katex` peer is shadowed. */
121
+ /** KaTeX math rendering; when off, `@takazudo/zudo-doc/katex-loader` is shadowed (bare `katex` is not). */
122
122
  math?: boolean;
123
123
  /** Enable transclusion of other Markdown/MDX files. */
124
124
  transclude?: boolean;
@@ -131,6 +131,7 @@ export interface PresetSettings {
131
131
  changelogs?: PresetChangelogConfig[] | false;
132
132
  /** Metadata fields shown in the doc metadata area. */
133
133
  docMetainfoFields?: Array<"created" | "updated" | "author">;
134
+ /** Per-page git history; when off, `@takazudo/zudo-doc/diff-loader` is shadowed (bare `diff` is not). */
134
135
  docHistory?: boolean;
135
136
  /** Whether the doc history dropdown UI and related artifacts are enabled. */
136
137
  docHistoryUi?: boolean;
package/dist/preset.js CHANGED
@@ -339,7 +339,18 @@ function buildPlugins(settings, routeContext) {
339
339
  // `design-token-panel-bootstrap` (#4261). Preset-level rather than inside
340
340
  // the routes plugin because the bootstrap is reachable from chrome even
341
341
  // when packageOwnedRoutes is off.
342
- ...resolvesBundleZdtp(settings) ? [] : [{ name: "@takazudo/zudo-doc/plugins/zdtp-loader", options: {} }]
342
+ ...resolvesBundleZdtp(settings) ? [] : [{ name: "@takazudo/zudo-doc/plugins/zdtp-loader", options: {} }],
343
+ // math off → shadow math-block's `@takazudo/zudo-doc/katex-loader` with a
344
+ // throwing virtual module so a site without the optional katex peer gets no
345
+ // Rolldown UNRESOLVED_IMPORT warning (#4651). The bare `katex` is untouched.
346
+ ...settings.math ? [] : [{ name: "@takazudo/zudo-doc/plugins/katex-loader", options: {} }],
347
+ // docHistory off → shadow doc-history's `@takazudo/zudo-doc/diff-loader`
348
+ // the same way (#4652). Gated on `docHistory`, NOT `docHistoryUi`: the
349
+ // dates-only mode (`docHistory: true, docHistoryUi: false`) keeps the doc
350
+ // history feature on, so the shadow stays out of every configuration where
351
+ // the Compare view is still part of the enabled feature. Bare `diff` is
352
+ // untouched.
353
+ ...settings.docHistory ? [] : [{ name: "@takazudo/zudo-doc/plugins/diff-loader", options: {} }]
343
354
  ];
344
355
  }
345
356
  export {
@@ -508,9 +508,7 @@ function TrayItem({
508
508
  rankDigits !== void 0 ? /* @__PURE__ */ jsx(
509
509
  "span",
510
510
  {
511
- class: computed(
512
- () => `shrink-0 tabular-nums${active.value ? "" : " text-muted"}`
513
- ),
511
+ class: "shrink-0 tabular-nums",
514
512
  children: computed(
515
513
  () => item.value.rank === void 0 ? "" : String(item.value.rank).padStart(rankDigits, "0")
516
514
  )
@@ -526,9 +524,7 @@ function TrayItem({
526
524
  /* @__PURE__ */ jsx(Show, { when: computed(() => showDate && !!shortDate.value), children: () => /* @__PURE__ */ jsx(
527
525
  "span",
528
526
  {
529
- class: computed(
530
- () => `shrink-0 tabular-nums${active.value ? "" : " text-muted"}`
531
- ),
527
+ class: "shrink-0 tabular-nums",
532
528
  children: shortDate
533
529
  }
534
530
  ) })
@@ -328,7 +328,7 @@ function NoteTrayRow({
328
328
  {
329
329
  href: computed(() => href.value ?? ""),
330
330
  "data-note-tray-row": true,
331
- class: "flex items-start gap-hsp-sm py-vsp-2xs text-small text-fg hover:text-accent hover:underline focus:underline focus-visible:text-accent",
331
+ class: "group flex items-start gap-hsp-sm py-vsp-2xs text-small text-fg hover:text-accent hover:underline focus:underline focus-visible:text-accent",
332
332
  children: [
333
333
  /* @__PURE__ */ jsx(
334
334
  Show,
@@ -337,7 +337,7 @@ function NoteTrayRow({
337
337
  fallback: () => /* @__PURE__ */ jsx(
338
338
  "span",
339
339
  {
340
- class: "shrink-0 font-mono tabular-nums text-caption text-muted",
340
+ class: "shrink-0 font-mono tabular-nums text-caption text-muted group-hover:text-accent group-focus-visible:text-accent",
341
341
  style: computed(() => ({ width: `${width.value}ch` })),
342
342
  children: rankLabel
343
343
  }
@@ -346,7 +346,7 @@ function NoteTrayRow({
346
346
  "time",
347
347
  {
348
348
  datetime: computed(() => item.value.date ?? ""),
349
- class: "shrink-0 font-mono tabular-nums text-caption text-muted",
349
+ class: "shrink-0 font-mono tabular-nums text-caption text-muted group-hover:text-accent group-focus-visible:text-accent",
350
350
  children: computed(() => dateLabel.value ?? "")
351
351
  }
352
352
  )
@@ -387,8 +387,8 @@ html[data-theme-pack="brutalist"] .zd-content blockquote a {
387
387
  font-weight: 500;
388
388
  }
389
389
  /* Card-style prose links (e.g. the note-tray IndexList rank/label/
390
- description spans) put their own color on group-hover: utility
391
- classes (group-hover:text-fg / group-hover:text-accent), which beats
390
+ description/date spans) put their own color on group-hover: utility
391
+ classes (group-hover:text-accent), which beats
392
392
  the anchor's inherited `color` — so the anchor-level color below
393
393
  never reached that text. Measured 2.63:1 (rgb(245 245 245) on
394
394
  rgb(255 106 31), dark) / 4.06:1 (rgb(0 0 0) on rgb(194 65 12), light)
package/dist/wind.json CHANGED
@@ -136,14 +136,12 @@
136
136
  "grid-rows-subgrid",
137
137
  "group",
138
138
  "group-focus-visible:text-accent",
139
- "group-focus-visible:text-fg",
140
139
  "group-focus-visible:underline",
141
140
  "group-focus-within:block",
142
141
  "group-hover:bg-fg",
143
142
  "group-hover:block",
144
143
  "group-hover:text-accent",
145
144
  "group-hover:text-bg",
146
- "group-hover:text-fg",
147
145
  "group-hover:underline",
148
146
  "group-open:rotate-90",
149
147
  "h-[0.5rem]",
@@ -180,16 +180,20 @@ async function getCachedDiff(
180
180
  return hit;
181
181
  }
182
182
  // Lazy-load diff — only needed after History → Compare. This keeps the
183
- // module out of the eager islands bundle. `diff` is an optional peer: the
184
- // literal `import("diff").then(onFulfilled, onRejected)` shape is what lets
185
- // esbuild leave it unresolved when absent instead of failing the build
186
- // (#4209). The rejection surfaces through DiffViewer's `.catch()` → diffError.
187
- const { diffLines } = await import("diff").then(
188
- (m) => m,
189
- () => {
190
- throw new Error('Compare requires the optional peer "diff": install it to use docHistory');
191
- },
183
+ // module out of the eager islands bundle. `diff` is an optional peer reached
184
+ // ONLY through the package-owned `@takazudo/zudo-doc/diff-loader` (#4652):
185
+ // with `docHistory` off the preset's `diff-loader` plugin shadows that
186
+ // specifier with a throwing virtual module, so the bundler never tries to
187
+ // resolve an absent `diff` (no UNRESOLVED_IMPORT); with `docHistory: true`
188
+ // and diff missing the loader resolves `diffLines` to `null` (#4209). The
189
+ // thrown error surfaces through DiffViewer's `.catch()` → diffError.
190
+ const diffLines = await import("@takazudo/zudo-doc/diff-loader").then(
191
+ (m) => m.diffLines,
192
+ () => null,
192
193
  );
194
+ if (!diffLines) {
195
+ throw new Error('Compare requires the optional peer "diff": install it to use docHistory');
196
+ }
193
197
  // The module import cannot be aborted; a replaced comparison must not do
194
198
  // work or populate the cache after its owning scope has been disposed.
195
199
  if (!isCurrent()) throw new Error("Comparison cancelled");
@@ -587,10 +587,7 @@ function TrayItem({
587
587
  >
588
588
  {rankDigits !== undefined ? (
589
589
  <span
590
- class={computed(
591
- () =>
592
- `shrink-0 tabular-nums${active.value ? "" : " text-muted"}`,
593
- )}
590
+ class="shrink-0 tabular-nums"
594
591
  >
595
592
  {computed(() =>
596
593
  item.value.rank === undefined
@@ -606,10 +603,7 @@ function TrayItem({
606
603
  <Show when={computed(() => showDate && !!shortDate.value)}>
607
604
  {() => (
608
605
  <span
609
- class={computed(
610
- () =>
611
- `shrink-0 tabular-nums${active.value ? "" : " text-muted"}`,
612
- )}
606
+ class="shrink-0 tabular-nums"
613
607
  >
614
608
  {shortDate}
615
609
  </span>
@@ -443,13 +443,13 @@ function NoteTrayRow({
443
443
  <a
444
444
  href={computed(() => href.value ?? "")}
445
445
  data-note-tray-row
446
- class="flex items-start gap-hsp-sm py-vsp-2xs text-small text-fg hover:text-accent hover:underline focus:underline focus-visible:text-accent"
446
+ class="group flex items-start gap-hsp-sm py-vsp-2xs text-small text-fg hover:text-accent hover:underline focus:underline focus-visible:text-accent"
447
447
  >
448
448
  <Show
449
449
  when={computed(() => Boolean(dateLabel.value))}
450
450
  fallback={() => (
451
451
  <span
452
- class="shrink-0 font-mono tabular-nums text-caption text-muted"
452
+ class="shrink-0 font-mono tabular-nums text-caption text-muted group-hover:text-accent group-focus-visible:text-accent"
453
453
  style={computed(() => ({ width: `${width.value}ch` }))}
454
454
  >
455
455
  {rankLabel}
@@ -459,7 +459,7 @@ function NoteTrayRow({
459
459
  {() => (
460
460
  <time
461
461
  datetime={computed(() => item.value.date ?? "")}
462
- class="shrink-0 font-mono tabular-nums text-caption text-muted"
462
+ class="shrink-0 font-mono tabular-nums text-caption text-muted group-hover:text-accent group-focus-visible:text-accent"
463
463
  >
464
464
  {computed(() => dateLabel.value ?? "")}
465
465
  </time>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takazudo/zudo-doc",
3
- "version": "8.1.0",
3
+ "version": "8.1.1",
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",
@@ -206,6 +206,14 @@
206
206
  "types": "./dist/plugins/zdtp-loader.d.ts",
207
207
  "default": "./dist/plugins/zdtp-loader.js"
208
208
  },
209
+ "./plugins/katex-loader": {
210
+ "types": "./dist/plugins/katex-loader.d.ts",
211
+ "default": "./dist/plugins/katex-loader.js"
212
+ },
213
+ "./plugins/diff-loader": {
214
+ "types": "./dist/plugins/diff-loader.d.ts",
215
+ "default": "./dist/plugins/diff-loader.js"
216
+ },
209
217
  "./content-admonition": {
210
218
  "types": "./dist/content-admonition/index.d.ts",
211
219
  "default": "./dist/content-admonition/index.js"
@@ -712,6 +720,14 @@
712
720
  "./zdtp-loader": {
713
721
  "types": "./dist/zdtp-loader.d.ts",
714
722
  "default": "./dist/zdtp-loader.js"
723
+ },
724
+ "./katex-loader": {
725
+ "types": "./dist/katex-loader.d.ts",
726
+ "default": "./dist/katex-loader.js"
727
+ },
728
+ "./diff-loader": {
729
+ "types": "./dist/diff-loader.d.ts",
730
+ "default": "./dist/diff-loader.js"
715
731
  }
716
732
  },
717
733
  "bin": {
@@ -736,9 +752,9 @@
736
752
  "peerDependencies": {
737
753
  "@modelcontextprotocol/sdk": "1.32.2",
738
754
  "@takazudo/zdtp": "^0.5.2 || ^0.6.0 || ^0.7.0 || ^0.8.0",
739
- "@takazudo/zfb": "^5.1.2",
740
- "@takazudo/zfb-md-wasm": "^5.1.2",
741
- "@takazudo/zfb-runtime": "^5.1.2",
755
+ "@takazudo/zfb": "^5.1.3",
756
+ "@takazudo/zfb-md-wasm": "^5.1.3",
757
+ "@takazudo/zfb-runtime": "^5.1.3",
742
758
  "@takazudo/zudo-doc-history-server": "^8.0.0",
743
759
  "diff": "^8.0.0",
744
760
  "katex": "^0.18.2 || ^0.19.0",
@@ -779,9 +795,9 @@
779
795
  "devDependencies": {
780
796
  "@modelcontextprotocol/sdk": "1.32.2",
781
797
  "@takazudo/mdx-formatter": "1.3.0",
782
- "@takazudo/zfb": "5.1.2",
783
- "@takazudo/zfb-md-wasm": "5.1.2",
784
- "@takazudo/zfb-runtime": "5.1.2",
798
+ "@takazudo/zfb": "5.1.3",
799
+ "@takazudo/zfb-md-wasm": "5.1.3",
800
+ "@takazudo/zfb-runtime": "5.1.3",
785
801
  "@types/fs-extra": "^11.0.4",
786
802
  "@types/minimist": "^1.2.5",
787
803
  "@types/node": "^25.3.5",
@@ -791,7 +807,7 @@
791
807
  "typescript": "^5.0.0",
792
808
  "vitest": "^4.1.0",
793
809
  "zod": "^4.3.6",
794
- "@takazudo/zudo-doc-history-server": "8.1.0"
810
+ "@takazudo/zudo-doc-history-server": "8.1.1"
795
811
  },
796
812
  "scripts": {
797
813
  "gen:search-widget-script": "node scripts/gen-search-widget-script.mjs",