{"record":{"id":"36e47ed6b6dcfea3","repo":"koala73/worldmonitor","slug":"is-missing-whywewinbody","errorCode":null,"errorMessage":" is missing whyWeWinBody","messagePattern":" is missing whyWeWinBody","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/build-comparison-pages.mjs","lineNumber":465,"sourceCode":"    throw new Error('Missing comparison narrative for ' + page.slug);\n  }\n  const faqs = [...page.faqs, ...(narrative.extraFaqs ?? [])];\n  if (faqs.length < 8 || faqs.length > 12) {\n    throw new Error(page.slug + ' FAQ count must be 8-12, got ' + faqs.length);\n  }\n  const faqNames = new Set();\n  for (const [question] of faqs) {\n    const key = String(question).trim().toLowerCase();\n    if (faqNames.has(key)) {\n      throw new Error(page.slug + ' duplicate FAQ question: ' + question);\n    }\n    faqNames.add(key);\n  }\n  const merged = { ...page, ...narrative, faqs };\n  delete merged.extraFaqs;\n  const whyBody = (merged.whyWeWinBody ?? []).join(' ').replace(/\\s+/g, ' ').trim();\n  if (!whyBody) {\n    throw new Error(page.slug + ' is missing whyWeWinBody');\n  }\n  if (whyBody === merged.whyWeWin.replace(/\\s+/g, ' ').trim()) {\n    throw new Error(page.slug + ' whyWeWinBody must not repeat whyWeWin');\n  }\n  if (merged.heading && !(merged.headingProse?.length || merged.competitorProfiles?.length)) {\n    throw new Error(page.slug + ' H2 \"' + merged.heading + '\" has no following prose');\n  }\n  if (!merged.evaluationHeading || !merged.evaluationProse?.length) {\n    throw new Error(page.slug + ' is missing the evaluation section');\n  }\n  if (!merged.switchHeading || !merged.switchProse?.length) {\n    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}","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-comparison-pages.mjs#L447-L483","documentation":"applyComparisonNarrative requires every page's merged content to include a non-empty whyWeWinBody array (joined and whitespace-normalized must be non-empty). This is the expanded 'why we win' prose that follows the summary claim; a page with only the headline whyWeWin text is rejected.","triggerScenarios":"Running the build when a slug's narrative lacks whyWeWinBody entirely, sets it to an empty array, or contains only empty/whitespace strings.","commonSituations":"Authoring a new comparison narrative and filling whyWeWin but not the body list; copy-editing that emptied the array; a merge step where the field key was misspelled (whyWeWinBodies etc.).","solutions":["Add a whyWeWinBody array of paragraph strings to the slug's COMPARISON_NARRATIVES entry.","Ensure the entries are non-empty after whitespace normalization.","Check the field name spelling in the narrative object.","Make sure whyWeWinBody does not simply duplicate whyWeWin (that throws separately)."],"exampleFix":"// before\n'worldmonitor-vs-x': { whyWeWin: 'More datasets.' }\n// after\n'worldmonitor-vs-x': {\n  whyWeWin: 'More datasets.',\n  whyWeWinBody: ['WorldMonitor fuses maritime, aviation and conflict feeds into one live map.'],\n}","handlingStrategy":"validation","validationCode":"const body = (narrative.whyWeWinBody ?? []).join(' ').replace(/\\s+/g, ' ').trim();\nif (!body) throw new Error(page.slug + ' needs non-empty whyWeWinBody');","typeGuard":"const hasWhyBody = (n) => Array.isArray(n.whyWeWinBody) && n.whyWeWinBody.some((s) => String(s).trim() !== '');","tryCatchPattern":"try {\n  page = applyComparisonNarrative(page);\n} catch (e) {\n  if (String(e.message).includes('is missing whyWeWinBody')) {\n    console.error('Author whyWeWinBody paragraphs for:', e.message);\n  } else throw e;\n}","preventionTips":["Use a narrative template/checklist that includes whyWeWinBody as a required field.","Never leave the array empty as a placeholder; stub with a TODO string caught in review.","Add a CI content audit that lists all slugs missing required prose fields."],"tags":["build-time","content-validation","missing-content"],"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"}