<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Warren Halderman</title>
    <description>The latest articles on Forem by Warren Halderman (@warren_halderman_19a8d4d6).</description>
    <link>https://forem.com/warren_halderman_19a8d4d6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3738930%2F8ddce33d-a621-4fb1-87b9-123544c53935.png</url>
      <title>Forem: Warren Halderman</title>
      <link>https://forem.com/warren_halderman_19a8d4d6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/warren_halderman_19a8d4d6"/>
    <language>en</language>
    <item>
      <title>I Spent Over 9000 Hours Building a Tool to Save 5 Seconds of Right-Clicking</title>
      <dc:creator>Warren Halderman</dc:creator>
      <pubDate>Mon, 02 Feb 2026 23:46:49 +0000</pubDate>
      <link>https://forem.com/warren_halderman_19a8d4d6/i-spent-over-9000-hours-building-a-tool-to-save-5-seconds-of-right-clicking-2dg5</link>
      <guid>https://forem.com/warren_halderman_19a8d4d6/i-spent-over-9000-hours-building-a-tool-to-save-5-seconds-of-right-clicking-2dg5</guid>
      <description>&lt;p&gt;Website auditing is the digital equivalent of checking if your blinker works by getting out of the car, walking to the back, looking, walking back to the seat, and repeating.&lt;/p&gt;

&lt;p&gt;If you’re a web developer (god help you) or an SEO (god help you), you know the ritual:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Right-click → View Page Source to see the raw response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;F12 → Elements to see if the JavaScript actually rendered them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch tabs to a schema validator for the JSON-LD hell.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy-paste the URL into an Open Graph social previewer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A tedious, productivity killing process. And "View Page Source" is almost meaningless nowadays.&lt;/p&gt;

&lt;p&gt;For those outside of the ugly web-dev world: with modern JavaScript tools like Svelte, React, and Vue, the server sends a near-empty HTML document, and relies on JavaScript to generate everything (rather than just sending everything at the start, for some reason, because CDNs I guess). If you’re debugging based on the raw HTML source, you’d be looking at a ghost.&lt;/p&gt;

&lt;p&gt;I needed a live debugger. Something that lived alongside the DOM. I needed something that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;was a part of DevTools (not some popup that vanishes the moment I try to copy a string),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;reflected the DOM state, and, optimally&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;didn't break the host page.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, I built &lt;a href="https://chromewebstore.google.com/detail/seodin-page-analyzer/obmnleflmffnkfdiaecgniokcfebhnkd" rel="noopener noreferrer"&gt;SEOdin Page Analyzer&lt;/a&gt;. Ridiculous name, I get it. Anyway, here’s how I survived the absolute nightmare that is browser extension development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comlink and Svelte 5
&lt;/h2&gt;

&lt;p&gt;To make this feel like a live debugger rather than an old one-time scanner, I went with the cutting edge (and barely survived). I started with SolidJS years ago, and enjoyed it a lot, but eventually settled on the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Svelte 5 for the (with the new Runes API)&lt;/li&gt;
&lt;li&gt;Vite (with rolldown), to compile the Svelte components, and&lt;/li&gt;
&lt;li&gt;Comlink (RPC wrapper), to communicate between each extension context&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Comlink
&lt;/h3&gt;

&lt;p&gt;Not the cutting-edge part. Chrome extension architecture is basically “design by committee.” You have Content Scripts, Service Workers, and DevTools Panels all needing to talk to each other through &lt;code&gt;chrome.runtime.sendMessage&lt;/code&gt;, &lt;code&gt;chrome.runtime.connect&lt;/code&gt;, &lt;code&gt;chrome.tabs.sendMessage&lt;/code&gt;, and &lt;code&gt;chrome.tabs.connect&lt;/code&gt;. You have one Service Worker for all Content Scripts and DevTools panels, and one DevTools panel handling the Content Script in each frame in a given tab. It’s wildly complicated for a fool like myself.&lt;/p&gt;

&lt;p&gt;I used a modified version of Comlink (since extension ports are auto-disconnecting nightmares) to wrap this in an RPC layer. It lets me call functions across contexts as if they were local modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Svelte 5 Runes
&lt;/h3&gt;

&lt;p&gt;The cutting-edge part. Handling an SEO audit is surprisingly state-heavy. You’re tracking hundreds of rules involving canonical links, hreflang links, heading hierarchies, image alt text… all while you might be live-editing the page.&lt;/p&gt;

&lt;p&gt;I didn't want to re-run everything on every keystroke. This is where Svelte’s runes come in. Using &lt;code&gt;$state&lt;/code&gt; and &lt;code&gt;$derived&lt;/code&gt;, I built a reactive analysis engine that only reanalyzes the specific elements that changed. The flow is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The DevTools panel is opened.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DevTools RPC is created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Content Scripts are injected into the page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Content Script RPC is created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DevTools requests data for the current displayed panel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Content Script creates and returns various &lt;code&gt;$state&lt;/code&gt; and &lt;code&gt;$derived&lt;/code&gt; objects representing a stateful list of individual element states.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using &lt;code&gt;$effect&lt;/code&gt;, whenever the state is updated, the content script attempts to send the updated state to DevTools.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;DevTools performs some analyses on the state.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;The reports are displayed.&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;I’m simplifying a bit here, but that’s the overall flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Isolation (i.e. Stop Screwing With My Font Face)
&lt;/h2&gt;

&lt;p&gt;Injecting a UI into a website is messy business. If, for example, I use a &lt;code&gt;.container&lt;/code&gt; class, and the website I’m auditing also uses &lt;code&gt;.container&lt;/code&gt;, the user is going to have a bad time. Of course, that particular issue is easy to avoid, but you eventually find yourself patching a chain of edge cases.&lt;/p&gt;

&lt;p&gt;To get around this, I wrapped the on-page portion of the extension (the tooltip) in a shadow DOM. It creates a "style firewall" so that the tooltip looks the same whether you’re auditing a pristine landing page or a 1998 GeoCities revival.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;get_mount_target_shadow_root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mount_target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seodin_root_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mount_target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Creating SEOdin mount root...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;mount_target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seodin-ext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seodin_root_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="c1"&gt;// The font stylesheet needs to *also* be appended outside the&lt;/span&gt;
        &lt;span class="c1"&gt;// shadow DOM until https://issues.chromium.org/issues/41085401&lt;/span&gt;
        &lt;span class="c1"&gt;// is fixed.&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;font_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;font_link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stylesheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;font_link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;font_css_url_resolved&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;font_link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mount_target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mount_target_shadow_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mount_target_shadow_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Creating SEOdin mount target...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;mount_target_shadow_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mount_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// must be "open" in order to access any existing shadowRoot&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mount_target_shadow_root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mounted_component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mount_seodin_component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;unmount_seodin_component&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mounting SEOdin...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;mounted_component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;get_mount_target_shadow_root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;mover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;childList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to mount SEOdin!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the best use case I’ve found for the shadow DOM. In every other situation, I avoid it like the plague...&lt;/p&gt;

&lt;h2&gt;
  
  
  The JSON-LD Chaos
&lt;/h2&gt;

&lt;p&gt;For example, a &lt;code&gt;Product&lt;/code&gt; schema requires specific properties like &lt;code&gt;offers&lt;/code&gt; or &lt;code&gt;review&lt;/code&gt;. Google's Rich Results test is great, but it's slow and requires a context switch.&lt;/p&gt;

&lt;p&gt;I decided to bring that validation directly into the DevTools panel. To do this, I implemented a collection of validators for Google Structured Data types. This allows the extension to analyze the structure of the data in real-time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./schema/types/Product&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;analyzeProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnalysisStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product is missing 'offers'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;review&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnalysisStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product is missing 'reviews'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Svelte 5's runes and CodeMirror's editor, SEOdin can instantly provide a simple interface (similar to VS Code) and reactively analyze the schema as a user alters it.&lt;/p&gt;

&lt;h2&gt;
  
  
  That’s a Known Issue (The Roadmap)
&lt;/h2&gt;

&lt;p&gt;Moving forward, I’m looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Moving the tooltip UI entirely into the sidebar to avoid overlapping with the actual page content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using Chrome's built-in Gemini Nano for language-related SEO issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Covering the less common corners of Schema.org and Google Structured Data schema.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It’s Free, You Know
&lt;/h2&gt;

&lt;p&gt;SEOdin is a labor of love at Bruce Clay Japan. It’s functional, it’s fast, and it’s free. If you’re tired of tedious website auditing, give &lt;a href="https://chromewebstore.google.com/detail/seodin-page-analyzer/obmnleflmffnkfdiaecgniokcfebhnkd" rel="noopener noreferrer"&gt;SEOdin Page Analyzer&lt;/a&gt; a spin.&lt;/p&gt;

&lt;p&gt;If you have any ideas or requests, or want to tell me how wrong my choices were, please leave a comment!&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>vite</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
