Skip to content

Dashboard 429 UX: client-side concurrency + honest retry messaging #2804

Description

@bokelley

Context

#2803 widened the per-agent read cap from 20/min → 240/min, fixing the self-inflicted "every card rate-limited" experience on normal dashboard loads. That removed the immediate bug, but the client-side UX if a 429 does legitimately fire is still weak.

Surface: server/public/dashboard-agents.html.

What's broken today

  1. Parallel fan-out with no concurrency cap. loadAgents fires Promise.allSettled(agents.map(fetchAgentState)) — every request for every card in flight at once. A heavy user (80+ agents, cold page, hard reload spam) can still self-429.

  2. Response body retryAfter is ignored. fetchWithRetry (line ~577) reads the Retry-After header only. fix(dashboard): stop applying bulk-resolve cap to per-agent reads #2803 drops the hardcoded body field because of this, but the underlying client behavior could stand to fall back to the body when the header is stripped by a proxy.

  3. "Retry in a moment" is vague. No countdown, no disabled state on a second retry, no explanation. A user who refreshes hard gets cryptic error cards with no path forward.

Proposed work

  • Add a p-limit (or hand-rolled) concurrency cap of ~6 in loadAgents so the fan-out is naturally rate-friendly. (Each agent is 2 requests against agentReadRateLimiter + 1 request via the separate auth-status middleware, so 6 in-flight agents = ~18 requests at any instant.)
  • When a 429 fires on a card, surface actual wait time from the RateLimit-Reset / Retry-After header, render a live countdown ("Retry in 28s…"), auto-retry once when it hits zero, then fall back to a manual retry button with "You've refreshed too quickly — wait 30s" guidance.
  • As a fallback, parse any numeric hint from the response body if the header is missing.

Out of scope

  • Backend cap tuning — leave at 240/min for now.
  • Redesigning the card error layout itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestwebsiteAny issues related to the website

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions