Design Token Panelは、テーマが提供するすべてのデザイントークンをページ上で編集できるインタラクティブなエディタです。以前のColor Tweak Panel（カラー専用）を置き換え、**Palette**・**Color**・**Font**・**Spacing**・**Size**の各トークン種別をタブ構成でカバーします。さらに統一されたJSONエクスポート／インポート機能を備えており、デザインをまとめてAIアシスタントに渡し、返ってきた結果を読み込むラウンドトリップ運用が可能です。

## パネルを有効にする

`zfb.config.ts` で `designTokenPanel` を `true` に設定します:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    designTokenPanel: true,
    // ...
  }),
);
```

有効になると、ヘッダーに（検索アイコンの左側に）パレットアイコンが表示されます。クリックするとパネルが開閉し、`Escape` を押せばページのどこからでも閉じられます。

[Dynamic content unavailable in text export]

設定フィールドは `designTokenPanel` です。`true` に設定するとパネルが有効になります。ホスト側の設定ファイルは不要です — `designTokenPanelConfigModule` で独自のビルダーを指定しない限り、パネルはパッケージのデフォルトビルダー（`@takazudo/zudo-doc/design-token-panel-config`）を使用します（詳しくは下記の[パネルのカスタマイズ](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel#%E3%83%91%E3%83%8D%E3%83%AB%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B-%E3%83%91%E3%83%8D%E3%83%AB%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA)を参照）。



### パネルのカスタマイズ

デフォルトでは `designTokenPanel: true` にホスト側の設定ファイルは不要です — 注入されるパネルはパッケージのデフォルトビルダー（`@takazudo/zudo-doc/design-token-panel-config`）を使用し、バンドルされたカラースキームから Palette／Color タブを、同梱のトークンマニフェストから Font／Spacing／Size タブを導出します。

完全にカスタムなパネル（独自のトークンマニフェスト、独自のカラースキーム）を用意するには、`buildDesignTokenPanelConfig(mode: "light" | "dark")` という名前付きビルダーをエクスポートするモジュールを作成し、`designTokenPanelConfigModule` にそのパスを指定します — プロジェクトルートからの相対パスで、`chromeBindingsModule` の契約をそのまま踏襲します:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    designTokenPanel: true,
    designTokenPanelConfigModule: "./src/config/design-token-panel-config.ts",
  }),
);
```

このショーケースは、パッケージ所有のブートストラップとパッケージのデフォルトビルダーを使用します。自動注入されるブートストラップに独自のモードスコープなビルダーを読み込ませたい場合にのみ、`designTokenPanelConfigModule` を設定してください。

この設定が適用されるのは、パッケージ注入ルートでレンダリングされるページだけである点に注意してください: 設定が存在する間、自己完結型のホスト `pages/` スタブでレンダリングされるドキュメントページは、パネルアイランド（およびヘッダーのパネルボタン）を一切マウントしません。これにより、スタブページがパッケージデフォルトのビルダーでセッション単位のパネル設定を横取りすることがなくなります。スタブでレンダリングされるページにもパネルを出したい場合は、クライアントのブートストラップを固定ターゲットのサーバー境界で包み、`chromeBindings.DesignTokenPanelBootstrap` に渡してください — この境界はどこでも優先されます（[ホストクロームバインディング](https://zudo-doc.takazudomodular.com/ja/docs/reference/host-chrome-bindings)を参照）。

## タブ構成

zudo-doc がパネル設定で渡しているトークンタブは、**Palette**・**Color**・**Font**・**Spacing**・**Size** の5つです。これに `@takazudo/zdtp` 自身が **Inspect** タブを先頭に追加します。Inspect はzdtpが合成するタブで、ホストの設定には現れず、設定側から取り除くこともできません。つまり実際のタブ列は常に設定した数より1つ多く、パネルは **Palette** を選択した状態で開きます。

設定している5つのIDはいずれもzdtp側の予約IDなので、汎用のティアレンダラーではなく、それぞれ専用のエディタが割り当てられます。各タブには担当するトークン以外は表示されないため、色の下までスクロールしてスペーシングにたどり着く必要はありません。

### Inspect

ホスト側の設定に関わらず、zdtpが常に用意するタブです。パネルヘッダーから（または `I` キーで）有効にしてページ上の要素をクリックすると、その要素の計算値と、それが解決されるトークン（継承されたものを含む）が並べて表示されます。`zfb.config.ts` 側に設定項目はありません — このタブはパネルの機能であり、このプロジェクトのトークンマニフェストの一部ではありません。

### Palette

Paletteタブは、すべてのカラースキームの土台となる、共有される3つのOKLCHランプを公開します（[Color](https://zudo-doc.takazudomodular.com/ja/docs/reference/color) を参照）:

- **Base** — 5 ストップ（`--palette-base-0` … `--palette-base-4`）、インデックス 0 が最も明るい
- **Accent** — 3 ストップ（`--palette-accent-0` … `--palette-accent-2`）
- **State** — 4 つの名前付きロール: `danger`・`success`・`warning`・`info`（`--palette-state-{role}`）

タブ上部の **Edit** / **Check** トグルで、同じランプに対する2つのビューを切り替えます。

- **Edit** では、各ストップがzdtpネイティブのOKLCH明度/彩度/色相カーブエディタ上のスウォッチとして並びます。ストップをドラッグすると、そこから構築されたすべてのセマンティックロールがライブで更新されます — 別途の "Scheme…" プリセットドロップダウンはありません。ランプそのものが編集可能なソースオブトゥルースです。
- **Check** は同じランプをコントラスト確認ビューに切り替えます。背景となるストップを選ぶと、前景側の候補それぞれのコントラストが一覧されるため、カーブを編集した結果をパネル内で確かめられます。

ウォークスルーは[カラースキームプレビュー](https://zudo-doc.takazudomodular.com/ja/docs/guides/color-scheme-preview)を参照してください。

### Color

Colorタブは**セマンティック→ランプのエディタ**です。行は3つのグループに分かれ、4 つの base ロール（`bg`・`fg`・`selection-bg`・`selection-fg`）、`--zd-*` セマンティックロール、シンタックスハイライト用のロール（`syntaxComment`・`syntaxKeyword`・`syntaxString` など）が並びます。各行は Palette タブのランプ上のストップ、あるいは一部のロールではリテラルの OKLCH オーバーライドに紐付けられています。このタブ自体はランプを持たず、Palette タブの **Base** / **Accent** / **State** の各階層を参照するため、そこでランプストップを編集すると、それを指すすべての行の色が変わります。

#### Ref 行 — グループ化されたランプのドロップダウン

ほとんどの行は、**Base** / **Accent** / **State** の `<optgroup>` を持つ `<select>` としてレンダリングされ、Palette タブのランプのストップごとに 1 つのオプションが並びます。各オプションにはストップのCSS変数名と解決後の色が表示され、行そのものにも解決結果を示すカラーチップが付くため、ドロップダウンを開かなくても参照先が現在どの色になっているか分かります。

オプションを選ぶと、その行の `--zd-*` カスタムプロパティが `var(--palette-{tier}-{n})` に紐付きます — 解決済みの色ではなく、ライブな CSS 変数参照です — そのため、Palette タブで参照先ストップの L/C/H カーブをドラッグするとページが即座に更新され、Colorタブで何かを選び直す必要はありません。

各 ref 行のドロップダウンの末尾には **「Literal…」** エントリもあり、行をランプから切り離して一度きりの色を固定できます。

#### Literal 行 — モードごとに AA 調整したオーバーライド

一部のロールは、両モードで共有ランプストップだけでは満たせず（[モードごとのリテラルオーバーライド](https://zudo-doc.takazudomodular.com/ja/docs/reference/color#%E3%83%A2%E3%83%BC%E3%83%89%E3%81%94%E3%81%A8%E3%81%AE%E3%83%AA%E3%83%86%E3%83%A9%E3%83%AB%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89)を参照）、デフォルトでリテラル行としてレンダリングされます：`Literal…` が選択された `<select>` と、編集可能な OKLCH スウォッチです。どのロールがリテラルで始まるかはスキームによって定義されます。同梱スキームでは、AA 調整されたステートカラー（`danger` は両モード、`accentHover`・`success`・`warning`・`info` は Default Light）、2 つのマッチキーワード用トークン、2 つの差分シンタックストークン、そしてリテラルなセマンティックロールを継承しているシンタックスロールがこれにあたります。

リテラル行のスウォッチには **Per-mode** チェックボックスがあります。オンにすると、1 つではなく 2 つの独立した OKLCH フィールド（Light / Dark）が現れます。適用される値は `light-dark(<light-value>, <dark-value>)` になり、パネルは適用先のルートに `color-scheme: light dark` を設定するため、ブラウザはアクティブなモードに対応する側を解決します。

#### マッチキーワード用トークン

検索結果でマッチしたキーワードのハイライト方法を制御する2つのセマンティックカラートークンがあります。これらはリテラル行としてレンダリングされ（ライトとダークで共有 — [モードごとのリテラルオーバーライド](https://zudo-doc.takazudomodular.com/ja/docs/reference/color#%E3%83%A2%E3%83%BC%E3%83%89%E3%81%94%E3%81%A8%E3%81%AE%E3%83%AA%E3%83%86%E3%83%A9%E3%83%AB%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89)を参照）、検索ウィジェット（`pages/lib/_search-widget.tsx`）が検索UIのヒット箇所のスタイリングで参照します。

- `matchedKeywordBg` — マッチしたキーワードの背景に塗られる色。
- `matchedKeywordFg` — その背景の上に重なる、マッチしたキーワードの文字色。

#### モードスコープの編集モデル

Colorタブは常に、現在アクティブなモード（ライトまたはダーク）の紐付けを編集し、パッケージの `@takazudo/zudo-doc/color-schemes-defaults`（または設定済みなら自作の `colorSchemes` 上書き — [Color](https://zudo-doc.takazudomodular.com/ja/docs/reference/color) を参照）のそのモードの `ColorScheme.map` からシードされます。

- **実効ライト/ダーク表示が変わると、タブの ref/literal のデフォルトが再シードされます** — 新しいモードの紐付けに切り替わります（例：`bg` が `base-0` ↔ `base-4` と入れ替わる）。System 選択中の OS 設定変更も含まれます。この背後にある destroy+reconfigure の仕組みは[実装](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel#%E5%AE%9F%E8%A3%85)を参照してください。
- **Palette・Spacing・Font・Size の編集は、外観変更をまたいでそのまま引き継がれます。** これらのタブはモードに依存しないため、ランプカーブの編集やスペーシングの変更は、これまでと同様にライト↔ダークをまたいで保持されます。
- **保存された Color タブのオーバーライドは、スキームおよびモードスコープです。** パッケージのデフォルトビルダーは`panelSettings.colorMode`で`Default Light`と`Default Dark`を対応付けるため、保存された Color オーバーライドはアクティブなスキーム識別子に対してのみ復元されます。一方のモードで行ったオーバーライドがもう一方を再配色することはありません。Palette・Spacing・Font・Size タブは外観変更をまたいで共有されます。

[Dynamic content unavailable in text export]

**zudo-doc のビルダーはモードごとのリテラルデフォルトを宣言していません。** `TierItem.default` は単一の文字列なので、（`danger` のような）AA 調整されたモードごとのリテラルは、事前に `{light, dark}` のペアとして宣言するのではなく、再構成時にアクティブなモードからシードされます。ただし zdtp 側の制約ではありません: 0.4.5 以降、Color タブの `colorExtras.semanticDefaults` マップが行ごとに `{ literal: { light, dark } }` を受け取れるようになっており、これがそうしたデフォルトを設定する正規の手段です。パッケージのデフォルトビルダーがまだこのマップを埋めていないだけです。実行時のモードごとの編集（上記の **Per-mode** チェックボックス）は影響を受けません — これは*設定*が開始時のデフォルトとして宣言する内容を制限するだけです。



### Font

タブに表示される順に、5 つのティアがあります。

- **Scale** — 生の `--text-scale-*` ステップ（`2xs` → `2xl`）。CSS値のサニタイズ付きの数値入力で編集します。
- **Font size** — セマンティックロール（`text-micro` … `text-display`）。これは*参照*ティアで、各行は生の値ではなく **Scale** のステップを選びます。そのためステップを編集すると、それを指すすべてのロールに波及します — Colorタブのセマンティック行がランプストップを指すのと同じ構造です。
- **Line height**・**Font weight**・**Font family** — 残りのタイポグラフィトークン。フォントファミリーは、複数スタックのフォールバックを含む任意の有効な `font-family` 値を受け付けます。無効な値（引用符の不一致、生のJavaScriptなど）は入力確定時に拒否されるため、貼り付けたスニペットによってページが壊れることはありません。

各ティアには、書体サンプル・行送り・ウェイト・ファミリーのライブプレビューが付きます。さらに、サイトの実際のフォントで組んだ読み取り専用の**スペシメン**をホストページ側に表示させることもできます（表示中はパネルが一時的に右ドックに固定されます）。

### Spacing

ティアは 4 つ: **Horizontal spacing**（`hsp-*`）・**Vertical spacing**（`vsp-*`）・**Icons**（`icon-xs` → `icon-lg`）・**Layout**。編集可能なステップには数値コントロールと、値に応じて伸縮するプレビューバーが付きます。数値の一括操作（乗算・加算・ステップ単位の丸め・一括設定）を使えば、選択した範囲の行をまとめて変更できます。

Layout のうち 3 つは読み取り専用として表示されます: `spacing-0`（ユーティリティが前提にしている構造的なゼロ）・`spacing-px`（1px のヘアライン）・`sidebar-w`（単一のコントロールでは表現できないレスポンシブな `clamp()` 式）。存在を把握してもらうために見せているだけで、動かすためのものではありません。

### Size

ティアは 2 つです。名前から想像しがちなアイコンサイズはここにはなく、Spacing タブにあります。

- **Radius** — `--radius-DEFAULT`・`--radius-lg`・`--radius-full`。最後のひとつにはピルトグルがあり、`9999px` のセンチネル値と編集可能な値を切り替えられます。
- **Transition** — デフォルトのトランジション時間。

## パネルのシェル

タブ以外にも、パネルのシェル自体が備えている機能がいくつかあります。探す前に把握しておくとよいものを挙げます。

- **ドックモード。** フロート・右ドック・下ドック・折りたたみのミニピルの4つがあり、`Alt+1` … `Alt+4` で切り替えます。2 つのドックはホストドキュメント側の領域を確保し、パネルを閉じると解放するため、ページはパネルの裏に隠れるのではなく脇に回り込みます。選んだモードはページをまたいで保持されます。
- **アイドル時のゴースト表示。** 操作していない間はパネルを薄く表示するモードです。色の変更をページ全体で確かめたいときに使えます。
- **ヒストリーレールと A/B スナップショット。** 編集の取り消し／やり直しに加えて、トークンの状態をまるごと保存して切り替えられるスロットが 2 つあります。
- **トークン検索とコマンドパレット。** 全タブ横断のあいまい検索（タブごとのヒット数付き）と、目的のトークンへ直接飛べる `Cmd/Ctrl+K` のコマンドパレットです。
- **変更トークンの可視化。** 編集した行にはマークが付き、タブにはバッジが表示され、フッターに合計（"*N* tokens changed across *M* tabs"）が出ます。**Changed only** で変更行だけに絞り込み、**Copy diff** で差分をクリップボードにコピーし、**Revert all** ですべて元に戻せます。Font・Spacing・Size の各タブには、そのタブだけを戻す **Reset** も用意されています。
- **チェーンポップオーバー。** トークンごとの解決チェーン — 何を参照しているか、何から参照されているか、現在のページで実際にいくつの要素が使っているか — を表示します。

## キーボードアクセシビリティ

タブの切り替えは標準的なWAI-ARIA Tabsパターンを実装しており、シェル側にもパネル全体のショートカットがあります:

| キー | 動作 |
|---|---|
| `←` / `→` | 前／次のタブにフォーカスを移動 |
| `Home` | 最初のタブにフォーカス |
| `End` | 最後のタブにフォーカス |
| `Enter` / `Space` | フォーカス中のタブをアクティブ化 |
| `Escape` | パネルを閉じる |
| `Alt+1` … `Alt+4` | フロート／右ドック／下ドック／ミニピルに切り替え |
| `Cmd/Ctrl+K` | コマンドパレットを開く |
| `I` | 要素インスペクトを有効化（そのあと要素をクリック） |

タブは自動アクティブ化方式で、矢印キーでフォーカスを移動するとパネルの表示も切り替わります。各タブ内のコントロールはすべて `Tab` で辿れ、いずれにも可視のフォーカスリングが用意されています。

## Apply がこのショーケースで無効な理由

パネルには、現在の差分をHTTPエンドポイント経由でソースファイルへ書き戻す **Apply** ボタンがあります。ただしこのショーケースでは意図的に機能しません。zdtpはApplyの有効化条件として `applyEndpoint` と空でない `applyRouting` マップの両方を要求しますが、zudo-doc のパッケージデフォルトビルダーはそのどちらも設定していないためです。ボタンは無効のままで、"Apply unavailable: configure an apply endpoint and a non-empty routing map." というツールチップが表示されます。ここでサポートされるラウンドトリップ手段は、以下のJSONエクスポート／インポートです。

## JSONエクスポート — 既定では差分のみ

パネルヘッダーの **Export** をクリックすると、エクスポートモーダルが開きます。モーダルには、各タブの現在の状態を表す単一のJSONドキュメントが表示され、タブ ID をキーとする `tabs` オブジェクトでラップされています。

既定のエクスポートは**デフォルトとの差分**です — 実際に変更したトークンだけが含まれます。まったく触っていないタブは丸ごと省略され、変更があったタブのなかでも編集したトークンだけが残ります。差分は小さく、diffしやすく、コミットに貼り付けても安定した出力になります。

```json
{
  "$schema": "zudo-design-tokens/v3",
  "exportedAt": "2026-07-05T12:00:00.000Z",
  "tabs": {
    "palette": {
      "raw": {
        "--palette-accent-1": "oklch(0.700 0.158 62)"
      }
    },
    "color": {
      "semantic": {
        "--zd-bg": { "ref": { "tab": "palette", "tier": "base", "item": "base-3" } },
        "--zd-danger": {
          "literal": { "light": "oklch(.505 .170 25)", "dark": "oklch(.655 .170 25)" }
        }
      }
    },
    "spacing": {
      "raw": { "--spacing-hsp-md": "1.25rem" }
    }
  }
}
```

`$schema` は、変更されたすべての値がプレーンな文字列またはパレットインデックスの数値である間は `zudo-design-tokens/v2` ですが、いずれかの Color タブの行が `{ref}` または `{literal}` オブジェクトを持った瞬間に自動的に `/v3` へアップグレードします — モードスコープのモデルでは、これは実際の zudo-doc のエクスポートでは通常のことです。`ref` は Palette タブのランプストップ（`{tab, tier, item}`）を指し、`literal` は単一の OKLCH 文字列か、**Per-mode** 行の場合は `{light, dark}` のペアのいずれかです。**Load** で再インポートすると、どちらの形状もきれいにラウンドトリップします。

**Load from JSON…** で Color タブの `literal` に単一の `light-dark(...)` 文字列を読み込むと、スウォッチを開いたときに式についての説明が表示され、HEX 入力と色のスライダーは無効になります。**Edit as literal** を選ぶと操作できるようになり、色を編集すると式がリテラルの色に置き換わります。この単一文字列の読み込みは、上で説明した **Per-mode** の `{light, dark}` ペアとは異なります。

モーダルヘッダーの **Show defaults too** トグルを有効にすると、完全なトークンツリー（すべてのタブ、すべてのトークン、未変更のデフォルトを含む）を出力できます。テーマの全容を知らないデザイナーやAIに渡すスナップショットが必要な場合は、この形式を使ってください。

## JSONから読み込み

**Load** ボタンを押すとインポートモーダルが開きます。以前のエクスポートやAIが生成したJSONドキュメントを貼り付けると、パネルは次の処理を行います:

1. 形状を検証（バージョン・既知のキー・値の妥当性）。
2. 不明なキーや不正な値を致命的でない警告として報告。
3. ドキュメントを現在のデフォルトにマージ。未指定のトークンはデフォルトのまま、明示されたトークンだけが上書きされます。
4. 結果を `localStorage` に永続化し、すべてのCSSカスタムプロパティを即時に再適用。

不正なJSONは具体的なエラーメッセージとともに拒否されます。`spacing.hsp.md` だけを変更するような部分的なインポートも、正規のユースケースとしてサポートされます。

## AIワークフロー

差分のみのエクスポートとJSONロードを組み合わせると、チャット型AIとの単純なラウンドトリップが実現できます:

1. 渡したいトークンを**編集**（触らなくてもOK）。
2. 差分を**エクスポート**してJSONをコピー。
3. AIチャットに**貼り付け**、自然言語でリクエストを添える。例:

   > これは現在のデザイントークンの差分です。カラーパレットはそのままに、より温かみがあり、コントラストの強いタイポグラフィスケールに書き換え、JSONだけを返してください。

4. AIが返したJSONを **Load** に**貼り付け**。
5. パネルが検証・マージ・反映を即座に行います。結果が気に入らなければ、変更トークンのツールバーにある **Revert all** ですべて巻き戻せます。

既定のエクスポートが差分になっているため、AIは気にしているトークンだけを読むことになり、未変更のデフォルトを推理する必要がありません。AIに全体像が必要な場合（例: ゼロから新しいテーマを作らせるとき）にだけ **Show defaults too** をオンにしてください。

## 永続化

パネルが記憶する内容はすべて、パッケージのデフォルトビルダーが固定しているストレージプレフィックス `zudo-doc-tweak` の配下に置かれます。トークンの状態そのものは単一のバージョン付きエンベロープに入り、残りはパネルのシェルや機能ごとの状態です。

| キー | 内容 |
|---|---|
| `zudo-doc-tweak-state-v4` | 統合トークンエンベロープ。各タブの状態を横並びに保持します。カラー状態はアクティブなスキーム識別子ごとにキー付けされるため、ライトとダークの編集を独立して復元できます。旧形式の `-state-v3`・`-state-v2`・`-state` も読み込まれ、前方に移行されます。 |
| `zudo-doc-tweak-open` | パネルを開いたままにしていたか |
| `zudo-doc-tweak-position`・`-size` | フロート時の位置とサイズ |
| `zudo-doc-tweak-dock`・`-dock-size` | ドックモード（`float` / `right` / `bottom` / `mini`）とドックの寸法 |
| `zudo-doc-tweak-spawn-ordinal` | パネルのカスケード表示位置。body の入れ替えをまたいで保持されます |
| `zudo-doc-tweak-density` | タブグリッドの密度設定 |
| `zudo-doc-tweak-ghost` | アイドル時のゴースト表示設定 |
| `zudo-doc-tweak-specimen`・`-on-page-specimen` | フォントスペシメンのツールバー設定と、スペシメンをホストページ上に表示するかどうか |
| `zudo-doc-tweak-snapshot-a`・`-snapshot-b` | A/B スナップショット |
| `zudo-doc-tweak-last-applied` | Apply の比較基準（Apply が無効なこのサイトでは未使用） |
| `zudo-doc-tweak:visible`・`zudo-doc-tweak:autoload` | 遅延読み込みの表示意図フラグと、オーナーモードの自動読み込みフラグ |
| `zudo-doc-tweak-elpath-enabled`・`-domtweaker-enabled` | エレメントパスピッカー／DOM Tweaker の有効フラグ |
| `zudo-doc-tweak-highlight-slots`・`-highlight-outline-width` | トークンハイライトのスロット色とアウトライン幅 |
| `zudo-doc-tweak-highlight-active` | アクティブなハイライトのマップ。ここだけは `localStorage` ではなく `sessionStorage` に保存されます |

このキー一覧は `@takazudo/zdtp` が所有しており、将来のリリースで増える可能性があります。zudo-doc が保証するのはプレフィックスであって、個々のキーではありません。

**Revert all** は全タブの変更トークンをまとめて元に戻し、Font・Spacing・Size タブの **Reset** はそのタブだけを戻します。いずれも設定済みのデフォルトに復帰させる操作です。ストレージ形式とライフサイクルはインストール済みの `@takazudo/zdtp` パッケージが所有し、zudo-doc 自身は永続化エンベロープを解析・変換しません。

実効ライト/ダーク表示が変わっても保存済みの編集が消えることは**なく**、開いているパネルは表示変更をまたいで開いたままになります。外観メニューは実効表示が変わった場合だけ `color-scheme-changed` イベントをディスパッチし、ホストはパネルをその場でパッチするのではなく、新しいモード用に destroy して再構成します（[実装](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel#%E5%AE%9F%E8%A3%85)を参照）。そのため、Color タブの ref/literal の*デフォルト*は新しくアクティブになったモードに追従し、保存済みの Color オーバーライドもそのモードのスキーム識別子に対して復元されます。デバイス設定と現在の表示が一致しているときに System を選んだ場合は、保存される設定だけが変わり、パネルは再構築されません。Palette・Spacing・Font・Size の編集はそのまま引き継がれます。[モードスコープの編集モデル](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel#%E3%82%BF%E3%83%96%E6%A7%8B%E6%88%90-color-%E3%83%A2%E3%83%BC%E3%83%89%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E3%81%AE%E7%B7%A8%E9%9B%86%E3%83%A2%E3%83%87%E3%83%AB)を参照してください。

[Dynamic content unavailable in text export]

ストレージプレフィックス `zudo-doc-tweak` は、パッケージのデフォルトビルダー（`@takazudo/zudo-doc/design-token-panel-config`、このショーケースもそのまま利用しています）に固定されており、変わりません。そのため、以前のリリースで保存した編集内容もそのまま引き継がれます。それ以外の永続化の詳細は `@takazudo/zdtp` が所有し、リポジトリ内の zudo-doc 互換レイヤーには属しません。



## ランタイム依存とCSS

インストールされた公開`@takazudo/zdtp` 0.8.6は、`preact: ^10.29.1`を自身の依存として含みます（0.8.5まではプロジェクト側でインストールが必要なpeerでした）。パネルを有効にする場合やカスタムマウント用にバンドルする場合も、zdtpをインストールするだけで済みます。zudo-doc自体のコンポーネントはzudo-reactを使い、Preactノードを必要としません。パネルは独立したPreactサブツリーとTailwind browser engineを所有し、サイトのユーティリティを登録しません。

```sh
pnpm add @takazudo/zdtp@0.8.6
```

```css
@import "@takazudo/zdtp/styles.css";
```

`./styles.css`と`./styles`は同じ配布CSSの公開エクスポートです。他の先頭インポートと一緒に置き、物理的な`dist/`パスを参照しないでください。スキャフォールドが追加するのはパネル機能を選択した場合だけです。

## 実装

パネルUIは `@takazudo/zdtp`（zdtp）npmパッケージが提供します。パッケージ所有の `DesignTokenPanelBootstrap` アイランドと、`configurePanel` の呼び出し、zfb のナビゲーションライフサイクルへのフック、および後述のモードスコープな destroy+reconfigure の仕組みは `@takazudo/zudo-doc/design-token-panel-bootstrap` にあります。固定ターゲットのサーバー境界がクライアントbootstrapをマウントし、`scope.onActivate`内で`runDesignTokenPanelBootstrapOnce(buildDesignTokenPanelConfig, "default", scope.abortSignal)`を呼び出します。`buildDesignTokenPanelConfig` は `(mode) => PanelConfig` のビルダーであり、これが Color タブをライト/ダークに追従させる仕組みです。アイランドがハイドレートすると、パネルはその副作用として自己マウントされます。

### 遅延読み込み

`@takazudo/zdtp` は初期ページバンドルには含まれません。`bootstrapDesignTokenPanel` はマウント時に小さな暫定クリックリスナーを登録するだけで、実際の zdtp の動的インポート（とそれに続く `configurePanel` の呼び出し）は、ヘッダーのトリガーが最初に `toggle-design-token-panel` をディスパッチしたときに実行されます。インポートが解決する前に届いたクリックはキューにためられ、configure完了後に正味1回分の表示/非表示にまとめられるため、すばやい連続クリックが二重に発火することはありません。

ブートストラップは、パッケージ所有のサブパス `@takazudo/zudo-doc/zdtp-loader` を経由してのみ zdtp を読み込みます。プリセットは、zdtp がバンドルされていない場合 — `bundleZdtp ?? designTokenPanel` として解決されます — にこの指定子だけをスタブモジュールに差し替えます。そのため `bundleZdtp` を一度も設定しないプロジェクトは、これまでどおりの挙動のままです: パネルがオフなら zdtp のチャンクは一切出力されません。`bundleZdtp: true` を設定すれば、パネルがオフのままでも本物のローダーを維持できます — その理由は[自前のパネルをマウントする](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel#%E5%AE%9F%E8%A3%85-%E8%87%AA%E5%89%8D%E3%81%AE%E3%83%91%E3%83%8D%E3%83%AB%E3%82%92%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E3%81%99%E3%82%8B)を参照してください。ホスト側で独自に書いた `import "@takazudo/zdtp"` には、どちらの場合も影響しません。

保存済みの状態を持つ再訪ユーザーは、そのクリックを待つ必要がありません。マウント時にブートストラップは、アクティブな（パックスコープの）ストレージプレフィックス配下の `localStorage` を確認し、以下のいずれかが真であれば、操作前に即座に zdtp を読み込みます:

- パネルが開いたままだった（`${prefix}-open` が `"1"`）
- トークンオーバーライドが保存されていた（`${prefix}-state*` というキーが存在する）
- オーナーモードのフラグが設定されている（`${prefix}:autoload`・`${prefix}-elpath-enabled`・`${prefix}-domtweaker-enabled` のいずれか）

`${prefix}-open` とオーナーモードのチェックはキーを直接指定した参照ですが、`${prefix}-state*` のチェックだけは、その時点で `localStorage` に存在するすべてのキーを走査します（プレフィックスで問い合わせるネイティブAPIは存在しないため）。ただし、アクティブなプレフィックスの厳密な語幹で始まるキーだけをヒットとして扱うため、すべてのキー名には目を通しつつも、無関係な別アプリ自身の `*-state` キーに誤反応することはありません。保存済み状態を持つテーマパックへの切り替えも、同様の即時読み込みを引き起こします。

いずれの経路でも zdtp が読み込まれるまでは、`window.zdtp`（zdtp が構成完了時にインストールするコンソールAPI）は存在しません。一度読み込まれると、そのページが存続する間はその読み込まれた状態を保ち続けます — トグルイベントは zdtp 自身のリスナーが直接引き継ぎます。この遅延状態に戻るのは、ページを丸ごと再読み込みしたときだけです。

実効表示が変わったときにディスパッチされる `color-scheme-changed` イベント（System 選択中の OS 変更を含む）のたびに、ブートストラップは新しいモードを読み取り、素早い連続変更を 1 つのマクロタスクにまとめてから、`handle.destroy()` に続けて `configurePanel(buildDesignTokenPanelConfig(mode))` を呼び出します — zdtp の `configurePanel` は、構造的に異なる設定でその場で再実行すると例外を投げるため、destroy してから再構成することが、Color タブのモードスコープなデフォルトを差し替える唯一のサポートされた方法です。保存設定が変わっても実効表示が同じ場合は `theme-preference-changed` イベントで通知し、パネルは再構築しません。パネルが開いていた場合は、開いた状態で再マウントされます。

ヘッダーのトリガーボタンは `window` に `toggle-design-token-panel` カスタムイベントをディスパッチします。zdtpはこのイベントをネイティブに受け取るため、`headerRightItems` フィールドでトリガーをカスタマイズしても追加の配線は必要ありません。

### 自前のパネルをマウントする

`designTokenPanel` と `bundleZdtp` はまったく別のことを制御しており、これを混同したことが zudo-doc#4261 の原因そのものでした。`designTokenPanel` が制御するのは**マウント**（パッケージ所有の `DesignTokenPanelBootstrap` アイランドを描画するかどうか）で、`bundleZdtp` が制御するのは**バンドル**（`@takazudo/zdtp` をビルドに含めるかどうか）です。両者はデフォルトで互いに連動します（[遅延読み込み](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel#%E5%AE%9F%E8%A3%85-%E9%81%85%E5%BB%B6%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF)を参照）が、パッケージのパネルではなく自前のパネルアイランドをマウントしたいホストは、この2つを独立に設定する必要があります。

パッケージのアイランドが呼んでいるのと同じブートストラップを、自前の `"use client"` アイランドから呼び出します。その際、`bootstrapDesignTokenPanel` を直接呼ぶのではなく `runDesignTokenPanelBootstrapOnce` を経由してください。再レンダリングやソフトナビゲーションで2回目のブートストラップが走るのを防ぐのが、この once ラッチの役割です。2回ブートストラップされると `toggle-design-token-panel` のリスナーが二重に登録され、トリガーを1回クリックしただけでトグル意図が2回分としてカウントされ、パネルが開かなくなります。

```tsx
"use client";

import { getScope } from "@takazudo/zfb/zudo-react";
import { runDesignTokenPanelBootstrapOnce } from "@takazudo/zudo-doc/design-token-panel-bootstrap";
import { buildDesignTokenPanelConfig } from "@/config/design-token-panel-config";

export function HostPanelBootstrap() {
  const scope = getScope();
  scope.onActivate(() => {
    runDesignTokenPanelBootstrapOnce(buildDesignTokenPanelConfig, "configured", scope.abortSignal);
  });
  return null;
}
```

`HostPanelBootstrap` を静的にインポートし、`Island({ when: "load", children: <HostPanelBootstrap /> })` を Fragment で包んで返すサーバーコンポーネントを `BodyEndIslands` に渡します。クライアントのブートストラップはハイドレーション後に起動し、SSR 中にはパネルを設定しません。バインディングオブジェクト経由で渡す場合も Fragment を維持してください。

これを機能させるには、2つの設定を連動させて変更します:

- `designTokenPanel: false` — パッケージ側は何もマウントしません。パネルを描画するのは自前のアイランドだけになります。
- `bundleZdtp: true` — ローダーを本物のまま維持します。パネルをオフにしたままこれを未設定にすると、`@takazudo/zudo-doc/zdtp-loader` は例外を投げるスタブに差し替わり、ビルド自体は正常に成功したように見えても、パネルを開いた瞬間に自前のブートストラップの動的インポートが失敗します。

トリガーもホスト側の担当です。`designTokenPanel: false` にすると、`settings.headerRightItems` の組み込み `design-token-panel` トリガーは除外されます（パッケージ側のパネルアイランドが無い状態でイベントだけをディスパッチする「死んだボタン」を描画しないため）。そのため自前のパネルには自前のボタンが必要です — `headerRightComponents` 経由で解決される `headerRightItems` エントリでも、任意の要素でもかまいませんが、`window` に `toggle-design-token-panel` をディスパッチしてください。

`@takazudo/zdtp`（`@takazudo/zudo-doc` のオプショナルなピア依存）自体のインストールと、そのスタイルシートのインポート — 自前の `global.css` に `@import "@takazudo/zdtp/styles.css"` を書くこと — もホスト側の作業です。この行を代わりに書いてくれるものはここにはありません: `create-zudo-doc` がこの行を書き込むのは、`designTokenPanel` をオンにしてスキャフォールドしたときだけです。

`bundleZdtp: true` のまま `@takazudo/zdtp` がインストールされていない場合、`@takazudo/zudo-doc/zdtp-loader` の静的再エクスポートの解決に失敗し、ビルドが失敗します。これは意図的な、サイレントなランタイム障害よりも望ましい失敗のさせ方です。

[Dynamic content unavailable in text export]

`designTokenPanel: true` と `bundleZdtp: false` の組み合わせは、設定解決の時点で拒否されます。パッケージのパネルがマウントされているのにローダーがスタブ化されていると、開いた瞬間に確実に例外が発生するため、zudo-doc はそれをビルドしてしまう代わりにビルドを失敗させます。



## 設定リファレンス

| 設定 | 型 | 説明 |
|---|---|---|
| `designTokenPanel` | `boolean` | パッケージ自身のパネルアイランドをマウントします。デフォルトは `false`。 |
| `bundleZdtp` | `boolean` | `@takazudo/zdtp` をビルドにバンドルします。デフォルトは `undefined`（`designTokenPanel` に追従: `bundleZdtp ?? designTokenPanel`）。`designTokenPanel: false` のまま `true` にすると、パッケージのパネルはマウントせずに zdtp だけをバンドルします — 自前のパネルをマウントするための組み合わせです（[自前のパネルをマウントする](https://zudo-doc.takazudomodular.com/ja/docs/reference/design-token-panel#%E5%AE%9F%E8%A3%85-%E8%87%AA%E5%89%8D%E3%81%AE%E3%83%91%E3%83%8D%E3%83%AB%E3%82%92%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E3%81%99%E3%82%8B)を参照）。`designTokenPanel: true` のまま `false` にすると設定解決の時点で拒否されます。 |
| `designTokenPanelConfigModule` | `string` | 完全にカスタムなパネル用に `buildDesignTokenPanelConfig(mode)` をエクスポートするモジュールへの、プロジェクトルート相対パス。デフォルトは `undefined`（パッケージのデフォルトビルダーを使用）。 |

デフォルトのトークンそのもの（スペーシングスケール・フォントスケール・アイコンサイズ・パレットのエイリアス）は`@takazudo/zudo-doc/theme.css`から提供されます。スキャフォールドの後続の`src/styles/global.css`にある`:root`ブロックはオーバーライドポイントです。カラーランプとマッピングには、パッケージの`@takazudo/zudo-doc/color-schemes-defaults`または自作の`colorSchemes`上書き（[Color](https://zudo-doc.takazudomodular.com/ja/docs/reference/color)を参照）を使います。パネルはブラウザ内のコピーを編集するだけで、ソースファイルに書き戻すことはありません。

[Dynamic content unavailable in text export]

気に入った組み合わせが見つかったら、差分をエクスポートしてコミットメッセージやデザインドキュメントに貼り付けてみてください — いま行った変更を再現するもっとも小さな説明になります。