{"record":{"id":"1616ddb9f9b63577","repo":"jackwener/OpenCLI","slug":"step-must-be-a-non-negative-number-got-json-str","errorCode":null,"errorMessage":"step must be a non-negative number, got ${JSON.stringify(step)}","messagePattern":"step must be a non-negative number, got (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":637,"sourceCode":"      })()\n    `;\n}\n\nexport function buildScrollHarvestJs(webHost, targetCount, options = {}) {\n    const maxRounds = options.maxRounds ?? 30;\n    const budgetMs = options.budgetMs ?? 30_000;\n    const step = options.step ?? DEFAULT_HARVEST_STEP;\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(maxRounds) || maxRounds < 1) {\n        throw new ArgumentError(`maxRounds must be a positive integer, got ${JSON.stringify(maxRounds)}`);\n    }\n    if (!Number.isFinite(budgetMs) || budgetMs <= 0) {\n        throw new ArgumentError(`budgetMs must be a positive number, got ${JSON.stringify(budgetMs)}`);\n    }\n    if (!Number.isFinite(step) || step < 0) {\n        throw new ArgumentError(`step must be a non-negative number, got ${JSON.stringify(step)}`);\n    }\n    return `\n      (async () => {\n        const targetCount = ${targetCount};\n        const maxRounds = ${maxRounds};\n        const budgetMs = ${budgetMs};\n        const configuredStep = ${step};\n        const webHost = ${JSON.stringify(webHost)};\n        const noteUrlInfo = ${noteUrlInfo.toString()};\n        const mergeHarvestedRow = ${mergeHarvestedRow.toString()};\n        const stripXhsAuthorDateSuffix = ${stripXhsAuthorDateSuffix.toString()};\n        const extractSearchRows = ${extractSearchRows.toString()};\n        const usableRowCount = ${usableRowCount.toString()};\n        const shouldStopScrolling = ${shouldStopScrolling.toString()};\n        const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n        const rootScroller = document.scrollingElement || document.documentElement || document.body;\n        const rootScrollHeight = () => Math.max(\n          rootScroller?.scrollHeight || 0,","sourceCodeStart":619,"sourceCodeEnd":655,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L619-L655","documentation":"buildScrollHarvestJs validates step as a finite number >= 0 (default DEFAULT_HARVEST_STEP via options.step) and throws this ArgumentError otherwise. step is the scroll distance per harvest round in the injected script; a negative or non-finite value would break viewport scrolling and is rejected at build time.","triggerScenarios":"Passing options { step: -500 } (or NaN, Infinity, a string, a non-plain number) to buildScrollHarvestJs.","commonSituations":"Config supplying a negative 'scroll back up' value; a calculation like height - header producing a negative; parsing errors yielding NaN; unit confusion between px and vh.","solutions":["Pass a finite non-negative number, e.g. { step: 800 }.","Omit options.step to use the library default.","Clamp with Math.max(0, computedStep) and check Number.isFinite.","Re-derive the step from window.innerHeight if it was computed from a stale value."],"exampleFix":"// before\nbuildScrollHarvestJs(host, target, { step: viewport - header });\n// after\nconst step = Math.max(0, viewport - header);\nbuildScrollHarvestJs(host, target, { step: Number.isFinite(step) ? step : undefined });","handlingStrategy":"validation","validationCode":"function assertStep(v) {\n  if (!Number.isFinite(v) || v < 0) {\n    throw new TypeError(`step must be a non-negative finite number, got ${JSON.stringify(v)}`);\n  }\n}\nassertStep(options.step ?? DEFAULT_HARVEST_STEP);","typeGuard":"const isValidStep = (v) => v === undefined || (Number.isFinite(v) && v >= 0);","tryCatchPattern":null,"preventionTips":["Clamp computed scroll distances with Math.max(0, v) before passing.","Omit step to use the library default for typical viewports.","Check Number.isFinite to catch NaN from subtraction of undefined values.","Never pass negative values to 'scroll up'; the API only accepts >= 0.","Keep scroll-step math in one helper with its own unit test."],"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"}