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. Übergeben Sie `true` oder lassen Sie das Feld weg, da Claude Code `false` ablehnt. |
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
319Um 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.
320
321<h4 id="when-a-pane-waits-for-a-wider-terminal">
322 Wenn ein Pane auf ein breiteres Terminal wartet
323</h4>
324
325Ein 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:
326
327* **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
328* **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.
329
330Wenn 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.
331
332<h2 id="build-a-tree-from-elements">
333 Erstellen Sie einen Baum aus Elementen
334</h2>
335
336Was 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.
337
338Um 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`.
339
340Die meisten Zeichnungen verwenden vier Elemente. Wählen Sie eine Registerkarte, um jedes zu sehen und wie das Terminal es zeichnet:
341
342<Tabs>
343 <Tab title="Text">
344 `Text` zeichnet einen String mit optionalem Styling wie `bold` und `color`:
345
346 ```javascript theme={null}
347 Text({ children: ['This is the first tab.'] })
348 ```
349
350 ```text theme={null}
351 This is the first tab.
352 ```
353 </Tab>
354
355 <Tab title="Box">
356 `Box` ordnet an, was darin ist, in einer Reihe oder einer Spalte. Diese setzt eine Schaltfläche und eine Textzeile nebeneinander, zwei Spalten auseinander:
357
358 ```javascript theme={null}
359 Box({
360 flexDirection: 'row',
361 columnGap: 2,
362 children: [
363 Button({ key: 'more', label: 'Add one', onPress: addOne }),
364 Text({ children: ['Count: 0'] }),
365 ],
366 })
367 ```
368
369 ```text theme={null}
370 [ Add one ] Count: 0
371 ```
372 </Tab>
373
374 <Tab title="Button">
375 `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:
376
377 ```javascript theme={null}
378 Button({ key: 'more', label: 'Add one', onPress: addOne })
379 Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
380 ```
381
382 ```text theme={null}
383 [ Add one ]
384 1: One
385 ```
386 </Tab>
387
388 <Tab title="Input">
389 `Input` ist ein Textfeld. Es führt Ihren `onSubmit` Callback mit dem Text aus, wenn der Benutzer Enter drückt:
390
391 ```javascript theme={null}
392 Input({
393 key: 'new-note',
394 label: 'Note',
395 placeholder: 'Type a note and press Enter',
396 value: '',
397 submitLabel: 'add',
398 onSubmit: addNote,
399 })
400 ```
401
402 ```text theme={null}
403 Note: Type a note and press Enter ⏎ add
404 ```
405 </Tab>
406</Tabs>
407
408Diese Tabelle listet jedes Element auf:
409
410| Element | Was es zeichnet | Wo |
411| :- | :- | :- |
412| `Box` | Ein Flex-Container. Nimmt Layout-Props wie `flexDirection`, `columnGap`, `padding`, `borderStyle` und `width`. | Überall |
413| `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 |
414| `Button` | Ein Steuerelement, das `onPress` aufruft | Überall |
415| `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 |
416| `Input`, `Select` | Ein Textfeld und ein Picker | Terminal, Desktop |
417| `Svg` | Ein SVG-Dokument | Desktop |
418| `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 |
419| `Raster`, `Image` | Ein [Gitter von farbigen Zellen](#draw-a-grid-of-colored-cells) und ein Bild | Terminal |
420
421Wenn 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.
422
423Wenn 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.
424
425In 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.
426
427<h3 id="draw-a-grid-of-colored-cells">
428 Zeichnen Sie ein Gitter von farbigen Zellen
429</h3>
430
431Fü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.
432
433Die 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:
434
435```javascript theme={null}
436// The value that means "use the terminal's default color"
437const DEFAULT_COLOR = 0x01000000
438
439// Pack rows of [character, color] pairs into the one string a Raster takes
440// One cell is three numbers: the character's code point, its color, and its background
441function cellsOf(rows) {
442 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
443 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
444}
445
446on('ui.render', { component: 'Pane' }, async ($, e, next) => {
447 // Draw only in the pane opened with the id 'heat'
448 if (e.requestId !== 'heat') return next(e)
449 const { Box, Text, Raster } = $.ui.resolve(e)
450 // Two rows of three cells, each a block character and its color
451 const rows = [
452 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
453 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
454 ]
455 if (e.surface !== 'terminal') {
456 return Text({ children: ['The heat map needs the terminal.'] })
457 }
458 return Box({
459 flexDirection: 'column',
460 children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
461 })
462})
463```
464
465Im Terminal zeigt das Pane das Gitter:
466
467<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" />
468
469Das `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.
470
471Jedes 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.
472
473<h2 id="respond-to-presses-and-typing">
474 Reagieren Sie auf Drücke und Eingaben
475</h2>
476
477Wenn 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:
478
479* **`Button`**: nimmt `onPress(e)`, wobei `e.surface` die App ist, von der der Druck kam
480* **`Input`**: nimmt `onSubmit(value)` und `onInput(value)`
481* **`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' }]`
482
483Ein 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.
484
485<h3 id="know-which-keys-your-mod-can-receive">
486 Tastaturfokus und Hotkeys
487</h3>
488
489Ihr 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.
490
491<h4 id="how-a-pane-gets-keyboard-focus">
492 Wie ein Pane den Tastaturfokus erhält
493</h4>
494
495Ein Pane erhält den Tastaturfokus auf eine von drei Arten:
496
497* Ihr Mod öffnet es mit `focus: true` von einem Befehl oder einem Druck
498* Der Benutzer drückt Ctrl+X dann Tab
499* Der Benutzer klickt darauf
500
501Claude 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.
502
503<h4 id="what-each-key-does">
504 Was jede Taste tut
505</h4>
506
507Diese Tabelle listet auf, was eine Taste tut, während Ihr Pane oder Band den Tastaturfokus hat:
508
509| Taste | Was es tut |
510| :- | :- |
511| Tab | Bewegt sich zum nächsten Steuerelement |
512| 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. |
513| Enter | Drückt die fokussierte `Button`, sendet die fokussierte `Input` oder wählt in einem `Select` |
514| Die Hotkey einer Schaltfläche | Drückt diese Schaltfläche. Während ein `Input` den Fokus hat, geht jede druckbare Taste zum Feld. |
515| Esc | Gibt den Tastaturfokus zur Eingabeaufforderung zurück. Mit `closeOnEscape: true` schließt es auch das Pane. |
516
517Ein Mod kann Tab oder die Pfeiltasten nicht an etwas anderes binden, daher steuert ein Spiel mit `w`, `a`, `s` und `d`.
518
519<h4 id="set-a-hotkey-and-the-first-focus">
520 Setzen Sie eine Hotkey und den ersten Fokus
521</h4>
522
523Zwei Props auf einem Steuerelement entscheiden, wie die Tastatur es erreicht:
524
525* **`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'`
526* **`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.
527
528Wie eine Hotkey angezeigt wird, hängt von der Schaltfläche und der App ab:
529
530| Schaltfläche | Im Terminal | In der Desktop-App |
531| :- | :- | :- |
532| Mit Klammern, der Standard | `[ Add one ]`, ohne Hotkey angezeigt | Das Label mit einem kleinen Schlüssel daneben |
533| Mit `plain: true` | `1: One` | Das Label mit einem kleinen Schlüssel daneben |
534
535Im 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.
536
537<h3 id="take-typed-input-and-draw-a-row-for-each-item">
538 Nehmen Sie eingegebenen Text und zeichnen Sie eine Reihe für jedes Element
539</h3>
540
541Viele 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:
542
543```text theme={null}
544╭──────────────────────────────────────────────────────────╮
545│ Note: Type a note and press Enter ⏎ add ✕ │
546│ x buy milk │
547│ x call bob │
548╰──────────────────────────────────────────────────────────╯
549```
550
551Das Beispiel verwendet zwei Techniken:
552
553* **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
554* **Zeichnen Sie eine Liste**: ordnen Sie Ihre Daten einer Reihe jeweils zu, und geben Sie jedem `key` der Reihe seine eigene
555
556Dieser Hook zeichnet den Inhalt des Pane:
557
558```javascript theme={null}
559// The list the pane draws
560let notes = []
561
562on('ui.render', { component: 'Pane' }, async ($, e, next) => {
563 // Draw only in the pane opened with the id 'notes'
564 if (e.requestId !== 'notes') return next(e)
565 const { Box, Text, Button, Input } = $.ui.resolve(e)
566 const redraw = () => $.ui.invalidate('ui.render')
567
568 return Box({
569 flexDirection: 'column',
570 children: [
571 Input({
572 key: 'new-note',
573 label: 'Note',
574 placeholder: 'Type a note and press Enter',
575 // Draw the field empty each time, which clears it after a submit
576 value: '',
577 submitLabel: 'add',
578 autoFocus: true,
579 // Runs when you press Enter in the field
580 onSubmit: async (value) => {
581 // Ignore an empty line
582 if (!value.trim()) return
583 notes = [...notes, value.trim()]
584 redraw()
585 await $.store.set('notes', notes)
586 },
587 }),
588 // One row for each note: a delete button, then the note's text
589 ...notes.map((note, i) =>
590 Box({
591 flexDirection: 'row',
592 columnGap: 1,
593 children: [
594 Button({
595 // A key of its own, so each row's button can be told apart
596 key: 'delete-' + i,
597 label: 'x',
598 plain: true,
599 onPress: async () => {
600 notes = notes.filter((_, j) => j !== i)
601 redraw()
602 await $.store.set('notes', notes)
603 },
604 }),
605 Text({ children: [note] }),
606 ],
607 }),
608 ),
609 ],
610 })
611})
612```
613
614Um das Pane zu versuchen:
615
616* **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.
617* **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.
618
619Jede Änderung folgt dem gleichen Render-Zyklus wie `hello-tabs`: Der Callback ändert `notes`, ruft `redraw` auf und speichert die Liste in `$.store`.
620
621Das 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 `''`.
622
623Das 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.
624
625Drei Props machen die Zeile des Feldes aus, `Note: Type a note and press Enter ⏎ add`:
626
627| Prop | Im Beispiel | Was es ist |
628| :- | :- | :- |
629| `label` | `Note` | Der Text vor dem Feld. Das Terminal zeichnet `: ` danach. |
630| `placeholder` | `Type a note and press Enter` | Schwacher Text, der angezeigt wird, während das Feld leer ist |
631| `submitLabel` | `add` | Das Wort nach `⏎`, das sagt, was Enter tut |
632
633Das 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.
634
635<h2 id="redraw-when-something-changes">
636 Zeichnen Sie eine Site neu
637</h2>
638
639Eine 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.
640
641<h3 id="when-claude-code-redraws-without-being-asked">
642 Wenn Claude Code ohne Aufforderung neu zeichnet
643</h3>
644
645Claude 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.
646
647<h3 id="redraw-when-your-data-changes">
648 Zeichnen Sie neu, wenn sich Ihre Daten ändern
649</h3>
650
651Um 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:
652
653```javascript theme={null}
654let count = 0
655
656on('ui.render', { component: 'Pane' }, async ($, e, next) => {
657 if (e.requestId !== 'counter') return next(e)
658 const { Box, Text, Button } = $.ui.resolve(e)
659 return Box({
660 flexDirection: 'row',
661 columnGap: 2,
662 children: [
663 Button({
664 key: 'more',
665 label: 'Add one',
666 onPress: () => {
667 count += 1
668 // The data changed, so ask Claude Code to draw the pane again
669 $.ui.invalidate('ui.render')
670 },
671 }),
672 Text({ children: ['Count: ' + count] }),
673 ],
674 })
675})
676```
677
678Jeder Druck erhöht die Zahl im Pane. Das [`hello-tabs` Beispiel](#build-a-pane-with-tabs) wickelt den gleichen Aufruf in seine `redraw` Funktion.
679
680Ein 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.
681
682<h3 id="redraw-on-a-timer">
683 Zeichnen Sie auf einem Timer neu
684</h3>
685
686Um 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:
687
688```javascript theme={null}
689on('session.start', async ($, e, next) => {
690 // Every 1000 milliseconds, ask Claude Code to draw your sites again
691 $.clock.every(1000, () => $.ui.invalidate('ui.render'))
692 return next(e)
693})
694```
695
696Claude 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.
697
698<h3 id="how-often-a-site-can-redraw">
699 Wie oft eine Site neu gezeichnet werden kann
700</h3>
701
702Claude 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.
703
704Aufrufe, 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.
705
706<h2 id="keep-state">
707 Behalten Sie den Status
708</h2>
709
710Ein 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:
711
712| Halten Sie es in | Es dauert bis | Verwenden Sie es für |
713| :- | :- | :- |
714| 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` |
715| `$.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 |
716| `$.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 |
717
718`$.store.get(key)` wird zu dem Wert oder `undefined` aufgelöst, und `$.store.set(key, value)` nimmt jeden JSON-Wert.
719
720<h3 id="keep-a-value-in-state">
721 Behalten Sie einen Wert in `$.state`
722</h3>
723
724`$.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.
725
726Um 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`.
727
728<h4 id="declare-the-values">
729 Deklarieren Sie die Werte
730</h4>
731
732Deklarieren 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`:
733
734```typescript hello-tabs/types/index.d.ts theme={null}
735declare module 'claude-code' {
736 interface PluginState {
737 'hello-tabs': {
738 tab: 'one' | 'two'
739 count: number
740 }
741 }
742}
743```
744
745<h4 id="point-the-manifest-at-the-declaration">
746 Zeigen Sie das Manifest auf die Deklaration
747</h4>
748
749Um `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:
750
751```json hello-tabs/.claude-plugin/plugin.json theme={null}
752{
753 "name": "hello-tabs",
754 "version": "0.1.0",
755 "description": "Opens a pane with two tabs and a counter",
756 "author": { "name": "Your Name" },
757 "types": "./types/index.d.ts"
758}
759```
760
761<h4 id="define-read-and-write-a-value">
762 Definieren, lesen und schreiben Sie einen Wert
763</h4>
764
765In 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:
766
767```javascript theme={null}
768import { atom, read, update } from 'claude-code'
769
770// At the top of the module: name the value and give its default
771const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)
772
773// In the ui.render hook: read the value to draw it
774const n = await read($, count)
775
776// In a Button: write a new value from the old one
777onPress: () => update($, count, (value) => value + 1)
778```
779
780Da der `ui.render` Hook `count` gelesen hat, führt Claude Code den Hook jedes Mal erneut aus, wenn die Schaltfläche ihn schreibt.
781
782Drei Regeln gelten für den Code:
783
784* **Schreiben Sie `plugin` und `key` als Literal-Strings**: `claude plugin validate` liest sie aus Ihrer Quelle
785* **Deklarieren Sie jeden Wert in der Typendatei**: sonst schlägt die Validierung mit `hello-tabs.count is not declared` fehl
786* **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
787
788<h4 id="change-hello-tabs-to-use-state">
789 Ändern Sie `hello-tabs`, um `$.state` zu verwenden
790</h4>
791
792Um `count` in `hello-tabs` in `$.state` zu verschieben, ändern Sie jede Zeile, die ihn verwendet:
793
794* **Am Anfang des Moduls**: fügen Sie die `import` Zeile hinzu, und ersetzen Sie `let count = 0` mit der `atom` Zeile
795* **Im `ui.render` Hook**: fügen Sie die `read` Zeile vor `tabButton` hinzu, und zeichnen Sie `'Count: ' + n` im `Text`
796* **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
797* **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)
798
799Behalten Sie `redraw` für die Tab-Schaltflächen, da `tab` immer noch eine Variable ist.
800
801<h3 id="load-a-saved-value-again-after-clear">
802 Laden Sie einen gespeicherten Wert erneut nach `/clear`
803</h3>
804
805Wenn 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.
806
807Dieser 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:
808
809```javascript theme={null}
810// Copy the saved count from $.store into $.state, or 0 if nothing is saved
811async function loadCount($) {
812 const saved = Number((await $.store.get('count')) ?? 0)
813 await update($, count, () => saved)
814}
815
816// Runs before your first prompt, and again after a reload
817on('session.start', async ($, e, next) => {
818 await loadCount($)
819 return next(e)
820})
821
822// Runs again after /clear, /resume, and /branch, which reports fork
823on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
824 await loadCount($)
825 return next(e)
826})
827```
828
829Mit 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.
830
831`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.
832
833Um den Reload ohne eine Sitzung zu überprüfen, [testen Sie die Zeichnung nach `/clear`](/docs/de/plugins/mods/test#test-a-drawing-after-clear).
834
835<h3 id="save-from-more-than-one-session">
836 Speichern Sie aus mehr als einer Sitzung
837</h3>
838
839Jede 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.
840
841Zwei Auswahlmöglichkeiten machen das weniger wahrscheinlich:
842
843* **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
844* **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.
845
846Diese Schaltfläche addiert eins zu dem, was der Store jetzt hält, dann aktualisiert die Zeichnung:
847
848```javascript theme={null}
849onPress: async () => {
850 // Read what the store holds now, which another session may have changed
851 const saved = Number((await $.store.get('count')) ?? 0)
852 // Save the new count, then show it
853 await $.store.set('count', saved + 1)
854 await update($, count, () => saved + 1)
855}
856```
857
858Wenn 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.
859
860<h2 id="next-steps">
861 Nächste Schritte
862</h2>
863
864* [Reagieren Sie auf Ereignisse](/docs/de/plugins/mods/events): speisen Sie Ihre Zeichnung aus Tool-Calls und Turns
865* [Verwenden Sie die Mods API](/docs/de/plugins/mods/api): speisen Sie Ihre Zeichnung aus Timern und Modell-Aufrufen
866* [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
867* [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