# WebUI — Leitfaden Für die zweite Oberfläche von ClawdDotNet: eine Weboberfläche im Browser, neben der Avalonia-Oberfläche und gleichzeitig mit ihr. Geschrieben für **Gemini 3.8 Flash**, das die Oberfläche baut. Stand: 2026-09-06. Gegenstück zum [Oberflächen-Leitfaden](Oberflaechen-Leitfaden.md), der für Avalonia gilt. Vorhaben im Deploymentcenter: **#494** (Weboberfläche), **#493** (Fachlogik nach `App`), **#531** (Instanzen in der Oberfläche). --- ## Vorweg: Vier Dinge in diesem Leitfaden sind überholt Seit dem 2026-09-09 trägt der **Instanzmanager** die Weboberfläche (#528). Der Leitfaden gilt weiter für Stil, Sicherheit und Aufbau — kein `innerHTML`, kein Paketmanager, Token nur im `sessionStorage`, Farben aus `app.css`. Vier Angaben darin stimmen nicht mehr: | Im Text | Tatsächlich | |---|---| | `wwwroot` liegt in `src/ClawdDotNet.Web/` | **`src/ClawdDotNet.Manager.Web/wwwroot/`** | | Die Oberfläche läuft im Prozess der Instanz | Sie läuft im **Instanzmanager**, einem eigenen Prozess. Er spricht mit den Instanzen über einen Socket und **leitet weiter** | | `http://127.0.0.1:7001`, Einstellungen in `InstanceSettings.json` | **`webUiPort`, `webUiBinding`, `webUiToken` in `manager.json`**, Vorgabe 7000 | | Ein `/api` für eine Instanz | **N Instanzen.** `/api/verwaltung/…` gehört dem Manager, alles andere unter `/api` geht an die Instanz aus der Kopfzeile `X-Instanz` | **Der aktuelle Auftrag und die vollständige API stehen in** [Instanzmanager-Konzept.md, Abschnitt 11](Instanzmanager-Konzept.md#11--übergabe-an-den-frontend-agenten). Lies den zuerst; dieser Leitfaden ist das Nachschlagewerk daneben. --- ## 0 — Der Auftrag, in einem Satz **Baue `src/ClawdDotNet.Web/wwwroot/` gegen eine fertige, laufende API.** Der ganze C#-Teil steht: Kestrel läuft im Prozess der Anwendung, die Anmeldung greift, die Endpunkte antworten, der Ereignisstrom sendet. Was fehlt, ist die Oberfläche selbst — HTML, CSS und Browser-JavaScript. **Was ausdrücklich nicht zum Auftrag gehört:** | Nicht anfassen | Warum | |---|---| | Jede `.cs`-Datei, überall | Der Kern steuert Agenten mit Datei-, Mail-, FTP- und Datenbankzugriff. Fehlt ein Endpunkt oder stimmt eine Antwort nicht: **melden**, nicht selbst bauen | | `src/ClawdDotNet.Web/*.cs`, `Endpoints/`, `Contracts/`, `Security/` | Dort steht die Absicherung. Eine Änderung daran ist eine Sicherheitsänderung | | `deploy/`, `NuGet.Config`, `*.csproj` | Release-Strecke und Bauplan | | `docs/Roadmap.md` zwischen den ``-Markern | Erzeugte Kopie, wird überschrieben | Ein Griff nach C# ist keine Grenzüberschreitung, sondern ein **Befund**: Es bedeutet, dass die API etwas nicht hergibt, was sie hergeben müsste. Melden (Abschnitt 8), dann weiterbauen. --- ## 1 — Was schon steht ### Der Aufbau ``` src/ClawdDotNet.Core Fachkern. Kennt keine Oberfläche. src/ClawdDotNet.App Fachlogik + Aufbau (AppHost). Kein Avalonia, kein ASP.NET. src/ClawdDotNet.Web Kestrel, Endpunkte, Anmeldung ← fertig └── wwwroot/ die Oberfläche ← DEIN TEIL src/ClawdDotNet.Desktop Avalonia. Startet die Weboberfläche mit. ``` **Ein Prozess, zwei Oberflächen.** Die Weboberfläche läuft im selben Prozess wie das Fenster und arbeitet auf demselben Zustand. Das ist keine Bequemlichkeit: Die `AgentEngine` hält Chatverlauf und Kontext je Agent im Arbeitsspeicher und reiht Läufe desselben Agenten hintereinander ein; `SqliteStorage` serialisiert Schreibvorgänge prozessweit. Zwei Prozesse hätten zwei Engines und keine gemeinsame Sperre — das Ergebnis wäre HTTP 400 mitten in einem Agentenlauf. Praktische Folge für dich: **Was du im Browser änderst, erscheint im Fenster, und umgekehrt.** Beides ist verdrahtet und funktioniert. ### Einschalten Die Weboberfläche ist **ausgeschaltet vorbelegt**. In der Instanzdatei `InstanceSettings.json`: ```json { "webUiEnabled": true, "webServerPort": 7001, "webUiBinding": "loopback", "webUiToken": "hier-ein-langes-zufaelliges-token" } ``` Dann `http://127.0.0.1:7001` im Browser. Das Token trägst du auf der Anmeldeseite ein. Startet sie nicht, steht der Grund im Protokoll (`Logs/`, Modul `Web`). Die häufigsten: Token fehlt, Token kürzer als 20 Zeichen, Port belegt. Ein Fehlschlag hält die Anwendung nie auf — sie läuft dann eben ohne Browser. ### Der Socket daneben — nicht deine Baustelle, aber gut zu wissen Seit #528 (Stufe 2) hört dieselbe Instanz zusätzlich auf einem Socket im Instanzverzeichnis (`manager.sock`, unter Windows eine Named Pipe). Über ihn wird der Instanzmanager die Instanz erreichen. Drei Dinge, die dich betreffen könnten: - **Er ist immer da**, auch wenn `webUiEnabled` aus ist. Was du in `Logs/Web.log` als „Instanz hört auf …" siehst, ist er und nicht die Weboberfläche. - **Er kennt kein Token.** Seine Grenze steht am Dateisystem. Wer über den Socket hereinkommt, ist an `TokenGate` vorbei — das ist Absicht und in `LocalTransport` festgehalten. - **Für dich ändert sich nichts.** Endpunkte, Übertragungstypen und der Ereignisstrom sind dieselben; es ist nur eine zweite Tür in denselben Raum. --- ## 2 — Die API Alles unter `/api` und `/events` braucht das Token. Alles andere — HTML, CSS, JavaScript — wird ohne ausgeliefert; in diesen Dateien steht nichts, was jemand nicht wissen dürfte. ```js fetch("/api/agents", { headers: { Authorization: `Bearer ${token}` } }) ``` Antworten sind JSON in `camelCase`. Ein fehlgeschlagener verwaltender Aufruf kommt als **400** mit `{ "ok": false, "message": "…" }` — die Begründung ist auf Deutsch und für den Benutzer gedacht, zeig sie unverändert. **503** heißt: Diesen Bereich gibt es in dieser Instanz nicht (keine Engine ohne API-Schlüssel, kein Taskboard ohne gemeinsamen Arbeitsbereich); `message` sagt warum. **401** heißt: Token weg, zurück zur Anmeldung. ### Lesen | Endpunkt | Liefert | |---|---| | `GET /api/info` | Version, Bau, Instanz, und welche Bereiche es gibt | | `GET /api/status` | OpenRouter: erreichbar, Guthaben | | `GET /api/agents` | Alle Agenten samt Werkzeugliste und `isRunning` | | `GET /api/agents/{id}` | Ein Agent | | `GET /api/agents/{id}/tools/{tool}/settings` | Werkzeug-Einstellungen, **maskiert** | | `GET /api/agents/{id}/chat` | Chatverlauf | | `GET /api/jobs` | Geplante Läufe mit `nextRunUtc` und `lastRunLocal` | | `GET /api/services` | Dienste der Instanz | | `GET /api/tasks?limit=50` | Taskboard | | `GET /api/staging` | Aufrufe, die auf Freigabe warten | | `GET /api/usage?limit=100` | Läufe mit Tokens und Kosten | | `GET /api/logs/modules` | Welche Protokollmodule es gibt | ### Schreiben | Endpunkt | Rumpf | |---|---| | `POST /api/agents` | `{ agentId, displayName, model, description }` | | `DELETE /api/agents/{id}` | — | | `POST /api/agents/{id}/run` | — (weckt den Agenten von Hand) | | `PUT /api/agents/{id}/tools/{tool}?enabled=true` | — | | `POST /api/agents/{id}/chat` | `{ message }` | | `POST /api/agents/{id}/chat/abort` | — | | `DELETE /api/agents/{id}/chat` | — (leert den Verlauf) | | `POST /api/jobs` | `{ agentId, kind, toolName, jobTypeId, cron, taskMessage, runOnStart }` | | `PUT /api/jobs/{jobId}` | wie oben | | `DELETE /api/jobs/{jobId}?agentId=…&kind=…` | — | | `PUT /api/jobs/{jobId}/enabled?agentId=…&enabled=false` | — | | `POST /api/jobs/{jobId}/run` | — | | `POST /api/services` | `{ name, type, port, description }` | | `DELETE /api/services/{id}` | — | | `PUT /api/services/{id}/enabled?enabled=false` | — | | `POST /api/staging/{id}/approve` | — | | `POST /api/staging/{id}/reject` | `{ reason }` | `kind` ist `"agentWakeup"` oder `"toolJob"`. ### Ereignisse ```js const strom = new EventSource(`/events?token=${encodeURIComponent(token)}`); strom.addEventListener("chat", e => …); ``` `EventSource` kann keine Kopfzeilen setzen — deshalb steht das Token hier ausnahmsweise in der Adresse. Das ist die einzige Stelle. | Ereignis | Daten | Wofür | |---|---|---| | `chat` | `{ agentId, role, content, source, timestamp }` | Neue Nachricht — **auch aus dem Fenster, aus Telegram, aus einem Tool-Job** | | `step` | `{ agentId, text }` | Fortschritt innerhalb eines Laufs | | `run` | `{ model, status, steps, tokens }` | Ein Lauf ist fertig | | `instance` | `{ kind, id }` | Agenten/Jobs/Dienste haben sich geändert — Bereich neu laden. `kind` ist `Agents`, `Services`, `Jobs` oder `Settings` | | `status` | `{}` | OpenRouter-Status neu — `/api/status` holen | Dazu `GET /events/logs?module=…&level=…` als eigener Strom mit `log`-Ereignissen. Zwischendurch kommen Zeilen, die mit `:` beginnen. Das sind Lebenszeichen, keine Ereignisse; `EventSource` schluckt sie von selbst. **`instance` ist der Grund, warum beide Oberflächen zusammenpassen.** Wenn es kommt, hat jemand anders etwas geändert — lade den betroffenen Bereich neu, statt deinem eigenen Zustand zu glauben. --- ## 3 — Fünf Regeln für die Oberfläche ### 3.1 Fremdtext ist nie HTML Modellantworten, Werkzeugergebnisse, Protokollzeilen, Aufgabentexte und die eingefrorenen Argumente einer Freigabe kommen von Modellen und von fremden Servern. ```js element.textContent = eintrag.content; // richtig element.innerHTML = eintrag.content; // nie ``` Auch nicht „nur für Markdown", auch nicht „nur an dieser einen Stelle". Wenn Markdown wirklich gebraucht wird: melden, dann wird eine Bereinigung dafür gebaut. ### 3.2 Navigation über `#` `#/agenten`, `#/chat/scout`, `#/aufgaben`. Kein `history.pushState`, keine Serverrouten. Damit kann ein Neuladen auf einer Unterseite nicht ins Leere gehen — der Server kennt nur `/` und die Dateien. ### 3.3 Kein Build-Schritt `deploy/publish.py` ruft `dotnet publish` und den Packager. **Kein npm, kein Node, kein Bundler.** Reines HTML, CSS und ES-Module im Browser. Wenn eine Bibliothek wirklich nötig ist: als fertige Datei ins `wwwroot`, nicht über ein CDN — die Zielmaschine hat womöglich kein Internet. ### 3.4 Dateinamen klein und ohne Umlaute Entwickelt wird unter Windows, betrieben unter **Linux**. `App.css` gegen `app.css` fällt auf dem Entwicklungsrechner nie auf und liefert auf der Zielmaschine 404. Also: Kleinbuchstaben, Ziffern, Bindestrich, Punkt. Kein Ordner `wwwroot/tools/` und keine `.xml`-Datei — beide wirft der Packager weg. ### 3.5 Alles auf Deutsch Oberflächentexte, Kommentare, Variablennamen im JavaScript, Fehlermeldungen. Der ganze Baum ist es, und ein englischer Einschub fällt sofort auf. Fehlermeldungen aus der API kommen fertig auf Deutsch — durchreichen, nicht umformulieren. --- ## 4 — Wie es aussehen soll **Der Entwurf liegt als HTML vor:** `Mockup/extracted/mockup/ClawdDotNet-UI-Mockup.dc.html` — im Browser öffnen. Er ist die Vorlage, nach der die Avalonia-Oberfläche gebaut wurde, und für eine Weboberfläche beinahe der Startpunkt: Farben, Rampen und das „Blueprint"-Rahmenmotiv stehen im `