{"record":{"id":"0b0786abb1d1e37f","repo":"koala73/worldmonitor","slug":"profile-profile-name-has-no-prose","errorCode":null,"errorMessage":" profile \"' + profile.name + '\" has no prose","messagePattern":" profile \"' \\+ profile\\.name \\+ '\" has no prose","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/build-comparison-pages.mjs","lineNumber":505,"sourceCode":"function 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) {\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) {","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-comparison-pages.mjs#L487-L523","documentation":"renderKeywordSection renders each competitor profile as a heading plus paragraphs. Every profile must carry at least one paragraph; if profile.paragraphs is missing or empty, the build throws '<slug> profile \"<name>\" has no prose' so no empty competitor block reaches the generated page.","triggerScenarios":"A page.competitorProfiles entry has no paragraphs property or paragraphs: [] when renderKeywordSection runs during COMPARISON_PAGES construction.","commonSituations":"Adding a new competitor profile entry and forgetting the paragraphs field; renaming paragraphs to body/text in one profile but not others; importing profiles from a data file where one entry was stubbed with an empty array.","solutions":["Add a non-empty paragraphs array to the named competitor profile in competitorProfiles.","Check for typos in the field name — it must be exactly paragraphs (optional chaining hides null/undefined until this check).","Remove the stub profile entirely if it is not ready; partial profiles are not allowed."],"exampleFix":"// before\ncompetitorProfiles: [\n  { name: 'Acme Risk', paragraphs: [] },\n]\n// after\ncompetitorProfiles: [\n  { name: 'Acme Risk', paragraphs: ['Acme Risk focuses on country risk scores with ...'] },\n]","handlingStrategy":"validation","validationCode":"page.competitorProfiles?.forEach((p) => {\n  if (!Array.isArray(p.paragraphs) || p.paragraphs.length === 0) throw new Error(`profile ${p.name} needs paragraphs`);\n});","typeGuard":"const hasProfileProse = (p) => Array.isArray(p?.paragraphs) && p.paragraphs.length > 0;","tryCatchPattern":null,"preventionTips":["Define competitor profiles via a factory that requires paragraphs at construction time.","Never stub profiles with empty arrays in committed seeds."],"tags":["build","content-validation","comparison-pages"],"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"}