{"record":{"id":"b85f780d0f252d6f","repo":"jackwener/OpenCLI","slug":"targetcount-must-be-a-positive-integer-got-json","errorCode":null,"errorMessage":"targetCount must be a positive integer, got ${JSON.stringify(targetCount)}","messagePattern":"targetCount must be a positive integer, got (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":541,"sourceCode":"/**\n * Build a \"scroll until enough or plateaued\" IIFE used in place of a fixed\n * `autoScroll({ times: N })`. Xiaohongshu's search results page lazy-loads\n * ~5-7 notes per scroll, so the previous `times: 2` capped extraction at\n * ~13 items regardless of `--limit` (see #1471). This helper drives scrolls\n * dynamically:\n *\n *   - count visible `section.note-item` rows (excluding related-search\n *     `.query-note-item` rows)\n *   - if count >= targetCount → break (got enough)\n *   - if two consecutive scrolls add no new rows → break (DOM plateaued,\n *     no more lazy-load available)\n *   - hard cap at `maxScrolls` iterations (default 15) to bound runtime\n *\n * Exported so the rednote adapter (same DOM shape) can reuse it.\n */\nexport function buildScrollUntilJs(targetCount, maxScrolls = 15) {\n    if (!Number.isSafeInteger(targetCount) || targetCount < 1) {\n        throw new ArgumentError(`targetCount must be a positive integer, got ${JSON.stringify(targetCount)}`);\n    }\n    if (!Number.isSafeInteger(maxScrolls) || maxScrolls < 1) {\n        throw new ArgumentError(`maxScrolls must be a positive integer, got ${JSON.stringify(maxScrolls)}`);\n    }\n    return `\n      (async () => {\n        const isVisibleNote = (el) => {\n          if (el.classList.contains('query-note-item')) return false;\n          const rect = el.getBoundingClientRect();\n          if (rect.width <= 0 || rect.height <= 0) return false;\n          const style = getComputedStyle(el);\n          return style.display !== 'none' && style.visibility !== 'hidden';\n        };\n        // Note containers: legacy \\`section.note-item\\` first, fallback to\n        // any \\`<section>\\` that wraps a search-result/explore note link\n        // (#1506 reports the class being dropped on some xhs renders).\n        const collectNoteCards = () => {\n          const classMatches = document.querySelectorAll('section.note-item');","sourceCodeStart":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L523-L559","documentation":"buildScrollUntilJs validates that targetCount is a safe integer >= 1 before generating its scroll-until-enough browser IIFE, throwing this ArgumentError immediately otherwise. The parameter sets how many visible note rows to scroll until, so an invalid value would produce broken injected JS. This is a synchronous, fail-fast argument validation error at JS build time, not a page/automation failure.","triggerScenarios":"Calling buildScrollUntilJs(targetCount) directly (or the rednote adapter reusing it) with 0, a negative number, a non-number (string, undefined, NaN, Infinity), or a float.","commonSituations":"Passing an unparsed CLI --limit string; forgetting a default so the argument is undefined; a config object returning null/NaN; computing the limit from a parse that failed silently.","solutions":["Pass a positive safe integer, e.g. buildScrollUntilJs(50).","If the value comes from user input, parse and validate it: Number.isSafeInteger(Number.parseInt(raw, 10)).","Coerce with Math.max(1, Math.trunc(n)) after confirming the value is finite.","Check upstream callers to find where undefined/null leaked into the argument."],"exampleFix":"// before\nconst js = buildScrollUntilJs(process.env.LIMIT);\n// after\nconst limit = Number.parseInt(process.env.LIMIT ?? '20', 10);\nif (!Number.isSafeInteger(limit) || limit < 1) {\n  throw new Error(`invalid LIMIT: ${process.env.LIMIT}`);\n}\nconst js = buildScrollUntilJs(limit);","handlingStrategy":"validation","validationCode":"function assertPositiveInt(v, name) {\n  if (!Number.isSafeInteger(v) || v < 1) {\n    throw new TypeError(`${name} must be a positive safe integer, got ${JSON.stringify(v)}`);\n  }\n}\nassertPositiveInt(targetCount, 'targetCount');","typeGuard":"const isPositiveInt = (v) => Number.isSafeInteger(v) && v >= 1;","tryCatchPattern":null,"preventionTips":["Parse CLI/config numbers with Number.parseInt and validate before calling.","Never pass raw strings or undefined into buildScrollUntilJs.","Default missing values explicitly (limit ?? 20) after validation.","Write a unit test covering 0, NaN, Infinity, and string inputs.","Use Math.trunc + isSafeInteger checks when deriving limits dynamically."],"tags":["argument-validation","typeerror","api-misuse"],"backgroundTag":"invalid-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}