{"record":{"id":"2d7ae9b046b5a456","repo":"jackwener/OpenCLI","slug":"xiaohongshu-search-filter-layout-did-not-match-the","errorCode":null,"errorMessage":"Xiaohongshu search filter layout did not match the expected visible panel (${detail}).","messagePattern":"Xiaohongshu search filter layout did not match the expected visible panel \\((.+?)\\)\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":521,"sourceCode":"        return;\n    }\n    const detail = typeof result.detail === 'string' ? result.detail : 'unknown';\n    if (result.status === 'auth') {\n        throw new AuthRequiredError('www.xiaohongshu.com', 'Xiaohongshu search filters require a logged-in browser session');\n    }\n    if (result.status === 'timeout') {\n        throw new TimeoutError(`xiaohongshu search filter ${detail}`, FILTER_SETTLE_SECONDS);\n    }\n    if (result.status === 'location') {\n        throw new CommandExecutionError(`Xiaohongshu location filter was not applied (${detail}); enable browser geolocation permission.`);\n    }\n    if (result.status === 'capability') {\n        throw new CommandExecutionError(`Xiaohongshu account-scoped filter was unavailable (${detail}); verify login and account access.`);\n    }\n    if (result.status === 'inactive') {\n        throw new CommandExecutionError(`Xiaohongshu search filter chip did not become active (${detail}).`);\n    }\n    throw new CommandExecutionError(`Xiaohongshu search filter layout did not match the expected visible panel (${detail}).`);\n}\n/**\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) {","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L503-L539","documentation":"This CommandExecutionError is the fall-through branch of requireFilterApplication: the in-page filter script returned a status the handler does not recognize (anything other than ok/auth/timeout/location/capability/inactive), and the message describes it as the filter layout not matching the expected visible panel. It guards against the filter panel's DOM diverging from the selectors the script relies on (e.g. the panel count differing from 1 or an unrecognized result shape).","triggerScenarios":"Running the search with filters when the filter panel structure changed on xiaohongshu.com so openPanel() returns non-ok statuses not explicitly mapped, or the in-page script returns an unexpected payload shape such as a non-object or missing status.","commonSituations":"Xiaohongshu ships a UI redesign (new panel markup, renamed classes); an older CLI version scraping a newer page layout; A/B-tested layouts served to some sessions; overly aggressive ad-blocking extensions removing panel nodes.","solutions":["Upgrade the CLI to the newest version where selectors are updated for the current xiaohongshu.com layout.","Retry the command — A/B layouts sometimes resolve to the standard one on a fresh session.","Run without filters as a workaround, applying results filtering client-side.","Disable browser extensions (ad blockers, privacy tools) that may strip panel DOM nodes.","Inspect the page in the automation browser and file an issue with the current panel markup."],"exampleFix":"// before\nawait cli.search({ query: 'coffee', filters: [{ group: 'sort', option: 'time' }] });\n// after\n// fallback when panel layout changed\ntry {\n  await cli.search({ query: 'coffee', filters: [{ group: 'sort', option: 'time' }] });\n} catch (e) {\n  if (/filter layout did not match/.test(e.message)) {\n    const raw = await cli.search({ query: 'coffee' }); // unfiltered fallback\n    // filter results manually\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// verify the panel exists before applying filters\nconst panelVisible = await page.locator('section.filter, [class*=filter-panel]').first().isVisible();\nif (!panelVisible) throw new Error('filter panel not rendered; skip filters');","typeGuard":"function isFilterResult(r) {\n  return r !== null && typeof r === 'object' && !Array.isArray(r)\n    && typeof r.status === 'string'\n    && ['ok','auth','timeout','location','capability','inactive'].includes(r.status);\n}","tryCatchPattern":"try {\n  await xhs.search({ query, filters });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /layout did not match/.test(e.message)) {\n    const results = await xhs.search({ query }); // unfiltered fallback\n    return clientSideFilter(results, filters);\n  }\n  throw e;\n}","preventionTips":["Update the CLI whenever xiaohongshu.com changes its UI.","Disable ad-blockers/extensions in the automation browser.","Prefer the unfiltered-search + client-side filter fallback in production.","Report persistent layout errors with page markup to the CLI maintainers.","Pin and test the CLI version against the current site before large crawls."],"tags":["filter","dom-automation","layout"],"backgroundTag":"dom-layout-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}