> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# モッドでインターフェースに描画する

> Claude Code モッドからペイン、プロンプト上部のバンド、ボタン、テキストフィールドを描画し、押下と入力を処理し、再描画とセッション間で状態を保持します。

モッドは Claude Code でそれ自身のインターフェースを描画し、Claude Code が既に描画しているインターフェースの一部を変更できます。モッドが描画できる各場所は[レンダーサイト](/docs/ja/plugins/mods/reference#render-sites)と呼ばれます。例えば、ペイン、プロンプト上部のバンド、またはスピナーなどです。Claude Code はレンダーサイトを描画しようとするたびに[`ui.render`](/docs/ja/plugins/mods/reference#interface)イベントを発生させ、そのイベントのフックはそこに何を描画するかを返します。

このマップは、モッドがターミナルセッションのどこに描画できるかを示しています。

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5fda26b6609c62b68c6f9e528c1590ea" className="dark:hidden" alt="Claude Code ターミナルセッションのマップ。モッドはトランスクリプトの右側にペインをサイドバーとして追加でき、トランスクリプトの右上にトーストを追加でき、トランスクリプトにログ行を追加でき、プロンプト上部にバンドを追加でき、プロンプト下部にステータス行を追加できます。モッドはメッセージ、ツール呼び出し行、スピナーを再描画できます。プロンプトは Claude Code 独自のものです。" width="600" height="336" data-path="images/mods-screen-map.svg" />

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map-dark.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5b4161581a1bd2c0450b0c8b57bc1225" className="hidden dark:block" alt="Claude Code ターミナルセッションのマップ。モッドはトランスクリプトの右側にペインをサイドバーとして追加でき、トランスクリプトの右上にトーストを追加でき、トランスクリプトにログ行を追加でき、プロンプト上部にバンドを追加でき、プロンプト下部にステータス行を追加できます。モッドはメッセージ、ツール呼び出し行、スピナーを再描画できます。プロンプトは Claude Code 独自のものです。" width="600" height="336" data-path="images/mods-screen-map-dark.svg" />

より狭いターミナルでは、ペインはトランスクリプトの横ではなくプロンプトの上に配置されます。

[最初のモッド](/docs/ja/plugins/mods/create)を作成してからここを始めてください。実装例から始めます。これは 2 つのタブとカウンターを持つペインを構築し、その後、変更したい各部分のセクションを読んでください。

<Note>
  1 つのプロップまたは制限を調べるには、[リファレンス](/docs/ja/plugins/mods/reference#render-sites)を参照してください。
</Note>

<h2 id="build-a-pane-with-tabs">
  タブ付きペインを構築する
</h2>

このセクションでは、`/hello-tabs` コマンドを追加するモッドを構築します。このコマンドはペインを開きます。ペインは、広いフルスクリーンターミナルではトランスクリプトの横にあるサイドバー、またはそれ以外の場合はプロンプト上部のフレーム領域です。このペインは 2 つのタブを表示し、2 番目のタブにはカウンターに 1 を加えるボタンがあります。カウントは Claude Code を再起動した後も残ります。

完成したモッドは次のようになります。記録はペインを開き、2 番目のタブに切り替え、ボタンを数回押し、最初のタブに戻ります。

<Frame>
  <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=49d520094d87b5b44bfe50fa49677f06" aria-label="/hello-tabs コマンドが Claude Code プロンプトに入力され、フレーム付きペインが上に開き、上部に「1: One」と「2: Two」があり、テキスト「This is the first tab.」が表示されます。2 番目のタブは「Add one」ボタンを「Count: 1」の横に表示し、カウントが 3 に上がります。ペインは最初のタブに戻ります。" data-path="images/mods-hello-tabs-light.mp4" />

  <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=ff7a14d713d6e5d3b0000efa8522ea4b" aria-label="/hello-tabs コマンドが Claude Code プロンプトに入力され、フレーム付きペインが上に開き、上部に「1: One」と「2: Two」があり、テキスト「This is the first tab.」が表示されます。2 番目のタブは「Add one」ボタンを「Count: 1」の横に表示し、カウントが 3 に上がります。ペインは最初のタブに戻ります。" data-path="images/mods-hello-tabs-dark.mp4" />
</Frame>

Claude Code には組み込みのタブ要素がないため、タブは行内の 2 つのボタンです。モッドはどのタブがアクティブかを追跡し、その行の下にそのタブのコンテンツを描画します。

<Steps>
  <Step title="プラグインを作成する">
    モッドはマニフェスト、`hooks.json` がコードを指す、およびコードファイルを持つプラグインです。[モッドを作成する](/docs/ja/plugins/mods/create#write-a-mod-yourself)は各ファイルについて説明しています。`hello-tabs` という名前のディレクトリを作成し、その中に `.claude-plugin` と `hooks` ディレクトリを作成してから、最初の 2 つのファイルを保存します。

    マニフェストを `hello-tabs/.claude-plugin/plugin.json` として保存します。

    ```json hello-tabs/.claude-plugin/plugin.json theme={null}
    {
      "name": "hello-tabs",
      "version": "0.1.0",
      "description": "Opens a pane with two tabs and a counter",
      "author": { "name": "Your Name" }
    }
    ```

    `hello-tabs/hooks/hooks.json` でエントリーポイントに名前を付けます。

    ```json hello-tabs/hooks/hooks.json theme={null}
    {
      "modules": ["./register.js"]
    }
    ```
  </Step>

  <Step title="コードを書く">
    コードは 3 つのジョブを実行し、各フックで 1 つずつ実行します。

    * `/hello-tabs` コマンドを追加する
    * そのコマンドを実行するときペインを開く
    * ペインのコンテンツを描画する。タブの行とオープンタブのボディ

    2 つのモジュールレベルの変数、`tab` と `count` がペインの状態を保持します。

    これを `hello-tabs/hooks/register.js` として保存します。

    ```javascript hello-tabs/hooks/register.js theme={null}
    // ペインの id。ペインを開くときと描画時に認識するために使用
    const PANE = 'hello-tabs'

    // ペインが表示するもの。どのタブがオープンか、カウンターの値
    let tab = 'one'
    let count = 0

    export function register(on) {
      // 最初のプロンプトの前に実行され、リロード後に再度実行
      on('session.start', async ($, e, next) => {
        await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
        // 以前のセッションが保存したカウントを読み込む（存在する場合）
        const saved = await $.store.get('count')
        if (typeof saved === 'number') count = saved
        return next(e)
      })

      // /hello-tabs を入力したときに実行
      on('command.run', { command: 'hello-tabs' }, async ($) => {
        // ペインを開き、キーボードを与え、Esc で閉じることを許可
        await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
        // トランスクリプトに何も出力しない
        return {}
      })

      // Claude Code がペインを描画するたびに実行
      on('ui.render', { component: 'Pane' }, async ($, e, next) => {
        // 他のモッドのペインはそのままにする
        if (e.requestId !== PANE) return next(e)
        // このアプリが描画できる要素を取得
        const { Box, Text, Button } = $.ui.resolve(e)
        // Claude Code にこのフックを再度実行するよう要求
        const redraw = () => $.ui.invalidate('ui.render')

        // 1 つのタブ。押されたときそのタブに切り替えるボタン
        const tabButton = (name, label, hotkey) =>
          Button({
            key: 'tab-' + name,
            label,
            hotkey,
            plain: true,
            // オープンされていないタブを暗くする
            dimColor: tab !== name,
            onPress: () => {
              tab = name
              redraw()
            },
          })

        // タブの下に表示されるもの。どのタブがオープンかによる
        const body =
          tab === 'one'
            ? [Text({ children: ['This is the first tab.'] })]
            : [
                Box({
                  flexDirection: 'row',
                  columnGap: 2,
                  children: [
                    Button({
                      key: 'more',
                      label: 'Add one',
                      hotkey: 'a',
                      onPress: async () => {
                        count += 1
                        redraw()
                        // カウントを保存して再起動後も残すようにする
                        await $.store.set('count', count)
                      },
                    }),
                    Text({ children: ['Count: ' + count] }),
                  ],
                }),
              ]

        // ペイン全体。タブの行、空行、その後ボディ
        return Box({
          flexDirection: 'column',
          children: [
            Box({
              flexDirection: 'row',
              columnGap: 3,
              children: [tabButton('one', 'One', '1'), tabButton('two', 'Two', '2')],
            }),
            Text({ children: [' '] }),
            ...body,
          ],
        })
      })
    }
    ```

    各フックはコードが明確にしていないことも実行します。

    * \*\*[`session.start`](/docs/ja/plugins/mods/reference#session)\*\*は[`$.store`](#keep-state)から保存されたカウントも読み込みます。これはセッション間で永続化するキー値ストアです。
    * \*\*[`command.run`](/docs/ja/plugins/mods/api#add-a-command)\*\*は Claude Code にペインが存在することを伝えるだけです。ペインを開くこと自体は何も描画しません。Claude Code は `ui.render` を発生させてそこに何が入るかを尋ねます。
    * \*\*`ui.render`\*\*は要素ツリーを返します。これは他のボックス、テキスト、ボタンを保持する `Box` であり、実行されるたびに `tab` と `count` から再度構築されます。

    ボタンを押すとその `onPress` コールバックが実行され、変数が変更され、`redraw` が呼ばれます。Claude Code は `ui.render` フックを再度実行し、フックは新しい値から新しいツリーを構築します。すべてのインタラクティブな描画はそのレンダーサイクルを使用します。コールバックが状態を変更し、フックが新しい状態から再度レンダリングします。
  </Step>

  <Step title="ペインを開く">
    シェルで `claude --plugin-dir ./hello-tabs` で Claude Code を起動します。Claude Code プロンプトで `/hello-tabs` を実行します。ペインが開き、上部に `1: One` と `2: Two` が表示されます。`2` を押してから、**Add one** のホットキーである `a` を数回押します。カウントが上がります。
  </Step>

  <Step title="カウントが保存されたことを確認する">
    Esc を押してペインを閉じ、セッションを終了します。シェルで同じ `claude --plugin-dir ./hello-tabs` コマンドで Claude Code を再度起動し、Claude Code プロンプトで `/hello-tabs` を実行します。カウントは残したままです。

    カウントをクリアするには、モッドに `$.store.delete('count')` を呼び出させます。[状態を保持する](#keep-state)は各種類の値がどのくらい続くかをカバーしています。
  </Step>
</Steps>

<h2 id="pick-where-to-draw">
  描画する場所を選ぶ
</h2>

`ui.render` フックは、希望するレンダーサイトに絞り込まない限り、すべてのレンダーサイトに対して実行されます。レンダーサイトを選択するには、`on` の 2 番目の引数として[マッチャー](/docs/ja/plugins/mods/events#filter-which-events-a-hook-handles)と呼ばれるフィルターを渡します。`{ component: 'Pane' }` はペインに対してのみフックを実行します。フック内では、`e.component` がサイトに名前を付け、`e.surface` はどのアプリが描画しているかを示し、`e.props` はサイト独自のデータを保持します。ペインの場合、`e.requestId` はそれを開いた `id` です。

2 つのサイトはモッドがそれらを埋めるまで空です。ペインとバンドです。タブを選択して、各サイトが何であり、どのように描画するかを確認してください。

<Tabs>
  <Tab title="ペイン">
    ペインは、広いフルスクリーンターミナルではトランスクリプトの横にあるサイドバー、またはそれ以外の場合はプロンプト上部のフレーム領域です。複数のペインがオープンされている場合、各ペインはそのタイトルを表示するタブを取得します。

    ペインは、モッドが `$.ui.open` を `id` で呼び出すときに表示されます。例えば `$.ui.open({ id: 'hello-tabs' })` のように。[適切なタイミングでペインを開く](#open-a-pane-at-the-right-time)は他のフィールドと、ペインがより広いターミナルを待つときをカバーしています。

    ペインに描画するには、`{ component: 'Pane' }` でフィルターし、`e.requestId` が `id` であることを確認します。
  </Tab>

  <Tab title="プロンプト上部のバンド">
    バンドはプロンプト入力の直上のストリップです。常に存在し、すべてのモッドがそれを共有します。

    フックは何かを表示するツリーを返すか、何も表示しない場合は `next(e)` を返します。ツリーは、モッド[の後に実行される](/docs/ja/plugins/mods/events#the-order-mods-run-in)ものが描画するものを置き換えます。それらを保持するには、`await next(e)` の結果をツリー内の[`Box`](#build-a-tree-from-elements)の子の中に配置します。

    バンドに描画するには、`{ component: 'AbovePrompt' }` でフィルターします。
  </Tab>
</Tabs>

<h3 id="change-what-claude-code-already-draws">
  Claude Code が既に描画しているものを変更する
</h3>

Claude Code はそのインターフェースのほとんどを自分で描画します。メッセージ、ツール呼び出し行、スピナーなど。これらの各部分もレンダーサイトであるため、モッドはそれをリスタイルまたは置き換えることができます。1 つを変更するには、`ui.render` フックをこのテーブルの名前でフィルターします。

| サイト | それが何であるか |
| :- | :- |
| `UserMessage`、`AssistantMessage` | トランスクリプト内のメッセージ |
| `ToolUse`、`ToolResult`、`ToolGroup` | ツール呼び出しの行、その結果、および折りたたまれた呼び出しの実行 |
| `CommandOutput` | コマンドが出力した行 |
| `AskUserQuestion` | Claude があなたに質問するために開くダイアログ |
| `Spinner`、`ToolProgress`、`TurnDuration` | ターンのステータス行。Claude が作業している間にアニメーションする行、実行中のツールのライブ進捗行、ターンを閉じる行 |
| `InfoNotice`、`SessionMode`、`PromptHint` | ロゴの下のステータス行、フッターのモードラベル、プロンプトの下のヒント行 |

Claude Code が既に描画しているサイトでは、フックには 3 つの選択肢があります。詳細を変更する、描画を置き換える、またはそのままにする。タブを選択して、スピナーに適用された各ものを確認してください。例は、[チュートリアルモッド](/docs/ja/plugins/mods/create#write-a-mod-yourself)のように別のフックがカウントする `calls` 変数を読みます。

<Tabs>
  <Tab title="詳細を変更する">
    Claude Code の描画を保持し、その一部を変更するには、変更された `props` を持つイベントのコピーを `next` に渡します。このフックはスピナーの単語の後のテキストを変更します。

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // Claude Code のスピナーを保持し、その単語の後のテキストを変更
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    スピナーはそのアニメーションと単語を保持し、テキストが単語に続きます。

    ```text theme={null}
    Thinking · tool calls: 2…
    ```
  </Tab>

  <Tab title="描画を置き換える">
    スピナーがある場所に独自のツリーを描画するには、ツリーを返し、`next` を呼び出さないでください。このフックはスピナーがある場所にテキストの 1 行を描画します。

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e) => {
      const { Text } = $.ui.resolve(e)
      // next への呼び出しがないため、この行はスピナーの場所に描画されます
      return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
    })
    ```

    Claude が作業している間、行が表示され、Claude Code のスピナーは表示されません。

    ```text theme={null}
    Claude has made 2 tool calls
    ```
  </Tab>

  <Tab title="そのままにする">
    Claude Code が描画するようにサイトをそのままにするには、`next(e)` を返します。フックはしばしば一部のイベントに対してそれを実行し、他のイベントに対しては実行しません。このフックはカウントするまでスピナーをそのままにします。

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // まだ表示するものがないため、イベントを変更せずに渡す
      if (calls === 0) return next(e)
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    最初のツール呼び出しの前に、スピナーはモッドなしで表示される方法で表示されます。

    ```text theme={null}
    Thinking…
    ```
  </Tab>
</Tabs>

権限プロンプトはレンダーサイトではないため、モッドはそれが表示するものを変更できません。質問ダイアログ `AskUserQuestion` は 1 つであるため、モッドはそれを変更できます。

ターミナルと Desktop アプリはすべての同じサイトを発生させません。`Pane`、`AbovePrompt`、`Spinner`、およびトランスクリプトサイトは両方で機能します。他のいくつかのステータス行はターミナルでのみ発生します。[レンダーサイトテーブル](/docs/ja/plugins/mods/reference#render-sites)は各サイトが発生する場所をリストしています。

<h3 id="open-a-pane-at-the-right-time">
  適切なタイミングでペインを開く
</h3>

ペインはモッドがそれを開くときにのみ表示されます。どのように、いつ開くかは、キーボードフォーカスを取得するかどうか、どのくらいのスペースを要求するか、および狭いターミナルで表示されるかどうかを決定します。

ペインを開くには、選択した `id` で[`$.ui.open`](/docs/ja/plugins/mods/reference#mods-api-methods)を呼び出します。`id` はペインの名前です。`ui.render` フックはそれをチェックし、ペインを閉じるときに再度渡します。

```javascript theme={null}
await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
```

ペインを閉じるには、それを開いた `id` で `$.ui.close` を呼び出します。

```javascript theme={null}
await $.ui.close({ id: 'hello-tabs' })
```

`id` の他に、`$.ui.open` はこれらのオプションフィールドを取ります。

| フィールド | それが実行すること |
| :- | :- |
| `title` | 複数のペインがオープンされているときのペインのタブラベル |
| `focus` | [キーボードフォーカス](#know-which-keys-your-mod-can-receive)をリクエスト |
| `closeOnEscape` | Esc でペインを閉じるようにします。`true` を渡すか、フィールドを省略してください。Claude Code は `false` を拒否します。 |
| `holdToasts` | [`$.ui.toast`](/docs/ja/plugins/mods/api#show-something-without-starting-a-turn)からの小さな通知であるトーストを、ペインが閉じるまで保持します |
| `rows` | ペインがプロンプト上部に配置されるときに要求する高さ。デフォルトはスペースの 3 分の 1 です。 |
| `columns` | ペインがトランスクリプトの横に配置されるときに要求する幅 |

Claude が作業している間にコマンドがペインを開くようにするには、[コマンドを登録](/docs/ja/plugins/mods/api#add-a-command)するときに `immediate: true` を追加します。それなしでは、ターン中に入力されたコマンドはターンが終わるまで待ちます。

<h4 id="when-a-pane-waits-for-a-wider-terminal">
  ペインがより広いターミナルを待つとき
</h4>

モッドが要求されずに開くペインは狭いターミナルに表示されないため、小さな画面を引き継ぐことはできません。表示されるかどうかは、それを開いたものによって異なります。

* **ユーザーが実行したコマンドやボタンを押すなど、ユーザーが実行したもの**によって開かれた場合、ペインは任意の幅で表示されます
* **タイマーまたは[`turn.start`](/docs/ja/plugins/mods/events#follow-a-turn)フックなど、モッドが自分で実行したもの**によって開かれた場合、ペインは少なくとも 144 列幅のターミナルでのみ表示されます。ユーザーが一度そのペインを自分で開いた後は、110 列で十分です。

ペインが表示されるとき、`$.ui.open` は `{ isPlaced: true }` に解決されます。ペインが待機しているとき、`isPlaced` は `false` で、`reason` は理由を説明する文字列です。待機中のペインはユーザーがそれを開くか、ターミナルを広げるときに表示されます。ペインを開かずに何かが利用可能であることを言うには、`$.ui.toast('Your message')` を呼び出します。これは数秒後に消える小さな通知を表示します。

<h2 id="build-a-tree-from-elements">
  要素からツリーを構築する
</h2>

`ui.render` フックが返すものは要素ツリーです。これは描画する内容の説明であり、ボックス、テキスト、および制御が互いにネストされています。描画を説明し、Claude Code はターミナルまたは Desktop アプリでそれをレンダリングします。

要素を取得するには、フック内で `$.ui.resolve(e)` を呼び出します。例えば `const { Box, Text, Button } = $.ui.resolve(e)` のように。各要素は関数です。プロップを渡し、その中に入るべき要素と文字列を `children` に配置します。

ほとんどの描画は 4 つの要素を使用します。タブを選択して、各要素とターミナルがそれをどのように描画するかを確認してください。

<Tabs>
  <Tab title="テキスト">
    `Text` は文字列を描画し、`bold` や `color` などのオプションのスタイリングを使用します。

    ```javascript theme={null}
    Text({ children: ['This is the first tab.'] })
    ```

    ```text theme={null}
    This is the first tab.
    ```
  </Tab>

  <Tab title="ボックス">
    `Box` は内部にあるものを行または列に配置します。これは、ボタンとテキスト行を 2 列離して並べて配置します。

    ```javascript theme={null}
    Box({
      flexDirection: 'row',
      columnGap: 2,
      children: [
        Button({ key: 'more', label: 'Add one', onPress: addOne }),
        Text({ children: ['Count: 0'] }),
      ],
    })
    ```

    ```text theme={null}
    [ Add one ]  Count: 0
    ```
  </Tab>

  <Tab title="ボタン">
    `Button` はユーザーが押すことができるコントロールです。`onPress` コールバックを実行します。`plain: true` を使用すると、括弧がなく、ホットキーが表示されます。

    ```javascript theme={null}
    Button({ key: 'more', label: 'Add one', onPress: addOne })
    Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
    ```

    ```text theme={null}
    [ Add one ]
    1: One
    ```
  </Tab>

  <Tab title="入力">
    `Input` はテキストフィールドです。ユーザーが Enter を押すと、`onSubmit` コールバックをテキストで実行します。

    ```javascript theme={null}
    Input({
      key: 'new-note',
      label: 'Note',
      placeholder: 'Type a note and press Enter',
      value: '',
      submitLabel: 'add',
      onSubmit: addNote,
    })
    ```

    ```text theme={null}
    Note: Type a note and press Enter ⏎ add
    ```
  </Tab>
</Tabs>

このテーブルはすべての要素をリストしています。

| 要素 | それが描画するもの | どこで |
| :- | :- | :- |
| `Box` | フレックスコンテナ。`flexDirection`、`columnGap`、`padding`、`borderStyle`、`width` などのレイアウトプロップを取ります。 | どこでも |
| `Text` | スタイル付きテキスト。`color`、`bold`、`dimColor`、`italic`、`wrap` を取ります。`color` はテーマキーまたは `'red'` などの色です。`wrap` は `'wrap'`、`'truncate'`、`'truncate-start'`、`'truncate-middle'`、または `'truncate-end'` です。 | どこでも |
| `Button` | `onPress` を呼び出すコントロール | どこでも |
| `Link`、`Code`、`Markdown` | `href` とオプションの `label` を持つリンク、コードブロック、および Claude の返信の方法でフォーマットされたテキスト。`Markdown` は `children` ではなく `text` プロップでコンテンツを取り、`onLinkPress` を渡すときは `key` が必要です。 | どこでも |
| `Input`、`Select` | テキストフィールドとピッカー | ターミナル、Desktop |
| `Svg` | SVG ドキュメント | Desktop |
| `Client` | 2 番目のファイルで描画される領域。アニメーションとポインター入力用。そのファイルはモッド API を取得しません。フックに到達するのはデータを投稿することだけで、`ui.message` イベントとして到達します。 | ターミナル、Desktop |
| `Raster`、`Image` | [色付きセルのグリッド](#draw-a-grid-of-colored-cells)と画像 | ターミナル |

モジュールが `.tsx` または `.jsx` ファイルの場合、ツリーを JSX として記述できます。`$.ui.resolve(e)` から要素を分割代入してください。フックモジュールには要素グローバルがないためです。

ツリーがアプリが持たない要素、要素が取らないプロップ、または子が入らない場所を使用する場合、Claude Code はサイトの独自のバージョンを描画します。

`--plugin-dir` で開始されたセッションでは、トランスクリプト行がそう言います。例えば `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`。[デバッグログ](/docs/ja/plugins/mods/troubleshoot#read-the-debug-log)は `ui.render (Pane): a hook returned a tree that does not validate` として同じ理由で記録します。他に何も表示されないため、描画が表示されない場合は、その行またはログを確認してください。

<h3 id="draw-a-grid-of-colored-cells">
  色付きセルのグリッドを描画する
</h3>

ヒートマップ、スパークライン、またはターミナルのゲームボードの場合、各セルに対して 1 つの `Raster` を描画し、`Box` ではありません。`Raster` は `key`、`columns` と `rows` のサイズ、および `cells` を取ります。これはすべてのセルを 1 つの文字列にパックします。各セルは 3 つの数字です。文字のコードポイント、その色、背景色。色は `0xc62828` のような赤、または `0x01000000` のようなターミナルのデフォルトの 16 進数です。

Desktop アプリには `Raster` がないため、`e.surface` をチェックしてそこにテキストを描画します。このペインボディは 3 x 2 のヒートマップを描画します。

```javascript theme={null}
// 「ターミナルのデフォルト色を使用」を意味する値
const DEFAULT_COLOR = 0x01000000

// [文字、色] ペアの行を Raster が取る 1 つの文字列にパック
// 1 つのセルは 3 つの数字です。文字のコードポイント、その色、背景色
function cellsOf(rows) {
  const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
  return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
}

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // id が 'heat' のペインでのみ描画
  if (e.requestId !== 'heat') return next(e)
  const { Box, Text, Raster } = $.ui.resolve(e)
  // 3 つのセルの 2 行。各セルはブロック文字とその色
  const rows = [
    [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
    [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
  ]
  if (e.surface !== 'terminal') {
    return Text({ children: ['The heat map needs the terminal.'] })
  }
  return Box({
    flexDirection: 'column',
    children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
  })
})
```

ターミナルでは、ペインはグリッドを表示します。

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-heat-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=b91bcce3bad74bc851149133d4acc5d5" alt="ターミナル内のペイン。色付きブロックの小さなグリッドを保持します。2 行の 3 つ。上の行は緑、琥珀色、赤です。下の行は緑、緑、琥珀色です。" width="360" height="132" data-path="images/mods-heat-map.svg" />

`rows` 配列は変更する部分であり、`cellsOf` はそれをパックされた文字列に変換します。フックは `id` が `heat` のペインでのみ描画するため、`$.ui.open({ id: 'heat' })` をコマンドから開きます。[`hello-tabs` の例](#build-a-pane-with-tabs)がそのペインを開く方法のように。

各文字は 1 セル幅である必要があります。既に画面上にある `Raster` をアニメーション化するには、ペインの `id` を `requestId` として、`Raster` の `key`、同じサイズ、および新しいセルで `$.ui.blit` を呼び出します。この例では、`$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })` です。`ui.render` フックを再度実行せずにその 1 つの要素を再描画します。

<h2 id="respond-to-presses-and-typing">
  押下とタイピングに応答する
</h2>

ユーザーがボタンを押す、フィールドに入力する、またはモッドが描画したリストから選択するとき、Claude Code はそのコントロールに与えた関数を呼び出し、モジュール内で実行されます。各コントロールは独自のコールバックを取ります。

* **`Button`**: `onPress(e)` を取ります。ここで `e.surface` は押下が来たアプリです
* **`Input`**: `onSubmit(value)` と `onInput(value)` を取ります
* **`Select`**: `onSelect(value)` を取ります。選択肢は `options` にあります。少なくとも 1 つの選択肢を持つリスト。例えば `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`

テストは `key` でコントロールを押すか入力するため、各コントロールに 1 つを与えます。コントロールの各使用は[`ui.press`、`ui.input`、または `ui.select`](/docs/ja/plugins/mods/reference#interface)も発生させます。`e.element` に `key` があり、別のモッドはそれらのイベントをフックできます。そのフックはコールバックの前に実行されるため、ユーザーが `Input` に入力するものを見て、それを変更するか、コールバックの代わりに答えることができます。モッド API には別のモッドのボタンを押すメソッドがありません。

<h3 id="know-which-keys-your-mod-can-receive">
  キーボードフォーカスとホットキー
</h3>

モッドはキーボード自体を読むことはありません。ユーザーがキーを押し、Claude Code はそれがコントロールのどれであるかを決定し、そのコントロールのコールバックが実行されます。[バンド上の数字ホットキー](/docs/ja/plugins/mods/reference#elements)を除いて、これはペインまたはバンドがキーボードフォーカスを持っている間にのみ発生します。それ以外の場合、キーはプロンプトに移動します。

<h4 id="how-a-pane-gets-keyboard-focus">
  ペインがキーボードフォーカスを取得する方法
</h4>

ペインは 3 つの方法のいずれかでキーボードフォーカスを取得します。

* モッドがコマンドまたは押下から `focus: true` で開く
* ユーザーが Ctrl+X を押してから Tab を押す
* ユーザーがそれをクリックする

Claude Code は `focus: true` をプロンプトが空で、他に何もキーボードフォーカスを持たない間にのみ付与します。ユーザーが入力している間に開くペインはそのキーストロークを取得しません。

<h4 id="what-each-key-does">
  各キーが実行すること
</h4>

このテーブルは、ペインまたはバンドがキーボードフォーカスを持っている間、キーが実行することをリストしています。

| キー | それが実行すること |
| :- | :- |
| Tab | 次のコントロールに移動 |
| 上下 | 描画がフィットしている間、コントロール間を移動します。ペインまたはバンドが表示できるより多くの行を持つ場合、それらはスクロールします。 |
| Enter | フォーカスされた `Button` を押す、フォーカスされた `Input` を送信する、または `Select` で選択 |
| ボタンのホットキー | そのボタンを押す。`Input` がフォーカスを持っている間、すべての印字可能キーはフィールドに移動します。 |
| Esc | キーボードフォーカスをプロンプトに返します。`closeOnEscape: true` を使用すると、ペインも閉じます。 |

モッドは Tab またはアロー キーを他のものにバインドできないため、ゲームは `w`、`a`、`s`、`d` で操舵します。

<h4 id="set-a-hotkey-and-the-first-focus">
  ホットキーと最初のフォーカスを設定する
</h4>

コントロール上の 2 つのプロップがキーボードがそれに到達する方法を決定します。

* **`hotkey`**: ユーザーが `Button` を 1 つのキーで押すことを許可するには、`hotkey: 'a'` のように 1 つの数字または 1 つの小文字の `hotkey` を与えます
* **`autoFocus`**: ペインが開くときどのコントロールがフォーカスを持つかを選択するには、`autoFocus: true` を追加します。他のコントロールからプロップを省略してください。Claude Code は `autoFocus: false` を拒否します。

ホットキーがどのように表示されるかはボタンとアプリによって異なります。

| ボタン | ターミナルで | Desktop アプリで |
| :- | :- | :- |
| 括弧付き、デフォルト | `[ Add one ]`。ホットキーは表示されません | ラベルと小さなキー |
| `plain: true` を使用 | `1: One` | ラベルと小さなキー |

ターミナルでは、括弧付きボタンのラベルにキーに名前を付けるか、`plain: true` を使用して、ユーザーが何を押すかを見ることができます。[要素リファレンス](/docs/ja/plugins/mods/reference#elements)には他の `Button` ルールがあります。`action`、バンド上の数字ホットキー、および 1 つのホットキー上の 2 つのボタン。

<h3 id="take-typed-input-and-draw-a-row-for-each-item">
  入力を取得し、各アイテムの行を描画する
</h3>

多くのペインはテキストフィールドとその下のリストです。このセクションの例はノートペインです。ノートを入力して Enter を押して追加し、各ノートには削除する `x` ボタンがあります。2 つのノートが追加されたとき、ターミナルはペインをこのように描画します。

```text theme={null}
╭──────────────────────────────────────────────────────────╮
│ Note: Type a note and press Enter ⏎ add                ✕ │
│ x buy milk │
│ x call bob │
╰──────────────────────────────────────────────────────────╯
```

例は 2 つのテクニックを使用します。

* **入力を取得**: `Input` はユーザーが Enter を押すとフィールドのテキストで `onSubmit(value)` を呼び出し、すべての変更で `onInput(value)` を呼び出します
* **リストを描画**: データを各行にマップし、すべての行のボタンに独自の `key` を与えます

このフックはペインのコンテンツを描画します。

```javascript theme={null}
// ペインが描画するリスト
let notes = []

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // id が 'notes' のペインでのみ描画
  if (e.requestId !== 'notes') return next(e)
  const { Box, Text, Button, Input } = $.ui.resolve(e)
  const redraw = () => $.ui.invalidate('ui.render')

  return Box({
    flexDirection: 'column',
    children: [
      Input({
        key: 'new-note',
        label: 'Note',
        placeholder: 'Type a note and press Enter',
        // 毎回フィールドを空で描画します。これは送信後にクリアします
        value: '',
        submitLabel: 'add',
        autoFocus: true,
        // フィールドで Enter を押すときに実行
        onSubmit: async (value) => {
          // 空の行を無視
          if (!value.trim()) return
          notes = [...notes, value.trim()]
          redraw()
          await $.store.set('notes', notes)
        },
      }),
      // 各ノートに対して 1 行。削除ボタン、その後ノートのテキスト
      ...notes.map((note, i) =>
        Box({
          flexDirection: 'row',
          columnGap: 1,
          children: [
            Button({
              // 独自のキー。各行のボタンを区別できるように
              key: 'delete-' + i,
              label: 'x',
              plain: true,
              onPress: async () => {
                notes = notes.filter((_, j) => j !== i)
                redraw()
                await $.store.set('notes', notes)
              },
            }),
            Text({ children: [note] }),
          ],
        }),
      ),
    ],
  })
})
```

ペインを試すには。

* **ノートを追加**: 行を入力して Enter を押します。行は新しい行として表示され、フィールドは空になります。
* **ノートを削除**: Tab を押してノートの `x` ボタンがフォーカスを持つまで、その後 Enter を押します。`x` はボタンのラベルであり、ホットキーではないため、文字を入力してもそれを押しません。

各変更は `hello-tabs` と同じレンダーサイクルに従います。コールバックが `notes` を変更し、`redraw` を呼び出し、リストを `$.store` に保存します。

フィールドは各送信後に空になります。これは `value` プロップのためです。`value` はフィールドが描画されるときに保持するテキストであり、ユーザーのタイピングはフックが再度フィールドを描画するまでそれを置き換えます。例は常にフィールドを `''` で描画します。

例はノートを保存し、それらを読み込みません。次のセッションでそれらを戻すには、`hello-tabs` が `count` を読み込む方法で `session.start` フックでそれらを読み込みます。

3 つのプロップはフィールドの行を構成します。`Note: Type a note and press Enter ⏎ add`。

| プロップ | 例では | それが何であるか |
| :- | :- | :- |
| `label` | `Note` | フィールドの前のテキスト。ターミナルはその後に `: ` を描画します。 |
| `placeholder` | `Type a note and press Enter` | フィールドが空の間に表示される暗いテキスト |
| `submitLabel` | `add` | `⏎` の後の単語。Enter が実行することを言います |

`Input` を送信してもターンを開始しません。コールバックが [`$.prompt.submit`](/docs/ja/plugins/mods/api#start-a-turn-from-a-background-job) を呼び出さない限り。

<h2 id="redraw-when-something-changes">
  サイトを再描画する
</h2>

描画はスナップショットです。`ui.render` フックが最後に実行したときに返したものを表示します。何か新しいものを表示するには、フックを再度実行する必要があります。Claude Code はいくつかの変更に対して再度実行し、モッドは残りを要求します。

<h3 id="when-claude-code-redraws-without-being-asked">
  Claude Code が要求なしで再描画するとき
</h3>

Claude Code はサイトのプロップが変更されるか、ターミナルの幅が変更されるときに `ui.render` フックを再度実行します。タイマーでフックを実行しません。モジュール内の変数が変更されたときは判断できません。

<h3 id="redraw-when-your-data-changes">
  データが変更されたときに再描画する
</h3>

データが変更された後にサイトを再度描画するには、`$.ui.invalidate('ui.render')` を呼び出します。このペインは押下をカウントします。ボタンのコールバックは `count` を変更し、再描画を要求します。

```javascript theme={null}
let count = 0

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  if (e.requestId !== 'counter') return next(e)
  const { Box, Text, Button } = $.ui.resolve(e)
  return Box({
    flexDirection: 'row',
    columnGap: 2,
    children: [
      Button({
        key: 'more',
        label: 'Add one',
        onPress: () => {
          count += 1
          // データが変更されたため、Claude Code にペインを再度描画するよう要求
          $.ui.invalidate('ui.render')
        },
      }),
      Text({ children: ['Count: ' + count] }),
    ],
  })
})
```

各押下はペイン内の数字を上げます。[`hello-tabs` の例](#build-a-pane-with-tabs)は同じ呼び出しを `redraw` 関数にラップします。

[`$.state`](#keep-a-value-in-\$-state)に保持する値は呼び出しを必要としません。値を書くことはそれを読むサイトを再描画するためです。

<h3 id="redraw-on-a-timer">
  タイマーで再描画する
</h3>

時計、カウントダウン、またはセッション外の値を最新に保つには、スケジュールで再描画します。モジュールの `session.start` フックでタイマーを開始します。モジュールが既に 1 つを持っている場合、`hello-tabs` のように、[`$.clock.every`](/docs/ja/plugins/mods/api#run-work-in-the-background)行をそれに追加します。

```javascript theme={null}
on('session.start', async ($, e, next) => {
  // 1000 ミリ秒ごとに、Claude Code にサイトを再度描画するよう要求
  $.clock.every(1000, () => $.ui.invalidate('ui.render'))
  return next(e)
})
```

Claude Code は 1 秒に 1 回 `ui.render` フックを実行します。タイマーはモジュールがリロードされるときに停止し、新しいコピーは独自のものを開始します。

<h3 id="how-often-a-site-can-redraw">
  サイトが再描画できる頻度
</h3>

Claude Code は再描画の頻度を制限するため、モッドはデータが変更されるたびに `$.ui.invalidate` を呼び出すことができます。表示されているペインとバンドは他のサイトより高い制限を持ち、[制限テーブル](/docs/ja/plugins/mods/reference#limits)に数字があります。

制限より速く来る呼び出しは 1 つの再描画に結合されます。その再描画はフックを 1 回実行し、フックはその時点でのデータを読むため、最新の値が表示され、その間の値は表示されません。アニメーションは制限より速く実行できません。

<h2 id="keep-state">
  状態を保持する
</h2>

モジュールには値を保持する 3 つの場所があり、値がどのくらい続くかが異なります。モジュールがリロードされるまで、セッションが終了するまで、または次のセッションまで続きます。値がどのくらい続く必要があるかで選択してください。

| 保持場所 | 続く期間 | 用途 |
| :- | :- | :- |
| モジュールレベルの変数 | モジュールがリロードされるまで。開発中にファイルを保存するたびに発生します | `hello-tabs` の `tab` のように失っても問題ない値 |
| `$.state` | セッションが終了するか、ユーザーが `/clear`、`/resume`、または `/branch` を実行するまで | 描画が依存し、リロードを生き残るべき値 |
| `$.store` | モジュールが削除するか、[`cleanupPeriodDays`](/docs/ja/settings-reference#cleanupperioddays) の期間、セッションがストアを読み書きしないまで。ストアはキー値ストアで、プラグイン自体の JSON ファイルとして `~/.claude/plugins/store/` に保存されます。 | 設定、履歴、ユーザーが次回見つけることを期待するもの |

`$.store.get(key)` は値または `undefined` に解決され、`$.store.set(key, value)` は任意の JSON 値を受け取ります。

<h3 id="keep-a-value-in-state">
  `$.state` に値を保持する
</h3>

`$.state` はセッションの長さの間値を保持し、自動的に再描画します。これはリアクティブな状態です。値を読む `ui.render` フックはそれにサブスクライブするため、値を書くたびに Claude Code はそのサイトを再描画し、`$.ui.invalidate` を呼び出す必要はありません。`$.state` の値は、変数とは異なり、モジュールのリロードも生き残ります。

これを設定するには、値を宣言し、マニフェストを宣言に指定し、各値を定義して使用します。例は `hello-tabs` の `count` を `$.state` に移動します。

<h4 id="declare-the-values">
  値を宣言する
</h4>

型ファイルで値を宣言します。外側のキーはプラグインの名前で、その下の各エントリは値とその型です。これを `hello-tabs/types/index.d.ts` として保存します。

```typescript hello-tabs/types/index.d.ts theme={null}
declare module 'claude-code' {
  interface PluginState {
    'hello-tabs': {
      tab: 'one' | 'two'
      count: number
    }
  }
}
```

<h4 id="point-the-manifest-at-the-declaration">
  マニフェストを宣言に指定する
</h4>

`claude plugin validate` がコードをそのファイルに対して検証できるようにするには、マニフェストに `types` フィールドをそのパスで追加します。

```json hello-tabs/.claude-plugin/plugin.json theme={null}
{
  "name": "hello-tabs",
  "version": "0.1.0",
  "description": "Opens a pane with two tabs and a counter",
  "author": { "name": "Your Name" },
  "types": "./types/index.d.ts"
}
```

<h4 id="define-read-and-write-a-value">
  値を定義、読み取り、書き込みする
</h4>

モジュールで、各値をデフォルトで定義し、描画中に読み取り、コールバックから書き込みます。`atom` は値とそのデフォルトに名前を付け、`read` はそれを返し、`update` はそれを書き込みます。3 つのヘルパーは `$.state.get` と `$.state.set` をあなたのために呼び出します。

```javascript theme={null}
import { atom, read, update } from 'claude-code'

// モジュールの最上部：値に名前を付け、デフォルトを指定します
const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)

// ui.render フック内：値を読み取って描画します
const n = await read($, count)

// ボタン内：古い値から新しい値を書き込みます
onPress: () => update($, count, (value) => value + 1)
```

`ui.render` フックが `count` を読み取ったため、ボタンがそれを書き込むたびに Claude Code はフックを再度実行します。

コードに 3 つのルールが適用されます。

* **`plugin` と `key` をリテラル文字列として書き込みます**：`claude plugin validate` はソースからそれらを読み取ります
* **型ファイルですべての値を宣言します**：そうしないと、検証は `hello-tabs.count is not declared` で失敗します
* **コールバックまたは別のイベントのフックから書き込みます**：`ui.render` フックは状態を読み取ることができ、それを書き込むことはできないため、`onPress`、`onSubmit`、または別のイベントのフックから書き込みます

<h4 id="change-hello-tabs-to-use-state">
  `hello-tabs` を `$.state` を使用するように変更する
</h4>

`hello-tabs` の `count` を `$.state` に移動するには、それを使用するすべての行を変更します。

* **モジュールの最上部**：`import` 行を追加し、`let count = 0` を `atom` 行に置き換えます
* **`ui.render` フック内**：`tabButton` の前に `read` 行を追加し、`Text` で `'Count: ' + n` を描画します
* **Add one ボタン内**：`onPress` を [Save from more than one session](#save-from-more-than-one-session) のものに置き換えます。これはカウントを保存し、それを書き込みます
* **`session.start` フック内**：`saved` を読み取る 2 行を [Load a saved value again after `/clear`](#load-a-saved-value-again-after-clear) の `loadCount` 呼び出しに置き換えます

`tab` はまだ変数であるため、タブボタンの `redraw` を保持します。

<h3 id="load-a-saved-value-again-after-clear">
  `/clear` の後に保存された値を再度読み込む
</h3>

モッドが `session.start` で `$.store` から保存された値を `$.state` にコピーする場合、`/clear`、`/resume`、または `/branch` の後に再度コピーする必要があります。これらのコマンドはすべての `$.state` 値をデフォルトに戻し、`session.start` は再度発火しません。[`classic.SessionStart`](/docs/ja/plugins/mods/events#hook-the-settings-hook-events) は各値の後に発火し、`e.source` は `clear`、`resume`、または `fork` に設定されるため、値を再度コピーします。そうしないと、描画はデフォルトを表示し、`$.state` 値を保存するコールバックは保存したものをデフォルトで上書きします。

このコードは両方のフックから `count` を読み込みます。これは `count` がアトムで `update` がインポートされている `hello-tabs` の `$.state` バージョンに基づいています。`loadCount` を `register` の上に配置し、`loadCount` 呼び出しを既に持っている `session.start` フックに追加します。`classic.SessionStart` はスタートアップと圧縮後にも発火します。圧縮は `$.state` をリセットしないため、`source` のフィルターはフックを 3 つのリセットに保ちます。

```javascript theme={null}
// 保存されたカウントを $.store から $.state にコピーするか、何も保存されていない場合は 0
async function loadCount($) {
  const saved = Number((await $.store.get('count')) ?? 0)
  await update($, count, () => saved)
}

// 最初のプロンプトの前に実行され、リロード後に再度実行されます
on('session.start', async ($, e, next) => {
  await loadCount($)
  return next(e)
})

// /clear、/resume、/branch の後に再度実行されます。fork を報告します
on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
  await loadCount($)
  return next(e)
})
```

両方のフックが配置されると、ペインは `/clear` の後に保存されたカウントを表示し、`0` ではなく、**Add one** の次のプレスは保存されたカウントに追加されます。

`loadCount` は保存された値を `$.state` のものに上書きし、`session.start` はモジュールがリロードされるたびに再度発火します。ストアが遅れないようにするには、**Add one** ボタンが行うように、すべての変更で保存します。

セッションなしでリロードを確認するには、[`/clear` の後の描画をテストします](/docs/ja/plugins/mods/test#test-a-drawing-after-clear)。

<h3 id="save-from-more-than-one-session">
  複数のセッションから保存する
</h3>

マシン上のモッドを実行するすべてのセッションは 1 つの `$.store` を共有します。`get` の後に `set` が続くことはアトミックではありません。2 つのセッションが各値を読み取り、変更し、書き戻すと、競合が発生し、2 番目の書き込みが最初の書き込みを置き換えます。

2 つの選択肢がそれをより可能性が低くします。

* **各アイテムに独自のキーを付与します**：`set` は独自のキーのみを変更するため、異なるキーを書き込むセッションは互いに上書きしません
* **書き込む直前に再度読み取ります**：複数のセッションが変更する値の場合、コールバックでキーを `get` し、`session.start` で読み込んだコピーからではなく、その値から新しい値を構築します。別のセッションの書き込みは、`get` と `set` の間に着地した場合でも失われます。

このボタンはストアが現在保持しているものに 1 を追加し、描画を更新します。

```javascript theme={null}
onPress: async () => {
  // ストアが現在保持しているものを読み取ります。別のセッションが変更した可能性があります
  const saved = Number((await $.store.get('count')) ?? 0)
  // 新しいカウントを保存し、それを表示します
  await $.store.set('count', saved + 1)
  await update($, count, () => saved + 1)
}
```

2 番目のセッションがこのセッションが開始されてから独自のボタンを 3 回押した場合、このプレスはそれらの 3 つを含むカウントを表示して保存します。

<h2 id="next-steps">
  次のステップ
</h2>

* [イベントに反応する](/docs/ja/plugins/mods/events)。ツール呼び出しとターンから描画をフィード
* [モッド API を使用する](/docs/ja/plugins/mods/api)。タイマーとモデル呼び出しから描画をフィード
* [描画をテストする](/docs/ja/plugins/mods/test#test-a-drawing)。複数のサーフェスでボタンをテストから押す
* [レンダーサイト](/docs/ja/plugins/mods/reference#render-sites)と[要素](/docs/ja/plugins/mods/reference#elements)。各サイトのプロップと各要素のプロップ
