1> ## Documentation Index
2> 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.
4
5# Mit einem Mod in der Benutzeroberfläche zeichnen
6
7> Zeichnen Sie Panes, ein Band über der Eingabeaufforderung, Schaltflächen und Textfelder aus einem Claude Code Mod, verarbeiten Sie Drücke und Eingaben, und behalten Sie den Status zwischen Neuzeichnungen und Sitzungen bei.
8
9Ein Mod kann seine eigene Benutzeroberfläche in Claude Code zeichnen und Teile der Benutzeroberfläche ändern, die Claude Code bereits zeichnet. Jeder Ort, an dem ein Mod zeichnen kann, wird als [Render-Site](/docs/de/plugins/mods/reference#render-sites) bezeichnet, z. B. ein Pane, das Band über der Eingabeaufforderung oder der Spinner. Claude Code löst das [`ui.render`](/docs/de/plugins/mods/reference#interface) Ereignis jedes Mal aus, wenn es eine Render-Site zeichnen möchte, und Ihr Hook für dieses Ereignis gibt zurück, was dort gezeichnet werden soll.
10
11Diese Karte zeigt, wo ein Mod in einer Terminal-Sitzung zeichnen kann:
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="Karte einer Claude Code Terminal-Sitzung. Ein Mod kann ein Pane als Seitenleiste auf der rechten Seite hinzufügen, einen Toast oben rechts im Transkript, eine Protokollzeile im Transkript, ein Band über der Eingabeaufforderung und eine Statuszeile unter der Eingabeaufforderung. Ein Mod kann Nachrichten, Tool-Call-Zeilen und den Spinner neu zeichnen. Die Eingabeaufforderung ist Claude Codes eigene." width="600" height="336" data-path="images/mods-screen-map.svg" />
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="Karte einer Claude Code Terminal-Sitzung. Ein Mod kann ein Pane als Seitenleiste auf der rechten Seite hinzufügen, einen Toast oben rechts im Transkript, eine Protokollzeile im Transkript, ein Band über der Eingabeaufforderung und eine Statuszeile unter der Eingabeaufforderung. Ein Mod kann Nachrichten, Tool-Call-Zeilen und den Spinner neu zeichnen. Die Eingabeaufforderung ist Claude Codes eigene." width="600" height="336" data-path="images/mods-screen-map-dark.svg" />
16
17In einem schmaleren Terminal sitzt das Pane über der Eingabeaufforderung statt neben dem Transkript.
18
19Erstellen Sie Ihren [ersten Mod](/docs/de/plugins/mods/create), bevor Sie hier beginnen. Beginnen Sie mit dem durchgearbeiteten Beispiel, das ein Pane mit zwei Registerkarten und einem Zähler erstellt, lesen Sie dann den Abschnitt für jeden Teil, den Sie ändern möchten.
20
21<Note>
22 Um eine Eigenschaft oder ein Limit nachzuschlagen, siehe die [Referenz](/docs/de/plugins/mods/reference#render-sites).
23</Note>
24
25<h2 id="build-a-pane-with-tabs">
26 Erstellen Sie ein Pane mit Registerkarten
27</h2>
28
29In diesem Abschnitt erstellen Sie einen Mod, der einen `/hello-tabs` Befehl hinzufügt, und der Befehl öffnet ein Pane. Ein Pane ist eine Seitenleiste neben dem Transkript in einem breiten Vollbildterminal oder eine gerahmte Region über der Eingabeaufforderung. Dieses Pane zeigt zwei Registerkarten, und die zweite Registerkarte hat eine Schaltfläche, die eins zu einem Zähler addiert. Die Anzahl ist immer noch da, nachdem Sie Claude Code neu starten.
30
31Der fertige Mod sieht so aus. Die Aufzeichnung öffnet das Pane, wechselt zur zweiten Registerkarte, drückt die Schaltfläche ein paar Mal und kehrt zur ersten Registerkarte zurück:
32
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="Der Befehl /hello-tabs wird an der Claude Code Eingabeaufforderung eingegeben und ein gerahmtes Pane öffnet sich darüber, mit „1: One" und „2: Two" oben und dem Text „This is the first tab." Die zweite Registerkarte zeigt eine Schaltfläche „Add one" neben „Count: 1", und die Anzahl steigt auf 3. Das Pane kehrt dann zur ersten Registerkarte zurück." data-path="images/mods-hello-tabs-light.mp4" />
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="Der Befehl /hello-tabs wird an der Claude Code Eingabeaufforderung eingegeben und ein gerahmtes Pane öffnet sich darüber, mit „1: One" und „2: Two" oben und dem Text „This is the first tab." Die zweite Registerkarte zeigt eine Schaltfläche „Add one" neben „Count: 1", und die Anzahl steigt auf 3. Das Pane kehrt dann zur ersten Registerkarte zurück." data-path="images/mods-hello-tabs-dark.mp4" />
37</Frame>
38
39Claude Code hat kein integriertes Registerkarten-Element, daher sind die Registerkarten zwei Schaltflächen in einer Reihe. Der Mod verfolgt, welche aktiv ist, und zeichnet den Inhalt dieser Registerkarte unter der Reihe.
40
41<Steps>
42 <Step title="Erstellen Sie das Plugin">
43 Ein Mod ist ein Plugin mit einem Manifest, einer `hooks.json`, die auf Ihren Code verweist, und der Codedatei. [Erstellen Sie einen Mod](/docs/de/plugins/mods/create#write-a-mod-yourself) erklärt jeden. Erstellen Sie ein Verzeichnis namens `hello-tabs` mit `.claude-plugin` und `hooks` Verzeichnissen darin, speichern Sie dann die ersten zwei Dateien.
44
45 Speichern Sie das Manifest als `hello-tabs/.claude-plugin/plugin.json`:
46
47 ```json hello-tabs/.claude-plugin/plugin.json theme={null}
48 {
49 "name": "hello-tabs",
50 "version": "0.1.0",
51 "description": "Opens a pane with two tabs and a counter",
52 "author": { "name": "Your Name" }
53 }
54 ```
55
56 Benennen Sie Ihren Einstiegspunkt in `hello-tabs/hooks/hooks.json`:
57
58 ```json hello-tabs/hooks/hooks.json theme={null}
59 {
60 "modules": ["./register.js"]
61 }
62 ```
63 </Step>
64
65 <Step title="Schreiben Sie den Code">
66 Der Code führt drei Aufgaben aus, eine in jedem Hook:
67
68 * Fügt den `/hello-tabs` Befehl hinzu
69 * Öffnet das Pane, wenn Sie diesen Befehl ausführen
70 * Zeichnet den Inhalt des Pane: die Reihe von Registerkarten und den Hauptteil der offenen Registerkarte
71
72 Zwei Variablen auf Modulebene, `tab` und `count`, halten den Status des Pane.
73
74 Speichern Sie dies als `hello-tabs/hooks/register.js`:
75
76 ```javascript hello-tabs/hooks/register.js theme={null}
77 // The pane's id, used to open the pane and to recognize it when drawing
78 const PANE = 'hello-tabs'
79
80 // What the pane shows: which tab is open, and the counter's value
81 let tab = 'one'
82 let count = 0
83
84 export function register(on) {
85 // Runs before your first prompt, and again after a reload
86 on('session.start', async ($, e, next) => {
87 await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
88 // Load the count an earlier session saved, if there is one
89 const saved = await $.store.get('count')
90 if (typeof saved === 'number') count = saved
91 return next(e)
92 })
93
94 // Runs when you type /hello-tabs
95 on('command.run', { command: 'hello-tabs' }, async ($) => {
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 })
98 // Print nothing in the transcript
99 return {}
100 })
101
102 // Runs each time Claude Code draws a pane
103 on('ui.render', { component: 'Pane' }, async ($, e, next) => {
104 // Leave other mods' panes alone
105 if (e.requestId !== PANE) return next(e)
106 // Get the elements this app can draw
107 const { Box, Text, Button } = $.ui.resolve(e)
108 // Ask Claude Code to run this hook again
109 const redraw = () => $.ui.invalidate('ui.render')
110
111 // One tab: a button that switches to its tab when pressed
112 const tabButton = (name, label, hotkey) =>
113 Button({
114 key: 'tab-' + name,
115 label,
116 hotkey,
117 plain: true,
118 // Dim the tab that isn't open
119 dimColor: tab !== name,
120 onPress: () => {
121 tab = name
122 redraw()
123 },
124 })
125
126 // What goes under the tabs, depending on which one is open
127 const body =
128 tab === 'one'
129 ? [Text({ children: ['This is the first tab.'] })]
130 : [
131 Box({
132 flexDirection: 'row',
133 columnGap: 2,
134 children: [
135 Button({
136 key: 'more',
137 label: 'Add one',
138 hotkey: 'a',
139 onPress: async () => {
140 count += 1
141 redraw()
142 // Save the count so it's there after a restart
143 await $.store.set('count', count)
144 },
145 }),
146 Text({ children: ['Count: ' + count] }),
147 ],
148 }),
149 ]
150
151 // The whole pane: the row of tabs, a blank line, then the body
152 return Box({
153 flexDirection: 'column',
154 children: [
155 Box({
156 flexDirection: 'row',
157 columnGap: 3,
158 children: [tabButton('one', 'One', '1'), tabButton('two', 'Two', '2')],
159 }),
160 Text({ children: [' '] }),
161 ...body,
162 ],
163 })
164 })
165 }
166 ```
167
168 Jeder Hook führt auch etwas aus, das der Code nicht deutlich macht:
169
170 * **[`session.start`](/docs/de/plugins/mods/reference#session)** liest auch die gespeicherte Anzahl aus [`$.store`](#keep-state), einem Schlüssel-Wert-Speicher, der zwischen Sitzungen bestehen bleibt.
171 * **[`command.run`](/docs/de/plugins/mods/api#add-a-command)** teilt Claude Code nur mit, dass das Pane existiert. Das Öffnen eines Pane zeichnet nichts von selbst: Claude Code löst dann `ui.render` aus, um zu fragen, was darin geht.
172 * **`ui.render`** gibt den Element-Baum zurück, ein `Box`, das andere Boxen, Text und Schaltflächen enthält, und erstellt ihn jedes Mal aus `tab` und `count` neu.
173
174 Das Drücken einer Schaltfläche führt ihren `onPress` Callback aus, der eine Variable ändert und `redraw` aufruft. Claude Code führt dann den `ui.render` Hook erneut aus, und der Hook erstellt einen neuen Baum aus den neuen Werten. Jede interaktive Zeichnung verwendet diesen Render-Zyklus: Ein Callback ändert den Status, und der Hook zeichnet aus dem neuen Status neu.
175 </Step>
176
177 <Step title="Öffnen Sie das Pane">
178 Starten Sie Claude Code in Ihrer Shell mit `claude --plugin-dir ./hello-tabs`. Führen Sie an der Claude Code Eingabeaufforderung `/hello-tabs` aus. Ein Pane öffnet sich mit `1: One` und `2: Two` oben. Drücken Sie `2`, dann drücken Sie `a`, die Hotkey für **Add one**, ein paar Mal. Die Anzahl steigt.
179 </Step>
180
181 <Step title="Überprüfen Sie, dass die Anzahl gespeichert wurde">
182 Drücken Sie Esc, um das Pane zu schließen, und beenden Sie dann die Sitzung. Starten Sie Claude Code in Ihrer Shell erneut mit dem gleichen `claude --plugin-dir ./hello-tabs` Befehl, und führen Sie an der Claude Code Eingabeaufforderung `/hello-tabs` aus. Die Anzahl ist dort, wo Sie sie gelassen haben.
183
184 Um die Anzahl zu löschen, lassen Sie den Mod `$.store.delete('count')` aufrufen. [Behalten Sie den Status](#keep-state) behandelt, wie lange jede Art von Wert dauert.
185 </Step>
186</Steps>
187
188<h2 id="pick-where-to-draw">
189 Wählen Sie, wo Sie zeichnen möchten
190</h2>
191
192Ein `ui.render` Hook läuft für jede Render-Site, es sei denn, Sie grenzen ihn auf die gewünschte ein. Um die Render-Site auszuwählen, übergeben Sie einen Filter, genannt [Matcher](/docs/de/plugins/mods/events#filter-which-events-a-hook-handles), als zweites Argument an `on`. `{ component: 'Pane' }` führt den Hook nur für Panes aus. Im Hook benennt `e.component` die Site, `e.surface` sagt, welche App zeichnet, und `e.props` enthält die eigenen Daten der Site. Für ein Pane ist `e.requestId` die `id`, mit der Sie es geöffnet haben.
193
194Zwei Sites sind leer, bis ein Mod sie ausfüllt, das Pane und das Band. Wählen Sie eine Registerkarte, um zu sehen, was jede ist und wie man darin zeichnet:
195
196<Tabs>
197 <Tab title="Pane">
198 Ein Pane ist eine Seitenleiste neben dem Transkript in einem breiten Vollbildterminal oder eine gerahmte Region über der Eingabeaufforderung. Mit mehreren offenen Panes erhält jedes eine Registerkarte, die seinen Titel anzeigt.
199
200 Ein Pane erscheint, wenn Ihr Mod `$.ui.open` mit einer `id` aufruft, die Sie wählen, wie in `$.ui.open({ id: 'hello-tabs' })`. [Öffnen Sie ein Pane zum richtigen Zeitpunkt](#open-a-pane-at-the-right-time) behandelt die anderen Felder und wann ein Pane auf ein breiteres Terminal wartet.
201
202 Um in Ihrem Pane zu zeichnen, filtern Sie auf `{ component: 'Pane' }` und überprüfen Sie, dass `e.requestId` Ihre `id` ist.
203 </Tab>
204
205 <Tab title="Band über der Eingabeaufforderung">
206 Das Band ist ein Streifen direkt über der Eingabeaufforderung. Es ist immer da, und jeder Mod teilt es sich.
207
208 Ihr Hook gibt einen Baum zurück, um etwas im Band zu zeigen, oder `next(e)`, um nichts zu zeigen. Ein Baum ersetzt das, was die Mods [nach Ihrem](/docs/de/plugins/mods/events#the-order-mods-run-in) dort zeichnen. Um das Ihre zu behalten, setzen Sie das Ergebnis von `await next(e)` unter die Kinder eines [`Box`](#build-a-tree-from-elements) in Ihrem Baum.
209
210 Um im Band zu zeichnen, filtern Sie auf `{ component: 'AbovePrompt' }`.
211 </Tab>
212</Tabs>
213
214<h3 id="change-what-claude-code-already-draws">
215 Ändern Sie, was Claude Code bereits zeichnet
216</h3>
217
218Claude Code zeichnet den Großteil seiner Benutzeroberfläche selbst: Nachrichten, Tool-Call-Zeilen, den Spinner und mehr. Jeder dieser Teile ist auch eine Render-Site, daher kann ein Mod ihn umgestalten oder ersetzen. Um einen zu ändern, filtern Sie Ihren `ui.render` Hook auf seinen Namen aus dieser Tabelle:
219
220| Site | Was es ist |
221| :- | :- |
222| `UserMessage`, `AssistantMessage` | Eine Nachricht im Transkript |
223| `ToolUse`, `ToolResult`, `ToolGroup` | Die Zeile eines Tool-Calls, sein Ergebnis und eine gefaltete Reihe von Calls |
224| `CommandOutput` | Die Zeile, die ein Befehl gedruckt hat |
225| `AskUserQuestion` | Der Dialog, den Claude öffnet, um Sie eine Frage zu stellen |
226| `Spinner`, `ToolProgress`, `TurnDuration` | Statuszeilen für einen Turn: die Zeile, die animiert wird, während Claude arbeitet, eine laufende Tool-Fortschrittszeile und die Zeile, die einen Turn schließt |
227| `InfoNotice`, `SessionMode`, `PromptHint` | Statuszeilen unter dem Logo, die Modusbezeichnungen in der Fußzeile und die Hinweiszeile unter der Eingabeaufforderung |
228
229An einer Site, die Claude Code bereits zeichnet, hat Ihr Hook drei Möglichkeiten: ein Detail ändern, die Zeichnung ersetzen oder sie allein lassen. Wählen Sie eine Registerkarte, um jede auf den Spinner angewendet zu sehen. Die Beispiele lesen eine `calls` Variable, die ein anderer Hook zählt, wie im [Tutorial-Mod](/docs/de/plugins/mods/create#write-a-mod-yourself).
230
231<Tabs>
232 <Tab title="Ändern Sie ein Detail">
233 Um Claude Codes Zeichnung zu behalten und einen Teil davon zu ändern, übergeben Sie `next` eine Kopie des Ereignisses mit geänderten `props`. Dieser Hook ändert den Text nach dem Wort des Spinners:
234
235 ```javascript theme={null}
236 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
237 // Keep Claude Code's spinner, and change the text after its word
238 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
239 })
240 ```
241
242 Der Spinner behält seine Animation und sein Wort, und Ihr Text folgt dem Wort:
243
244 ```text theme={null}
245 Thinking · tool calls: 2…
246 ```
247 </Tab>
248
249 <Tab title="Ersetzen Sie die Zeichnung">
250 Um etwas Eigenes an der Stelle des Spinners zu zeichnen, geben Sie einen Baum zurück und rufen Sie `next` nicht auf. Dieser Hook zeichnet eine Textzeile, wo der Spinner wäre:
251
252 ```javascript theme={null}
253 on('ui.render', { component: 'Spinner' }, async ($, e) => {
254 const { Text } = $.ui.resolve(e)
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'] })
257 })
258 ```
259
260 Während Claude arbeitet, zeigt Ihre Zeile und Claude Codes Spinner nicht:
261
262 ```text theme={null}
263 Claude has made 2 tool calls
264 ```
265 </Tab>
266
267 <Tab title="Lassen Sie es allein">
268 Um die Site so zu lassen, wie Claude Code sie zeichnet, geben Sie `next(e)` zurück. Ein Hook tut das oft für einige Ereignisse und nicht für andere. Dieser Hook lässt den Spinner allein, bis es einen Call zu zählen gibt:
269
270 ```javascript theme={null}
271 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
272 // Nothing to show yet, so pass the event on unchanged
273 if (calls === 0) return next(e)
274 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
275 })
276 ```
277
278 Vor dem ersten Tool-Call sieht der Spinner so aus, wie er ohne den Mod aussieht:
279
280 ```text theme={null}
281 Thinking…
282 ```
283 </Tab>
284</Tabs>
285
286Die Berechtigungsaufforderung ist keine Render-Site, daher kann ein Mod nicht ändern, was sie zeigt. Der Frage-Dialog, `AskUserQuestion`, ist einer, daher kann ein Mod diesen ändern.
287
288Das Terminal und die Desktop-App lösen nicht alle gleichen Sites aus. `Pane`, `AbovePrompt`, `Spinner` und die Transkript-Sites funktionieren in beiden. Ein paar andere Statuszeilen werden nur im Terminal ausgelöst. Die [Render-Sites-Tabelle](/docs/de/plugins/mods/reference#render-sites) listet auf, wo jede ausgelöst wird.
289
290<h3 id="open-a-pane-at-the-right-time">
291 Öffnen Sie ein Pane zum richtigen Zeitpunkt
292</h3>
293
294Ein Pane erscheint nur, wenn Ihr Mod es öffnet. Wie und wann Sie es öffnen, entscheidet, ob es den Tastaturfokus erhält, wie viel Platz es anfordert und ob es überhaupt in einem schmalen Terminal angezeigt wird.
295
296Um ein Pane zu öffnen, rufen Sie [`$.ui.open`](/docs/de/plugins/mods/reference#mods-api-methods) mit einer `id` auf, die Sie wählen. Die `id` ist der Name des Pane: Ihr `ui.render` Hook überprüft sie, und Sie übergeben sie erneut, um das Pane zu schließen.
297
298```javascript theme={null}
299await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
300```
301
302Um das Pane zu schließen, rufen Sie `$.ui.close` mit der `id` auf, mit der Sie es geöffnet haben:
303
304```javascript theme={null}
305await $.ui.close({ id: 'hello-tabs' })
306```
307
308Neben `id` nimmt `$.ui.open` diese optionalen Felder:
309
310| Feld | Was es tut |
311| :- | :- |
312| `title` | Die Registerkarte des Pane, wenn mehr als ein Pane offen ist |
313| `focus` | Fordert [Tastaturfokus](#know-which-keys-your-mod-can-receive) an |
314| `closeOnEscape` | Macht Esc das Pane schließen |
315| `holdToasts` | Hält Toasts, die kleinen Mitteilungen von [`$.ui.toast`](/docs/de/plugins/mods/api#show-something-without-starting-a-turn), bis das Pane schließt |
316| `rows` | Die Höhe, die angefordert wird, wenn das Pane über der Eingabeaufforderung sitzt. Der Standard ist ein Drittel des Platzes. |
317| `columns` | Die Breite, die angefordert wird, wenn das Pane neben dem Transkript sitzt |
318
319`focus`, `closeOnEscape` und `holdToasts` sind optional und akzeptieren nur `true`. Um eines wegzulassen, lassen Sie es weg. Das Übergeben von `false` wirft einen Fehler wie `ui.open: focus is true or left out`. Um eines davon bedingt zu setzen, fügen Sie das Feld nur hinzu, wenn die Bedingung erfüllt ist. Dieser Aufruf fordert Tastaturfokus nur an, wenn `items` nicht leer ist:
320
321```javascript theme={null}
322const pane = { id: 'hello-tabs', title: 'Hello tabs' }
323await $.ui.open(items.length > 0 ? { ...pane, focus: true } : pane)
324```
325
326Um einem Befehl zu ermöglichen, das Pane zu öffnen, während Claude arbeitet, fügen Sie `immediate: true` hinzu, wenn Sie den [Befehl registrieren](/docs/de/plugins/mods/api#add-a-command). Ohne ihn wartet ein Befehl, der während eines Turns eingegeben wird, bis der Turn endet.
327
328<h4 id="when-a-pane-waits-for-a-wider-terminal">
329 Wenn ein Pane auf ein breiteres Terminal wartet
330</h4>
331
332Ein Pane, das Ihr Mod öffnet, ohne gefragt zu werden, erscheint nicht in einem schmalen Terminal, daher kann es einen kleinen Bildschirm nicht übernehmen. Ob es erscheint, hängt davon ab, was es geöffnet hat:
333
334* **Geöffnet durch etwas, das der Benutzer getan hat**, z. B. ein Befehl, den er ausgeführt hat, oder eine Schaltfläche, die er gedrückt hat, das Pane erscheint bei jeder Breite
335* **Geöffnet durch Ihren Mod, der von selbst handelt**, z. B. von einem Timer oder einem [`turn.start`](/docs/de/plugins/mods/events#follow-a-turn) Hook, das Pane erscheint nur in einem Terminal mit mindestens 144 Spalten Breite. Nachdem der Benutzer dieses Pane einmal selbst geöffnet hat, reichen 110 Spalten aus.
336
337Wenn das Pane erscheint, wird `$.ui.open` zu `{ isPlaced: true }` aufgelöst. Wenn das Pane wartet, ist `isPlaced` `false` und `reason` ist ein String, der sagt, warum. Ein wartendes Pane erscheint, wenn der Benutzer es öffnet oder das Terminal verbreitert. Um zu sagen, dass etwas verfügbar ist, ohne ein Pane zu öffnen, rufen Sie `$.ui.toast('Your message')` auf, das eine kleine Mitteilung zeigt, die nach ein paar Sekunden verschwindet.
338
339<h2 id="build-a-tree-from-elements">
340 Erstellen Sie einen Baum aus Elementen
341</h2>
342
343Was ein `ui.render` Hook zurückgibt, ist ein Element-Baum: eine Beschreibung dessen, was zu zeichnen ist, bestehend aus Boxen, Text und Steuerelementen, die ineinander verschachtelt sind. Sie beschreiben die Zeichnung, und Claude Code rendert sie im Terminal oder der Desktop-App.
344
345Um die Elemente zu erhalten, rufen Sie `$.ui.resolve(e)` in Ihrem Hook auf, wie in `const { Box, Text, Button } = $.ui.resolve(e)`. Jedes Element ist eine Funktion. Sie übergeben ihr Props, und Sie setzen die Elemente und Strings, die darin gehen, in `children`.
346
347Die meisten Zeichnungen verwenden vier Elemente. Wählen Sie eine Registerkarte, um jedes zu sehen und wie das Terminal es zeichnet:
348
349<Tabs>
350 <Tab title="Text">
351 `Text` zeichnet einen String mit optionalem Styling wie `bold` und `color`:
352
353 ```javascript theme={null}
354 Text({ children: ['This is the first tab.'] })
355 ```
356
357 ```text theme={null}
358 This is the first tab.
359 ```
360 </Tab>
361
362 <Tab title="Box">
363 `Box` ordnet an, was darin ist, in einer Reihe oder einer Spalte. Diese setzt eine Schaltfläche und eine Textzeile nebeneinander, zwei Spalten auseinander:
364
365 ```javascript theme={null}
366 Box({
367 flexDirection: 'row',
368 columnGap: 2,
369 children: [
370 Button({ key: 'more', label: 'Add one', onPress: addOne }),
371 Text({ children: ['Count: 0'] }),
372 ],
373 })
374 ```
375
376 ```text theme={null}
377 [ Add one ] Count: 0
378 ```
379 </Tab>
380
381 <Tab title="Button">
382 `Button` ist ein Steuerelement, das der Benutzer drücken kann. Es führt Ihren `onPress` Callback aus. Mit `plain: true` hat es keine Klammern und zeigt seine Hotkey:
383
384 ```javascript theme={null}
385 Button({ key: 'more', label: 'Add one', onPress: addOne })
386 Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
387 ```
388
389 ```text theme={null}
390 [ Add one ]
391 1: One
392 ```
393 </Tab>
394
395 <Tab title="Input">
396 `Input` ist ein Textfeld. Es führt Ihren `onSubmit` Callback mit dem Text aus, wenn der Benutzer Enter drückt:
397
398 ```javascript theme={null}
399 Input({
400 key: 'new-note',
401 label: 'Note',
402 placeholder: 'Type a note and press Enter',
403 value: '',
404 submitLabel: 'add',
405 onSubmit: addNote,
406 })
407 ```
408
409 ```text theme={null}
410 Note: Type a note and press Enter ⏎ add
411 ```
412 </Tab>
413</Tabs>
414
415Diese Tabelle listet jedes Element auf:
416
417| Element | Was es zeichnet | Wo |
418| :- | :- | :- |
419| `Box` | Ein Flex-Container. Nimmt Layout-Props wie `flexDirection`, `columnGap`, `padding`, `borderStyle` und `width`. | Überall |
420| `Text` | Gestylter Text. Nimmt `color`, `bold`, `dimColor`, `italic` und `wrap`. Ein `color` ist ein Theme-Schlüssel oder eine Farbe wie `'red'`. Ein `wrap` ist `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'` oder `'truncate-end'`. | Überall |
421| `Button` | Ein Steuerelement, das `onPress` aufruft | Überall |
422| `Link`, `Code`, `Markdown` | Ein Link mit `href` und einem optionalen `label`, ein Codeblock und Text, der so formatiert ist wie Claude's Antworten. `Markdown` nimmt seinen Inhalt in einem `text` Prop, nicht in `children`, und braucht einen `key`, wenn Sie `onLinkPress` übergeben. | Überall |
423| `Input`, `Select` | Ein Textfeld und ein Picker | Terminal, Desktop |
424| `Svg` | Ein SVG-Dokument | Desktop |
425| `Client` | Eine Region, die von einer zweiten Datei von Ihnen gezeichnet wird, für Animation und Zeigereingabe. Diese Datei erhält keine Mods API. Sie erreicht Ihre Hooks nur durch das Posten von Daten, die als `ui.message` Ereignis ankommen. | Terminal, Desktop |
426| `Raster`, `Image` | Ein [Gitter von farbigen Zellen](#draw-a-grid-of-colored-cells) und ein Bild | Terminal |
427
428Wenn Ihr Modul eine `.tsx` oder `.jsx` Datei ist, können Sie den Baum als JSX schreiben. Destrukturieren Sie die Elemente zuerst aus `$.ui.resolve(e)`, da ein Hooks-Modul keine Element-Globals hat.
429
430Wenn ein Baum ein Element verwendet, das die App nicht hat, ein Prop, das ein Element nicht nimmt, oder ein Kind, wo keines geht, zeichnet Claude Code seine eigene Version der Site.
431
432In einer Sitzung, die mit `--plugin-dir` gestartet wurde, sagt eine Transkriptzeile so, wie `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`. Das [Debug-Protokoll](/docs/de/plugins/mods/troubleshoot#read-the-debug-log) zeichnet es als `ui.render (Pane): a hook returned a tree that does not validate` mit dem gleichen Grund auf. Nichts anderes erscheint in der Sitzung, also wenn eine Zeichnung nicht angezeigt wird, überprüfen Sie diese Zeile oder das Protokoll.
433
434<h3 id="draw-a-grid-of-colored-cells">
435 Zeichnen Sie ein Gitter von farbigen Zellen
436</h3>
437
438Für eine Wärmekarte, eine Sparkline oder ein Spielbrett im Terminal zeichnen Sie ein `Raster` und nicht ein `Box` für jede Zelle. Ein `Raster` nimmt einen `key`, seine Größe in `columns` und `rows` und `cells`, die jede Zelle in einen String packt. Jede Zelle sind drei Zahlen: der Code-Punkt des Zeichens, seine Farbe und seine Hintergrundfarbe. Eine Farbe ist eine Hexadezimalzahl mit zwei Ziffern jeweils für Rot, Grün und Blau, wie `0xc62828` für ein Rot oder `0x01000000` für das Standard des Terminals.
439
440Die Desktop-App hat kein `Raster`, also überprüfen Sie `e.surface` und zeichnen Sie dort Text. Dieser Pane-Hauptteil zeichnet eine drei mal zwei Wärmekarte:
441
442```javascript theme={null}
443// The value that means "use the terminal's default color"
444const DEFAULT_COLOR = 0x01000000
445
446// Pack rows of [character, color] pairs into the one string a Raster takes
447// One cell is three numbers: the character's code point, its color, and its background
448function cellsOf(rows) {
449 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
450 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
451}
452
453on('ui.render', { component: 'Pane' }, async ($, e, next) => {
454 // Draw only in the pane opened with the id 'heat'
455 if (e.requestId !== 'heat') return next(e)
456 const { Box, Text, Raster } = $.ui.resolve(e)
457 // Two rows of three cells, each a block character and its color
458 const rows = [
459 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
460 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
461 ]
462 if (e.surface !== 'terminal') {
463 return Text({ children: ['The heat map needs the terminal.'] })
464 }
465 return Box({
466 flexDirection: 'column',
467 children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
468 })
469})
470```
471
472Im Terminal zeigt das Pane das Gitter:
473
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="Ein Pane im Terminal, das ein kleines Gitter von farbigen Blöcken enthält, zwei Reihen von drei. Die obere Reihe ist grün, bernsteinfarben und rot. Die untere Reihe ist grün, grün und bernsteinfarben." width="360" height="132" data-path="images/mods-heat-map.svg" />
475
476Das `rows` Array ist der Teil, den Sie ändern würden, und `cellsOf` verwandelt es in den gepackten String. Der Hook zeichnet nur in einem Pane, dessen `id` `heat` ist, also öffnen Sie eines mit `$.ui.open({ id: 'heat' })` von einem Befehl, wie das [`hello-tabs` Beispiel](#build-a-pane-with-tabs) sein Pane öffnet.
477
478Jedes Zeichen muss eine Zelle breit sein. Um ein `Raster`, das bereits auf dem Bildschirm ist, zu animieren, rufen Sie `$.ui.blit` mit der `id` des Pane als `requestId`, dem `key` des `Raster`, der gleichen Größe und neuen Zellen auf. Für dieses Beispiel ist das `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })`. Es malt dieses eine Element neu, ohne Ihren `ui.render` Hook erneut auszuführen.
479
480<h2 id="respond-to-presses-and-typing">
481 Reagieren Sie auf Drücke und Eingaben
482</h2>
483
484Wenn der Benutzer eine Schaltfläche drückt, in ein Feld tippt oder aus einer Liste auswählt, die Ihr Mod gezeichnet hat, ruft Claude Code die Funktion auf, die Sie diesem Steuerelement gegeben haben, und sie läuft in Ihrem Modul. Jedes Steuerelement nimmt seine eigenen Callbacks:
485
486* **`Button`**: nimmt `onPress(e)`, wobei `e.surface` die App ist, von der der Druck kam
487* **`Input`**: nimmt `onSubmit(value)` und `onInput(value)`
488* **`Select`**: nimmt `onSelect(value)` mit seinen Auswahlmöglichkeiten in `options`, eine Liste von mindestens einer Auswahlmöglichkeit mit eindeutigen Werten, wie `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`
489
490Ein Test drückt oder tippt in ein Steuerelement nach seinem `key`, also geben Sie jedem einen. Jede Verwendung eines Steuerelements löst auch [`ui.press`, `ui.input` oder `ui.select`](/docs/de/plugins/mods/reference#interface) mit dem `key` in `e.element` aus, und ein anderer Mod kann diese Ereignisse hooken. Sein Hook läuft vor Ihrem Callback, daher sieht er, was der Benutzer in Ihren `Input` tippt und kann es ändern oder an Stelle Ihres Callbacks antworten. Die Mods API hat keine Methode, die die Schaltfläche eines anderen Mods drückt.
491
492<h3 id="know-which-keys-your-mod-can-receive">
493 Tastaturfokus und Hotkeys
494</h3>
495
496Ihr Mod liest die Tastatur nie selbst. Der Benutzer drückt eine Taste, Claude Code entscheidet, welches Ihrer Steuerelemente es ist, und der Callback dieses Steuerelements läuft. Abgesehen von einer [Ziffern-Hotkey auf dem Band](/docs/de/plugins/mods/reference#elements) passiert das nur, während Ihr Pane oder Band den Tastaturfokus hat. Der Rest der Zeit gehen Tasten zur Eingabeaufforderung.
497
498<h4 id="how-a-pane-gets-keyboard-focus">
499 Wie ein Pane den Tastaturfokus erhält
500</h4>
501
502Ein Pane erhält den Tastaturfokus auf eine von drei Arten:
503
504* Ihr Mod öffnet es mit `focus: true` von einem Befehl oder einem Druck
505* Der Benutzer drückt Ctrl+X dann Tab
506* Der Benutzer klickt darauf
507
508Claude Code gewährt `focus: true` nur, während die Eingabeaufforderung leer ist und nichts anderes den Tastaturfokus hat. Ein Pane, das sich öffnet, während der Benutzer tippt, nimmt seine Tastenanschläge nicht.
509
510<h4 id="what-each-key-does">
511 Was jede Taste tut
512</h4>
513
514Diese Tabelle listet auf, was eine Taste tut, während Ihr Pane oder Band den Tastaturfokus hat:
515
516| Taste | Was es tut |
517| :- | :- |
518| Tab | Bewegt sich zum nächsten Steuerelement |
519| Auf und Ab | Bewegen Sie sich zwischen Steuerelementen, während Ihre Zeichnung passt. Wenn das Pane oder Band mehr Reihen hat, als es anzeigen kann, scrollen sie es. |
520| Enter | Drückt die fokussierte `Button`, sendet die fokussierte `Input` oder wählt in einem `Select` |
521| Die Hotkey einer Schaltfläche | Drückt diese Schaltfläche. Während ein `Input` den Fokus hat, geht jede druckbare Taste zum Feld. |
522| Esc | Gibt den Tastaturfokus zur Eingabeaufforderung zurück. Mit `closeOnEscape: true` schließt es auch das Pane. |
523
524Ein Mod kann Tab oder die Pfeiltasten nicht an etwas anderes binden, daher steuert ein Spiel mit `w`, `a`, `s` und `d`.
525
526<h4 id="set-a-hotkey-and-the-first-focus">
527 Setzen Sie eine Hotkey und den ersten Fokus
528</h4>
529
530Zwei Props auf einem Steuerelement entscheiden, wie die Tastatur es erreicht:
531
532* **`hotkey`**: um dem Benutzer zu ermöglichen, eine `Button` mit einer Taste zu drücken, geben Sie ihr eine `hotkey` von einer Ziffer oder einem Kleinbuchstaben, wie in `hotkey: 'a'`
533* **`autoFocus`**: um zu wählen, welches Steuerelement den Fokus hat, wenn das Pane sich öffnet, fügen Sie `autoFocus: true` hinzu. Lassen Sie das Prop bei den anderen weg, da Claude Code `autoFocus: false` ablehnt.
534
535Wie eine Hotkey angezeigt wird, hängt von der Schaltfläche und der App ab:
536
537| Schaltfläche | Im Terminal | In der Desktop-App |
538| :- | :- | :- |
539| Mit Klammern, der Standard | `[ Add one ]`, ohne Hotkey angezeigt | Das Label mit einem kleinen Schlüssel daneben |
540| Mit `plain: true` | `1: One` | Das Label mit einem kleinen Schlüssel daneben |
541
542Im Terminal benennen Sie die Taste im Label einer geklammerten Schaltfläche oder verwenden Sie `plain: true`, damit der Benutzer sehen kann, was zu drücken ist. Die [Elements-Referenz](/docs/de/plugins/mods/reference#elements) hat die anderen `Button` Regeln: `action`, Ziffern-Hotkeys auf dem Band und zwei Schaltflächen auf einer Hotkey.
543
544<h3 id="take-typed-input-and-draw-a-row-for-each-item">
545 Nehmen Sie eingegebenen Text und zeichnen Sie eine Reihe für jedes Element
546</h3>
547
548Viele Panes sind ein Textfeld mit einer Liste darunter. Das Beispiel in diesem Abschnitt ist ein Notizen-Pane: Sie tippen eine Notiz und drücken Enter, um sie hinzuzufügen, und jede Notiz hat eine `x` Schaltfläche, die sie löscht. Mit zwei hinzugefügten Notizen zeichnet das Terminal das Pane so:
549
550```text theme={null}
551╭──────────────────────────────────────────────────────────╮
552│ Note: Type a note and press Enter ⏎ add ✕ │
553│ x buy milk │
554│ x call bob │
555╰──────────────────────────────────────────────────────────╯
556```
557
558Das Beispiel verwendet zwei Techniken:
559
560* **Nehmen Sie eingegebenen Text**: ein `Input` ruft `onSubmit(value)` mit dem Text des Feldes auf, wenn der Benutzer Enter drückt, und `onInput(value)` bei jeder Änderung
561* **Zeichnen Sie eine Liste**: ordnen Sie Ihre Daten einer Reihe jeweils zu, und geben Sie jedem `key` der Reihe seine eigene
562
563Dieser Hook zeichnet den Inhalt des Pane:
564
565```javascript theme={null}
566// The list the pane draws
567let notes = []
568
569on('ui.render', { component: 'Pane' }, async ($, e, next) => {
570 // Draw only in the pane opened with the id 'notes'
571 if (e.requestId !== 'notes') return next(e)
572 const { Box, Text, Button, Input } = $.ui.resolve(e)
573 const redraw = () => $.ui.invalidate('ui.render')
574
575 return Box({
576 flexDirection: 'column',
577 children: [
578 Input({
579 key: 'new-note',
580 label: 'Note',
581 placeholder: 'Type a note and press Enter',
582 // Draw the field empty each time, which clears it after a submit
583 value: '',
584 submitLabel: 'add',
585 autoFocus: true,
586 // Runs when you press Enter in the field
587 onSubmit: async (value) => {
588 // Ignore an empty line
589 if (!value.trim()) return
590 notes = [...notes, value.trim()]
591 redraw()
592 await $.store.set('notes', notes)
593 },
594 }),
595 // One row for each note: a delete button, then the note's text
596 ...notes.map((note, i) =>
597 Box({
598 flexDirection: 'row',
599 columnGap: 1,
600 children: [
601 Button({
602 // A key of its own, so each row's button can be told apart
603 key: 'delete-' + i,
604 label: 'x',
605 plain: true,
606 onPress: async () => {
607 notes = notes.filter((_, j) => j !== i)
608 redraw()
609 await $.store.set('notes', notes)
610 },
611 }),
612 Text({ children: [note] }),
613 ],
614 }),
615 ),
616 ],
617 })
618})
619```
620
621Um das Pane zu versuchen:
622
623* **Fügen Sie eine Notiz hinzu**: tippen Sie eine Zeile und drücken Sie Enter. Die Zeile erscheint als neue Reihe, und das Feld leert sich.
624* **Löschen Sie eine Notiz**: drücken Sie Tab, bis die `x` Schaltfläche der Notiz den Fokus hat, dann drücken Sie Enter. Das `x` ist das Label der Schaltfläche und keine Hotkey, daher drückt das Tippen des Buchstabens es nicht.
625
626Jede Änderung folgt dem gleichen Render-Zyklus wie `hello-tabs`: Der Callback ändert `notes`, ruft `redraw` auf und speichert die Liste in `$.store`.
627
628Das Feld leert sich nach jedem Submit wegen seines `value` Props. `value` ist der Text, den das Feld hält, wenn es gezeichnet wird, und das Tippen des Benutzers ersetzt ihn, bis Ihr Hook das Feld erneut zeichnet. Das Beispiel zeichnet das Feld immer mit `''`.
629
630Das Beispiel speichert die Notizen und lädt sie nicht. Um sie in der nächsten Sitzung zurückzubringen, lesen Sie sie in einem `session.start` Hook, wie `hello-tabs` `count` liest.
631
632Drei Props machen die Zeile des Feldes aus, `Note: Type a note and press Enter ⏎ add`:
633
634| Prop | Im Beispiel | Was es ist |
635| :- | :- | :- |
636| `label` | `Note` | Der Text vor dem Feld. Das Terminal zeichnet `: ` danach. |
637| `placeholder` | `Type a note and press Enter` | Schwacher Text, der angezeigt wird, während das Feld leer ist |
638| `submitLabel` | `add` | Das Wort nach `⏎`, das sagt, was Enter tut |
639
640Das Absenden eines `Input` startet keinen Turn, es sei denn, Ihr Callback ruft [`$.prompt.submit`](/docs/de/plugins/mods/api#start-a-turn-from-a-background-job) auf.
641
642<h2 id="redraw-when-something-changes">
643 Zeichnen Sie eine Site neu
644</h2>
645
646Eine Zeichnung ist ein Schnappschuss: Sie zeigt, was Ihr `ui.render` Hook das letzte Mal zurückgegeben hat, als der Hook lief. Um etwas Neues zu zeigen, muss der Hook erneut laufen. Claude Code führt ihn für einige Änderungen erneut aus, und Ihr Mod fragt nach dem Rest.
647
648<h3 id="when-claude-code-redraws-without-being-asked">
649 Wenn Claude Code ohne Aufforderung neu zeichnet
650</h3>
651
652Claude Code führt Ihren `ui.render` Hook erneut aus, wenn sich die Props der Site ändern oder die Breite des Terminals ändert. Es führt den Hook nicht auf einem Timer aus, und es kann nicht sagen, wenn sich eine Variable in Ihrem Modul ändert.
653
654<h3 id="redraw-when-your-data-changes">
655 Zeichnen Sie neu, wenn sich Ihre Daten ändern
656</h3>
657
658Um Ihre Sites nach Ihren eigenen Datenänderungen erneut zu zeichnen, rufen Sie `$.ui.invalidate('ui.render')` auf. Dieses Pane zählt Drücke. Der Callback der Schaltfläche ändert `count` und fragt dann nach einem Neuzeichnen:
659
660```javascript theme={null}
661let count = 0
662
663on('ui.render', { component: 'Pane' }, async ($, e, next) => {
664 if (e.requestId !== 'counter') return next(e)
665 const { Box, Text, Button } = $.ui.resolve(e)
666 return Box({
667 flexDirection: 'row',
668 columnGap: 2,
669 children: [
670 Button({
671 key: 'more',
672 label: 'Add one',
673 onPress: () => {
674 count += 1
675 // The data changed, so ask Claude Code to draw the pane again
676 $.ui.invalidate('ui.render')
677 },
678 }),
679 Text({ children: ['Count: ' + count] }),
680 ],
681 })
682})
683```
684
685Jeder Druck erhöht die Zahl im Pane. Das [`hello-tabs` Beispiel](#build-a-pane-with-tabs) wickelt den gleichen Aufruf in seine `redraw` Funktion.
686
687Ein Wert, den Sie in [`$.state`](#keep-a-value-in-\$-state) halten, braucht den Aufruf nicht, da das Schreiben des Wertes die Sites neu zeichnet, die ihn lesen.
688
689<h3 id="redraw-on-a-timer">
690 Zeichnen Sie auf einem Timer neu
691</h3>
692
693Um eine Uhr, einen Countdown oder einen Wert von außerhalb der Sitzung aktuell zu halten, zeichnen Sie nach einem Zeitplan neu. Starten Sie einen Timer im `session.start` Hook des Moduls. Wenn das Modul bereits einen hat, wie `hello-tabs`, fügen Sie die [`$.clock.every`](/docs/de/plugins/mods/api#run-work-in-the-background) Zeile hinzu:
694
695```javascript theme={null}
696on('session.start', async ($, e, next) => {
697 // Every 1000 milliseconds, ask Claude Code to draw your sites again
698 $.clock.every(1000, () => $.ui.invalidate('ui.render'))
699 return next(e)
700})
701```
702
703Claude Code führt jetzt Ihren `ui.render` Hook einmal pro Sekunde aus. Der Timer stoppt, wenn das Modul neu geladen wird, und die neue Kopie des Moduls startet ihren eigenen.
704
705<h3 id="how-often-a-site-can-redraw">
706 Wie oft eine Site neu gezeichnet werden kann
707</h3>
708
709Claude Code begrenzt, wie oft es neu zeichnet, daher kann Ihr Mod `$.ui.invalidate` so oft aufrufen, wie sich seine Daten ändern. Das sichtbare Pane und das Band haben ein höheres Limit als andere Sites, und die [Limits-Tabelle](/docs/de/plugins/mods/reference#limits) hat die Zahlen.
710
711Aufrufe, die schneller als das Limit kommen, werden in einem Neuzeichnen kombiniert. Dieses Neuzeichnen führt Ihren Hook einmal aus, und der Hook liest Ihre Daten, wie sie in diesem Moment sind, daher zeigt der neueste Wert und die Werte dazwischen nicht. Eine Animation kann nicht schneller als das Limit laufen.
712
713<h2 id="keep-state">
714 Behalten Sie den Status
715</h2>
716
717Ein Mod hat drei Orte, um einen Wert zu halten, und sie unterscheiden sich darin, wie lange der Wert dauert: bis das Modul neu geladen wird, bis die Sitzung endet oder von einer Sitzung zur nächsten. Wählen Sie danach, wie lange der Wert dauern muss:
718
719| Halten Sie es in | Es dauert bis | Verwenden Sie es für |
720| :- | :- | :- |
721| Eine Modulebenen-Variable | Das Modul wird neu geladen, was jedes Mal passiert, wenn Sie eine Datei während der Entwicklung speichern | Werte, die Sie verlieren können, wie `tab` in `hello-tabs` |
722| `$.state` | Die Sitzung endet oder der Benutzer führt `/clear`, `/resume` oder `/branch` aus | Werte, die eine Zeichnung abhängt, die einen Reload überleben sollten |
723| `$.store` | Ihr Mod löscht es oder keine Sitzung liest oder schreibt den Store für [`cleanupPeriodDays`](/docs/de/settings-reference#cleanupperioddays). Der Store ist ein Schlüssel-Wert-Speicher, gespeichert als JSON-Datei Ihres Plugins unter `~/.claude/plugins/store/`. | Einstellungen, Verlauf, alles, das der Benutzer das nächste Mal erwartet zu finden |
724
725`$.store.get(key)` wird zu dem Wert oder `undefined` aufgelöst, und `$.store.set(key, value)` nimmt jeden JSON-Wert.
726
727<h3 id="keep-a-value-in-state">
728 Behalten Sie einen Wert in `$.state`
729</h3>
730
731`$.state` hält Werte für die Länge einer Sitzung, und es zeichnet für Sie neu. Es ist reaktiver Status: ein `ui.render` Hook, der einen Wert liest, abonniert ihn, daher zeichnet Claude Code diese Site jedes Mal neu, wenn Sie den Wert schreiben, und Sie rufen `$.ui.invalidate` nicht auf. Ein Wert in `$.state` überlebt auch ein Reload des Moduls, das eine Variable nicht tut.
732
733Um es einzurichten, deklarieren Sie Ihre Werte, zeigen Sie Ihr Manifest auf die Deklaration, dann definieren und verwenden Sie jeden Wert. Die Beispiele verschieben den `count` aus `hello-tabs` in `$.state`.
734
735<h4 id="declare-the-values">
736 Deklarieren Sie die Werte
737</h4>
738
739Deklarieren Sie die Werte in einer Typendatei. Der äußere Schlüssel ist der Name Ihres Plugins, und jeder Eintrag darunter ist ein Wert und sein Typ. Speichern Sie dies als `hello-tabs/types/index.d.ts`:
740
741```typescript hello-tabs/types/index.d.ts theme={null}
742declare module 'claude-code' {
743 interface PluginState {
744 'hello-tabs': {
745 tab: 'one' | 'two'
746 count: number
747 }
748 }
749}
750```
751
752<h4 id="point-the-manifest-at-the-declaration">
753 Zeigen Sie das Manifest auf die Deklaration
754</h4>
755
756Um `claude plugin validate` zu lassen, Ihren Code gegen diese Datei zu überprüfen, fügen Sie ein `types` Feld zum Manifest mit seinem Pfad hinzu:
757
758```json hello-tabs/.claude-plugin/plugin.json theme={null}
759{
760 "name": "hello-tabs",
761 "version": "0.1.0",
762 "description": "Opens a pane with two tabs and a counter",
763 "author": { "name": "Your Name" },
764 "types": "./types/index.d.ts"
765}
766```
767
768<h4 id="define-read-and-write-a-value">
769 Definieren, lesen und schreiben Sie einen Wert
770</h4>
771
772In Ihrem Modul definieren Sie jeden Wert mit einem Standard, lesen ihn beim Zeichnen und schreiben ihn von einem Callback. `atom` benennt einen Wert und seinen Standard, `read` gibt ihn zurück, und `update` schreibt ihn. Die drei Helfer rufen `$.state.get` und `$.state.set` für Sie auf:
773
774```javascript theme={null}
775import { atom, read, update } from 'claude-code'
776
777// At the top of the module: name the value and give its default
778const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)
779
780// In the ui.render hook: read the value to draw it
781const n = await read($, count)
782
783// In a Button: write a new value from the old one
784onPress: () => update($, count, (value) => value + 1)
785```
786
787Da der `ui.render` Hook `count` gelesen hat, führt Claude Code den Hook jedes Mal erneut aus, wenn die Schaltfläche ihn schreibt.
788
789Drei Regeln gelten für den Code:
790
791* **Schreiben Sie `plugin` und `key` als Literal-Strings**: `claude plugin validate` liest sie aus Ihrer Quelle
792* **Deklarieren Sie jeden Wert in der Typendatei**: sonst schlägt die Validierung mit `hello-tabs.count is not declared` fehl
793* **Schreiben Sie von einem Callback oder einem anderen Ereignis-Hook**: ein `ui.render` Hook kann Status lesen und kann ihn nicht schreiben, daher schreiben Sie von `onPress`, `onSubmit` oder einem Hook für ein anderes Ereignis
794
795<h4 id="change-hello-tabs-to-use-state">
796 Ändern Sie `hello-tabs`, um `$.state` zu verwenden
797</h4>
798
799Um `count` in `hello-tabs` in `$.state` zu verschieben, ändern Sie jede Zeile, die ihn verwendet:
800
801* **Am Anfang des Moduls**: fügen Sie die `import` Zeile hinzu, und ersetzen Sie `let count = 0` mit der `atom` Zeile
802* **Im `ui.render` Hook**: fügen Sie die `read` Zeile vor `tabButton` hinzu, und zeichnen Sie `'Count: ' + n` im `Text`
803* **Im Add one Button**: ersetzen Sie `onPress` mit dem aus [Speichern Sie aus mehr als einer Sitzung](#save-from-more-than-one-session), das die Anzahl speichert sowie schreibt
804* **Im `session.start` Hook**: ersetzen Sie die zwei Zeilen, die `saved` lesen, mit dem `loadCount` Aufruf aus [Laden Sie einen gespeicherten Wert erneut nach `/clear`](#load-a-saved-value-again-after-clear)
805
806Behalten Sie `redraw` für die Tab-Schaltflächen, da `tab` immer noch eine Variable ist.
807
808<h3 id="load-a-saved-value-again-after-clear">
809 Laden Sie einen gespeicherten Wert erneut nach `/clear`
810</h3>
811
812Wenn Ihr Mod einen gespeicherten Wert aus `$.store` in `$.state` bei `session.start` kopiert, muss er ihn nach `/clear`, `/resume` oder `/branch` erneut kopieren. Diese Befehle setzen jeden `$.state` Wert auf seinen Standard zurück, und `session.start` wird nicht erneut ausgelöst. [`classic.SessionStart`](/docs/de/plugins/mods/events#hook-the-settings-hook-events) wird nach jedem ausgelöst, mit `e.source` auf `clear`, `resume` oder `fork` gesetzt, daher kopieren Sie den Wert erneut in einem Hook darauf. Sonst zeigt Ihre Zeichnung den Standard, und ein Callback, der den `$.state` Wert speichert, schreibt den Standard über das, was Sie gespeichert haben.
813
814Dieser Code lädt `count` aus beiden Hooks. Es baut auf der `$.state` Version von `hello-tabs` auf, wo `count` ein Atom ist und `update` importiert wird. Setzen Sie `loadCount` über `register`, und fügen Sie den `loadCount` Aufruf zum `session.start` Hook hinzu, den Sie bereits haben. `classic.SessionStart` wird auch beim Start und nach Verdichtung ausgelöst, was `$.state` nicht zurückgesetzt, daher hält der Filter auf `source` den Hook zu den drei Resets:
815
816```javascript theme={null}
817// Copy the saved count from $.store into $.state, or 0 if nothing is saved
818async function loadCount($) {
819 const saved = Number((await $.store.get('count')) ?? 0)
820 await update($, count, () => saved)
821}
822
823// Runs before your first prompt, and again after a reload
824on('session.start', async ($, e, next) => {
825 await loadCount($)
826 return next(e)
827})
828
829// Runs again after /clear, /resume, and /branch, which reports fork
830on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
831 await loadCount($)
832 return next(e)
833})
834```
835
836Mit beiden Hooks an Ort und Stelle zeigt das Pane die gespeicherte Anzahl nach `/clear` und nicht `0`, und der nächste Druck von **Add one** addiert zur gespeicherten Anzahl.
837
838`loadCount` schreibt den gespeicherten Wert über den in `$.state`, und `session.start` wird jedes Mal ausgelöst, wenn das Modul neu geladen wird. Um den Store nicht hinter sich zu lassen, speichern Sie bei jeder Änderung, wie die **Add one** Schaltfläche tut.
839
840Um den Reload ohne eine Sitzung zu überprüfen, [testen Sie die Zeichnung nach `/clear`](/docs/de/plugins/mods/test#test-a-drawing-after-clear).
841
842<h3 id="save-from-more-than-one-session">
843 Speichern Sie aus mehr als einer Sitzung
844</h3>
845
846Jede Sitzung auf Ihrem Computer, die Ihren Mod ausführt, teilt einen `$.store`. Ein `get` gefolgt von einem `set` ist nicht atomar. Wenn zwei Sitzungen jeweils einen Wert lesen, ihn ändern und zurückschreiben, rennen sie, und der zweite Schreib ersetzt den ersten.
847
848Zwei Auswahlmöglichkeiten machen das weniger wahrscheinlich:
849
850* **Geben Sie jedem Element seinen eigenen Schlüssel**: ein `set` ändert nur seinen eigenen Schlüssel, daher überschreiben Sitzungen, die verschiedene Schlüssel schreiben, sich nicht gegenseitig
851* **Lesen Sie erneut direkt vor dem Schreiben**: für einen Wert, den mehrere Sitzungen ändern, `get` den Schlüssel im Callback und erstellen Sie den neuen Wert daraus, nicht aus einer Kopie, die Sie bei `session.start` geladen haben. Ein Schreib einer anderen Sitzung geht immer noch verloren, wenn er zwischen Ihrem `get` und Ihrem `set` landet.
852
853Diese Schaltfläche addiert eins zu dem, was der Store jetzt hält, dann aktualisiert die Zeichnung:
854
855```javascript theme={null}
856onPress: async () => {
857 // Read what the store holds now, which another session may have changed
858 const saved = Number((await $.store.get('count')) ?? 0)
859 // Save the new count, then show it
860 await $.store.set('count', saved + 1)
861 await update($, count, () => saved + 1)
862}
863```
864
865Wenn eine zweite Sitzung ihre eigene Schaltfläche drei Mal gedrückt hat, seit diese Sitzung gestartet wurde, zeigt dieser Druck und speichert eine Anzahl, die diese drei enthält.
866
867<h2 id="next-steps">
868 Nächste Schritte
869</h2>
870
871* [Reagieren Sie auf Ereignisse](/docs/de/plugins/mods/events): speisen Sie Ihre Zeichnung aus Tool-Calls und Turns
872* [Verwenden Sie die Mods API](/docs/de/plugins/mods/api): speisen Sie Ihre Zeichnung aus Timern und Modell-Aufrufen
873* [Testen Sie eine Zeichnung](/docs/de/plugins/mods/test#test-a-drawing): drücken Sie Ihre Schaltflächen aus einem Test auf mehr als einer Oberfläche
874* [Render-Sites](/docs/de/plugins/mods/reference#render-sites) und [Elemente](/docs/de/plugins/mods/reference#elements): jede Site's Props und jedes Element's Props