{"record":{"id":"158abd02e3abe05d","repo":"koala73/worldmonitor","slug":"h2-page-heading-has-no-following-content","errorCode":null,"errorMessage":" H2 \"' + page.heading + '\" has no following content","messagePattern":" H2 \"' \\+ page\\.heading \\+ '\" has no following content","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/build-comparison-pages.mjs","lineNumber":516,"sourceCode":"}\n\nfunction renderKeywordSection(page, escapeHtml) {\n  const headingProse = page.headingProse ?? [];\n  const profiles = page.competitorProfiles ?? [];\n  const profileBlocks = profiles.flatMap((profile) => {\n    if (!profile.paragraphs?.length) {\n      throw new Error(page.slug + ' profile \"' + profile.name + '\" has no prose');\n    }\n    const tag = page.heading ? 'h3' : 'h2';\n    return [\n      '      <' + tag + '>' + escapeHtml(profile.name) + '</' + tag + '>',\n      ...renderParagraphs(profile.paragraphs, escapeHtml),\n    ];\n  });\n  if (page.heading) {\n    const inner = [...renderParagraphs(headingProse, escapeHtml), ...profileBlocks];\n    if (inner.length === 0) {\n      throw new Error(page.slug + ' H2 \"' + page.heading + '\" has no following content');\n    }\n    return ['      <h2>' + escapeHtml(page.heading) + '</h2>', ...inner];\n  }\n  return profileBlocks;\n}\n\nfunction renderMatrix(rows, escapeHtml) {\n  for (const row of rows) {\n    if (row.length !== COMPARISON_MATRIX_COLUMNS.length) {\n      throw new Error(\n        'matrix row for \"' + row[0] + '\" has ' + row.length +\n        ' cells, expected ' + COMPARISON_MATRIX_COLUMNS.length\n      );\n    }\n  }\n  const header = COMPARISON_MATRIX_COLUMNS\n    .map((column) => '<th>' + escapeHtml(column) + '</th>')\n    .join('');","sourceCodeStart":498,"sourceCodeEnd":534,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-comparison-pages.mjs#L498-L534","documentation":"When a keyword-section page defines a page.heading (H2), renderKeywordSection requires that the heading be followed by content: the heading prose paragraphs and/or competitor profile blocks. If both are empty, it throws '<slug> H2 \"<heading>\" has no following content' to prevent shipping a bare H2 with nothing under it.","triggerScenarios":"renderKeywordSection is called on a page where page.heading is truthy but headingProse is undefined/empty AND competitorProfiles is undefined/empty (or all profiles were rejected), so inner.length === 0.","commonSituations":"A page seed sets a heading for SEO keyword targeting but the prose and profiles were never filled in; both headingProse and competitorProfiles keys are misspelled so the ?? [] fallbacks silently produce empty arrays.","solutions":["Populate headingProse with at least one paragraph under the page heading.","Alternatively add competitorProfiles entries with paragraphs so profileBlocks is non-empty.","Verify field names match exactly (headingProse, competitorProfiles, paragraphs) since the ?? [] defaults mask missing keys."],"exampleFix":"// before\n{\n  heading: 'Country Instability Index comparisons',\n  headingProse: [],\n  competitorProfiles: [],\n}\n// after\n{\n  heading: 'Country Instability Index comparisons',\n  headingProse: ['This page compares how leading tools measure instability ...'],\n  competitorProfiles: [{ name: 'Acme Risk', paragraphs: ['...'] }],\n}","handlingStrategy":"validation","validationCode":"if (page.heading && !(page.headingProse?.length || page.competitorProfiles?.length)) {\n  throw new Error(`${page.slug}: heading \"${page.heading}\" needs headingProse or competitorProfiles`);\n}","typeGuard":"const headingHasContent = (p) => !p.heading || (p.headingProse?.length ?? 0) + (p.competitorProfiles?.length ?? 0) > 0;","tryCatchPattern":null,"preventionTips":["When adding a keyword heading, always fill headingProse in the same change.","Beware ?? [] defaults masking missing keys — validate seeds explicitly."],"tags":["build","content-validation","html-rendering"],"backgroundTag":"empty-required-field","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"}