58 lines
3.7 KiB
Markdown
58 lines
3.7 KiB
Markdown
# 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.
|