Skip to content

[quality] MDX wraps 13 multi-line <p> elements in a second paragraph: invalid nested <p> shipped on / and /architectures/ #381

Description

@hivecommons-hive

Finding

Thirteen JSX <p> elements in docs/ defer their content to the following
line. MDX v3 parses those children as markdown flow content and wraps them
in a paragraph of its own, so the server-rendered HTML is <p><p>…</p></p> —
nested paragraphs, which HTML does not permit.

The two affected pages are the site's homepage (/) and
/architectures/ — the two highest-traffic routes on the site.

docusaurus build reports this, and then succeeds anyway:

- "/endusers/":
  - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen
  ... (5 total)
- "/endusers/architectures/":
  - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen
  ... (8 total)

[SUCCESS] Generated static files in "build".

Because the diagnostics are non-fatal, ci.yml and deploy-gh-pages.yml stay
green and the invalid markup ships on every deploy.

What the browser actually receives

Source (docs/architectures/index.md:14-18):

<div className="pillar">
  <h3>Showcase your brand's engineering</h3>
  <p>
    Put your production work in front of the whole cloud native community —
    ...
  </p>
</div>

Built output at 00b44df:

<div class=pillar><h3>Showcase your brand's engineering</h3><p><p>Put your production work in front of the whole cloud native community — …<p></div>

A <p> may not contain a <p>, so every HTML parser applies the same recovery:
it closes the outer paragraph at the inner one and discards the now-unmatched
end tag. The DOM the browser builds therefore does not match the DOM the
author wrote — one paragraph becomes two siblings plus, on the homepage, a
stray empty <p></p>. Any .pillar p or .hero-tagline rule in
src/css/custom.css then applies to a different element set than intended,
and the empty paragraph collects that styling with no content in it.

Affected locations

All 13 are the same pattern — an opening <p …> tag with nothing after it on
its line.

file lines
docs/architectures/index.md 15, 23, 31, 40, 59, 66, 74, 81
docs/practitioners/index.md 13, 24, 31, 38, 45

These map 1:1 onto the 13 build diagnostics. Single-line
<p>text</p> elements elsewhere in the same files (for example
docs/practitioners/index.md:92-107 and all six in
docs/community/end-user-community.md) are correct and are not affected.

Why existing gates miss it

Recommendation

Two boxes. They must land in one PR: the guard test is red against main
until the content fix is applied.

  • Collapse each multi-line <p> onto a single line. The verified patch
    is below — it is content-only, changes no wording, and is
    prettier --check clean.
  • Add tests/mdx-paragraph-nesting.test.mjs so the pattern cannot
    return. Verified text is below.

Patch 1 of 2 — the content fix

diff --git a/docs/architectures/index.md b/docs/architectures/index.md
index 3597047..eb8d369 100644
--- a/docs/architectures/index.md
+++ b/docs/architectures/index.md
@@ -5,82 +5,53 @@ sidebar_position: 1
 
 # Reference architectures
 
-A growing catalog of real-world cloud native architectures contributed by CNCF end users. Browse by organization, industry, and the CNCF projects used in each architecture.
+A growing catalog of real-world cloud native architectures contributed by CNCF
+end users. Browse by organization, industry, and the CNCF projects used in each
+architecture.
 
 ## Why submit a reference architecture?
 
 <div className="pillars pillars-duo">
   <div className="pillar">
     <h3>Showcase your brand's engineering</h3>
-    <p>
-      Put your production work in front of the whole cloud native community —
-      shared architectures have helped earn organizations the Top End User Award.
-      You've already done the work — take your victory lap.
-    </p>
+    <p>Put your production work in front of the whole cloud native community — shared architectures have helped earn organizations the Top End User Award. You've already done the work — take your victory lap.</p>
   </div>
   <div className="pillar">
     <h3>Get expert review</h3>
-    <p>
-      Every submission is reviewed by the End User TAB and your peers, giving
-      your design vetted, independent feedback. Hundreds of collective years of
-      production experience lead to cheaper prototyping.
-    </p>
+    <p>Every submission is reviewed by the End User TAB and your peers, giving your design vetted, independent feedback. Hundreds of collective years of production experience lead to cheaper prototyping.</p>
   </div>
   <div className="pillar">
     <h3>Guide the projects you rely on</h3>
-    <p>
-      Real adoption patterns show maintainers how their projects are used in
-      production and help shape roadmaps around end user needs. The more you
-      participate, the more you influence — move the needle on the projects
-      your business depends on.
-    </p>
+    <p>Real adoption patterns show maintainers how their projects are used in production and help shape roadmaps around end user needs. The more you participate, the more you influence — move the needle on the projects your business depends on.</p>
   </div>
   <div className="pillar">
     <h3>Help the next adopter</h3>
-    <p>
-      A decade of shared knowledge has led to the immense scale of cloud native.
-      Your trade-offs and lessons learned become a practical map for teams
-      facing the same decisions — a contribution no code can replace. Take the
-      lessons of your peers back to your platform teams and continue the circle
-      of open source.
-    </p>
+    <p>A decade of shared knowledge has led to the immense scale of cloud native. Your trade-offs and lessons learned become a practical map for teams facing the same decisions — a contribution no code can replace. Take the lessons of your peers back to your platform teams and continue the circle of open source.</p>
   </div>
 </div>
 
 Organizations with community-accepted Reference Architectures are
-[automatically eligible](https://www.cncf.io/enduser-contributor/) for the End User
-Contributor program free of charge.
+[automatically eligible](https://www.cncf.io/enduser-contributor/) for the End
+User Contributor program free of charge.
 
 ## Benefits for your Organization
 
 <div className="benefit-cards">
   <div className="benefit">
     <h3>KubeCon + CloudNativeCon Keynote time</h3>
-    <p>
-      Tell your data-driven story from the biggest stage in open source — the
-      community celebrates its end users in front of thousands of practitioners.
-    </p>
+    <p>Tell your data-driven story from the biggest stage in open source — the community celebrates its end users in front of thousands of practitioners.</p>
   </div>
   <div className="benefit">
     <h3>Complimentary KubeCon passes</h3>
-    <p>
-      Bring the engineers behind your architecture to KubeCon + CloudNativeCon
-      to meet the maintainers and peers they work alongside. Celebrate our
-      collective operational successes, learn from the failures.
-    </p>
+    <p>Bring the engineers behind your architecture to KubeCon + CloudNativeCon to meet the maintainers and peers they work alongside. Celebrate our collective operational successes, learn from the failures.</p>
   </div>
   <div className="benefit">
     <h3>End User track talk selection</h3>
-    <p>
-      Coming soon: special consideration for production-grade stories to share
-      with the community.
-    </p>
+    <p>Coming soon: special consideration for production-grade stories to share with the community.</p>
   </div>
   <div className="benefit">
     <h3>CNCF marketing support</h3>
-    <p>
-      Celebrate your brand's success with full support from the CNCF Marketing team, backed by the Linux Foundation's global events.
-    </p>
+    <p>Celebrate your brand's success with full support from the CNCF Marketing team, backed by the Linux Foundation's global events.</p>
   </div>
 </div>
 
@@ -91,10 +62,14 @@ or start with the
 
 ## The catalog
 
-import ReferenceArchitectures from '@site/src/components/ReferenceArchitectures';
+import ReferenceArchitectures from
+'@site/src/components/ReferenceArchitectures';
 
 <ReferenceArchitectures />
 
 ## About this catalog
 
-Architecture content is imported from the [CNCF Cloud Native Reference Architecture project](https://github.com/cncf/architecture). Each architecture includes its source revision and attribution. Documentation is distributed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
+Architecture content is imported from the
+[CNCF Cloud Native Reference Architecture project](https://github.com/cncf/architecture).
+Each architecture includes its source revision and attribution. Documentation is
+distributed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
diff --git a/docs/practitioners/index.md b/docs/practitioners/index.md
index 96a1e96..04f6ff1 100644
--- a/docs/practitioners/index.md
+++ b/docs/practitioners/index.md
@@ -10,10 +10,7 @@ import ProjectsBorn from '@site/src/components/ProjectsBorn';
 
 # CNCF End Users
 
-<p className="hero-tagline">
-  Proven performance in production
-  <span>by the organizations running cloud native at scale</span>
-</p>
+<p className="hero-tagline">Proven performance in production <span>by the organizations running cloud native at scale</span></p>
 
 Welcome to the home of the **CNCF End User Community** — the practitioners,
 architects, and organizations running cloud native technologies in production.
@@ -21,31 +18,19 @@ architects, and organizations running cloud native technologies in production.
 <div className="pillars">
   <div className="pillar">
     <h3>Work with proven leaders</h3>
-    <p>
-      Maximize what open source can do for your business, alongside the
-      organizations already doing it at scale.
-    </p>
+    <p>Maximize what open source can do for your business, alongside the organizations already doing it at scale.</p>
   </div>
   <div className="pillar">
     <h3>Stay ahead of change</h3>
-    <p>
-      CNCF projects move at <a href="https://insights.linuxfoundation.org/collection/details/cncf">high velocity</a> —
-      end users keep their organizations in the know on modern infrastructure.
-    </p>
+    <p>CNCF projects move at <a href="https://insights.linuxfoundation.org/collection/details/cncf">high velocity</a> — end users keep their organizations in the know on modern infrastructure.</p>
   </div>
   <div className="pillar">
     <h3>Sustain open source</h3>
-    <p>
-      Pool resources with your peers to support the sustainable development of
-      the projects you depend on.
-    </p>
+    <p>Pool resources with your peers to support the sustainable development of the projects you depend on.</p>
   </div>
   <div className="pillar">
     <h3>Elevate your platform teams</h3>
-    <p>
-      A supportive, knowledge-focused community where teamwork and empathy
-      help your teams succeed.
-    </p>
+    <p>A supportive, knowledge-focused community where teamwork and empathy help your teams succeed.</p>
   </div>
 </div>
 
@@ -77,7 +62,11 @@ architects, and organizations running cloud native technologies in production.
 
 ## Member proof and recognition
 
-The End User Community is where production experience gets recognized. Recent [Top End User Award](/awards) winners include [SNCF](/awards), [Michelin](/awards), [ANT Group](/awards), [Adobe](/awards), and [CERN](/awards) — organizations that have shared architectures, case studies, and operational lessons with the wider community.
+The End User Community is where production experience gets recognized. Recent
+[Top End User Award](/awards) winners include [SNCF](/awards),
+[Michelin](/awards), [ANT Group](/awards), [Adobe](/awards), and [CERN](/awards)
+— organizations that have shared architectures, case studies, and operational
+lessons with the wider community.
 
 <ProjectsBorn
   title="Lead the way, set the industry standard"

Patch 2 of 2 — the guard test

tests/mdx-paragraph-nesting.test.mjs:

// Guards against JSX <p> elements in docs/ and blog/ whose content starts on a
// following line. MDX parses such children as markdown flow content and wraps
// them in a paragraph of its own, emitting <p><p>...</p></p> — invalid HTML
// that the HTML parser recovers from by splitting the paragraph and leaving a
// stray end tag. `docusaurus build` reports it only as a non-fatal minifier
// diagnostic, so the build stays green and nothing in the suite notices.
import assert from 'node:assert/strict';
import test from 'node:test';
import { readFileSync, readdirSync } from 'node:fs';
import { join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';

const repoRoot = fileURLToPath(new URL('..', import.meta.url));

function markdownFiles(dir) {
  const found = [];
  for (const entry of readdirSync(join(repoRoot, dir), {
    withFileTypes: true,
    recursive: true,
  })) {
    if (!entry.isFile()) continue;
    if (!/\.mdx?$/.test(entry.name)) continue;
    found.push(relative(repoRoot, join(entry.parentPath, entry.name)));
  }
  return found;
}

const files = [...markdownFiles('docs'), ...markdownFiles('blog')].sort();

// A `<p ...>` open tag with nothing but whitespace after it on the same line.
const MULTILINE_P = /^[ \t]*<p(?:\s[^>\n]*)?>[ \t]*$/;

function offendingLines(source) {
  return source
    .split('\n')
    .map((line, index) => [index + 1, line])
    .filter(([, line]) => MULTILINE_P.test(line))
    .map(([lineNumber]) => lineNumber);
}

test('the scan finds markdown sources to check', () => {
  assert.ok(
    files.length > 0,
    'no .md/.mdx files found under docs/ or blog/ — the assertions below would be vacuous',
  );
});

test('the detector matches a multi-line <p> and not a single-line one', () => {
  assert.deepEqual(offendingLines('<p>\n  text\n</p>\n'), [1]);
  assert.deepEqual(offendingLines('  <p className="x">\n  text\n  </p>\n'), [1]);
  assert.deepEqual(offendingLines('<p>text</p>\n'), []);
  assert.deepEqual(offendingLines('<p className="x">text</p>\n'), []);
});

test('no JSX <p> in docs/ or blog/ defers its content to the next line', () => {
  const offenders = [];
  for (const file of files) {
    for (const lineNumber of offendingLines(
      readFileSync(join(repoRoot, file), 'utf8'),
    )) {
      offenders.push(`${file}:${lineNumber}`);
    }
  }
  assert.deepEqual(
    offenders,
    [],
    `MDX wraps these <p> elements' content in a second paragraph, emitting invalid nested <p>. Put the content on the same line as the tag:\n${offenders.join('\n')}`,
  );
});

Evidence

  • Revision 00b44df, node v26.8.2, run locally 2026-09-21.
  • npx docusaurus build (SITE_URL=https://cncf.github.io BASE_URL=/endusers/)
    before the fix: 13 No "p" element in scope diagnostics (5 on
    /endusers/, 8 on /endusers/architectures/), exit [SUCCESS].
    Built HTML contains 12 literal <p><p> plus 1 stray <p></p>.
  • Same command after the fix: 0 diagnostics, 0 occurrences of <p><p>
    or <p></p> across every file in build/.
  • Guard test verified red on unfixed main — it names all 13 locations,
    matching the 13 diagnostics 1:1 — and green once the fix is applied. Its
    second test pins the detector itself against four inputs so a regex
    regression cannot make the scan silently vacuous.
  • Full suite with both patches: 58/58 pass (55 before, +3).
  • npx prettier --check clean on all three files.
  • Unit coverage: no test in tests/ reads docs/ element nesting or build/.
  • End-to-end coverage: package.json declares no playwright, cypress or
    puppeteer, so there is no e2e mechanism to query — the e2e status is
    unobtainable rather than absent. See [quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186. A rendered-DOM assertion is
    exactly the class of check that would have caught this at authoring time.

Priority

  • Impact: medium — invalid HTML on the site's two highest-traffic routes, with a
    real DOM/CSS divergence from what the author wrote, shipped silently on every
    deploy
  • Effort: low — content-only patch plus one test file, no new dependencies

Why there is no PR attached

The half that carries the fix is a change to docs/ content, which is
outside the quality lane's PR scope (tests only). The guard test cannot be
PR'd on its own because it is red against main by exactly the 13 locations
above, and splitting it out to make it mergeable would mean shipping a test
that asserts an invariant the repository violates. Both patches are given here
in full so applying them is mechanical.

— hive: agent=quality backend=copilot model=claude-opus-5

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityApproved by a Hive merger/owner for auto-merge on green CIhive/hosted-available-lke648397-260827-5n31Approved by a Hive merger/owner for auto-merge on green CIqualityApproved by a Hive merger/owner for auto-merge on green CItestingApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions