Skip to content

Feat SEO Section (Overview, Heading Structure, Links & JSON LD Preview)#416

Open
abedshaaban wants to merge 33 commits intoTanStack:mainfrom
abedshaaban:feat/seo-overview
Open

Feat SEO Section (Overview, Heading Structure, Links & JSON LD Preview)#416
abedshaaban wants to merge 33 commits intoTanStack:mainfrom
abedshaaban:feat/seo-overview

Conversation

@abedshaaban
Copy link
Copy Markdown
Contributor

@abedshaaban abedshaaban commented Apr 6, 2026

🎯 Changes

Introduced new tabs in the SEO section:

  • SEO Overview
  • Heading Structure
  • Links Preview
  • JSON-LD Preview

(Sorry for the low quality video but GitHub didn't let me upload the high quality one)

SEO-tab-pr.mp4

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

Release Notes

New Features

  • Added SEO tab with live head-driven social and search result previews
  • Added heading structure analyzer with hierarchy validation
  • Added links preview with classification and validation
  • Added JSON-LD structured data viewer and validator
  • Added SEO health overview dashboard with aggregated scoring and issue tracking

abedshaaban and others added 30 commits April 1, 2026 01:02
…functionality

This commit introduces a new README.md file for the SEO tab in the devtools package. It outlines the purpose of the SEO tab, including its major features such as Social Previews, SERP Previews, JSON-LD Previews, and more. Each section provides an overview of functionality, data sources, and how the previews are rendered, enhancing the documentation for better user understanding.
…structure, and links preview

This commit introduces several new sections to the SEO tab in the devtools package, enhancing its functionality. The new features include:

- **JSON-LD Preview**: Parses and validates JSON-LD scripts on the page, providing detailed feedback on required and recommended attributes.
- **Heading Structure Preview**: Analyzes heading tags (`h1` to `h6`) for hierarchy and common issues, ensuring proper SEO practices.
- **Links Preview**: Scans all links on the page, classifying them as internal, external, or invalid, and reports on accessibility and SEO-related issues.

Additionally, the SEO tab navigation has been updated to include these new sections, improving user experience and accessibility of SEO insights.
This commit refactors the SEO tab components to standardize the handling of severity levels for issues. The `Severity` type has been replaced with `SeoSeverity`, and the `severityColor` function has been removed in favor of a centralized `seoSeverityColor` function. This change improves code consistency and maintainability across the `canonical-url-preview`, `heading-structure-preview`, `json-ld-preview`, and `links-preview` components, ensuring a unified approach to displaying issue severity in the SEO analysis features.
This commit adds a canonical link and robots meta tag to the basic example's HTML file, improving SEO capabilities. Additionally, it refactors the SEO tab components to utilize the `Show` component for conditional rendering of issues, enhancing the user experience by only displaying relevant information when applicable. This change streamlines the presentation of SEO analysis results across the canonical URL, heading structure, and links preview sections.
…lysis

This commit adds a new SEO overview section to the devtools package, aggregating insights from various SEO components including canonical URLs, social previews, SERP previews, JSON-LD, heading structure, and links. It implements a health scoring system to provide a quick assessment of SEO status, highlighting issues and offering hints for improvement. Additionally, it refactors existing components to enhance data handling and presentation, improving the overall user experience in the SEO tab.
…reporting

This commit introduces new styles for the SEO tab components, improving the visual presentation of SEO analysis results. It adds structured issue reporting for SEO elements, including headings, JSON-LD, and links, utilizing a consistent design for severity indicators. Additionally, it refactors existing components to enhance readability and maintainability, ensuring a cohesive user experience across the SEO tab.
This commit introduces new styles for the SEO tab components, including enhanced visual presentation for SEO analysis results. It refactors the handling of severity indicators across various sections, such as headings, JSON-LD, and links, utilizing a consistent design approach. Additionally, it improves the structure and readability of the code, ensuring a cohesive user experience throughout the SEO tab.
…ization

This commit enhances the SEO tab by updating styles for the health score indicators, including a new design for the health track and fill elements. It refactors the health score rendering logic to utilize a more consistent approach across components, improving accessibility with ARIA attributes. Additionally, it introduces a sorting function for links in the report, ensuring a clearer display order based on link types. These changes aim to provide a more cohesive and visually appealing user experience in the SEO analysis features.
This commit enhances the LinksPreviewSection by introducing an accordion-style layout for displaying links, allowing users to expand and collapse groups of links categorized by type (internal, external, non-web, invalid). It adds new styles for the accordion components, improving the visual organization of link reports. Additionally, it refactors the existing link rendering logic to accommodate the new structure, enhancing user experience and accessibility in the SEO analysis features.
…on features

This commit introduces new styles for the JSON-LD preview component, improving the visual presentation of structured data. It adds functionality for validating supported schema types and enhances the display of entity previews, including detailed rows for required and recommended fields. Additionally, it refactors the health scoring system to account for missing schema attributes, providing clearer insights into SEO performance. These changes aim to improve user experience and accessibility in the SEO tab.
…tures

This commit introduces a comprehensive update to the SEO overview section, adding a scoring system for subsections based on issue severity. It includes new styles for the score ring visualization, improving the presentation of SEO health metrics. Additionally, it refactors the issue reporting logic to provide clearer insights into the status of SEO elements, enhancing user experience and accessibility in the SEO tab.
…links preview in SEO tab

This commit enhances the SEO tab by introducing new navigation buttons for 'Heading Structure' and 'Links Preview', allowing users to easily switch between these views. It also updates the display logic to show the corresponding sections when selected, improving the overall user experience and accessibility of SEO insights. The SEO overview section has been adjusted to maintain a cohesive structure.
…and scrollbar customization

This commit updates the styles for the seoSubNav component, adding responsive design features for smaller screens, including horizontal scrolling and custom scrollbar styles. It also ensures that the seoSubNavLabel maintains proper layout with flex properties, enhancing the overall user experience in the SEO tab.
…inks preview functionality

This commit modifies the package.json to improve testing scripts by adding a command to clear the NX daemon and updating the size limit for the devtools package. Additionally, it refactors the JSON-LD and links preview components to enhance readability and maintainability, including changes to function declarations and formatting for better code clarity. These updates aim to improve the overall user experience and accessibility in the SEO tab.
… tab components

This commit refactors the SEO tab components by cleaning up imports related to severity handling and ensuring consistent text handling by removing unnecessary nullish coalescing and optional chaining. These changes enhance code readability and maintainability across the heading structure, JSON-LD, and links preview components.
…ew component

This commit refactors the classifyLink function in the links preview component by removing unnecessary checks for non-web links and the 'nofollow' issue reporting. It enhances the handling of relative paths and same-document fragments to align with browser behavior, improving code clarity and maintainability in the SEO tab.
…README

This commit removes the unused 'seoOverviewFootnote' style and its corresponding JSX element from the SEO overview section. Additionally, it updates the README to streamline the description of checks included in the SEO tab, enhancing clarity and conciseness. These changes improve code maintainability and documentation accuracy.
This commit modifies the size limit for the devtools package in package.json, increasing the limit from 60 KB to 69 KB. This change reflects adjustments in the package's size requirements, ensuring accurate size tracking for future development.
… in SEO tab components

This commit updates the SEO tab components by standardizing the capitalization of section titles and improving code formatting for better readability. Changes include updating button labels to 'SEO Overview' and 'Social Previews', as well as enhancing the structure of JSX elements for consistency. These adjustments aim to enhance the overall clarity and maintainability of the code.
This commit modifies the titles of the 'Links' and 'JSON-LD' sections in the SEO overview to 'Links Preview' and 'JSON-LD Preview', respectively. These changes aim to enhance clarity and consistency in the presentation of SEO insights, aligning with previous updates to standardize capitalization and improve formatting across the SEO tab components.
…ed data analysis

This commit adds a new SEO tab in the devtools, featuring live head-driven social and SERP previews, structured data (JSON-LD) analysis, heading and link assessments, and an overview that scores and links to each section. This enhancement aims to provide users with comprehensive SEO insights and improve the overall functionality of the devtools.
…nonicalPageData

This commit modifies the export statements for the CanonicalPageIssue and CanonicalPageData types in the SEO tab components, changing them from 'export type' to 'type'. This adjustment aims to streamline the code structure and improve consistency in type declarations across the module.
…link and improving robots handling

This commit removes the canonical link from the basic example HTML file and updates the robots handling logic in the canonical URL data module. The changes include refining the conditions for indexability and follow directives, ensuring more accurate SEO assessments. Additionally, the links preview component is updated to enforce the inclusion of both 'noopener' and 'noreferrer' for external links with target='_blank'. These adjustments aim to improve the overall functionality and security of the SEO tab.
…tion

This commit introduces a new hook, useLocationChanges, that allows components to react to changes in the browser's location. The hook sets up listeners for pushState, replaceState, and popstate events, enabling efficient updates when the URL changes. Additionally, it integrates with the SEO tab components to enhance responsiveness to location changes, improving user experience and functionality.
This commit refactors the links-preview component by consolidating import statements for better clarity and organization. The countBySeverity function and SeoSectionSummary type are now imported separately, enhancing code readability and maintainability.
This commit updates the JSON-LD analysis function to ensure that it handles cases where the script content is null or empty. By using optional chaining and providing a default empty string, the function now avoids potential errors and improves robustness in processing JSON-LD scripts.
…mponents

This commit updates the json-ld-preview and links-preview components by removing optional chaining from the textContent property. This change ensures that the textContent is always trimmed, improving the handling of empty strings and enhancing the robustness of the SEO tab components.
… preview text handling

This commit updates the max-width values for certain styles in the use-styles.ts file, increasing the desktop max-width to 620px and decreasing the mobile max-width to 328px. Additionally, it introduces new functions in serp-preview.tsx for measuring text width and truncating text based on width and line limits, improving the handling of SERP previews for better SEO representation.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

TanStack Devtools receives a new comprehensive SEO analysis tab with live head-driven previews, structured data validation, heading hierarchy analysis, link classification, and an aggregated health dashboard. The feature includes multiple analysis modules, styles, and location-change tracking hook support.

Changes

Cohort / File(s) Summary
SEO Tab Core
packages/devtools/src/tabs/seo-tab/index.tsx, seo-section-summary.ts, seo-severity.ts
Multi-view SEO tab restructured with shared data types, issue severity tracking, and health score utilities enabling modular analysis subsections.
SEO Analysis Modules
packages/devtools/src/tabs/seo-tab/canonical-url-data.ts, heading-structure-preview.tsx, json-ld-preview.tsx, links-preview.tsx
New specialized analyzers for canonical URLs/robots meta, H1–H6 hierarchy validation, JSON-LD schema compliance (supports WebSite, Organization, Article, etc.), and internal/external link classification with issue reporting.
SEO Overview & Aggregation
packages/devtools/src/tabs/seo-tab/seo-overview.tsx
Dashboard component computing aggregated SEO health score (0–100), severity counts, and severity-based subsection buttons linking to detail views.
Enhanced Preview Components
packages/devtools/src/tabs/seo-tab/serp-preview.tsx, social-previews.tsx
SERP preview now uses canvas-based pixel-width text measurement and wrapping; social previews refactored to use typed accent system and summary generation.
Supporting Infrastructure
packages/devtools/src/styles/use-styles.ts, packages/devtools/src/hooks/use-location-changes.ts, .changeset/puny-games-bow.md, examples/react/basic/index.html, package.json
Extensive SEO-related style exports (840+ lines), new Solid.js hook for tracking location changes, changeset entry, example HTML with robots meta and JSON-LD markup, and devtools bundle size limit increased from 60 KB to 69 KB.

Sequence Diagram

sequenceDiagram
    participant User
    participant SeoOverviewSection
    participant HeadChangeListener
    participant LocationChangeListener
    participant AnalysisModules as Analysis Modules<br/>(Canonical, Heading, Links, JSON-LD, Social, SERP)
    participant HealthAggregator
    participant DOM

    User->>SeoOverviewSection: Mount/Navigate
    SeoOverviewSection->>HeadChangeListener: Register head change callback
    SeoOverviewSection->>LocationChangeListener: Register location change callback
    
    User->>DOM: Navigate or modify page
    DOM->>LocationChangeListener: Fire location change event
    LocationChangeListener->>SeoOverviewSection: Notify onChange
    
    SeoOverviewSection->>DOM: Scan for meta tags & elements
    SeoOverviewSection->>AnalysisModules: Call multiple analyze functions
    AnalysisModules->>DOM: Extract & validate<br/>(canonical, headings, links,<br/>JSON-LD, social, SERP)
    AnalysisModules-->>SeoOverviewSection: Return section summaries
    
    SeoOverviewSection->>HealthAggregator: Aggregate all summaries
    HealthAggregator-->>SeoOverviewSection: Compute overall health score<br/>& severity counts
    
    SeoOverviewSection->>User: Render health ring, subsection<br/>buttons with severity indicators
    User->>SeoOverviewSection: Click subsection button
    SeoOverviewSection->>User: Display detail view<br/>(e.g., JsonLdPreviewSection)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop and bound through SEO dreams,
Where canonical links and JSON schemas gleam,
Health scores ring and links align,
A devtools tab that's so divine!
With headings structured, pure and neat,
This SEO feature can't be beat! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat SEO Section (Overview, Heading Structure, Links & JSON LD Preview)' is clear and specific, accurately summarizing the main feature additions of the pull request.
Description check ✅ Passed The PR description includes the required 'Changes' and 'Checklist' sections with details about the new tabs introduced and confirmation of local testing and changeset generation, though the 'Contributing guide' checkbox remains unchecked.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Apr 6, 2026

View your CI Pipeline Execution ↗ for commit 9e1ab03

Command Status Duration Result
nx affected --targets=test:eslint,test:sherif,t... ✅ Succeeded 2m 25s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-06 07:25:08 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 6, 2026

More templates

@tanstack/devtools

npm i https://pkg.pr.new/@tanstack/devtools@416

@tanstack/devtools-a11y

npm i https://pkg.pr.new/@tanstack/devtools-a11y@416

@tanstack/devtools-client

npm i https://pkg.pr.new/@tanstack/devtools-client@416

@tanstack/devtools-ui

npm i https://pkg.pr.new/@tanstack/devtools-ui@416

@tanstack/devtools-utils

npm i https://pkg.pr.new/@tanstack/devtools-utils@416

@tanstack/devtools-vite

npm i https://pkg.pr.new/@tanstack/devtools-vite@416

@tanstack/devtools-event-bus

npm i https://pkg.pr.new/@tanstack/devtools-event-bus@416

@tanstack/devtools-event-client

npm i https://pkg.pr.new/@tanstack/devtools-event-client@416

@tanstack/preact-devtools

npm i https://pkg.pr.new/@tanstack/preact-devtools@416

@tanstack/react-devtools

npm i https://pkg.pr.new/@tanstack/react-devtools@416

@tanstack/solid-devtools

npm i https://pkg.pr.new/@tanstack/solid-devtools@416

@tanstack/vue-devtools

npm i https://pkg.pr.new/@tanstack/vue-devtools@416

commit: 9e1ab03

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/devtools/src/styles/use-styles.ts (1)

288-295: ⚠️ Potential issue | 🟠 Major

Keep the hidden mobile measurement width in sync with the rendered card.

Line 293 shrinks the mobile snippet to 328px, but serpMeasureHiddenMobile is still 340px later in this file. The truncation logic will now measure against the old layout, so the mobile preview can disagree with what users see.

🧩 Suggested fix
-      width: 340px;
+      width: 288px;

Also applies to: 375-384

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/styles/use-styles.ts` around lines 288 - 295, The
mobile preview width constant is out of sync: update the hidden measurement used
by serpMeasureHiddenMobile to match the rendered card width in serpSnippetMobile
(change 340px to 328px or better unify both to a single shared constant), so the
truncation logic measures against the actual max-width; locate serpSnippetMobile
and serpMeasureHiddenMobile in use-styles.ts and make them derive from the same
value (or replace the hard-coded 340px with 328px).
packages/devtools/src/tabs/seo-tab/serp-preview.tsx (1)

525-530: ⚠️ Potential issue | 🟠 Major

Refresh the SERP snapshot on client-side navigation.

getSerpFromHead() includes window.location.href, but this component only resamples on head mutations. Route changes that leave the head untouched will keep showing the previous URL until the section remounts.

Proposed fix
 import { useHeadChanges } from '../../hooks/use-head-changes'
+import { useLocationChanges } from '../../hooks/use-location-changes'
...
   useHeadChanges(() => {
     setSerp(getSerpFromHead())
   })
+
+  useLocationChanges(() => {
+    setSerp(getSerpFromHead())
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/serp-preview.tsx` around lines 525 - 530,
The component SerpPreviewSection only updates serp via useHeadChanges, so
getSerpFromHead() (which reads window.location.href) is stale on client-side
navigation; modify SerpPreviewSection to also listen for client navigation
events and call setSerp(getSerpFromHead())—add listeners for 'popstate' and a
custom 'locationchange' fired from patched
history.pushState/history.replaceState (or listen for a router navigation event
if available), ensure you register these listeners on mount and remove them on
cleanup; keep useHeadChanges behavior intact and reference SerpPreviewSection,
getSerpFromHead, useHeadChanges, and setSerp when implementing the fix.
🧹 Nitpick comments (1)
packages/devtools/src/tabs/seo-tab/links-preview.tsx (1)

109-113: Reuse TANSTACK_DEVTOOLS for the self-filter.

This filter is what keeps the report from counting the devtools' own links, but it currently relies on a duplicated data-testid literal and a fallback selector that is not wired to the current root element. Importing the shared constant here avoids that drift.

Proposed refactor
+import { TANSTACK_DEVTOOLS } from '../../utils/storage'
...
   return anchors
     .filter(
       (anchor) =>
-        !anchor.closest('[data-testid="tanstack_devtools"]') &&
-        !anchor.closest('[data-devtools-root]'),
+        !anchor.closest(`[data-testid="${TANSTACK_DEVTOOLS}"]`),
     )
     .map(classifyLink)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/links-preview.tsx` around lines 109 - 113,
Replace the hard-coded '[data-testid="tanstack_devtools"]' & the ad-hoc
'[data-devtools-root]' fallback in the anchor filter with the shared
TANSTACK_DEVTOOLS selector: import the TANSTACK_DEVTOOLS constant at the top of
links-preview.tsx and use it in the filter that currently checks
anchor.closest(...). Remove the duplicated literal and the fallback selector so
the filter reads something like anchor.closest(TANSTACK_DEVTOOLS) to reliably
exclude the devtools' own links.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/react/basic/index.html`:
- Around line 36-37: The canonical link in examples/react/basic/index.html
currently hard-codes "http://localhost:3005" which will mislead crawlers; update
the <link rel="canonical"> element (or remove it) so it does not point to
localhost—either replace the hard-coded URL with the deployed example's public
URL or remove the canonical tag entirely from the fixture; ensure the change is
applied to the <link rel="canonical"> element so the example no longer signals a
localhost origin.

In `@packages/devtools/src/hooks/use-location-changes.ts`:
- Around line 28-56: The teardown unconditionally restores
originalPushState/originalReplaceState which can clobber newer wrappers; instead
capture the wrapper functions when installing them (e.g. assign wrappedPushState
and wrappedReplaceState when you set window.history.pushState/replaceState) and,
in teardownLocationObservation, only restore the originals if the current
window.history.pushState === wrappedPushState (and similarly for replaceState);
leave the current values alone otherwise and still remove the event listeners
and clear teardownLocationObservation.

In `@packages/devtools/src/tabs/seo-tab/heading-structure-preview.tsx`:
- Around line 141-166: HeadingStructurePreviewSection currently computes
headings and issues once via extractHeadings() and validateHeadings(), causing
stale results after client-side navigation; subscribe to the location-change
signal (useLocationChanges()) and re-run the scan by moving headings/issues into
state or memo and recomputing them inside an effect or memo that depends on the
location-change value (and optionally a mutation observer trigger for body
changes). Specifically, call useLocationChanges() in
HeadingStructurePreviewSection, then in a useEffect or useMemo triggered by that
value, call extractHeadings() and validateHeadings(headings) and update local
state (or return memoized values) so the UI reflects the current route (and
consider adding a small debounce if needed for rapid mutations).

In `@packages/devtools/src/tabs/seo-tab/json-ld-preview.tsx`:
- Around line 204-216: The current code builds allowedSet from rules +
RESERVED_KEYS and treats any extra top-level keys in entity as a warning
(unknownKeys -> issues.push), which incorrectly penalizes valid but unlisted
schema properties; remove or disable this warning: delete the unknownKeys check
and the issues.push block (or gate it behind an explicit opt-in feature flag),
so that allowedSet/unknownKeys are not used to add warnings for arbitrary
top-level fields; reference symbols: allowedSet, rules, RESERVED_KEYS,
unknownKeys, entity, typeName, and issues.
- Around line 452-470: Replace the custom scoring logic in getJsonLdScore with a
call to the shared sectionHealthScore to ensure consistent weights; compute the
same errors/warnings/infos counts from entries (as currently done) then return
sectionHealthScore(errors, warnings, infos) (or the appropriate
sectionHealthScore signature used in the repo), and add the necessary import of
sectionHealthScore at the top of the file; remove the local penalty/math logic
so the score is derived solely from the shared sectionHealthScore implementation
used elsewhere.

In `@packages/devtools/src/tabs/seo-tab/links-preview.tsx`:
- Around line 26-37: The current heuristic sets text from anchor.textContent and
only aria-label/title attributes, which misclassifies accessible links that use
aria-labelledby or descendant img[alt]; update the logic around the text
variable (and the place that pushes into issues) to compute a more accurate
accessible name: first check aria-label, then resolve aria-labelledby by looking
up referenced element(s) and using their textContent, then check title, then
look for descendant elements that provide names (e.g., img[alt], svg title or
desc), and use the first non-empty result as the link name; if you prefer not to
implement the full computation, at minimum narrow the error message emitted to
only mention the specific signals you actually check (e.g., "Missing link text,
aria-label, aria-labelledby, title, or image alt") so the message matches the
implemented checks (referencing the variables anchor, text, and issues).

In `@packages/devtools/src/tabs/seo-tab/seo-section-summary.ts`:
- Around line 88-110: aggregateSeoHealth currently sets label based on counts
but the UI derives tier from the computed score; change label derivation in
aggregateSeoHealth (function aggregateSeoHealth) to use the computed score
instead of counts: after computing score, set label = score >= 80 ? 'Good' :
score >= 60 ? 'Fair' : 'Poor', then return { score, label, counts } so the label
aligns with seoHealthTier(score).

In `@packages/devtools/src/tabs/seo-tab/social-previews.tsx`:
- Around line 214-220: The component snapshots the styles signal into const s =
styles(), breaking reactivity; remove the snapshot and call the signal inline.
Replace usages of s (and the accent calculation) to use styles() directly —
e.g., compute accent with socialAccentClasses(styles(), props.accent) and use
className expressions like `${styles().seoPreviewCard} ${accent.card}` so theme
changes update without remounting. Ensure you keep the original useStyles()
binding and delete the `const s = styles()` line.

---

Outside diff comments:
In `@packages/devtools/src/styles/use-styles.ts`:
- Around line 288-295: The mobile preview width constant is out of sync: update
the hidden measurement used by serpMeasureHiddenMobile to match the rendered
card width in serpSnippetMobile (change 340px to 328px or better unify both to a
single shared constant), so the truncation logic measures against the actual
max-width; locate serpSnippetMobile and serpMeasureHiddenMobile in use-styles.ts
and make them derive from the same value (or replace the hard-coded 340px with
328px).

In `@packages/devtools/src/tabs/seo-tab/serp-preview.tsx`:
- Around line 525-530: The component SerpPreviewSection only updates serp via
useHeadChanges, so getSerpFromHead() (which reads window.location.href) is stale
on client-side navigation; modify SerpPreviewSection to also listen for client
navigation events and call setSerp(getSerpFromHead())—add listeners for
'popstate' and a custom 'locationchange' fired from patched
history.pushState/history.replaceState (or listen for a router navigation event
if available), ensure you register these listeners on mount and remove them on
cleanup; keep useHeadChanges behavior intact and reference SerpPreviewSection,
getSerpFromHead, useHeadChanges, and setSerp when implementing the fix.

---

Nitpick comments:
In `@packages/devtools/src/tabs/seo-tab/links-preview.tsx`:
- Around line 109-113: Replace the hard-coded
'[data-testid="tanstack_devtools"]' & the ad-hoc '[data-devtools-root]' fallback
in the anchor filter with the shared TANSTACK_DEVTOOLS selector: import the
TANSTACK_DEVTOOLS constant at the top of links-preview.tsx and use it in the
filter that currently checks anchor.closest(...). Remove the duplicated literal
and the fallback selector so the filter reads something like
anchor.closest(TANSTACK_DEVTOOLS) to reliably exclude the devtools' own links.
🪄 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: 60f7759d-3922-4791-9df2-30fd2914c864

📥 Commits

Reviewing files that changed from the base of the PR and between 22d60fc and 9e1ab03.

📒 Files selected for processing (15)
  • .changeset/puny-games-bow.md
  • examples/react/basic/index.html
  • package.json
  • packages/devtools/src/hooks/use-location-changes.ts
  • packages/devtools/src/styles/use-styles.ts
  • packages/devtools/src/tabs/seo-tab/canonical-url-data.ts
  • packages/devtools/src/tabs/seo-tab/heading-structure-preview.tsx
  • packages/devtools/src/tabs/seo-tab/index.tsx
  • packages/devtools/src/tabs/seo-tab/json-ld-preview.tsx
  • packages/devtools/src/tabs/seo-tab/links-preview.tsx
  • packages/devtools/src/tabs/seo-tab/seo-overview.tsx
  • packages/devtools/src/tabs/seo-tab/seo-section-summary.ts
  • packages/devtools/src/tabs/seo-tab/seo-severity.ts
  • packages/devtools/src/tabs/seo-tab/serp-preview.tsx
  • packages/devtools/src/tabs/seo-tab/social-previews.tsx

Comment on lines +36 to +37
<meta name="robots" content="index, follow" />
<link rel="canonical" href="http://localhost:3005" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't ship a localhost canonical URL.

Line 37 hard-codes http://localhost:3005, so the published example will canonicalize to the wrong origin outside local dev. That gives crawlers — and this new SEO tab — a false signal. Use the deployed example URL here, or drop the canonical tag from this fixture.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/react/basic/index.html` around lines 36 - 37, The canonical link in
examples/react/basic/index.html currently hard-codes "http://localhost:3005"
which will mislead crawlers; update the <link rel="canonical"> element (or
remove it) so it does not point to localhost—either replace the hard-coded URL
with the deployed example's public URL or remove the canonical tag entirely from
the fixture; ensure the change is applied to the <link rel="canonical"> element
so the example no longer signals a localhost origin.

Comment on lines +28 to +56
const originalPushState = window.history.pushState
const originalReplaceState = window.history.replaceState

const handleLocationSignal = () => {
emitLocationChangeIfNeeded()
}

window.history.pushState = function (...args) {
originalPushState.apply(this, args)
dispatchLocationChangeEvent()
}

window.history.replaceState = function (...args) {
originalReplaceState.apply(this, args)
dispatchLocationChangeEvent()
}

window.addEventListener('popstate', handleLocationSignal)
window.addEventListener('hashchange', handleLocationSignal)
window.addEventListener(LOCATION_CHANGE_EVENT, handleLocationSignal)

teardownLocationObservation = () => {
window.history.pushState = originalPushState
window.history.replaceState = originalReplaceState
window.removeEventListener('popstate', handleLocationSignal)
window.removeEventListener('hashchange', handleLocationSignal)
window.removeEventListener(LOCATION_CHANGE_EVENT, handleLocationSignal)
teardownLocationObservation = undefined
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Only restore history methods if this hook still owns them.

Lines 50-51 unconditionally put back the originals. If something else wraps history.pushState or history.replaceState after this observer starts, tearing down the last listener will silently remove that newer wrapper and can break routing or analytics hooks in the host app.

🛠️ Suggested fix
-  window.history.pushState = function (...args) {
+  const pushStateWrapper: History['pushState'] = function (...args) {
     originalPushState.apply(this, args)
     dispatchLocationChangeEvent()
   }
+  window.history.pushState = pushStateWrapper

-  window.history.replaceState = function (...args) {
+  const replaceStateWrapper: History['replaceState'] = function (...args) {
     originalReplaceState.apply(this, args)
     dispatchLocationChangeEvent()
   }
+  window.history.replaceState = replaceStateWrapper
...
   teardownLocationObservation = () => {
-    window.history.pushState = originalPushState
-    window.history.replaceState = originalReplaceState
+    if (window.history.pushState === pushStateWrapper) {
+      window.history.pushState = originalPushState
+    }
+    if (window.history.replaceState === replaceStateWrapper) {
+      window.history.replaceState = originalReplaceState
+    }
     window.removeEventListener('popstate', handleLocationSignal)
     window.removeEventListener('hashchange', handleLocationSignal)
     window.removeEventListener(LOCATION_CHANGE_EVENT, handleLocationSignal)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/hooks/use-location-changes.ts` around lines 28 - 56,
The teardown unconditionally restores originalPushState/originalReplaceState
which can clobber newer wrappers; instead capture the wrapper functions when
installing them (e.g. assign wrappedPushState and wrappedReplaceState when you
set window.history.pushState/replaceState) and, in teardownLocationObservation,
only restore the originals if the current window.history.pushState ===
wrappedPushState (and similarly for replaceState); leave the current values
alone otherwise and still remove the event listeners and clear
teardownLocationObservation.

Comment on lines +141 to +166
export function HeadingStructurePreviewSection() {
const styles = useStyles()
const headings = extractHeadings()
const issues = validateHeadings(headings)
const s = styles()

const issueBulletClass = (sev: SeoSeverity) =>
`${s.seoIssueBullet} ${pickSeverityClass(sev, {
error: s.seoIssueBulletError,
warning: s.seoIssueBulletWarning,
info: s.seoIssueBulletInfo,
})}`

const issueBadgeClass = (sev: SeoSeverity) =>
`${s.seoIssueSeverityBadge} ${pickSeverityClass(sev, {
error: s.seoIssueSeverityBadgeError,
warning: s.seoIssueSeverityBadgeWarning,
info: s.seoIssueSeverityBadgeInfo,
})}`

return (
<Section>
<SectionDescription>
Visualizes heading structure (`h1`-`h6`) in DOM order and highlights
common hierarchy issues. This section scans once when opened.
</SectionDescription>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Refresh this scan after client-side navigation.

Lines 143-144 compute headings and issues once, so if the user keeps this tab open while the app navigates, the tree and issue list keep showing the previous route. Re-run the scan on useLocationChanges() here (and ideally after relevant body mutations) so the panel tracks the current page.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/heading-structure-preview.tsx` around
lines 141 - 166, HeadingStructurePreviewSection currently computes headings and
issues once via extractHeadings() and validateHeadings(), causing stale results
after client-side navigation; subscribe to the location-change signal
(useLocationChanges()) and re-run the scan by moving headings/issues into state
or memo and recomputing them inside an effect or memo that depends on the
location-change value (and optionally a mutation observer trigger for body
changes). Specifically, call useLocationChanges() in
HeadingStructurePreviewSection, then in a useEffect or useMemo triggered by that
value, call extractHeadings() and validateHeadings(headings) and update local
state (or return memoized values) so the UI reflects the current route (and
consider adding a small debounce if needed for rapid mutations).

Comment on lines +204 to +216
const allowedSet = new Set([
...rules.required,
...rules.recommended,
...rules.optional,
...RESERVED_KEYS,
])
const unknownKeys = Object.keys(entity).filter((key) => !allowedSet.has(key))
if (unknownKeys.length > 0) {
issues.push({
severity: 'warning',
message: `Possible invalid attributes for ${typeName}: ${unknownKeys.join(', ')}`,
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't score unlisted schema properties as warnings.

SUPPORTED_RULES is a curated subset, not an exhaustive allowlist. With this block, any additional top-level field becomes a warning and lowers the health score, so valid snippets will look unhealthy just for using more properties.

Proposed fix
-  const allowedSet = new Set([
-    ...rules.required,
-    ...rules.recommended,
-    ...rules.optional,
-    ...RESERVED_KEYS,
-  ])
-  const unknownKeys = Object.keys(entity).filter((key) => !allowedSet.has(key))
-  if (unknownKeys.length > 0) {
-    issues.push({
-      severity: 'warning',
-      message: `Possible invalid attributes for ${typeName}: ${unknownKeys.join(', ')}`,
-    })
-  }
+  // Only warn on unexpected keys once these per-type rules are exhaustive.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allowedSet = new Set([
...rules.required,
...rules.recommended,
...rules.optional,
...RESERVED_KEYS,
])
const unknownKeys = Object.keys(entity).filter((key) => !allowedSet.has(key))
if (unknownKeys.length > 0) {
issues.push({
severity: 'warning',
message: `Possible invalid attributes for ${typeName}: ${unknownKeys.join(', ')}`,
})
}
// Only warn on unexpected keys once these per-type rules are exhaustive.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/json-ld-preview.tsx` around lines 204 -
216, The current code builds allowedSet from rules + RESERVED_KEYS and treats
any extra top-level keys in entity as a warning (unknownKeys -> issues.push),
which incorrectly penalizes valid but unlisted schema properties; remove or
disable this warning: delete the unknownKeys check and the issues.push block (or
gate it behind an explicit opt-in feature flag), so that allowedSet/unknownKeys
are not used to add warnings for arbitrary top-level fields; reference symbols:
allowedSet, rules, RESERVED_KEYS, unknownKeys, entity, typeName, and issues.

Comment on lines +452 to +470
/**
* JSON-LD health 0–100: errors and warnings dominate; each info issue applies a
* small penalty so optional-field gaps match how the SEO overview weights them.
*/
function getJsonLdScore(entries: Array<JsonLdEntry>): number {
let errors = 0
let warnings = 0
let infos = 0

for (const entry of entries) {
for (const issue of entry.issues) {
if (issue.severity === 'error') errors += 1
else if (issue.severity === 'warning') warnings += 1
else infos += 1
}
}

const penalty = Math.min(100, errors * 20 + warnings * 10 + infos * 2)
return Math.max(0, 100 - penalty)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the shared section scorer here.

This card uses 20/10/2 penalties while sectionHealthScore() uses 22/9/2. The same JSON-LD issues can therefore show one percentage here and a different one in the overview ring.

Proposed fix
+import { sectionHealthScore } from './seo-section-summary'
 import type { SeoSectionSummary } from './seo-section-summary'
...
 function getJsonLdScore(entries: Array<JsonLdEntry>): number {
-  let errors = 0
-  let warnings = 0
-  let infos = 0
-
-  for (const entry of entries) {
-    for (const issue of entry.issues) {
-      if (issue.severity === 'error') errors += 1
-      else if (issue.severity === 'warning') warnings += 1
-      else infos += 1
-    }
-  }
-
-  const penalty = Math.min(100, errors * 20 + warnings * 10 + infos * 2)
-  return Math.max(0, 100 - penalty)
+  return sectionHealthScore(entries.flatMap((entry) => entry.issues))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* JSON-LD health 0–100: errors and warnings dominate; each info issue applies a
* small penalty so optional-field gaps match how the SEO overview weights them.
*/
function getJsonLdScore(entries: Array<JsonLdEntry>): number {
let errors = 0
let warnings = 0
let infos = 0
for (const entry of entries) {
for (const issue of entry.issues) {
if (issue.severity === 'error') errors += 1
else if (issue.severity === 'warning') warnings += 1
else infos += 1
}
}
const penalty = Math.min(100, errors * 20 + warnings * 10 + infos * 2)
return Math.max(0, 100 - penalty)
/**
* JSON-LD health 0–100: errors and warnings dominate; each info issue applies a
* small penalty so optional-field gaps match how the SEO overview weights them.
*/
function getJsonLdScore(entries: Array<JsonLdEntry>): number {
return sectionHealthScore(entries.flatMap((entry) => entry.issues))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/json-ld-preview.tsx` around lines 452 -
470, Replace the custom scoring logic in getJsonLdScore with a call to the
shared sectionHealthScore to ensure consistent weights; compute the same
errors/warnings/infos counts from entries (as currently done) then return
sectionHealthScore(errors, warnings, infos) (or the appropriate
sectionHealthScore signature used in the repo), and add the necessary import of
sectionHealthScore at the top of the file; remove the local penalty/math logic
so the score is derived solely from the shared sectionHealthScore implementation
used elsewhere.

Comment on lines +26 to +37
const text =
anchor.textContent.trim() ||
anchor.getAttribute('aria-label')?.trim() ||
anchor.getAttribute('title')?.trim() ||
''
const issues: Array<LinkIssue> = []

if (!text) {
issues.push({
severity: 'error',
message: 'Missing link text or accessible label.',
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't treat textContent as the link's accessible name.

This will report common accessible patterns as errors, such as icon-only links labeled via aria-labelledby or descendant img[alt]. Either broaden the name heuristic or narrow the message so it only describes the signals you actually inspect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/links-preview.tsx` around lines 26 - 37,
The current heuristic sets text from anchor.textContent and only
aria-label/title attributes, which misclassifies accessible links that use
aria-labelledby or descendant img[alt]; update the logic around the text
variable (and the place that pushes into issues) to compute a more accurate
accessible name: first check aria-label, then resolve aria-labelledby by looking
up referenced element(s) and using their textContent, then check title, then
look for descendant elements that provide names (e.g., img[alt], svg title or
desc), and use the first non-empty result as the link name; if you prefer not to
implement the full computation, at minimum narrow the error message emitted to
only mention the specific signals you actually check (e.g., "Missing link text,
aria-label, aria-labelledby, title, or image alt") so the message matches the
implemented checks (referencing the variables anchor, text, and issues).

Comment on lines +88 to +110
export function aggregateSeoHealth(summaries: Array<SeoSectionSummary>): {
score: number
label: 'Good' | 'Fair' | 'Poor'
counts: SeoIssueCounts
} {
const counts = summaries.reduce<SeoIssueCounts>(
(allCounts, summary) => {
const summaryCounts = countBySeverity(summary)
allCounts.error += summaryCounts.error
allCounts.warning += summaryCounts.warning
allCounts.info += summaryCounts.info
return allCounts
},
{ error: 0, warning: 0, info: 0 },
)
const penalty = Math.min(
100,
counts.error * 22 + counts.warning * 9 + counts.info * 2,
)
const score = Math.max(0, 100 - penalty)
const label: 'Good' | 'Fair' | 'Poor' =
counts.error > 0 ? 'Poor' : counts.warning > 0 ? 'Fair' : 'Good'
return { score, label, counts }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Derive the overview label from the computed score.

The overview styles the progress bar with seoHealthTier(score), but this label uses a separate heuristic. That can produce combinations like Poor next to a fair 78% bar, or Good below the 80-point threshold.

Proposed fix
+import { seoHealthTier } from './seo-severity'
 import type { SeoSeverity } from './seo-severity'
...
   const score = Math.max(0, 100 - penalty)
-  const label: 'Good' | 'Fair' | 'Poor' =
-    counts.error > 0 ? 'Poor' : counts.warning > 0 ? 'Fair' : 'Good'
+  const tier = seoHealthTier(score)
+  const label: 'Good' | 'Fair' | 'Poor' =
+    tier === 'good' ? 'Good' : tier === 'fair' ? 'Fair' : 'Poor'
   return { score, label, counts }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function aggregateSeoHealth(summaries: Array<SeoSectionSummary>): {
score: number
label: 'Good' | 'Fair' | 'Poor'
counts: SeoIssueCounts
} {
const counts = summaries.reduce<SeoIssueCounts>(
(allCounts, summary) => {
const summaryCounts = countBySeverity(summary)
allCounts.error += summaryCounts.error
allCounts.warning += summaryCounts.warning
allCounts.info += summaryCounts.info
return allCounts
},
{ error: 0, warning: 0, info: 0 },
)
const penalty = Math.min(
100,
counts.error * 22 + counts.warning * 9 + counts.info * 2,
)
const score = Math.max(0, 100 - penalty)
const label: 'Good' | 'Fair' | 'Poor' =
counts.error > 0 ? 'Poor' : counts.warning > 0 ? 'Fair' : 'Good'
return { score, label, counts }
export function aggregateSeoHealth(summaries: Array<SeoSectionSummary>): {
score: number
label: 'Good' | 'Fair' | 'Poor'
counts: SeoIssueCounts
} {
const counts = summaries.reduce<SeoIssueCounts>(
(allCounts, summary) => {
const summaryCounts = countBySeverity(summary)
allCounts.error += summaryCounts.error
allCounts.warning += summaryCounts.warning
allCounts.info += summaryCounts.info
return allCounts
},
{ error: 0, warning: 0, info: 0 },
)
const penalty = Math.min(
100,
counts.error * 22 + counts.warning * 9 + counts.info * 2,
)
const score = Math.max(0, 100 - penalty)
const tier = seoHealthTier(score)
const label: 'Good' | 'Fair' | 'Poor' =
tier === 'good' ? 'Good' : tier === 'fair' ? 'Fair' : 'Poor'
return { score, label, counts }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/seo-section-summary.ts` around lines 88 -
110, aggregateSeoHealth currently sets label based on counts but the UI derives
tier from the computed score; change label derivation in aggregateSeoHealth
(function aggregateSeoHealth) to use the computed score instead of counts: after
computing score, set label = score >= 80 ? 'Good' : score >= 60 ? 'Fair' :
'Poor', then return { score, label, counts } so the label aligns with
seoHealthTier(score).

Comment on lines 214 to +220
const styles = useStyles()
const s = styles()
const accent = socialAccentClasses(s, props.accent)

return (
<div
class={styles().seoPreviewCard}
style={{ 'border-color': props.color }}
>
<div class={styles().seoPreviewHeader} style={{ color: props.color }}>
<div class={`${s.seoPreviewCard} ${accent.card}`}>
<div class={`${s.seoPreviewHeader} ${accent.header}`}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file and the specific lines mentioned
cat -n packages/devtools/src/tabs/seo-tab/social-previews.tsx | head -230 | tail -30

Repository: TanStack/devtools

Length of output: 1091


🏁 Script executed:

# Let's see more context around those lines
cat -n packages/devtools/src/tabs/seo-tab/social-previews.tsx | head -250 | tail -50

Repository: TanStack/devtools

Length of output: 1691


🏁 Script executed:

# Check the imports and useStyles definition
head -30 packages/devtools/src/tabs/seo-tab/social-previews.tsx

Repository: TanStack/devtools

Length of output: 885


🏁 Script executed:

# Search for useStyles to understand what it returns
rg "useStyles" packages/devtools/src/tabs/seo-tab/ -A 3 -B 3

Repository: TanStack/devtools

Length of output: 13903


🏁 Script executed:

# Check if useStyles is defined in this file or imported
rg "export.*useStyles|function useStyles" packages/devtools/src/ -A 5

Repository: TanStack/devtools

Length of output: 512


🏁 Script executed:

# Get the complete use-styles.ts file
cat -n packages/devtools/src/styles/use-styles.ts

Repository: TanStack/devtools

Length of output: 50373


🏁 Script executed:

# Check the SocialPreviewsSection parent to see if it triggers re-renders
cat -n packages/devtools/src/tabs/seo-tab/social-previews.tsx | sed -n '241,270p'

Repository: TanStack/devtools

Length of output: 1197


🏁 Script executed:

# Look at how other similar components handle this - check serp-preview for comparison
cat -n packages/devtools/src/tabs/seo-tab/serp-preview.tsx | sed -n '1,100p'

Repository: TanStack/devtools

Length of output: 3489


🏁 Script executed:

# Check how many components in seo-tab use the snapshot pattern
rg "const s = styles\(\)" packages/devtools/src/tabs/seo-tab/ -l

Repository: TanStack/devtools

Length of output: 340


🏁 Script executed:

# Count total occurrences of snapshot vs inline patterns in seo-tab
echo "=== Snapshot pattern (const s = styles()) ===" && \
rg "const s = styles\(\)" packages/devtools/src/tabs/seo-tab/ | wc -l && \
echo "=== Inline pattern (styles().someClass) ===" && \
rg "styles\(\)\.\w+" packages/devtools/src/tabs/seo-tab/ | wc -l

Repository: TanStack/devtools

Length of output: 156


Keep useStyles() reactive inside SocialPreview.

The snapshot pattern on lines 215–216 (const s = styles()) prevents theme changes from updating class names in this component until it remounts. Use the signal inline where needed to maintain reactivity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/tabs/seo-tab/social-previews.tsx` around lines 214 -
220, The component snapshots the styles signal into const s = styles(), breaking
reactivity; remove the snapshot and call the signal inline. Replace usages of s
(and the accent calculation) to use styles() directly — e.g., compute accent
with socialAccentClasses(styles(), props.accent) and use className expressions
like `${styles().seoPreviewCard} ${accent.card}` so theme changes update without
remounting. Ensure you keep the original useStyles() binding and delete the
`const s = styles()` line.

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.

1 participant