{"record":{"id":"54b7b30cf3272126","repo":"koala73/worldmonitor","slug":"report-slug-id-must-match-slug-pattern-report-slug-report-id","errorCode":null,"errorMessage":"Report slug/id must match ${SLUG_PATTERN}: ${report.slug} / ${report.id}","messagePattern":"Report slug/id must match (.+?): (.+?) / (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/build-research-reports.mjs","lineNumber":710,"sourceCode":"function trackedLink(href, text, target, escapeHtml, extraAttrs = '') {\n  return `<a href=\"${escapeHtml(href)}\" data-umami-event=\"research-cta\" data-umami-event-target=\"${escapeHtml(target)}\"${extraAttrs}>${text}</a>`;\n}\n\nexport function renderResearchReportPage({\n  report,\n  snapshot,\n  metrics,\n  tpl,\n  baseUrl,\n  lastmod,\n  chokepointSlugById,\n  dataCatalog,\n  includedInDataCatalog,\n}) {\n  const { escapeHtml, absoluteUrl, breadcrumbLd, withUtmSource, pageDocument } = tpl;\n  const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;\n  if (!SLUG_PATTERN.test(report.slug) || !SLUG_PATTERN.test(report.id)) {\n    throw new Error(`Report slug/id must match ${SLUG_PATTERN}: ${report.slug} / ${report.id}`);\n  }\n  for (const [role, ids] of [\n    ['focusChokepointId', [report.focusChokepointId]],\n    ['contextChokepointId', report.contextChokepointIds],\n  ]) {\n    for (const id of ids) {\n      if (!SLUG_PATTERN.test(chokepointSlugById.get(id) ?? '')) {\n        throw new Error(`Report ${report.id}: ${role} ${id} has no valid route in the chokepoint registry`);\n      }\n    }\n  }\n  const chokepointSlug = chokepointSlugById.get(report.focusChokepointId);\n  const path = `/research/${report.slug}/`;\n  const canonical = absoluteUrl(baseUrl, path);\n  const focus = snapshot.chokepoints[report.focusChokepointId];\n  const resolve = (text) => resolveMetricTokens(escapeHtml(text), metrics, escapeHtml);\n  const m = (id) => {\n    const metric = metrics.get(id);","sourceCodeStart":692,"sourceCodeEnd":728,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-research-reports.mjs#L692-L728","documentation":"The research-reports builder validates each report's slug and id against /^[a-z0-9][a-z0-9-]*$/ before rendering pages, since both become URL path segments and file names. A slug/id that is empty, has uppercase, underscores, spaces, or starts with a hyphen throws this error naming the offending values.","triggerScenarios":"Adding or editing a research report entry whose slug or id violates the pattern — e.g. slug 'Q1_2026_Report', an id with uppercase, or an empty string from missing front-matter — then running the reports build.","commonSituations":"Hand-authored report metadata with human-friendly titles pasted into slug fields; renamed reports using underscores; automated imports that don't slugify; empty ids from template scaffolds.","solutions":["Rewrite report.slug to lowercase alphanumeric words separated by hyphens, not starting with a hyphen.","Apply the same pattern to report.id.","Add a slugify helper at data-entry time so new reports are normalized automatically.","Re-run the reports build to confirm validation passes."],"exampleFix":"// before\n{ id: 'Chokepoint_01', slug: 'Strait of Hormuz' }\n// after\n{ id: 'chokepoint-01', slug: 'strait-of-hormuz' }","handlingStrategy":"validation","validationCode":"const SLUG = /^[a-z0-9][a-z0-9-]*$/;\nif (!SLUG.test(report.slug) || !SLUG.test(report.id)) throw new Error('report slug/id must match ' + SLUG);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Slugify titles automatically at authoring time.","Enforce the slug pattern in report schema/front-matter lint.","Never use spaces, underscores, or uppercase in slugs/ids."],"tags":["build","slug","validation"],"backgroundTag":"invalid-identifier-format","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}