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
- 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.
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
- Define competitor profiles via a factory that requires paragraphs at construction time.
- Never stub profiles with empty arrays in committed seeds.
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
- is missing methodology prose
- needs a World Monitor measurement explanation
- is missing following prose
- H2 "' + page.heading + '" has no following content
- Comparison measurement requires the captured snapshot date
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)