leanos/tasks/lessons.md
Thomas Lutz Kolter ee203fd9fb leanOS: initial import (basiert auf NEON NOODLE OS)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-15 14:58:54 +02:00

3.7 KiB

Lessons

Runtime-kompilierte React-Komponenten: Identität stabil halten

Kontext: Generierte Apps werden via new Function(...) zu einer App-Komponente kompiliert und gerendert.

Fehler: Die App-Funktion wurde im Render-Body des Wrapper-Components per factory(...) neu erzeugt. Jeder Render → neue Funktionsidentität → React unmountet/ remountet den Teilbaum → useState resettet, useEffect feuert erneut → Endlos- Schleife ("Maximum update depth exceeded") bei jeder App mit State.

Regel: Dynamisch erzeugte Komponenten einmal memoisieren (useMemo(() => factory(...), [stableDeps])), nie pro Render neu bauen. Galt hier für die Sandbox-Runtime; gilt generell für jedes React.createElement mit einer zur Laufzeit konstruierten Komponente.

Verifikation: innerText enthält keine Placeholder

Beim Headless-Test gegen Spotlight schlug die Assertion fehl, weil sie auf den placeholder-Text eines <input> prüfte — document.body.innerText liefert Placeholder/Attribut-Text nicht. Für UI-Smoke-Tests auf echte Text-Knoten oder Selektoren (querySelector('input[placeholder*="…"]')) prüfen, nicht auf innerText.

Anthropic-kompatible Provider (z.ai): URL-Pfad + CORS beim Streaming

Zwei aufeinanderfolgende Stolpersteine beim Umstellen auf z.ai/GLM:

  1. URL ohne Pfad: VITE_ANTHROPIC_API_URL=https://api.z.ai/api/anthropic ist nur die Basis-URL → Request landete auf …/anthropic statt …/anthropic/v1/messages (z.ai: {"code":500,"msg":"404 NOT_FOUND"}). Fix: resolveApiUrl() hängt /v1/messages an, wenn nicht vorhanden (Basis- oder Voll-URL beide ok).
  2. Doppelte CORS-Header beim Streaming: Die stream:true-Antwort von z.ai enthält ZWEI access-control-allow-origin-Header (http://localhost:5173 UND *) plus allow-credentials: true. Das ist laut CORS-Spec ungültig → Browser wirft "TypeError: Failed to fetch". Die Nicht-Stream-Antwort hat nur einen Header, deshalb ging App-Generierung, aber der Assistant nicht. Fix: Vite-Dev-Proxy (server.proxy["/__ai"] → Upstream-Origin+Pfad aus env, changeOrigin:true). Browser ruft same-origin /__ai/v1/messages → kein CORS, Streaming bleibt erhalten. Client nutzt im DEV den Proxy, im Prod-Build die absolute URL. Merke: curl testet CORS NICHT — Browser-Reproduktion ist Pflicht.

X-Frame-Options / CSP beim Einbetten externer Seiten (in-OS Browser)

X-Frame-Options und CSP frame-ancestors werden vom Browser anhand der Ziel-Response erzwungen — client-seitig NICHT umgehbar (kein iframe-Trick, kein Header-Override). Einzige saubere Lösung: Server-Proxy, der die Seite serverseitig holt (dort sind die Header wirkungslos) und sie ohne Framing-Header zurückgibt. Umsetzung hier: Vite-Dev-Middleware /__proxy?url=… → fetch upstream, droppt x-frame-options/content-security-policy/content-encoding/content-length, injiziert <base href> (relative Assets) + ein Klick-Interceptor-Script (Navigation bleibt im Proxy). SDK-Helfer os.net.proxyUrl(url) + Generierungs-Prompt weisen Browser-Apps an, NIE eine rohe URL in den iframe zu setzen. <iframe> im Forbidden-Guard freigeben. End-to-end verifiziert: en.wikipedia/wiki/Cat lädt im iframe (innerTitle "Cat - Wikipedia"). Prod-Build hat keinen Proxy → Fallback auf rohe URL (dann wieder geblockt); für Prod bräuchte es einen echten Backend-Proxy.

Verifikation lohnt sich auch ohne externen Key

Die KI-App-Generierung braucht einen API-Key. Statt "kann ich nicht testen" wurde der konsumierende Runtime (Babel→Sandbox→Render→Error-Boundary→Forbidden-Guard) mit synthetischem KI-Output headless verifiziert. Das fand den Remount-Bug, den der echte API-Pfad sonst erst beim Nutzer gezeigt hätte.