koala73/worldmonitor · error

whyWeWinBody must not repeat whyWeWin

Error message

 whyWeWinBody must not repeat whyWeWin

What it means

applyComparisonNarrative rejects narratives whose whyWeWinBody, after whitespace normalization, is identical to the whyWeWin summary text. The body must be expanded prose distinct from the headline claim, so verbatim duplication is treated as an authoring mistake.

Solutions

  1. Replace whyWeWinBody content with distinct expanded paragraphs, not the whyWeWin sentence.
  2. If the text was duplicated as a placeholder, author real supporting prose.
  3. Compare both strings normalized the same way the script does before committing.
  4. Keep whyWeWin as a short claim and whyWeWinBody as 1-3 supporting paragraphs.

Example fix

// before
whyWeWin: 'One map for every feed.',
whyWeWinBody: ['One map for every feed.']
// after
whyWeWin: 'One map for every feed.',
whyWeWinBody: ['WorldMonitor overlays maritime, aviation and conflict data so analysts stop switching tabs.']
Defensive patterns

Strategy: validation

Validate before calling

const norm = (s) => String(s).replace(/\s+/g, ' ').trim();
if (norm((narrative.whyWeWinBody ?? []).join(' ')) === norm(narrative.whyWeWin ?? '')) throw new Error('whyWeWinBody repeats whyWeWin');

Type guard

const bodyDistinct = (n) => norm((n.whyWeWinBody ?? []).join(' ')) !== norm(n.whyWeWin ?? '');

Try / catch

try {
  page = applyComparisonNarrative(page);
} catch (e) {
  if (String(e.message).includes('must not repeat whyWeWin')) {
    console.error('Write distinct body prose for:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Running the build when String(whyWeWinBody joined) === String(whyWeWin) after both are collapsed to single spaces and trimmed — typically because whyWeWinBody was set to [whyWeWinText].

Common situations: Copy-pasting the whyWeWin sentence into whyWeWinBody as a placeholder; a generator/template that emits the same string into both fields.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/053dcd7e58241849. Report an issue: GitHub.

Appendix: source

Thrown at scripts/build-comparison-pages.mjs:468

  if (faqs.length < 8 || faqs.length > 12) {
    throw new Error(page.slug + ' FAQ count must be 8-12, got ' + faqs.length);
  }
  const faqNames = new Set();
  for (const [question] of faqs) {
    const key = String(question).trim().toLowerCase();
    if (faqNames.has(key)) {
      throw new Error(page.slug + ' duplicate FAQ question: ' + question);
    }
    faqNames.add(key);
  }
  const merged = { ...page, ...narrative, faqs };
  delete merged.extraFaqs;
  const whyBody = (merged.whyWeWinBody ?? []).join(' ').replace(/\s+/g, ' ').trim();
  if (!whyBody) {
    throw new Error(page.slug + ' is missing whyWeWinBody');
  }
  if (whyBody === merged.whyWeWin.replace(/\s+/g, ' ').trim()) {
    throw new Error(page.slug + ' whyWeWinBody must not repeat whyWeWin');
  }
  if (merged.heading && !(merged.headingProse?.length || merged.competitorProfiles?.length)) {
    throw new Error(page.slug + ' H2 "' + merged.heading + '" has no following prose');
  }
  if (!merged.evaluationHeading || !merged.evaluationProse?.length) {
    throw new Error(page.slug + ' is missing the evaluation section');
  }
  if (!merged.switchHeading || !merged.switchProse?.length) {
    throw new Error(page.slug + ' is missing the switch-trigger section');
  }
  if (!merged.methodologyProse?.length) {
    throw new Error(page.slug + ' is missing methodology prose');
  }
  return merged;
}

export const COMPARISON_PAGES = COMPARISON_PAGE_SEEDS.map(applyComparisonNarrative);

View on GitHub (pinned to 7d06c8633d)