{"record":{"id":"e1ff567c44f722e8","repo":"koala73/worldmonitor","slug":"invalid-limit","errorCode":"invalid_limit","errorMessage":"limit must be an integer from 1 to ${DASHBOARD_PANEL_CATALOG_MAX_LIMIT}.","messagePattern":"limit must be an integer from 1 to (.+?)\\.","errorType":"validation","errorClass":"DashboardPanelCatalogError","httpStatus":null,"severity":"error","filePath":"src/services/webmcp-panel-catalog.ts","lineNumber":206,"sourceCode":"      typeof cursor !== 'string'\n      || cursor.length > DASHBOARD_PANEL_ID_MAX_CHARS\n      || !PANEL_ID_RE.test(cursor)\n      || !CANONICAL_PANEL_ID_SET.has(cursor)\n    ) {\n      throw new DashboardPanelCatalogError(\n        'invalid_cursor',\n        'cursor is not a valid catalog cursor.',\n      );\n    }\n  }\n\n  const limit = query.limit ?? DASHBOARD_PANEL_CATALOG_DEFAULT_LIMIT;\n  if (\n    !Number.isInteger(limit)\n    || limit < 1\n    || limit > DASHBOARD_PANEL_CATALOG_MAX_LIMIT\n  ) {\n    throw new DashboardPanelCatalogError(\n      'invalid_limit',\n      `limit must be an integer from 1 to ${DASHBOARD_PANEL_CATALOG_MAX_LIMIT}.`,\n    );\n  }\n\n  const catalogVariant = variantFilter ?? currentVariant;\n  const registryIds = getCanonicalDashboardPanelIds(variantFilter);\n  const items: DashboardPanelCatalogItem[] = [];\n  for (const panelId of registryIds) {\n    const item = describePanel(panelId, live, catalogVariant);\n    if (categoryFilter !== undefined && item.category !== categoryFilter) continue;\n    if (query.enabled !== undefined && item.enabled !== query.enabled) continue;\n    if (query.available !== undefined && item.available !== query.available) continue;\n    items.push(item);\n  }\n\n  const startIndex = cursor === undefined\n    ? 0","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/src/services/webmcp-panel-catalog.ts#L188-L224","documentation":"listDashboardPanelCatalog() clamps page size via query.limit, defaulting to DASHBOARD_PANEL_CATALOG_DEFAULT_LIMIT (6) with a hard maximum DASHBOARD_PANEL_CATALOG_MAX_LIMIT (8). A limit that is not an integer or is outside 1..8 throws DashboardPanelCatalogError with reason 'invalid_limit'; it does not silently clamp, so callers must pre-validate.","triggerScenarios":"Calling with { limit: 0 }, { limit: 50 }, { limit: 6.5 }, { limit: '6' }, { limit: NaN } — any non-integer or out-of-range value, e.g. an unclamped UI page-size selector or string page-size from a querystring.","commonSituations":"Assuming the server clamps instead of throws; passing string '6' from URL params; float results from dividing available height by row height; request sizes above 8 rejected while tuning output budgets.","solutions":["Clamp before calling: limit = Math.min(8, Math.max(1, Math.floor(Number(raw)))) and omit the key if NaN.","Omit query.limit to use the default of 6 when no specific size is needed.","Validate Number.isInteger(limit) && limit >= 1 && limit <= DASHBOARD_PANEL_CATALOG_MAX_LIMIT before the call, importing the exported constants.","Catch DashboardPanelCatalogError with reason 'invalid_limit' and retry with the default limit."],"exampleFix":"// before\nlistDashboardPanelCatalog(live, { limit: Number(searchParams.get('limit')) }); // may be NaN/0/100\n// after\nconst parsed = Number(searchParams.get('limit'));\nconst limit = Number.isInteger(parsed) ? Math.min(DASHBOARD_PANEL_CATALOG_MAX_LIMIT, Math.max(1, parsed)) : undefined;\nlistDashboardPanelCatalog(live, limit === undefined ? {} : { limit });","handlingStrategy":"validation","validationCode":"import { DASHBOARD_PANEL_CATALOG_MAX_LIMIT } from '@/services/webmcp-panel-catalog';\nconst parsed = Number(rawLimit);\nconst limit = Number.isInteger(parsed) && parsed >= 1\n  ? Math.min(parsed, DASHBOARD_PANEL_CATALOG_MAX_LIMIT)\n  : undefined;","typeGuard":"function isValidLimit(value: unknown): value is number {\n  return Number.isInteger(value) && (value as number) >= 1 && (value as number) <= DASHBOARD_PANEL_CATALOG_MAX_LIMIT;\n}","tryCatchPattern":"try {\n  return listDashboardPanelCatalog(live, { limit, ...query });\n} catch (err) {\n  if (err instanceof DashboardPanelCatalogError && err.reason === 'invalid_limit') {\n    return listDashboardPanelCatalog(live, { ...query, limit: undefined }); // default limit\n  }\n  throw err;\n}","preventionTips":["Clamp and floor any computed page size before passing it (Math.min/max/floor).","Omit limit to accept the default (6) instead of computing your own page size.","Coerce querystring values with Number() and reject NaN/strings before calling.","Import DASHBOARD_PANEL_CATALOG_MAX_LIMIT rather than hardcoding 8 so bounds stay in sync."],"tags":["validation","pagination","panel-catalog","limit"],"backgroundTag":"invalid-pagination-limit","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}