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

# Mit einem Mod in der Benutzeroberfläche zeichnen

> 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.

Ein 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.

Diese Karte zeigt, wo ein Mod in einer Terminal-Sitzung zeichnen kann:

<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" />

<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" />

In einem schmaleren Terminal sitzt das Pane über der Eingabeaufforderung statt neben dem Transkript.

Erstellen 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.

<Note>
  Um eine Eigenschaft oder ein Limit nachzuschlagen, siehe die [Referenz](/docs/de/plugins/mods/reference#render-sites).
</Note>

<h2 id="build-a-pane-with-tabs">
  Erstellen Sie ein Pane mit Registerkarten
</h2>

In 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.

Der 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:

<Frame>
  <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=49d520094d87b5b44bfe50fa49677f06" aria-label="Der Befehl /hello-tabs wird an der Claude Code Eingabeaufforderung eingegeben und ein gerahmtes Pane öffnet sich darüber, mit „1: One&#x22; und „2: Two&#x22; oben und dem Text „This is the first tab.&#x22; Die zweite Registerkarte zeigt eine Schaltfläche „Add one&#x22; neben „Count: 1&#x22;, und die Anzahl steigt auf 3. Das Pane kehrt dann zur ersten Registerkarte zurück." data-path="images/mods-hello-tabs-light.mp4" />

  <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=ff7a14d713d6e5d3b0000efa8522ea4b" aria-label="Der Befehl /hello-tabs wird an der Claude Code Eingabeaufforderung eingegeben und ein gerahmtes Pane öffnet sich darüber, mit „1: One&#x22; und „2: Two&#x22; oben und dem Text „This is the first tab.&#x22; Die zweite Registerkarte zeigt eine Schaltfläche „Add one&#x22; neben „Count: 1&#x22;, und die Anzahl steigt auf 3. Das Pane kehrt dann zur ersten Registerkarte zurück." data-path="images/mods-hello-tabs-dark.mp4" />
</Frame>

Claude 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.

<Steps>
  <Step title="Erstellen Sie das Plugin">
    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.

    Speichern Sie das Manifest als `hello-tabs/.claude-plugin/plugin.json`:

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

    Benennen Sie Ihren Einstiegspunkt in `hello-tabs/hooks/hooks.json`:

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

  <Step title="Schreiben Sie den Code">
    Der Code führt drei Aufgaben aus, eine in jedem Hook:

    * Fügt den `/hello-tabs` Befehl hinzu
    * Öffnet das Pane, wenn Sie diesen Befehl ausführen
    * Zeichnet den Inhalt des Pane: die Reihe von Registerkarten und den Hauptteil der offenen Registerkarte

    Zwei Variablen auf Modulebene, `tab` und `count`, halten den Status des Pane.

    Speichern Sie dies als `hello-tabs/hooks/register.js`:

    ```javascript hello-tabs/hooks/register.js theme={null}
    // The pane's id, used to open the pane and to recognize it when drawing
    const PANE = 'hello-tabs'

    // What the pane shows: which tab is open, and the counter's value
    let tab = 'one'
    let count = 0

    export function register(on) {
      // Runs before your first prompt, and again after a reload
      on('session.start', async ($, e, next) => {
        await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
        // Load the count an earlier session saved, if there is one
        const saved = await $.store.get('count')
        if (typeof saved === 'number') count = saved
        return next(e)
      })

      // Runs when you type /hello-tabs
      on('command.run', { command: 'hello-tabs' }, async ($) => {
        // Open the pane, give it the keyboard, and let Esc close it
        await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
        // Print nothing in the transcript
        return {}
      })

      // Runs each time Claude Code draws a pane
      on('ui.render', { component: 'Pane' }, async ($, e, next) => {
        // Leave other mods' panes alone
        if (e.requestId !== PANE) return next(e)
        // Get the elements this app can draw
        const { Box, Text, Button } = $.ui.resolve(e)
        // Ask Claude Code to run this hook again
        const redraw = () => $.ui.invalidate('ui.render')

        // One tab: a button that switches to its tab when pressed
        const tabButton = (name, label, hotkey) =>
          Button({
            key: 'tab-' + name,
            label,
            hotkey,
            plain: true,
            // Dim the tab that isn't open
            dimColor: tab !== name,
            onPress: () => {
              tab = name
              redraw()
            },
          })

        // What goes under the tabs, depending on which one is open
        const body =
          tab === 'one'
            ? [Text({ children: ['This is the first tab.'] })]
            : [
                Box({
                  flexDirection: 'row',
                  columnGap: 2,
                  children: [
                    Button({
                      key: 'more',
                      label: 'Add one',
                      hotkey: 'a',
                      onPress: async () => {
                        count += 1
                        redraw()
                        // Save the count so it's there after a restart
                        await $.store.set('count', count)
                      },
                    }),
                    Text({ children: ['Count: ' + count] }),
                  ],
                }),
              ]

        // The whole pane: the row of tabs, a blank line, then the body
        return Box({
          flexDirection: 'column',
          children: [
            Box({
              flexDirection: 'row',
              columnGap: 3,
              children: [tabButton('one', 'One', '1'), tabButton('two', 'Two', '2')],
            }),
            Text({ children: [' '] }),
            ...body,
          ],
        })
      })
    }
    ```

    Jeder Hook führt auch etwas aus, das der Code nicht deutlich macht:

    * **[`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.
    * **[`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.
    * **`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.

    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.
  </Step>

  <Step title="Öffnen Sie das Pane">
    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.
  </Step>

  <Step title="Überprüfen Sie, dass die Anzahl gespeichert wurde">
    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.

    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.
  </Step>
</Steps>

<h2 id="pick-where-to-draw">
  Wählen Sie, wo Sie zeichnen möchten
</h2>

Ein `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.

Zwei 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:

<Tabs>
  <Tab title="Pane">
    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.

    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.

    Um in Ihrem Pane zu zeichnen, filtern Sie auf `{ component: 'Pane' }` und überprüfen Sie, dass `e.requestId` Ihre `id` ist.
  </Tab>

  <Tab title="Band über der Eingabeaufforderung">
    Das Band ist ein Streifen direkt über der Eingabeaufforderung. Es ist immer da, und jeder Mod teilt es sich.

    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.

    Um im Band zu zeichnen, filtern Sie auf `{ component: 'AbovePrompt' }`.
  </Tab>
</Tabs>

<h3 id="change-what-claude-code-already-draws">
  Ändern Sie, was Claude Code bereits zeichnet
</h3>

Claude 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:

| Site | Was es ist |
| :- | :- |
| `UserMessage`, `AssistantMessage` | Eine Nachricht im Transkript |
| `ToolUse`, `ToolResult`, `ToolGroup` | Die Zeile eines Tool-Calls, sein Ergebnis und eine gefaltete Reihe von Calls |
| `CommandOutput` | Die Zeile, die ein Befehl gedruckt hat |
| `AskUserQuestion` | Der Dialog, den Claude öffnet, um Sie eine Frage zu stellen |
| `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 |
| `InfoNotice`, `SessionMode`, `PromptHint` | Statuszeilen unter dem Logo, die Modusbezeichnungen in der Fußzeile und die Hinweiszeile unter der Eingabeaufforderung |

An 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).

<Tabs>
  <Tab title="Ändern Sie ein Detail">
    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:

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // Keep Claude Code's spinner, and change the text after its word
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    Der Spinner behält seine Animation und sein Wort, und Ihr Text folgt dem Wort:

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

  <Tab title="Ersetzen Sie die Zeichnung">
    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:

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e) => {
      const { Text } = $.ui.resolve(e)
      // No call to next, so this line is drawn in the spinner's place
      return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
    })
    ```

    Während Claude arbeitet, zeigt Ihre Zeile und Claude Codes Spinner nicht:

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

  <Tab title="Lassen Sie es allein">
    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:

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // Nothing to show yet, so pass the event on unchanged
      if (calls === 0) return next(e)
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    Vor dem ersten Tool-Call sieht der Spinner so aus, wie er ohne den Mod aussieht:

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

Die 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.

Das 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.

<h3 id="open-a-pane-at-the-right-time">
  Öffnen Sie ein Pane zum richtigen Zeitpunkt
</h3>

Ein 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.

Um 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.

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

Um das Pane zu schließen, rufen Sie `$.ui.close` mit der `id` auf, mit der Sie es geöffnet haben:

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

Neben `id` nimmt `$.ui.open` diese optionalen Felder:

| Feld | Was es tut |
| :- | :- |
| `title` | Die Registerkarte des Pane, wenn mehr als ein Pane offen ist |
| `focus` | Fordert [Tastaturfokus](#know-which-keys-your-mod-can-receive) an |
| `closeOnEscape` | Macht Esc das Pane schließen. Übergeben Sie `true` oder lassen Sie das Feld weg, da Claude Code `false` ablehnt. |
| `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 |
| `rows` | Die Höhe, die angefordert wird, wenn das Pane über der Eingabeaufforderung sitzt. Der Standard ist ein Drittel des Platzes. |
| `columns` | Die Breite, die angefordert wird, wenn das Pane neben dem Transkript sitzt |

Um 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.

<h4 id="when-a-pane-waits-for-a-wider-terminal">
  Wenn ein Pane auf ein breiteres Terminal wartet
</h4>

Ein 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:

* **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
* **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.

Wenn 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.

<h2 id="build-a-tree-from-elements">
  Erstellen Sie einen Baum aus Elementen
</h2>

Was 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.

Um 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`.

Die meisten Zeichnungen verwenden vier Elemente. Wählen Sie eine Registerkarte, um jedes zu sehen und wie das Terminal es zeichnet:

<Tabs>
  <Tab title="Text">
    `Text` zeichnet einen String mit optionalem Styling wie `bold` und `color`:

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

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

  <Tab title="Box">
    `Box` ordnet an, was darin ist, in einer Reihe oder einer Spalte. Diese setzt eine Schaltfläche und eine Textzeile nebeneinander, zwei Spalten auseinander:

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

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

  <Tab title="Button">
    `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:

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

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

  <Tab title="Input">
    `Input` ist ein Textfeld. Es führt Ihren `onSubmit` Callback mit dem Text aus, wenn der Benutzer Enter drückt:

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

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

Diese Tabelle listet jedes Element auf:

| Element | Was es zeichnet | Wo |
| :- | :- | :- |
| `Box` | Ein Flex-Container. Nimmt Layout-Props wie `flexDirection`, `columnGap`, `padding`, `borderStyle` und `width`. | Überall |
| `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 |
| `Button` | Ein Steuerelement, das `onPress` aufruft | Überall |
| `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 |
| `Input`, `Select` | Ein Textfeld und ein Picker | Terminal, Desktop |
| `Svg` | Ein SVG-Dokument | Desktop |
| `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 |
| `Raster`, `Image` | Ein [Gitter von farbigen Zellen](#draw-a-grid-of-colored-cells) und ein Bild | Terminal |

Wenn 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.

Wenn 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.

In 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.

<h3 id="draw-a-grid-of-colored-cells">
  Zeichnen Sie ein Gitter von farbigen Zellen
</h3>

Fü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.

Die 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:

```javascript theme={null}
// The value that means "use the terminal's default color"
const DEFAULT_COLOR = 0x01000000

// Pack rows of [character, color] pairs into the one string a Raster takes
// One cell is three numbers: the character's code point, its color, and its background
function cellsOf(rows) {
  const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
  return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
}

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // Draw only in the pane opened with the id 'heat'
  if (e.requestId !== 'heat') return next(e)
  const { Box, Text, Raster } = $.ui.resolve(e)
  // Two rows of three cells, each a block character and its color
  const rows = [
    [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
    [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
  ]
  if (e.surface !== 'terminal') {
    return Text({ children: ['The heat map needs the terminal.'] })
  }
  return Box({
    flexDirection: 'column',
    children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
  })
})
```

Im Terminal zeigt das Pane das Gitter:

<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" />

Das `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.

Jedes 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.

<h2 id="respond-to-presses-and-typing">
  Reagieren Sie auf Drücke und Eingaben
</h2>

Wenn 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:

* **`Button`**: nimmt `onPress(e)`, wobei `e.surface` die App ist, von der der Druck kam
* **`Input`**: nimmt `onSubmit(value)` und `onInput(value)`
* **`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' }]`

Ein 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.

<h3 id="know-which-keys-your-mod-can-receive">
  Tastaturfokus und Hotkeys
</h3>

Ihr 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.

<h4 id="how-a-pane-gets-keyboard-focus">
  Wie ein Pane den Tastaturfokus erhält
</h4>

Ein Pane erhält den Tastaturfokus auf eine von drei Arten:

* Ihr Mod öffnet es mit `focus: true` von einem Befehl oder einem Druck
* Der Benutzer drückt Ctrl+X dann Tab
* Der Benutzer klickt darauf

Claude 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.

<h4 id="what-each-key-does">
  Was jede Taste tut
</h4>

Diese Tabelle listet auf, was eine Taste tut, während Ihr Pane oder Band den Tastaturfokus hat:

| Taste | Was es tut |
| :- | :- |
| Tab | Bewegt sich zum nächsten Steuerelement |
| 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. |
| Enter | Drückt die fokussierte `Button`, sendet die fokussierte `Input` oder wählt in einem `Select` |
| Die Hotkey einer Schaltfläche | Drückt diese Schaltfläche. Während ein `Input` den Fokus hat, geht jede druckbare Taste zum Feld. |
| Esc | Gibt den Tastaturfokus zur Eingabeaufforderung zurück. Mit `closeOnEscape: true` schließt es auch das Pane. |

Ein Mod kann Tab oder die Pfeiltasten nicht an etwas anderes binden, daher steuert ein Spiel mit `w`, `a`, `s` und `d`.

<h4 id="set-a-hotkey-and-the-first-focus">
  Setzen Sie eine Hotkey und den ersten Fokus
</h4>

Zwei Props auf einem Steuerelement entscheiden, wie die Tastatur es erreicht:

* **`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'`
* **`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.

Wie eine Hotkey angezeigt wird, hängt von der Schaltfläche und der App ab:

| Schaltfläche | Im Terminal | In der Desktop-App |
| :- | :- | :- |
| Mit Klammern, der Standard | `[ Add one ]`, ohne Hotkey angezeigt | Das Label mit einem kleinen Schlüssel daneben |
| Mit `plain: true` | `1: One` | Das Label mit einem kleinen Schlüssel daneben |

Im 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.

<h3 id="take-typed-input-and-draw-a-row-for-each-item">
  Nehmen Sie eingegebenen Text und zeichnen Sie eine Reihe für jedes Element
</h3>

Viele 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:

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

Das Beispiel verwendet zwei Techniken:

* **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
* **Zeichnen Sie eine Liste**: ordnen Sie Ihre Daten einer Reihe jeweils zu, und geben Sie jedem `key` der Reihe seine eigene

Dieser Hook zeichnet den Inhalt des Pane:

```javascript theme={null}
// The list the pane draws
let notes = []

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // Draw only in the pane opened with the id 'notes'
  if (e.requestId !== 'notes') return next(e)
  const { Box, Text, Button, Input } = $.ui.resolve(e)
  const redraw = () => $.ui.invalidate('ui.render')

  return Box({
    flexDirection: 'column',
    children: [
      Input({
        key: 'new-note',
        label: 'Note',
        placeholder: 'Type a note and press Enter',
        // Draw the field empty each time, which clears it after a submit
        value: '',
        submitLabel: 'add',
        autoFocus: true,
        // Runs when you press Enter in the field
        onSubmit: async (value) => {
          // Ignore an empty line
          if (!value.trim()) return
          notes = [...notes, value.trim()]
          redraw()
          await $.store.set('notes', notes)
        },
      }),
      // One row for each note: a delete button, then the note's text
      ...notes.map((note, i) =>
        Box({
          flexDirection: 'row',
          columnGap: 1,
          children: [
            Button({
              // A key of its own, so each row's button can be told apart
              key: 'delete-' + i,
              label: 'x',
              plain: true,
              onPress: async () => {
                notes = notes.filter((_, j) => j !== i)
                redraw()
                await $.store.set('notes', notes)
              },
            }),
            Text({ children: [note] }),
          ],
        }),
      ),
    ],
  })
})
```

Um das Pane zu versuchen:

* **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.
* **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.

Jede Änderung folgt dem gleichen Render-Zyklus wie `hello-tabs`: Der Callback ändert `notes`, ruft `redraw` auf und speichert die Liste in `$.store`.

Das 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 `''`.

Das 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.

Drei Props machen die Zeile des Feldes aus, `Note: Type a note and press Enter ⏎ add`:

| Prop | Im Beispiel | Was es ist |
| :- | :- | :- |
| `label` | `Note` | Der Text vor dem Feld. Das Terminal zeichnet `: ` danach. |
| `placeholder` | `Type a note and press Enter` | Schwacher Text, der angezeigt wird, während das Feld leer ist |
| `submitLabel` | `add` | Das Wort nach `⏎`, das sagt, was Enter tut |

Das 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.

<h2 id="redraw-when-something-changes">
  Zeichnen Sie eine Site neu
</h2>

Eine 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.

<h3 id="when-claude-code-redraws-without-being-asked">
  Wenn Claude Code ohne Aufforderung neu zeichnet
</h3>

Claude 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.

<h3 id="redraw-when-your-data-changes">
  Zeichnen Sie neu, wenn sich Ihre Daten ändern
</h3>

Um 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:

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

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  if (e.requestId !== 'counter') return next(e)
  const { Box, Text, Button } = $.ui.resolve(e)
  return Box({
    flexDirection: 'row',
    columnGap: 2,
    children: [
      Button({
        key: 'more',
        label: 'Add one',
        onPress: () => {
          count += 1
          // The data changed, so ask Claude Code to draw the pane again
          $.ui.invalidate('ui.render')
        },
      }),
      Text({ children: ['Count: ' + count] }),
    ],
  })
})
```

Jeder Druck erhöht die Zahl im Pane. Das [`hello-tabs` Beispiel](#build-a-pane-with-tabs) wickelt den gleichen Aufruf in seine `redraw` Funktion.

Ein 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.

<h3 id="redraw-on-a-timer">
  Zeichnen Sie auf einem Timer neu
</h3>

Um 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:

```javascript theme={null}
on('session.start', async ($, e, next) => {
  // Every 1000 milliseconds, ask Claude Code to draw your sites again
  $.clock.every(1000, () => $.ui.invalidate('ui.render'))
  return next(e)
})
```

Claude 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.

<h3 id="how-often-a-site-can-redraw">
  Wie oft eine Site neu gezeichnet werden kann
</h3>

Claude 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.

Aufrufe, 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.

<h2 id="keep-state">
  Behalten Sie den Status
</h2>

Ein 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:

| Halten Sie es in | Es dauert bis | Verwenden Sie es für |
| :- | :- | :- |
| 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` |
| `$.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 |
| `$.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 |

`$.store.get(key)` wird zu dem Wert oder `undefined` aufgelöst, und `$.store.set(key, value)` nimmt jeden JSON-Wert.

<h3 id="keep-a-value-in-state">
  Behalten Sie einen Wert in `$.state`
</h3>

`$.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.

Um 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`.

<h4 id="declare-the-values">
  Deklarieren Sie die Werte
</h4>

Deklarieren 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`:

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

<h4 id="point-the-manifest-at-the-declaration">
  Zeigen Sie das Manifest auf die Deklaration
</h4>

Um `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:

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

<h4 id="define-read-and-write-a-value">
  Definieren, lesen und schreiben Sie einen Wert
</h4>

In 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:

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

// At the top of the module: name the value and give its default
const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)

// In the ui.render hook: read the value to draw it
const n = await read($, count)

// In a Button: write a new value from the old one
onPress: () => update($, count, (value) => value + 1)
```

Da der `ui.render` Hook `count` gelesen hat, führt Claude Code den Hook jedes Mal erneut aus, wenn die Schaltfläche ihn schreibt.

Drei Regeln gelten für den Code:

* **Schreiben Sie `plugin` und `key` als Literal-Strings**: `claude plugin validate` liest sie aus Ihrer Quelle
* **Deklarieren Sie jeden Wert in der Typendatei**: sonst schlägt die Validierung mit `hello-tabs.count is not declared` fehl
* **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

<h4 id="change-hello-tabs-to-use-state">
  Ändern Sie `hello-tabs`, um `$.state` zu verwenden
</h4>

Um `count` in `hello-tabs` in `$.state` zu verschieben, ändern Sie jede Zeile, die ihn verwendet:

* **Am Anfang des Moduls**: fügen Sie die `import` Zeile hinzu, und ersetzen Sie `let count = 0` mit der `atom` Zeile
* **Im `ui.render` Hook**: fügen Sie die `read` Zeile vor `tabButton` hinzu, und zeichnen Sie `'Count: ' + n` im `Text`
* **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
* **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)

Behalten Sie `redraw` für die Tab-Schaltflächen, da `tab` immer noch eine Variable ist.

<h3 id="load-a-saved-value-again-after-clear">
  Laden Sie einen gespeicherten Wert erneut nach `/clear`
</h3>

Wenn 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.

Dieser 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:

```javascript theme={null}
// Copy the saved count from $.store into $.state, or 0 if nothing is saved
async function loadCount($) {
  const saved = Number((await $.store.get('count')) ?? 0)
  await update($, count, () => saved)
}

// Runs before your first prompt, and again after a reload
on('session.start', async ($, e, next) => {
  await loadCount($)
  return next(e)
})

// Runs again after /clear, /resume, and /branch, which reports fork
on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
  await loadCount($)
  return next(e)
})
```

Mit 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.

`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.

Um den Reload ohne eine Sitzung zu überprüfen, [testen Sie die Zeichnung nach `/clear`](/docs/de/plugins/mods/test#test-a-drawing-after-clear).

<h3 id="save-from-more-than-one-session">
  Speichern Sie aus mehr als einer Sitzung
</h3>

Jede 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.

Zwei Auswahlmöglichkeiten machen das weniger wahrscheinlich:

* **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
* **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.

Diese Schaltfläche addiert eins zu dem, was der Store jetzt hält, dann aktualisiert die Zeichnung:

```javascript theme={null}
onPress: async () => {
  // Read what the store holds now, which another session may have changed
  const saved = Number((await $.store.get('count')) ?? 0)
  // Save the new count, then show it
  await $.store.set('count', saved + 1)
  await update($, count, () => saved + 1)
}
```

Wenn 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.

<h2 id="next-steps">
  Nächste Schritte
</h2>

* [Reagieren Sie auf Ereignisse](/docs/de/plugins/mods/events): speisen Sie Ihre Zeichnung aus Tool-Calls und Turns
* [Verwenden Sie die Mods API](/docs/de/plugins/mods/api): speisen Sie Ihre Zeichnung aus Timern und Modell-Aufrufen
* [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
* [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
