Skip to main content
Ein Mod kann seine eigene Oberfläche in Claude Code zeichnen und Teile der Oberfläche ändern, die Claude Code bereits zeichnet. Jede Stelle, an der ein Mod zeichnen kann, wird als Render-Stelle bezeichnet, etwa ein Bereich, das Band über dem Prompt oder der Spinner. Claude Code löst das Ereignis ui.render jedes Mal aus, bevor es eine Render-Stelle zeichnet, und Ihr Hook für dieses Ereignis gibt zurück, was dort gezeichnet werden soll. Diese Übersicht zeigt, wo ein Mod in einer Terminal-Sitzung zeichnen kann: Übersicht einer Claude Code Terminal-Sitzung im Vollbild-Rendering. Ein Mod kann einen Bereich als Seitenleiste rechts hinzufügen, einen Toast oben rechts im Transkript, eine Log-Zeile im Transkript, ein Band über dem Prompt und eine Statuszeile unter dem Prompt. Ein Mod kann Nachrichten, Zeilen von Tool-Aufrufen und den Spinner neu zeichnen. Der Prompt gehört Claude Code selbst. Übersicht einer Claude Code Terminal-Sitzung im Vollbild-Rendering. Ein Mod kann einen Bereich als Seitenleiste rechts hinzufügen, einen Toast oben rechts im Transkript, eine Log-Zeile im Transkript, ein Band über dem Prompt und eine Statuszeile unter dem Prompt. Ein Mod kann Nachrichten, Zeilen von Tool-Aufrufen und den Spinner neu zeichnen. Der Prompt gehört Claude Code selbst. In einem schmaleren Terminal befindet sich der Bereich über dem Prompt statt neben dem Transkript. Erstellen Sie Ihren ersten Mod, bevor Sie hier beginnen. Starten Sie mit dem ausgearbeiteten Beispiel, das einen Bereich mit zwei Tabs und einem Zähler erstellt, und lesen Sie dann den Abschnitt zu jedem Teil, den Sie ändern möchten.
Um eine einzelne Prop oder ein Limit nachzuschlagen, lesen Sie die Referenz.

Einen Bereich mit Tabs erstellen

In diesem Abschnitt erstellen Sie einen Mod, der einen Befehl /hello-tabs hinzufügt, und der Befehl öffnet einen Bereich. Ein Bereich ist in einem breiten Vollbild-Terminal eine Seitenleiste neben dem Transkript, andernfalls ein umrahmter Bereich oberhalb des Prompts. Dieser Bereich zeigt zwei Tabs, und der zweite Tab enthält eine Schaltfläche, die einen Zähler um eins erhöht. Der Zählerstand bleibt erhalten, nachdem Sie Claude Code neu starten. Der fertige Mod sieht so aus. Die Aufzeichnung öffnet den Bereich, wechselt zum zweiten Tab, drückt die Schaltfläche einige Male und kehrt zum ersten Tab zurück:
Die Tabs sind zwei Schaltflächen in einer Zeile. Der Mod merkt sich, welcher Tab aktiv ist, und zeichnet dessen Inhalt unterhalb der Zeile.
1

Das Plugin erstellen

Ein Mod ist ein Plugin mit einem Manifest, einer hooks.json, die auf Ihren Code verweist, und der Code-Datei. Einen Mod erstellen erklärt jede davon. Erstellen Sie ein Verzeichnis namens hello-tabs mit den Verzeichnissen .claude-plugin und hooks darin, und speichern Sie dann die ersten beiden Dateien.Speichern Sie das Manifest als hello-tabs/.claude-plugin/plugin.json:
hello-tabs/.claude-plugin/plugin.json
Benennen Sie Ihren Einstiegspunkt in hello-tabs/hooks/hooks.json:
hello-tabs/hooks/hooks.json
2

Den Code schreiben

Diese Liste beschreibt, was jeder Hook tut, in der Reihenfolge, in der sie im Code erscheinen:
  • Fügt den Befehl /hello-tabs hinzu und lädt den Zählerstand, den eine frühere Sitzung gespeichert hat
  • Öffnet den Bereich, wenn Sie diesen Befehl ausführen
  • Zeichnet den Inhalt des Bereichs: die Zeile mit den Tabs und den Inhalt des geöffneten Tabs
Zwei Variablen auf Modulebene, tab und count, halten den Zustand des Bereichs.Speichern Sie dies als hello-tabs/hooks/register.js:
hello-tabs/hooks/register.js
Jeder Hook tut außerdem etwas, das der Code nicht deutlich macht:
  • session.start liest zusätzlich den gespeicherten Zählerstand aus $.store, einem Key-Value-Speicher, der zwischen Sitzungen erhalten bleibt.
  • command.run teilt Claude Code nur mit, dass der Bereich existiert. Das Öffnen eines Bereichs zeichnet für sich genommen nichts: Claude Code löst anschließend ui.render aus, um zu erfragen, was hineingehört.
  • ui.render gibt den Elementbaum zurück, eine Box, die weitere Boxen, Text und Schaltflächen enthält, und baut ihn bei jeder Ausführung erneut aus tab und count auf.
Das Drücken einer Schaltfläche führt deren onPress-Callback aus, der eine Variable ändert und redraw aufruft. Claude Code führt daraufhin den Hook ui.render erneut aus, und der Hook baut aus den neuen Werten einen neuen Baum auf. Jede interaktive Darstellung nutzt diesen Render-Zyklus: Ein Callback ändert den Zustand, und der Hook rendert erneut aus dem neuen Zustand.
3

Den Bereich öffnen

Starten Sie Claude Code in Ihrer Shell mit claude --plugin-dir ./hello-tabs. Führen Sie im Prompt von Claude Code /hello-tabs aus. Ein Bereich öffnet sich mit 1: One und 2: Two am oberen Rand. Drücken Sie 2 und dann einige Male a, das Tastenkürzel für Add one. Der Zählerstand steigt.
4

Prüfen, ob der Zählerstand gespeichert wurde

Drücken Sie Esc, um den Bereich zu schließen, und beenden Sie dann die Sitzung. Starten Sie Claude Code in Ihrer Shell erneut mit demselben Befehl claude --plugin-dir ./hello-tabs, und führen Sie im Prompt von Claude Code /hello-tabs aus. Der Zählerstand ist dort, wo Sie ihn verlassen haben.Um den Zählerstand zurückzusetzen, lassen Sie den Mod $.store.delete('count') aufrufen. Zustand beibehalten beschreibt, wie lange jede Art von Wert erhalten bleibt.

Wählen Sie, wo gezeichnet wird

Ein ui.render-Hook wird für jede Render-Stelle ausgeführt, sofern Sie ihn nicht auf die Stelle eingrenzen, an der Sie zeichnen möchten. Um die Render-Stelle auszuwählen, übergeben Sie einen Filter, einen sogenannten Matcher, als zweites Argument an on. { component: 'Pane' } führt den Hook nur für Panes aus. Im Hook benennt e.component die Stelle, e.surface gibt an, welche App zeichnet, und e.props enthält die eigenen Daten der Stelle. Bei einem Pane ist e.requestId die id, mit der Sie es geöffnet haben. Das Pane und das Band sind leer, bis ein Mod sie füllt. Wählen Sie einen Tab aus, um zu sehen, was jedes davon ist und wie Sie darin zeichnen:
Ein Pane ist eine Seitenleiste neben dem Transkript in einem breiten Vollbild-Terminal, andernfalls ein umrahmter Bereich über dem Prompt. Wenn mehrere Panes geöffnet sind, erhält jedes einen Tab, der seinen Titel anzeigt.Ein Pane erscheint, wenn Ihr Mod $.ui.open mit einer von Ihnen gewählten id aufruft, etwa $.ui.open({ id: 'hello-tabs' }). Ein Pane zum richtigen Zeitpunkt öffnen behandelt die übrigen Felder und wann ein Pane auf ein breiteres Terminal wartet.Um in Ihrem Pane zu zeichnen, filtern Sie auf { component: 'Pane' } und prüfen Sie, ob e.requestId Ihre id ist.

Ändern, was Claude Code bereits zeichnet

Claude Code zeichnet den Großteil seiner Oberfläche selbst: Nachrichten, Zeilen für Tool-Aufrufe, den Spinner und mehr. Jeder dieser Teile ist ebenfalls eine Render-Stelle, sodass ein Mod ihn umgestalten oder ersetzen kann. Um einen davon zu ändern, filtern Sie Ihren ui.render-Hook auf seinen Namen aus dieser Tabelle: Pane, AskUserQuestion, Spinner, UserMessage, AssistantMessage und die Stellen für Tool-Aufrufe funktionieren im Terminal, in der Desktop-App und in der VS Code-Erweiterung. Die Tabelle der Render-Stellen listet auf, wo jede der übrigen ausgelöst wird. An einer Stelle, die Claude Code bereits zeichnet, kann Ihr Hook ein Detail ändern, die Darstellung ersetzen oder sie unverändert lassen. Wählen Sie einen Tab aus, um jede Variante auf den Spinner angewendet zu sehen. Die Beispiele lesen eine Variable calls, die ein anderer Hook hochzählt, wie im Tutorial-Mod.
Um die Darstellung von Claude Code beizubehalten und nur einen Teil davon zu ändern, übergeben Sie an next eine Kopie des Events mit geänderten props. Dieser Hook ändert den Text nach dem Wort des Spinners:
Der Spinner behält seine Animation und sein Wort, und Ihr Text folgt auf das Wort:
An diesen Stellen gibt next(e) eine Referenz auf die Darstellung von Claude Code zurück, { type: 'engine', ref }, es sei denn, ein Mod, der nach Ihrem ausgeführt wird, hat einen eigenen Baum zurückgegeben. Um zu ändern, was in dieser Darstellung steht, übergeben Sie an next eine Kopie des Events mit anderen Props, wie es der Tab Ein Detail ändern tut. Sie können die Referenz unverändert zurückgeben oder sie in einer Box neben eigene Elemente setzen:
Während Claude arbeitet, wird der Spinner wie zuvor animiert, und under the spinner erscheint darunter. Die Berechtigungsabfrage ist keine Render-Stelle, daher kann ein Mod nicht ändern, was sie anzeigt. Der Fragedialog AskUserQuestion ist eine, sodass ein Mod ihn ändern kann. Ein Baum für den Dialog muss die Referenz genau einmal enthalten, mit Ihren Elementen darüber. Andernfalls zeichnet Claude Code seinen eigenen Dialog.

Ein Pane zum richtigen Zeitpunkt öffnen

Ein Pane erscheint nur, wenn Ihr Mod es öffnet. Wie und wann Sie es öffnen, entscheidet darüber, ob es den Tastaturfokus übernimmt, wie viel Platz es beansprucht und ob es in einem schmalen Terminal überhaupt angezeigt wird. Um ein Pane zu öffnen, rufen Sie $.ui.open mit einer von Ihnen gewählten id auf. Die id ist der Name des Panes: Ihr ui.render-Hook prüft darauf, und Sie übergeben sie erneut, um das Pane zu schließen.
Um das Pane zu schließen, rufen Sie $.ui.close mit der id auf, mit der Sie es geöffnet haben:
Neben id akzeptiert $.ui.open diese optionalen Felder: Die VS Code-Erweiterung ignoriert rows und columns und legt die Größe des Panes selbst fest. focus, closeOnEscape und holdToasts sind optional und akzeptieren nur true. Um eines davon wegzulassen, lassen Sie es aus. Die Übergabe von false löst einen Fehler wie ui.open: focus is true or left out aus. Um eines davon bedingt zu setzen, fügen Sie das Feld nur hinzu, wenn die Bedingung erfüllt ist. Dieser Aufruf fordert den Tastaturfokus nur an, wenn items nicht leer ist:
Damit ein Befehl das Pane öffnen kann, während Claude arbeitet, fügen Sie immediate: true hinzu, wenn Sie den Befehl registrieren. Ohne diese Option wartet ein während eines Turns eingegebener Befehl, bis der Turn endet.

Toasts hinter einem Dialog zurückhalten

Übergeben Sie holdToasts: true an $.ui.open, wenn das Pane ein Dialog ist, den der Benutzer beantwortet und wieder verlässt, damit keine Toasts erscheinen, während er eine Entscheidung trifft. Im Terminal gilt das Zurückhalten, solange dieses Pane angezeigt wird, und ein in dieser Zeit ausgelöster Toast wartet, bis das Zurückhalten endet. Claude Code hält die Toasts anderer Mods und seine eigenen kurzlebigen Benachrichtigungen ebenso zurück wie diejenigen, die Ihr Mod mit $.ui.toast auslöst. Lassen Sie das Feld bei einem Pane weg, das geöffnet bleibt, damit der Benutzer sie weiterhin sieht.

Wenn ein Pane auf ein breiteres Terminal wartet

Ein Pane, das Ihr Mod unaufgefordert öffnet, erscheint nicht in einem schmalen Terminal, damit es keinen kleinen Bildschirm vereinnahmen kann. Ob es erscheint, hängt davon ab, wodurch es geöffnet wurde:
  • Durch eine Aktion des Benutzers geöffnet, etwa einen ausgeführten Befehl oder eine gedrückte Schaltfläche: Das Pane erscheint bei jeder Breite
  • Durch Ihren Mod von sich aus geöffnet, etwa über einen Timer oder einen turn.start-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 den Grund angibt. Ein wartendes Pane erscheint, wenn der Benutzer es öffnet oder das Terminal verbreitert. Um mitzuteilen, dass etwas verfügbar ist, ohne ein Pane zu öffnen, rufen Sie $.ui.toast('Your message') auf, das eine Toast-Benachrichtigung anzeigt.

In der VS Code-Erweiterung zeichnen

In der VS Code-Erweiterung erscheint, was ein Mod zeichnet, im Chat-Panel ab Erweiterungsversion 2.1.299, und zwar in einer Sitzung, die Feature-Flags abruft. In einem ui.render-Hook für das Panel ist e.surface gleich vscode. Welche Stellen und Elemente das Panel zeichnet und welche Props es ignoriert, entnehmen Sie den Tabellen der Render-Stellen und der Elemente. Damit ein Mod sowohl im Panel als auch im Terminal funktioniert, berücksichtigen Sie diese Unterschiede:
  • Panes: In einem Panel mit mindestens 800 Pixeln Breite werden Panes neben der Konversation angedockt, e.props.placement ist 'dock' und e.viewport.isFullscreen ist true. In einem schmaleren Panel werden Panes über dem Prompt-Eingabefeld gezeichnet, placement ist 'inline', und ein Pane zeigt 12 Zeilen an, bevor es scrollt.
  • Toasts und Statuszeilen: Diese befinden sich in einem gemeinsamen Stapel direkt über dem Prompt-Eingabefeld, bei schmalem Panel zusammen mit den Panes, mit einer Statuszeile pro Mod. Der Stapel wird ausgeblendet, solange eine Berechtigungsanfrage oder ein Dialog angezeigt wird.
  • Beschriftungen: Das Panel umrahmt, was ein Mod zeichnet oder umschreibt, und beschriftet es mit dem Namen des Plugins, etwa From the token-chart plugin oder Edited by the token-chart plugin.
  • Tastatur: Schaltflächen haben keine Tastenkürzel, daher klickt der Benutzer auf eine Schaltfläche oder drückt Enter, sobald sie den Fokus hat.
  • Tastaturfokus: Das Panel gewährt die Anfrage focus: true eines Panes nur, wenn das Panel den Fokus hat, Claude untätig ist und keine Frage wartet und das Prompt-Eingabefeld leer ist.
  • Der Fragedialog: Bei AskUserQuestion behält das Panel seine eigene Fragekarte und zeichnet Ihre Elemente darüber. Props, die Sie an next übergeben, ändern die Karte nicht.
  • Prompt-Vorschläge: $.prompt.suggest wird mit isShown: false aufgelöst und zeigt nichts an, da das Panel den abgeblendeten Vorschlagstext des Prompt-Eingabefelds für Claudes eigene Vorschläge reserviert.
  • Textlimits: Das Panel kürzt eine Statuszeile nach 200 Zeichen oder zwei Zeilen und einen Toast nach 500 Zeichen.

Einen Baum aus Elementen erstellen

Was ein ui.render-Hook zurückgibt, ist ein Elementbaum: eine Beschreibung dessen, was gezeichnet werden soll, bestehend aus Boxen, Text und Steuerelementen, die ineinander verschachtelt sind. Sie beschreiben die Zeichnung, und Claude Code rendert sie in der App, die der Benutzer geöffnet hat: im Terminal, in der Desktop-App oder in der VS Code-Erweiterung. Um die Elemente zu erhalten, rufen Sie in Ihrem Hook $.ui.resolve(e) auf, etwa mit const { Box, Text, Button } = $.ui.resolve(e). Jedes Element ist eine Funktion. Sie übergeben ihr Props und legen die Elemente und Strings, die darin enthalten sein sollen, in children ab. Wählen Sie einen Tab aus, um die gängigsten Elemente und ihre Darstellung im Terminal zu sehen:
Text zeichnet einen String, optional mit Formatierung wie bold und color:
Die Oberflächengalerie enthält Beispiele und Screenshots der meisten Elemente. Diese Tabelle listet alle Elemente auf: 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). Wenn ein Baum ein Element verwendet, das die App nicht kennt, eine Prop, die ein Element nicht akzeptiert, oder ein Kind an einer Stelle, an der keines vorgesehen ist, zeichnet Claude Code seine eigene Version der Stelle. In einer mit --plugin-dir gestarteten Sitzung weist eine Zeile im Transkript darauf hin, etwa ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own. Das Debug-Log vermerkt dies als ui.render (Pane): a hook returned a tree that does not validate mit derselben Begründung. Sonst erscheint nichts in der Sitzung. Wenn eine Zeichnung also nicht angezeigt wird, prüfen Sie diese Zeile oder das Log. In der Desktop-App und in der VS Code-Erweiterung wird ein Link als einfacher Text gezeichnet, sofern sein href nicht diese Anforderungen erfüllt:
  • Schema und Host: eine https:-URL oder eine http://localhost-URL wie http://localhost:3000
  • Kein @: Schreiben Sie ein @ im Pfad oder in der Query als %40
  • Schreibweise: das, was new URL(href).href zurückgibt, abgesehen von einem fehlenden / nach dem Host. Das schließt einen Host in Großbuchstaben, ein Leerzeichen und :443 bei einer https:-URL aus.
Im Terminal gelten diese Anforderungen nicht.

Wenn ein Client fehlschlägt

Wenn im Terminal die Datei fehlschlägt, die ein Client ausführt, nimmt eine abgeblendete Zeile wie my-mod: Client client/spinner.js: boom den Platz des Client ein, und der Rest Ihrer Zeichnung wird weiterhin angezeigt. Wenn Ihr Mod ui.fault verarbeitet, zeichnet Claude Code die Stelle anschließend neu.

Ein Raster aus farbigen Zellen zeichnen

Für eine Heatmap, eine Sparkline oder ein Spielbrett im Terminal zeichnen Sie ein einziges Raster statt einer Box für jede Zelle. Ein Raster erhält einen key, seine Größe in columns und rows sowie cells, einen Base64-String, der alle Zellen enthält. Jede Zelle besteht aus drei Zahlen: dem Codepoint des Zeichens, seiner Farbe und seiner Hintergrundfarbe. Eine Farbe ist ein 24-Bit-RGB-Wert in hexadezimaler Schreibweise, etwa 0xc62828 für ein Rot. Der Wert 0x01000000, eins über diesem Bereich, steht für die Standardfarbe des Terminals. Andere Apps als das Terminal haben kein Raster. Prüfen Sie daher e.surface und zeichnen Sie dort Text. Dieser Pane-Inhalt zeichnet eine Heatmap mit drei mal zwei Zellen:
Im Terminal zeigt der Pane das Raster an: Ein Pane im Terminal mit einem kleinen Raster aus farbigen Blöcken, zwei Zeilen zu je drei Blöcken. Die obere Zeile ist grün, gelb und rot. Die untere Zeile ist grün, grün und gelb. Das rows-Array ist der Teil, den Sie ändern würden, und cellsOf wandelt es in den gepackten String um. Der Hook zeichnet nur in einem Pane, dessen id heat ist. Öffnen Sie daher einen solchen Pane mit $.ui.open({ id: 'heat' }) aus einem Befehl heraus, so wie das hello-tabs-Beispiel seinen Pane öffnet. Jedes Zeichen muss genau eine Zelle breit sein. Um ein Raster zu animieren, das bereits auf dem Bildschirm ist, rufen Sie $.ui.blit mit der id des Panes als requestId, dem key des Raster, derselben 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) }). Dadurch wird nur dieses eine Element neu gezeichnet, ohne Ihren ui.render-Hook erneut auszuführen.

Auf Tastendrücke und Eingaben reagieren

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 den Callback dieses Steuerelements auf, der in Ihrem Modul ausgeführt wird. Jedes Steuerelement nimmt eigene Callbacks entgegen:
  • Button: nimmt onPress(e) entgegen, wobei e.surface die App ist, aus der der Tastendruck kam
  • Input: nimmt onSubmit(value) und onInput(value) entgegen
  • Select: nimmt onSelect(value) entgegen, mit seinen Auswahlmöglichkeiten in options, einer Liste mit mindestens einer Auswahl mit eindeutigen Werten, etwa [{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]
Ein Test drückt ein Steuerelement oder tippt in es hinein, indem er es über seinen key anspricht, geben Sie also jedem Steuerelement einen. Jede Verwendung eines Steuerelements löst außerdem ui.press, ui.input oder ui.select mit dem key in e.element aus, und ein anderer Mod kann diese Ereignisse verarbeiten. Sein Hook läuft vor Ihrem Callback, sodass er sieht, was der Benutzer in Ihr Input eingibt, und es ändern oder anstelle Ihres Callbacks antworten kann. Die Mods-API hat keine Methode, mit der sich die Schaltfläche eines anderen Mods drücken lässt.

Tastaturfokus und Hotkeys

Ihr Mod liest die Tastatur nie selbst. Der Benutzer drückt eine Taste, Claude Code entscheidet, für welches Ihrer Steuerelemente sie bestimmt ist, und der Callback dieses Steuerelements wird ausgeführt. Abgesehen von einem Ziffern-Hotkey auf dem Band geschieht das nur, solange Ihr Bereich oder Band den Tastaturfokus hat. Die übrige Zeit gehen Tastendrücke an den Prompt. In der VS Code-Erweiterung haben Schaltflächen keine Hotkeys: Der Benutzer klickt auf eine Schaltfläche oder drückt darauf Enter und tippt in Felder.

Wie ein Bereich den Tastaturfokus erhält

Ein Bereich erhält den Tastaturfokus, wenn:
  • Ihr Mod ihn mit focus: true aus einem Befehl oder einem Tastendruck heraus öffnet
  • Der Benutzer Ctrl+X und dann Tab drückt
  • Der Benutzer darauf klickt
Claude Code gewährt focus: true nur, solange der Prompt leer ist und nichts anderes den Tastaturfokus hat. Ein Bereich, der sich öffnet, während der Benutzer tippt, übernimmt dessen Tastenanschläge nicht.

Was jede Taste bewirkt

Diese Tabelle listet auf, was eine Taste bewirkt, während Ihr Bereich oder Band den Tastaturfokus hat: Ein Mod kann Tab oder die Pfeiltasten nicht anderweitig belegen, daher steuert ein Spiel mit w, a, s und d.

Einen Hotkey und den ersten Fokus festlegen

  • hotkey: Damit der Benutzer einen Button mit einer Taste drücken kann, geben Sie ihm einen hotkey aus einer Ziffer oder einem Kleinbuchstaben, wie in hotkey: 'a'
  • autoFocus: Um festzulegen, welches Steuerelement beim Öffnen des Bereichs den Fokus hat, fügen Sie ihm autoFocus: true hinzu. In der VS Code-Erweiterung bewirkt autoFocus nichts: Das Steuerelement wird so gezeichnet, als hätten Sie die Prop weggelassen, und Sie müssen nichts ändern. Die Prop akzeptiert nur true, lassen Sie sie also bei den anderen Steuerelementen weg.
Wie ein Hotkey angezeigt wird, hängt von der Schaltfläche und der App ab: Nennen Sie im Terminal die Taste in der Beschriftung einer Schaltfläche mit Klammern oder verwenden Sie plain: true, damit der Benutzer sehen kann, was er drücken muss. Die Elementreferenz enthält die übrigen Button-Regeln: action, Ziffern-Hotkeys auf dem Band und zwei Schaltflächen auf einem Hotkey.

Texteingaben entgegennehmen und für jedes Element eine Zeile zeichnen

Viele Bereiche bestehen aus einem Textfeld mit einer Liste darunter. Das Beispiel in diesem Abschnitt ist ein Notizbereich: 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 den Bereich so:
Das ✕ am oberen Rand ist die eigene Markierung von Claude Code zum Schließen des Bereichs. Das Beispiel verwendet diese Techniken:
  • Texteingaben entgegennehmen: Ein Input ruft onSubmit(value) mit dem Text des Felds auf, wenn der Benutzer Enter drückt, und onInput(value) bei jeder Änderung
  • Eine Liste zeichnen: Ordnen Sie Ihren Daten jeweils eine Zeile zu und geben Sie der Schaltfläche jeder Zeile einen eigenen key
Dieser Hook zeichnet den Inhalt des Bereichs:
So probieren Sie den Bereich aus:
  • Eine Notiz hinzufügen: Tippen Sie eine Zeile und drücken Sie Enter. Die Zeile erscheint als neue Zeile in der Liste, und das Feld wird geleert.
  • Eine Notiz löschen: Drücken Sie Tab, bis die x-Schaltfläche der Notiz den Fokus hat, und drücken Sie dann Enter. Das x ist die Beschriftung der Schaltfläche und kein Hotkey, daher wird sie durch Tippen des Buchstabens nicht gedrückt.
Jede Änderung folgt demselben Render-Zyklus wie hello-tabs: Der Callback ändert notes, ruft redraw auf und speichert die Liste in $.store. Das Feld wird nach jedem Absenden wegen seiner value-Prop geleert. value ist der Text, den das Feld beim Zeichnen enthält, und die Eingaben des Benutzers ersetzen ihn, bis Ihr Hook das Feld erneut zeichnet. Das Beispiel zeichnet das Feld immer mit ''. Das Beispiel speichert die Notizen, lädt sie aber nicht. Um sie in der nächsten Sitzung wiederherzustellen, lesen Sie sie in einem session.start-Hook ein, so wie hello-tabs count einliest. Diese Props bilden die Zeile des Felds, Note: Type a note and press Enter ⏎ add: Das Absenden eines Input startet keinen Turn, es sei denn, Ihr Callback ruft $.prompt.submit auf.

Einen Bereich neu zeichnen

Eine Zeichnung ist eine Momentaufnahme: Sie zeigt, was Ihr ui.render-Hook beim letzten Ausführen zurückgegeben hat. Damit etwas Neues angezeigt wird, muss der Hook erneut ausgeführt werden. Claude Code führt ihn bei einigen Änderungen erneut aus, und für die übrigen fordert Ihr Mod dies an.

Wann Claude Code ohne Aufforderung neu zeichnet

Claude Code führt Ihren ui.render-Hook erneut aus, wenn sich die Props des Bereichs oder die Breite des Terminals ändern. Wenn ein Client im Bereich fehlschlägt und Ihr Mod ui.fault behandelt, führt Claude Code den Hook nach der Rückkehr Ihrer ui.fault-Hooks noch einmal aus, damit Ihr ui.render-Hook den Client weglassen kann. Der Hook wird nicht zeitgesteuert ausgeführt, und Claude Code kann nicht erkennen, wann sich eine Variable in Ihrem Modul ändert.

Neu zeichnen, wenn sich Ihre Daten ändern

Damit Ihre Bereiche nach einer Änderung Ihrer eigenen Daten neu gezeichnet werden, rufen Sie $.ui.invalidate('ui.render') auf. Dieses Panel zählt Tastendrücke. Der Callback der Schaltfläche ändert count und fordert dann ein Neuzeichnen an:
Jeder Druck erhöht die Zahl im Panel. Das hello-tabs-Beispiel kapselt denselben Aufruf in seiner Funktion redraw. Ein Wert, den Sie in $.state speichern, benötigt diesen Aufruf nicht, da das Schreiben des Werts die Bereiche neu zeichnet, die ihn lesen.

Zeitgesteuert neu zeichnen

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 Zeile mit $.clock.every dort hinzu:
Claude Code führt Ihren ui.render-Hook nun einmal pro Sekunde aus. Der Timer stoppt, wenn das Modul neu geladen wird, und die neue Instanz des Moduls startet ihren eigenen.

Wie oft ein Bereich neu gezeichnet werden kann

Claude Code drosselt das Neuzeichnen eines Bereichs, sodass Ihr Mod $.ui.invalidate so oft aufrufen kann, wie sich seine Daten ändern. Wie oft jeder Bereich neu gezeichnet werden kann, entnehmen Sie der Tabelle der Grenzwerte. Aufrufe, die schneller als der Grenzwert eintreffen, werden zu einem einzigen Neuzeichnen zusammengefasst. Dieses Neuzeichnen führt Ihren Hook einmal aus, und der Hook liest Ihre Daten in ihrem Zustand zu diesem Zeitpunkt, sodass der neueste Wert angezeigt wird und die Zwischenwerte nicht. Eine Animation kann nicht schneller als der Grenzwert laufen.

Zustand speichern

Wo ein Mod einen Wert speichert, bestimmt, wie lange der Wert erhalten bleibt: 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 erhalten bleiben muss: $.store.get(key) wird zum Wert oder zu undefined aufgelöst, und $.store.set(key, value) akzeptiert jeden JSON-Wert.

Einen Wert in $.state speichern

$.state hält Werte für die Dauer einer Sitzung und zeichnet für Sie neu. Es handelt sich um reaktiven Zustand: Ein ui.render-Hook, der einen Wert liest, abonniert ihn, sodass Claude Code diese Stelle jedes Mal neu zeichnet, wenn Sie den Wert schreiben, und Sie $.ui.invalidate nicht aufrufen müssen. Ein Wert in $.state übersteht außerdem ein Neuladen des Moduls, was eine Variable nicht tut. Zur Einrichtung deklarieren Sie Ihre Werte, verweisen in Ihrem Manifest auf die Deklaration und definieren und verwenden dann jeden Wert. Die Beispiele verschieben count aus hello-tabs in $.state.

Die Werte deklarieren

Deklarieren Sie die Werte in einer Typdeklarationsdatei. Der äußere Schlüssel ist der Name Ihres Plugins, und jeder Eintrag darunter ist ein Wert mit seinem Typ. Speichern Sie dies als hello-tabs/types/index.d.ts:
hello-tabs/types/index.d.ts

Im Manifest auf die Deklaration verweisen

Damit claude plugin validate Ihren Code anhand dieser Datei prüfen kann, fügen Sie dem Manifest ein Feld types mit ihrem Pfad hinzu:
hello-tabs/.claude-plugin/plugin.json

Einen Wert definieren, lesen und schreiben

Definieren Sie in Ihrem Modul jeden Wert mit einem Standardwert, lesen Sie ihn beim Zeichnen und schreiben Sie ihn aus einem Callback. atom benennt einen Wert und seinen Standardwert, read gibt ihn zurück und update schreibt ihn. Die drei Hilfsfunktionen rufen $.state.get und $.state.set für Sie auf:
Da der ui.render-Hook count gelesen hat, führt Claude Code den Hook jedes Mal erneut aus, wenn die Schaltfläche den Wert schreibt. Für den Code gelten diese Regeln:
  • Schreiben Sie plugin und key als String-Literale: claude plugin validate liest sie aus Ihrem Quellcode
  • Speichern Sie das Ergebnis jedes atom-Aufrufs in einer const: Wenn Sie count mit let deklarieren, schlägt die Validierung mit takes a source the scan can read fehl
  • Deklarieren Sie jeden Wert in der Typdeklarationsdatei: Andernfalls schlägt die Validierung mit hello-tabs.count is not declared fehl
  • Schreiben Sie aus einem Callback oder dem Hook eines anderen Events: Ein ui.render-Hook kann Zustand lesen, aber nicht schreiben, schreiben Sie also aus onPress, onSubmit oder einem Hook für ein anderes Event

hello-tabs auf $.state umstellen

Um count in hello-tabs nach $.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 durch die atom-Zeile
  • Im ui.render-Hook: Fügen Sie die read-Zeile vor tabButton hinzu und zeichnen Sie 'Count: ' + n im Text
  • In der Schaltfläche Add one: Ersetzen Sie onPress durch die Variante aus Aus mehr als einer Sitzung speichern, die den Zähler sowohl speichert als auch schreibt
  • Im session.start-Hook: Ersetzen Sie die beiden Zeilen, die saved lesen, durch den loadCount-Aufruf aus Einen gespeicherten Wert nach /clear erneut laden
Behalten Sie redraw für die Tab-Schaltflächen bei, da tab weiterhin eine Variable ist.

Einen gespeicherten Wert nach /clear erneut laden

Wenn Ihr Mod bei session.start einen gespeicherten Wert aus $.store in $.state kopiert, muss er ihn nach /clear, /resume oder /branch erneut kopieren. Diese Befehle setzen jeden $.state-Wert auf seinen Standardwert zurück, und session.start wird nicht erneut ausgelöst. classic.SessionStart wird jedoch nach jedem dieser Befehle ausgelöst, wobei e.source auf clear, resume oder fork gesetzt ist. Kopieren Sie den Wert daher in einem Hook auf dieses Event erneut. Andernfalls zeigt Ihre Darstellung den Standardwert, und ein Callback, der den $.state-Wert speichert, überschreibt Ihren gespeicherten Wert mit dem Standardwert. Dieser Code lädt count aus beiden Hooks. Er baut auf der $.state-Version von hello-tabs auf, in der count ein Atom ist und update importiert wird. Platzieren Sie loadCount oberhalb von register und fügen Sie den loadCount-Aufruf dem bereits vorhandenen session.start-Hook hinzu. classic.SessionStart wird außerdem beim Start und nach der Komprimierung ausgelöst, was $.state nicht zurücksetzt, daher beschränkt der Filter auf source den Hook auf die drei Zurücksetzungen:
Mit beiden Hooks zeigt der Bereich nach /clear den gespeicherten Zähler statt 0 an, und der nächste Klick auf Add one erhöht den gespeicherten Zähler. loadCount überschreibt den Wert in $.state mit dem gespeicherten Wert, und session.start wird bei jedem Neuladen des Moduls erneut ausgelöst. Damit der Store nicht veraltet, speichern Sie bei jeder Änderung, wie es die Schaltfläche Add one tut. Um das Neuladen ohne Sitzung zu prüfen, testen Sie die Darstellung nach /clear.

Aus mehr als einer Sitzung speichern

Alle Sitzungen auf Ihrem Rechner, die Ihren Mod ausführen, teilen sich einen $.store. Ein get gefolgt von einem set ist nicht atomar. Wenn zwei Sitzungen jeweils einen Wert lesen, ihn ändern und zurückschreiben, entsteht eine Race Condition, und der zweite Schreibvorgang ersetzt den ersten. Um dies unwahrscheinlicher zu machen:
  • Geben Sie jedem Element einen eigenen Schlüssel: Ein set ändert nur seinen eigenen Schlüssel, sodass Sitzungen, die unterschiedliche Schlüssel schreiben, sich nicht gegenseitig überschreiben
  • Lesen Sie direkt vor dem Schreiben erneut: Rufen Sie für einen Wert, den mehrere Sitzungen ändern, den Schlüssel im Callback mit get ab und bilden Sie den neuen Wert daraus, nicht aus einer Kopie, die Sie bei session.start geladen haben. Der Schreibvorgang einer anderen Sitzung geht trotzdem verloren, wenn er zwischen Ihrem get und Ihrem set stattfindet.
Diese Schaltfläche erhöht den aktuellen Wert im Store um eins und aktualisiert dann die Darstellung:
Wenn eine zweite Sitzung seit dem Start dieser Sitzung dreimal auf ihre eigene Schaltfläche geklickt hat, zeigt und speichert dieser Klick einen Zählerstand, der diese drei einschließt.

Nächste Schritte