2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.3> Use this file to discover all available pages before exploring further.
4 4
5# モッドでインターフェースに描画する5# mod でインターフェースに描画する
6 6
7> Claude Code モッドからペイン、プロンプト上部のバンド、ボタン、テキストフィールドを描画し、押下と入力を処理し、再描画とセッション間で状態を保持します。7> Claude Code の mod からペイン、プロンプト上部の帯、ボタン、テキストフィールドを描画し、押下や入力を処理して、再描画やセッションをまたいで状態を保持します。
8 8
9モッドは Claude Code でそれ自身のインターフェースを描画し、Claude Code が既に描画しているインターフェースの一部を変更できます。モッドが描画できる各場所は[レンダーサイト](/docs/ja/plugins/mods/reference#render-sites)と呼ばれます。例えば、ペイン、プロンプト上部のバンド、またはスピナーなどです。Claude Code はレンダーサイトを描画しようとするたびに[`ui.render`](/docs/ja/plugins/mods/reference#interface)イベントを発生させ、そのイベントのフックはそこに何を描画するかを返します。9mod は Claude Code 内に独自のインターフェースを描画できるほか、Claude Code がすでに描画しているインターフェースの一部を変更することもできます。mod が描画できる各場所は[描画箇所](/docs/ja/plugins/mods/reference#render-sites)と呼ばれ、ペイン、プロンプト上部の帯、スピナーなどがあります。Claude Code は描画箇所を描画しようとするたびに [`ui.render`](/docs/ja/plugins/mods/reference#interface) イベントを発火し、そのイベントに対するフックがそこに描画する内容を返します。
10 10
11このマップは、モッドがターミナルセッションのどこに描画できるかを示しています。11次の図は、ターミナルセッションで mod が描画できる場所を示しています。
12 12
13<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" />13<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 のターミナルセッションの図。mod は、右側のサイドバーとしてのペイン、トランスクリプト右上のトースト、トランスクリプト内のログ行、プロンプト上部の帯、プロンプト下部のステータスラインを追加できます。mod はメッセージ、ツール呼び出しの行、スピナーを再描画できます。プロンプトは Claude Code 自身のものです。" width="600" height="336" data-path="images/mods-screen-map.svg" />
14 14
15<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" />15<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 のターミナルセッションの図。mod は、右側のサイドバーとしてのペイン、トランスクリプト右上のトースト、トランスクリプト内のログ行、プロンプト上部の帯、プロンプト下部のステータスラインを追加できます。mod はメッセージ、ツール呼び出しの行、スピナーを再描画できます。プロンプトは Claude Code 自身のものです。" width="600" height="336" data-path="images/mods-screen-map-dark.svg" />
16 16
17より狭いターミナルでは、ペインはトランスクリプトの横ではなくプロンプトの上に配置されます。17幅の狭いターミナルでは、ペインはトランスクリプトの横ではなくプロンプトの上に配置されます。
18 18
19[最初のモッド](/docs/ja/plugins/mods/create)を作成してからここを始めてください。実装例から始めます。これは 2 つのタブとカウンターを持つペインを構築し、その後、変更したい各部分のセクションを読んでください。19ここから始める前に、[最初の mod](/docs/ja/plugins/mods/create) を作成してください。まず、2 つのタブとカウンターを持つペインを作成する実例から始め、その後、変更したい各要素のセクションを読んでください。
20 20
21<Note>21<Note>
22 1 つのプロップまたは制限を調べるには、[リファレンス](/docs/ja/plugins/mods/reference#render-sites)を参照してください。22 個々の props や制限を調べるには、[リファレンス](/docs/ja/plugins/mods/reference#render-sites)を参照してください。
23</Note>23</Note>
24 24
25<h2 id="build-a-pane-with-tabs">25<h2 id="build-a-pane-with-tabs">
26 タブ付きペインを構築する26 タブ付きのペインを作成する
27</h2>27</h2>
28 28
29このセクションでは、`/hello-tabs` コマンドを追加するモッドを構築します。このコマンドはペインを開きます。ペインは、広いフルスクリーンターミナルではトランスクリプトの横にあるサイドバー、またはそれ以外の場合はプロンプト上部のフレーム領域です。このペインは 2 つのタブを表示し、2 番目のタブにはカウンターに 1 を加えるボタンがあります。カウントは Claude Code を再起動した後も残ります。29このセクションでは、`/hello-tabs` コマンドを追加する mod を作成します。このコマンドはペインを開きます。ペインとは、幅の広いフルスクリーンのターミナルではトランスクリプトの横に表示されるサイドバー、それ以外の場合はプロンプトの上に表示される枠付きの領域です。このペインには 2 つのタブが表示され、2 番目のタブにはカウンターに 1 を加えるボタンがあります。カウントは Claude Code を再起動した後も保持されます。
30 30
31完成したモッドは次のようになります。記録はペインを開き、2 番目のタブに切り替え、ボタンを数回押し、最初のタブに戻ります。31完成した mod は次のようになります。この録画では、ペインを開き、2 番目のタブに切り替え、ボタンを数回押してから、最初のタブに戻っています。
32 32
33<Frame>33<Frame>
34 <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" />34 <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="Claude Code のプロンプトで /hello-tabs コマンドが入力され、その上に枠付きのペインが開きます。上部には「1: One」と「2: Two」が並び、「This is the first tab.」というテキストが表示されています。2 番目のタブでは「Count: 1」の横に「Add one」ボタンが表示され、カウントが 3 まで増えます。その後、ペインは最初のタブに戻ります。" data-path="images/mods-hello-tabs-light.mp4" />
35 35
36 <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" />36 <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="Claude Code のプロンプトで /hello-tabs コマンドが入力され、その上に枠付きのペインが開きます。上部には「1: One」と「2: Two」が並び、「This is the first tab.」というテキストが表示されています。2 番目のタブでは「Count: 1」の横に「Add one」ボタンが表示され、カウントが 3 まで増えます。その後、ペインは最初のタブに戻ります。" data-path="images/mods-hello-tabs-dark.mp4" />
37</Frame>37</Frame>
38 38
39Claude Code には組み込みのタブ要素がないため、タブは行内の 2 つのボタンです。モッドはどのタブがアクティブかを追跡し、その行の下にそのタブのコンテンツを描画します。39タブは横一列に並んだ 2 つのボタンです。mod はどちらがアクティブかを追跡し、そのタブのコンテンツを列の下に描画します。
40 40
41<Steps>41<Steps>
42 <Step title="プラグインを作成する">42 <Step title="プラグインを作成する">
43 モッドはマニフェスト、`hooks.json` がコードを指す、およびコードファイルを持つプラグインです。[モッドを作成する](/docs/ja/plugins/mods/create#write-a-mod-yourself)は各ファイルについて説明しています。`hello-tabs` という名前のディレクトリを作成し、その中に `.claude-plugin` と `hooks` ディレクトリを作成してから、最初の 2 つのファイルを保存します。43 mod は、マニフェスト、コードを指す `hooks.json`、およびコードファイルを持つプラグインです。それぞれについては [mod を作成する](/docs/ja/plugins/mods/create#write-a-mod-yourself) で説明しています。`hello-tabs` という名前のディレクトリを作成し、その中に `.claude-plugin` と `hooks` ディレクトリを作成してから、最初の 2 つのファイルを保存します。
44 44
45 マニフェストを `hello-tabs/.claude-plugin/plugin.json` として保存します。45 マニフェストを `hello-tabs/.claude-plugin/plugin.json` として保存します。
46 46
53 }53 }
54 ```54 ```
55 55
56 `hello-tabs/hooks/hooks.json` でエントリーポイントに名前を付けます。56 `hello-tabs/hooks/hooks.json` でエントリポイントを指定します。
57 57
58 ```json hello-tabs/hooks/hooks.json theme={null}58 ```json hello-tabs/hooks/hooks.json theme={null}
59 {59 {
63 </Step>63 </Step>
64 64
65 <Step title="コードを書く">65 <Step title="コードを書く">
66 コードは 3 つのジョブを実行し、各フックで 1 つずつ実行します。66 次のリストは、各フックが何をするかをコード内に登場する順に示しています。
67 67
68 * `/hello-tabs` コマンドを追加する68 * `/hello-tabs` コマンドを追加し、以前のセッションで保存されたカウントを読み込む
69 * そのコマンドを実行するときペインを開く69 * そのコマンドを実行したときにペインを開く
70 * ペインのコンテンツを描画する。タブの行とオープンタブのボディ70 * ペインのコンテンツ(タブの列と、開いているタブの本体)を描画する
71 71
72 2 つのモジュールレベルの変数、`tab` と `count` がペインの状態を保持します。72 モジュールレベルの 2 つの変数 `tab` と `count` が、ペインの状態を保持します。
73 73
74 これを `hello-tabs/hooks/register.js` として保存します。74 これを `hello-tabs/hooks/register.js` として保存します。
75 75
76 ```javascript hello-tabs/hooks/register.js theme={null}76 ```javascript hello-tabs/hooks/register.js theme={null}
77 // ペインの id。ペインを開くときと描画時に認識するために使用77 // The pane's id, used to open the pane and to recognize it when drawing
78 const PANE = 'hello-tabs'78 const PANE = 'hello-tabs'
79 79
80 // ペインが表示するもの。どのタブがオープンか、カウンターの値80 // What the pane shows: which tab is open, and the counter's value
81 let tab = 'one'81 let tab = 'one'
82 let count = 082 let count = 0
83 83
84 export function register(on) {84 export function register(on) {
85 // 最初のプロンプトの前に実行され、リロード後に再度実行85 // Runs before your first prompt, and again after a reload
86 on('session.start', async ($, e, next) => {86 on('session.start', async ($, e, next) => {
87 await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })87 await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
88 // 以前のセッションが保存したカウントを読み込む(存在する場合)88 // Load the count an earlier session saved, if there is one
89 const saved = await $.store.get('count')89 const saved = await $.store.get('count')
90 if (typeof saved === 'number') count = saved90 if (typeof saved === 'number') count = saved
91 return next(e)91 return next(e)
92 })92 })
93 93
94 // /hello-tabs を入力したときに実行94 // Runs when you type /hello-tabs
95 on('command.run', { command: 'hello-tabs' }, async ($) => {95 on('command.run', { command: 'hello-tabs' }, async ($) => {
96 // ペインを開き、キーボードを与え、Esc で閉じることを許可96 // Open the pane, give it the keyboard, and let Esc close it
97 await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })97 await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
98 // トランスクリプトに何も出力しない98 // Print nothing in the transcript
99 return {}99 return {}
100 })100 })
101 101
102 // Claude Code がペインを描画するたびに実行102 // Runs each time Claude Code draws a pane
103 on('ui.render', { component: 'Pane' }, async ($, e, next) => {103 on('ui.render', { component: 'Pane' }, async ($, e, next) => {
104 // 他のモッドのペインはそのままにする104 // Leave other mods' panes alone
105 if (e.requestId !== PANE) return next(e)105 if (e.requestId !== PANE) return next(e)
106 // このアプリが描画できる要素を取得106 // Get the elements this app can draw
107 const { Box, Text, Button } = $.ui.resolve(e)107 const { Box, Text, Button } = $.ui.resolve(e)
108 // Claude Code にこのフックを再度実行するよう要求108 // Ask Claude Code to run this hook again
109 const redraw = () => $.ui.invalidate('ui.render')109 const redraw = () => $.ui.invalidate('ui.render')
110 110
111 // 1 つのタブ。押されたときそのタブに切り替えるボタン111 // One tab: a button that switches to its tab when pressed
112 const tabButton = (name, label, hotkey) =>112 const tabButton = (name, label, hotkey) =>
113 Button({113 Button({
114 key: 'tab-' + name,114 key: 'tab-' + name,
115 label,115 label,
116 hotkey,116 hotkey,
117 plain: true,117 plain: true,
118 // オープンされていないタブを暗くする118 // Dim the tab that isn't open
119 dimColor: tab !== name,119 dimColor: tab !== name,
120 onPress: () => {120 onPress: () => {
121 tab = name121 tab = name
123 },123 },
124 })124 })
125 125
126 // タブの下に表示されるもの。どのタブがオープンかによる126 // What goes under the tabs, depending on which one is open
127 const body =127 const body =
128 tab === 'one'128 tab === 'one'
129 ? [Text({ children: ['This is the first tab.'] })]129 ? [Text({ children: ['This is the first tab.'] })]
139 onPress: async () => {139 onPress: async () => {
140 count += 1140 count += 1
141 redraw()141 redraw()
142 // カウントを保存して再起動後も残すようにする142 // Save the count so it's there after a restart
143 await $.store.set('count', count)143 await $.store.set('count', count)
144 },144 },
145 }),145 }),
148 }),148 }),
149 ]149 ]
150 150
151 // ペイン全体。タブの行、空行、その後ボディ151 // The whole pane: the row of tabs, a blank line, then the body
152 return Box({152 return Box({
153 flexDirection: 'column',153 flexDirection: 'column',
154 children: [154 children: [
165 }165 }
166 ```166 ```
167 167
168 各フックはコードが明確にしていないことも実行します。168 各フックは、コードからは明確に読み取れない処理も行っています。
169 169
170 * \*\*[`session.start`](/docs/ja/plugins/mods/reference#session)\*\*は[`$.store`](#keep-state)から保存されたカウントも読み込みます。これはセッション間で永続化するキー値ストアです。170 * **[`session.start`](/docs/ja/plugins/mods/reference#session)** は、保存されたカウントを [`$.store`](#keep-state) からも読み込みます。`$.store` はセッション間で永続化されるキーバリューストアです。
171 * \*\*[`command.run`](/docs/ja/plugins/mods/api#add-a-command)\*\*は Claude Code にペインが存在することを伝えるだけです。ペインを開くこと自体は何も描画しません。Claude Code は `ui.render` を発生させてそこに何が入るかを尋ねます。171 * **[`command.run`](/docs/ja/plugins/mods/api#add-a-command)** は、ペインが存在することを Claude Code に伝えるだけです。ペインを開くだけでは何も描画されません。Claude Code はその後 `ui.render` を発火させ、ペインに何を表示するかを問い合わせます。
172 * \*\*`ui.render`\*\*は要素ツリーを返します。これは他のボックス、テキスト、ボタンを保持する `Box` であり、実行されるたびに `tab` と `count` から再度構築されます。172 * **`ui.render`** は要素ツリー(他のボックス、テキスト、ボタンを保持する `Box`)を返し、実行されるたびに `tab` と `count` からツリーを構築し直します。
173 173
174 ボタンを押すとその `onPress` コールバックが実行され、変数が変更され、`redraw` が呼ばれます。Claude Code は `ui.render` フックを再度実行し、フックは新しい値から新しいツリーを構築します。すべてのインタラクティブな描画はそのレンダーサイクルを使用します。コールバックが状態を変更し、フックが新しい状態から再度レンダリングします。174 ボタンを押すとその `onPress` コールバックが実行され、変数を変更して `redraw` を呼び出します。すると Claude Code が `ui.render` フックを再度実行し、フックは新しい値から新しいツリーを構築します。インタラクティブな描画はすべてこのレンダーサイクルを使用します。コールバックが状態を変更し、フックが新しい状態から再びレンダリングします。
175 </Step>175 </Step>
176 176
177 <Step title="ペインを開く">177 <Step title="ペインを開く">
178 シェルで `claude --plugin-dir ./hello-tabs` で Claude Code を起動します。Claude Code プロンプトで `/hello-tabs` を実行します。ペインが開き、上部に `1: One` と `2: Two` が表示されます。`2` を押してから、**Add one** のホットキーである `a` を数回押します。カウントが上がります。178 シェルで `claude --plugin-dir ./hello-tabs` を使って Claude Code を起動します。Claude Code のプロンプトで `/hello-tabs` を実行します。上部に `1: One` と `2: Two` が並んだペインが開きます。`2` を押し、続いて **Add one** のホットキーである `a` を数回押します。カウントが増えていきます。
179 </Step>179 </Step>
180 180
181 <Step title="カウントが保存されたことを確認する">181 <Step title="カウントが保存されたことを確認する">
182 Esc を押してペインを閉じ、セッションを終了します。シェルで同じ `claude --plugin-dir ./hello-tabs` コマンドで Claude Code を再度起動し、Claude Code プロンプトで `/hello-tabs` を実行します。カウントは残したままです。182 Esc を押してペインを閉じ、セッションを終了します。シェルで同じ `claude --plugin-dir ./hello-tabs` コマンドを使って Claude Code を再度起動し、Claude Code のプロンプトで `/hello-tabs` を実行します。カウントは中断したときの値のままです。
183 183
184 カウントをクリアするには、モッドに `$.store.delete('count')` を呼び出させます。[状態を保持する](#keep-state)は各種類の値がどのくらい続くかをカバーしています。184 カウントをクリアするには、mod から `$.store.delete('count')` を呼び出します。各種の値がどのくらいの期間保持されるかについては、[状態を保持する](#keep-state) で説明しています。
185 </Step>185 </Step>
186</Steps>186</Steps>
187 187
189 描画する場所を選ぶ189 描画する場所を選ぶ
190</h2>190</h2>
191 191
192`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` です。192`ui.render` フックは、描画したい描画箇所に絞り込まない限り、すべての描画箇所で実行されます。描画箇所を選ぶには、[matcher](/docs/ja/plugins/mods/events#filter-which-events-a-hook-handles) と呼ばれるフィルターを `on` の第 2 引数として渡します。`{ component: 'Pane' }` を渡すと、フックはペインに対してのみ実行されます。フック内では、`e.component` が描画箇所の名前、`e.surface` が描画しているアプリ、`e.props` が描画箇所固有のデータを表します。ペインの場合、`e.requestId` はペインを開いたときに指定した `id` です。
193 193
1942 つのサイトはモッドがそれらを埋めるまで空です。ペインとバンドです。タブを選択して、各サイトが何であり、どのように描画するかを確認してください。194ペインとバンドは、mod が中身を描画するまで空です。タブを選択すると、それぞれが何であり、どのように描画するかを確認できます。
195 195
196<Tabs>196<Tabs>
197 <Tab title="ペイン">197 <Tab title="ペイン">
198 ペインは、広いフルスクリーンターミナルではトランスクリプトの横にあるサイドバー、またはそれ以外の場合はプロンプト上部のフレーム領域です。複数のペインがオープンされている場合、各ペインはそのタイトルを表示するタブを取得します。198 ペインは、幅の広いフルスクリーンのターミナルではトランスクリプトの横に表示されるサイドバーで、それ以外の場合はプロンプトの上に表示される枠付きの領域です。複数のペインが開いている場合、各ペインにはタイトルを表示するタブが付きます。
199 199
200 ペインは、モッドが `$.ui.open` を `id` で呼び出すときに表示されます。例えば `$.ui.open({ id: 'hello-tabs' })` のように。[適切なタイミングでペインを開く](#open-a-pane-at-the-right-time)は他のフィールドと、ペインがより広いターミナルを待つときをカバーしています。200 ペインは、mod が任意の `id` を指定して `$.ui.open` を呼び出したときに表示されます(例: `$.ui.open({ id: 'hello-tabs' })`)。その他のフィールドや、ペインがより広いターミナルを待つ場合については、[適切なタイミングでペインを開く](#open-a-pane-at-the-right-time)で説明しています。
201 201
202 ペインに描画するには、`{ component: 'Pane' }` でフィルターし、`e.requestId` が `id` であることを確認します。202 ペインに描画するには、`{ component: 'Pane' }` でフィルターし、`e.requestId` が自分の `id` であることを確認します。
203 </Tab>203 </Tab>
204 204
205 <Tab title="プロンプト上部のバンド">205 <Tab title="プロンプト上のバンド">
206 バンドはプロンプト入力の直上のストリップです。常に存在し、すべてのモッドがそれを共有します。206 バンドは、プロンプト入力のすぐ上にある帯状の領域です。常に存在し、すべての mod で共有されます。
207 207
208 フックは何かを表示するツリーを返すか、何も表示しない場合は `next(e)` を返します。ツリーは、モッド[の後に実行される](/docs/ja/plugins/mods/events#the-order-mods-run-in)ものが描画するものを置き換えます。それらを保持するには、`await next(e)` の結果をツリー内の[`Box`](#build-a-tree-from-elements)の子の中に配置します。208 バンドに何かを表示するにはフックからツリーを返し、何も表示しない場合は `next(e)` を返します。ツリーを返すと、[自分の mod より後に実行される](/docs/ja/plugins/mods/events#the-order-mods-run-in) mod がそこに描画する内容は置き換えられます。それらの内容を残すには、`await next(e)` の結果をツリー内の [`Box`](#build-a-tree-from-elements) の子要素に含めます。
209 209
210 バンドに描画するには、`{ component: 'AbovePrompt' }` でフィルターします。210 バンドに描画するには、`{ component: 'AbovePrompt' }` でフィルターします。
211 </Tab>211 </Tab>
212</Tabs>212</Tabs>
213 213
214<h3 id="change-what-claude-code-already-draws">214<h3 id="change-what-claude-code-already-draws">
215 Claude Code が既に描画しているものを変更する215 Claude Code がすでに描画しているものを変更する
216</h3>216</h3>
217 217
218Claude Code はそのインターフェースのほとんどを自分で描画します。メッセージ、ツール呼び出し行、スピナーなど。これらの各部分もレンダーサイトであるため、モッドはそれをリスタイルまたは置き換えることができます。1 つを変更するには、`ui.render` フックをこのテーブルの名前でフィルターします。218Claude Code は、メッセージ、ツール呼び出しの行、スピナーなど、インターフェースの大部分を自ら描画します。これらの各部分も描画箇所であるため、mod でスタイルを変更したり置き換えたりできます。変更するには、次の表にある名前で `ui.render` フックをフィルターします。
219 219
220| サイト | それが何であるか |220| 描画箇所 | 内容 |
221| :- | :- |221| :- | :- |
222| `UserMessage`、`AssistantMessage` | トランスクリプト内のメッセージ |222| `UserMessage`、`AssistantMessage` | トランスクリプト内のメッセージ |
223| `ToolUse`、`ToolResult`、`ToolGroup` | ツール呼び出しの行、その結果、および折りたたまれた呼び出しの実行 |223| `ToolUse`、`ToolResult`、`ToolGroup` | ツール呼び出しの行、その結果、折りたたまれた呼び出しのグループ |
224| `CommandOutput` | コマンドが出力した行 |224| `CommandOutput` | コマンドが出力した行 |
225| `AskUserQuestion` | Claude があなたに質問するために開くダイアログ |225| `AskUserQuestion` | Claude がユーザーに質問するために開くダイアログ |
226| `Spinner`、`ToolProgress`、`TurnDuration` | ターンのステータス行。Claude が作業している間にアニメーションする行、実行中のツールのライブ進捗行、ターンを閉じる行 |226| `Spinner`、`ToolProgress`、`TurnDuration` | ターンのステータスライン: Claude の作業中にアニメーションする行、実行中のツールのリアルタイムの進行状況を示す行、ターンを締めくくる行 |
227| `InfoNotice`、`SessionMode`、`PromptHint` | ロゴの下のステータス行、フッターのモードラベル、プロンプトの下のヒント行 |227| `InfoNotice`、`SessionMode`、`PromptHint` | ロゴの下のステータスライン、フッターのモードラベル、プロンプトの下のヒント行 |
228 228
229Claude Code が既に描画しているサイトでは、フックには 3 つの選択肢があります。詳細を変更する、描画を置き換える、またはそのままにする。タブを選択して、スピナーに適用された各ものを確認してください。例は、[チュートリアルモッド](/docs/ja/plugins/mods/create#write-a-mod-yourself)のように別のフックがカウントする `calls` 変数を読みます。229Claude Code がすでに描画している描画箇所では、フックで細部を変更したり、描画を置き換えたり、そのままにしたりできます。タブを選択すると、それぞれをスピナーに適用した例を確認できます。例では、[チュートリアルの mod](/docs/ja/plugins/mods/create#write-a-mod-yourself) と同様に、別のフックがカウントする `calls` 変数を読み取っています。
230 230
231<Tabs>231<Tabs>
232 <Tab title="詳細を変更する">232 <Tab title="細部を変更する">
233 Claude Code の描画を保持し、その一部を変更するには、変更された `props` を持つイベントのコピーを `next` に渡します。このフックはスピナーの単語の後のテキストを変更します。233 Claude Code の描画を残しつつ一部を変更するには、`props` を変更したイベントのコピーを `next` に渡します。次のフックは、スピナーの単語の後に続くテキストを変更します。
234 234
235 ```javascript theme={null}235 ```javascript theme={null}
236 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {236 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
237 // Claude Code のスピナーを保持し、その単語の後のテキストを変更237 // Keep Claude Code's spinner, and change the text after its word
238 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })238 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
239 })239 })
240 ```240 ```
241 241
242 スピナーはそのアニメーションと単語を保持し、テキストが単語に続きます。242 スピナーのアニメーションと単語はそのまま残り、単語の後に独自のテキストが続きます。
243 243
244 ```text theme={null}244 ```text theme={null}
245 Thinking · tool calls: 2…245 Thinking · tool calls: 2…
247 </Tab>247 </Tab>
248 248
249 <Tab title="描画を置き換える">249 <Tab title="描画を置き換える">
250 スピナーがある場所に独自のツリーを描画するには、ツリーを返し、`next` を呼び出さないでください。このフックはスピナーがある場所にテキストの 1 行を描画します。250 描画箇所の代わりに独自のものを描画するには、ツリーを返し、`next` を呼び出さないようにします。次のフックは、スピナーが表示される場所に 1 行のテキストを描画します。
251 251
252 ```javascript theme={null}252 ```javascript theme={null}
253 on('ui.render', { component: 'Spinner' }, async ($, e) => {253 on('ui.render', { component: 'Spinner' }, async ($, e) => {
254 const { Text } = $.ui.resolve(e)254 const { Text } = $.ui.resolve(e)
255 // next への呼び出しがないため、この行はスピナーの場所に描画されます255 // No call to next, so this line is drawn in the spinner's place
256 return Text({ children: ['Claude has made ' + calls + ' tool calls'] })256 return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
257 })257 })
258 ```258 ```
259 259
260 Claude が作業している間、行が表示され、Claude Code のスピナーは表示されません。260 Claude の作業中は独自の行が表示され、Claude Code のスピナーは表示されません。
261 261
262 ```text theme={null}262 ```text theme={null}
263 Claude has made 2 tool calls263 Claude has made 2 tool calls
265 </Tab>265 </Tab>
266 266
267 <Tab title="そのままにする">267 <Tab title="そのままにする">
268 Claude Code が描画するようにサイトをそのままにするには、`next(e)` を返します。フックはしばしば一部のイベントに対してそれを実行し、他のイベントに対しては実行しません。このフックはカウントするまでスピナーをそのままにします。268 描画箇所を Claude Code が描画するとおりに残すには、`next(e)` を返します。フックでは、一部のイベントではこのようにし、他のイベントではそうしないことがよくあります。次のフックは、カウントする呼び出しが発生するまでスピナーをそのままにします。
269 269
270 ```javascript theme={null}270 ```javascript theme={null}
271 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {271 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
272 // まだ表示するものがないため、イベントを変更せずに渡す272 // Nothing to show yet, so pass the event on unchanged
273 if (calls === 0) return next(e)273 if (calls === 0) return next(e)
274 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })274 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
275 })275 })
276 ```276 ```
277 277
278 最初のツール呼び出しの前に、スピナーはモッドなしで表示される方法で表示されます。278 最初のツール呼び出しの前は、スピナーは mod がない場合と同じ見た目になります。
279 279
280 ```text theme={null}280 ```text theme={null}
281 Thinking…281 Thinking…
283 </Tab>283 </Tab>
284</Tabs>284</Tabs>
285 285
286権限プロンプトはレンダーサイトではないため、モッドはそれが表示するものを変更できません。質問ダイアログ `AskUserQuestion` は 1 つであるため、モッドはそれを変更できます。286これらの描画箇所では、自分の mod より後に実行される mod が独自のツリーを返していない限り、`next(e)` は Claude Code の描画への参照 `{ type: 'engine', ref }` を返します。その描画の内容を変更するには、**細部を変更する**タブの例のように、props を変えたイベントのコピーを `next` に渡します。参照はそのまま返すことも、独自の要素と並べて `Box` に配置することもできます。
287 287
288ターミナルと Desktop アプリはすべての同じサイトを発生させません。`Pane`、`AbovePrompt`、`Spinner`、およびトランスクリプトサイトは両方で機能します。他のいくつかのステータス行はターミナルでのみ発生します。[レンダーサイトテーブル](/docs/ja/plugins/mods/reference#render-sites)は各サイトが発生する場所をリストしています。288```javascript theme={null}
289on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
290 const { Box, Text } = $.ui.resolve(e)
291 const theirs = await next(e)
292 return Box({ flexDirection: 'column', children: [theirs, Text({ children: ['under the spinner'] })] })
293})
294```
295
296Claude の作業中、スピナーはこれまでどおりアニメーションし、その下に `under the spinner` が表示されます。
297
298権限プロンプトは描画箇所ではないため、mod でその表示内容を変更することはできません。質問ダイアログ `AskUserQuestion` は描画箇所であるため、mod で変更できます。ダイアログ用のツリーには参照をちょうど 1 回だけ含め、独自の要素はその上に配置する必要があります。そうでない場合、Claude Code は独自のダイアログを描画します。
299
300ターミナルと Desktop アプリでは、発生する描画箇所がすべて同じというわけではありません。`Pane`、`AbovePrompt`、`Spinner`、およびトランスクリプトの描画箇所は両方で機能します。その他の一部のステータスラインはターミナルでのみ発生します。各描画箇所がどこで発生するかは、[描画箇所の表](/docs/ja/plugins/mods/reference#render-sites)に記載されています。
289 301
290<h3 id="open-a-pane-at-the-right-time">302<h3 id="open-a-pane-at-the-right-time">
291 適切なタイミングでペインを開く303 適切なタイミングでペインを開く
292</h3>304</h3>
293 305
294ペインはモッドがそれを開くときにのみ表示されます。どのように、いつ開くかは、キーボードフォーカスを取得するかどうか、どのくらいのスペースを要求するか、および狭いターミナルで表示されるかどうかを決定します。306ペインは、mod がそれを開いたときにのみ表示されます。ペインをどのように、いつ開くかによって、キーボードフォーカスを取得するかどうか、どれだけのスペースを求めるか、そして狭いターミナルで表示されるかどうかが決まります。
295 307
296ペインを開くには、選択した `id` で[`$.ui.open`](/docs/ja/plugins/mods/reference#mods-api-methods)を呼び出します。`id` はペインの名前です。`ui.render` フックはそれをチェックし、ペインを閉じるときに再度渡します。308ペインを開くには、任意の `id` を指定して [`$.ui.open`](/docs/ja/plugins/mods/reference#mods-api-methods) を呼び出します。`id` はペインの名前です。`ui.render` フックはこの名前を確認し、ペインを閉じるときにも再度渡します。
297 309
298```javascript theme={null}310```javascript theme={null}
299await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })311await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
300```312```
301 313
302ペインを閉じるには、それを開いた `id` で `$.ui.close` を呼び出します。314ペインを閉じるには、開いたときの `id` を指定して `$.ui.close` を呼び出します。
303 315
304```javascript theme={null}316```javascript theme={null}
305await $.ui.close({ id: 'hello-tabs' })317await $.ui.close({ id: 'hello-tabs' })
306```318```
307 319
308`id` の他に、`$.ui.open` はこれらのオプションフィールドを取ります。320`$.ui.open` は、`id` のほかに次のオプションフィールドを受け取ります。
309 321
310| フィールド | それが実行すること |322| フィールド | 機能 |
311| :- | :- |323| :- | :- |
312| `title` | 複数のペインがオープンされているときのペインのタブラベル |324| `title` | 複数のペインが開いているときのペインのタブラベル |
313| `focus` | [キーボードフォーカス](#know-which-keys-your-mod-can-receive)をリクエスト |325| `focus` | [キーボードフォーカス](#know-which-keys-your-mod-can-receive)を要求する |
314| `closeOnEscape` | Esc でペインを閉じるようにします |326| `closeOnEscape` | Esc でペインを閉じられるようにする |
315| `holdToasts` | [`$.ui.toast`](/docs/ja/plugins/mods/api#show-something-without-starting-a-turn)からの小さな通知であるトーストを、ペインが閉じるまで保持します |327| `holdToasts` | [`$.ui.toast`](/docs/ja/plugins/mods/api#show-something-without-starting-a-turn) による小さな通知であるトーストを、ペインが閉じるまで保留する |
316| `rows` | ペインがプロンプト上部に配置されるときに要求する高さ。デフォルトはスペースの 3 分の 1 です。 |328| `rows` | ペインがプロンプトの上にある場合に求める高さ。デフォルトはスペースの 3 分の 1 です。 |
317| `columns` | ペインがトランスクリプトの横に配置されるときに要求する幅 |329| `columns` | ペインがトランスクリプトの横にある場合に求める幅 |
318 330
319`focus`、`closeOnEscape`、および `holdToasts` はオプションであり、`true` のみを受け入れます。1 つを省略するには、それを省略します。`false` を渡すとエラーが発生します。例えば `ui.open: focus is true or left out` のようなエラーです。条件付きで 1 つを設定するには、条件が成立するときにのみフィールドを追加します。この呼び出しは、`items` が空でない場合にのみキーボードフォーカスをリクエストします。331`focus`、`closeOnEscape`、`holdToasts` は省略可能で、`true` のみを受け付けます。指定しない場合は省略してください。`false` を渡すと、`ui.open: focus is true or left out` のようなエラーがスローされます。これらを条件付きで設定するには、条件が成り立つ場合にのみフィールドを追加します。次の呼び出しは、`items` が空でない場合にのみキーボードフォーカスを要求します。
320 332
321```javascript theme={null}333```javascript theme={null}
322const pane = { id: 'hello-tabs', title: 'Hello tabs' }334const pane = { id: 'hello-tabs', title: 'Hello tabs' }
323await $.ui.open(items.length > 0 ? { ...pane, focus: true } : pane)335await $.ui.open(items.length > 0 ? { ...pane, focus: true } : pane)
324```336```
325 337
326Claude が作業している間にコマンドがペインを開くようにするには、[コマンドを登録](/docs/ja/plugins/mods/api#add-a-command)するときに `immediate: true` を追加します。それなしでは、ターン中に入力されたコマンドはターンが終わるまで待ちます。338Claude の作業中にコマンドでペインを開けるようにするには、[コマンドを登録する](/docs/ja/plugins/mods/api#add-a-command)際に `immediate: true` を追加します。これを指定しない場合、ターン中に入力されたコマンドはターンが終了するまで待機します。
327 339
328<h4 id="when-a-pane-waits-for-a-wider-terminal">340<h4 id="when-a-pane-waits-for-a-wider-terminal">
329 ペインがより広いターミナルを待つとき341 ペインがより広いターミナルを待つ場合
330</h4>342</h4>
331 343
332モッドが要求されずに開くペインは狭いターミナルに表示されないため、小さな画面を引き継ぐことはできません。表示されるかどうかは、それを開いたものによって異なります。344ユーザーの要求なしに mod が開いたペインは、狭いターミナルでは表示されません。そのため、小さな画面を占有することはありません。ペインが表示されるかどうかは、何がペインを開いたかによって決まります。
333 345
334* **ユーザーが実行したコマンドやボタンを押すなど、ユーザーが実行したもの**によって開かれた場合、ペインは任意の幅で表示されます346* **ユーザーの操作によって開かれた場合**(ユーザーが実行したコマンドや押したボタンなど)、ペインは幅に関係なく表示されます
335* **タイマーまたは[`turn.start`](/docs/ja/plugins/mods/events#follow-a-turn)フックなど、モッドが自分で実行したもの**によって開かれた場合、ペインは少なくとも 144 列幅のターミナルでのみ表示されます。ユーザーが一度そのペインを自分で開いた後は、110 列で十分です。347* **mod が自発的に開いた場合**(タイマーや [`turn.start`](/docs/ja/plugins/mods/events#follow-a-turn) フックからなど)、ペインは幅が 144 列以上のターミナルでのみ表示されます。ユーザーが一度自分でそのペインを開いた後は、110 列で十分です。
336 348
337ペインが表示されるとき、`$.ui.open` は `{ isPlaced: true }` に解決されます。ペインが待機しているとき、`isPlaced` は `false` で、`reason` は理由を説明する文字列です。待機中のペインはユーザーがそれを開くか、ターミナルを広げるときに表示されます。ペインを開かずに何かが利用可能であることを言うには、`$.ui.toast('Your message')` を呼び出します。これは数秒後に消える小さな通知を表示します。349ペインが表示されると、`$.ui.open` は `{ isPlaced: true }` に解決されます。ペインが待機中の場合、`isPlaced` は `false` になり、`reason` はその理由を示す文字列になります。待機中のペインは、ユーザーがそのペインを開くか、ターミナルの幅を広げたときに表示されます。ペインを開かずに何かが利用可能であることを伝えるには、`$.ui.toast('Your message')` を呼び出します。これにより、トースト通知が表示されます。
338 350
339<h2 id="build-a-tree-from-elements">351<h2 id="build-a-tree-from-elements">
340 要素からツリーを構築する352 要素からツリーを構築する
341</h2>353</h2>
342 354
343`ui.render` フックが返すものは要素ツリーです。これは描画する内容の説明であり、ボックス、テキスト、および制御が互いにネストされています。描画を説明し、Claude Code はターミナルまたは Desktop アプリでそれをレンダリングします。355`ui.render` フックが返すのは要素ツリーです。これは描画内容の記述で、ボックス、テキスト、コントロールを互いに入れ子にして構成します。描画内容を記述すると、Claude Code がそれをターミナルまたは Desktop アプリでレンダリングします。
344 356
345要素を取得するには、フック内で `$.ui.resolve(e)` を呼び出します。例えば `const { Box, Text, Button } = $.ui.resolve(e)` のように。各要素は関数です。プロップを渡し、その中に入るべき要素と文字列を `children` に配置します。357要素を取得するには、フック内で `const { Box, Text, Button } = $.ui.resolve(e)` のように `$.ui.resolve(e)` を呼び出します。各要素は関数です。props を渡し、その中に入れる要素や文字列を `children` に指定します。
346 358
347ほとんどの描画は 4 つの要素を使用します。タブを選択して、各要素とターミナルがそれをどのように描画するかを確認してください。359タブを選択すると、よく使われる各要素と、ターミナルでの描画結果を確認できます。
348 360
349<Tabs>361<Tabs>
350 <Tab title="テキスト">362 <Tab title="Text">
351 `Text` は文字列を描画し、`bold` や `color` などのオプションのスタイリングを使用します。363 `Text` は文字列を描画します。`bold` や `color` などのスタイルを任意で指定できます。
352 364
353 ```javascript theme={null}365 ```javascript theme={null}
354 Text({ children: ['This is the first tab.'] })366 Text({ children: ['This is the first tab.'] })
359 ```371 ```
360 </Tab>372 </Tab>
361 373
362 <Tab title="ボックス">374 <Tab title="Box">
363 `Box` は内部にあるものを行または列に配置します。これは、ボタンとテキスト行を 2 列離して並べて配置します。375 `Box` は中身を行または列に並べます。この例では、ボタンと 1 行のテキストを 2 カラム空けて横に並べます。
364 376
365 ```javascript theme={null}377 ```javascript theme={null}
366 Box({378 Box({
378 ```390 ```
379 </Tab>391 </Tab>
380 392
381 <Tab title="ボタン">393 <Tab title="Button">
382 `Button` はユーザーが押すことができるコントロールです。`onPress` コールバックを実行します。`plain: true` を使用すると、括弧がなく、ホットキーが表示されます。394 `Button` はユーザーが押せるコントロールです。`onPress` コールバックを実行します。`plain: true` を指定すると角括弧がなくなり、ホットキーが表示されます。
383 395
384 ```javascript theme={null}396 ```javascript theme={null}
385 Button({ key: 'more', label: 'Add one', onPress: addOne })397 Button({ key: 'more', label: 'Add one', onPress: addOne })
392 ```404 ```
393 </Tab>405 </Tab>
394 406
395 <Tab title="入力">407 <Tab title="Input">
396 `Input` はテキストフィールドです。ユーザーが Enter を押すと、`onSubmit` コールバックをテキストで実行します。408 `Input` はテキストフィールドです。ユーザーが Enter を押すと、入力されたテキストを引数に `onSubmit` コールバックを実行します。
397 409
398 ```javascript theme={null}410 ```javascript theme={null}
399 Input({411 Input({
407 ```419 ```
408 420
409 ```text theme={null}421 ```text theme={null}
410 Note: Type a note and press Enter ⏎ add422 Note: Type a note and press Enter
411 ```423 ```
412 </Tab>424 </Tab>
413</Tabs>425</Tabs>
414 426
415このテーブルはすべての要素をリストしています。427[インターフェースギャラリー](/docs/ja/plugins/mods/gallery)には、ほとんどの要素のサンプルとスクリーンショットがあります。次の表はすべての要素の一覧です。
416 428
417| 要素 | それが描画するもの | どこで |429| 要素 | 描画されるもの | 使用できる場所 |
418| :- | :- | :- |430| :- | :- | :- |
419| `Box` | フレックスコンテナ。`flexDirection`、`columnGap`、`padding`、`borderStyle`、`width` などのレイアウトプロップを取ります。 | どこでも |431| `Box` | flex コンテナ。`flexDirection`、`columnGap`、`padding`、`borderStyle`、`width` などのレイアウト props を受け取ります。 | すべて |
420| `Text` | スタイル付きテキスト。`color`、`bold`、`dimColor`、`italic`、`wrap` を取ります。`color` はテーマキーまたは `'red'` などの色です。`wrap` は `'wrap'`、`'truncate'`、`'truncate-start'`、`'truncate-middle'`、または `'truncate-end'` です。 | どこでも |432| `Text` | スタイル付きテキスト。`color`、`bold`、`dimColor`、`italic`、`wrap` を受け取ります。`color` はテーマキーか、`'red'` などの色です。`wrap` は `'wrap'`、`'truncate'`、`'truncate-start'`、`'truncate-middle'`、`'truncate-end'` のいずれかです。 | すべて |
421| `Button` | `onPress` を呼び出すコントロール | どこでも |433| `Button` | `onPress` を呼び出すコントロール | すべて |
422| `Link`、`Code`、`Markdown` | `href` とオプションの `label` を持つリンク、コードブロック、および Claude の返信の方法でフォーマットされたテキスト。`Markdown` は `children` ではなく `text` プロップでコンテンツを取り、`onLinkPress` を渡すときは `key` が必要です。 | どこでも |434| `Link`、`Code`、`Markdown` | `href` と任意の `label` を持つリンク、コードブロック、Claude の返答と同じ形式で整形されたテキスト。`Markdown` は内容を `children` ではなく `text` props で受け取り、`onLinkPress` を渡す場合は `key` が必要です。 | すべて |
423| `Input`、`Select` | テキストフィールドとピッカー | ターミナル、Desktop |435| `Input`、`Select` | テキストフィールドとドロップダウン | ターミナル、Desktop |
424| `Svg` | SVG ドキュメント | Desktop |436| `Svg` | SVG ドキュメント | Desktop |
425| `Client` | 2 番目のファイルで描画される領域。アニメーションとポインター入力用。そのファイルはモッド API を取得しません。フックに到達するのはデータを投稿することだけで、`ui.message` イベントとして到達します。 | ターミナル、Desktop |437| `Client` | アニメーションやポインター入力のために、別のファイルで描画する領域。そのファイルは mod API を利用できません。フックにはデータを送信することでのみ到達でき、そのデータは `ui.message` イベントとして届きます。 | ターミナル、Desktop |
426| `Raster`、`Image` | [色付きセルのグリッド](#draw-a-grid-of-colored-cells)と画像 | ターミナル |438| `Raster`、`Image` | [色付きセルのグリッド](#draw-a-grid-of-colored-cells)と画像 | ターミナル |
427 439
428モジュールが `.tsx` または `.jsx` ファイルの場合、ツリーを JSX として記述できます。`$.ui.resolve(e)` から要素を分割代入してください。フックモジュールには要素グローバルがないためです。440モジュールが `.tsx` または `.jsx` ファイルの場合は、ツリーを JSX で記述できます。その場合は、先に `$.ui.resolve(e)` から要素を分割代入してください。
429 441
430ツリーがアプリが持たない要素、要素が取らないプロップ、または子が入らない場所を使用する場合、Claude Code はサイトの独自のバージョンを描画します。442アプリにない要素、要素が受け付けない props、または子を置けない場所に子を使ったツリーの場合、Claude Code はそのサイトを独自のバージョンで描画します。
431 443
432`--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` として同じ理由で記録します。他に何も表示されないため、描画が表示されない場合は、その行またはログを確認してください。444`--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` として記録されます。セッションにはそれ以外何も表示されないため、描画が表示されない場合は、その行またはログを確認してください。
433 445
434<h3 id="draw-a-grid-of-colored-cells">446<h3 id="draw-a-grid-of-colored-cells">
435 色付きセルのグリッドを描画する447 色付きセルのグリッドを描画する
436</h3>448</h3>
437 449
438ヒートマップ、スパークライン、またはターミナルのゲームボードの場合、各セルに対して 1 つの `Raster` を描画し、`Box` ではありません。`Raster` は `key`、`columns` と `rows` のサイズ、および `cells` を取ります。これはすべてのセルを 1 つの文字列にパックします。各セルは 3 つの数字です。文字のコードポイント、その色、背景色。色は `0xc62828` のような赤、または `0x01000000` のようなターミナルのデフォルトの 16 進数です。450ターミナルでヒートマップ、スパークライン、ゲーム盤などを描画するには、セルごとに `Box` を使うのではなく、`Raster` を 1 つ描画します。`Raster` は、`key`、`columns` と `rows` によるサイズ、そしてすべてのセルを詰め込んだ base64 文字列である `cells` を受け取ります。各セルは 3 つの数値で構成されます。文字のコードポイント、文字色、背景色です。色は 16 進数の 24 ビット RGB 値で、たとえば赤なら `0xc62828` です。その範囲より 1 大きい値 `0x01000000` は、ターミナルのデフォルトを意味します。
439 451
440Desktop アプリには `Raster` がないため、`e.surface` をチェックしてそこにテキストを描画します。このペインボディは 3 x 2 のヒートマップを描画します。452Desktop アプリには `Raster` がないため、`e.surface` を確認し、Desktop ではテキストを描画します。次のペイン本体は 3×2 のヒートマップを描画します。
441 453
442```javascript theme={null}454```javascript theme={null}
443// 「ターミナルのデフォルト色を使用」を意味する値455// The value that means "use the terminal's default color"
444const DEFAULT_COLOR = 0x01000000456const DEFAULT_COLOR = 0x01000000
445 457
446// [文字、色] ペアの行を Raster が取る 1 つの文字列にパック458// Pack rows of [character, color] pairs into the one string a Raster takes
447// 1 つのセルは 3 つの数字です。文字のコードポイント、その色、背景色459// One cell is three numbers: the character's code point, its color, and its background
448function cellsOf(rows) {460function cellsOf(rows) {
449 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])461 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
450 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()462 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
451}463}
452 464
453on('ui.render', { component: 'Pane' }, async ($, e, next) => {465on('ui.render', { component: 'Pane' }, async ($, e, next) => {
454 // id が 'heat' のペインでのみ描画466 // Draw only in the pane opened with the id 'heat'
455 if (e.requestId !== 'heat') return next(e)467 if (e.requestId !== 'heat') return next(e)
456 const { Box, Text, Raster } = $.ui.resolve(e)468 const { Box, Text, Raster } = $.ui.resolve(e)
457 // 3 つのセルの 2 行。各セルはブロック文字とその色469 // Two rows of three cells, each a block character and its color
458 const rows = [470 const rows = [
459 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],471 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
460 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],472 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
469})481})
470```482```
471 483
472ターミナルでは、ペインはグリッドを表示します。484ターミナルでは、ペインにグリッドが表示されます。
473 485
474<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" />486<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" />
475 487
476`rows` 配列は変更する部分であり、`cellsOf` はそれをパックされた文字列に変換します。フックは `id` が `heat` のペインでのみ描画するため、`$.ui.open({ id: 'heat' })` をコマンドから開きます。[`hello-tabs` の例](#build-a-pane-with-tabs)がそのペインを開く方法のように。488変更する部分は `rows` 配列で、`cellsOf` がそれを詰め込んだ文字列に変換します。このフックは `id` が `heat` のペインでのみ描画するため、[`hello-tabs` の例](#build-a-pane-with-tabs)がペインを開くのと同じように、コマンドから `$.ui.open({ id: 'heat' })` でペインを開いてください。
477 489
478各文字は 1 セル幅である必要があります。既に画面上にある `Raster` をアニメーション化するには、ペインの `id` を `requestId` として、`Raster` の `key`、同じサイズ、および新しいセルで `$.ui.blit` を呼び出します。この例では、`$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })` です。`ui.render` フックを再度実行せずにその 1 つの要素を再描画します。490各文字は 1 セル幅である必要があります。すでに画面に表示されている `Raster` をアニメーションさせるには、ペインの `id` を `requestId` として、`Raster` の `key`、同じサイズ、新しいセルを指定して `$.ui.blit` を呼び出します。この例では `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })` となります。これにより、`ui.render` フックを再実行せずに、その 1 つの要素だけが再描画されます。
479 491
480<h2 id="respond-to-presses-and-typing">492<h2 id="respond-to-presses-and-typing">
481 押下とタイピングに応答する493 押下と入力に応答する
482</h2>494</h2>
483 495
484ユーザーがボタンを押す、フィールドに入力する、またはモッドが描画したリストから選択するとき、Claude Code はそのコントロールに与えた関数を呼び出し、モジュール内で実行されます。各コントロールは独自のコールバックを取ります。496ユーザーが mod の描画したボタンを押したり、フィールドに入力したり、リストから選択したりすると、Claude Code はそのコントロールのコールバックを呼び出します。コールバックはユーザーのモジュール内で実行されます。各コントロールはそれぞれ独自のコールバックを受け取ります。
485 497
486* **`Button`**: `onPress(e)` を取ります。ここで `e.surface` は押下が来たアプリです498* **`Button`**:`onPress(e)` を受け取ります。`e.surface` は押下が発生したアプリです
487* **`Input`**: `onSubmit(value)` と `onInput(value)` を取ります499* **`Input`**:`onSubmit(value)` と `onInput(value)` を受け取ります
488* **`Select`**: `onSelect(value)` を取ります。選択肢は `options` にあります。少なくとも 1 つの選択肢を持つリスト。例えば `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`500* **`Select`**:`onSelect(value)` を受け取り、選択肢は `options` で指定します。`options` は一意の値を持つ 1 つ以上の選択肢のリストで、たとえば `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]` のようになります
489 501
490テストは `key` でコントロールを押すか入力するため、各コントロールに 1 つを与えます。コントロールの各使用は[`ui.press`、`ui.input`、または `ui.select`](/docs/ja/plugins/mods/reference#interface)も発生させます。`e.element` に `key` があり、別のモッドはそれらのイベントをフックできます。そのフックはコールバックの前に実行されるため、ユーザーが `Input` に入力するものを見て、それを変更するか、コールバックの代わりに答えることができます。モッド API には別のモッドのボタンを押すメソッドがありません。502テストはコントロールの `key` を使ってそのコントロールを押したり入力したりするため、各コントロールに `key` を付けてください。コントロールが使われるたびに、`e.element` に `key` を含む [`ui.press`、`ui.input`、または `ui.select`](/docs/ja/plugins/mods/reference#interface) も発火し、別の mod がこれらのイベントを処理できます。そのフックはユーザーのコールバックより先に実行されるため、ユーザーが `Input` に入力した内容を参照でき、それを変更したり、コールバックの代わりに応答したりできます。mods API には、別の mod のボタンを押すメソッドはありません。
491 503
492<h3 id="know-which-keys-your-mod-can-receive">504<h3 id="know-which-keys-your-mod-can-receive">
493 キーボードフォーカスとホットキー505 キーボードフォーカスとホットキー
494</h3>506</h3>
495 507
496モッドはキーボード自体を読むことはありません。ユーザーがキーを押し、Claude Code はそれがコントロールのどれであるかを決定し、そのコントロールのコールバックが実行されます。[バンド上の数字ホットキー](/docs/ja/plugins/mods/reference#elements)を除いて、これはペインまたはバンドがキーボードフォーカスを持っている間にのみ発生します。それ以外の場合、キーはプロンプトに移動します。508mod 自体がキーボードを読み取ることはありません。ユーザーがキーを押すと、Claude Code がそれがどのコントロール宛てかを判断し、そのコントロールのコールバックが実行されます。[バンド上の数字ホットキー](/docs/ja/plugins/mods/reference#elements)を除き、これはペインまたはバンドがキーボードフォーカスを持っている間にのみ発生します。それ以外のときは、キーはプロンプトに送られます。
497 509
498<h4 id="how-a-pane-gets-keyboard-focus">510<h4 id="how-a-pane-gets-keyboard-focus">
499 ペインがキーボードフォーカスを取得する方法511 ペインがキーボードフォーカスを得る方法
500</h4>512</h4>
501 513
502ペインは 3 つの方法のいずれかでキーボードフォーカスを取得します。514ペインは次の場合にキーボードフォーカスを得ます。
503 515
504* モッドがコマンドまたは押下から `focus: true` で開く516* mod がコマンドまたは押下から `focus: true` を指定してペインを開いたとき
505* ユーザーが Ctrl+X を押してから Tab を押す517* ユーザーが Ctrl+X に続いて Tab を押したとき
506* ユーザーがそれをクリックする518* ユーザーがペインをクリックしたとき
507 519
508Claude Code は `focus: true` をプロンプトが空で、他に何もキーボードフォーカスを持たない間にのみ付与します。ユーザーが入力している間に開くペインはそのキーストロークを取得しません。520Claude Code が `focus: true` を許可するのは、プロンプトが空で、他に何もキーボードフォーカスを持っていない間だけです。ユーザーが入力中に開いたペインが、そのキー入力を奪うことはありません。
509 521
510<h4 id="what-each-key-does">522<h4 id="what-each-key-does">
511 各キーが実行すること523 各キーの動作
512</h4>524</h4>
513 525
514このテーブルは、ペインまたはバンドがキーボードフォーカスを持っている間、キーが実行することをリストしています。526次の表は、ペインまたはバンドがキーボードフォーカスを持っている間の各キーの動作を示しています。
515 527
516| キー | それが実行すること |528| キー | 動作 |
517| :- | :- |529| :- | :- |
518| Tab | 次のコントロールに移動 |530| Tab | 次のコントロールに移動します |
519| 上下 | 描画がフィットしている間、コントロール間を移動します。ペインまたはバンドが表示できるより多くの行を持つ場合、それらはスクロールします。 |531| Up と Down | 描画内容が収まっている間はコントロール間を移動します。ペインまたはバンドに表示できる以上の行がある場合は、スクロールします。 |
520| Enter | フォーカスされた `Button` を押す、フォーカスされた `Input` を送信する、または `Select` で選択 |532| Enter | フォーカスされている `Button` を押す、フォーカスされている `Input` を送信する、または `Select` で選択します |
521| ボタンのホットキー | そのボタンを押す。`Input` がフォーカスを持っている間、すべての印字可能キーはフィールドに移動します。 |533| ボタンのホットキー | そのボタンを押します。`Input` がフォーカスを持っている間は、すべての印字可能なキーがフィールドに送られます。 |
522| Esc | キーボードフォーカスをプロンプトに返します。`closeOnEscape: true` を使用すると、ペインも閉じます。 |534| Esc | キーボードフォーカスをプロンプトに戻します。`closeOnEscape: true` の場合は、ペインも閉じます。 |
523 535
524モッドは Tab またはアロー キーを他のものにバインドできないため、ゲームは `w`、`a`、`s`、`d` で操舵します。536mod は Tab や矢印キーを他の動作に割り当てることができないため、ゲームでは `w`、`a`、`s`、`d` で操作します。
525 537
526<h4 id="set-a-hotkey-and-the-first-focus">538<h4 id="set-a-hotkey-and-the-first-focus">
527 ホットキーと最初のフォーカスを設定する539 ホットキーと最初のフォーカスを設定する
528</h4>540</h4>
529 541
530コントロール上の 2 つのプロップがキーボードがそれに到達する方法を決定します。542コントロールの次の props によって、キーボードがそのコントロールにどう到達するかが決まります。
531 543
532* **`hotkey`**: ユーザーが `Button` を 1 つのキーで押すことを許可するには、`hotkey: 'a'` のように 1 つの数字または 1 つの小文字の `hotkey` を与えます544* **`hotkey`**:ユーザーが 1 つのキーで `Button` を押せるようにするには、`hotkey: 'a'` のように、1 桁の数字または 1 文字の小文字を `hotkey` として指定します
533* **`autoFocus`**: ペインが開くときどのコントロールがフォーカスを持つかを選択するには、`autoFocus: true` を追加します。他のコントロールからプロップを省略してください。Claude Code は `autoFocus: false` を拒否します。545* **`autoFocus`**:ペインが開いたときにどのコントロールがフォーカスを持つかを選ぶには、そのコントロールに `autoFocus: true` を追加します。この props は `true` のみを受け付けるため、他のコントロールでは省略してください。
534 546
535ホットキーがどのように表示されるかはボタンとアプリによって異なります。547ホットキーの表示方法は、ボタンとアプリによって異なります。
536 548
537| ボタン | ターミナルで | Desktop アプリで |549| ボタン | ターミナル | Desktop アプリ |
538| :- | :- | :- |550| :- | :- | :- |
539| 括弧付き、デフォルト | `[ Add one ]`。ホットキーは表示されません | ラベルと小さなキー |551| 角括弧付き(デフォルト) | `[ Add one ]`、ホットキーは表示されない | ラベルの横に小さなキーが表示される |
540| `plain: true` を使用 | `1: One` | ラベルと小さなキー |552| `plain: true` 指定時 | `1: One` | ラベルの横に小さなキーが表示される |
541 553
542ターミナルでは、括弧付きボタンのラベルにキーに名前を付けるか、`plain: true` を使用して、ユーザーが何を押すかを見ることができます。[要素リファレンス](/docs/ja/plugins/mods/reference#elements)には他の `Button` ルールがあります。`action`、バンド上の数字ホットキー、および 1 つのホットキー上の 2 つのボタン。554ターミナルでは、ユーザーが何を押せばよいかわかるように、角括弧付きボタンのラベルにキーを記載するか、`plain: true` を使用してください。その他の `Button` のルール(`action`、バンド上の数字ホットキー、1 つのホットキーに割り当てた 2 つのボタン)については、[要素リファレンス](/docs/ja/plugins/mods/reference#elements)を参照してください。
543 555
544<h3 id="take-typed-input-and-draw-a-row-for-each-item">556<h3 id="take-typed-input-and-draw-a-row-for-each-item">
545 入力を取得し、各アイテムの行を描画する557 入力を受け取り、項目ごとに行を描画する
546</h3>558</h3>
547 559
548多くのペインはテキストフィールドとその下のリストです。このセクションの例はノートペインです。ノートを入力して Enter を押して追加し、各ノートには削除する `x` ボタンがあります。2 つのノートが追加されたとき、ターミナルはペインをこのように描画します。560多くのペインは、テキストフィールドとその下のリストで構成されます。このセクションの例はメモペインです。メモを入力して Enter を押すと追加され、各メモにはそれを削除する `x` ボタンがあります。メモを 2 つ追加すると、ターミナルはペインを次のように描画します。
549 561
550```text theme={null}562```text theme={null}
551╭──────────────────────────────────────────────────────────╮563╭──────────────────────────────────────────────────────────╮
555╰──────────────────────────────────────────────────────────╯567╰──────────────────────────────────────────────────────────╯
556```568```
557 569
558例は 2 つのテクニックを使用します。570この例では次の手法を使用しています。
559 571
560* **入力を取得**: `Input` はユーザーが Enter を押すとフィールドのテキストで `onSubmit(value)` を呼び出し、すべての変更で `onInput(value)` を呼び出します572* **入力を受け取る**:`Input` は、ユーザーが Enter を押したときにフィールドのテキストを引数として `onSubmit(value)` を呼び出し、変更のたびに `onInput(value)` を呼び出します
561* **リストを描画**: データを各行にマップし、すべての行のボタンに独自の `key` を与えます573* **リストを描画する**:データを 1 件ずつ 1 行にマッピングし、各行のボタンに固有の `key` を付けます
562 574
563このフックはペインのコンテンツを描画します。575次のフックがペインの内容を描画します。
564 576
565```javascript theme={null}577```javascript theme={null}
566// ペインが描画するリスト578// The list the pane draws
567let notes = []579let notes = []
568 580
569on('ui.render', { component: 'Pane' }, async ($, e, next) => {581on('ui.render', { component: 'Pane' }, async ($, e, next) => {
570 // id が 'notes' のペインでのみ描画582 // Draw only in the pane opened with the id 'notes'
571 if (e.requestId !== 'notes') return next(e)583 if (e.requestId !== 'notes') return next(e)
572 const { Box, Text, Button, Input } = $.ui.resolve(e)584 const { Box, Text, Button, Input } = $.ui.resolve(e)
573 const redraw = () => $.ui.invalidate('ui.render')585 const redraw = () => $.ui.invalidate('ui.render')
579 key: 'new-note',591 key: 'new-note',
580 label: 'Note',592 label: 'Note',
581 placeholder: 'Type a note and press Enter',593 placeholder: 'Type a note and press Enter',
582 // 毎回フィールドを空で描画します。これは送信後にクリアします594 // Draw the field empty each time, which clears it after a submit
583 value: '',595 value: '',
584 submitLabel: 'add',596 submitLabel: 'add',
585 autoFocus: true,597 autoFocus: true,
586 // フィールドで Enter を押すときに実行598 // Runs when you press Enter in the field
587 onSubmit: async (value) => {599 onSubmit: async (value) => {
588 // 空の行を無視600 // Ignore an empty line
589 if (!value.trim()) return601 if (!value.trim()) return
590 notes = [...notes, value.trim()]602 notes = [...notes, value.trim()]
591 redraw()603 redraw()
592 await $.store.set('notes', notes)604 await $.store.set('notes', notes)
593 },605 },
594 }),606 }),
595 // 各ノートに対して 1 行。削除ボタン、その後ノートのテキスト607 // One row for each note: a delete button, then the note's text
596 ...notes.map((note, i) =>608 ...notes.map((note, i) =>
597 Box({609 Box({
598 flexDirection: 'row',610 flexDirection: 'row',
599 columnGap: 1,611 columnGap: 1,
600 children: [612 children: [
601 Button({613 Button({
602 // 独自のキー。各行のボタンを区別できるように614 // A key of its own, so each row's button can be told apart
603 key: 'delete-' + i,615 key: 'delete-' + i,
604 label: 'x',616 label: 'x',
605 plain: true,617 plain: true,
618})630})
619```631```
620 632
621ペインを試すには。633ペインを試すには:
622 634
623* **ノートを追加**: 行を入力して Enter を押します。行は新しい行として表示され、フィールドは空になります。635* **メモを追加する**:1 行入力して Enter を押します。その行が新しい行として表示され、フィールドは空になります。
624* **ノートを削除**: Tab を押してノートの `x` ボタンがフォーカスを持つまで、その後 Enter を押します。`x` はボタンのラベルであり、ホットキーではないため、文字を入力してもそれを押しません。636* **メモを削除する**:メモの `x` ボタンがフォーカスを持つまで Tab を押し、Enter を押します。`x` はボタンのラベルであってホットキーではないため、その文字を入力してもボタンは押されません。
625 637
626各変更は `hello-tabs` と同じレンダーサイクルに従います。コールバックが `notes` を変更し、`redraw` を呼び出し、リストを `$.store` に保存します。638各変更は `hello-tabs` と同じレンダリングサイクルに従います。コールバックが `notes` を変更し、`redraw` を呼び出し、リストを `$.store` に保存します。
627 639
628フィールドは各送信後に空になります。これは `value` プロップのためです。`value` はフィールドが描画されるときに保持するテキストであり、ユーザーのタイピングはフックが再度フィールドを描画するまでそれを置き換えます。例は常にフィールドを `''` で描画します。640送信のたびにフィールドが空になるのは、`value` props によるものです。`value` はフィールドが描画されるときに保持するテキストで、フックがフィールドを再び描画するまでは、ユーザーの入力がそれを置き換えます。この例では常に `''` でフィールドを描画しています。
629 641
630例はノートを保存し、それらを読み込みません。次のセッションでそれらを戻すには、`hello-tabs` が `count` を読み込む方法で `session.start` フックでそれらを読み込みます。642この例ではメモを保存しますが、読み込みはしません。次のセッションでメモを復元するには、`hello-tabs` が `count` を読み込むのと同じように、`session.start` フックでメモを読み込んでください。
631 643
6323 つのプロップはフィールドの行を構成します。`Note: Type a note and press Enter ⏎ add`。644次の props が、フィールドの行 `Note: Type a note and press Enter ⏎ add` を構成します。
633 645
634| プロップ | 例では | それが何であるか |646| Props | 例での値 | 説明 |
635| :- | :- | :- |647| :- | :- | :- |
636| `label` | `Note` | フィールドの前のテキスト。ターミナルはその後に `: ` を描画します。 |648| `label` | `Note` | フィールドの前のテキスト。ターミナルはその後に `: ` を描画します。 |
637| `placeholder` | `Type a note and press Enter` | フィールドが空の間に表示される暗いテキスト |649| `placeholder` | `Type a note and press Enter` | フィールドが空の間に表示される薄いテキスト |
638| `submitLabel` | `add` | `⏎` の後の単語。Enter が実行することを言います |650| `submitLabel` | `add` | `⏎` の後に続く、Enter の動作を示す語 |
639 651
640`Input` を送信してもターンを開始しません。コールバックが [`$.prompt.submit`](/docs/ja/plugins/mods/api#start-a-turn-from-a-background-job) を呼び出さない限り。652`Input` を送信しても、コールバックが [`$.prompt.submit`](/docs/ja/plugins/mods/api#start-a-turn-from-a-background-job) を呼び出さない限り、ターンは開始されません。
641 653
642<h2 id="redraw-when-something-changes">654<h2 id="redraw-when-something-changes">
643 サイトを再描画する655 サイトを再描画する
644</h2>656</h2>
645 657
646描画はスナップショットです。`ui.render` フックが最後に実行したときに返したものを表示します。何か新しいものを表示するには、フックを再度実行する必要があります。Claude Code はいくつかの変更に対して再度実行し、モッドは残りを要求します。658描画はスナップショットです。`ui.render` フックが最後に実行したときに返したものを表示します。何か新しいものを表示するには、フックを再度実行する必要があります。Claude Code はいくつかの変更に対して再度実行し、mod は残りを要求します。
647 659
648<h3 id="when-claude-code-redraws-without-being-asked">660<h3 id="when-claude-code-redraws-without-being-asked">
649 Claude Code が要求なしで再描画するとき661 Claude Code が要求なしで再描画するとき
650</h3>662</h3>
651 663
652Claude Code はサイトのプロップが変更されるか、ターミナルの幅が変更されるときに `ui.render` フックを再度実行します。タイマーでフックを実行しません。モジュール内の変数が変更されたときは判断できません。664Claude Code はサイトの props が変更されるか、ターミナルの幅が変更されるときに `ui.render` フックを再度実行します。タイマーでフックを実行しません。モジュール内の変数が変更されたときは判断できません。
653 665
654<h3 id="redraw-when-your-data-changes">666<h3 id="redraw-when-your-data-changes">
655 データが変更されたときに再描画する667 データが変更されたときに再描画する
700})712})
701```713```
702 714
703Claude Code は 1 秒に 1 回 `ui.render` フックを実行します。タイマーはモジュールがリロードされるときに停止し、新しいコピーは独自のものを開始します。715Claude Code は 1 秒に 1 回 `ui.render` フックを実行します。タイマーはモジュールがリロードされるときに停止し、モジュールの新しいインスタンスが独自のタイマーを開始します。
704 716
705<h3 id="how-often-a-site-can-redraw">717<h3 id="how-often-a-site-can-redraw">
706 サイトが再描画できる頻度718 サイトが再描画できる頻度
707</h3>719</h3>
708 720
709Claude Code は再描画の頻度を制限するため、モッドはデータが変更されるたびに `$.ui.invalidate` を呼び出すことができます。表示されているペインとバンドは他のサイトより高い制限を持ち、[制限テーブル](/docs/ja/plugins/mods/reference#limits)に数字があります。721Claude Code はサイトの再描画の頻度を制限するため、mod はデータが変更されるたびに `$.ui.invalidate` を呼び出すことができます。各サイトが再描画できる頻度については、[制限テーブル](/docs/ja/plugins/mods/reference#limits)を参照してください。
710 722
711制限より速く来る呼び出しは 1 つの再描画に結合されます。その再描画はフックを 1 回実行し、フックはその時点でのデータを読むため、最新の値が表示され、その間の値は表示されません。アニメーションは制限より速く実行できません。723制限より速く来る呼び出しは 1 つの再描画に結合されます。その再描画はフックを 1 回実行し、フックはその時点でのデータを読むため、最新の値が表示され、その間の値は表示されません。アニメーションは制限より速く実行できません。
712 724
714 状態を保持する726 状態を保持する
715</h2>727</h2>
716 728
717モジュールには値を保持する 3 つの場所があり、値がどのくらい続くかが異なります。モジュールがリロードされるまで、セッションが終了するまで、または次のセッションまで続きます。値がどのくらい続く必要があるかで選択してください。729mod が値をどこに保持するかによって、その値がどれだけの期間残るかが決まります。モジュールが再読み込みされるまで、セッションが終了するまで、またはセッションをまたいで次のセッションまでです。値をどれだけの期間残す必要があるかに応じて選択します。
718 730
719| 保持場所 | 続く期間 | 用途 |731| 保持する場所 | 保持される期間 | 用途 |
720| :- | :- | :- |732| :- | :- | :- |
721| モジュールレベルの変数 | モジュールがリロードされるまで。開発中にファイルを保存するたびに発生します | `hello-tabs` の `tab` のように失っても問題ない値 |733| モジュールレベルの変数 | モジュールが再読み込みされるまで(開発中はファイルを保存するたびに再読み込みされます) | 失われてもかまわない値(`hello-tabs` の `tab` など) |
722| `$.state` | セッションが終了するか、ユーザーが `/clear`、`/resume`、または `/branch` を実行するまで | 描画が依存し、リロードを生き残るべき値 |734| `$.state` | セッションが終了するまで、またはユーザーが `/clear`、`/resume`、`/branch` を実行するまで | 描画が依存する値のうち、再読み込み後も残すべきもの |
723| `$.store` | モジュールが削除するか、[`cleanupPeriodDays`](/docs/ja/settings-reference#cleanupperioddays) の期間、セッションがストアを読み書きしないまで。ストアはキー値ストアで、プラグイン自体の JSON ファイルとして `~/.claude/plugins/store/` に保存されます。 | 設定、履歴、ユーザーが次回見つけることを期待するもの |735| `$.store` | mod が削除するまで、または [`cleanupPeriodDays`](/docs/ja/settings-reference#cleanupperioddays) の間どのセッションもストアを読み書きしないまで。ストアはキーと値のストアで、`~/.claude/plugins/store/` 配下にプラグイン専用の JSON ファイルとして保存されます。 | 設定、履歴など、ユーザーが次回も残っていることを期待するもの |
724 736
725`$.store.get(key)` は値または `undefined` に解決され、`$.store.set(key, value)` は任意の JSON 値を受け取ります。737`$.store.get(key)` は値または `undefined` に解決され、`$.store.set(key, value)` は任意の JSON 値を受け取ります。
726 738
727<h3 id="keep-a-value-in-state">739<h3 id="keep-a-value-in-$-state">
728 `$.state` に値を保持する740 `$.state` に値を保持する
729</h3>741</h3>
730 742
731`$.state` はセッションの長さの間値を保持し、自動的に再描画します。これはリアクティブな状態です。値を読む `ui.render` フックはそれにサブスクライブするため、値を書くたびに Claude Code はそのサイトを再描画し、`$.ui.invalidate` を呼び出す必要はありません。`$.state` の値は、変数とは異なり、モジュールのリロードも生き残ります。743`$.state` はセッションの間値を保持し、再描画も自動で行います。これはリアクティブな状態です。値を読み取る `ui.render` フックはその値をサブスクライブするため、値を書き込むたびに Claude Code がその箇所を再描画し、`$.ui.invalidate` を呼び出す必要はありません。また、`$.state` 内の値はモジュールの再読み込み後も残りますが、変数は残りません。
732 744
733これを設定するには、値を宣言し、マニフェストを宣言に指定し、各値を定義して使用します。例は `hello-tabs` の `count` を `$.state` に移動します。745設定するには、値を宣言し、マニフェストでその宣言を指定してから、各値を定義して使用します。以下の例では、`hello-tabs` の `count` を `$.state` に移します。
734 746
735<h4 id="declare-the-values">747<h4 id="declare-the-values">
736 値を宣言する748 値を宣言する
737</h4>749</h4>
738 750
739型ファイルで値を宣言します。外側のキーはプラグインの名前で、その下の各エントリは値とその型です。これを `hello-tabs/types/index.d.ts` として保存します。751値は型宣言ファイルで宣言します。外側のキーはプラグインの名前で、その下の各エントリが値とその型です。これを `hello-tabs/types/index.d.ts` として保存します。
740 752
741```typescript hello-tabs/types/index.d.ts theme={null}753```typescript hello-tabs/types/index.d.ts theme={null}
742declare module 'claude-code' {754declare module 'claude-code' {
750```762```
751 763
752<h4 id="point-the-manifest-at-the-declaration">764<h4 id="point-the-manifest-at-the-declaration">
753 マニフェストを宣言に指定する765 マニフェストで宣言を指定する
754</h4>766</h4>
755 767
756`claude plugin validate` がコードをそのファイルに対して検証できるようにするには、マニフェストに `types` フィールドをそのパスで追加します。768`claude plugin validate` でそのファイルに照らしてコードをチェックできるようにするには、マニフェストにそのパスを指定した `types` フィールドを追加します。
757 769
758```json hello-tabs/.claude-plugin/plugin.json theme={null}770```json hello-tabs/.claude-plugin/plugin.json theme={null}
759{771{
766```778```
767 779
768<h4 id="define-read-and-write-a-value">780<h4 id="define-read-and-write-a-value">
769 値を定義、読み取り、書き込みする781 値を定義、読み取り、書き込む
770</h4>782</h4>
771 783
772モジュールで、各値をデフォルトで定義し、描画中に読み取り、コールバックから書き込みます。`atom` は値とそのデフォルトに名前を付け、`read` はそれを返し、`update` はそれを書き込みます。3 つのヘルパーは `$.state.get` と `$.state.set` をあなたのために呼び出します。784モジュール内で、各値をデフォルト付きで定義し、描画時に読み取り、コールバックから書き込みます。`atom` は値とそのデフォルトに名前を付け、`read` は値を返し、`update` は値を書き込みます。これら 3 つのヘルパーが `$.state.get` と `$.state.set` を代わりに呼び出します。
773 785
774```javascript theme={null}786```javascript theme={null}
775import { atom, read, update } from 'claude-code'787import { atom, read, update } from 'claude-code'
776 788
777// モジュールの最上部:値に名前を付け、デフォルトを指定します789// At the top of the module: name the value and give its default
778const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)790const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)
779 791
780// ui.render フック内:値を読み取って描画します792// In the ui.render hook: read the value to draw it
781const n = await read($, count)793const n = await read($, count)
782 794
783// ボタン内:古い値から新しい値を書き込みます795// In a Button: write a new value from the old one
784onPress: () => update($, count, (value) => value + 1)796onPress: () => update($, count, (value) => value + 1)
785```797```
786 798
787`ui.render` フックが `count` を読み取ったため、ボタンがそれを書き込むたびに Claude Code はフックを再度実行します。799`ui.render` フックが `count` を読み取ったため、ボタンがそれを書き込むたびに Claude Code はフックを再実行します。
788 800
789コードに 3 つのルールが適用されます。801コードには次のルールが適用されます。
790 802
791* **`plugin` と `key` をリテラル文字列として書き込みます**:`claude plugin validate` はソースからそれらを読み取ります803* **`plugin` と `key` は文字列リテラルで記述する**:`claude plugin validate` はソースからこれらを読み取ります
792* **型ファイルですべての値を宣言します**:そうしないと、検証は `hello-tabs.count is not declared` で失敗します804* **すべての値を型宣言ファイルで宣言する**:宣言しないと、`hello-tabs.count is not declared` で検証が失敗します
793* **コールバックまたは別のイベントのフックから書き込みます**:`ui.render` フックは状態を読み取ることができ、それを書き込むことはできないため、`onPress`、`onSubmit`、または別のイベントのフックから書き込みます805* **コールバックまたは別のイベントのフックから書き込む**:`ui.render` フックは状態を読み取れますが書き込めないため、`onPress`、`onSubmit`、または別のイベントのフックから書き込みます
794 806
795<h4 id="change-hello-tabs-to-use-state">807<h4 id="change-hello-tabs-to-use-$-state">
796 `hello-tabs` を `$.state` を使用するように変更する808 `hello-tabs` を `$.state` を使うように変更する
797</h4>809</h4>
798 810
799`hello-tabs` の `count` を `$.state` に移動するには、それを使用するすべての行を変更します。811`hello-tabs` の `count` を `$.state` に移すには、それを使用しているすべての行を変更します。
800 812
801* **モジュールの最上部**:`import` 行を追加し、`let count = 0` を `atom` 行に置き換えます813* **モジュールの先頭**:`import` 行を追加し、`let count = 0` を `atom` 行に置き換えます
802* **`ui.render` フック内**:`tabButton` の前に `read` 行を追加し、`Text` で `'Count: ' + n` を描画します814* **`ui.render` フック内**:`tabButton` の前に `read` 行を追加し、`Text` で `'Count: ' + n` を描画します
803* **Add one ボタン内**:`onPress` を [Save from more than one session](#save-from-more-than-one-session) のものに置き換えます。これはカウントを保存し、それを書き込みます815* **Add one ボタン内**:`onPress` を、[複数のセッションから保存する](#save-from-more-than-one-session)にあるものに置き換えます。これはカウントを書き込むだけでなく保存も行います
804* **`session.start` フック内**:`saved` を読み取る 2 行を [Load a saved value again after `/clear`](#load-a-saved-value-again-after-clear) の `loadCount` 呼び出しに置き換えます816* **`session.start` フック内**:`saved` を読み取る 2 行を、[`/clear` 後に保存済みの値を再度読み込む](#load-a-saved-value-again-after-clear)にある `loadCount` の呼び出しに置き換えます
805 817
806`tab` はまだ変数であるため、タブボタンの `redraw` を保持します。818`tab` は引き続き変数なので、タブボタンの `redraw` はそのまま残します。
807 819
808<h3 id="load-a-saved-value-again-after-clear">820<h3 id="load-a-saved-value-again-after-clear">
809 `/clear` の後に保存された値を再度読み込む821 `/clear` 後に保存済みの値を再度読み込む
810</h3>822</h3>
811 823
812モッドが `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` 値を保存するコールバックは保存したものをデフォルトで上書きします。824mod が `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` の値を保存するコールバックが保存済みの内容をデフォルトで上書きしてしまいます。
813 825
814このコードは両方のフックから `count` を読み込みます。これは `count` がアトムで `update` がインポートされている `hello-tabs` の `$.state` バージョンに基づいています。`loadCount` を `register` の上に配置し、`loadCount` 呼び出しを既に持っている `session.start` フックに追加します。`classic.SessionStart` はスタートアップと圧縮後にも発火します。圧縮は `$.state` をリセットしないため、`source` のフィルターはフックを 3 つのリセットに保ちます。826次のコードは、両方のフックから `count` を読み込みます。これは `$.state` 版の `hello-tabs` をベースにしており、`count` は atom で、`update` はインポート済みです。`loadCount` を `register` の上に置き、既存の `session.start` フックに `loadCount` の呼び出しを追加します。`classic.SessionStart` は起動時とコンテキスト圧縮の後にも発火しますが、これらは `$.state` をリセットしないため、`source` のフィルターでフックを 3 つのリセットに限定します。
815 827
816```javascript theme={null}828```javascript theme={null}
817// 保存されたカウントを $.store から $.state にコピーするか、何も保存されていない場合は 0829// Copy the saved count from $.store into $.state, or 0 if nothing is saved
818async function loadCount($) {830async function loadCount($) {
819 const saved = Number((await $.store.get('count')) ?? 0)831 const saved = Number((await $.store.get('count')) ?? 0)
820 await update($, count, () => saved)832 await update($, count, () => saved)
821}833}
822 834
823// 最初のプロンプトの前に実行され、リロード後に再度実行されます835// Runs before your first prompt, and again after a reload
824on('session.start', async ($, e, next) => {836on('session.start', async ($, e, next) => {
825 await loadCount($)837 await loadCount($)
826 return next(e)838 return next(e)
827})839})
828 840
829// /clear、/resume、/branch の後に再度実行されます。fork を報告します841// Runs again after /clear, /resume, and /branch, which reports fork
830on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {842on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
831 await loadCount($)843 await loadCount($)
832 return next(e)844 return next(e)
833})845})
834```846```
835 847
836両方のフックが配置されると、ペインは `/clear` の後に保存されたカウントを表示し、`0` ではなく、**Add one** の次のプレスは保存されたカウントに追加されます。848両方のフックを配置すると、`/clear` の後もペインには `0` ではなく保存済みのカウントが表示され、次に **Add one** を押すと保存済みのカウントに加算されます。
837 849
838`loadCount` は保存された値を `$.state` のものに上書きし、`session.start` はモジュールがリロードされるたびに再度発火します。ストアが遅れないようにするには、**Add one** ボタンが行うように、すべての変更で保存します。850`loadCount` は保存済みの値で `$.state` 内の値を上書きし、`session.start` はモジュールが再読み込みされるたびに再度発火します。ストアが遅れないようにするには、**Add one** ボタンのように、変更のたびに保存します。
839 851
840セッションなしでリロードを確認するには、[`/clear` の後の描画をテストします](/docs/ja/plugins/mods/test#test-a-drawing-after-clear)。852セッションなしで再読み込みを確認するには、[`/clear` 後に描画をテストします](/docs/ja/plugins/mods/test#test-a-drawing-after-clear)。
841 853
842<h3 id="save-from-more-than-one-session">854<h3 id="save-from-more-than-one-session">
843 複数のセッションから保存する855 複数のセッションから保存する
844</h3>856</h3>
845 857
846マシン上のモッドを実行するすべてのセッションは 1 つの `$.store` を共有します。`get` の後に `set` が続くことはアトミックではありません。2 つのセッションが各値を読み取り、変更し、書き戻すと、競合が発生し、2 番目の書き込みが最初の書き込みを置き換えます。858マシン上で mod を実行するすべてのセッションは、1 つの `$.store` を共有します。`get` の後に `set` を行う操作はアトミックではありません。2 つのセッションがそれぞれ値を読み取り、変更して書き戻すと競合が発生し、2 番目の書き込みが最初の書き込みを置き換えます。
847 859
8482 つの選択肢がそれをより可能性が低くします。860この可能性を低くするには、次のようにします。
849 861
850* **各アイテムに独自のキーを付与します**:`set` は独自のキーのみを変更するため、異なるキーを書き込むセッションは互いに上書きしません862* **各項目に個別のキーを割り当てる**:`set` は自身のキーのみを変更するため、異なるキーに書き込むセッション同士は互いに上書きしません
851* **書き込む直前に再度読み取ります**:複数のセッションが変更する値の場合、コールバックでキーを `get` し、`session.start` で読み込んだコピーからではなく、その値から新しい値を構築します。別のセッションの書き込みは、`get` と `set` の間に着地した場合でも失われます。863* **書き込む直前に再度読み取る**:複数のセッションが変更する値については、コールバック内でキーを `get` し、`session.start` で読み込んだコピーではなく、その値から新しい値を構築します。ただし、別のセッションの書き込みが `get` と `set` の間に行われた場合、その書き込みは失われます。
852 864
853このボタンはストアが現在保持しているものに 1 を追加し、描画を更新します。865次のボタンは、現在ストアにある値に 1 を加算してから、描画を更新します。
854 866
855```javascript theme={null}867```javascript theme={null}
856onPress: async () => {868onPress: async () => {
857 // ストアが現在保持しているものを読み取ります。別のセッションが変更した可能性があります869 // Read what the store holds now, which another session may have changed
858 const saved = Number((await $.store.get('count')) ?? 0)870 const saved = Number((await $.store.get('count')) ?? 0)
859 // 新しいカウントを保存し、それを表示します871 // Save the new count, then show it
860 await $.store.set('count', saved + 1)872 await $.store.set('count', saved + 1)
861 await update($, count, () => saved + 1)873 await update($, count, () => saved + 1)
862}874}
863```875```
864 876
8652 番目のセッションがこのセッションが開始されてから独自のボタンを 3 回押した場合、このプレスはそれらの 3 つを含むカウントを表示して保存します。877このセッションの開始以降に 2 つ目のセッションが自身のボタンを 3 回押していた場合、このボタンを押すと、その 3 回分を含むカウントが表示され保存されます。
866 878
867<h2 id="next-steps">879<h2 id="next-steps">
868 次のステップ880 次のステップ
869</h2>881</h2>
870 882
871* [イベントに反応する](/docs/ja/plugins/mods/events)。ツール呼び出しとターンから描画をフィード883* [イベントに反応する](/docs/ja/plugins/mods/events): ツール呼び出しとターンから描画にデータを渡します
872* [モッド API を使用する](/docs/ja/plugins/mods/api)。タイマーとモデル呼び出しから描画をフィード884* [mods API を使用する](/docs/ja/plugins/mods/api): タイマーとモデル呼び出しから描画にデータを渡します
873* [描画をテストする](/docs/ja/plugins/mods/test#test-a-drawing)。複数のサーフェスでボタンをテストから押す885* [描画をテストする](/docs/ja/plugins/mods/test#test-a-drawing): テストからボタンを押し、複数のサーフェスで確認します
874* [レンダーサイト](/docs/ja/plugins/mods/reference#render-sites)と[要素](/docs/ja/plugins/mods/reference#elements)。各サイトのプロップと各要素のプロップ886* [描画箇所](/docs/ja/plugins/mods/reference#render-sites)と[要素](/docs/ja/plugins/mods/reference#elements): 各描画箇所の props と各要素の props