Skip to content

DOCS-3014: Fix three defects in the llms.txt Markdown conversion - #2956

Merged
ctauchen merged 1 commit into
tigera:mainfrom
ctauchen:DOCS-3014-conversion-fixes
Aug 24, 2026
Merged

DOCS-3014: Fix three defects in the llms.txt Markdown conversion#2956
ctauchen merged 1 commit into
tigera:mainfrom
ctauchen:DOCS-3014-conversion-fixes

Conversation

@ctauchen

Copy link
Copy Markdown
Collaborator

First of seven PRs on DOCS-3014, adding .md twins for every docs URL. This one fixes defects in the existing HTML-to-Markdown converter so that later PRs build on correct output. It changes no doc content.

The converter reads rendered HTML and produces the Markdown in llms-full.txt. Three defects made that output worse than it needed to be, and each would be far more visible on a per-page .md file than it is buried in a 5 MB blob.

Heading anchors leaked into the text. Docusaurus appends a hash-link anchor containing a zero-width space to every heading, and the converter kept it, producing headings like "## Before you begin". There were 2,559 of these in calico/llms-full.txt alone. Fixed by stripping a.hash-link during extraction.

Code fences were double-spaced and lost their indentation. Prism renders each source line as a block-level token-line div that also ends in a br, so the line break is represented twice and hast-util-to-text honoured both. Fixed by demoting those divs to spans, so the br is the only break, and by passing whitespace: 'pre' so leading indentation survives. Two visible consequences:

  • YAML samples were not valid YAML, because every line was separated by a blank line and list indentation had been collapsed from two spaces to one. They are valid again.
  • Tabular command output, such as kubectl get nodes, had its columns collapsed to single spaces. The alignment is back.

Heading hierarchy was inverted. The page h1 lives in a header element that we strip, so page bodies start at h2, but they were then nested under an h3 doc title. Body headings are now shifted to sit below their doc title.

Fixing the hierarchy meant reducing the nesting depth, which is the one change here that goes beyond a straight bug fix. Under the old section-then-title structure the shift had to be two levels, and 69% of headings in these docs are already at h4 or deeper, so a two-level shift pushed nearly all of them onto h6 and flattened them into a single indistinguishable level. Each doc now gets one h2 in llms-full.txt and carries its section as a metadata line rather than a heading, so a one-level shift is enough and nothing collapses. A side benefit is that every doc now has a Source line, so a reader can tell which page a passage came from. Previously there was no way to know.

Verified against a full local build of all three products, 1,036 pages:

  • anchor artifacts in calico/llms-full.txt: 2,559 to 0
  • h1 headings per file: 0 to exactly 1
  • h6 headings in calico/llms-full.txt: 825 to 428, with h5 now a distinct level rather than absorbed
  • code fences: same number of fences and the same 383 non-blank code lines before and after, so nothing was dropped

The remaining heading-level jumps in the output come from source pages that skip a level themselves, mostly DocCardLink rendering an h5 under an h3. That is component markup, not conversion, and is out of scope here.

There is nothing to see on the deploy preview. Generation is still gated behind GENERATE_LLMS, which Netlify never sets, so the preview serves the unchanged copies committed under static/. That gate is removed in the next PR in the stack. To see the effect locally:

GENERATE_LLMS=true yarn build

Tests: adds 15 unit tests covering all three fixes, at src/plugins/docusaurus-plugin-llms-txt/test/conversion.test.js. These need Jest to transform the plugin's ESM-only dependencies (cheerio, unified, rehype, remark), so jest.config.mjs now sets transformIgnorePatterns to an empty array. That costs about two seconds on the full suite and avoids maintaining a brittle allowlist of the whole unified ecosystem. All 9 suites pass.

The converter turns rendered HTML into Markdown for llms-full.txt. Three
defects made that output worse than it needed to be, and all three would be
far more visible once we publish per-page Markdown.

Heading anchors leaked into headings. Docusaurus appends a hash-link anchor
containing a zero-width space to every heading, which the converter kept:
"## Before you begin[<zero-width space>](#before-you-begin)". There were 2,559
of these in calico/llms-full.txt alone. Strip a.hash-link during extraction.

Code fences were double-spaced and lost their indentation. Prism renders each
source line as a block-level token-line div that also ends in a <br>, so the
line break is represented twice and hast-util-to-text honoured both. Demote
those divs to spans so the <br> is the only break, and pass whitespace: 'pre'
so leading indentation and column alignment survive. This turns unusable YAML
samples back into valid YAML and restores the columns in kubectl output.

Heading hierarchy was inverted. The page <h1> lives in a <header> we strip, so
bodies start at h2, but they were nested under an h3 doc title. Shift body
headings to sit below their doc title.

Fixing the hierarchy meant reducing the nesting depth. Under the previous
section/title structure the shift needed to be two levels, which pushed 69% of
headings onto h6 and flattened them together. Each doc now gets a single h2 in
llms-full.txt, with its section carried as a metadata line instead of a
heading, so only one shift level is needed. That also gives every doc a Source
line, so a reader can tell which page a passage came from.

Verified against a full build: 2,559 anchor artifacts to 0, exactly one h1 per
file, and h6 headings down from 825 to 428 in calico/llms-full.txt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ctauchen
ctauchen requested a review from a team as a code owner August 24, 2026 14:52
Copilot AI lite review requested due to automatic review settings August 24, 2026 14:52
@netlify

netlify Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploy Preview for calico-docs-preview-next ready!

Name Link
🔨 Latest commit 009f1c1
🔍 Latest deploy log https://app.netlify.com/projects/calico-docs-preview-next/deploys/6a8c5acb6fb1ad0008a4dbf5
😎 Deploy Preview https://deploy-preview-2956--calico-docs-preview-next.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploy Preview succeeded!

Built without sensitive environment variables

Name Link
🔨 Latest commit 009f1c1
🔍 Latest deploy log https://app.netlify.com/projects/tigera/deploys/6a8c5acb8964400008f44739
😎 Deploy Preview https://deploy-preview-2956--tigera.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 72 (🔴 down 24 from production)
Accessibility: 98 (no change from production)
Best Practices: 83 (🔴 down 9 from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the docusaurus-plugin-llms-txt HTML→Markdown conversion pipeline so the generated llms-full.txt output is cleaner and structurally correct ahead of follow-on PRs that will emit per-page .md files.

Changes:

  • Strip Docusaurus heading “hash-link” anchors during HTML extraction.
  • Fix Prism-rendered code block conversion to preserve indentation and avoid double-spaced lines (hast-util-to-text with whitespace: 'pre' + preprocessing).
  • Restructure llms-full.txt generation to add per-doc Source: metadata and adjust body heading hierarchy via shiftHeadings(), plus add Jest unit tests and update Jest transforms for ESM deps.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/plugins/docusaurus-plugin-llms-txt/index.js Passes siteUrl into full-output generation to support per-doc Source: lines.
src/plugins/docusaurus-plugin-llms-txt/generate.js Adds shiftHeadings() and changes llms-full.txt layout to doc-title + metadata + shifted body headings.
src/plugins/docusaurus-plugin-llms-txt/extract.js Removes a.hash-link from extracted HTML and preprocesses Prism token lines before conversion.
src/plugins/docusaurus-plugin-llms-txt/convert.js Preserves code whitespace by using toText(..., { whitespace: 'pre' }) for code blocks.
src/plugins/docusaurus-plugin-llms-txt/test/conversion.test.js Adds unit tests covering anchor stripping, code fence formatting/indentation, and heading shifting.
jest.config.mjs Configures Jest to transform ESM-only dependencies by not ignoring node_modules.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +37 to +42
const marker = fenceMatch[1];
if (openFence === null) {
openFence = marker[0];
} else if (marker[0] === openFence) {
openFence = null;
}
@ctauchen
ctauchen merged commit e1c71e3 into tigera:main Aug 24, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants