mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
* 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>
65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
import { execFile } from 'node:child_process'
|
|
import { promisify } from 'node:util'
|
|
import { gitExecFileAsync, ghExecFileAsync } from '../git/runner'
|
|
// Pure error-parsing helpers come from the lightweight module (not `runner`) so
|
|
// tests that mock `../git/runner` still resolve the real implementations.
|
|
import { extractExecError, parseRetryAfterMs } from '../git/exec-error'
|
|
|
|
// Why: legacy generic execFile wrapper - only used by callers that don't need
|
|
// WSL-aware routing. Repo-scoped callers should use the runner exports below.
|
|
export const execFileAsync = promisify(execFile)
|
|
export { ghExecFileAsync, gitExecFileAsync, extractExecError, parseRetryAfterMs }
|
|
export {
|
|
classifyGhError,
|
|
classifyListIssuesError,
|
|
classifyListPrsError
|
|
} from './gh-error-classification'
|
|
export {
|
|
_getOwnerRepoCacheSize,
|
|
_resetOwnerRepoCache,
|
|
getOwnerRepoForRemote,
|
|
getRemoteUrlForRepo,
|
|
ghRepoExecOptions,
|
|
githubRepoContext,
|
|
parseGitHubOwnerRepo,
|
|
parseGitHubRemoteIdentity
|
|
} from './github-repository-identity'
|
|
export type {
|
|
GitHubRemoteIdentity,
|
|
GitHubRepoContext,
|
|
LocalGitExecOptions,
|
|
OwnerRepo
|
|
} from './github-repository-identity'
|
|
export {
|
|
getIssueOwnerRepo,
|
|
getOwnerRepo,
|
|
resolveIssueSource,
|
|
resolvePRRepositoryCandidates
|
|
} from './github-owner-repo-selection'
|
|
export type { PRRepositoryCandidates, ResolvedIssueSource } from './github-owner-repo-selection'
|
|
|
|
const MAX_CONCURRENT = 4
|
|
let running = 0
|
|
const queue: (() => void)[] = []
|
|
|
|
export function acquire(): Promise<void> {
|
|
if (running < MAX_CONCURRENT) {
|
|
running += 1
|
|
return Promise.resolve()
|
|
}
|
|
return new Promise((resolve) =>
|
|
queue.push(() => {
|
|
running += 1
|
|
resolve()
|
|
})
|
|
)
|
|
}
|
|
|
|
export function release(): void {
|
|
running -= 1
|
|
const next = queue.shift()
|
|
if (next) {
|
|
next()
|
|
}
|
|
}
|