apiFetch threw new Error(api_error_<status>), so any UI fallback to (e).message showed a cryptic code, and some flows surfaced bare codes like slug_taken. Added a central humanizeError() in lib/api.ts: prefers the backend detail sentence, then a mapped friendly message per known code, then a status-based fallback - never a raw code. apiFetch now sets the thrown error message via it, so every (e).message fallback across the app becomes a real sentence. Wizard analyze/build now use humanizeError directly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Architectural fix for "spec_too_large" / preview_timeout — the sync
endpoint had to fit the whole model run into Cloudflare's ~100s edge
window, which made the system fragile against any prompt that produced
a verbose spec. The new streaming path pipes Anthropic's token deltas
as Server-Sent Events; every chunk resets CF's idle timer and a 15s
keepalive comment guarantees activity even during slow first-token
windows.
@bmm/llm: new streamSpecFromAnthropic() exposes the SDK's .stream()
flow with the same typed-error contract as generateSpec — same
SpecTruncatedError / SpecValidationError / SpecTimeoutError raised from
the relevant moment.
API: POST /v1/servers/preview/stream returns text/event-stream with
events 'text' (deltas), 'spec' (final success payload, same shape as
the sync endpoint), 'error' (typed). Anthropic-only — GLM/hobby falls
back to the sync route via 409 streaming_unavailable.
Frontend: apiSseStream() handles the POST + ReadableStream + SSE
parser. The wizard's analyze() prefers the stream and only uses the
sync endpoint on the explicit 409 fallback.
nginx (api.buildmymcpserver.com): the /v1/builds/ location block (which
already had proxy_buffering off + 600s read timeout for the WS build
stream) now also matches /v1/servers/preview/stream so the SSE
response isn't buffered.
Server-side authorization-code flow: /v1/auth/google redirects to the
consent screen with a CSRF state cookie; /v1/auth/google/callback
exchanges the code, validates the ID token (iss/aud/exp/email_verified),
and mints a 30-day session via upsertOAuthLogin. /v1/auth/providers lets
the login UI hide the button until GOOGLE_OAUTH_ID/SECRET are set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>