Skip to content

Show how much longer a rate limited batch has to run - #91

Merged
alistair3149 merged 3 commits into
masterfrom
show-time-left-while-rate-limited
Aug 21, 2026
Merged

Show how much longer a rate limited batch has to run#91
alistair3149 merged 3 commits into
masterfrom
show-time-left-while-rate-limited

Conversation

@alistair3149

Copy link
Copy Markdown
Member

Follows-up to #89

A throttled row said only that it was waiting, so a batch that was working correctly but slowly looked the same as one that had hung. On a wiki allowing 8 uploads a minute, 100 files take over twelve minutes — the right outcome, but only if the user is told.

A line above the result list now says how much longer the wiki's limit will hold the batch up:

About 3 minutes left at this wiki's upload rate limit.

It is a schedule, not a measurement

The gate hands out one upload slot per interval and already knows when the next is due, so the figure is arithmetic on a schedule the client is itself enforcing:

remaining = waitMs + ( pending − 1 ) × intervalMs

Nothing is sampled, so nothing needs smoothing. The exponential averaging and downward bias that upload UIs usually carry exist to tame a measured byte rate; there is no byte rate here.

It excludes the transfer time itself, which is additive and matters for large files, and it can grow if the wiki refuses again. Hence "about", and hence whole minutes rather than a countdown.

Shown only while throttled

Pacing begins on the first refusal and lapses after a quiet window, so that is exactly when a schedule exists. Before then there is nothing to estimate — and inferring one from the advertised hits would assume a fresh budget, which is the one thing uiprop=ratelimits does not report. No placeholder, no "calculating…", and the line removes itself when the batch ends or the gate gives up.

Rows are untouched: a row still says why it is stuck, and one line says how long. Per-file figures would be invented, since slots are handed out in promise-resolution order and a retry takes a fresh one.

Rounding is the accessibility fix

The line is a role="status" live region and the DOM write is guarded on the text actually changing. Because it is whole minutes, the string changes at most once a minute. Verified against a live wiki: a 128 second batch produced two distinct strings, so two announcements rather than 128.

The count comes from the batch limit, not the queue

batchLimit.active() spans admit-to-terminal. queue.running() + queue.waiting() looks equivalent and is not: a file being retried has left the queue and not yet rejoined it, so the queue undercounts by one per retrying file at exactly the moment onRateLimited fires and the figure is refreshed.

Verified

95 unit tests; PHPCS, PHPStan and PHPUnit green. Checked in a browser at 2 uploads per 40 seconds: no line before the first refusal, "About 2 minutes left" then "Less than a minute left" during, and no line once all seven files had landed.

Considered, omitted

  • A countdown, or any display of seconds. Seconds are the digit that forces a repaint and the least trustworthy part of the number.
  • Per-file estimates or queue positions. Not knowable — see above.
  • Measuring throughput to estimate unthrottled uploads too. That is a different and much weaker feature: it needs a sampled byte rate, and the answer would not be reliable.
  • An hours unit. "About 90 minutes left" is fine and avoids a second rounding decision.
  • A config variable to turn it off.

AI-authored — Claude Code, Opus 5 1M (ultracode); one-line ask, then one clarification confirming it should appear only while throttled; diff not yet human-reviewed; 95 unit tests plus PHPCS/PHPStan/PHPUnit green, and the readout checked end to end in a browser against a live wiki.

alistair3149 and others added 2 commits August 21, 2026 10:44
A throttled row said only that it was waiting, so a batch that was working
correctly but slowly was indistinguishable from one that had hung. On a
wiki allowing 8 uploads a minute, 100 files take over twelve minutes, and
that is the right outcome rather than a fault -- but only if the user is
told.

The figure is not a measurement. The gate hands out one slot per interval
and already knows when the next is due, so this is arithmetic on a
schedule the client is itself enforcing: the wait left on the current
backoff, plus one interval for every file queued behind it. Nothing is
sampled, so nothing needs smoothing.

Shown only while the wiki is actually throttling, because that is the only
time a schedule exists. Before the first refusal there is nothing to
estimate, and guessing from the advertised hits would assume a fresh
budget, which is the one thing the API does not report.

Whole minutes, rounded up, and "about". The estimate excludes the transfer
itself and can grow if the wiki refuses again, so a ticking countdown would
claim a precision it does not have. Rounding is also what keeps the live
region quiet: the text changes at most once a minute rather than on every
refresh. Verified against a live wiki -- a 128 second batch produced two
distinct strings, not 128.

The count comes from the batch limit rather than the upload queue. A file
being retried has left the queue and not yet rejoined it, so the queue
undercounts by one per retrying file at exactly the moment a refusal is
being handled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Several explained the mechanism the code already shows, or repeated a
rationale that belonged in one place. What remains is the four notes that
stop someone making a plausible wrong change: why the count does not come
from the upload queue, why there is no smoothing, why the figure is
rounded up to whole minutes, and why the DOM write is guarded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alistair3149

Copy link
Copy Markdown
Member Author
simplebatchupload-estimate

Three tests looked like they covered the new code and did not. Verified by
mutation: deleting the halted guard from schedule(), dropping nextReleaseAt
from the wait it reports, and adding ful-estimate to the pruned classes all
left the suite green.

The nextReleaseAt one mattered most. Every schedule test read the gate
before any file had passed it, so the term was only ever exercised on the
openAt side -- yet once the first backoff expires it is the only thing
holding the next file up, which is where a paced batch spends nearly all
its life. Under that mutation the readout would have said "less than a
minute" for an entire batch with nothing to catch it.

The create, update and remove logic moves to estimateRow.js so it can be
tested under jsdom the way resultRow.js already is. It was the riskiest
part of the change and the only part with no tests, purely because of
where it lived.

While extracting it: role="status" was on the <li>, which replaces its
listitem role, so the list announced one fewer item than it had. The live
region is now a span inside the row, which keeps both semantics.

Also stops claiming a wait for a single file that is released immediately,
moves a docblock the earlier split left on the wrong function, and drops a
padding declaration the rule above already sets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alistair3149
alistair3149 marked this pull request as ready for review August 21, 2026 15:10
@alistair3149
alistair3149 merged commit 2b95811 into master Aug 21, 2026
22 checks passed
@alistair3149
alistair3149 deleted the show-time-left-while-rate-limited branch August 21, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant