Skip to content

feat(core): SEO024–SEO027 — charset, image alt, hreflang, heading hierarchy - #59

Merged
oekazuma merged 4 commits into
mainfrom
feat/seo-static-gaps-phase-d
Jun 27, 2026
Merged

oekazuma merged 4 commits into
mainfrom
feat/seo-static-gaps-phase-d

Conversation

@oekazuma

@oekazuma oekazuma commented Jun 27, 2026 •

Copy link
Copy Markdown
Owner

SEO Phase D (after Phase B length, #58): close the remaining statically-analyzable SEO checks covered by claude-seo that fit svelte-vitals' design. claude-seo is a live, API/crawl/agent-driven audit suite — its CrUX/Lighthouse/GSC/GA4/backlinks/local/E-E-A-T coverage is out of scope for a dependency-free static linter. The overlap that does fit is four rules.

New rules

ID Check Severity Scope Modes
SEO024 Character encoding (<meta charset>) warning route rendered
SEO025 Image alt text warning route static (CLI)
SEO026 hreflang / x-default validity warning route both
SEO027 Heading hierarchy (single <h1>) warning route both

Each emits no signal when its subject is invisible to a mode (no false positives), following the existing appliesTo (viewport) and image-collection conventions.

How it works

  • SEO024 — charset lives in app.html, so rendered-only (appliesTo: rendered, like SEO014 viewport). Both parsers now model <meta charset> as name:'charset' (previously dropped — it has neither name nor property).
  • SEO025 — ImageInfo gains hasAlt; imageRule gains an optional category so this reports under seo. Empty alt="" passes (decorative); spread {...rest} is not flagged. CLI-only, like the perf image rules.
  • SEO026 — HeadTag gains hreflang, captured in both parsers. Opt-in: a route with no rel="alternate" hreflang emits nothing. Flags malformed codes and 2+ alternates without an x-default.
  • SEO027 — adds a page-body headings channel (ctx.headings) collected by both providers (CLI walks the AST; rendered queries the parsed body). Flags zero/multiple <h1>; layout-chain headings count.

Surfaces & docs

  • @svelte-vitals/core + svelte-vitals + @svelte-vitals/vite + @svelte-vitals/mcp minor (MCP surfaces them via allRules).
  • 8 rule reference pages (SEO024–027, en + ja). Design spec under docs/superpowers/specs/.

Validation

pnpm -r test (420: core 192 / vite 65 / cli 154 / mcp 9; +27), pnpm -r typecheck, pnpm lint, pnpm --filter docs build (79 pages incl. 8 new) — all green.

Two clean-page fixtures gained a valid <h1> + <meta charset> (they were genuinely flagged under the new rules); existing perf-image test fixtures gained hasAlt — additive, no assertions loosened.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added new SEO warnings for: missing <meta charset>, missing image alt, invalid hreflang (with x-default guidance), and heading hierarchy (exactly one <h1>, including layout chain).
    • Expanded analysis to include page-body heading detection for rule evaluation.
  • Bug Fixes
    • Improved static vs rendered reporting so charset, images, hreflang, and headings are checked in the correct mode with fewer false/misleading results.
  • Documentation
    • Added rule documentation for SEO024–SEO027 in multiple languages, including fixes and examples.

…rarchy

Phase D closes the remaining statically-analyzable SEO gaps vs claude-seo:

- SEO024 Character encoding: rendered-only <meta charset> presence (lives in
  app.html, like the viewport rule). Both parsers now model charset as
  name:'charset'.
- SEO025 Image alt text: flags <img> with no alt (empty alt="" passes;
  static/CLI mode only, like the perf image rules). ImageInfo gains hasAlt;
  imageRule takes an optional category so this reports under 'seo'.
- SEO026 hreflang validity: opt-in check of rel="alternate" hreflang alternates
  — malformed codes, or 2+ alternates without x-default. HeadTag gains hreflang,
  captured in both parsers.
- SEO027 Heading hierarchy: flags zero/multiple <h1> per page (layout-chain
  headings count). Adds a page-body headings channel (ctx.headings) collected by
  both providers.

Docs (en+ja) for all four, changeset (core/cli/vite/mcp minor), spec, and tests
(27 new). pnpm -r test 420 green; typecheck, lint, docs build (79 pages) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c9e5d92-335e-4430-9b6d-a3bc0a52d8fb

📥 Commits

Reviewing files that changed from the base of the PR and between 069e0db and ca86b1b.

📒 Files selected for processing (14)
  • docs/src/content/docs/ja/rules/seo026.md
  • docs/src/content/docs/ja/rules/seo027.md
  • docs/src/content/docs/rules/seo026.md
  • docs/src/content/docs/rules/seo027.md
  • docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md
  • packages/cli/src/providers/source/parse.ts
  • packages/cli/test/parse-static-gaps.test.ts
  • packages/core/src/rules/seo/seo026-hreflang.ts
  • packages/core/src/rules/seo/seo027-heading.ts
  • packages/core/test/seo-static-gaps-rules.test.ts
  • packages/vite/src/hooks/handle.ts
  • packages/vite/src/providers/rendered/parse-html.ts
  • packages/vite/test/dev-handle.test.ts
  • packages/vite/test/parse-html.test.ts
✅ Files skipped from review due to trivial changes (4)
  • docs/src/content/docs/rules/seo026.md
  • docs/src/content/docs/ja/rules/seo027.md
  • docs/src/content/docs/rules/seo027.md
  • docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/vite/test/parse-html.test.ts
  • docs/src/content/docs/ja/rules/seo026.md
  • packages/cli/test/parse-static-gaps.test.ts
  • packages/core/src/rules/seo/seo026-hreflang.ts
  • packages/core/src/rules/seo/seo027-heading.ts
  • packages/core/test/seo-static-gaps-rules.test.ts
  • packages/cli/src/providers/source/parse.ts

📝 Walkthrough

Walkthrough

Adds SEO024–SEO027 checks and the plumbing to capture charset, hreflang, image alt presence, and page headings in static and rendered analysis. The PR also registers the new rules, updates tests, and adds English and Japanese documentation.

Changes

SEO Phase D static gaps

Layer / File(s) Summary
Shared signal contracts and parsers
packages/core/src/head.ts, packages/core/src/headings.ts, packages/core/src/images.ts, packages/core/src/rule.ts, packages/cli/src/providers/source/parse.ts, packages/vite/src/providers/rendered/parse-html.ts, packages/cli/test/parse-static-gaps.test.ts, packages/vite/test/parse-html.test.ts
Core data shapes and both parsers now carry charset, hreflang, alt presence, and heading data.
Route and analysis wiring
packages/cli/src/providers/source/routes.ts, packages/vite/src/providers/rendered/collect.ts, packages/cli/src/index.ts, packages/vite/src/analyze.ts, packages/vite/src/hooks/handle.ts, packages/vite/test/dev-handle.test.ts
Static route collection and rendered collection now assemble per-route headings and pass them into rule execution.
SEO rules and registry
packages/core/src/rules/perf/image-rule.ts, packages/core/src/rules/seo/seo024-charset.ts, packages/core/src/rules/seo/seo025-image-alt.ts, packages/core/src/rules/seo/seo026-hreflang.ts, packages/core/src/rules/seo/seo027-heading.ts, packages/core/src/rules/index.ts, packages/core/src/index.ts, packages/core/test/perf-rules.test.ts, packages/core/test/seo-static-gaps-rules.test.ts
SEO024–SEO027 rule modules, shared image-rule categorization, registry exports, and rule tests are added.
Docs and release metadata
.changeset/seo-static-gaps-phase-d.md, docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md, docs/src/content/docs/rules/seo024.md, docs/src/content/docs/rules/seo025.md, docs/src/content/docs/rules/seo026.md, docs/src/content/docs/rules/seo027.md, docs/src/content/docs/ja/rules/seo024.md, docs/src/content/docs/ja/rules/seo025.md, docs/src/content/docs/ja/rules/seo026.md, docs/src/content/docs/ja/rules/seo027.md
The changeset, design spec, and English/Japanese docs describe the new rules and checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • oekazuma/svelte-vitals#26: Extends the rendered-analysis parsing and rule-input path in packages/vite/src/hooks/handle.ts, which this PR also updates for heading capture.
  • oekazuma/svelte-vitals#36: Shares the image parsing and ImageInfo/RuleContext.images path that this PR extends with hasAlt and SEO025.
  • oekazuma/svelte-vitals#49: Also modifies packages/vite/src/hooks/handle.ts’s analyzeAndWarn/rule-execution flow.

Poem

I hopped through heads and sniffed the light,
Found one <h1> standing right.
My carrots had alt tags, crisp and true,
And charset sparkled utf-8 in view.
Hreflang burrowed home—thump!—all set tonight. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change set: four new SEO rules for charset, image alt, hreflang, and heading hierarchy.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/seo-static-gaps-phase-d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/vite/test/parse-html.test.ts (1)

128-133: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: heading test doesn't distinguish document order from level-grouped order.

The rendered parseHtmlHead collects headings by iterating level 1→6, whereas the static collectHeadings walks the AST in document order. The <h1><h2><h2> input here yields [1,2,2] under both strategies, so this case can't catch the divergence. SEO027 only counts <h1>, so it's not a current defect — but if heading-hierarchy order ever matters, consider a case like <h2>…<h1> to lock the intended ordering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vite/test/parse-html.test.ts` around lines 128 - 133, The heading
test in parseHtmlHead currently uses an input whose result is the same whether
headings are collected in document order or grouped by level, so it does not
catch the ordering difference between parseHtmlHead and collectHeadings. Update
the SEO027 test case in the parse-html test to use a heading sequence where
order and level grouping diverge, such as a lower-level heading appearing before
a higher-level one, so the expectation clearly locks the intended document-order
behavior.
packages/vite/src/providers/rendered/parse-html.ts (1)

95-101: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Preserve document order when collecting headings.

This loop groups headings by level, so <h2>…</h2><h1>…</h1> becomes [1, 2] instead of [2, 1]. The source parser keeps document order, so this creates a provider-parity mismatch and can skew future hierarchy/skip-level checks. Traverse the DOM once in order instead of switching to a comma-separated selector; node-html-parser doesn’t reliably preserve order for grouped selectors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vite/src/providers/rendered/parse-html.ts` around lines 95 - 101,
Preserve heading document order in parseHtml’s heading collection logic: the
current loop in the headings scan groups by level and loses the source sequence,
causing provider-parity mismatches. Update the heading collection in
parse-html.ts to traverse the DOM once in order (rather than iterating
level-by-level with querySelectorAll), using the existing headings handling
around the Page-body headings SEO027 block so the collected array matches the
original document order.
docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md (1)

218-221: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the empty fenced code block at the end.

The trailing empty ``` block triggers markdownlint (MD040) and serves no purpose.

 ## Suggested delivery
 
 Two PRs to keep review tractable, or one if preferred:
 
 - **PR1 (head/image rules):** SEO024 charset, SEO025 image alt, SEO026 hreflang —
   small, additive capture extensions to existing channels.
 - **PR2 (body channel):** SEO027 heading hierarchy — introduces the new
   `ctx.headings` channel in both providers.
-
-```
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md` around
lines 218 - 221, Remove the trailing empty fenced code block from the document
so markdownlint MD040 no longer flags the file. Locate the stray fence at the
end of the spec content and delete the opening/closing backticks, leaving only
the intended prose in the section.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/rules/seo/seo026-hreflang.ts`:
- Around line 34-52: The SEO026 rule is losing the original offending <link>
tag, so failures are attributed to head.file instead of the real source. Update
seo026-hreflang’s tag collection and validation flow to retain the tag/file
metadata alongside each hreflang value, then set the result location from the
offending tag (for example, tag.file ?? head.file) when building the warning.
Keep the current detection/status behavior unchanged; only improve source
attribution in the SEO026 result object.
- Around line 9-15: Tighten SEO026 hreflang validation and preserve the
originating file for invalid alternates. Update isValidHreflang/HREFLANG_RE in
seo026-hreflang.ts so it only accepts lang, lang-REGION, or x-default, and
rejects script/extra-subtag forms like zh-Hant-TW. Also adjust the
alternate-validation path in SEO026’s rule logic to report the matched tag’s own
file instead of always defaulting to head.file, using the existing
alternate/matched tag symbols to locate the source correctly.

---

Nitpick comments:
In `@docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md`:
- Around line 218-221: Remove the trailing empty fenced code block from the
document so markdownlint MD040 no longer flags the file. Locate the stray fence
at the end of the spec content and delete the opening/closing backticks, leaving
only the intended prose in the section.

In `@packages/vite/src/providers/rendered/parse-html.ts`:
- Around line 95-101: Preserve heading document order in parseHtml’s heading
collection logic: the current loop in the headings scan groups by level and
loses the source sequence, causing provider-parity mismatches. Update the
heading collection in parse-html.ts to traverse the DOM once in order (rather
than iterating level-by-level with querySelectorAll), using the existing
headings handling around the Page-body headings SEO027 block so the collected
array matches the original document order.

In `@packages/vite/test/parse-html.test.ts`:
- Around line 128-133: The heading test in parseHtmlHead currently uses an input
whose result is the same whether headings are collected in document order or
grouped by level, so it does not catch the ordering difference between
parseHtmlHead and collectHeadings. Update the SEO027 test case in the parse-html
test to use a heading sequence where order and level grouping diverge, such as a
lower-level heading appearing before a higher-level one, so the expectation
clearly locks the intended document-order behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6ed44a6a-30cb-4b0e-9dac-0d96712b25f3

📥 Commits

Reviewing files that changed from the base of the PR and between 94839a2 and 069e0db.

📒 Files selected for processing (32)
  • .changeset/seo-static-gaps-phase-d.md
  • docs/src/content/docs/ja/rules/seo024.md
  • docs/src/content/docs/ja/rules/seo025.md
  • docs/src/content/docs/ja/rules/seo026.md
  • docs/src/content/docs/ja/rules/seo027.md
  • docs/src/content/docs/rules/seo024.md
  • docs/src/content/docs/rules/seo025.md
  • docs/src/content/docs/rules/seo026.md
  • docs/src/content/docs/rules/seo027.md
  • docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md
  • packages/cli/src/index.ts
  • packages/cli/src/providers/source/parse.ts
  • packages/cli/src/providers/source/routes.ts
  • packages/cli/test/parse-static-gaps.test.ts
  • packages/core/src/head.ts
  • packages/core/src/headings.ts
  • packages/core/src/images.ts
  • packages/core/src/index.ts
  • packages/core/src/rule.ts
  • packages/core/src/rules/index.ts
  • packages/core/src/rules/perf/image-rule.ts
  • packages/core/src/rules/seo/seo024-charset.ts
  • packages/core/src/rules/seo/seo025-image-alt.ts
  • packages/core/src/rules/seo/seo026-hreflang.ts
  • packages/core/src/rules/seo/seo027-heading.ts
  • packages/core/test/perf-rules.test.ts
  • packages/core/test/seo-static-gaps-rules.test.ts
  • packages/vite/src/analyze.ts
  • packages/vite/src/providers/rendered/collect.ts
  • packages/vite/src/providers/rendered/parse-html.ts
  • packages/vite/test/dev-handle.test.ts
  • packages/vite/test/parse-html.test.ts

Comment thread packages/core/src/rules/seo/seo026-hreflang.ts Outdated
Comment thread packages/core/src/rules/seo/seo026-hreflang.ts Outdated
oekazuma and others added 2 commits June 27, 2026 14:05
…g order

- SEO026: broaden hreflang regex to a sound BCP-47 subset (language + optional
  script + optional region incl. 3-digit UN M49 like es-419) instead of
  tightening — the prior pattern false-positived valid es-419 and the docs
  under-described accepted forms. Reconciles regex with docs/spec.
- SEO026: attribute a malformed-hreflang finding to the offending <link>'s own
  file (tag.file ?? head.file), matching the SEO022/023 convention, instead of
  always the route head file.
- parse-html (rendered): collect page-body headings via an ordered DOM walk so
  levels match the static provider's document order (level-grouping diverged for
  inputs like <h2>…<h1>).
- Tests: lock document order in the rendered heading test; add a BCP-47
  script/numeric-region acceptance test. Docs (en+ja) + spec wording aligned;
  drop a stray empty code fence in the spec.

pnpm -r test 421 green; typecheck, lint, docs build (79 pages) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mode

The dev `handle` discarded the `headings` parsed from the rendered HTML and
never passed them to runRules, so SEO027 (heading hierarchy) silently no-op'd
in the dev server even though the data was available. Wire the parsed heading
levels into the RuleContext, mirroring analyze.ts / collect.ts.

Add a dev-handle test that drives a two-<h1> page through the hook and asserts
SEO027 fires; document the static vs rendered heading-collection differences in
the SEO027 reference pages (en + ja).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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

Adds SEO Phase D coverage by introducing four new statically-analyzable SEO rules (SEO024–SEO027) and extending both static (CLI) and rendered (vite) analysis pipelines to capture the additional signals (charset, hreflang, image alt presence, and heading hierarchy), along with tests and documentation updates.

Changes:

  • Introduce new core SEO rules: charset presence (rendered-only), image alt presence (static-only), hreflang validation (both), and single-<h1> heading hierarchy (both).
  • Extend parsers/providers to capture <meta charset>, hreflang, <img> alt presence, and page-body heading levels; thread a new ctx.headings channel through analysis entrypoints.
  • Add/adjust unit tests, docs reference pages (EN/JA), and a changeset for minor releases.

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/vite/test/parse-html.test.ts Adds rendered-parser tests for charset/hreflang/headings capture.
packages/vite/test/dev-handle.test.ts Adds dev-hook coverage ensuring SEO027 can read rendered body headings.
packages/vite/src/providers/rendered/parse-html.ts Captures <meta charset>, hreflang, and collects heading levels from rendered HTML.
packages/vite/src/providers/rendered/collect.ts Threads rendered headings into collected analysis data.
packages/vite/src/hooks/handle.ts Threads rendered headings into runRules context for dev warnings.
packages/vite/src/analyze.ts Threads rendered headings into runRules context for prerender analysis.
packages/core/test/seo-static-gaps-rules.test.ts Adds unit tests for SEO024–SEO027 rule behavior and mode applicability.
packages/core/test/perf-rules.test.ts Updates perf test fixtures for new ImageInfo.hasAlt field.
packages/core/src/rules/seo/seo027-heading.ts Implements the single-<h1> route-scoped heading hierarchy rule.
packages/core/src/rules/seo/seo026-hreflang.ts Implements route-scoped hreflang validation with opt-in behavior.
packages/core/src/rules/seo/seo025-image-alt.ts Implements static-only image alt presence rule via imageRule.
packages/core/src/rules/seo/seo024-charset.ts Implements rendered-only charset presence rule via headTagRule.
packages/core/src/rules/perf/image-rule.ts Generalizes image-rule factory to allow non-performance categories (SEO025).
packages/core/src/rules/index.ts Registers and re-exports SEO024–SEO027 in allRules.
packages/core/src/rule.ts Extends RuleContext with optional headings channel.
packages/core/src/index.ts Exports new heading types and new SEO rules from the package entrypoint.
packages/core/src/images.ts Adds ImageInfo.hasAlt to support SEO025.
packages/core/src/headings.ts Introduces normalized heading types (HeadingInfo, ResolvedHeadings).
packages/core/src/head.ts Adds HeadTag.hreflang to support SEO026.
packages/cli/test/parse-static-gaps.test.ts Adds static-parser tests for charset/hreflang/alt/headings capture.
packages/cli/src/providers/source/routes.ts Threads per-route headings through the static provider (layout chain included).
packages/cli/src/providers/source/parse.ts Captures charset/hreflang/alt/headings from Svelte AST.
packages/cli/src/index.ts Threads static headings into runRules context.
docs/superpowers/specs/2026-06-26-seo-static-gaps-phase-d-design.md Adds design spec for Phase D rules and capture model changes.
docs/src/content/docs/rules/seo024.md Adds SEO024 rule documentation (EN).
docs/src/content/docs/rules/seo025.md Adds SEO025 rule documentation (EN).
docs/src/content/docs/rules/seo026.md Adds SEO026 rule documentation (EN).
docs/src/content/docs/rules/seo027.md Adds SEO027 rule documentation (EN).
docs/src/content/docs/ja/rules/seo024.md Adds SEO024 rule documentation (JA).
docs/src/content/docs/ja/rules/seo025.md Adds SEO025 rule documentation (JA).
docs/src/content/docs/ja/rules/seo026.md Adds SEO026 rule documentation (JA).
docs/src/content/docs/ja/rules/seo027.md Adds SEO027 rule documentation (JA).
.changeset/seo-static-gaps-phase-d.md Declares minor bumps for affected packages and summarizes additions.

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

Comment thread packages/vite/src/providers/rendered/parse-html.ts Outdated
Comment thread packages/cli/src/providers/source/parse.ts Outdated
Comment thread packages/vite/src/providers/rendered/parse-html.ts Outdated
Comment thread packages/cli/src/providers/source/parse.ts
Comment thread packages/core/src/rules/seo/seo026-hreflang.ts
Comment thread packages/core/src/rules/seo/seo026-hreflang.ts Outdated
Comment thread packages/core/src/rules/seo/seo027-heading.ts Outdated
…g scope

- SEO026: accept "x-default" case-insensitively (both the validity check and the
  multiple-alternates x-default presence check).
- hreflang: keep a literal empty hreflang="" (present-but-invalid) in both
  parsers instead of dropping it, so SEO026 flags it.
- headings: collect body headings only — the static collector now skips
  <svelte:head>, and the rendered collector scopes to <body> (fallback root for
  fragments) — so a stray <h1> in the head is not counted.
- SEO027: include the line number on a "Missing <h1>" finding when known
  (static mode), matching the multiple-<h1> branch.

Tests added for each (empty/uppercase hreflang, head-scoped headings). pnpm -r
test 428 green; typecheck, lint, docs build (79 pages) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oekazuma
oekazuma merged commit e7a56cf into main Jun 27, 2026
5 checks passed
@oekazuma
oekazuma deleted the feat/seo-static-gaps-phase-d branch June 27, 2026 05:40
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