* fix(tasks): cap advertised GitHub pages at the search result window
GitHub's Search API rejects requests past its first-1000-results window
with HTTP 422, but totalPages was derived from the raw total_count, so
the pagination bar advertised pages that could never load and clicks on
them silently did nothing (#11485).
Cap per-repo advertised pages at floor(1000 / perRepoLimit), and when a
page load comes back empty, say so with a toast instead of ignoring the
click — clamping the advertised count only when no fetch threw, so
transient failures don't shrink the bar.
* fix(tasks): key pagination resets on repo selection, not array identity
The repos store installs a fresh array on every repos:changed event, so
the pagination-reset effect fired on background refreshes and bumped the
request generation, silently discarding any in-flight page navigation —
clicking an unloaded page did nothing whenever a repo refresh landed
during the fetch. Key the effect on the stable selection string instead.
* fix(tasks): distinguish end-of-data, window 422s, and failures on empty pages
Adversarial-review round 1 rework:
- fetchWorkItemsNextPage now returns issue-side envelope error types — the
channel the search-window 422 actually travels on (failedCount only
counts thrown repo calls).
- resolveEmptyPageOutcome (unit-tested) maps an empty page to
window-unreachable (clamp + toast), load-failed (toast only; may be
transient), or end-of-data (silently withdraw the speculative page the
count-fallback advertises).
- The work-items fetch effect is keyed on selectedReposKey too — its
unconditional page reset re-fired on every repos:changed array identity,
bouncing the user to page 1 mid-click. The key now includes the resolved
GitHub source context so identity changes still re-dispatch.
- Toasts carry stable ids so repeats replace instead of stack.
- Cap comment documents the conservative PR-scope tail loss; cap tests
pinned at shipped (36 → 27) and dividing (25 → 40) limits.
* fix(tasks): withdraw the speculative page when the failed count is zero
countedTotalPages of 0 comes from a swallowed count failure and routes
totalPages through the fallback, so the clamp must replace it like null.
* fix(tasks): tighten empty-page outcomes after round-2 review
- en.json's loadPageUnreachable carried the pre-reword text, and the
catalog beats the inline default — the two toasts were identical.
- end-of-data clamps only while the count is unknown/failed: the PR list
path swallows its own failures into clean-empty results, and clamping a
real count silently hid healthy pages (worse than the pre-fix no-op).
- A window 422 no longer clamps when a sibling repo's fetch threw.
- The generation effect mirrors every fetch-effect dep that resets page
state, so manual refresh/source switches invalidate in-flight clicks.
- selectedReposKey extracted as buildSelectedReposKey with stability
tests; envelope error types wire-tested through the store.
* fix(tasks): clamp against the committed count, not the click-time closure
Round-3 review: the count promise routinely resolves between click and
response, so deciding the end-of-data clamp from the closure value let a
stale null overwrite a real count. applyEmptyPageClamp now runs inside
the functional updater against the committed value, never raises an
earlier clamp, and a window 422 coinciding with a thrown sibling repo
resolves as load-failed so the toast and the clamp always agree.
* fix(tasks): only an all-window-422 empty page may clamp; harden count merges
Round-4 review: a sibling repo's envelope 403/404 arrives with
failedCount still 0, so the window branch now requires every error to be
the window 422 (non-window validation errors are demoted at the store);
the count resolution mins against an applied clamp instead of
re-advertising withdrawn pages; the generation effect mirrors
taskResumeApplied so its doc claim holds.
* fix(tasks): split the proven window limit from the count slot
Round-5 review: min-ing the count against an applied clamp pinned a
SPECULATIVE end-of-data withdrawal that raced ahead of the count,
permanently collapsing the bar for the generation. Proven window-422
limits now live in provenPageLimit (set once, only lowered, reset per
generation); the count overwrites its own slot unconditionally; and
deriveAdvertisedTotalPages (unit-tested for both arrival orders) caps
the count-or-fallback estimate with the proven limit, floored at the
loaded pages.
* fix(tasks): surface PR-side list failures so they can't read as end-of-data
Round-6 review: PartialWorkItemsResult had no PR error slot, so a
swallowed gh pr list failure reached the renderer as a clean empty page
— and with the count blocked (0) the speculative withdrawal deleted the
pagination bar with no toast and no recovery (a regression vs main's
silent no-op). PR-side errors now ride the envelope (errors.prs),
demoted so they can never join the issue-only window-422 signal;
errorTypes replaces issueErrorTypes; an empty page that a real count
said should exist now toasts instead of looking dead.
* test(tasks): cover the PR-error envelope end-to-end; neutral no-more-results toast
Round-7 review: the two literal gh-utils mocks lacked classifyListPrsError
(a PR-side rejection in those suites would TypeError instead of assert),
and the producer half of the errors.prs contract had no main-side test —
added both, plus a classifier contract test pinning the search-window
phrase the renderer keys on. The refused-clamp toast now reads the
committed count via a synchronous ref mirror instead of the click-time
closure, and says 'No more results' — nothing failed on that branch.
Both toast keys plus the new one are translated in es/ja/ko/zh.
* fix(tasks): preserve final reachable GitHub search page
* Extract GitHub search result window error pattern to shared constant
Extract the 1000-result window detection pattern to a single source of truth so
the classifier and consumer stay synchronized. The pattern is the only signal
separating a permanently unreachable page from a transient validation failure,
so drift or trimming silently demotes window 422s to generic failures and stops
capping the advertised page count (#11485).
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>