create-zudo-doc 0.2.8 → 0.2.10

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/dist/compose.d.ts CHANGED
@@ -80,6 +80,12 @@ export declare function validateDependencies(features: FeatureDefinition[], allS
80
80
  * `p:` map entry) when imageEnlarge is enabled.
81
81
  * - `pages/lib/_body-end-islands.tsx` — tauri.ts injects the FindInPageInit
82
82
  * island (import, displayName, Island mount) when tauri is enabled.
83
+ * design-token-panel.ts injects the DesignTokenPanelBootstrap island
84
+ * (import, displayName, Island mount + toggle shim) when designTokenPanel
85
+ * is enabled.
86
+ * - `src/config/settings-types.ts` — design-token-panel.ts injects the
87
+ * `"design-token-panel"` member into `HeaderRightTriggerName` when the
88
+ * feature is enabled (replace-range between the :start/:end anchors).
83
89
  */
84
90
  export declare const ANCHOR_FILES: string[];
85
91
  /**
package/dist/compose.js CHANGED
@@ -166,11 +166,18 @@ export function validateDependencies(features, allSelectedNames) {
166
166
  * `p:` map entry) when imageEnlarge is enabled.
167
167
  * - `pages/lib/_body-end-islands.tsx` — tauri.ts injects the FindInPageInit
168
168
  * island (import, displayName, Island mount) when tauri is enabled.
169
+ * design-token-panel.ts injects the DesignTokenPanelBootstrap island
170
+ * (import, displayName, Island mount + toggle shim) when designTokenPanel
171
+ * is enabled.
172
+ * - `src/config/settings-types.ts` — design-token-panel.ts injects the
173
+ * `"design-token-panel"` member into `HeaderRightTriggerName` when the
174
+ * feature is enabled (replace-range between the :start/:end anchors).
169
175
  */
170
176
  export const ANCHOR_FILES = [
171
177
  "src/styles/global.css",
172
178
  "pages/_mdx-components.ts",
173
179
  "pages/lib/_body-end-islands.tsx",
180
+ "src/config/settings-types.ts",
174
181
  ];
175
182
  /**
176
183
  * Main composition entry point. Orchestrates the full feature composition
@@ -2,11 +2,13 @@ import type { FeatureModule } from "../compose.js";
2
2
  /**
3
3
  * Design-token-panel (zdtp) feature.
4
4
  *
5
- * Injects the zdtp CSS @import at `@slot:global-css:feature-styles` in
6
- * `src/styles/global.css`. The bootstrap is loaded by
7
- * `pages/lib/design-token-panel-bootstrap` as a side effect when the
8
- * feature is enabled, and the header trigger is rendered by
9
- * `pages/lib/_header-with-defaults.tsx` from the `settings.headerRightItems`
10
- * entry `{ type: "trigger", trigger: "design-token-panel" }`.
5
+ * Injects:
6
+ * 1. The zdtp CSS @import at `@slot:global-css:feature-styles` in
7
+ * `src/styles/global.css`.
8
+ * 2. The DesignTokenPanelBootstrap import, displayName, and Island mount
9
+ * (with pre-hydration toggle shim) into
10
+ * `pages/lib/_body-end-islands.tsx` via the three @slot: anchors
11
+ * (imports / display-names / extra-islands). Mirrors the tauri feature
12
+ * injection shape (zudolab/zudo-doc#2162).
11
13
  */
12
14
  export declare const designTokenPanelFeature: FeatureModule;
@@ -1,12 +1,38 @@
1
+ /**
2
+ * Pre-hydration toggle shim for the zdtp panel (inline, minified).
3
+ *
4
+ * Emitted as a <script dangerouslySetInnerHTML> alongside the
5
+ * DesignTokenPanelBootstrap Island so the header palette button is
6
+ * responsive before the island hydrates. Records the first click as a
7
+ * boolean flag and exposes window.__zdtpReadyClicks so the bootstrap
8
+ * module can drain and re-dispatch once the real zdtp listener is live.
9
+ * Guards against double-installation across SPA body swaps via
10
+ * __zdtpToggleShimInstalled. Mirrors the host's ZDTP_TOGGLE_SHIM_SRC
11
+ * constant (pages/lib/_body-end-islands.tsx, zudolab/zudo-doc#1627 Part B).
12
+ */
13
+ const ZDTP_TOGGLE_SHIM_SRC = `(function(){
14
+ if(window.__zdtpToggleShimInstalled)return;
15
+ window.__zdtpToggleShimInstalled=true;
16
+ var pending=false;
17
+ function shim(){pending=true;}
18
+ window.addEventListener('toggle-design-token-panel',shim);
19
+ window.__zdtpReadyClicks=function(){
20
+ window.removeEventListener('toggle-design-token-panel',shim);
21
+ delete window.__zdtpReadyClicks;
22
+ if(pending){pending=false;window.dispatchEvent(new CustomEvent('toggle-design-token-panel'));}
23
+ };
24
+ })();`;
1
25
  /**
2
26
  * Design-token-panel (zdtp) feature.
3
27
  *
4
- * Injects the zdtp CSS @import at `@slot:global-css:feature-styles` in
5
- * `src/styles/global.css`. The bootstrap is loaded by
6
- * `pages/lib/design-token-panel-bootstrap` as a side effect when the
7
- * feature is enabled, and the header trigger is rendered by
8
- * `pages/lib/_header-with-defaults.tsx` from the `settings.headerRightItems`
9
- * entry `{ type: "trigger", trigger: "design-token-panel" }`.
28
+ * Injects:
29
+ * 1. The zdtp CSS @import at `@slot:global-css:feature-styles` in
30
+ * `src/styles/global.css`.
31
+ * 2. The DesignTokenPanelBootstrap import, displayName, and Island mount
32
+ * (with pre-hydration toggle shim) into
33
+ * `pages/lib/_body-end-islands.tsx` via the three @slot: anchors
34
+ * (imports / display-names / extra-islands). Mirrors the tauri feature
35
+ * injection shape (zudolab/zudo-doc#2162).
10
36
  */
11
37
  export const designTokenPanelFeature = () => ({
12
38
  name: "designTokenPanel",
@@ -21,5 +47,54 @@ export const designTokenPanelFeature = () => ({
21
47
  anchor: "/* @slot:global-css:feature-styles */",
22
48
  content: `@import "@takazudo/zdtp/styles.css";`,
23
49
  },
50
+ // 1. Import the island entry. Inserted AFTER the
51
+ // `// @slot:body-end-islands:imports` anchor.
52
+ {
53
+ file: "pages/lib/_body-end-islands.tsx",
54
+ anchor: "// @slot:body-end-islands:imports",
55
+ position: "after",
56
+ content: `import DesignTokenPanelBootstrap from "@/components/design-token-panel-bootstrap";`,
57
+ },
58
+ // 2. Stable island marker name (belt-and-braces guard matching the
59
+ // sibling islands in the file). Inserted AFTER the
60
+ // `// @slot:body-end-islands:display-names` anchor.
61
+ {
62
+ file: "pages/lib/_body-end-islands.tsx",
63
+ anchor: "// @slot:body-end-islands:display-names",
64
+ position: "after",
65
+ content: `(DesignTokenPanelBootstrap as { displayName?: string }).displayName = "DesignTokenPanelBootstrap";`,
66
+ },
67
+ // 4. Extend HeaderRightTriggerName with "design-token-panel". Uses
68
+ // replace-range between the :start/:end anchors in settings-types.ts
69
+ // so the base template emits only "ai-chat" and this injection adds
70
+ // the zdtp trigger only when the feature is on (zudolab/zudo-doc#2162).
71
+ {
72
+ file: "src/config/settings-types.ts",
73
+ anchor: "// @slot:settings-types:trigger-names:start",
74
+ position: "replace",
75
+ content: `export type HeaderRightTriggerName = "design-token-panel" | "ai-chat";`,
76
+ },
77
+ // 3. Island mount + pre-hydration toggle shim. Inserted AFTER the
78
+ // `{/* @slot:body-end-islands:extra-islands */}` anchor.
79
+ // when="load" (not "idle"): the bootstrap module registers the
80
+ // `toggle-design-token-panel` window listener as a side effect of
81
+ // its import, so it must hydrate as early as possible (zudolab/zudo-doc#1623).
82
+ // The inline <script> is the pre-hydration shim (zudolab/zudo-doc#1627 Part B).
83
+ {
84
+ file: "pages/lib/_body-end-islands.tsx",
85
+ anchor: "{/* @slot:body-end-islands:extra-islands */}",
86
+ position: "after",
87
+ content: ` {/* zdtp panel bootstrap: hydrates on load so configurePanel() runs early
88
+ and the toggle-design-token-panel listener is live before the user
89
+ clicks the header trigger. The inline script is the pre-hydration
90
+ shim that queues the first click (zudolab/zudo-doc#1627 Part B). */}
91
+ <script
92
+ dangerouslySetInnerHTML={{ __html: ${JSON.stringify(ZDTP_TOGGLE_SHIM_SRC)} }}
93
+ />
94
+ {Island({
95
+ when: "load",
96
+ children: <DesignTokenPanelBootstrap />,
97
+ }) as unknown as VNode}`,
98
+ },
24
99
  ],
25
100
  });
@@ -12,7 +12,7 @@ import type { FeatureModule } from "../compose.js";
12
12
  * feature, the server-side figure/button emission is re-implemented via an
13
13
  * MDX paragraph (p) component override in pages/_mdx-components.ts. When
14
14
  * imageEnlarge is enabled, three injections into the template file install:
15
- * 1. Additional imports: toChildArray + VNode from preact, settings.
15
+ * 1. Additional imports: toChildArray + VNode from preact.
16
16
  * 2. ENLARGE_SVG const + EnlargeableParagraph function definition.
17
17
  * 3. `p: EnlargeableParagraph` entry in the createMdxComponents return map.
18
18
  * When imageEnlarge is OFF, none of these are injected, so the override is
@@ -11,7 +11,7 @@
11
11
  * feature, the server-side figure/button emission is re-implemented via an
12
12
  * MDX paragraph (p) component override in pages/_mdx-components.ts. When
13
13
  * imageEnlarge is enabled, three injections into the template file install:
14
- * 1. Additional imports: toChildArray + VNode from preact, settings.
14
+ * 1. Additional imports: toChildArray + VNode from preact.
15
15
  * 2. ENLARGE_SVG const + EnlargeableParagraph function definition.
16
16
  * 3. `p: EnlargeableParagraph` entry in the createMdxComponents return map.
17
17
  * When imageEnlarge is OFF, none of these are injected, so the override is
@@ -21,15 +21,17 @@
21
21
  export const imageEnlargeFeature = () => ({
22
22
  name: "imageEnlarge",
23
23
  injections: [
24
- // 1. Import additions: toChildArray + VNode from preact, settings.
24
+ // 1. Import additions: toChildArray + VNode from preact.
25
25
  // Inserted AFTER the `// @slot:mdx-components:enlarge-imports` anchor.
26
+ // NOTE: `settings` is NOT injected here — the base template already
27
+ // imports it (#2172); injecting it again caused a duplicate ES-module
28
+ // lexical binding.
26
29
  {
27
30
  file: "pages/_mdx-components.ts",
28
31
  anchor: "// @slot:mdx-components:enlarge-imports",
29
32
  position: "after",
30
33
  content: `import { toChildArray } from "preact";
31
- import type { VNode } from "preact";
32
- import { settings } from "@/config/settings";`,
34
+ import type { VNode } from "preact";`,
33
35
  },
34
36
  // 2. ENLARGE_SVG const + EnlargeableParagraph function.
35
37
  // Inserted AFTER the `// @slot:mdx-components:enlarge-defs` anchor
package/dist/scaffold.js CHANGED
@@ -178,7 +178,7 @@ export async function scaffold(choices) {
178
178
  await fs.outputFile(path.join(targetDir, "zfb.config.ts"), zfbConfigContent);
179
179
  const pkg = generatePackageJson(choices);
180
180
  await fs.outputFile(path.join(targetDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
181
- await fs.outputFile(path.join(targetDir, ".gitignore"), [
181
+ const gitignoreLines = [
182
182
  "# Build output",
183
183
  "node_modules",
184
184
  "dist",
@@ -201,7 +201,34 @@ export async function scaffold(choices) {
201
201
  "# Cloudflare Wrangler",
202
202
  ".wrangler/",
203
203
  "",
204
- ].join("\n"));
204
+ ];
205
+ // The doc-lookup skill is only generated when skillSymlinker is selected
206
+ // (the setup-doc-skill.sh script and `setup:doc-skill` npm script are gated
207
+ // on the same feature above), so only emit its ignore entries then —
208
+ // otherwise they would be dead rules that could silently hide an unrelated
209
+ // skill a user later installs under a matching name. The skill name is
210
+ // deterministic (always `<projectName>-wisdom`, matching DEFAULT_SKILL_NAME
211
+ // in scripts/setup-doc-skill.sh and the package name), so these entries match
212
+ // the directory the script creates. The docs-ja symlink only exists for i18n
213
+ // projects (the script creates it conditionally), so gate that line on i18n.
214
+ if (choices.features.includes("skillSymlinker")) {
215
+ gitignoreLines.push("# Generated doc-lookup skill", `.claude/skills/${choices.projectName}-wisdom/SKILL.md`, `.claude/skills/${choices.projectName}-wisdom/docs`);
216
+ if (choices.features.includes("i18n")) {
217
+ gitignoreLines.push(`.claude/skills/${choices.projectName}-wisdom/docs-ja`);
218
+ }
219
+ gitignoreLines.push("");
220
+ }
221
+ await fs.outputFile(path.join(targetDir, ".gitignore"), gitignoreLines.join("\n"));
222
+ // Emit an .npmrc exempting undici-types from pnpm's trust-downgrade policy.
223
+ // External pnpm supply-chain quirk (pnpm >= 10.21, https://github.com/pnpm/pnpm/issues/8889):
224
+ // when a consumer enables `trust-policy=no-downgrade` (off by default, but a
225
+ // common hardened posture), `pnpm install` aborts with ERR_PNPM_TRUST_DOWNGRADE
226
+ // on undici-types@6.21.0 — a transitive dep of @types/node@^22 whose earlier
227
+ // releases carried provenance attestation that 6.21.0 dropped. The package is a
228
+ // type-only stub and safe; this narrow exclusion lets a fresh scaffold install
229
+ // under a strict trust policy without disabling the guard wholesale. Pinned to
230
+ // the exact known-safe version so a future undici-types bump is re-reviewed.
231
+ await fs.outputFile(path.join(targetDir, ".npmrc"), "trust-policy-exclude[]=undici-types@6.21.0\n");
205
232
  const claudeContent = generateCLAUDEFile(choices);
206
233
  await fs.outputFile(path.join(targetDir, "CLAUDE.md"), claudeContent);
207
234
  // 4. Compose features (copy feature files + inject into shared files)
@@ -290,21 +317,40 @@ function generatePackageJson(choices) {
290
317
  // extraWatchPaths rebuilds, and TS-config-loader path canonicalization
291
318
  // (Takazudo/zudo-front-builder#1036–#1043). next.45: docs-only. next.46:
292
319
  // opt-in dev boot-lazy mode (#1057) + client-router timer lifecycle fixes —
293
- // dev-server-only. next.47 (current pin): dual light/dark syntect themes
294
- // (themeLight/themeDark on CodeHighlightConfig, --shiki-light/--shiki-dark,
295
- // #1067) plus stricter build-start validation that rejects unknown theme
296
- // names additive; a fresh scaffold sets no explicit codeHighlight.theme so
297
- // the default still applies. No consumer-facing / CLI breaking change.
298
- "@takazudo/zfb": "0.1.0-next.47",
299
- "@takazudo/zfb-runtime": "0.1.0-next.47",
320
+ // dev-server-only. next.47: dual light/dark syntect themes (themeLight/
321
+ // themeDark on CodeHighlightConfig, --shiki-light/--shiki-dark, #1067) plus
322
+ // stricter build-start validation that rejects unknown theme names. next.48:
323
+ // re-export @takazudo/zfb/config from the zfb-shim.d.ts type shim type-only
324
+ // fix, additive. next.49: client-router WebKit bfcache fix
325
+ // re-sync the history index + originalLocation on a bfcache restore so
326
+ // browser Back after an SPA navigation returns to the previous page instead
327
+ // of skipping an entry — runtime-only bug fix, additive. next.50 (current
328
+ // pin): client-router fix to commit the SPA history entry BEFORE the View
329
+ // Transition, so on WebKit/iOS a single browser Back after an SPA navigation
330
+ // creates a distinct history entry instead of falling off the site —
331
+ // runtime-only bug fix, additive. next.51 (current pin): additive public-API
332
+ // surface — VNode/VNodeArray/VNodeObject are now exported from
333
+ // "@takazudo/zfb" (#972) — plus removal of the no-op linkValidation.allowExternal
334
+ // knob (#925); both are non-breaking for a fresh scaffold. A fresh scaffold
335
+ // sets no explicit codeHighlight.theme so the default still applies. No
336
+ // consumer-facing / CLI breaking change.
337
+ "@takazudo/zfb": "0.1.0-next.51",
338
+ "@takazudo/zfb-runtime": "0.1.0-next.51",
300
339
  // zfb-adapter-cloudflare — required for any route with `prerender = false`.
301
340
  // Pinned in lockstep with @takazudo/zfb.
302
- "@takazudo/zfb-adapter-cloudflare": "0.1.0-next.47",
341
+ "@takazudo/zfb-adapter-cloudflare": "0.1.0-next.51",
303
342
  // @takazudo/zudo-doc — published from this monorepo via
304
343
  // .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
305
344
  // lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
306
345
  // version moves, so a fresh scaffold pulls the version we just published.
307
- "@takazudo/zudo-doc": "^0.2.8",
346
+ // RELEASE DEPENDENCY (zudolab/zudo-doc#2188): the base template's
347
+ // global.css now `@import`s `@takazudo/zudo-doc/content.css`, an export
348
+ // added alongside this pin. The pinned range MUST resolve to a PUBLISHED
349
+ // version that ships that export — published 0.2.9 does NOT. check-pin-parity
350
+ // ties this pin to packages/zudo-doc's version, so the lockstep release
351
+ // bumps both together; do not cut a create-zudo-doc release until the
352
+ // matching @takazudo/zudo-doc version (with content.css) is on npm.
353
+ "@takazudo/zudo-doc": "^0.2.10",
308
354
  // zod — used by the generated zfb.config.ts. zfb-config-gen emits
309
355
  // `import { z } from "zod"` for the content-collection schema +
310
356
  // `z.toJSONSchema(...)` conversion. Without this dep, the consumer
@@ -359,7 +405,7 @@ function generatePackageJson(choices) {
359
405
  // @takazudo/zudo-doc/integrations/doc-history which in turn imports
360
406
  // @takazudo/zudo-doc-history-server/git-history. Without this dep the
361
407
  // plugin host fails at init with ERR_MODULE_NOT_FOUND — W8A (#1739).
362
- deps["@takazudo/zudo-doc-history-server"] = "^0.2.8";
408
+ deps["@takazudo/zudo-doc-history-server"] = "^0.2.10";
363
409
  // W7A (#1736): doc-history-plugin.mjs spawns `tsx -e <inline-script>` to
364
410
  // run the v2 runtime in a TS-aware Node subprocess; without tsx the
365
411
  // plugin's preBuild step exits with ENOENT before zfb finishes config
@@ -259,10 +259,17 @@ export function generateSettingsFile(choices) {
259
259
  if (choices.headerRightItems !== undefined) {
260
260
  // User-supplied override (including empty array): emit each entry verbatim,
261
261
  // in the chosen order. An empty array means "no header-right items" — honor it.
262
- // filterHeaderRightItems (src/utils/header-right-items.ts) handles runtime
263
- // hiding of items whose feature is disabledno need to gate emission
264
- // here on choices.features.
262
+ // DEFENSIVE STRIP: drop any "design-token-panel" trigger when the
263
+ // designTokenPanel feature is offthe type "design-token-panel" is absent
264
+ // from HeaderRightTriggerName in a feature-off scaffold (gated via
265
+ // @slot:settings-types:trigger-names), so emitting it would cause a TS error
266
+ // (zudolab/zudo-doc#2162).
265
267
  for (const item of choices.headerRightItems) {
268
+ if (item.type === "trigger" &&
269
+ item.trigger === "design-token-panel" &&
270
+ !choices.features.includes("designTokenPanel")) {
271
+ continue;
272
+ }
266
273
  if (item.type === "trigger") {
267
274
  lines.push(` { type: "trigger", trigger: ${JSON.stringify(item.trigger)} },`);
268
275
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Create a new zudo-doc documentation site",
5
5
  "license": "MIT",
6
6
  "author": "Takeshi Takatsudo",
@@ -34,8 +34,8 @@ import { settings } from "@/config/settings";
34
34
 
35
35
  import AiChatModal from "@/components/ai-chat-modal";
36
36
  import ClientRouterBootstrap from "@/components/client-router-bootstrap";
37
- import DesignTokenPanelBootstrap from "@/components/design-token-panel-bootstrap";
38
37
  import ImageEnlarge, { ImageEnlargeSsrFallback } from "@/components/image-enlarge";
38
+ import MermaidEnlarge, { MermaidEnlargeSsrFallback } from "@/components/mermaid-enlarge";
39
39
  import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";
40
40
  // @slot:body-end-islands:imports
41
41
 
@@ -49,9 +49,8 @@ import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";
49
49
  (AiChatModal as { displayName?: string }).displayName = "AiChatModal";
50
50
  (ClientRouterBootstrap as { displayName?: string }).displayName =
51
51
  "ClientRouterBootstrap";
52
- (DesignTokenPanelBootstrap as { displayName?: string }).displayName =
53
- "DesignTokenPanelBootstrap";
54
52
  (ImageEnlarge as { displayName?: string }).displayName = "ImageEnlarge";
53
+ (MermaidEnlarge as { displayName?: string }).displayName = "MermaidEnlarge";
55
54
  // @slot:body-end-islands:display-names
56
55
 
57
56
  /**
@@ -64,37 +63,6 @@ import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";
64
63
  */
65
64
  const DEFAULT_AI_CHAT_BODY_LABEL = "Ask a question about the documentation.";
66
65
 
67
- /**
68
- * SSR-emitted inline script that acts as a pre-hydration shim for the
69
- * `toggle-design-token-panel` window event. Because the
70
- * DesignTokenPanelBootstrap Island is deferred, zdtp's real
71
- * `toggle-design-token-panel` listener (registered in index.tsx at
72
- * module init) is not yet installed when the user clicks the header
73
- * palette button. This shim:
74
- *
75
- * 1. Records the first (and only meaningful) click as a boolean flag.
76
- * 2. Exposes `window.__zdtpReadyClicks` so the bootstrap Island can
77
- * drain the queue and re-dispatch a single event once the real
78
- * listener is live.
79
- * 3. Guards against double-installation across any re-evaluation path
80
- * (SPA body swap, HMR, etc.) via `__zdtpToggleShimInstalled`.
81
- *
82
- * A single boolean (not an array) is used because the panel is a toggle —
83
- * any number of pre-hydration clicks should result in at most one open.
84
- */
85
- const ZDTP_TOGGLE_SHIM_SRC = `(function(){
86
- if(window.__zdtpToggleShimInstalled)return;
87
- window.__zdtpToggleShimInstalled=true;
88
- var pending=false;
89
- function shim(){pending=true;}
90
- window.addEventListener('toggle-design-token-panel',shim);
91
- window.__zdtpReadyClicks=function(){
92
- window.removeEventListener('toggle-design-token-panel',shim);
93
- delete window.__zdtpReadyClicks;
94
- if(pending){pending=false;window.dispatchEvent(new CustomEvent('toggle-design-token-panel'));}
95
- };
96
- })();`;
97
-
98
66
  /** Props for {@link BodyEndIslands}. */
99
67
  export interface BodyEndIslandsProps {
100
68
  /** Base path the AI chat modal uses to construct API URLs. */
@@ -109,15 +77,18 @@ export interface BodyEndIslandsProps {
109
77
  }
110
78
 
111
79
  /**
112
- * The default body-end islands a doc page may mount: the design-token
113
- * tweak panel (overlay, fixed-position), the AI chat modal (`<dialog>`
114
- * overlay), and the image-enlarge dialog (mounted lazily based on
115
- * viewport scan). Each is feature-gated — the design-token panel on
116
- * `settings.designTokenPanel`, the AI chat modal (and its sr-only
117
- * landmark heading) on `settings.aiAssistant`, and image-enlarge on
118
- * `settings.imageEnlarge` — so a feature-off consumer ships neither the
80
+ * The default body-end islands a doc page may mount: the AI chat modal
81
+ * (`<dialog>` overlay) and the image-enlarge dialog (mounted lazily based
82
+ * on viewport scan). Each is feature-gated the AI chat modal (and its
83
+ * sr-only landmark heading) on `settings.aiAssistant`, and image-enlarge
84
+ * on `settings.imageEnlarge` so a feature-off consumer ships neither the
119
85
  * island marker nor a misleading landmark (zudolab/zudo-doc#2058).
120
86
  *
87
+ * Optional feature islands (e.g. the design token panel bootstrap) are not
88
+ * listed here: they are injected at the body-end-islands composition
89
+ * anchors only when their feature is selected, so a feature-off scaffold
90
+ * carries no trace of them.
91
+ *
121
92
  * Each island is wrapped in `<Island ssrFallback>` so the heavy
122
93
  * component is NOT evaluated server-side — they depend on
123
94
  * `dialog.showModal()`, `localStorage`, `ResizeObserver`, runtime
@@ -143,37 +114,12 @@ export function BodyEndIslands({
143
114
  children: <ClientRouterBootstrap />,
144
115
  }) as unknown as VNode;
145
116
 
146
- // Hydrates on load so configurePanel() runs as early as possible and
147
- // the `toggle-design-token-panel` window listener is registered before
148
- // the user can click the header trigger. Renders nothing visually —
149
- // the zdtp panel self-mounts as a side-effect (zudolab/zudo-doc#1623).
150
- //
151
- // The inline <script> emitted alongside the Island is the pre-hydration
152
- // toggle shim (zudolab/zudo-doc#1627 Part B). It captures the first
153
- // click as a boolean flag and exposes window.__zdtpReadyClicks so the
154
- // bootstrap module can drain and re-dispatch once the real zdtp listener
155
- // is registered. Mirrors the PageLoadingOverlay SSR-script pattern.
156
- const designTokenPanelBootstrap =
157
- settings.designTokenPanel
158
- ? (
159
- <>
160
- <script
161
- dangerouslySetInnerHTML={{ __html: ZDTP_TOGGLE_SHIM_SRC }}
162
- />
163
- {Island({
164
- when: "load",
165
- children: <DesignTokenPanelBootstrap />,
166
- }) as unknown as VNode}
167
- </>
168
- )
169
- : null;
170
-
171
117
  // Gated on `settings.aiAssistant` (zudolab/zudo-doc#2058): when the AI
172
118
  // assistant feature is off, neither the AiChatModal island marker nor the
173
119
  // sr-only "AI Assistant" landmark heading should reach the SSG output —
174
120
  // otherwise feature-off consumers ship a dead island marker plus a
175
121
  // misleading screen-reader landmark for a section that never hydrates.
176
- // Mirrors the `designTokenPanel` gating above.
122
+ // Same feature-gating pattern as the other optional body-end islands.
177
123
  //
178
124
  // KNOWN CAVEAT: zfb's island scanner walks the static `"use client"`
179
125
  // import chain, so gating this JSX removes the SSR marker and heading but
@@ -215,6 +161,20 @@ export function BodyEndIslands({
215
161
  }) as unknown as VNode)
216
162
  : null;
217
163
 
164
+ // Gated on `settings.mermaid`. Mirrors the imageEnlarge block: the SSR
165
+ // fallback is an empty, closed `<dialog class="zd-mermaid-dialog ...">` so
166
+ // the dist HTML carries one dialog from the start and hydration (when="idle")
167
+ // swaps in the real component. Unlike images (SSR-wrapped by the MDX paragraph
168
+ // override), mermaid renders client-side, so this island injects the enlarge
169
+ // button into each rendered diagram container itself.
170
+ const mermaidEnlarge = settings.mermaid
171
+ ? (Island({
172
+ when: "idle",
173
+ ssrFallback: <MermaidEnlargeSsrFallback />,
174
+ children: <MermaidEnlarge />,
175
+ }) as unknown as VNode)
176
+ : null;
177
+
218
178
  return (
219
179
  <>
220
180
  {/* Pure SSR — no Island wrap. The component emits its overlay div,
@@ -222,9 +182,9 @@ export function BodyEndIslands({
222
182
  zfb:before-preparation / zfb:after-swap listeners at runtime. */}
223
183
  <PageLoadingOverlay />
224
184
  {clientRouterBootstrap}
225
- {designTokenPanelBootstrap}
226
185
  {aiAssistant}
227
186
  {imageEnlarge}
187
+ {mermaidEnlarge}
228
188
  {/* @slot:body-end-islands:extra-islands */}
229
189
  </>
230
190
  );