Skip to content

feat(docs): site health + AI-citation fixes (A1, A2, A8 …) - #897

Open
dhananjay6561 wants to merge 47 commits into
keploy:mainfrom
dhananjay6561:feat/ai-citation-health
Open

feat(docs): site health + AI-citation fixes (A1, A2, A8 …)#897
dhananjay6561 wants to merge 47 commits into
keploy:mainfrom
dhananjay6561:feat/ai-citation-health

Conversation

@dhananjay6561

@dhananjay6561 dhananjay6561 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

Site Health + AI-citation / GEO overhaul for the docs site, from the Aug 2026 SEMrush audit (Site Health 84%, AI Search Health 84%) and the schema-type audit. Covers docs tickets A1–A10 / AI1–AI6 plus a full structured-data rework, prioritising template-level fixes that clear hundreds of pages at once.

Scope: docs repo only. Off main, independent of the Core Web Vitals PR (#896). Verified against the served version (v4.0.0).

Audit vs. live docs: the SEMrush export ran on an older main. Several flagged pages no longer exist in v4.0.0 (5 *-filter quickstarts, samples-mux/mysql/nextjs, agile-unit-testing, …), and several "thin" glossary pages are now 1.3k–2.4k words. Rows below reflect the live state.


📊 Ticket status

ID Area What Status
A1 / AI1 SEO AI image on Article schema — fixes ~146 "invalid structured data" ✅ Done
A2 SEO <title> differentiated from on-page <h1> (124 pages) ✅ Done
A3 / AI2 A11Y AI icon-link aria-labels ✅ Already satisfied in theme
A4 / AI5 SEO AI Cross-link pages with a single internal link — "Related" sections on ~190 pages (glossary + running-keploy + quickstart + keploy-cloud + keploy-explained + ci-cd + server) ✅ Done
A5 / AI3 SEO AI Expand thin pages ✅ Done (2 real thin pages expanded; flagged glossary already 1.3k–2.4k words; flagged filter pages deleted)
A7 SEO Unique meta descriptions ✅ Done (surviving pages covered; 4 no longer exist)
A8 A11Y SEO alt on 13 images; samples-echo triple-h1 → single h1 ✅ Done
A9 SEO httphttps doc links; auth-setup title-too-short + dup description ✅ Done
A10 SEO Legacy 1.0.0/2.0.0 are noIndex: true — intentional ✅ Verified, no action
Schema: specialize SEO AI Generic ArticleTechArticle + APIReference (0 generic Article remain) ✅ Done
Schema: @graph SEO AI ~6 fragmented Organization/WebSite/SoftwareApplication nodes → one @id graph ✅ Done
Schema: DefinedTerm SEO AI DefinedTerm on 37 glossary term pages, keyed to the hub's DefinedTermSet @id ✅ Done
Schema: bespoke pages SEO AI / (CollectionPage+ItemList), /about (AboutPage), /leadership (CollectionPage+Person), /security (WebPage+ContactPoint), /privacy-policy (DigitalDocument) ✅ Done
Schema: CI guard SEO scripts/verify-schema-graph.js fails on invalid JSON-LD / dangling @id ✅ Done
AI4: FAQPage AI FAQPage JSON-LD on the 3 FAQ docs ✅ Done
AI4: wider HowTo AI HowTo JSON-LD (visible={false}) on 10 more step-by-step guides (CI/CD, SDK install, Linux/Windows install) — 44 HowTo pages total ✅ Done
Schema: net-new (VideoObject / Course / Event / Offer / SoftwareSourceCode) SEO AI Targets are latent / noIndex / commented components — no rich-result payoff 🚫 Recommend skip
A6 PERF brotli/gzip ⚙️ Vercel/CDN host setting, not source

✅ Changes (highlights)

Structured data — one @id-linked entity graph

  • Each route emitted several disconnected Organization nodes (site-wide + SoftwareApplication.publisher + Article.author + Article.publisher), drifted to two logo URLs. Now declared once in src/schema/siteEntities.js with a stable @id and referenced by @id everywhere. docusaurus.config.js: three ld+json blocks → one @graph.
  • DocItem references the shared @ids; Article @id derives from the trailing-slash canonical URL so it matches other emitters. Added DefinedTerm on glossary term pages.
  • scripts/verify-schema-graph.js — CI guard against invalid JSON-LD / dangling @id.

Structured data — specialization & bespoke pages

  • API-reference docs → APIReference, all other docs → TechArticle; 0 generic Article.
  • A1 image on the Article schema (was computed but never emitted → ~146 "invalid").
  • Bespoke React pages went from zero page schema to CollectionPage/ItemList/AboutPage/WebPage+ContactPoint/DigitalDocument, all @id-linked to the site graph.

AI-citation / GEO

  • FAQPage remark plugin on the 3 FAQ docs.
  • Wider HowTovisible={false} HowTo on the CI/CD, SDK-install and OS-install guides (schema only, no visual change).
  • Cross-linking (A4) — a "Related" section (3–4 contextual internal links) on ~190 previously single-linked pages; glossary terms interlink as a "Related Terms" cluster. Link targets use each page's real route slug (which frequently differs from its filename), verified by a clean build.

SEO / a11y

  • A2 title suffix; A7 unique descriptions; A8 alt text + samples-echo single-h1; A9 https links + descriptive auth-setup title.

🧭 Notes / deviations

  • Net-new emitters recommended skip — VideoObject/Course/Event/Offer/SoftwareSourceCode target latent, commented, or noIndex components; emitting them adds schema with no citation/rich-result payoff.
  • Review/rating schema out of scope — self-hosted AggregateRating/Review violates Google policy; real ratings stay via Organization sameAs (G2/Gartner/Capterra). (Same call as perf(docs): core web vitals — self-host fonts, drop dead assets, defer pixel, add security headers #896.)
  • A6 is a Vercel/CDN host setting, not source.

✅ Verification

  • npm run build succeeds (onBrokenLinks: throw); changed files pass prettier 3.9.6.
  • node scripts/verify-schema-graph.js build (served versions; skips noIndex 1.0.0/2.0.0): 479 pages, 982 JSON-LD blocks, 0 invalid, 0 dangling. @type distribution includes TechArticle 174, HowTo 44, DefinedTerm 37, APIReference 31, FAQPage 3, CollectionPage 2, AboutPage/DigitalDocument/WebPage/DefinedTermSet 1 each, and 0 generic Article (all-versions counts are higher; 1.0.0/2.0.0 keep their own copies).
  • Recommend validating a doc page, the home page, a FAQ page and an install guide in the Rich Results Test, then re-running SEMrush Site Audit + AI Search Health to confirm the DoD (Site Health ≥ 90%, AI ≥ 92%).

⚠️ CI note — Vale doc linter failure is pre-existing, not this PR

GitHub's PR diff is v4-only, but Vale reports 364 errors in untouched 1.0.0/2.0.0/3.0.0 files. The old errata-ai/vale-action@v2.1.1 reviewdog only reads the first page of a large PR's changed-files API, so on this 186-file PR it stops diff-scoping and lints the whole versioned_docs tree's pre-existing spelling/style debt. Small fork PRs (#890, #891) pass Vale with the same config — the only difference is size. This PR's own added lines are clean. Fix: split into smaller PRs or bump the vale-action repo-side; no content change needed here.


🔄 Review iteration 1 — fixes pushed

Addressing the code-review pass. All verified against a clean npm run build +
node scripts/verify-schema-graph.js build (479 pages, 986 blocks, 0 invalid,
0 dangling, 0 generic Article, 3 FAQPage
).

Blockers

  • A1 image was a 404. The fallback keploy-hero.png hit the landing app's
    404 page, so Article schema (and every OG/social preview) still pointed at an
    unfetchable image. Committed a real 1200×630 card to static/img/ served
    from /docs/img/keploy-docs-card.png; repointed both the Article-schema
    fallback and the site-wide og:image.
  • CI guard ran nowhere. Added a verify-schema npm script and a build step
    in build_and_check.yml, so JSON-LD regressions now fail CI.
  • Leadership schema pointed at a 404. CollectionPage.url and the page
    permalink used /Leadership; corrected to the real /docs/leadership/
    route (also renamed the copy-pasted Security() component).

Important

  • FAQ answers were fusing list/table items into run-on text, and the
    ## Related section was being captured as a bogus Q&A. The remark plugin now
    separates list items, skips code/tables, and ignores the Related section.
  • A5/A7 re-scoped. The earlier "no longer exist" dismissal was wrong for the
    live pages (samples-mux/mysql/nextjs, golang/python/javascript sample
    indexes, agile-unit-testing) — they resolve because the renamed files kept
    their old id: slug. Fixed the concrete duplicate/generic descriptions
    (go-mux-mysql vs go-mux-sql shared one; agile-testing had glossary
    boilerplate). The thin sample-index pages (~80 words) are navigation stubs and
    left as-is.
  • A8 finished — added alt to the remaining 19 alt-less v4 images and gave
    RowOfImages alt props.
  • A2 refined — dropped the emoji titleDelimiter for a plain |, and the
    | Keploy Docs suffix is now appended only when the title stays within the
    60-char SERP limit.

Guard hardening / nits

  • Typed refs ({@type,@id}) are now validated (seeding each page's own og:url
    so mainEntityOfPage self-refs resolve); noindex pages detected from HTML
    instead of a hard-coded version list; fails on any generic Article.
  • SearchAction.target now carries the trailing slash; Related heading casing
    normalized; contradictory comments corrected.

Note on AI4 / FAQPage: Google restricted FAQ rich results to gov/health
sites in Aug 2023, so these no longer earn an FAQ SERP snippet — the real
payoff is clean AI/LLM answer extraction, which the readability fix above
enables. The ticket framing is updated to reflect this.


🔄 Review iteration 2 — fixes pushed

Heads-up: Iteration 2 was measured against 0295310f (the pre-iteration-1
head). The current branch head is different — all three "carried blockers" (A1
image, the CI guard, the /Leadership casing) and the carried 🟡s (FAQ
fusing, A8 alt, A2 delimiter) were already fixed in iteration 1 and are on
the branch now. The genuinely new findings are addressed below.

New findings fixed

  • APIReference over-classification. The api-testing/api tags and the
    api-testing- path prefix name the product feature, not the page genre, so
    ~28 task guides were typed APIReference (often contradicting their own HowTo
    block). Restricted to genuine reference docs — APIReference is now 2
    (public-api, cli-commands); the rest are TechArticle (now 157).
  • Article node had no @id. Added @id: <pageUrl>#article so the article
    is addressable in the @id graph (distinct from mainEntityOfPage.@id, the
    WebPage document). Corrects the earlier summary line.
  • A7 duplicates cleared. The Node.js sample trio (samples-node-mongo,
    node-express-mongoose, samples-express-mongoose) shared one description;
    each now has a distinct in-band one. Measured: 0 duplicate descriptions
    across all 205 v4 pages.
  • Length band. The two descriptions this PR itself edited that fell outside
    70–160 (general-glossary 167, api-testing-filter-suites 36) are now in
    band.

Withdrawn

  • The iteration-1 "Related heading casing" commit was reverted — the
    reviewer's own correction confirmed the lowercase Related guides/pages/ workflows headings are pre-existing in files this PR doesn't touch, so
    normalizing them was out of scope.

Deliberately out of scope

  • 55 pre-existing description-length outliers on pages this PR never edits.
    These are pre-existing SEMrush debt; rewriting 55 untouched files would be the
    same scope-creep as the reverted heading-casing change. Best handled as a
    dedicated description-hygiene pass. The 2 outliers this PR did touch are
    fixed above.

Verification (clean build): 479 pages, 986 JSON-LD blocks, 0 invalid, 0
dangling, 0 generic Article
; TechArticle 157, HowTo 44, DefinedTerm 37,
APIReference 2, FAQPage 3. The schema guard is stricter this round (typed refs
validated), so "0 dangling" now means more than it did.

DocItem computed socialImage but never included it in the Article/
TechArticle JSON-LD, so every doc page emitted an Article without the
required 'image' field -> ~146 pages flagged invalid structured data.

Add image to articleSchema, falling back to the site-wide default social
card (the same 1200x630 og:image from docusaurus.config.js) when a doc
sets no front-matter image.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
The swizzled DocItem set <title>{title}</title>, identical to the on-page
<h1> (also the doc title) -> SEMrush 'Duplicate content in h1 and title'
on 124 pages. Append ' | Keploy Docs' so the title differs from the h1.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Copilot AI lite review requested due to automatic review settings August 7, 2026 10:00
13 images across 8 doc pages (glossary GIFs + running-keploy screenshots)
had no alt attribute -> SEMrush 'Missing ALT' + weaker AI/accessibility
extraction. Add descriptive alt to each. utg-vscode-extension from the
audit list no longer exists on main, so it is not included.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Pre-existing whitespace/list-indent/final-newline issues in these two
files (unrelated to the alt fix) would fail the prettier CI check once the
files are touched. Apply prettier so the PR is clean.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

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

Updates the Docusaurus DocItem theme component to address SEO / AI-citation audit findings by improving structured data completeness and avoiding duplicate <title>/<h1> signals across docs pages.

Changes:

  • Adds an image field to the per-page Article/TechArticle JSON-LD (with a fallback when front matter has no image).
  • Appends a suffix to <title> to differentiate it from the on-page <h1>.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/theme/DocItem/index.js Outdated
Comment thread src/theme/DocItem/index.js Outdated
Wrap the long articleImage fallback line and flatten the pre-existing
nested-ternary indentation so the swizzled DocItem is clean under the
CI-pinned prettier 2.8.8 (--check runs on changed files).

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
The prettify_code workflow pins prettier_version 2.8.8, but creyD/
prettier_action's npx step actually installs and runs prettier 3.9.6.
3.x changed nested-ternary indentation, so the prior 2.8.8-flattened
schemaType ternary failed CI. Reformat to 3.9.6 (the version CI runs) so
--check passes; all other changed files are already 3.9.6-clean.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Every /docs page emitted a generic Article. Route API-reference pages
(api-testing-*, public-api, cli-commands, sdk-installation — by tag or
path) to APIReference, and default all other docs to TechArticle. Both are
Article subtypes accepting the same properties as articleSchema, so the
schema shape is unchanged — just a precise, AI/Google-friendly type.
Built HTML: 35 APIReference, 343 TechArticle, 0 generic Article from docs.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
general-glossary + 3 glossary terms shared one boilerplate description;
share-tests copied the generate-api-tests description; k8s-proxy had a
wrong (NextJS/Drizzle) description. Give each a unique, accurate one to
clear SEMrush 'Duplicate meta descriptions'.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Pre-existing leading-space paragraphs (unrelated to the description
change) would fail the prettier CI check once the file is touched.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
/leadership, /security and /privacy-policy emitted zero page-level schema.
Add JSON-LD: leadership -> CollectionPage + ItemList of Person (name,
photo, Twitter, worksFor Keploy); security -> WebPage + security
ContactPoint (responsible-disclosure email); privacy-policy ->
DigitalDocument with dateModified. Verified in built HTML.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
@dhananjay6561 dhananjay6561 self-assigned this Aug 7, 2026
New dependency-free remark plugin extracts each ### question + following
answer prose from FAQ docs (keploy-explained/*-faq) and injects FAQPage
JSON-LD with Question/acceptedAnswer pairs, so they're eligible for FAQ
rich results and AI extraction. Built HTML: FAQPage with 14/11/12 Q&A on
the three FAQ pages.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Every route emitted several disconnected Organization nodes: the site-wide
one plus copies inlined as SoftwareApplication.publisher, Article.author and
Article.publisher, which had drifted to two different logo URLs. Declare each
site-wide entity (Organization, WebSite, SoftwareApplication) once in
src/schema/siteEntities.js with a stable @id and reference it by @id
everywhere else, so consumers merge them into a single entity per page.

- docusaurus.config.js: three sibling ld+json blocks -> one @graph (siteGraph)
- DocItem: author/publisher/isPartOf reference the shared @ids; derive the
  Article @id from the trailing-slash canonical URL so it matches the URLs the
  glossary hub and bespoke pages emit; add a DefinedTerm on glossary term
  pages, keyed to the hub's DefinedTermSet @id (latest version only)
- bespoke pages: about -> AboutPage, index -> CollectionPage + ItemList
  (off GetStartedPaths' exported data), leadership -> CollectionPage of Person,
  privacy-policy -> DigitalDocument, security -> WebPage + Organization
  contactPoint on the shared Org @id
- glossary hub DefinedTerms gain the same @id as their term pages so the two
  listings resolve to one entity
- GlossaryCard uses Docusaurus Link (build-checked, trailingSlash-aware)
- scripts/verify-schema-graph.js: fail CI on invalid JSON-LD or dangling @id

Verified: clean build, verify-schema-graph reports 0 invalid / 0 dangling
across 479 pages / 972 blocks; no generic Article remains.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
- samples-echo: two body sections used a single `#`, producing three h1s on
  the page (title + two). Demote both to `##` so the page has one h1 and a
  clean heading hierarchy (A8, multiple-h1).
- configuration-file: rewrite four in-body doc links from http:// to https://
  (the two remaining http:// occurrences are regex examples in a code block
  and are left as-is) (A9, HTTP-link-on-HTTPS-site).
- api-testing-auth-setup: the title "Auth Setup" was flagged as too short and
  its description was a near-duplicate stub; give it a descriptive title and a
  unique description (sidebar_label stays "Auth Setup") (A9, title-too-short).

Verified: clean build (onBrokenLinks: throw), verify-schema-graph 0 invalid /
0 dangling.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
…inks

integrations-pr-agent was ~120 words (flagged "low word count"). Add a
"What the PR Agent does" section and a Related block linking the AI test
generator, UTG PR agent, and CI/CD GitHub setup, so the page carries real
content and more than one internal link (A4).

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
…d links

windows-wsl was ~177 words. Add why-WSL context, a Prerequisites section,
clearer install steps, and a Related block (macOS/Linux install, CLI
reference, common errors) to fix the thin-content and single-internal-link
flags (A5/A4).

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Glossary term pages each had only one internal link (SEMrush "pages with
only one internal link"). Add a "Related Terms" section to every term page
linking 3–4 conceptually related terms plus the glossary hub, so terms form
a connected cluster that both readers and AI engines can traverse.

Link targets use each term's real route slug (e.g. agile-testing.md serves
at /agile-unit-testing/, grey-box-testing.md at /gray-box-testing/), verified
by a clean build under onBrokenLinks: throw.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

@dhananjay6561 dhananjay6561 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Code review — feat/ai-citation-health (PR #897)

Reviewed following the four-phase process. This is a large but well-structured PR (+1075/−263, 61 files); the bulk is low-risk template/frontmatter work, and the risky part — a site-wide @id-linked entity graph — is exactly the right architecture. Consolidating the four drifting Organization nodes into one siteEntities.js source of truth referenced by @id, driving GET_STARTED_PATHS from the same data the cards render, and the defensive link-filter in glossary.js are all strong calls. Build passes under onBrokenLinks: throw and the schema verifier reports 0 invalid / 0 dangling.

No blocking issues. I left inline notes: one 🟡 on the CI guard's dangling-@id detection (a real coverage gap, though it doesn't affect this PR's current bare-ref schema), one 🟡 on a URL-casing mismatch in the leadership schema, plus a 💡 on FAQ answer-text quality and a 🟢 on a self-contradicting comment. None need to block merge.

🎉 src/schema/siteEntities.js + the @id derivation from the trailing-slash canonical in DocItem is the standout — it fixes the actual root cause (fragmented, drifted entities) rather than papering over the symptoms, and the verify-schema-graph.js guard to keep it that way is the right instinct.

Comment thread scripts/verify-schema-graph.js Outdated
Comment thread src/remark/remarkFaqSchema.js Outdated
Comment thread src/remark/remarkFaqSchema.js Outdated
Comment thread src/pages/leadership.js Outdated
Add a "Related" section (3–4 contextual internal links) to running-keploy feature docs that had 
only one internal link. Links use each page real route slug, verified by a clean build under 
onBrokenLinks: throw.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Add a "Related" section to quickstart sample-app guides, linking same-language / same-datastore 
samples and the language recommendation page. Routes verified against the build (slugs differ from 
filenames here).

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Add a "Related" section to keploy-cloud pages that had a single internal link, linking sibling 
cloud features. Build-verified routes.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Add a "Related" section to keploy-explained pages, linking sibling concept/FAQ/setup docs. 
Build-verified routes.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Cross-link the GitHub, GitLab and Jenkins CI guides to each other and to the API-test CI/CD setup. 
Build-verified routes.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Add a "Related" section to the install pages and language SDK pages, cross-linking the SDKs and 
install guides. Build-verified routes.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Emit schema.org HowTo JSON-LD (visible={false}, no visual change) on the GitHub, GitLab and Jenkins 
guides so AI engines can extract the install-and-run steps.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Emit HowTo JSON-LD (visible={false}) on the Go, Java, JavaScript and Python SDK pages covering 
agent setup and coverage merge steps.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Emit HowTo JSON-LD (visible={false}) on the Linux, Windows and native Windows/WSL install pages.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
The community section rendered six official channels with no structured data. Emit an ItemList 
built from the same data the cards render, so search engines and AI can enumerate the channels.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
The home, about, glossary and application-development pages each hand-built the same Home -> Docs 
breadcrumb prefix, repeating the site/docs URLs and the ListItem position numbering. Add 
breadcrumbList(trail) to siteEntities (the existing schema source of truth); callers pass only the 
crumbs beyond Docs. Removes the now-unused SITE/HOME_URL constants; built breadcrumbs are unchanged.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
@dhananjay6561
dhananjay6561 force-pushed the feat/ai-citation-health branch 2 times, most recently from f683b35 to 0295310 Compare August 15, 2026 10:24
@dhananjay6561

Copy link
Copy Markdown
Member Author

Note for reviewers — the failing Vale doc linter is a pre-existing, repo-wide issue, not this PR's content.

  • GitHub's PR diff is v4-only (186 files), yet Vale reports 364 errors in untouched version-1.0.0/2.0.0/3.0.0 files — those cannot come from this PR.
  • The old errata-ai/vale-action@v2.1.1 reviewdog only reads the first page of a PR's changed-files API. On a large (186-file) PR it can't map most files to the diff, gives up on diff-scoping, and lints the entire versioned_docs tree's pre-existing spelling/style debt (e.g. configration, PHPUnit, en-dashes that predate this PR).
  • Small fork PRs (feat: add Meta Pixel to docs site #890, feat: add llms markdown in doc pages #891) pass Vale with the same config — the only difference is PR size.
  • This PR's own added lines are clean (0 Vale errors locally; the only in-diff terms — gzip, Mux, Prisma, etc. — were added to the Vale accept-vocab).

Resolution: split into smaller PRs (each scopes correctly) or bump/adjust the vale-action repo-side. Nothing to change in this branch's content.

@amaan-bhati

Copy link
Copy Markdown
Member

Claude Review Skill, Iteration 1

Scope: full pass over 0295310f (187 files, +2473 / -510). Four-phase process: context, high level, line by line, summary. Every URL and count below was checked against the live site or the branch, not read off the description.

VERDICT: 🔄 REQUEST CHANGES. The @id consolidation is solid work and the hard parts are right. Three blockers: the headline ticket (A1) does not actually fix what it claims, the "CI guard" is wired to nothing, and one new schema node points at a 404.

Tally: 🔴 3 · 🟡 4 · 🟢 4 · 💡 2 · 📚 1 · 🎉 6


🔴 BLOCKING (3)

🔴 1. A1 is not fixed: the fallback image is a 404.

src/theme/DocItem/index.js sets the Article image to socialImage || "https://keploy.io/images/keploy-hero.png".

$ curl -sI https://keploy.io/images/keploy-hero.png
HTTP/2 404
content-type: text/html; charset=utf-8
content-length: 108217

That is the landing app's 404 page. The same URL is already the site-wide og:image at docusaurus.config.js:165, so it has been broken independently of this PR. But A1's stated payoff is "image on Article schema, fixes ~146 invalid structured data", and an unfetchable image does not clear that error. The ~146 pages stay invalid, just for a different reason.

Fix: commit a real 1200x630 card into this repo's static/img/ and point at ${SITE_URL}/docs/img/<card>.png, so it cannot break from a change in the landing repo. What currently resolves: /docs/img/favicon.png (200) and /images/keploy-logo-full.svg (200). Neither is a 1200x630 raster card, so neither is a real substitute. The broken og:image is worth fixing in the same commit.

🔴 2. The "CI guard" runs nowhere.

scripts/verify-schema-graph.js is referenced by nothing:

$ grep -rn verify-schema-graph .github/ package.json
(no match)

This PR changes no workflow file and no package.json. The ticket row still marks it ✅ Done as a CI guard that "fails on invalid JSON-LD / dangling @id". It is a manual script, so the 479-page / 982-block / 0-invalid figure is a one-time local measurement that nothing preserves. Add a package.json script and a step in build_and_check.yml after the build.

🔴 3. A new schema node points at a 404.

src/pages/leadership.js:31 emits url: ${DOCS_URL}Leadership/.

404  https://keploy.io/docs/Leadership/
200  https://keploy.io/docs/leadership/

The capital L matches the pre-existing <Layout permalink="/Leadership"> on line 51, so both want fixing. Note what this exposes: verify-schema-graph.js passes on this page, because it checks JSON validity and @id resolution and never whether a URL resolves.


🟡 IMPORTANT (4)

🟡 1. FAQPage answers come out with words fused together.
textOf in src/remark/remarkFaqSchema.js joins every array of children with "", and the only space inserted anywhere is between top-level block nodes. So list and table answers concatenate. From api-testing-faq.md Q1:

...Functional Tests – CRUD operations, endpoint validationEdge Case Tests – Invalid payloads, error responsesPerformance Tests – Response time checks...

Q3's markdown table collapses to HTTP/HTTPSJSON, XMLFull supportgRPCProtocol BuffersCode generation.... All 3 FAQ docs are list-based and one has a table, so this affects every emitted answer. AI4's entire value is machine extraction, so garbled answers make the ticket counterproductive rather than partial. Fix: join block children with a space, separate list items with ". ", and skip table and code nodes.

🟡 2. The A5 / A7 dismissals are wrong for 7 of the 9 pages named.
The summary says several flagged pages "no longer exist in v4.0.0 (5 *-filter quickstarts, samples-mux/mysql/nextjs, agile-unit-testing)". Live:

200  samples-mux      200  samples-mysql     200  samples-nextjs
200  golang-filter    200  python-filter     200  javascript-filter
200  agile-unit-testing
404  java-filter      404  csharp-filter

They resolve because the files were renamed while the front-matter id: kept the old slug (go-mux-mysql.md has id: samples-mysql, golang-recommendation.md has id: golang-filter). This PR's own ## Related sections link to those exact routes from 8+ files, which is the proof they are live. So the thin-content and duplicate-description work for those pages is still open, not moot. A5 and A7 need re-scoping against the real live set.

🟡 3. A8 leaves 19 raw <img> without alt on the served version.
13 fixed, but v4 still has 19 alt-less tags: running-keploy/utg-vsc-extension.md x4, hacktoberfest/non-code.md x4, hacktoberfest/contribution-guide.md x4, server/index.md x3, running-keploy/docker-tls.md, gsoc/contribution-guide.md, components/RowOfImages.js x2. None sit in files this PR touches, so the sweep was scoped to the audit list rather than the version. Either finish them or mark A8 partial.

🟡 4. A2 is half-done, and 16 titles cross the 60-char limit.
titleDelimiter: "🐰" (docusaurus.config.js:100) still governs the 6 React pages, so /docs/about/ ships this today:

About the Keploy Documentation 🐰 Keploy Documentation

Doc pages now get | Keploy Docs. That leaves two delimiters, two brand strings, and an emoji in SERP titles, on a PR whose ticket is title hygiene. Separately, the hardcoded 14-char suffix pushes 16 v4 docs from within 60 chars to over it (worst: Visual Regression Testing-Ensuring Pixel Perfect Experiences, 60 → 74). Since A9 explicitly tracked title length, this trades one SEMrush warning for another. Suggest appending only when it fits, and aligning the React pages.


🟢 NITS (4)

  • SearchAction.target redirects and loses the query. siteEntities.js:88 uses ${DOCS_URL}search?q={search_term_string}. curl -sI "https://keploy.io/docs/search?q=test" returns 302 with location: /docs/search/, query dropped. The same file's comment says hand-written paths omitting the slash "costs a redirect hop for users and makes JSON-LD URLs disagree with the canonical tag", so this is the one place withTrailingSlash was not applied. Use ${DOCS_URL}search/?q={search_term_string}.
  • Two new files make opposite claims about the same mechanism. remarkFaqSchema.js says a bare body <script> "is stripped" and must go in <head>, then the next comment says it emits into the body, which is what the code does and it works. It also says <script> is raw-text so JSON renders unescaped, while verify-schema-graph.js says React HTML-escapes it and unescapes before parsing. Both cannot be right. Harmless now, but the unescape pass would corrupt a description that legitimately contains &quot;.
  • The guard over-reports its own coverage. walk counts any node with more than one key as a definition, so a {"@id","@type"} stub masks a dangling ref. And inDefinedTermSet: TERMSET_ID is a plain string, so the cross-page pointer to the hub's DefinedTermSet is never counted as a reference at all. The string form is fine for schema.org, but "0 dangling" is narrower than the docstring's "every reference resolves on the same page".
  • ## Related heading casing is inconsistent. ## Related x130, ## Related Terms x37, ## Related Features x3, ## Related guides x3, ## Related pages x1, ## Related workflows x1. Lowercase guides / pages / workflows against Title Case elsewhere.

💡 SUGGESTIONS (2)

  • Make the guard assert presence, not just shape. Every figure the description quotes by hand (FAQPage 3, DefinedTerm 37, APIReference 31, generic Article 0) is exactly what a guard should pin. remarkFaqSchema wraps its whole body in a bare catch that swallows everything, so a parse regression emits nothing silently and today's guard still passes. Minimum counts plus Article === 0 would catch both.
  • Two small consistency items. src/pages/leadership.js:11 exports function Security(), copy-pasted from security.js. And SKIP_VERSIONS = ["/1.0.0/", "/2.0.0/"] in the guard duplicates the noIndex version list in the config, so it will drift the next time a version is archived. Derive it from the config instead.

📚 LEARNING (1)

Google restricted FAQ rich results to authoritative government and health sites in August 2023, so FAQPage no longer earns an FAQ snippet for a docs site. The comment in remarkFaqSchema.js and the AI4 row both say "eligible for FAQ rich results". The real payoff is AI and LLM extraction, which is precisely why 🟡1 decides whether AI4 pays off at all.


🎉 PRAISE (verified, not taken on trust)

  • The @id consolidation holds up. organizationRef and websiteRef resolve on every route because siteGraph ships via headTags, so those references genuinely are not dangling. Four Organization nodes with two drifting logo URLs down to one is a real fix.
  • The trailing-slash reasoning in DocItem is correct and subtle. metadata.permalink does omit the slash under trailingSlash: true, and every @id derived from it would have failed to merge with the hub's. Easy to miss, and the comment explains why.
  • The ## Related link targets survive the trap. I went looking for dead links here and found none. /docs/quickstart/samples-mysql/ comes from go-mux-mysql.md via id: samples-mysql; golang-filter from golang-recommendation.md. All resolve 200 on prod. Using route slugs over filenames across ~175 files is genuinely careful.
  • isGlossaryTerm gated on isLatestVersion, so v1 and v2 do not put three competing definitions of the same term into one term set. Good catch, and the comment says why.
  • 44 <HowTo visible={false}> pages, matching the claim exactly.
  • Everything else in the graph resolves: Organization logo 200, glossary hub 200, a sample term URL 200, /docs/about/ 200.

Verify before merge

  1. Rich Results Test on one TechArticle, one FAQ page, and /docs/leadership/, after the image and the Leadership casing are fixed.
  2. Re-run verify-schema-graph.js once it is in CI, and confirm the counts hold on a clean build.
  3. The Vale diagnosis (old vale-action paginating the changed-files API) is plausible but I did not confirm it. If the remedy is "split the PR", that is a merge decision rather than a footnote.

Reviewed with the code-review-skill four-phase process. Severity: 🔴 blocking · 🟡 important · 🟢 nit · 💡 suggestion · 📚 learning · 🎉 praise.

@amaan-bhati

Copy link
Copy Markdown
Member

Claude Review Skill, Iteration 2

Scope: no new commits since Iteration 1 (head is still 0295310f), so this is a deeper pass over what the first pass sampled rather than measured: the A7 descriptions across all 205 v4 pages, the 647 links in the Related sections, whether APIReference fits the pages it lands on, and whether isCategoryHub picks the right pages.

VERDICT: 🔄 REQUEST CHANGES (unchanged). The three Iteration 1 blockers are still open. Two new important findings, one correction to my own earlier review.

Tally: 🔴 3 carried · 🟡 2 new + 4 carried · 🟢 1 new · ✅ 1 correction


🟡 NEW 1. A7 is not done: 5 pages still share descriptions, and 2 of them are edited by this PR

Measured across all 205 v4 pages. Every page has a description (good), but two description values are duplicated:

Shared by Pages
x3 quickstart/node-express-mongoose.md, quickstart/samples-express-mongoose.md, quickstart/samples-node-mongo.md
x2 quickstart/go-mux-mysql.md, quickstart/go-mux-sql.md

go-mux-mysql.md and go-mux-sql.md are both in this PR's diff (they each got a ## Related section) and both still carry the identical description. So the two files were open, edited, and the A7 defect left in place while A7 is marked ✅ Done.

This is the same root cause as my Iteration 1 🟡2: go-mux-mysql.md carries id: samples-mysql, one of the pages the summary dismisses as "no longer exists". It does exist, it is live, and its duplicate description is exactly the thing A7 was meant to clear.

Length also still trips the audit that drove the ticket: 23 descriptions exceed 160 characters (worst: what-are-keploy-features.md at 312, what-is-a-keploy-ebpf.md at 304) and 34 fall under 70. SEMrush flags both bands, so A7 will resurface on the next run.

🟡 NEW 2. About 30 of the 32 APIReference pages are not API references

The isApi regex in src/theme/DocItem/index.js matches /running-keploy/(api-testing-|public-api|cli-commands). The api-testing- prefix names Keploy's product feature, not the page genre, so it sweeps in the whole UI how-to set:

  • api-testing-adding-labels : "Guide to creating and assigning labels to test suites individually or in bulk"
  • api-testing-chrome-extension : "Learn how to install, record, export, and auto-generate Keploy tests straight from your browser"
  • plus api-testing-buggy-suites, api-testing-fixing-ai, api-testing-run-report, and roughly 25 more of the same shape

schema.org defines APIReference as reference documentation for an API's endpoints and parameters. These are task guides. Of the 32 matches only public-api ("Use the Keploy Public API to manage apps… programmatically") and cli-commands genuinely qualify.

This matters more than a mislabel: the point of the specialization work is telling AI consumers what a page is. Typing 30 how-to guides as API reference tells them the opposite, and many of those pages already carry HowTo, so the two blocks now disagree with each other on the same page. Narrow the regex to public-api|cli-commands and let the rest fall through to TechArticle.


🟢 NEW: the Article node has no @id

The summary says "Article @id derives from the trailing-slash canonical URL so it matches other emitters." The articleSchema object has no @id and no url. What actually derives from the canonical is mainEntityOfPage.@id, plus the CollectionPage @id and the DefinedTerm #term id.

Nothing breaks, but the TechArticle is the one node in a graph built around @id addressability that has no identity, so nothing can ever point at it. Either add "@id": pageUrl + "#article" or correct the sentence.


✅ CORRECTION TO MY ITERATION 1 REVIEW

I flagged inconsistent ## Related heading casing (Related guides, Related pages, Related workflows against Title Case). That was wrong. This PR adds exactly two variants:

130  +## Related
 37  +## Related Terms

Related Features (x3), Related guides (x3), Related pages, and Related workflows are all pre-existing in files this PR does not touch. Disregard that nit; the PR's own convention is consistent.


🔴 CARRIED FROM ITERATION 1, ALL STILL OPEN

  1. A1 does not fix what it claims. The Article image fallback https://keploy.io/images/keploy-hero.png returns 404, so the ~146 pages stay invalid. Also still the broken site-wide og:image.
  2. The "CI guard" runs nowhere. scripts/verify-schema-graph.js is referenced by no workflow and no package.json.
  3. leadership.js:31 emits .../docs/Leadership/, which 404s; the live route is /docs/leadership/.

Carried 🟡: the FAQPage answers with fused words, the A5/A7 dismissal of 7 live pages, A8's 19 remaining alt-less <img>, and A2's titleDelimiter: "🐰" plus 16 titles pushed past 60 chars.


🎉 PRAISE (measured this pass, not sampled)

The Related sections hold up at full scale. I went looking for the usual failure modes across all 175 sections and 647 links and found none:

  • 0 self-links
  • 0 sections with a repeated target
  • link counts sit in a sane band: 3 links x65, 4 x82, 5 x23
  • 167 distinct targets, so the links spread rather than funnelling into a handful of pages (heaviest is the glossary hub at 37, which is correct for a "browse all terms" footer link)

For a mechanically applied change across 175 files, that is a better result than hand-written cross-links usually get.

isCategoryHub picks exactly the right pages. All five v4 index.md files it converts from Article to CollectionPage are genuine indexes: concepts/ 30 words, dependencies/ 34, operation/ 38, server/ 69, server/sdk-installation/ 135. No real content page is caught by it.


Verify before merge

  1. Rich Results Test on one TechArticle, one FAQ page, one APIReference, and /docs/leadership/, after the image, the regex, and the URL casing are fixed.
  2. Re-run verify-schema-graph.js once it is actually in CI.
  3. Re-run the SEMrush duplicate-description and description-length checks, since A7 will not clear as it stands.

Reviewed with the code-review-skill four-phase process. Severity: 🔴 blocking · 🟡 important · 🟢 nit · 💡 suggestion · 📚 learning · 🎉 praise.

The old fallback https://keploy.io/images/keploy-hero.png 404s (it hit the
landing app's 404 page), so every social preview and the Article-schema image
that reused it were broken. Commit a real 1200x630 card served from /docs/img/
so it can't break from a change in the landing repo.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
- Point og:image and the Article-schema image fallback at the repo-hosted
  /docs/img/keploy-docs-card.png instead of the 404ing landing-app URL.
- Drop the emoji title delimiter (put a rabbit in SERP titles) for a plain
  "|", and only append the "| Keploy Docs" suffix when the title stays within
  the 60-char SERP limit, so title hygiene doesn't create title-too-long.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
The verifier existed but nothing ran it, so its 0-invalid/0-dangling figures
were a one-time local measurement. Add a verify-schema npm script and a build
step so JSON-LD regressions fail CI.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
- Classify typed refs ({@type,@id}) as references, not definitions, so a typed
  pointer at an undefined @id is caught; seed each page's own og:url as defined
  so the self-referential mainEntityOfPage doesn't false-positive.
- Skip noindex pages via the built HTML instead of a hard-coded version list.
- Fail on any surviving generic Article node.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
The CollectionPage url and the Layout permalink used /Leadership (capital L),
but the route derived from leadership.js is /docs/leadership/ -- the capital
form 404s on case-sensitive hosting. Also rename the copy-pasted Security()
component to Leadership().

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
textOf concatenated list/table children with no separator, so list-based FAQ
answers fused into run-on text ("foobar") -- the exact thing AI extraction
reads. Separate list items as sentences, skip code/tables, and skip the
"## Related" section so its link list isn't captured as a bogus Q&A.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
${DOCS_URL}search?q=... 302s to /docs/search/ and drops the query; use the
canonical trailing-slash form.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
A8 sweep missed 19 alt-less images on the served version (VS Code extension
screenshots, hacktoberfest/server/gsoc icons, docker-tls). Add descriptive
alt, and give RowOfImages alt1/alt2 props so callers can supply it.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
go-mux-mysql and go-mux-sql shared an identical description, and agile-testing
carried the generic glossary boilerplate. These pages are live (renamed files
kept their old id: slug), so A7 was still open for them.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
@dhananjay6561
dhananjay6561 requested a review from nehagup as a code owner August 20, 2026 07:03
- Restrict APIReference to genuine API/CLI reference docs (public-api,
  cli-commands). The api-testing/api tags and the api-testing- path prefix name
  Keploy's product feature, not the page genre, and were typing ~28 task guides
  as APIReference -- often contradicting the HowTo block on the same page. Those
  now fall through to TechArticle.
- Give the Article node its own "@id" (pageUrl#article) so it is addressable in
  the @id graph, distinct from mainEntityOfPage's @id (the WebPage document).

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
samples-node-mongo, node-express-mongoose and samples-express-mongoose all
shared one boilerplate description; give each a distinct in-band description.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
general-glossary (was 167) and api-testing-filter-suites (was 36) had their
descriptions changed by this PR but landed outside SEMrush's length band.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
@dhananjay6561
dhananjay6561 force-pushed the feat/ai-citation-health branch from 66d6985 to ca85b25 Compare August 20, 2026 07:13
Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
@dhananjay6561

Copy link
Copy Markdown
Member Author

Thanks for the deep pass — the Related-sections and isCategoryHub measurements are appreciated. One important note up front, then a point-by-point.

This iteration was measured against a stale head

The review states "head is still 0295310f", but the branch head is now 7583f81a (iteration-1 + iteration-2 fixes, plus a main merge). Every 🔴 and most 🟡s below were already resolved on the current head — re-measuring against 7583f81a should clear them.

Verified just now on the current head: npm run build[SUCCESS], and node scripts/verify-schema-graph.js build479 pages, 986 blocks, 0 invalid, 0 dangling, 0 generic Article (TechArticle 157, APIReference 2, FAQPage 3).

Point by point

Finding Status on 7583f81a Where
🔴 A1 image is a 404 ✅ Fixed A real 1200×630 card is committed to static/img/keploy-docs-card.png; the Article-schema fallback and site-wide og:image both point at /docs/img/keploy-docs-card.png.
🔴 CI guard runs nowhere ✅ Fixed verify-schema npm script + a Verify structured-data graph step in build_and_check.yml.
🔴 leadership.js/Leadership/ 404 ✅ Fixed Now ${DOCS_URL}leadership/ and permalink="/leadership".
🟡 NEW 1 — duplicate descriptions ✅ Fixed The Node trio and go-mux-mysql/go-mux-sql now have distinct descriptions. Measured 0 duplicate description values across all 205 v4 pages.
🟡 NEW 2 — APIReference too broad ✅ Fixed Regex narrowed to /running-keploy/(public-api|cli-commands) and the api/api-testing tag triggers dropped. APIReference 30 → 2; the ~28 how-to guides are now TechArticle.
🟢 NEW — Article node has no @id ✅ Fixed Added "@id": <pageUrl>#article.
🟡 carried — FAQ fused answers ✅ Fixed List items separated, code/tables skipped, and the ## Related section is no longer captured as a bogus Q&A.
🟡 carried — A8 alt-less <img> ✅ Fixed 19 images + RowOfImages alt props.
🟡 carried — A2 emoji delimiter / >60-char titles ✅ Fixed titleDelimiter: "|"; the `
✅ your correction — Related heading casing ✅ Reverted Agreed — the iteration-1 heading-casing commit was dropped, since those lowercase variants are pre-existing in files this PR doesn't touch.

One thing left open by design

Description length flags 55 pre-existing outliers on pages this PR never edits (only 2 of the flagged 57 were touched here, and both are now in the 70–160 band). Rewriting 55 untouched files would be scope creep — that pre-existing SEMrush length debt is better handled as a dedicated description-hygiene pass, noted in the PR description.

Happy to re-request review once you've re-measured against 7583f81a.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
@dhananjay6561
dhananjay6561 force-pushed the feat/ai-citation-health branch from 7583f81 to e579122 Compare August 20, 2026 07:43
Google.EnDash flags every en-dash (–). Convert spaced prose separators to an
em-dash (—, the repo's convention; Google.EmDash is disabled) and numeric
ranges / compounds (3–5, per–test-set) to a hyphen. Clears all EnDash errors
across v2/v3/v4 docs and two src components.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Keep NDJSON as plain text instead of an external link.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Vale only lints *.md, so the en-dashes in QuickStartTabs.js and WhatIsKeploy.js
were never flagged; and version-2.0.0/api-test-generator.md carries pre-existing
prettier debt in main. Editing these three only pulled that debt into the PR and
broke prettier — revert them to main. The .md en-dash fixes for served v3/v4
content stay.

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
@amaan-bhati

Copy link
Copy Markdown
Member

Claude Review Skill, Iteration 3

Scope: the 17 commits after 0295310f. Diff vs the last reviewed head: 22 more files (187 to 209), 8 source files touched. Every fix below was verified against the branch or the CI log, not read off a commit message.

VERDICT: 🔄 REQUEST CHANGES. All 3 blockers and all 4 important findings from the previous rounds are genuinely fixed, several past what I asked for. Two new problems, both from the Vale remediation: CI is red, and the fix attempt broke the PR's stated scope.

Tally: 🔴 1 new · 🟡 1 new · 🟢 2 new · ✅ 11 fixed


✅ THE THREE BLOCKERS ARE CLOSED

1. A1's image. static/img/keploy-docs-card.png is committed at exactly 1200x630 (264 KB, confirmed with ffprobe). defaultSocialCard is built from siteConfig.url + baseUrl rather than a hardcoded host, and docusaurus.config.js now serves the same card as og:image. The landing-repo dependency that produced the 404 is gone from both places.

2. The guard runs. package.json gains verify-schema, and build_and_check.yml runs it as a Verify structured-data graph step straight after the build. From the passing CI log on this head:

> node scripts/verify-schema-graph.js build
Pages scanned:     479
Dangling @id refs: 0
Generic Article:   0

It is a guard now, not a local measurement.

3. Leadership. url: ${DOCS_URL}leadership/, permalink="/leadership", and the copy-pasted export default function Security() is now function Leadership().

✅ AND THE FOUR IMPORTANT ONES

Finding Verified state
FAQ answers with fused words textOf returns "" for code / table / html, joins list items with ". " (stripping a trailing period first), and joins listItem children with " "
A7 duplicate descriptions 0 duplicate values across all 205 v4 pages, 0 missing. All five previously-colliding pages now differ
A8 alt text 0 alt-less <img> left in v4 (was 19)
A2 titles titleDelimiter: "|" replaces the 🐰, and the suffix is appended only when title.length + 14 <= 60 and the title does not already end in the brand

Also fixed: isApi narrowed to public-api|cli-commands so ~28 task guides fall through to TechArticle; the Article node gains "@id": ${pageUrl}#article; SearchAction.target gains its trailing slash.


🔴 BLOCKING: Vale is red, and content edits cannot fix it

Your diagnosis in the PR body is correct, and I can now confirm it with numbers. Vale reports 198 distinct files. Cross-referencing against the PR's actual 209-file list:

  • 138 of those files are not in this PR's diff at all, including versioned_docs/version-1.0.0/** which this PR never touches
  • only 60 of the reported files are in the diff

At 209 changed files the diff is well past the 100-per-page changed-files API limit, so errata-ai/vale-action@v2.1.1 loses diff scoping and lints the repo's entire pre-existing spelling debt (prerequest x16, userId x14, PHPUnit x14, json x12, and so on). None of that is yours.

Nothing you write in these files closes it. The options are bumping vale-action repo-side, splitting the PR, or an explicit maintainer override. It just cannot be merged red, so it needs a decision rather than another content commit.

🟡 The Vale remediation is self-defeating and left the stated scope

The PR opens with "Verified against the served version (v4.0.0)." It now edits 11 files in version-2.0.0 and version-3.0.0, purely to swap en dashes for hyphens in prose:

- per–test-set table with time taken
+ per-test-set table with time taken

version-2.0.0 is noIndex: true, so those edits have no SEO or AI-citation value whatsoever. And they are counterproductive twice over: they took the diff from 187 to 209 files, pushing it further past the boundary that is causing the Vale failure they were meant to satisfy. bba636e2 already reverted part of this, which suggests you reached the same conclusion; the remaining 11 files should go the same way.

Revert them, keep the fix at the action level, and the PR gets both smaller and back inside its declared scope.


🟢 NEW (2)

🟢 1. One FAQ question is now silently dropped. Skipping table nodes is right, but api-testing-faq.md Q3, "What protocols and formats does Keploy support?", has a table as its entire answer. So current.answer.trim() is empty, the pair never reaches qa, and the question disappears from the FAQPage. I checked all three FAQ docs: it is 1 of 40 questions, and the other two files lose nothing. It is also arguably the single most citable answer on the page, since a protocol and format support matrix is exactly what an AI engine wants to quote. Either serialize table rows into a sentence ("HTTP/HTTPS supports JSON and XML with full support; gRPC supports Protocol Buffers with code generation; …") or add a one-line prose lead-in above the table, which helps human readers too.

🟢 2. title.length is unguarded while the same function guards title three times below. src/theme/DocItem/index.js:193 calls title.length and .test(title) unconditionally, but lines 248, 298 and 313 all gate on pageUrl && title, so the surrounding code treats title as possibly falsy. Latent rather than live (every doc has a title today and the build passes), but the old <title>{title}</title> was null-safe and this is not. (title?.length ?? 0) restores that.


🎉 PRAISE

The guard hardening went past what the finding asked for. I flagged that {"@type","@id"} stubs were being counted as definitions. That is fixed, and so are three things I only mentioned in passing:

  • noindex is now detected from the built HTML instead of a hardcoded ["1.0.0","2.0.0"] list, so it cannot drift when a version is archived
  • the docstring now states outright that the guard verifies JSON validity and @id resolution, not that every emitted url resolves, which is exactly the lesson from the /docs/Leadership/ blocker
  • the unescapeHtml comment now explains both cases correctly (Helmet escapes, the raw-text body <script> does not), resolving the contradiction between the two files

Seeding defined from og:url rather than the canonical link is the right call too, and the comment says why: a few docs set a cross-site canonical, while og:url is always the page's own trailing-slash URL.

You also found a defect I missed in two passes. ## Related is an h2, so the FAQ plugin was treating it as a Question and pulling the cross-link list into an acceptedAnswer. That is A4 and AI4 colliding with each other, it was live at the head I reviewed twice, and the /^related\b/i divider check is the right fix.


Verify before merge

  1. Rich Results Test on one TechArticle, one FAQ page, /docs/leadership/, and /docs/running-keploy/public-api/ (now one of only two APIReference pages).
  2. Confirm the new card renders in a social preview, since it is also the og:image for every doc without a front-matter image.
  3. Decide the Vale route (bump the action, split, or override) before anything else, since it gates the merge either way.

Reviewed with the code-review-skill four-phase process. Severity: 🔴 blocking · 🟡 important · 🟢 nit · 💡 suggestion · 📚 learning · 🎉 praise.

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.

3 participants