fix: batch large-thread candidates + stop negative-caching uncapped names (closes task/chunk-large-thread-candidates)

Source: task/chunk-large-thread-candidates (plan/steward-linker-quality) — content.js sent all candidates in one call, the server capped at 100, and names past the cap were permanently null-cached and never retried — silently breaking large threads.

- content.js: resolve the per-scan ask set in batches of BATCH (100, <= server MAX_CANDIDATES) via a new pure Extract.chunk helper, so every candidate is actually answered and none is dropped by the resolver's slice.
- content.js: cache only keys actually present in a response (hasOwnProperty); cap-overflow / partial-error / resolver-down names are left out of `resolved` and retried next scan instead of stuck at null.
- extract.js: add pure chunk(arr, size) on the RSLExtract global; extract.test.cjs covers exact-multiple, remainder, under-size, empty, and lossless in-order concatenation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
paul
2026-06-23 12:28:08 +00:00
co-authored by Claude Opus 4.8
parent b0f7116493
commit 5a3abf6c89
3 changed files with 50 additions and 5 deletions
+16 -3
View File
@@ -97,6 +97,12 @@
// ---- resolver client ------------------------------------------------------
const resolved = new Map(); // candidate string -> { name, spotify?, google?, primary } | null
// The resolver caps each /resolve call at MAX_CANDIDATES (service/resolve-core.js); names past the
// cap are silently dropped from the response. So split a large ask into batches of at most that cap
// and resolve each — every candidate is actually answered, none lost to the server-side slice.
// MUST stay <= the server's MAX_CANDIDATES.
const BATCH = 100;
// The network call goes through the background script — a page-context fetch to
// http://localhost is blocked by reddit's CSP / mixed-content rules.
async function resolve(names) {
@@ -133,9 +139,16 @@
log("candidates found:", seen.size);
const ask = [...seen].filter((c) => !resolved.has(c));
if (ask.length) {
const res = await resolve(ask);
if (res) for (const c of ask) resolved.set(c, res[c] || null); // negative-cache misses
// Resolve in server-cap-sized batches so no candidate is dropped by the resolver's slice.
// Only cache keys the response actually carries: a name the server didn't answer (cap overflow,
// partial error, or a resolver-down null response) is left OUT of `resolved` so it is retried on
// the next scan, instead of being stuck at null and never retried again.
for (const batch of Extract.chunk(ask, BATCH)) {
const res = await resolve(batch);
if (!res) continue; // whole-batch failure (resolver down): leave every name un-cached for retry
for (const c of batch) {
if (Object.prototype.hasOwnProperty.call(res, c)) resolved.set(c, res[c] || null);
}
}
for (const node of nodes) if (node.isConnected) wrapNode(node);