- Light mode --color-status-ready: #5B7EF8 → #5e6ad2 (DESIGN.md specifies darker shade for light backgrounds)
- Light mode --color-tint-red: rgba(207,34,46,...) → rgba(220,38,38,...) to match new #dc2626
- Dark mode --color-tint-red: rgba(255,123,114,...) → rgba(239,68,68,...) to match new #ef4444
- ActivityDot.tsx: update hardcoded bg tints — active gets green rgba(34,197,94,0.1), ready gets blue rgba(91,126,248,0.1)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete design system created via /design-consultation with competitive
research (Conductor.build, T3 Code, OpenAI Codex, Emdash) and 4 design
voices (primary, Codex CLI, Claude subagent, Emil Kowalski design eng).
Key decisions: Geist Sans + JetBrains Mono (2 fonts), dual accent system
(blue #5B7EF8 + amber #f1be64), 2px base border-radius, GPU-composited
animations, status colors with distinct semantic roles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this, the catch block's `err === lastError` identity check fails and
the error is absorbed into the retry loop instead of propagating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When rateLimitRetries was exhausted, a 429 fell through to the normal
error path where isRetryableHttpStatus(429)=true caused it to also
consume the error retry budget — giving 2×retries total attempts.
Now throws immediately when rate-limit budget is exhausted so the two
counters remain independent: up to `retries` rate-limit waits, then
up to `retries+1` attempts for genuine errors (5xx), never compounding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- openclaw-probe.ts: fix trailing ": " when response body is empty —
use conditional interpolation instead of .trim() on the full string
- doctor.ts: split connectivity check and test-notify into separate
try-catch blocks with distinct error messages so failures are
attributed correctly ("Notifier connectivity check failed" vs
"Sending test notifications failed")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, a 429 with no Retry-After header fell through to response.text()
with no explicit wait, relying on the generic exponential backoff. Now all 429
responses are handled uniformly via rateLimitRetries: use the Retry-After
value when present, otherwise fall back to retryDelayMs as the minimum wait.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each 429 with a Retry-After header was consuming one slot from the error
retry budget. With retries=3, three sustained rate-limits would exhaust
retries before any real error retry could fire.
Track rate-limit waits with a separate rateLimitRetries counter (capped at
retries to prevent infinite loops) and decrement attempt before continue so
the for-loop increment cancels out and the error retry budget is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When notificationRouting didn't exist, the setup wizard created a default
that included desktop only for urgent/action and dropped it for warning/info.
Users with defaults.notifiers: [desktop] would silently lose desktop
notifications for lower-priority events since notificationRouting takes
precedence over defaults.notifiers.
Now seeds all four priority levels from the existing defaults.notifiers array
(plus openclaw) so no previously-configured notifier is lost.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>