{"record":{"id":"1993c074a1279d21","repo":"koala73/worldmonitor","slug":"is-missing-following-prose","errorCode":null,"errorMessage":" is missing following prose","messagePattern":" is missing following prose","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/build-comparison-pages.mjs","lineNumber":495,"sourceCode":"    throw new Error(page.slug + ' is missing the switch-trigger section');\n  }\n  if (!merged.methodologyProse?.length) {\n    throw new Error(page.slug + ' is missing methodology prose');\n  }\n  return merged;\n}\n\nexport const COMPARISON_PAGES = COMPARISON_PAGE_SEEDS.map(applyComparisonNarrative);\n\nfunction renderParagraphs(paragraphs, escapeHtml) {\n  return (paragraphs ?? []).map((paragraph) => '      <p>' + escapeHtml(paragraph) + '</p>');\n}\n\nfunction renderHeadingSection(heading, paragraphs, escapeHtml, label) {\n  if (!heading) return [];\n  const body = renderParagraphs(paragraphs, escapeHtml);\n  if (body.length === 0) {\n    throw new Error((label || heading) + ' is missing following prose');\n  }\n  return ['      <h2>' + escapeHtml(heading) + '</h2>', ...body];\n}\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) {","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-comparison-pages.mjs#L477-L513","documentation":"renderHeadingSection renders an H2 plus its paragraphs for a comparison page. A heading with no following body text produces an empty section, which is both an SEO/content quality problem and an invalid page shape, so the build throws '<label|heading> is missing following prose' when renderParagraphs returns nothing for a heading.","triggerScenarios":"renderHeadingSection called with a truthy heading whose paragraphs argument is undefined, null, [], or an array of empty strings, causing body.length === 0.","commonSituations":"A seed defines a heading (e.g. evaluationHeading, switchHeading) but the corresponding prose array key is misspelled or missing; content editors emptied the paragraph array; a refactor renamed the prose field but not the heading field.","solutions":["Populate the prose array paired with that heading in the page seed (check the field name matches what applyComparisonNarrative merges, e.g. switchProse for switchHeading).","If the section is intentionally absent, remove the heading too — a heading without prose is what triggers the throw.","Re-run the build and confirm the section renders with at least one <p>."],"exampleFix":"// before\nswitchHeading: 'When to switch tools',\nswitchProse: [],\n// after\nswitchHeading: 'When to switch tools',\nswitchProse: [\n  'Switch to World Monitor when you need sub-daily updates across ...',\n],","handlingStrategy":"validation","validationCode":"for (const [heading, prose] of [['evaluation', page.evaluationProse], ['switch', page.switchProse]]) {\n  if (heading && !(Array.isArray(prose) && prose.length)) throw new Error(`${page.slug}: ${heading} heading has no prose`);\n}","typeGuard":"const hasHeadingProse = (p) => typeof p === 'object' && p !== null && Array.isArray(p.prose) && p.prose.length > 0;","tryCatchPattern":null,"preventionTips":["Keep heading and prose fields adjacent in the seed object so one is never added without the other.","Lint seeds for paired heading/prose keys before running the build."],"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"}