fix(preview): stop spec generation timing out behind the edge proxy
All checks were successful
Deploy to Production / deploy (push) Successful in 50s

The /v1/servers/preview route ran claude-opus-4-7 synchronously; full spec
generation routinely exceeded Cloudflare's ~100s proxy cap, so the browser
received a headerless 524 and reported it as a CORS failure.

- preview now uses claude-sonnet-4-6 with a 45s per-attempt timeout and one
  retry — comfortably inside the proxy budget
- generateSpec maps an exhausted timeout to SpecTimeoutError; the route
  returns a clean 504 (with CORS headers) instead of a stalled connection
- analyze step: live elapsed-seconds counter as freeze-proof, plus a
  reduced-motion exception so the loading spinner keeps spinning (a status
  indicator, which WCAG exempts from reduced-motion)
- textarea resize grip restyled to dark theme (light hatch on dark square)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marco Sadjadi
2026-05-21 23:52:48 +02:00
parent 5d0d5668d8
commit e198d44e1e
4 changed files with 163 additions and 76 deletions

View File

@@ -87,6 +87,17 @@
::-webkit-scrollbar-thumb:hover {
background: var(--color-fg-subtle);
}
/* Textarea resize grip — light hatching on a dark square so it matches the
theme instead of the OS default (light square with dark hatching). */
textarea::-webkit-resizer {
background-color: var(--color-bg);
background-image: repeating-linear-gradient(
-45deg,
var(--color-fg-muted) 0 1px,
transparent 1px 4px
);
border-bottom-right-radius: var(--radius-md);
}
}
@layer components {
@@ -115,6 +126,16 @@
transition-duration: 0.001ms !important;
}
}
/* A loading spinner reports system status — WCAG treats status indicators as
an exception to reduced-motion, so keep it spinning after the rule above
has damped every decorative animation. Same layer + higher specificity
than the universal selector, so this !important wins. */
@media (prefers-reduced-motion: reduce) {
.animate-spin {
animation-duration: 1s !important;
animation-iteration-count: infinite !important;
}
}
}
@keyframes pulse-dot {