koala73/worldmonitor · error

profile "' + profile.name + '" has no prose

Error message

 profile "' + profile.name + '" has no prose

What it means

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.

Solutions

  1. Add a non-empty paragraphs array to the named competitor profile in competitorProfiles.
  2. Check for typos in the field name — it must be exactly paragraphs (optional chaining hides null/undefined until this check).
  3. Remove the stub profile entirely if it is not ready; partial profiles are not allowed.

Example fix

// before
competitorProfiles: [
  { name: 'Acme Risk', paragraphs: [] },
]
// after
competitorProfiles: [
  { name: 'Acme Risk', paragraphs: ['Acme Risk focuses on country risk scores with ...'] },
]
Defensive patterns

Strategy: validation

Validate before calling

page.competitorProfiles?.forEach((p) => {
  if (!Array.isArray(p.paragraphs) || p.paragraphs.length === 0) throw new Error(`profile ${p.name} needs paragraphs`);
});

Type guard

const hasProfileProse = (p) => Array.isArray(p?.paragraphs) && p.paragraphs.length > 0;

Prevention

When it happens

Trigger: A page.competitorProfiles entry has no paragraphs property or paragraphs: [] when renderKeywordSection runs during COMPARISON_PAGES construction.

Common situations: 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.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

function renderParagraphs(paragraphs, escapeHtml) {
  return (paragraphs ?? []).map((paragraph) => '      <p>' + escapeHtml(paragraph) + '</p>');
}

function renderHeadingSection(heading, paragraphs, escapeHtml, label) {
  if (!heading) return [];
  const body = renderParagraphs(paragraphs, escapeHtml);
  if (body.length === 0) {
    throw new Error((label || heading) + ' is missing following prose');
  }
  return ['      <h2>' + escapeHtml(heading) + '</h2>', ...body];
}

function renderKeywordSection(page, escapeHtml) {
  const headingProse = page.headingProse ?? [];
  const profiles = page.competitorProfiles ?? [];
  const profileBlocks = profiles.flatMap((profile) => {
    if (!profile.paragraphs?.length) {
      throw new Error(page.slug + ' profile "' + profile.name + '" has no prose');
    }
    const tag = page.heading ? 'h3' : 'h2';
    return [
      '      <' + tag + '>' + escapeHtml(profile.name) + '</' + tag + '>',
      ...renderParagraphs(profile.paragraphs, escapeHtml),
    ];
  });
  if (page.heading) {
    const inner = [...renderParagraphs(headingProse, escapeHtml), ...profileBlocks];
    if (inner.length === 0) {
      throw new Error(page.slug + ' H2 "' + page.heading + '" has no following content');
    }
    return ['      <h2>' + escapeHtml(page.heading) + '</h2>', ...inner];
  }
  return profileBlocks;
}

function renderMatrix(rows, escapeHtml) {

View on GitHub (pinned to 7d06c8633d)