create-zudo-doc 0.2.11 → 0.2.12

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/scaffold.js CHANGED
@@ -333,12 +333,16 @@ function generatePackageJson(choices) {
333
333
  // "@takazudo/zfb" (#972) — plus removal of the no-op linkValidation.allowExternal
334
334
  // knob (#925); both are non-breaking for a fresh scaffold. A fresh scaffold
335
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",
336
+ // consumer-facing / CLI breaking change. next.52 (current pin): adds the
337
+ // `ClientRouter({ preserveHtmlAttrs })` option (zfb#1104) — consumers can
338
+ // declare runtime `<html>` attribute names to preserve across SPA swaps so
339
+ // e.g. `data-sidebar-hidden` / `data-theme` survive (zudolab/zudo-doc#2200).
340
+ // Additive, non-breaking for a fresh scaffold.
341
+ "@takazudo/zfb": "0.1.0-next.52",
342
+ "@takazudo/zfb-runtime": "0.1.0-next.52",
339
343
  // zfb-adapter-cloudflare — required for any route with `prerender = false`.
340
344
  // Pinned in lockstep with @takazudo/zfb.
341
- "@takazudo/zfb-adapter-cloudflare": "0.1.0-next.51",
345
+ "@takazudo/zfb-adapter-cloudflare": "0.1.0-next.52",
342
346
  // @takazudo/zudo-doc — published from this monorepo via
343
347
  // .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
344
348
  // lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
@@ -350,7 +354,7 @@ function generatePackageJson(choices) {
350
354
  // ties this pin to packages/zudo-doc's version, so the lockstep release
351
355
  // bumps both together; do not cut a create-zudo-doc release until the
352
356
  // matching @takazudo/zudo-doc version (with content.css) is on npm.
353
- "@takazudo/zudo-doc": "^0.2.11",
357
+ "@takazudo/zudo-doc": "^0.2.12",
354
358
  // zod — used by the generated zfb.config.ts. zfb-config-gen emits
355
359
  // `import { z } from "zod"` for the content-collection schema +
356
360
  // `z.toJSONSchema(...)` conversion. Without this dep, the consumer
@@ -373,7 +377,6 @@ function generatePackageJson(choices) {
373
377
  "preact-render-to-string": "^6.6.6",
374
378
  shiki: "^4.0.2",
375
379
  "@shikijs/transformers": "^4.0.0",
376
- clsx: "^2.1.0",
377
380
  "gray-matter": "^4.0.0",
378
381
  mermaid: "^11.12.3",
379
382
  "remark-cjk-friendly": "^2.0.1",
@@ -405,7 +408,7 @@ function generatePackageJson(choices) {
405
408
  // @takazudo/zudo-doc/integrations/doc-history which in turn imports
406
409
  // @takazudo/zudo-doc-history-server/git-history. Without this dep the
407
410
  // plugin host fails at init with ERR_MODULE_NOT_FOUND — W8A (#1739).
408
- deps["@takazudo/zudo-doc-history-server"] = "^0.2.11";
411
+ deps["@takazudo/zudo-doc-history-server"] = "^0.2.12";
409
412
  // W7A (#1736): doc-history-plugin.mjs spawns `tsx -e <inline-script>` to
410
413
  // run the v2 runtime in a TS-aware Node subprocess; without tsx the
411
414
  // plugin's preBuild step exits with ENOENT before zfb finishes config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "Create a new zudo-doc documentation site",
5
5
  "license": "MIT",
6
6
  "author": "Takeshi Takatsudo",
@@ -6,7 +6,6 @@
6
6
  // packages/zudo-doc/src/theme-toggle/index.tsx. Type references via
7
7
  // the global React namespace still resolve via @types/react.
8
8
  import { useState, useEffect } from "preact/hooks";
9
- import clsx from "clsx";
10
9
  // After zudolab/zudo-doc#1335 (E2 task 2 half B) the host components
11
10
  // pull lifecycle event names from the v2 transitions module rather
12
11
  // than hard-coding `astro:*` literals.
@@ -18,6 +17,9 @@ import type { LocaleLink } from "@/types/locale";
18
17
  // pulled in by `./sidebar`'s runtime barrel.
19
18
  import type { SidebarRootMenuItem } from "@takazudo/zudo-doc/sidebar/types";
20
19
 
20
+ const cx = (...classes: Array<string | false | null | undefined>) =>
21
+ classes.filter(Boolean).join(" ");
22
+
21
23
  // Mobile drawer hosts the SidebarTree directly (rather than receiving it as
22
24
  // JSX children) so the tree's data props ride across the SSR → hydrate
23
25
  // boundary inside the Island marker's `data-props` attribute. zfb's
@@ -90,7 +92,7 @@ export default function SidebarToggle({
90
92
  {/* X icon — visible only when open */}
91
93
  <svg
92
94
  xmlns="http://www.w3.org/2000/svg"
93
- className={clsx("h-icon-lg w-icon-lg", !open && "hidden")}
95
+ className={cx("h-icon-lg w-icon-lg", !open && "hidden")}
94
96
  aria-hidden="true"
95
97
  fill="none"
96
98
  viewBox="0 0 24 24"
@@ -106,7 +108,7 @@ export default function SidebarToggle({
106
108
  {/* Hamburger icon — visible only when closed */}
107
109
  <svg
108
110
  xmlns="http://www.w3.org/2000/svg"
109
- className={clsx("h-icon-lg w-icon-lg", open && "hidden")}
111
+ className={cx("h-icon-lg w-icon-lg", open && "hidden")}
110
112
  aria-hidden="true"
111
113
  fill="none"
112
114
  viewBox="0 0 24 24"
@@ -131,7 +133,7 @@ export default function SidebarToggle({
131
133
  backdrop (onClick below), so the header hamburger being dimmed under
132
134
  it is fine. */}
133
135
  <div
134
- className={clsx("fixed inset-0 z-modal-backdrop bg-overlay/30 lg:hidden", !open && "hidden")}
136
+ className={cx("fixed inset-0 z-modal-backdrop bg-overlay/30 lg:hidden", !open && "hidden")}
135
137
  aria-hidden={!open}
136
138
  onClick={() => setOpen(false)}
137
139
  />
@@ -717,19 +717,10 @@ pre[class^="syntect-"] .line .highlighted-word {
717
717
  }
718
718
  }
719
719
 
720
- /* Suppress sidebar geometry transitions during an SPA swap (#2198).
721
- * swapRootAttributes wipes data-sidebar-hidden mid-swap, so without this the
722
- * sidebar would animate open (attribute absent) and then animate shut again
723
- * when the toggle island restores the attribute on zfb:after-swap — a visible
724
- * flash + slide. The island sets data-sidebar-no-transition for the swap
725
- * window and removes it (double rAF) after restoring the hidden state, so the
726
- * wipe/restore apply instantly and a later user toggle still animates. */
727
- html[data-sidebar-no-transition] #desktop-sidebar,
728
- html[data-sidebar-no-transition] .zd-sidebar-content-wrapper,
729
- html[data-sidebar-no-transition] .zd-doc-content-band,
730
- html[data-sidebar-no-transition] .zd-desktop-sidebar-toggle {
731
- transition: none !important;
732
- }
720
+ /* The SPA-swap flash guard (`data-sidebar-no-transition`) was removed once
721
+ * doc-layout adopted <ClientRouter preserveHtmlAttrs={["data-sidebar-hidden",
722
+ * ...]} /> (zfb-runtime >= 0.1.0-next.52): the attribute now survives the swap
723
+ * before paint, so no sidebar geometry transition fires mid-swap. */
733
724
 
734
725
  /* ── Find-in-page highlight (W7A: unconditional — Tauri feature gates at runtime) ── */
735
726
  .find-match {
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect, useRef } from 'preact/hooks';
4
- import { BEFORE_NAVIGATE_EVENT, AFTER_NAVIGATE_EVENT } from '@takazudo/zudo-doc/transitions';
5
4
 
6
5
  export const SIDEBAR_STORAGE_KEY = 'zudo-doc-sidebar-visible';
7
6
 
@@ -22,93 +21,13 @@ function setDataAttribute(isVisible: boolean) {
22
21
  }
23
22
  }
24
23
 
25
- // SPA-navigation guard for the desktop sidebar's hidden-state (#2198).
26
- //
27
- // zfb's Strategy-B client router wipes EVERY <html> attribute during the body
28
- // swap (swapRootAttributes re-adds only NON_OVERRIDABLE_ZFB_ATTRS plus the
29
- // incoming SSR document's attributes), so the persisted `data-sidebar-hidden`
30
- // runtime value is lost on every navigation, and the pre-paint inline script
31
- // does not re-run on SPA hops. Left alone, the freshly-rendered sidebar paints
32
- // visible and then animates shut when the value is restored — the flash + slide.
33
- //
34
- // These listeners MUST live at module scope, NOT in the island's useEffect:
35
- // zfb-runtime (>= 0.1.0-next.51) calls unmountIslands() BEFORE the swap and
36
- // before firing AFTER_NAVIGATE_EVENT, so a useEffect-registered listener is torn
37
- // down (effect cleanup) before the swap and never sees the after-swap event —
38
- // the restore would never run (#2198 verification). Registering once on
39
- // `document` at bundle load — the same lifecycle-independent pattern as
40
- // client-router-bootstrap.tsx — keeps the guard alive across island
41
- // unmount/remount. SSR-safe: no-ops when `document` is undefined.
42
- //
43
- // Strategy: on BEFORE_NAVIGATE_EVENT, record whether the sidebar was hidden and
44
- // set a transient `data-sidebar-no-transition` marker (global.css zeroes the
45
- // sidebar/wrapper/band/toggle transitions). The swap then wipes BOTH the marker
46
- // and data-sidebar-hidden. On AFTER_NAVIGATE_EVENT (swap done → the live <html>
47
- // is no longer wiped) RE-SET the marker, then re-add data-sidebar-hidden in the
48
- // same synchronous batch, so the hidden geometry snaps in with transitions
49
- // suppressed (no slide, no open frame). Remove the marker on a double rAF
50
- // afterward so a later user toggle still animates.
51
- let spaNavGuardRegistered = false;
52
- function registerSidebarSpaNavGuard() {
53
- if (typeof document === 'undefined' || spaNavGuardRegistered) return;
54
- spaNavGuardRegistered = true;
55
-
56
- let wasHidden = false;
57
- let swapToken = 0;
58
- // Long enough to outlast the View-Transition swap pipeline on slow
59
- // machines/CI; short enough that an aborted nav self-heals quickly. Only a
60
- // safety net for a nav that never reaches `restore` (aborted/superseded).
61
- const STUCK_MARKER_FALLBACK_MS = 1500;
62
-
63
- const capture = () => {
64
- wasHidden = document.documentElement.hasAttribute('data-sidebar-hidden');
65
- document.documentElement.setAttribute('data-sidebar-no-transition', '');
66
- const token = ++swapToken;
67
- window.setTimeout(() => {
68
- if (token === swapToken) {
69
- document.documentElement.removeAttribute('data-sidebar-no-transition');
70
- }
71
- }, STUCK_MARKER_FALLBACK_MS);
72
- };
73
-
74
- const restore = () => {
75
- // Marker first, then the attribute, in one synchronous batch — so the
76
- // hidden geometry is restored with transitions off (the swap already wiped
77
- // the capture-time marker, so re-setting it here on the post-swap <html> is
78
- // what actually suppresses the animation).
79
- document.documentElement.setAttribute('data-sidebar-no-transition', '');
80
- if (wasHidden) {
81
- document.documentElement.setAttribute('data-sidebar-hidden', '');
82
- }
83
- // Drop the marker only after the restored state is committed. A single rAF
84
- // can coincide with the restore's style recalc and animate it, so defer one
85
- // extra frame. The token bump no-ops capture's safety-net for this swap.
86
- const token = ++swapToken;
87
- requestAnimationFrame(() => {
88
- requestAnimationFrame(() => {
89
- if (token === swapToken) {
90
- document.documentElement.removeAttribute('data-sidebar-no-transition');
91
- }
92
- });
93
- });
94
- };
95
-
96
- document.addEventListener(BEFORE_NAVIGATE_EVENT, capture);
97
- document.addEventListener(AFTER_NAVIGATE_EVENT, restore);
98
- }
99
-
100
- // Register at module load (browser-only via the guard) so the listeners exist
101
- // before the first navigation and survive island unmount/remount.
102
- registerSidebarSpaNavGuard();
103
-
104
24
  export default function DesktopSidebarToggle() {
105
25
  // Initial state must match server render (always `true`) to avoid a
106
26
  // hydration mismatch when the persisted preference is "hidden". The
107
27
  // doc-layout's pre-paint inline script applies `data-sidebar-hidden`
108
28
  // to <html> from localStorage *before* this island mounts, so the
109
29
  // visual state stays correct; we only need to sync this island's
110
- // React state to the persisted preference after hydration. Same
111
- // pattern as packages/zudo-doc/src/theme-toggle/index.tsx (commit 9aebd8e).
30
+ // React state to the persisted preference after hydration.
112
31
  const [visible, setVisible] = useState<boolean>(true);
113
32
  // Tracks whether the hydration sync (below) has run. The persistence
114
33
  // effect below skips the very first mount so we don't overwrite the
@@ -145,10 +64,11 @@ export default function DesktopSidebarToggle() {
145
64
  // eslint-disable-next-line react-hooks/exhaustive-deps
146
65
  }, []);
147
66
 
148
- // The SPA-navigation flash guard (data-sidebar-hidden preservation +
149
- // transition suppression) is NOT registered here. It must outlive this
150
- // island's mount/unmount, so it lives at module scope — see
151
- // registerSidebarSpaNavGuard() above. (#2198)
67
+ // The SPA-navigation flash (a collapsed sidebar briefly painting open on
68
+ // every soft swap) is handled upstream: doc-layout mounts
69
+ // <ClientRouter preserveHtmlAttrs={["data-sidebar-hidden", ...]} /> so
70
+ // zfb-runtime (>= 0.1.0-next.52) re-applies the runtime attribute across the
71
+ // swap before paint. No host-side capture/restore guard is needed.
152
72
 
153
73
  return (
154
74
  <button