🍕 feat(seo): collapsible JSON-LD viewer + inline validator#8
Merged
Conversation
The JSON-LD row used to be a one-liner that just said "N block(s)" — you had no way to see what was actually in the structured data without opening DevTools. This adds a proper viewer: - A new "Structured data (JSON-LD)" section under the social previews, one card per <script type="application/ld+json"> block on the page. - Each card uses a native <details>/<summary> for accessibility (works with keyboard, screen readers, and the browser's reveal-on-find). - Collapsed header shows: block index, schema.org @type (or types joined with " / "), the most useful name field (headline / name / title / url), and a copy button. - Expanded body shows the full JSON pretty-printed with the same syntax highlighting as the JSON tab. The <pre> is only rendered once the user opens the card so big @graph payloads don't run the highlighter regex if no one looks at them. - Invalid/unparseable blocks (the ones the extractor flags with __invalid: true) get a red border + raw-text fallback view + a clear "this isn't valid JSON" header. - New summarizeJsonLd() helper handles the common shapes: - bare @type: "Article" → "Article" - @type array: ["Article","NewsArticle"] → "Article / NewsArticle" - Yoast/Rank-Math @graph → "@graph (Article, BreadcrumbList, WebSite) · 3 items" - top-level array of entities → "Array (Person)" - missing @type → "Untyped object" - long names truncated with an ellipsis at 80 chars Refactor: - Extracted the syntax-highlight regex from JsonPreview.tsx into a new src/lib/json-highlight.ts module so the SEO tab and the JSON tab share one implementation. JsonPreview now imports from there. Bug fix bundled with the extraction: - The original highlightJson() had a dead-code branch for keys: it used a zero-width lookahead (?=\s*:) to detect them, then checked match.endsWith(':') in the callback — which never fired because the colon was outside the match. As a result, the .cs-json-key CSS class defined in styles.css was never actually applied; every key rendered with the same green as string values. Captured the colon inside the match so keys now correctly pick up the accent color in both the JSON tab and the new JSON-LD viewer. Tests: 11 new (7 for summarizeJsonLd covering all the shapes above + 4 for highlightJson covering the key-detection fix and XSS escape). Full suite: 95/95. Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a focused schema.org validator that runs over every JSON-LD block on the page and surfaces issues directly inside the new collapsible cards. The goal is "high signal for news/CMS pages" — not a complete schema.org validator, but it catches the bugs that actually break Google rich results in the wild. What's checked Universal - missing-context error: block has no @context - invalid-context error: @context isn't a schema.org URL (catches typos like https://shema.org) - missing-type warn: entity has no @type Article / NewsArticle / BlogPosting / etc. - article-missing-headline error: required for Google rich results - article-headline-long warn: > 110 chars (Google's limit) - article-missing-image error: required for Google rich results - article-image-relative warn: image URL must be absolute (works across string, ImageObject, and array forms; reports the precise sub-path) - article-missing-date-published error - article-bad-date-published warn: not ISO 8601 - article-bad-date-modified warn: not ISO 8601 - article-modified-before-published warn: copy-paste bug - article-missing-author warn: recommended - article-missing-publisher info: recommended BreadcrumbList - breadcrumb-empty warn: no itemListElement - breadcrumb-missing-position warn: per item - breadcrumb-missing-name warn: per item - breadcrumb-missing-item info: per item (final crumb may omit) - breadcrumb-bad-positions warn: positions aren't 1, 2, … N Cross-block - duplicate-id warn: same @id appears in 2+ blocks (Google may merge or drop entities) The walker recurses into @graph (Yoast / Rank-Math style) and bare arrays so every inner entity gets its own checks, with paths like "@graph[1].headline" or "itemListElement[2]" attached to each issue. UI - Per-card severity badge in the collapsed summary (red ✕ N for errors, yellow ! N for warnings, blue i N for notes). - Card border picks up red/yellow when issues are present. - Cards with errors auto-open on first render so problems are visible without an extra click. - Issues list rendered above the JSON pre when expanded, reusing the existing .cs-seo-issue tone classes for visual consistency. Each row shows the message + the precise dotted path to the offending field in monospace. - Section header gains aggregate counters: "3 blocks · ✕ 2 errors · ! 4 warnings". Tests: 18 new (every check above + @graph recursion + cross-block @id dedup behavior + skip-invalid-blocks). Full suite: 113/113. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The SEO tab's JSON-LD row used to be a one-liner that just said "2 block(s)". You had no way to see what was in the structured data, and no way to tell whether it was valid. This PR turns that line into a proper inspector and validator.
What's new
1. Collapsible JSON-LD viewer
A new "Structured data (JSON-LD)" section under the social previews with one card per
<script type="application/ld+json">block on the page.<details>/<summary>for accessibility (keyboard, screen readers, browser find-in-page).@type(or types joined with/), the most useful name field (`headline` → `name` → `title` → `url`), and a copy button.2. Inline JSON-LD validator
A focused schema.org linter that runs over every block and surfaces issues directly inside each card. Goal: high signal for news/CMS pages, not a full schema.org validator. Catches the bugs that actually break Google rich results in the wild.
Checks
The walker recurses into `@graph` (Yoast / Rank-Math style) and bare arrays so every inner entity gets its own checks. Each issue carries a precise dotted path like `@graph[1].headline` or `itemListElement[2].item`.
UI integration
3. Refactor + bonus bug fix (carried over from earlier in this PR)
Test plan
Files
Commits