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