zudo-doc
GitHub repository

Type to search...

to open search from anywhere

Migrating to zudo-doc 6

Created Oct 9, 2026Updated Oct 12, 2026Takeshi Takatsudo

Upgrade a 5.x consumer to the zudo-react and Wind major release.

Release status

This guide covers the migration from 5.x to zudo-doc 6.0.0, the coordinated major release of @takazudo/zudo-doc, @takazudo/zudo-doc-history-server and create-zudo-doc (see the 6.0.0 release notes). All three packages are published at 6.0.0 on npm (latest); the 6.0.0 publication record lists the publish runs and the published-consumer checks. The engine target is the published, aligned zfb 4.3.3 family, with package peer floor ^4.3.3. Do not install historical 3.0/3.1 targets.

The fix for #4097 first shipped in 4.3.0; the issue is resolved. The strict published-consumer browser/navigation control passed 3/3 with no failures, skips or retries on the supported 4.3.1 tuple, as recorded in the source-bound verification. That is Linux Chromium evidence, not native macOS WebKit or Apple IME acceptance. The migration status record preserves the platform limits and the owner's cancellation of the remaining manual IME check.

For a 3.x consumer, start with Migrating from zudo-doc 3 to inventory and reconcile the earlier scaffold/API changes while preserving authored work.

Dependencies and TypeScript

To upgrade an existing project, edit the project's own package.json and reinstall with your package manager. Set @takazudo/zudo-doc to ^6.0.0, and set @takazudo/zudo-doc-history-server to ^6.0.0 if the project declares it (it is needed only when doc history or the asset viewer is enabled). Align every installed engine package (@takazudo/zfb, @takazudo/zfb-runtime, @takazudo/zfb-md-wasm, and the Cloudflare adapter when used) to 4.3.3. Core rendering no longer requires Preact.

create-zudo-doc is the initializer, not an application dependency: do not add it to an existing project, and do not rerun it over one. To compare against the matching 6.0.0 output, generate a separate new or reference scaffold with pnpm dlx create-zudo-doc@6.0.0 in another directory and copy only what you need.

Extend the package base below. It owns jsx: "react-jsx" and jsxImportSource: "@takazudo/zfb/zudo-react". Remove react, react-dom, and react/jsx-runtime → Preact compatibility paths and replace type imports from React/Preact. Retain project aliases only when needed, with explicitly relative targets such as ./src/*: each paths entry resolves against the tsconfig that declares it. The base sets no baseUrl (TypeScript 7 rejects the option with TS5102), so the same project config typechecks under TypeScript 5.9 and 7.0. An existing project may keep its own baseUrl while it stays on TypeScript 5. Do not replace the base's top-level files, which loads the config and virtual-module declarations.

{
  "extends": "@takazudo/zudo-doc/tsconfig.base.json",
  "include": ["src", "pages", "zfb.config.ts"],
  "compilerOptions": {
    "paths": { "@/*": ["./src/*"] }
  }
}

CSS and Wind

Remove Tailwind imports, @theme, @source, @tailwind, @apply, @utility, and @plugin directives from your CSS entry. Move token declarations to ordinary :root rules, retaining their variable names. Remove the --color-*: initial palette reset. Replace theme-no-reset.css with theme.css and delete the safelist.css import: both former subpath exports are removed.

Keep the public CSS imports below. Add @takazudo/zdtp/styles.css only when the panel is enabled. Do not import physical dist/*.css paths. compiled.css remains a public browser-ready export for embedding without a zfb CSS build; it is not an additional required import in this setup.

@layer zw-reset, zd-flow;
@import "@takazudo/zudo-doc/theme.css";
@import "@takazudo/zudo-doc/content.css";
@import "@takazudo/zudo-doc/page-loading.css";
@import "@takazudo/zudo-doc/features.css";

:root {
  --spacing-hsp-md: 1rem;
  --z-index-toolbar: 25;
}

zudoDoc() registers the new public @takazudo/zudo-doc/wind.json manifest automatically. It supplies the package utility candidates without scanning package dist/; you normally do not import or register this manifest yourself. Wind tokens point at the existing CSS variables so live theme/panel updates still work.

Use :root for variable values, or the top-level wind option for utility mappings. zfb deep-merges this override over package defaults. wind is not a Settings field. Choose reset with wind.reset ("owned-v1" is the package default); wind: false disables Wind entirely.

import { defineConfig } from "@takazudo/zfb/config";
import { zudoDoc } from "@takazudo/zudo-doc/config";

export default defineConfig(zudoDoc({
  wind: {
    tokens: { colors: { accent: "var(--brand-accent)" } },
    reset: "owned-v1",
  },
}));

Wind is a defined utility language, not Tailwind compatibility. The package config has named spacing, no implicit numeric spacing scale, sm/lg/xl breakpoints (640/1024/1280px), and no dark utility mode; existing data-theme variable switching remains. Unsupported utilities need explicit authored CSS with zd- class names and corresponding coverage. Generated utilities are unlayered and follow authored CSS; check selector specificity when porting overrides.

Run pnpm exec zfb wind audit --project-root . --fail-on error. The error exit flag is required. Audit success does not prove package manifest completeness or emitted/computed CSS: retain manifest, authored-class, emitted-rule and browser checks.

Public type and prop census

5.x contract6.0 migration target
Preact VNode / React ReactNodezudo-react Description for element descriptions; Child for renderable values/returns
Preact component callback typeszudo-react Component<P>; retain domain names such as FrontmatterCellRenderer and ThemePackDialogComponent
Preact intrinsic attributesJSX.IntrinsicElements["button"] (or the actual tag) from @takazudo/zfb/zudo-react/jsx-runtime
Intrinsic-compatible content components and shared icons classNameclass; EnlargeDialogProps.class follows the same rule
ThemePackDialogProps.open: booleanReadonlySignal<boolean> within one client component tree; never serialize this signal through Island props
useModalDialog(...)modalDialog(scope, options) at the retained ./use-modal-dialog subpath, with ModalDialogOptions / ModalDialogResult
./safelist.css, ./theme-no-reset.cssRemoved; ./wind.json added; other public CSS exports retained
Required Preact peer for coreRemoved; zdtp 0.8.6 owns its Preact dependency, so panel users install zdtp only

ThemeToggle retains defaultMode, respectPrefersColorScheme, labels, and pendingUntilHydrated. Keep other domain prop names: the class change is for intrinsic-compatible APIs, not a blanket rename of custom className props (for example, SidebarTree retains its custom className).

ENLARGE_DIALOG_STYLE remains exported from ./island-types. On the actual 4.3.3 target it is the literal style object { position: "fixed", inset: 0, margin: "auto" }, and EnlargeDialogProps.style is typeof ENLARGE_DIALOG_STYLE. The earlier 3.x plan for a string is superseded by native style support. Use supported CSS property names and explicit units for nonzero lengths; strings remain valid for complete CSS declarations.

Ejected and host components

Rewrite interactive components using setup-time signals, computed, Show, For, and scope activation/cleanup. JSX runs once per scope; reading count.value as a plain snapshot will not make text reactive. Intrinsic events use on:click, on:input, and on:change. Native change fires on commit; use input or a supported modelValue binding for updates while typing. Do not mechanically convert every React onChange into native change.

Call modalDialog during setup in the scope that owns the dialog. If the dialog is conditional, place a child component inside Show and call the helper in that child. The options are isOpen, onClose, navigateEvent, backdropClickClose, manageFocus, restoreFocusOnly, and returnFocusRef; the result contains dialogRef and handleBackdropClick. It is an ordinary-module helper, not a "use client" island export.

"use client";
import { computed, getScope, signal } from "@takazudo/zfb/zudo-react";
import { modalDialog } from "@takazudo/zudo-doc/use-modal-dialog";

export function ExampleDialog() {
  const scope = getScope();
  const open = signal(false);
  const { dialogRef, handleBackdropClick } = modalDialog(scope, {
    isOpen: open,
    onClose: () => { open.value = false; },
    backdropClickClose: true,
    manageFocus: true,
  });
  return <>
    <button type="button" on:click={() => { open.value = true; }}>Open</button>
    <dialog ref={dialogRef} on:click={handleBackdropClick}>
      <p>{computed(() => open.value ? "Open" : "Closed")}</p>
      <button type="button" on:click={() => { open.value = false; }}>Close</button>
    </dialog>
  </>;
}

Fixed-target server boundaries

Keep defineChromeBindings and the six primary replacement keys (Header, Footer, Sidebar, Toc, Breadcrumb, DocPager); omitted slots retain defaults. Import runtime values from topic subpaths: the package root is type-only. Config evaluation (./config, ./preset) is a different graph from browser imports. browser-safe defaults live in the internal settings-defaults.ts leaf and are re-exported by ./config; ./settings is types-only; ./site-schema remains engine-free. Never import host virtual route modules from a client entry.

DocHistory and DesignTokenPanelBootstrap host overrides now receive server boundaries, not a dynamic client component that the package wraps in Island. Each boundary statically imports its own named client target, owns the Island mount, and returns a Fragment. The optional BodyEndIslandsDeps.ThemePackSwitcher follows the same boundary rule. Keep package settings gates and override precedence; a custom panel configuration usually belongs in designTokenPanelConfigModule, not a replacement island.

For standard history use the retained public DocHistoryBoundary export. It removes ssrFallback before creating client props, so fallback markup stays on the server while slug/locale/base/date data crosses JSON transport.

import { defineChromeBindings } from "@takazudo/zudo-doc/chrome-bindings";
import { DocHistoryBoundary } from "@takazudo/zudo-doc/doc-history-area";

export const chromeBindings = defineChromeBindings({
  DocHistory: DocHistoryBoundary,
});

A custom panel boundary uses the same static shape; the client target is your own uniquely named "use client" component.

import { Island } from "@takazudo/zfb";
import { defineChromeBindings } from "@takazudo/zudo-doc/chrome-bindings";
import { MyPanelClient } from "./my-panel-client";

function MyPanelBoundary() {
  return <><Island when="load"><MyPanelClient /></Island></>;
}

export const chromeBindings = defineChromeBindings({
  DesignTokenPanelBootstrap: MyPanelBoundary,
});

Only finite JSON scalars, dense arrays and plain records cross an Island boundary. Explicitly omit absent optional object keys before constructing the client description; use the same normalized data for SSR and serialization. Functions, signals, descriptions, dates/classes, cycles, getters, symbols and undefined array entries are invalid. Do not pass ssrFallback inside the client child's props or sanitize props by a JSON round trip. DocHistory client and the existing public fallback components (such as ImageEnlargeSsrFallback and MermaidEnlargeSsrFallback) remain available at their documented subpaths.

zdtp and runtime limits

The installed zdtp 0.8.6 manifest carries Preact ^10.29.1 as its own dependency and exports ./styles.css (also ./styles) to its own CSS. Install zdtp when using the panel; a separate Preact install is no longer required (it was a peer through 0.8.5). zdtp owns its opaque Preact subtree and internal Tailwind browser dependency; this does not restore Preact or Tailwind as the host rendering engine. Keep lazy loading, persisted mode-scoped panel state and root CSS-variable updates.

Use native ordered lists, leading-line-feed handling and unchanged-root persistence from the current engine. Unchanged island identity/props preserve live scope state; changed identity/props recreate it. The package's zfb:before-swap adapter updates only the detached incoming document, honors live data-zd-props-preserve, and removes incoming persistence for unsafe structure changes. Do not restore blanket remounts.

Parser rules are part of the owned JSX contract. Keep legal table/select structure; use explicit tbody and do not place Show/For/component regions directly at table structural levels. rawHtml accepts trusted strings and is mutually exclusive with children; it does not sanitize. Do not insert island wrappers or runtime protocol markers through raw HTML. Script/style payloads require static raw text with closing-tag guards. Native support has evolved since the 3.x spike: consult the published 4.3.3 declarations/contract rather than copying old iframe/head/style shims.

Follow the supported fixed-target composition #4004 contract and the Wind coverage #3371 limits. Earlier issues are historical context, not evidence that current release gates passed.

Optional adoption and verification

Static agent exports and MCP are separate opt-in features, not required for this core migration. When configuring zudoDoc() directly, the package defaults for agentExport, mcp, and llmsTxt are all false. The create-zudo-doc initializer selects llmsTxt by default, while agent export and MCP remain opt-in. Keep existing choices unless you explicitly adopt those features; see llms.txt, Agent-readable export, and MCP server for their complete recipes.

Before deploying the upgraded production site, verify it against the published 6.0.0 packages: strict types (including rejected retired exports/invalid props), public CSS/manifest resolution, a real build, hydration and repeated navigation. Exercise base paths and locale fallback, history fallback serialization, open dialogs, persisted sidebar state and live panel variables. Reconcile both language versions and run formatting/link checks.

Revision History

Takeshi TakatsudoCreated: 2026-10-09T16:21:33ZUpdated: 2026-10-12T23:48:23+09:00

AI Assistant

Ask a question about the documentation.

Preview theme

Loading theme previews…