@takazudo/zudo-doc 4.0.0 → 4.2.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
@@ -4,6 +4,53 @@ 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
+ ## [4.2.0] - 2026-07-18
8
+
9
+ ### Features
10
+
11
+ - HtmlPreview gains a `fullHeight` opt-in prop for full-height embeds (18246fb7)
12
+ - HtmlPreview gains `externalStyles` / `externalScripts` / `preflight` / `showResources` props for embedding and disclosing external resources (20c679ed)
13
+ - New `DOC_HISTORY_SKIP_POSTBUILD` env var skips only the postBuild dropdown JSON step, leaving the preBuild Created/Updated/Author manifest intact (9234443c)
14
+ - Scaffolds with docHistory enabled now emit a `dev:network` script for LAN access (e0c0f9f6)
15
+ - The four preset-only config fields are now mirrored onto `CreateOptions` for scaffolding parity (a97ee2d0)
16
+
17
+ ### Bug Fixes
18
+
19
+ - create-zudo-doc: the doc-history-server now starts alongside zfb dev when docHistory is enabled (7efda47e)
20
+ - create-zudo-doc: disable pnpm 11's `minimumReleaseAge` gate in scaffolds, and warn about a parent workspace's gate when skipping the nested pnpm-workspace (1b3fe1a0, 27375853)
21
+ - create-zudo-doc: ship claudeSkills content from `templates/`, not the monorepo root (103082e8)
22
+ - setup-doc-skill: resolve nested-subdir symlinks, make SKILL.md commands runtime-conditional, and detect a `format:md` script name (5b4a6416, f58ab17e)
23
+ - generator: add `.zfb-build/` to the generated `.gitignore`, and sync the tauri template CSP with the showcase (474f4f1a, da2f19b3)
24
+ - Docs: corrected the metaTags fragment link to the hierarchical heading id (0728d985)
25
+ - Mermaid: drop the semicolon from stateDiagram statement repair (3390d35d)
26
+ - Tests: strip ANSI codes in eject slow-test output assertions (c2aad778)
27
+
28
+ ### Other Changes
29
+
30
+ - Skills: authored scaffold variants of design-system, translate, and zudo-doc-version-bump, completing the drift-guard transition (317e3449, 167fe424)
31
+ - Tests: added the generated-scaffold skill-reference integration guard, split the skills drift guard, and exempted scaffold-variant skills from the template-drift shell guard (ef233939, cd37d1c2)
32
+ - Tests: synced the target-manifest exam fixture to the 13-file scaffold and re-baselined route-injection parity hashes (16faafd3, b1eea45b)
33
+ - Docs: documented `dev:network` and zfb flag forwarding (9dbc4f4d)
34
+
35
+ ## [4.1.0] - 2026-07-18
36
+
37
+ ### Features
38
+
39
+ - Theme-pack fonts now reach the app shell (header, sidebar, TOC, breadcrumb, pager) via a global body font seam, with per-surface chrome font tokens for finer control (f84b2bd2)
40
+
41
+ ### Bug Fixes
42
+
43
+ - Per-surface chrome font knobs anchor to the seam token instead of relying on inheritance (dbd95365)
44
+ - Docs: corrected the false claim that mobile chrome strips theme-pack font tokens — mobile hooks receive them like desktop (3cbe0d45, 493f76dc)
45
+ - E2E: theme-pack font specs assert the body font-family directly, target the bundled foundry pack, and wait for switcher hydration (d88e4c5a, 218e8844)
46
+ - Reverted a hand-edited generated CHANGELOG.md — it is corpus-generated (ef43274c)
47
+
48
+ ### Other Changes
49
+
50
+ - Toolchain: @takazudo/zfb family (zfb, zfb-runtime, zfb-adapter-cloudflare, zfb-md-wasm) adopted through 0.1.0-next.89 (02268414)
51
+ - Bilingual docs for the chrome font seam tokens and the mobile reach caveat (58cf6fc2)
52
+ - Durable e2e regression spec for the chrome font seam (69af425e)
53
+
7
54
  ## [4.0.0] - 2026-07-17
8
55
 
9
56
  ### Breaking Changes
@@ -193,9 +193,13 @@ function buildMermaidInitScript(cdnUrl) {
193
193
  .replace(/\\s+(Note\\s+(?:left|right|over)\\s+of\\s+)/gi, ";\\n$1")
194
194
  .replace(/\\s+(loop|alt|else|opt|par|and|rect|critical|break|end)\\b/gi, ";\\n$1");
195
195
  } else if (/^stateDiagram(?:-v2)?\\b/i.test(out)) {
196
+ // Newline only \u2014 unlike flowchart/sequenceDiagram, \`;\` is not a
197
+ // trailing separator mermaid strips from a stateDiagram state id;
198
+ // it parses "Draft;" as a state DISTINCT from "Draft", producing
199
+ // a spurious node (zudolab/zudo-doc#2909).
196
200
  out = out.replace(
197
201
  /([A-Za-z0-9_*\\]\\)\\}])\\s+((?:\\[\\*\\]|[A-Za-z0-9_][\\w-]*)\\s*--?>)/g,
198
- "$1;\\n$2",
202
+ "$1\\n$2",
199
203
  );
200
204
  }
201
205
 
@@ -16,7 +16,7 @@ export type ComponentTokenSurface = "content" | "chrome";
16
16
  * Provides autocomplete when a consumer iterates or references individual tokens.
17
17
  * Must be kept in sync with the `cssVar` values in `COMPONENT_TOKENS` below.
18
18
  */
19
- export type ComponentTokenName = "--zdc-doc-title-font" | "--zdc-doc-title-weight" | "--zdc-doc-title-tracking" | "--zdc-doc-h2-font" | "--zdc-doc-h2-weight" | "--zdc-doc-h2-tracking" | "--zdc-doc-h3-weight" | "--zdc-doc-h4-weight" | "--zdc-doc-prose-font" | "--zdc-doc-link-decoration" | "--zdc-admonition-radius" | "--zdc-admonition-border-width" | "--zdc-card-radius" | "--zdc-content-max-width" | "--zdc-toc-width" | "--zdc-nav-active-indicator-color" | "--zdc-nav-active-weight";
19
+ export type ComponentTokenName = "--zdc-doc-title-font" | "--zdc-doc-title-weight" | "--zdc-doc-title-tracking" | "--zdc-doc-h2-font" | "--zdc-doc-h2-weight" | "--zdc-doc-h2-tracking" | "--zdc-doc-h3-weight" | "--zdc-doc-h4-weight" | "--zdc-doc-prose-font" | "--zdc-doc-link-decoration" | "--zdc-admonition-radius" | "--zdc-admonition-border-width" | "--zdc-card-radius" | "--zdc-content-max-width" | "--zdc-toc-width" | "--zdc-nav-active-indicator-color" | "--zdc-nav-active-weight" | "--zdc-chrome-font" | "--zdc-header-font" | "--zdc-sidebar-font" | "--zdc-toc-font";
20
20
  export interface ComponentToken {
21
21
  /**
22
22
  * The CSS custom-property name the consumer redefines in `:root`, e.g.
@@ -98,11 +98,17 @@ const COMPONENT_TOKENS = [
98
98
  },
99
99
  // ── Content prose font (.zd-content, #2460) ────────────────────────────────
100
100
  // Sets font-family on the content root so every element inside inherits it by
101
- // default. The headings whose `--zdc-doc-*-font` default to `inherit` will
102
- // inherit this value — safe because the current computed heading family already
103
- // resolves to `var(--font-sans)` (Tailwind preflight sets it on `html`).
104
- // Byte-identical: `.zd-content` currently carries no explicit font-family, so
105
- // setting it to `var(--font-sans)` produces the same computed value.
101
+ // default. The headings whose `--zdc-doc-*-font` default to `inherit` inherit
102
+ // this value.
103
+ // NOT byte-identical, by design (#2887 corrects the original claim here): this
104
+ // project imports only `tailwindcss/preflight` + `utilities` and never defines
105
+ // `--default-font-family`, so preflight's `html, :host` rule compiles its
106
+ // `--theme(--default-font-family, ui-sans-serif, system-ui, …)` down to the
107
+ // LITERAL fallback stack — it does NOT resolve to `var(--font-sans)`. Pointing
108
+ // `.zd-content` at `var(--font-sans)` therefore moved prose off that literal
109
+ // onto the token (`system-ui, sans-serif` by default) — the same system UI face
110
+ // on every major platform, which is why the delta went unnoticed. `body` takes
111
+ // the identical step for the app shell — see `--zdc-chrome-font` below.
106
112
  {
107
113
  cssVar: "--zdc-doc-prose-font",
108
114
  selector: ".zd-content",
@@ -255,6 +261,99 @@ const COMPONENT_TOKENS = [
255
261
  surface: "chrome",
256
262
  category: "typography",
257
263
  description: "Font weight of the active sidebar nav link (non-root leaf, the current page). Defaults to `var(--font-weight-medium)` (byte-identical to the current `font-medium` utility). Redefine in :root to use semibold or bold for stronger active emphasis."
264
+ },
265
+ // ── Chrome font seam (body, #2887 / epic #2886) ───────────────────────────
266
+ // THE foundation the chrome-font epic stacks on. Every app-shell surface —
267
+ // header, desktop sidebar, mobile drawer, right-rail TOC, mobile TOC,
268
+ // breadcrumb, footer — sets NO font-family of its own, so all of them inherit
269
+ // Tailwind preflight's `html, :host` rule. Because this project imports only
270
+ // `tailwindcss/preflight` + `utilities` and never defines
271
+ // `--default-font-family`, that rule's `--theme(--default-font-family,
272
+ // ui-sans-serif, system-ui, …)` compiles to the HARDCODED LITERAL fallback
273
+ // stack. A theme pack's `--font-sans` override therefore had no path to any of
274
+ // those surfaces at all — only `.zd-content` prose consumed the token.
275
+ //
276
+ // This rule is that path: `body` is UNLAYERED here, so it beats preflight
277
+ // (which the consumer imports into `layer(zd-preflight)`), and every chrome
278
+ // surface inherits `var(--font-sans)` — including any surface a future
279
+ // component adds, since inheritance is the mechanism.
280
+ //
281
+ // NOT byte-identical — the ONE deliberate exception in this registry. Chrome
282
+ // falls from preflight's `ui-sans-serif, system-ui, …` literal to whatever
283
+ // `--font-sans` resolves to (`system-ui, sans-serif` by default): the same
284
+ // system UI face on every major platform, and the identical step `.zd-content`
285
+ // already took in #2460 (see `--zdc-doc-prose-font` above). Accepted per #2887.
286
+ //
287
+ // Deliberately NO `--font-mono` counterpart: every code surface (`code`, `pre`,
288
+ // `.hi-root`, the diff viewer, …) sets `font-family: var(--font-mono)` on
289
+ // itself explicitly, so the mono token already reaches all of them.
290
+ {
291
+ cssVar: "--zdc-chrome-font",
292
+ selector: "body",
293
+ property: "font-family",
294
+ default: "var(--font-sans)",
295
+ component: "chrome",
296
+ surface: "chrome",
297
+ category: "typography",
298
+ description: "Base font family for the whole document, and the seam that lets `--font-sans` reach the app shell (header/sidebar/TOC/breadcrumb/footer) at all \u2014 without it those surfaces inherit Tailwind preflight's hardcoded literal stack instead of the token. Defaults to `var(--font-sans)`. Redefine `--font-sans` (the usual theme-pack route) to restyle everything, or `--zdc-chrome-font` to move the shell independently of prose."
299
+ },
300
+ // ── Per-surface chrome font knobs (#2887 / epic #2886) ────────────────────
301
+ // Each lets a pack give ONE shell surface its own face (e.g. a display font in
302
+ // the header while nav and TOC stay on the body font) without ejecting
303
+ // anything. Each selector deliberately covers BOTH the desktop and the mobile
304
+ // emitter of its surface, so one override lands on both viewports. The mobile
305
+ // hooks (`data-zd-mobile-sidebar`, `data-zd-mobile-toc`) were added in #2887
306
+ // for exactly this — the mobile drawer and the mobile TOC previously had no
307
+ // stable anchor at all, and the mobile TOC does NOT reuse `nav[data-zd-toc]`
308
+ // (it emits its own `<div>` markup).
309
+ //
310
+ // ── Why the default is the chrome seam, NOT `inherit` ─────────────────────
311
+ // These three chain to `var(--zdc-chrome-font, var(--font-sans))` — the same
312
+ // value the `body` seam above resolves to — rather than to `inherit` like the
313
+ // content-surface `-font` tokens do. `inherit` is the WRONG default here
314
+ // because a surface's two emitters live in DIFFERENT parts of the DOM, so
315
+ // `inherit` resolves them against different ancestors and silently splits them:
316
+ // - The mobile drawer renders INSIDE `header[data-header]` (Header projects
317
+ // `SidebarToggle` into its `sidebarToggle` slot). Under `inherit`, a pack
318
+ // setting ONLY `--zdc-header-font` would leak that face into the drawer
319
+ // while `#desktop-sidebar` — which inherits from `body` — kept the body
320
+ // font. One "sidebar", two fonts.
321
+ // - The mobile TOC renders inside `.zd-content`, so under `inherit` it would
322
+ // follow `--zdc-doc-prose-font` while the desktop rail followed the chrome
323
+ // font. The TOC is navigation, not prose; it belongs on the chrome font.
324
+ // Anchoring to the seam makes each knob depend only on its own value and the
325
+ // chrome font — never on where the component happens to be mounted. Still a
326
+ // no-op by default: the resolved value is identical to what `inherit` produced
327
+ // when nothing is overridden (both land on `var(--font-sans)`).
328
+ {
329
+ cssVar: "--zdc-header-font",
330
+ selector: "header[data-header]",
331
+ property: "font-family",
332
+ default: "var(--zdc-chrome-font, var(--font-sans))",
333
+ component: "header",
334
+ surface: "chrome",
335
+ category: "typography",
336
+ description: "Font family of the site header. Defaults to the `--zdc-chrome-font` seam (a no-op until redefined); set in :root to give the header its own face, e.g. a display font for the wordmark and nav. Scoped to the header itself \u2014 it does not leak into the mobile sidebar drawer that the header hosts."
337
+ },
338
+ {
339
+ cssVar: "--zdc-sidebar-font",
340
+ selector: "#desktop-sidebar, aside[data-zd-mobile-sidebar]",
341
+ property: "font-family",
342
+ default: "var(--zdc-chrome-font, var(--font-sans))",
343
+ component: "sidebar",
344
+ surface: "chrome",
345
+ category: "typography",
346
+ description: "Font family of the navigation sidebar \u2014 covers BOTH the desktop `#desktop-sidebar` rail and the mobile drawer, so one override styles both viewports and neither drifts onto a different face. Defaults to the `--zdc-chrome-font` seam (a no-op until redefined)."
347
+ },
348
+ {
349
+ cssVar: "--zdc-toc-font",
350
+ selector: "nav[data-zd-toc], div[data-zd-mobile-toc]",
351
+ property: "font-family",
352
+ default: "var(--zdc-chrome-font, var(--font-sans))",
353
+ component: "toc",
354
+ surface: "chrome",
355
+ category: "typography",
356
+ description: "Font family of the table of contents \u2014 covers BOTH the desktop right rail and the mobile collapsible TOC, so one override styles both viewports. Defaults to the `--zdc-chrome-font` seam (a no-op until redefined), which also keeps the mobile TOC on the chrome font rather than the prose font of the `.zd-content` it renders inside."
258
357
  }
259
358
  ];
260
359
  export {
package/dist/features.css CHANGED
@@ -946,8 +946,15 @@ dialog.zd-mermaid-dialog::backdrop {
946
946
  *
947
947
  * Single source of truth: `src/config/component-tokens.ts` (tokens with
948
948
  * `surface: "chrome"`). The `gen-component-tokens` generator owns the BEGIN/END
949
- * block below — never hand-edit between the markers. Seeded empty: no chrome
950
- * tokens exist yet (S1, #2458 added only the routing mechanism).
949
+ * block below — never hand-edit between the markers.
950
+ *
951
+ * The `body { font-family }` rule below is the CHROME FONT SEAM (#2887): it is
952
+ * why a theme pack's `--font-sans` reaches the header/sidebar/TOC/footer at all.
953
+ * Those surfaces set no font-family of their own, and preflight's `html, :host`
954
+ * rule compiles to a hardcoded literal stack here (no `--default-font-family` is
955
+ * ever defined), so without this rule the token could only ever reach `.zd-content`
956
+ * prose. It works because this block is UNLAYERED and preflight is imported into
957
+ * `layer(zd-preflight)` — see component-tokens.ts for the full rationale.
951
958
  * ========================================================================== */
952
959
 
953
960
  /* BEGIN --zdc-* chrome component tokens (generated by gen-component-tokens; do not edit by hand) */
@@ -969,4 +976,20 @@ a[data-nav-active] {
969
976
  font-weight: var(--zdc-nav-active-weight, var(--font-weight-medium));
970
977
  }
971
978
 
979
+ body {
980
+ font-family: var(--zdc-chrome-font, var(--font-sans));
981
+ }
982
+
983
+ header[data-header] {
984
+ font-family: var(--zdc-header-font, var(--zdc-chrome-font, var(--font-sans)));
985
+ }
986
+
987
+ #desktop-sidebar, aside[data-zd-mobile-sidebar] {
988
+ font-family: var(--zdc-sidebar-font, var(--zdc-chrome-font, var(--font-sans)));
989
+ }
990
+
991
+ nav[data-zd-toc], div[data-zd-mobile-toc] {
992
+ font-family: var(--zdc-toc-font, var(--zdc-chrome-font, var(--font-sans)));
993
+ }
994
+
972
995
  /* END --zdc-* chrome component tokens */
@@ -40,6 +40,15 @@ export interface HtmlPreviewWrapperProps {
40
40
  height?: number;
41
41
  /** When true, the code section is expanded by default. */
42
42
  defaultOpen?: boolean;
43
+ /**
44
+ * Forwarded to `<HtmlPreview>`. When true, makes the preview document's
45
+ * `html`/`body` stretch to 100% height. Interacts with auto-height — pair
46
+ * with an explicit `height` prop. See `HtmlPreviewProps.fullHeight` for
47
+ * details.
48
+ *
49
+ * @default false
50
+ */
51
+ fullHeight?: boolean;
43
52
  /**
44
53
  * iframe `sandbox` attribute value, forwarded to `<HtmlPreview>`. Omit to
45
54
  * use the computed default (`allow-scripts allow-same-origin` when scripts
@@ -53,6 +62,37 @@ export interface HtmlPreviewWrapperProps {
53
62
  * `height`. See `HtmlPreviewProps.sandbox` for details.
54
63
  */
55
64
  sandbox?: string;
65
+ /**
66
+ * External stylesheet URLs, forwarded to `<HtmlPreview>` as-is. Per-usage
67
+ * only (v1) — deliberately NOT part of `HtmlPreviewGlobalConfig`, so there
68
+ * is no site-wide equivalent to merge. Loaded client-side at view time
69
+ * (a network dependency at render), not build-bundled. See
70
+ * `HtmlPreviewProps.externalStyles` for details.
71
+ */
72
+ externalStyles?: string[];
73
+ /**
74
+ * External script URLs, forwarded to `<HtmlPreview>` as-is. Presence
75
+ * flips the sandbox/`syncDelay` derivation to script-allowing exactly
76
+ * like `js`. Per-usage only (v1) — deliberately NOT part of
77
+ * `HtmlPreviewGlobalConfig`. See `HtmlPreviewProps.externalScripts` for
78
+ * details.
79
+ */
80
+ externalScripts?: string[];
81
+ /**
82
+ * Forwarded to `<HtmlPreview>`. When false, skips the injected preflight
83
+ * reset — useful when a framework loaded via `externalStyles`/
84
+ * `externalScripts` ships its own reset.
85
+ *
86
+ * @default true
87
+ */
88
+ preflight?: boolean;
89
+ /**
90
+ * Forwarded to `<HtmlPreview>`. When true, surfaces `externalStyles`/
91
+ * `externalScripts` as literal lines at the top of the "HTML" code panel.
92
+ *
93
+ * @default false
94
+ */
95
+ showResources?: boolean;
56
96
  }
57
97
  /**
58
98
  * Bare HTML preview body — the actual island **hydration target**.
@@ -12,7 +12,12 @@ function HtmlPreviewWrapperInner(props) {
12
12
  title,
13
13
  height,
14
14
  defaultOpen,
15
- sandbox
15
+ fullHeight,
16
+ sandbox,
17
+ externalStyles,
18
+ externalScripts,
19
+ preflight,
20
+ showResources
16
21
  } = props;
17
22
  const mergedHead = [globalConfig?.head, head].filter(Boolean).join("\n") || void 0;
18
23
  const mergedCss = [globalConfig?.css, css].filter(Boolean).join("\n") || void 0;
@@ -27,10 +32,15 @@ function HtmlPreviewWrapperInner(props) {
27
32
  title,
28
33
  height,
29
34
  defaultOpen,
35
+ fullHeight,
30
36
  sandbox,
31
37
  componentCss: css,
32
38
  componentHead: head,
33
- componentJs: js
39
+ componentJs: js,
40
+ externalStyles,
41
+ externalScripts,
42
+ preflight,
43
+ showResources
34
44
  }
35
45
  );
36
46
  }
@@ -9,6 +9,23 @@ export interface HtmlPreviewProps {
9
9
  title?: string;
10
10
  height?: number;
11
11
  defaultOpen?: boolean;
12
+ /**
13
+ * When true, injects `<style>html,body{height:100%}</style>` into the
14
+ * preview document so the preview's content can stretch to fill the
15
+ * iframe (e.g. a flex/grid layout that relies on `height: 100%` reaching
16
+ * the viewport).
17
+ *
18
+ * ⚠️ **Interacts with auto-height — pair with an explicit
19
+ * {@link HtmlPreviewProps.height}.** Auto-height measures
20
+ * `iframe.contentDocument.body.scrollHeight` and resizes the iframe to
21
+ * fit; `fullHeight` makes the body's height derive FROM the iframe's own
22
+ * height instead, which creates a feedback loop when the iframe height is
23
+ * itself derived from the body. This component does not attempt to
24
+ * detect or break that loop — always set `height` alongside `fullHeight`.
25
+ *
26
+ * @default false
27
+ */
28
+ fullHeight?: boolean;
12
29
  /**
13
30
  * iframe `sandbox` attribute value. When omitted, defaults to the value
14
31
  * computed from the preview content (`allow-scripts allow-same-origin`
@@ -36,8 +53,55 @@ export interface HtmlPreviewProps {
36
53
  componentHead?: string;
37
54
  /** Per-component js for code block display (before global merge) */
38
55
  componentJs?: string;
56
+ /**
57
+ * External stylesheet URLs, emitted as `<link rel="stylesheet" href="...">`
58
+ * tags in the srcdoc head — injected AFTER the preflight/fullHeight styles
59
+ * and BEFORE {@link HtmlPreviewProps.head}/{@link HtmlPreviewProps.css}, so
60
+ * author styles can still override them.
61
+ *
62
+ * Per-usage only (v1) — there is no site-wide `globalConfig` equivalent.
63
+ *
64
+ * ⚠️ Loaded **client-side at view time** (a network dependency at render),
65
+ * not build-bundled — the preview may show unstyled content until the
66
+ * stylesheet(s) finish loading.
67
+ *
68
+ * @default undefined
69
+ */
70
+ externalStyles?: string[];
71
+ /**
72
+ * External script URLs, emitted as `<script src="...">` tags in the
73
+ * srcdoc head. Presence flips the sandbox/`syncDelay` derivation to
74
+ * script-allowing exactly like an inline {@link HtmlPreviewProps.js} — see
75
+ * {@link containsScript} / {@link resolveSandbox}.
76
+ *
77
+ * Per-usage only (v1) — there is no site-wide `globalConfig` equivalent.
78
+ *
79
+ * ⚠️ Loaded **client-side at view time** (a network dependency at render),
80
+ * not build-bundled.
81
+ *
82
+ * @default undefined
83
+ */
84
+ externalScripts?: string[];
85
+ /**
86
+ * When false, skips the injected Tailwind-preflight `<style>` reset.
87
+ * Useful when a framework loaded via {@link HtmlPreviewProps.externalStyles}
88
+ * or {@link HtmlPreviewProps.externalScripts} ships its own reset and would
89
+ * otherwise be double-applied.
90
+ *
91
+ * @default true
92
+ */
93
+ preflight?: boolean;
94
+ /**
95
+ * When true, renders {@link HtmlPreviewProps.externalStyles} and
96
+ * {@link HtmlPreviewProps.externalScripts} as literal `<link>`/`<script
97
+ * src>` lines at the top of the "HTML" code panel. Excluded by default so
98
+ * CDN plumbing doesn't clutter the visible lesson code.
99
+ *
100
+ * @default false
101
+ */
102
+ showResources?: boolean;
39
103
  }
40
- export declare function containsScript(head?: string, js?: string): boolean;
104
+ export declare function containsScript(head?: string, js?: string, externalScripts?: string[]): boolean;
41
105
  /**
42
106
  * Resolve the iframe `sandbox` attribute value.
43
107
  *
@@ -57,6 +121,7 @@ export declare function containsScript(head?: string, js?: string): boolean;
57
121
  * trust note.
58
122
  */
59
123
  export declare function resolveSandbox(sandbox: string | undefined, hasScripts: boolean): string;
124
+ export declare function buildSrcdoc(html: string, css?: string, head?: string, js?: string, fullHeight?: boolean, externalStyles?: string[], externalScripts?: string[], preflight?: boolean): string;
60
125
  /**
61
126
  * HTML preview widget — renders an isolated iframe with viewport
62
127
  * controls and a collapsible code section.
@@ -69,4 +134,4 @@ export declare function resolveSandbox(sandbox: string | undefined, hasScripts:
69
134
  * Astro, or wire up the SSR-skip placeholder pattern for non-Astro
70
135
  * consumers.
71
136
  */
72
- export declare function HtmlPreview({ html, css, head, js, title, height, defaultOpen, sandbox, componentCss, componentHead, componentJs, }: HtmlPreviewProps): VNode;
137
+ export declare function HtmlPreview({ html, css, head, js, title, height, defaultOpen, fullHeight, sandbox, componentCss, componentHead, componentJs, externalStyles, externalScripts, preflight, showResources, }: HtmlPreviewProps): VNode;
@@ -3,21 +3,29 @@ import { useMemo } from "preact/hooks";
3
3
  import { PreviewBase } from "./preview-base.js";
4
4
  import { dedent } from "./dedent.js";
5
5
  import { preflightCss } from "./preflight.js";
6
- function containsScript(head, js) {
6
+ function containsScript(head, js, externalScripts) {
7
7
  if (js) return true;
8
8
  if (head && /<script/i.test(head)) return true;
9
+ if (externalScripts && externalScripts.length > 0) return true;
9
10
  return false;
10
11
  }
11
12
  function resolveSandbox(sandbox, hasScripts) {
12
13
  return sandbox ?? (hasScripts ? "allow-scripts allow-same-origin" : "allow-same-origin");
13
14
  }
14
- function buildSrcdoc(html, css, head, js) {
15
+ const fullHeightStyle = "<style>html,body{height:100%}</style>";
16
+ function buildSrcdoc(html, css, head, js, fullHeight, externalStyles, externalScripts, preflight) {
17
+ const includePreflight = preflight ?? true;
18
+ const externalStylesHtml = (externalStyles ?? []).map((href) => `<link rel="stylesheet" href="${href}">`).join("\n");
19
+ const externalScriptsHtml = (externalScripts ?? []).map((src) => `<script src="${src}"></script>`).join("\n");
15
20
  return `<!doctype html>
16
21
  <html>
17
22
  <head>
18
23
  <meta charset="utf-8">
19
24
  <meta name="viewport" content="width=device-width,initial-scale=1">
20
- <style>${preflightCss}</style>
25
+ ${includePreflight ? `<style>${preflightCss}</style>` : ""}
26
+ ${fullHeight ? fullHeightStyle : ""}
27
+ ${externalStylesHtml}
28
+ ${externalScriptsHtml}
21
29
  ${head ?? ""}
22
30
  ${css ? `<style>${css}</style>` : ""}
23
31
  </head>
@@ -34,21 +42,46 @@ function HtmlPreview({
34
42
  title,
35
43
  height,
36
44
  defaultOpen,
45
+ fullHeight,
37
46
  sandbox,
38
47
  componentCss,
39
48
  componentHead,
40
- componentJs
49
+ componentJs,
50
+ externalStyles,
51
+ externalScripts,
52
+ preflight,
53
+ showResources
41
54
  }) {
42
55
  const srcdoc = useMemo(
43
- () => buildSrcdoc(html, css, head, js),
44
- [html, css, head, js]
56
+ () => buildSrcdoc(
57
+ html,
58
+ css,
59
+ head,
60
+ js,
61
+ fullHeight,
62
+ externalStyles,
63
+ externalScripts,
64
+ preflight
65
+ ),
66
+ [html, css, head, js, fullHeight, externalStyles, externalScripts, preflight]
45
67
  );
46
- const hasScripts = containsScript(head, js);
68
+ const hasScripts = containsScript(head, js, externalScripts);
47
69
  const syncDelay = hasScripts ? 300 : 0;
48
70
  const sandboxValue = resolveSandbox(sandbox, hasScripts);
49
- const codeBlocks = useMemo(
50
- () => [
51
- { language: "html", title: "HTML", code: dedent(html) },
71
+ const codeBlocks = useMemo(() => {
72
+ const resourceLines = showResources ? [
73
+ ...(externalStyles ?? []).map(
74
+ (href) => `<link rel="stylesheet" href="${href}">`
75
+ ),
76
+ ...(externalScripts ?? []).map(
77
+ (src) => `<script src="${src}"></script>`
78
+ )
79
+ ] : [];
80
+ const htmlCode = resourceLines.length ? `${resourceLines.join("\n")}
81
+
82
+ ${dedent(html)}` : dedent(html);
83
+ return [
84
+ { language: "html", title: "HTML", code: htmlCode },
52
85
  ...componentCss ? [{ language: "css", title: "CSS", code: dedent(componentCss) }] : [],
53
86
  ...componentHead ? [{ language: "html", title: "Head", code: dedent(componentHead) }] : [],
54
87
  ...componentJs ? [
@@ -58,9 +91,16 @@ function HtmlPreview({
58
91
  code: dedent(componentJs)
59
92
  }
60
93
  ] : []
61
- ],
62
- [html, componentCss, componentHead, componentJs]
63
- );
94
+ ];
95
+ }, [
96
+ html,
97
+ componentCss,
98
+ componentHead,
99
+ componentJs,
100
+ showResources,
101
+ externalStyles,
102
+ externalScripts
103
+ ]);
64
104
  return /* @__PURE__ */ jsx(
65
105
  PreviewBase,
66
106
  {
@@ -76,6 +116,7 @@ function HtmlPreview({
76
116
  }
77
117
  export {
78
118
  HtmlPreview,
119
+ buildSrcdoc,
79
120
  containsScript,
80
121
  resolveSandbox
81
122
  };
@@ -71,6 +71,16 @@ export interface PostBuildContext {
71
71
  }
72
72
  /** Env var that opts a LOCAL build back into postBuild per-page JSON generation. */
73
73
  export declare const DOC_HISTORY_GEN_ENV = "GEN_DOC_HISTORY";
74
+ /**
75
+ * Env var that skips ONLY the postBuild per-page JSON step, independent of
76
+ * the preBuild Created/Updated/Author meta step (#2927). Unlike
77
+ * `SKIP_DOC_HISTORY=1` — which blanks both steps — this lets a shallow-clone
78
+ * CI variant keep real preBuild metadata while explicitly opting out of the
79
+ * heavier postBuild `git log --follow` chain. Deliberately does not contain
80
+ * the substring `SKIP_DOC_HISTORY`, so it is a distinct marker for
81
+ * `scripts/check-compatibility-contract.ts`'s literal survivor scan.
82
+ */
83
+ export declare const DOC_HISTORY_SKIP_POSTBUILD_ENV = "DOC_HISTORY_SKIP_POSTBUILD";
74
84
  /**
75
85
  * Decide whether the postBuild hook should generate per-page doc-history JSON.
76
86
  *
@@ -81,12 +91,16 @@ export declare const DOC_HISTORY_GEN_ENV = "GEN_DOC_HISTORY";
81
91
  * per content file, which on a large corpus exceeds zfb's 120s postBuild
82
92
  * lifecycle-hook budget (#1986). So the default flips to opt-in:
83
93
  *
84
- * - `SKIP_DOC_HISTORY=1` → never generate (highest priority; back-compat).
85
- * - `GEN_DOC_HISTORY=1` → always generate (explicit local opt-in).
86
- * - CI → generate (keeps the CI build-site artifact
87
- * byte-identical to before; D1's async generator
88
- * keeps it within budget).
89
- * - otherwise (local) skip (the #1986 fix).
94
+ * - `SKIP_DOC_HISTORY=1` → never generate (highest priority;
95
+ * back-compat; also blanks preBuild).
96
+ * - `DOC_HISTORY_SKIP_POSTBUILD=1` never generate (explicit skip that
97
+ * leaves preBuild untouched; #2927).
98
+ * - `GEN_DOC_HISTORY=1` → always generate (explicit local opt-in).
99
+ * - CI generate (keeps the CI build-site
100
+ * artifact byte-identical to before;
101
+ * the async generator keeps it within
102
+ * budget).
103
+ * - otherwise (local) → skip (the #1986 fix).
90
104
  *
91
105
  * This gates ONLY the postBuild per-page dropdown JSON. The preBuild meta step
92
106
  * (the visible Created/Updated/Author block) is unaffected — it keys off
@@ -104,7 +118,8 @@ export declare function shouldGeneratePostBuild(env?: NodeJS.ProcessEnv): {
104
118
  *
105
119
  * Generation is gated by `shouldGeneratePostBuild` (see its docs): skipped by
106
120
  * default on local builds (opt in with `GEN_DOC_HISTORY=1`), run in CI and
107
- * when explicitly opted in, and always suppressed by `SKIP_DOC_HISTORY=1`.
121
+ * when explicitly opted in, and always suppressed by `SKIP_DOC_HISTORY=1` or
122
+ * `DOC_HISTORY_SKIP_POSTBUILD=1`.
108
123
  *
109
124
  * The CLI is spawned as `node <cli> <args>` (shell: false) so option-derived
110
125
  * paths are never interpolated into a command line. Output is inherited so
@@ -44,10 +44,17 @@ function createDocHistoryDevMiddleware(options, logger) {
44
44
  };
45
45
  }
46
46
  const DOC_HISTORY_GEN_ENV = "GEN_DOC_HISTORY";
47
+ const DOC_HISTORY_SKIP_POSTBUILD_ENV = "DOC_HISTORY_SKIP_POSTBUILD";
47
48
  function shouldGeneratePostBuild(env = process.env) {
48
49
  if (env.SKIP_DOC_HISTORY === "1") {
49
50
  return { generate: false, reason: "SKIP_DOC_HISTORY=1" };
50
51
  }
52
+ if (env[DOC_HISTORY_SKIP_POSTBUILD_ENV] === "1") {
53
+ return {
54
+ generate: false,
55
+ reason: `${DOC_HISTORY_SKIP_POSTBUILD_ENV}=1`
56
+ };
57
+ }
51
58
  if (env[DOC_HISTORY_GEN_ENV] === "1") {
52
59
  return { generate: true, reason: `${DOC_HISTORY_GEN_ENV}=1` };
53
60
  }
@@ -148,6 +155,7 @@ export {
148
155
  DOC_HISTORY_GEN_ENV,
149
156
  DOC_HISTORY_OUTPUT_DIRNAME,
150
157
  DOC_HISTORY_ROUTE_PREFIX,
158
+ DOC_HISTORY_SKIP_POSTBUILD_ENV,
151
159
  buildGenerateCliArgs,
152
160
  createDocHistoryDevMiddleware,
153
161
  resolveDocHistoryGenerateBin,
package/dist/safelist.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /* generated by gen-safelist.mjs — do not edit by hand */
2
- @source inline("-link -mb-px -ml-hsp-sm -noscript -open -translate-x-full 2xl:w-[24px] [&::-webkit-details-marker]:hidden [&_a]:text-accent [&_a]:underline [&_nav]:mb-0 [data-admonition] [data-kbd-shortcut] [data-switcher-launcher] [doc-history-meta] [doc-history] [doc-layout] [llms-txt] [zudo-doc] a a2 abbr about above absent absolute accent accent- accent:accent- across activated active actual added admonition admonition- admonition-body admonition-title admonition/callout after after-breadcrumb after-content after-navigate after-sidebar after-title against agent agents ai-chat ai-chat-md ai-chat-trigger alert align-top all allow-same-origin allow-scripts allowed alone already already-executed already-multiline already-picked also always an anchor and and/or animate-pulse animate-spin announce ansehen antialiased any anywhere anzeigen appear application/json application/xml applied applies apply approach are area arg argument aria-atomic aria-busy aria-controls aria-current aria-disabled aria-expanded aria-haspopup aria-hidden aria-label aria-labelledby aria-live aria-orientation aria-pressed aria-selected aria-valuemax aria-valuemin aria-valuenow arm arms arrows article as asc aside aspect-[1200/630] aspect-square asset- assets assistant async at attach attribute attributes auf authored auto autogenerated available avoid await away b back backdrop:bg-bg/30 backdrop:bg-bg/80 backdrop:bg-overlay/60 backdrop:z-modal-backdrop background background-color backtick backticks baked banner bare base base- base64 base:base- based batch be bearbeiten because been before below best between bg bg-[#fff] bg-accent bg-bg bg-chat-assistant-bg bg-chat-user-bg bg-code-bg bg-fg bg-info/10 bg-info/5 bg-muted bg-overlay/30 bg-surface bg-surface/50 bg-transparent bg-warning/10 bg-warning/5 bi bigint bin binaries bind blank blanks block blockquote blocks blur body body-end-components body-end-scripts bold boolean bootstrap border border-accent border-b border-b-2 border-b-[5px] border-bg/30 border-collapse border-danger border-dashed border-fg border-image border-info/30 border-l border-l-0 border-l-[3px] border-left-width border-muted border-none border-r border-radius border-solid border-t border-t-[2px] border-t-[3px] border-transparent border-warning/30 border-width border-y both bottom-hsp-lg bottom-vsp-xl boundaries box-border br brackets brand breadcrumb:end breadcrumb:start break-words brown browser browsers btn budget bug build built built-in bundler but button buttons by bypassed byte-identical bytes cached call callable called caller calls can cancellation cannot canonical canvas caption card card-grid carry cases cat-nav- catch category catppuccin-latte caught caution center center/contain ch chains change changed changelog changelogs changes child choose chrome ci circle cite class class-less class-mode claude claude-agents claude-commands claude-md claude-skills cleaned clear clearing click client client-router client-side clip clobbering close closed closing code code-block-sr-announce code-group code-group-panel col col-resize colgroup collapses collision color color-scheme color-scheme-changed color-scheme-provider color-tweak colorization colors column comma command commands comment commercial commercial-font-denylist commit compare component component:github-link component:language-switcher component:search component:theme-toggle component:version-switcher composition compute computed concrete config configuration configure configured confuse connect const construction consumer consumes container containers containing contains content content-admonition content-layer content-link content-type content-wrapper:end content-wrapper:start contents context contract controller controls converts copied copy corners correct correctly corrupt could count covered covers cp crashes created cross-component crumb- cs css css-presence ctx cur current cursor-not-allowed cursor-pointer custom danger dark data data-active data-admonition data-base data-close-search data-current-locale data-default-locale data-doc-description data-doc-pager data-find-active data-find-match data-footer data-group-id data-header data-header-logo data-header-nav data-header-right data-kbd-shortcut data-language-switcher data-loading-index data-mermaid-enlarge-ready data-mermaid-rendered data-mermaid-src data-nav-active data-nav-item data-nav-item-dropdown data-nav-more data-nav-more-menu data-nav-more-toggle data-no-results data-open-search data-pan-active data-processed data-result-count-template data-search-count data-search-count-narrow data-search-dialog data-search-input data-search-placeholder data-search-results data-search-unavailable data-sidebar-hidden data-sidebar-resizer data-site-nav data-switcher-card data-switcher-launcher data-tab-btn data-tab-default data-tab-label data-tab-value data-tabs data-taglist-group data-testid data-theme data-theme-pack data-theme-pack-switcher data-theme/style data-trailing-slash data-variant data-version-banner data-version-latest data-version-menu data-version-rewire data-version-slug data-version-switcher data-version-toggle data-version-trigger-label data-zd-nosidebar data-zd-theme-pack-css data-zd-theme-pack-css-loading data-zd-toc data-zd-wide data-zfb-transition-persist dd decimal declaration declare declares decoration decoration-muted default default-transition-duration defaults del delegated delete dependency depth der desc description design design-token-panel design-token-trigger desktop desktop-sidebar desktop-sidebar-toggle desktop-sidebar-toggle-island destructive detach detached details determine deterministic develop dfn diagram diagrams dialog die dieser diff diff-line-added diff-line-content diff-line-empty diff-line-num diff-line-removed diff-row dir directly directories directory disabled disabled:opacity-50 disabled:pointer-events-none disc display:none dist distinct div dl do doc doc-card- doc-content-band doc-history doc-history-generate doc-history-panel doc-history-trigger doc-page doc-pager doc-prose doc-title docs docs- docs-v- document document-level documentation documented does dog double-registration draft drag draggable drop dropdown dropdown-child dropdown-parent dropdowns dt duplicate duration-150 duration-200 during dynamically e2e each ease-in-out edge einer eject ejectable ejectables ejected el element elements els else em embedded emit emitting empty empty/undefined en enable enabled end enlarged entire entities entries entry equal error escape escaped even eventually every exactly exceeds excerpt excludes exclusively existing exists exit expected export extends f factories failed fall fallback fallbacks falling falls false family fast feature fg field fields fieldset figcaption figure file fill fills finally find find-match find-match-active fire fires first fixed fixed-width fixtures flag flash flat flex flex-1 flex-col flex-wrap flip flipping flips flush-left focus focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 focus-visible:underline focus:border-accent focus:outline-none focus:underline font font-bold font-face-parity font-family font-medium font-mono font-sans font-scale font-semibold font-size font-weight font-weight-bold font-weight-medium font-weight-normal font-weight-semibold font/woff2 fonts footer footer- for form format found fox free fresh from frontmatter frontmatter-preview frozen fs-extra full fully function further g gap-[0.3em] gap-[clamp(1.5rem,3vw,4rem)] gap-hsp-2xs gap-hsp-lg gap-hsp-md gap-hsp-sm gap-hsp-xl gap-hsp-xs gap-vsp-2xs gap-vsp-3xs gap-vsp-lg gap-vsp-md gap-vsp-xs gap-x-hsp-2xs gap-x-hsp-lg gap-x-hsp-md gap-x-hsp-sm gap-x-hsp-xs gap-y-vsp-2xs gap-y-vsp-lg gap-y-vsp-md gap-y-vsp-xs gaps gate geladen2026 generate generated generation genuine geometry get github github-dark github-link go got gradient graph gray-matter grid grid-cols-1 grid-cols-2 group group-focus-visible:text-accent group-focus-visible:underline group-focus-within:block group-hover:bg-fg group-hover:block group-hover:text-accent group-hover:text-bg group-hover:underline group-open:rotate-90 guard h-10 h-40 h-[0.5rem] h-[0.625rem] h-[0.875rem] h-[1.125rem] h-[1.25rem] h-[1.575rem] h-[14px] h-[1em] h-[1lh] h-[2rem] h-[3.5rem] h-[3rem] h-[90vh] h-[calc(100%-3rem)] h-[calc(100vh-3.5rem)] h-dvh h-full h-icon-lg h-icon-md h-icon-sm h-icon-xs h1 h1s h2 h22013h4 h2s h3 h4 h5 h6 half hand-copied hand-editable handle handled handler handlers has hash-link have head head-links head-scripts header header- header-call:end header-call:start header-right heading heading-h2 heading-h3 heading-h4 heading-rule headings height here hex hidden hierarchical highlight history hit home horizontal host hover:bg-[color-mix(in_srgb,var(--color-surface)_80%,var(--color-fg)_20%)] hover:bg-accent-hover hover:bg-accent/10 hover:bg-danger/10 hover:bg-surface hover:border-accent hover:border-accent-hover hover:border-fg hover:text-accent hover:text-accent-hover hover:text-fg hover:underline hr href hrefs hsp hsp-2xl hsp-2xs hsp-lg hsp-md hsp-sm hsp-xl hsp-xs html i i18n/theme. i2 i3 i4 icon icon-lg icon-md icon-sm icon-xs identical idle idx if iframe image image-enlarge image-overlay-inset image/png img implementation import important important-allowlist imports in inactive inbox includes incomplete index index2026 info inherit inherited initial initialised injected inline inline-block inline-flex inner input input-clear ins inserted-after-color-mode inserted-after-color-scheme inserted-first inset-0 inside install installation installed instance instanceof instead instructions intended intent internal interpolation into invalid inverse inversion invoke is issues it italic item item- items items-baseline items-center items-end items-start its itself javascript jumps justify-between justify-center justify-end justify-start katex kbd keep keeping keeps keyboard keyboard-shortcut keydown keys keystroke keyword keywords khroma known-token-names kopieren label landing language-switcher last:border-b-0 later latest launch layout lazy leading-normal leading-relaxed leading-snug leading-tight leaf- leak leaves left left-0 left:calc legend legitimate length letter-spacing lg lg:block lg:border lg:border-fg lg:border-solid lg:flex lg:flex-col lg:flex-row lg:gap-hsp-xl lg:grid-cols-3 lg:grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] lg:h-[90vh] lg:hidden lg:justify-start lg:m-auto lg:max-h-[90vh] lg:max-w-[52.5rem] lg:ml-[var(--zd-sidebar-w)] lg:pt-vsp-2xl lg:px-hsp-2xl lg:py-vsp-2xl lg:text-left lg:w-[90vw] lg:w-[clamp(16rem,25%,22rem)] li li2 library license light light/dark like likely line line-height line/statement linger link link- links list list-disc list-none listener lists literal literals lives llms llms-txt load loaded loading local local-1 local-2 local-3 locale locales log longer longest-match look lostpointercapture lower luminance m m-0 m-auto m21 m6 machinery main major make malformed malicious manually maps mark marks match matches matching math math-display math-inline max max-h-[85vh] max-h-[90vh] max-h-full max-h-none max-w-[64rem] max-w-[85%] max-w-[85vw] max-w-[90vw] max-w-[calc(100vw-2rem)] max-w-[clamp(50rem,75vw,90rem)] max-w-full max-w-none max-w-sm max-width may mb-0 mb-vsp-2xs mb-vsp-lg mb-vsp-md mb-vsp-sm mb-vsp-xl mb-vsp-xs measures measuring mechanism menu mermaid message messages meta meta-knob meta-schema metadata migration min-h-0 min-h-[60vh] min-h-[calc(100vh-3.5rem)] min-h-screen min-w-0 min-w-[10rem] min-w-[3rem] min-w-[8rem] minifier minor mirror mirrors missing mit ml-auto ml-hsp-2xl ml-hsp-lg ml-hsp-sm ml-hsp-xl mod modal modal-backdrop mode modify module monospace more most mounted mouseenter mouseleave mr-hsp-sm ms mt-0 mt-vsp-2xl mt-vsp-2xs mt-vsp-3xs mt-vsp-lg mt-vsp-md mt-vsp-xl must mutates mutation mutations muted mx-auto my-vsp-lg my-vsp-md n name named names native nav nav-active nav-card- nav/doc navigating navigation navigations near needs nested neutral new newly-swapped next nicht no no-color-scheme no-data-theme-selector no-enlarge no-op no-repeat no-underline noch node node:buffer node:fs node:fs/promises node:module node:path node:url nodes nofollow noindex non-empty non-light-dark non-literal non-persisted none noopener noreferrer normal noscript not notable note notes now null number numeric object object-contain observe observer occurred of off ofl-required og:description og:image og:image:alt og:image:height og:image:width og:title og:type og:url oklch ol old older on once one only onto opacity-60 open open/close option or original other others out outgoing outline-none over overflow overflow-auto overflow-hidden overflow-x-auto overflow-y-auto overflow-y:auto overrides overscroll-contain overwrite own owned p p-0 p-hsp-2xs p-hsp-lg p-hsp-md p-hsp-sm pack package package-owned packages packs padding page page-loading page-loading-overlay page-loading-spinner page-navigate-end page-title pages pages/. paint paint-and-read palette pan panel panels paren-balance-aware parent parse parsed parser part pass passed passes patch path paths pattern payload payload-budget pb-[50vh] pb-vsp-md pb-vsp-xl pb-vsp-xs per per-link permanently persisted pi pick picked picks picocolors pins pipelines pl-[1.25rem] pl-hsp-lg pl-hsp-sm pl-hsp-xl place placeholder placeholder:text-muted plain plural plus pnpm point pointer-events-none pointercancel pointerdown pointermove pointerup polite polygon polyline popover populates port position position:fixed pr-[4px] pr-hsp-lg pr-hsp-md pr-hsp-sm pr-hsp-xl pre pre-lowercased preact preact/compat preact/hooks preact/jsx-runtime preconnect prefix preload pres preserving preview preview-swatch-color previews2026 previously primary print produce produced produces production project project-owned project-root-relative properties property props prose provided proxy pt-[0.15rem] pt-[2px] pt-vsp-3xs pt-vsp-md pt-vsp-sm pt-vsp-xl pt-vsp-xs ptag- public purely px px-hsp-2xl px-hsp-2xs px-hsp-lg px-hsp-md px-hsp-sm px-hsp-xl px-hsp-xs py-0 py-[2px] py-[4px] py-[calc(var(--spacing-vsp-xs)+0.15rem)] py-hsp-2xs py-hsp-3xs py-hsp-sm py-hsp-xs py-vsp-2xs py-vsp-3xs py-vsp-lg py-vsp-md py-vsp-sm py-vsp-xl py-vsp-xs q query question quick r radius radius-full radius-lg ramp range raw re-encode/decode re-querying re-render re-renders re-run re-running re-selects reach reached reaches read reading real real-value received receives recorded recovers rect redefine redistribution ref- referenced references refetch refreshes regenerate regenerates regex registry reinit reinits rel relative release released reload relying rem remove removed render rendered renderer renderers renders reorder repaint repair repeated repeating replace replaced replacement replaces repopulate repository required requires reserved resize resize-x resolve resolved resolves responded response restore restores result result-click results results-area retry return returns revision revisions rewrite rewrites right right- right-0 right-hsp-lg ring-2 ring-accent risking ro robots role roles root rotate-180 rotate-90 round round-trip rounded rounded-[0.75rem] rounded-bl-[0.25rem] rounded-bl-[1rem] rounded-bl-lg rounded-br-[0.25rem] rounded-br-[1rem] rounded-full rounded-lg rounded-md rounded-t-[1rem] rounds route router routes routes-src running runs runtime s safe safely safer same same-locale samp sans sans-serif scale scanned scanning scheme scoped scoping scored script script- script-eval script-evaluation script-injection scripts scroll scrollbar scrolled scrollend search search-index section section- see sehen select select-none selection-bg selection-fg selector self self-hosted self-start semantic semibold semver sentinel separator serialised serialize server server-rendered set sets setting setup shadow-[0_1px_3px_color-mix(in_srgb,var(--color-fg)_8%,transparent)] shadow-lg shadow-md shape share shared sharing shiki ship shipped ships short shortcut should show shown shrink-0 sidebar sidebar- sidebar-toggle-island sidebar-tree-island sidebar-w signal similarity simple single single-line single-object-literal singular site site-search site-tree-nav-island sitemap- sites size skill skills skipping skips slash slug slug-dir-parity sm:border sm:border-muted sm:flex-row sm:grid-cols-2 sm:h-auto sm:items-center sm:justify-between sm:max-h-[80vh] sm:max-w-[52rem] sm:mx-auto sm:my-[10vh] sm:rounded-lg small smooth snapshot snapshots so soft soft-nav solid some somehow source sources space-y-vsp-2xs spacing spacing-0 spacing-px span spans spec specifiers splitter spread square sr-only src stable stale standalone start state state- state:state- status stay staying sticky still stock stop stored stray strict string strings strip stripe stroke-linecap stroke-linejoin stroke-width strong stronger style style-attribute styled styles stylesheet sub subagents subsequent substitute success successful summary sup supported surface surfaces survives svg swap swapped swaps switcher synchronous synchronously syntactically syntax t tab tab-item tab-panel tabindex table tablist tabpanel tabs tabs-container tabs-content tabs-nav tag tag- tag-item- tagged tags tags:audit take tbody td temp-element template temporary temporary-element terminal terms test-results tested text text-accent text-bg text-body text-caption text-center text-chat-assistant-text text-chat-user-text text-code-fg text-danger text-decoration text-display text-fg text-fg/60 text-heading text-info text-left text-micro text-muted text-muted/50 text-right text-scale-2xl text-scale-2xs text-scale-lg text-scale-md text-scale-sm text-scale-xl text-scale-xs text-small text-title text-warning text/css text/plain textarea tfoot th than that the thead their them theme theme-color theme-pack theme-pack-changed theme-packs theme-packs/index.json theme-toggle theme/token then there these they this threw through throw tighten time tip title to toast toc toggle toggle-ai-chat toggle-design-token-panel toggles token tokens tolerates too toolbar tooltip top-0 top-[3.5rem] top-full top-level total touches tp tr tracked tracking-wide tracking-wider trade-off transition transition-[background,color,border-color] transition-[left,color] transition-colors transition-transform translate-x-0 translated translations transparent treats tree tree-child- tree-item- tree-top- trigger trigger:ai-chat trigger:design-token-panel triggers true truncate truncated try turn twitter:card twitter:creator twitter:description twitter:image twitter:site twitter:title two type typeface typography u ul umschalten unavailable unbalanced unchanged und undefined under underline underlines understand unit-tested unknown unlisted unmaintained unobserve unreadable unrelated unreleased unset unterminated until up up-to-date uppercase use used user uses utf-8 utf8 utilities utility v v2 val value value-reader values var variable variant verbatim version version- version-menu version-switcher versions vertical via video viewing viewport virtual:zudo-doc-chrome-bindings virtual:zudo-doc-design-token-panel-config virtual:zudo-doc-route-context visible vitesse-dark vocabulary von vsp vsp-2xl vsp-2xs vsp-3xs vsp-lg vsp-md vsp-sm vsp-xl vsp-xs w w-1/2 w-10 w-48 w-72 w-[0.5rem] w-[0.625rem] w-[0.875rem] w-[1.125rem] w-[1.575rem] w-[1.5rem] w-[1.75rem] w-[14px] w-[16px] w-[16rem] w-[18px] w-[1em] w-[280px] w-[2rem] w-[320px] w-[90vw] w-[calc(100vw-2rem)] w-[var(--zd-sidebar-w)] w-dvw w-full w-icon-lg w-icon-md w-icon-sm w-icon-xs want warn warning was watching wbr wbr- we website weight went were what when where whereas whether which while whitespace-nowrap whitespace-pre whole wide wide-gamut width will wird wired with without word working worktrees would wrap wrapper wrappers written wrong wrote wurde xl:flex xl:hidden y-scrollbar yet you your z-dropdown z-local-1 z-modal z-modal-backdrop z-popover z-sidebar z-toolbar zd-content zd-desktop-sidebar-toggle zd-doc-content-band zd-enlarge-btn zd-enlarge-dialog zd-enlarge-dialog-close zd-enlargeable zd-html-preview-code zd-mermaid-dialog zd-mermaid-enlargeable zd-mermaid-tool-btn zd-mermaid-toolbar zd-mermaid-transform zd-mermaid-viewport zd-sidebar-content-wrapper zd-sidebar-open zd-theme-pack-dialog-title zfb zfb:after-swap zfb:before-preparation zod zoom zudo-design-tokens/v3 zudo-doc zudo-doc-design-token-panel-modal zudo-doc-design-tokens zudo-doc-sidebar-visible zudo-doc-sidebar-width zudo-doc-theme zudo-doc-theme-pack zudo-doc-tweak zum");
2
+ @source inline("-link -mb-px -ml-hsp-sm -noscript -open -translate-x-full 2xl:w-[24px] [&::-webkit-details-marker]:hidden [&_a]:text-accent [&_a]:underline [&_nav]:mb-0 [data-admonition] [data-kbd-shortcut] [data-switcher-launcher] [doc-history-meta] [doc-history] [doc-layout] [llms-txt] [zudo-doc] a a2 abbr about above absent absolute accent accent- accent:accent- across activated active actual added admonition admonition- admonition-body admonition-title admonition/callout after after-breadcrumb after-content after-navigate after-sidebar after-title against agent agents ai-chat ai-chat-md ai-chat-trigger alert align-top all allow-same-origin allow-scripts allowed alone already already-executed already-multiline already-picked also always an anchor and and/or animate-pulse animate-spin announce ansehen antialiased any anywhere anzeigen app appear application/json application/xml applied applies apply approach are area arg argument aria-atomic aria-busy aria-controls aria-current aria-disabled aria-expanded aria-haspopup aria-hidden aria-label aria-labelledby aria-live aria-orientation aria-pressed aria-selected aria-valuemax aria-valuemin aria-valuenow arm arms arrows article as asc aside aspect-[1200/630] aspect-square asset- assets assistant async at attach attribute attributes auf authored auto autogenerated available avoid await away b back backdrop:bg-bg/30 backdrop:bg-bg/80 backdrop:bg-overlay/60 backdrop:z-modal-backdrop background background-color backtick backticks baked banner bare base base- base64 base:base- based batch be bearbeiten because been before below best between bg bg-[#fff] bg-accent bg-bg bg-chat-assistant-bg bg-chat-user-bg bg-code-bg bg-fg bg-info/10 bg-info/5 bg-muted bg-overlay/30 bg-surface bg-surface/50 bg-transparent bg-warning/10 bg-warning/5 bi bigint bin binaries bind blank blanks block blockquote blocks blur body body-end-components body-end-scripts bold boolean bootstrap border border-accent border-b border-b-2 border-b-[5px] border-bg/30 border-collapse border-danger border-dashed border-fg border-image border-info/30 border-l border-l-0 border-l-[3px] border-left-width border-muted border-none border-r border-radius border-solid border-t border-t-[2px] border-t-[3px] border-transparent border-warning/30 border-width border-y both bottom-hsp-lg bottom-vsp-xl boundaries box-border br brackets brand breadcrumb:end breadcrumb:start break-words brown browser browsers btn budget bug build built built-in bundler but button buttons by bypassed byte-identical bytes cached call callable called caller calls can cancellation cannot canonical canvas caption card card-grid carry cases cat-nav- catch category catppuccin-latte caught caution center center/contain ch chains change changed changelog changelogs changes child choose chrome ci circle cite class class-less class-mode claude claude-agents claude-commands claude-md claude-skills cleaned clear clearing click client client-router client-side clip clobbering close closed closing code code-block-sr-announce code-group code-group-panel col col-resize colgroup collapses collapsible collision color color-scheme color-scheme-changed color-scheme-provider color-tweak colorization colors column comma command commands comment commercial commercial-font-denylist commit compare component component:github-link component:language-switcher component:search component:theme-toggle component:version-switcher composition compute computed concrete config configuration configure configured confuse connect const construction consumer consumes container containers containing contains content content-admonition content-layer content-link content-type content-wrapper:end content-wrapper:start contents context contract controller controls converts copied copy corners correct correctly corrupt could count covered covers cp crashes created cross-component crumb- cs css css-presence ctx cur current cursor-not-allowed cursor-pointer custom danger dark data data-active data-admonition data-base data-close-search data-current-locale data-default-locale data-doc-description data-doc-pager data-find-active data-find-match data-footer data-group-id data-header data-header-logo data-header-nav data-header-right data-kbd-shortcut data-language-switcher data-loading-index data-mermaid-enlarge-ready data-mermaid-rendered data-mermaid-src data-nav-active data-nav-item data-nav-item-dropdown data-nav-more data-nav-more-menu data-nav-more-toggle data-no-results data-open-search data-pan-active data-processed data-result-count-template data-search-count data-search-count-narrow data-search-dialog data-search-input data-search-placeholder data-search-results data-search-unavailable data-sidebar-hidden data-sidebar-resizer data-site-nav data-switcher-card data-switcher-launcher data-tab-btn data-tab-default data-tab-label data-tab-value data-tabs data-taglist-group data-testid data-theme data-theme-pack data-theme-pack-switcher data-theme/style data-trailing-slash data-variant data-version-banner data-version-latest data-version-menu data-version-rewire data-version-slug data-version-switcher data-version-toggle data-version-trigger-label data-zd-mobile-sidebar data-zd-mobile-toc data-zd-nosidebar data-zd-theme-pack-css data-zd-theme-pack-css-loading data-zd-toc data-zd-wide data-zfb-transition-persist dd decimal declaration declare declares decoration decoration-muted default default-transition-duration defaults del delegated delete dependency depth der desc description design design-token-panel design-token-trigger desktop desktop-sidebar desktop-sidebar-toggle desktop-sidebar-toggle-island destructive detach detached details determine deterministic develop dfn diagram diagrams dialog die dieser diff diff-line-added diff-line-content diff-line-empty diff-line-num diff-line-removed diff-row different dir directly directories directory disabled disabled:opacity-50 disabled:pointer-events-none disc display display:none dist distinct div dl do doc doc-card- doc-content-band doc-history doc-history-generate doc-history-panel doc-history-trigger doc-page doc-pager doc-prose doc-title docs docs- docs-v- document document-level documentation documented does dog double-registration draft drag draggable drawer drifts drop dropdown dropdown-child dropdown-parent dropdowns dt duplicate duration-150 duration-200 during dynamically e2e each ease-in-out edge einer eject ejectable ejectables ejected el element elements els else em embedded emit emitting empty empty/undefined en enable enabled end enlarged entire entities entries entry equal error escape escaped even eventually every exactly exceeds excerpt excludes exclusively existing exists exit expected export extends f factories failed fall fallback fallbacks falling falls false family fast feature fg field fields fieldset figcaption figure file fill fills finally find find-match find-match-active fire fires first fixed fixed-width fixtures flag flash flat flex flex-1 flex-col flex-wrap flip flipping flips flush-left focus focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 focus-visible:underline focus:border-accent focus:outline-none focus:underline font font-bold font-face-parity font-family font-medium font-mono font-sans font-scale font-semibold font-size font-weight font-weight-bold font-weight-medium font-weight-normal font-weight-semibold font/woff2 fonts footer footer- for form format found fox free fresh from frontmatter frontmatter-preview frozen fs-extra full fully function further g gap-[0.3em] gap-[clamp(1.5rem,3vw,4rem)] gap-hsp-2xs gap-hsp-lg gap-hsp-md gap-hsp-sm gap-hsp-xl gap-hsp-xs gap-vsp-2xs gap-vsp-3xs gap-vsp-lg gap-vsp-md gap-vsp-xs gap-x-hsp-2xs gap-x-hsp-lg gap-x-hsp-md gap-x-hsp-sm gap-x-hsp-xs gap-y-vsp-2xs gap-y-vsp-lg gap-y-vsp-md gap-y-vsp-xs gaps gate geladen2026 generate generated generation genuine geometry get github github-dark github-link give go got gradient graph gray-matter grid grid-cols-1 grid-cols-2 group group-focus-visible:text-accent group-focus-visible:underline group-focus-within:block group-hover:bg-fg group-hover:block group-hover:text-accent group-hover:text-bg group-hover:underline group-open:rotate-90 guard h-10 h-40 h-[0.5rem] h-[0.625rem] h-[0.875rem] h-[1.125rem] h-[1.25rem] h-[1.575rem] h-[14px] h-[1em] h-[1lh] h-[2rem] h-[3.5rem] h-[3rem] h-[90vh] h-[calc(100%-3rem)] h-[calc(100vh-3.5rem)] h-dvh h-full h-icon-lg h-icon-md h-icon-sm h-icon-xs h1 h1s h2 h22013h4 h2s h3 h4 h5 h6 half hand-copied hand-editable handle handled handler handlers hardcoded has hash-link have head head-links head-scripts header header- header-call:end header-call:start header-right heading heading-h2 heading-h3 heading-h4 heading-rule headings height here hex hidden hierarchical highlight history hit home horizontal host hover:bg-[color-mix(in_srgb,var(--color-surface)_80%,var(--color-fg)_20%)] hover:bg-accent-hover hover:bg-accent/10 hover:bg-danger/10 hover:bg-surface hover:border-accent hover:border-accent-hover hover:border-fg hover:text-accent hover:text-accent-hover hover:text-fg hover:underline hr href hrefs hsp hsp-2xl hsp-2xs hsp-lg hsp-md hsp-sm hsp-xl hsp-xs html i i18n/theme. i2 i3 i4 icon icon-lg icon-md icon-sm icon-xs identical idle idx if iframe image image-enlarge image-overlay-inset image/png img implementation import important important-allowlist imports in inactive inbox includes incomplete independently index index2026 info inherit inherited initial initialised injected inline inline-block inline-flex inner input input-clear ins inserted-after-color-mode inserted-after-color-scheme inserted-first inset-0 inside install installation installed instance instanceof instead instructions intended intent internal interpolation into invalid inverse inversion invoke is issues it italic item item- items items-baseline items-center items-end items-start its itself javascript jumps justify-between justify-center justify-end justify-start katex kbd keep keeping keeps keyboard keyboard-shortcut keydown keys keystroke keyword keywords khroma known-token-names kopieren label landing language-switcher last:border-b-0 later latest launch layout lazy leading-normal leading-relaxed leading-snug leading-tight leaf- leak leaves left left-0 left:calc legend legitimate length lets letter-spacing lg lg:block lg:border lg:border-fg lg:border-solid lg:flex lg:flex-col lg:flex-row lg:gap-hsp-xl lg:grid-cols-3 lg:grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] lg:h-[90vh] lg:hidden lg:justify-start lg:m-auto lg:max-h-[90vh] lg:max-w-[52.5rem] lg:ml-[var(--zd-sidebar-w)] lg:pt-vsp-2xl lg:px-hsp-2xl lg:py-vsp-2xl lg:text-left lg:w-[90vw] lg:w-[clamp(16rem,25%,22rem)] li li2 library license light light/dark like likely line line-height line/statement linger link link- links list list-disc list-none listener lists literal literals lives llms llms-txt load loaded loading local local-1 local-2 local-3 locale locales log longer longest-match look lostpointercapture lower luminance m m-0 m-auto m21 m6 machinery main major make malformed malicious manually maps mark marks match matches matching math math-display math-inline max max-h-[85vh] max-h-[90vh] max-h-full max-h-none max-w-[64rem] max-w-[85%] max-w-[85vw] max-w-[90vw] max-w-[calc(100vw-2rem)] max-w-[clamp(50rem,75vw,90rem)] max-w-full max-w-none max-w-sm max-width may mb-0 mb-vsp-2xs mb-vsp-lg mb-vsp-md mb-vsp-sm mb-vsp-xl mb-vsp-xs measures measuring mechanism menu mermaid message messages meta meta-knob meta-schema metadata migration min-h-0 min-h-[60vh] min-h-[calc(100vh-3.5rem)] min-h-screen min-w-0 min-w-[10rem] min-w-[3rem] min-w-[8rem] minifier minor mirror mirrors missing mit ml-auto ml-hsp-2xl ml-hsp-lg ml-hsp-sm ml-hsp-xl mobile mod modal modal-backdrop mode modify module monospace more most mounted mouseenter mouseleave move mr-hsp-sm ms mt-0 mt-vsp-2xl mt-vsp-2xs mt-vsp-3xs mt-vsp-lg mt-vsp-md mt-vsp-xl must mutates mutation mutations muted mx-auto my-vsp-lg my-vsp-md n name named names native nav nav-active nav-card- nav/doc navigating navigation navigations near needs neither nested neutral new newly-swapped next nicht no no-color-scheme no-data-theme-selector no-enlarge no-op no-repeat no-underline noch node node:buffer node:fs node:fs/promises node:module node:path node:url nodes nofollow noindex non-empty non-light-dark non-literal non-persisted none noopener noreferrer normal noscript not notable note notes now null number numeric object object-contain observe observer occurred of off ofl-required og:description og:image og:image:alt og:image:height og:image:width og:title og:type og:url oklch ol old older on once one only onto opacity-60 open open/close option or original other others out outgoing outline-none over overflow overflow-auto overflow-hidden overflow-x-auto overflow-y-auto overflow-y:auto override overrides overscroll-contain overwrite own owned p p-0 p-hsp-2xs p-hsp-lg p-hsp-md p-hsp-sm pack package package-owned packages packs padding page page-loading page-loading-overlay page-loading-spinner page-navigate-end page-title pages pages/. paint paint-and-read palette pan panel panels paren-balance-aware parent parse parsed parser parses part pass passed passes patch path paths pattern payload payload-budget pb-[50vh] pb-vsp-md pb-vsp-xl pb-vsp-xs per per-link permanently persisted pi pick picked picks picocolors pins pipelines pl-[1.25rem] pl-hsp-lg pl-hsp-sm pl-hsp-xl place placeholder placeholder:text-muted plain plural plus pnpm point pointer-events-none pointercancel pointerdown pointermove pointerup polite polygon polyline popover populates port position position:fixed pr-[4px] pr-hsp-lg pr-hsp-md pr-hsp-sm pr-hsp-xl pre pre-lowercased preact preact/compat preact/hooks preact/jsx-runtime preconnect prefix preload pres preserving preview preview-swatch-color previews2026 previously primary print produce produced produces producing production project project-owned project-root-relative properties property props prose provided proxy pt-[0.15rem] pt-[2px] pt-vsp-3xs pt-vsp-md pt-vsp-sm pt-vsp-xl pt-vsp-xs ptag- public purely px px-hsp-2xl px-hsp-2xs px-hsp-lg px-hsp-md px-hsp-sm px-hsp-xl px-hsp-xs py-0 py-[2px] py-[4px] py-[calc(var(--spacing-vsp-xs)+0.15rem)] py-hsp-2xs py-hsp-3xs py-hsp-sm py-hsp-xs py-vsp-2xs py-vsp-3xs py-vsp-lg py-vsp-md py-vsp-sm py-vsp-xl py-vsp-xs q query question quick r radius radius-full radius-lg rail ramp range rather raw re-encode/decode re-querying re-render re-renders re-run re-running re-selects reach reached reaches read reading real real-value received receives recorded recovers rect redefine redistribution ref- referenced references refetch refreshes regenerate regenerates regex registry reinit reinits rel relative release released reload relying rem remove removed render rendered renderer renderers renders reorder repaint repair repeated repeating replace replaced replacement replaces repopulate repository required requires reserved resize resize-x resolve resolved resolves responded response restore restores restyle result result-click results results-area retry return returns revision revisions rewrite rewrites right right- right-0 right-hsp-lg ring-2 ring-accent risking ro robots role roles root rotate-180 rotate-90 round round-trip rounded rounded-[0.75rem] rounded-bl-[0.25rem] rounded-bl-[1rem] rounded-bl-lg rounded-br-[0.25rem] rounded-br-[1rem] rounded-full rounded-lg rounded-md rounded-t-[1rem] rounds route router routes routes-src running runs runtime s safe safely safer same same-locale samp sans sans-serif scale scanned scanning scheme scoped scoping scored script script- script-eval script-evaluation script-injection scripts scroll scrollbar scrolled scrollend seam search search-index section section- see sehen select select-none selection-bg selection-fg selector self self-hosted self-start semantic semibold semver sentinel separator serialised serialize server server-rendered set sets setting setup shadow-[0_1px_3px_color-mix(in_srgb,var(--color-fg)_8%,transparent)] shadow-lg shadow-md shape share shared sharing shell shiki ship shipped ships short shortcut should show shown shrink-0 sidebar sidebar- sidebar-toggle-island sidebar-tree-island sidebar-w signal similarity simple single single-line single-object-literal singular site site-search site-tree-nav-island sitemap- sites size skill skills skipping skips slash slug slug-dir-parity sm:border sm:border-muted sm:flex-row sm:grid-cols-2 sm:h-auto sm:items-center sm:justify-between sm:max-h-[80vh] sm:max-w-[52rem] sm:mx-auto sm:my-[10vh] sm:rounded-lg small smooth snapshot snapshots so soft soft-nav solid some somehow source sources space-y-vsp-2xs spacing spacing-0 spacing-px span spans spec specifiers splitter spread spurious square sr-only src stable stack stale standalone start state state- state:state- status stay staying sticky still stock stop stored stray strict string strings strip stripe strips stroke-linecap stroke-linejoin stroke-width strong stronger style style-attribute styled styles stylesheet sub subagents subsequent substitute success successful summary sup supported surface surfaces survives svg swap swapped swaps switcher synchronous synchronously syntactically syntax t tab tab-item tab-panel tabindex table tablist tabpanel tabs tabs-container tabs-content tabs-nav tag tag- tag-item- tagged tags tags:audit take tbody td temp-element template temporary temporary-element terminal terms test-results tested text text-accent text-bg text-body text-caption text-center text-chat-assistant-text text-chat-user-text text-code-fg text-danger text-decoration text-display text-fg text-fg/60 text-heading text-info text-left text-micro text-muted text-muted/50 text-right text-scale-2xl text-scale-2xs text-scale-lg text-scale-md text-scale-sm text-scale-xl text-scale-xs text-small text-title text-warning text/css text/plain textarea tfoot th than that the thead their them theme theme-color theme-pack theme-pack-changed theme-packs theme-packs/index.json theme-toggle theme/token then there these they this those threw through throw tighten time tip title to toast toc toggle toggle-ai-chat toggle-design-token-panel toggles token tokens tolerates too toolbar tooltip top-0 top-[3.5rem] top-full top-level total touches tp tr tracked tracking-wide tracking-wider trade-off trailing transition transition-[background,color,border-color] transition-[left,color] transition-colors transition-transform translate-x-0 translated translations transparent treats tree tree-child- tree-item- tree-top- trigger trigger:ai-chat trigger:design-token-panel triggers true truncate truncated try turn twitter:card twitter:creator twitter:description twitter:image twitter:site twitter:title two type typeface typography u ul umschalten unavailable unbalanced unchanged und undefined under underline underlines understand unit-tested unknown unlike unlisted unmaintained unobserve unreadable unrelated unreleased unset unterminated until up up-to-date uppercase use used user uses usual utf-8 utf8 utilities utility v v2 val value value-reader values var variable variant verbatim version version- version-menu version-switcher versions vertical via video viewing viewport viewports virtual:zudo-doc-chrome-bindings virtual:zudo-doc-design-token-panel-config virtual:zudo-doc-route-context visible vitesse-dark vocabulary von vsp vsp-2xl vsp-2xs vsp-3xs vsp-lg vsp-md vsp-sm vsp-xl vsp-xs w w-1/2 w-10 w-48 w-72 w-[0.5rem] w-[0.625rem] w-[0.875rem] w-[1.125rem] w-[1.575rem] w-[1.5rem] w-[1.75rem] w-[14px] w-[16px] w-[16rem] w-[18px] w-[1em] w-[280px] w-[2rem] w-[320px] w-[90vw] w-[calc(100vw-2rem)] w-[var(--zd-sidebar-w)] w-dvw w-full w-icon-lg w-icon-md w-icon-sm w-icon-xs want warn warning was watching wbr wbr- we website weight went were what when where whereas whether which while whitespace-nowrap whitespace-pre whole wide wide-gamut width will wird wired with without word wordmark working worktrees would wrap wrapper wrappers written wrong wrote wurde xl:flex xl:hidden y-scrollbar yet you your z-dropdown z-local-1 z-modal z-modal-backdrop z-popover z-sidebar z-toolbar zd-content zd-desktop-sidebar-toggle zd-doc-content-band zd-enlarge-btn zd-enlarge-dialog zd-enlarge-dialog-close zd-enlargeable zd-html-preview-code zd-mermaid-dialog zd-mermaid-enlargeable zd-mermaid-tool-btn zd-mermaid-toolbar zd-mermaid-transform zd-mermaid-viewport zd-sidebar-content-wrapper zd-sidebar-open zd-theme-pack-dialog-title zfb zfb:after-swap zfb:before-preparation zod zoom zudo-design-tokens/v3 zudo-doc zudo-doc-design-token-panel-modal zudo-doc-design-tokens zudo-doc-sidebar-visible zudo-doc-sidebar-width zudo-doc-theme zudo-doc-theme-pack zudo-doc-tweak zum");
@@ -95,6 +95,7 @@ function SidebarToggle({
95
95
  "aside",
96
96
  {
97
97
  inert: !open,
98
+ "data-zd-mobile-sidebar": true,
98
99
  className: `
99
100
  fixed top-[3.5rem] left-0 z-modal h-[calc(100vh-3.5rem)] w-[16rem] flex flex-col
100
101
  border-r border-muted bg-bg transition-transform duration-200
@@ -15,7 +15,7 @@ function MobileToc({
15
15
  if (filtered.length === 0) {
16
16
  return /* @__PURE__ */ jsx("div", { className: "hidden", "aria-hidden": "true", children: title });
17
17
  }
18
- return /* @__PURE__ */ jsxs("div", { className: "xl:hidden border border-muted mb-vsp-lg", children: [
18
+ return /* @__PURE__ */ jsxs("div", { "data-zd-mobile-toc": true, className: "xl:hidden border border-muted mb-vsp-lg", children: [
19
19
  /* @__PURE__ */ jsxs(
20
20
  "button",
21
21
  {
@@ -138,9 +138,15 @@ export function SidebarToggle({
138
138
  stay in the tab order and accessibility tree while off-screen
139
139
  (zudolab/zudo-doc#2059). `inert={false}` serialises to no attribute,
140
140
  so the SSR (open=false → `inert`) and initial client render stay
141
- byte-stable for hydration. */}
141
+ byte-stable for hydration.
142
+ `data-zd-mobile-sidebar` is the stable theme-pack hook for this
143
+ drawer — the mobile counterpart of the desktop `#desktop-sidebar`,
144
+ which has no id of its own here (zudolab/zudo-doc#2887). Unlike
145
+ `inert` it is UNCONDITIONAL, so it is hydration-stable by
146
+ construction: it does not vary with `open`. */}
142
147
  <aside
143
148
  inert={!open}
149
+ data-zd-mobile-sidebar
144
150
  className={`
145
151
  fixed top-[3.5rem] left-0 z-modal h-[calc(100vh-3.5rem)] w-[16rem] flex flex-col
146
152
  border-r border-muted bg-bg transition-transform duration-200
@@ -68,8 +68,16 @@ export function MobileToc({
68
68
  );
69
69
  }
70
70
 
71
+ // `data-zd-mobile-toc` is the stable theme-pack hook for this panel. It is
72
+ // deliberately NOT `data-zd-toc`: this component emits its own markup and
73
+ // never renders the desktop `nav[data-zd-toc]`, so packs need a separate
74
+ // anchor to reach the mobile TOC (zudolab/zudo-doc#2887). Unconditional, so
75
+ // it is hydration-stable by construction — it does not vary with `open`.
76
+ // The empty-headings early return above stays unhooked on purpose: that
77
+ // branch is a `display:none` placeholder carrying only the locale label, so
78
+ // there is nothing for a pack to style.
71
79
  return (
72
- <div className="xl:hidden border border-muted mb-vsp-lg">
80
+ <div data-zd-mobile-toc className="xl:hidden border border-muted mb-vsp-lg">
73
81
  <button
74
82
  type="button"
75
83
  onClick={() => setOpen((prev) => !prev)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takazudo/zudo-doc",
3
- "version": "4.0.0",
3
+ "version": "4.2.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",
@@ -589,9 +589,9 @@
589
589
  ],
590
590
  "peerDependencies": {
591
591
  "@takazudo/zdtp": "^0.4.9",
592
- "@takazudo/zfb": "^0.1.0-next.87",
593
- "@takazudo/zfb-md-wasm": "^0.1.0-next.87",
594
- "@takazudo/zfb-runtime": "^0.1.0-next.87",
592
+ "@takazudo/zfb": "^0.1.0-next.89",
593
+ "@takazudo/zfb-md-wasm": "^0.1.0-next.89",
594
+ "@takazudo/zfb-runtime": "^0.1.0-next.89",
595
595
  "@takazudo/zudo-doc-history-server": "^4.0.0",
596
596
  "diff": "^8.0.0",
597
597
  "katex": "^0.16.0",
@@ -630,9 +630,9 @@
630
630
  "tsx": "^4.21.0"
631
631
  },
632
632
  "devDependencies": {
633
- "@takazudo/zfb": "0.1.0-next.87",
634
- "@takazudo/zfb-md-wasm": "0.1.0-next.87",
635
- "@takazudo/zfb-runtime": "0.1.0-next.87",
633
+ "@takazudo/zfb": "0.1.0-next.89",
634
+ "@takazudo/zfb-md-wasm": "0.1.0-next.89",
635
+ "@takazudo/zfb-runtime": "0.1.0-next.89",
636
636
  "@types/fs-extra": "^11.0.4",
637
637
  "@types/minimist": "^1.2.5",
638
638
  "@types/node": "^25.3.5",
@@ -647,7 +647,7 @@
647
647
  "typescript": "^5.0.0",
648
648
  "vitest": "^4.1.0",
649
649
  "zod": "^4.3.6",
650
- "@takazudo/zudo-doc-history-server": "4.0.0"
650
+ "@takazudo/zudo-doc-history-server": "4.2.0"
651
651
  },
652
652
  "scripts": {
653
653
  "build": "tsup && tsc -p tsconfig.build.json",