● Product changelog

Changelog

Every release across the whole platform — the schema tools, the audit tools, the performance tools, the content and AI-visibility tools, the learning hub and the admin panels. Newest first. Each release gives the task it set out to do, which tools it touched, a summary of what happened, and every individual edit. Where something was reporting incorrectly it says so, and says what it does now. The actual code changes are at the bottom of the page.

Currently running v2.9.0 · Jump to code changes

v2.9.0

Task Make the site pass its own accessibility audit, fix every place the audit tools reported wrongly, make the blog properly SEO and AEO optimised, and bring every AI and llms file in line with the live pages.

ToolsAccessibility AuditCSS CheckerSpam Policy AuditBlogHomepageAI filesDemo pages

Summary

The homepage, pricing page and blog were run through our own Accessibility Audit and every finding was fixed in the pages themselves. The homepage went from 20 out of 100 to 100 out of 100, with no errors or warnings, and the pricing page and blog, checked in the same run, came back clean too. A free third-party scan reported 186 issues on the same page; each example it gave was checked against WCAG 2.2 and the A and AA ones were false alarms, written up on the blog.

Running the audit on our own site also showed where the audit itself was wrong: a site-search check that passed on a link to another site's search page, report text that failed its own contrast rule, and labels that still said WCAG 2.1. Each was fixed and re-tested. The blog's 97 posts were given proper SEO and AEO fields, and the llms, markdown and AI discovery files were corrected so AI tools describe the platform as it is now.

Edits

Accessibility of the site

  • Homepage: 20 to 100 out of 100. FAQ focus outlines were clipped by their box; they are now drawn inside the question. Footer links are underlined, the logo has its size set, and a Site map link was added so there is more than one way to find pages (WCAG 2.4.5).
  • Blog. Grey text darkened to pass 4.5:1, search and category controls given names, the page wrapped in a main landmark, card and filter borders darkened to 3:1, a visible focus ring added, and the post grid fixed so it no longer scrolls sideways on phones.
  • Pricing. Button borders darkened to 3:1, a main landmark added and a Site map link added.
  • Independent security checks. The homepage strip now shows eight live third-party checks with their badges: Google Safe Browsing, VirusTotal, Mozilla Observatory, Microsoft Edge, SSL Labs, W3C CSS, W3C HTML and Sucuri SiteCheck.

Audit accuracy

  • Accessibility Audit passed a site search that wasn't there. Any link containing /search? counted as a site search, including a link to Google Safe Browsing's search page. Search and site map links now only count when they point to the same site.
  • Accessibility Audit report failed its own contrast check. Descriptions were grey at 2.6:1 and some headings green at 2.3:1; in dark mode, titles were near-black on a dark card. The report now uses the page's theme colours: 171 pieces of report text below AA in light mode and 40 in dark mode became 0.
  • Accessibility Audit marked some controls as never seen. Controls that start off-screen are now measured again half a second after they take focus, before being reported.
  • AAA contrast now shown, not scored. Text that passes AA (4.5:1) but not AAA (7:1) is listed as an optional note. The score is unchanged, because UK and EU rules ask for AA.
  • Accessibility Audit said WCAG 2.1. Its title, description, tool card and subtitle now say WCAG 2.2, which is what it tests.
  • CSS Checker false alarms. Rules that flagged valid CSS were corrected in both copies of the checker, and the real issues it found in the homepage stylesheet were fixed.

Blog

  • 97 posts given SEO and AEO fields: SEO title, meta description, focus keyword, category, key answer and FAQs, which feed each post's meta tags and BlogPosting, BreadcrumbList and FAQPage schema.
  • Keywords, excerpts and the blog listing were corrected, and an outdated third-party link removed.
  • New post: what a free Recite Me accessibility scan got wrong about our homepage, and why.

AI and llms files

  • Brand and founder. Every file now names the platform AI Web Page SEO and the founder John Carlsson, with the current social profiles.
  • Wrong descriptions corrected. The Combined Audit was described as a security and spam audit; it runs up to 21 audits in the Quick Audit and 35 in the Full Audit. The Security Audit files described the schema generator and now describe the Security Audit.
  • Out-of-date content removed. Old subscription pricing, links to login and test pages, and files cut off part-way were fixed; the What AI Says files list all eight AI tools.
  • Spam Policy Audit no longer names its data supplier on the page.
  • Accessibility example report rebuilt with a real report and the same page re-tested afterwards.

v2.8.0

Task Compare our tools against other free tools, fix every place ours were wrong or overstated, close the security gaps found along the way, and bring the site's own SEO in line with what the tools recommend.

ToolsKeyword AnalysisMeta Tag AnalyzerHTML CheckerSecurity AuditTrust AuditSitemapsCommunity

Summary

Our tools were compared side by side with other free SEO tools on the same pages. Where another tool was right and ours was wrong, ours was fixed. Where ours gave advice Google does not actually give, such as a keyword density limit or a 500-word minimum, the advice was removed. Every fix was tested on real pages before going live, and the old and new results compared.

Along the way, several security problems were found and closed: a login rate limit that could be bypassed with a forged header, a vote counter that could be stuffed, contact messages readable by any logged-in account, and a way for a hostile web page to run code inside the Meta Tag Analyzer. None showed signs of having been used.

Finally, the site's own pages were brought up to the standard the tools check for: titles and descriptions that fit Google's display, complete sitemaps for pages, images and videos, and community pages with proper descriptions, canonicals and schema.

Edits

Security

  • Login rate limit could be bypassed. The limit counted attempts per value of a header the visitor can set freely. It now uses the address nginx records, and was proven to block on attempt 11 even with a forged header on every request.
  • Homepage votes could be stuffed. The same header problem let anyone vote repeatedly, while real visitors all looked like one person. Fixed, and proven by refusing a second vote from a forged address.
  • Contact messages were readable by any account. The route checked that a user was logged in, but not that they were an admin. Now admin-only; server logs showed no one else had accessed it.
  • Meta Tag Analyzer could run a hostile page's code. Tag values from the page being checked were inserted as HTML. They are now decoded and then shown as plain text; a test attack that ran before now runs nothing.
  • Community pages printed member names unescaped. Names are now escaped everywhere they appear.
  • Leftover files were publicly downloadable. A copy of server code, a configuration list, a sign-up list and an old pricing page pointing at a development address were moved out of the public folder.

Audit accuracy

  • Keyword Analysis made up a density limit. Claims that 2.5% or more is "stuffing" that Google penalises were removed; bands are now reading prompts, not scores.
  • Keyword Analysis only counted single words. It now shows 2- and 3-word phrases, accepts a target keyword, compares phrases against a competitor, and exports phrases to CSV.
  • Competitor gaps listed words you already used. A word only counted as used if it was in your top 30. Gaps are now words that appear nowhere on your page, with the competitor's real figures.
  • Meta description check passed without the keyword. It passed whenever a description existed. It now checks for the keyword.
  • HTML Checker label and link warnings were false. Inputs labelled with aria-label were flagged, and every full link was reported as missing noopener. Each input and each new-tab link to another site is now checked individually.
  • Title length counted encoded characters. An ampersand counted as five characters. Titles are now measured as displayed.
  • Security Audit reported a missing page as a risk. When a login address returns 404, the rate-limit test now says it could not run, rather than suggesting missing protection.
  • Trust Audit wording. The reputation check is now named Google Web Risk, the service it uses, and the advice for sites without a mainstream CMS no longer contradicts itself.
  • og:site_name added as a recommendation. Missing site names are now noted as optional, not ignored.

Site SEO

  • 89 titles and descriptions rewritten to fit Google's display width, measured in pixels.
  • Blog descriptions end on a whole word. They were cut at 155 characters, often mid-word.
  • Sitemaps rebuilt. Pages and AI files are picked up automatically, with separate image and video sitemaps, and file names are encoded so one special character can no longer break the file.
  • Community pages fixed. Each thread now has its own description, a canonical that doesn't redirect, working links and complete schema.
  • Scripts and styles update immediately. Browsers now check for new versions on each visit, so fixes reach visitors without version tags.
  • Homepage retargeted to "free SEO tools" and "schema generator" in its title, heading and description.

v2.7.0

Task Harden the server the tools run on, and fix the faults the security audit itself was reporting incorrectly — then publish the whole account as a transparency report.

ToolsSecurity AuditServer AuditPlatform SweepTransparency Report

Summary

The server audit — the inside-the-machine check that runs on the box itself, reading real files, permissions, running processes and login settings — flagged that the most powerful account on the server could be reached with a password alone. Because that account can touch every site on the machine, one guessed password would have compromised all of them, not one. It was closed by refusing passwords but still accepting a cryptographic key, so the file-management tool that logs in through that same account keeps working while the password an attacker could guess is gone. A limited everyday account was created and proven to work first, so the change could never lock us out.

The on-page security audit — the one run against any web address the way a browser loads a page — was cross-checked against Mozilla’s HTTP Observatory, an independent scanner. Where Observatory was stricter, the audit was corrected to match: it had treated a Content-Security-Policy that only looked present as real. A security check that was missing entirely was added, and a detection fault that confused writing about a risky programming feature with code actually using it was fixed.

Both tools were tested against real sites rather than our own alone. The on-page audit was run against two live banking sites returning two clearly different but plausible scores, proving it inspects each site rather than rubber-stamping everything; the checks were also swept passively across ten major website platforms; and the server test was run across every site on our own server. The full account — what was checked, what was found, what was done — is published as a transparency report in plain English or full technical detail.

Edits

Server hardening

  • Root reachable by password. The most powerful account on the machine accepted a password over remote login — the single most attacked door on any server, and one that reaches every site on the box. Switched to key-only: passwords refused, a valid key accepted.
  • Everyday work ran with full power. A separate, limited administrator account was created for routine work and proven to perform a full-power task before anything else was touched, so the login change could not lock us out. Full power is now held only at the moment it is needed, not by default.
  • File tool broke, then was restored on a safer footing. Turning off the password briefly stopped the file-management tool connecting, because file transfer rides on the same secure-shell login it had been authenticating to with that password. Moved to key-based access, restoring it for whoever holds the private key only.

Security audit

  • Content-Security-Policy only looked present. Cross-checked against Mozilla HTTP Observatory, the audit was passing a policy that appeared set but did not hold up to stricter logic. Corrected to match, then taken further — strict CSP and Subresource-Integrity, TLS and cookie flags, mixed content, server fingerprinting, CVE lookups, SPF/DMARC/DKIM mail records, security.txt and CMS detection.
  • A check was missing entirely. Added a security check the audit had never performed, closing a gap against the independent benchmark.
  • Writing about a risky feature read as using it. The scanner mistook a blog article that was about a risky programming feature for code actually using it. Detection was fixed to tell the two apart — and a first attempt that wrongly cleared a genuine use tucked inside a busier line was caught and corrected, then re-tested against real examples until every one was judged correctly.

Testing

  • Proven to differentiate, not rubber-stamp. The on-page audit was run against two real financial-institution sites returning two clearly different but plausible scores — evidence it genuinely inspects each site rather than passing or failing everything identically.
  • Swept across ten platforms. The same checks were run passively from our own server against the official sites of ten major website platforms.
  • Server test run site-wide. The inside-the-machine check was run across every site hosted on our own server.

v2.6.0

Task Correct the audit rules that were reporting faults which were not there, and rebuild the usage statistics.

ToolsSite AuditSite Audit ProAccessibility AuditImage OptimisationSchema DebuggerReadability CheckerTool DirectoryAdmin statistics

Summary

Eleven rules were producing false findings. That matters more than a missing feature: people use these reports to decide what to change on their sites, so a rule that invents a problem costs them real work, and a rule that misses one costs them rankings. Every correction below was made in both places the rules live — the server module and the copy that runs in the browser — because fixing one leaves the fault live in the other.

The social profile checks needed a different kind of fix. Several large platforms refuse automated requests outright, and the audit was treating a refusal as proof the profile was broken. It cannot tell the difference from the outside, so it now says so rather than guessing, while still reporting genuine failures from platforms that do answer.

The usage statistics were wrong in both directions at once, which is why the numbers had never looked credible: some tools were never counted at all, and others were counted many times per run.

Edits

Audit rules

  • Links without an href — false count. The pattern matched any tag starting with the letter A, so <article>, <address>, <aside>, <abbr> and <area> were counted as broken links. A page with two articles and an address block was told it had three faults it did not have. Now requires a word boundary, so it matches anchors only.
  • Legacy image format — ignored <picture>. The rule read the fallback <img src> and reported PNG even when a WebP or AVIF source was offered above it. Now checks the parent element for a modern source before flagging.
  • Lazy loading recommended for the LCP image. Every image was flagged, including the hero. Lazy-loading that image delays the largest contentful paint. The first image, and anything marked fetchpriority="high", is now skipped.
  • Long sentences undercounted. The count was taken after the list was cut to eight for display, so eleven long sentences reported as eight. The full count is taken first and sent separately from the examples.
  • Duplicate “click here” warnings. The plain-English layer matched text inside each finding's own “Learn more” link, so two schema recommendations were relabelled as two identical generic-link warnings. Help links are stripped before matching, and the pattern narrowed to what the link rule actually emits.
  • Raw HTML shown as text. Two schema messages carried an anchor tag inside the message string, which the display layer escaped for safety, printing the tags on screen. The markup was removed from the messages; the escaping stays.

Brand and social presence

  • Blocked platforms reported as broken profiles. A 403, a 429, or a redirect to a login page was shown as a red error at the top of the report. Those platforms, and the status codes meaning “blocked” (401, 403, 405, 429, 451, 999), now produce an informational “could not be verified automatically” notice. A genuine 404 from a platform that answers bots is still an error.
  • Threads not detected. The domain list held threads.net only, so profiles on threads.com — the current domain — were reported as verified but not linked, when they were linked.
  • Google Business Profile not detected. Google's own share button produces share.google and maps.app.goo.gl links, neither of which was in the list. Both added, with goo.gl/maps and business.site.
  • “You're not on any major social platforms” removed. It replaced an accurate count of missing platforms with a claim that was false for anyone with some but not all, then listed platforms the customer already had among the things to claim.

Contact detection

  • Contact details reported absent when present. The upstream provider returns an empty contacts field for most sites, even when its own page extract contains the email address. The page is now read directly for mailto links, plain addresses, telephone links and social profiles, with share and intent links excluded. On our own homepage this went from nothing found to one address and eight profiles.

Usage statistics

  • Counted wrongly in both directions. The endpoint map listed three routes that do not exist, so most tools never registered a run, while geo-grid audits counted every progress poll and parallel sub-request separately. One day's real figure of 31 runs displayed as 1,252.
  • Counting rebuilt. Forty-one real endpoints mapped; free and member versions of a tool collapse to one entry; polling, job status, report fetches, credit checks and authentication excluded; and repeat requests from one visitor to one tool within five minutes count as a single run. Log entries are de-duplicated first, since the server writes each request to two files.

This site

  • Accessibility. A main landmark and skip link added, so keyboard and screen-reader users can pass the navigation.
  • Hero image. Converted to WebP — 94 KB to 20 KB, 79% smaller at 46.8 dB — served with a PNG fallback and given explicit dimensions to stop layout shift.
  • Readability. Twelve over-long sentences split; long-sentence count from eleven to zero.
  • Meta description. The homepage description opened with an apology rather than saying what the site does. Replaced in all eight places it appeared — two meta tags and six structured-data fields.
  • Tool directory layout. Two-column groups now collapse to one where there is no room, and the demo and guide links wrap rather than forcing a fixed width.

v2.5.0

Task Add the Google Maps layer to geo-grid audits, make generated schema claim the page properly, and make the audits usable on a phone.

ToolsGeo-Grid AuditSchema Markup BuilderAI Schema GeneratorSite AuditAudit toolsPerformance toolsContent toolsSchema toolsAdmin statistics

Summary

Geo-grid audits measured organic rankings across a grid of points, but local searches are won in the map pack, which was not being measured at all. Every point now runs a Maps query alongside the organic one, and the results plot on a real map with a competitor table. The field list came from reading an actual API response rather than the documentation, which showed a dozen populated fields being discarded — including Google's own stated reason for why each business ranks where it does.

On the schema side, three faults were undermining the output. Every primary node was being emitted with nothing pointing at it, so a parser saw an orphan and the page gained no authority for the type it declared. Product pages were detecting as blog posts because two major platforms wrap products in an article tag. And the page-type label was reading the customer's own previously published schema rather than the server's decision — so for anyone who had used the generator before, it was reporting its own last answer back at them.

The mobile fault was the most severe of the three: no logged-out visitor on a phone could run any audit at all, on any of the four tool pages.

Edits

Added

  • Geo-grid Maps layer. A Maps query at every grid point alongside the organic one, storing up to twenty businesses per point with thirty-two fields each. The report returns per-business aggregates — average position, coverage, points held outright, distance, rating and category.
  • Fifteen further data fields after a live response showed them being discarded: telephone, claimed status, photographs, opening hours, categories, postcode, and the ranking justifications.
  • Map and competitor table. Real coordinates, with a top-six competitor table showing ratings, distances and categories. Self-hosted, because the site's content security policy blocks external script sources.
  • Administrator statistics panel — visitors by day, week, month and rolling window; bots listed individually; tool usage with session counts and dwell time; top pages; referrers. Regenerated every fifteen minutes.
  • Crawler verification script that fetches any URL as seven different agents and reports what each receives.

Fixed

  • Schema type enforced server-side. The type list was restricted in the browser, but the endpoint still accepted any type from any caller — hiding an option changes what the browser offers, not what the server accepts. A permitted-list check now covers all nine generation routes including the free one. Administrators are exempt; a logged-out caller reads as not-administrator and is refused.
  • Orphaned schema nodes. The WebPage now names the primary node as its main entity. Placement mattered: an earlier attempt referenced an identifier that a later de-duplication pass deleted, producing a reference to nothing.
  • Product pages detected as blog posts. Detection triggered on any article element, and its guard only checked existing schema — which is empty on precisely the pages people bring to a generator. The tag trigger was removed; explicit publisher signals are still honoured.
  • Page-type label read its own output. It now takes the server's decision first.
  • Contradictory geo-grid banner. It said you did not rank in the top hundred while the panel below reported an average position of nine. Both were true — one organic, one local pack — but it read as broken. The banner names which is which, with the Maps position shown separately.
  • Geo-grid time estimates. The Maps call roughly doubled the work per point, so the quoted one, three and six minutes became two to five, six to fourteen, and twelve to twenty-seven.
  • Mobile URL entry — 33 inputs. Validation ran only when a field lost focus, which on a phone frequently never happens, so the run button stayed disabled. None of the thirty-three inputs had an Enter handler either.
  • Dead first tap. Validators set the URL invalid before their asynchronous check began, so tapping Run blurred the field, restarted validation and disabled the button mid-tap. An unchanged URL is no longer re-validated.

Changed

  • Maps failure degrades rather than fails — the audit returns organic results instead of failing outright.
  • Homepage hero image added to the header, marked as the largest contentful element.

v2.4.0

Task Overhaul schema detection and the enrichment panel.

ToolsAI Schema GeneratorSchema Markup BuilderSchema DebuggerAEO / AI Visibility CheckerHomepage

Summary

Detection was rebuilt for FAQ, HowTo, Article and SoftwareApplication pages, and the enrichment panel gained the ability to prefill from the page and write corrections back into the generated markup. The most consequential finding was not a feature at all: a flag in the field-setting logic was silently overwriting genuine values read from the page, so information the generator had correctly extracted never reached the output. Nothing reported an error — the data simply disappeared.

Edits

Fixed

  • Field-setting flag wiping real page data. Correctly extracted values were being overwritten before output, invisibly.
  • Article rows matched by position. Rows are matched by headline now, so editing one article's details no longer applies them to another.
  • Dates and author details. Dates emitted in ISO form; author profile URLs included rather than dropped.
  • Author profiles overwritten. Multiple profile links for a person are merged rather than replaced.

Added

  • Detection rebuilt for FAQ, HowTo, Article and SoftwareApplication pages.
  • Enrichment panel prefill, filling from the page and applying corrections back into the markup.
  • Plan pricing emitted as an aggregate offer rather than a single price.
  • Images emitted as proper image objects rather than bare URLs.
  • Homepage questions, articles and sharing. A genuine FAQ section and article listings were added to the homepage, with social sharing in the footer, so the structured data describes content that is actually on the page rather than asserted about it.
  • Answer-engine tooling updated alongside the generator changes, keeping the content tools consistent with the new detection.

v2.3.0

Task Add comments to every tool, track free usage, and rewrite the keyword tool.

ToolsAll tool pagesKeyword Analysis & GapAdmin free-usage

Summary

Questions about a tool were arriving by email, detached from the thing they were about. Comments now sit on each tool page with one level of threading, styled for both themes, with moderation behind them. Free tool usage is logged per visitor, which required correcting how the visitor's address was read — the server passes it in a different header than the code expected, so every free run had been recorded against the proxy rather than the person.

Edits

Added

  • Comments, per tool — one level of threading, avatars, light and dark themes, collapsible section and collapsible replies.
  • Moderation with administrator approval and auto-approve, and a spam trap that no longer catches genuine visitors whose browsers autofill hidden fields.
  • Free usage tracking per visitor, enriched with network information.

Fixed

  • Every free run attributed to the proxy. Corrected to read the header the server actually sets.
  • Keyword tool rewritten to extract terms from the page itself rather than an external lookup.

v2.2.0

Task Verify the specification behind the AI discovery files, and rebuild on real authorities.

ToolsLLMs.txt GeneratorAI permission filesRobots & Sitemap TesterLLMs.txt AuditorSitemap

Summary

The platform had been generating a bundle of AI discovery files to a specification presented as an emerging standard. Reading its own published pages carefully showed something else. It is published by a digital agency rather than a standards body; its governance page states plainly that no standards-body engagement is currently active; and every file in the bundle is a borrowed convention, with the original contribution being the bundling — a numbering scheme, tiers, a validator and a registry. The same organisation writes the specification, certifies conformance to it, and sells directory listings, certification badges and quarterly re-checks.

None of that is illegal. The licensing genuinely permits free use and nothing controls the filenames, so the objection was product direction rather than law: building on it meant tracking one vendor's release cycle while describing it to customers as a standard.

Every reference was removed — generators, live files, sitemap entries, audit checks and the marketing copy claiming search engines already consumed these files. What replaced it stands on three things that hold up: Schema.org, which search engines genuinely parse today; llmstxt.org, the actual origin of llms.txt; and the IETF working group doing the real standards work on AI usage preferences, chaired by the author of the robots.txt standard.

Edits

Removed

  • ai-catalog.json — both copies deleted, belonging to a third commercial specification, along with its generator, button, instruction text and sitemap entry.
  • agents.md — removed as out of scope. A legitimate open format backed by the Linux Foundation, but it guides AI coding agents around a code repository, which has nothing to do with search. Removed from the generator, the related-files list, the live site and the sitemap.
  • Specification headers stripped from the live ai.txt and the generator that writes it.
  • Two audit checks tied to the commercial specifications, with its labels stripped from every remaining check title and piece of advice.
  • False marketing copy — claims that one search engine already fetched identity.json and another had shipped support for the catalogue file. Neither was true.

Rebuilt

  • identity.json to pure Schema.org, with a nine-option organisation-type selector driving the type. The live file was replaced and validated, a stray duplicate deleted, and the wrong upload path that produced it corrected in two places.
  • ai.json became ai-permissions.json — our own format rather than validating against someone else's schema URL, with permissions mirroring the ai.txt output so the two cannot contradict.
  • llms.txt gained About, Services and Contact sections, using real site data only, confirmed to parse with the reference implementation's own parser.
  • Sitemap trailing-slash bug fixed, where subdirectory index pages produced redirects.

Added

  • An authority configuration file naming the two genuine sources.
  • A monthly authority watcher that fingerprints the published specifications and reports when they change, so the platform follows the standards bodies rather than a vendor's announcements.
  • Content-Usage parsing in the robots.txt tester — the draft standard's way of expressing whether content may be used for AI training or search. Sites carrying it score full marks; sites without lose three points and get an explanation covering the syntax, the draft's status, and its own caveat that these preferences express a wish and compel nothing.

Considered and declined

  • A content-signal format promoted by a large infrastructure provider was reviewed and not adopted. It is one company's convention rather than a standards-body output, and adopting it would have repeated the mistake this release exists to correct.
  • A separate Content-Usage generator was scoped and closed as unnecessary — the tester's own fix text already gives the exact line to add.

v2.1.0

Task Expand the thin guide and demonstration pages, and correct what they claim.

ToolsGuidesDemo pagesLearning Hub

Summary

Fifty-four pages were rewritten from roughly four hundred words to over two thousand — twenty-six guides, completing that family, and twenty-eight demonstration pages. Each gained teaching sections and six or seven genuine questions and answers, mirrored into the structured data so the markup matches what is actually visible.

The larger part of the work was editorial rather than technical. A great deal of the existing content asserted SEO folklore as fact, and correcting it took more judgement than adding words. Two pages were also found genuinely broken on the live site, by scanning every file rather than reading them.

Edits

Content corrected — claims that were not true

  • Redirect chains diluting link equity
  • Temporary redirects not passing equity
  • Multiple headings confusing search engines
  • A duplicate content penalty
  • A penalty for inconsistent business details
  • Keyword density targets
  • Readability as a ranking factor
  • Click-through rate as a direct ranking signal
  • The need to disavow toxic backlinks
  • FAQ and how-to rich results as still widely shown
  • Structured data volume driving AI citations
  • Author profiles conferring expertise signals
  • Generic anchor text wasting equity
  • A hundred-link-per-page limit
  • Business posts and photographs as ranking factors
  • Virtual offices and listing suspension risk

Fixed — found live on the site

  • Unescaped quotation marks in a demonstration page had broken its meta description, its social description and its structured data, which failed to parse entirely.
  • Template artefacts removed from three files. Three others flagged by the same search were genuine framework syntax and correctly left alone.
  • Structured-data drift in two completed pages, where a rewritten visible answer had not been carried into the markup.
  • One page under the word floor at 1,906 words, extended to 2,149.

Where the programme stands

  • Fifty-four pages of two hundred and fifty-three are complete, across two batches — an initial fifteen, then a further thirty-nine. The twenty-six guide pages are finished as a family; twenty-eight of the demonstration pages are done.
  • Remaining: roughly thirty-one demonstration pages, twenty-five beginner tutorials, forty-one how-to pages and twenty-seven schema-generator pages, the last of which are the hardest and are deliberately left until the method is proven.
  • One page is still broken and known. A tutorial page carries the same unescaped-quotation fault described above and has not yet been corrected. It is recorded here rather than quietly fixed later, because it is live.

v2.0.0

Task Stop the generator emitting markup the page does not support, and make the item-count control work.

ToolsAI Schema GeneratorSchema Markup BuilderRich Snippets SuiteHomepageLanding pages

Summary

Two faults were producing markup that did not describe the page. An ItemList was being added to every page regardless, and where no list existed the generator built a single fabricated item out of the page title — markup asserting something untrue, which is the fastest way to lose rich results. Recipe was being detected whenever the words “recipe” or “ingredient” appeared anywhere in the text.

Separately, the item-count control had never worked. The cap was fixed per route rather than read from the request, and a second hard limit of five applied to questions and answers regardless of selection. That is the actual cause of the long-standing complaint that choosing twenty-five returned five, or that the count appeared stuck.

Edits

Fixed

  • ItemList emitted on every page, with a fabricated item built from the page title where no list existed. Now requires at least two genuine list items; the title fallback is gone.
  • Item count ignored your selection. The cap was fixed per route rather than read from the request. The selected count now governs every type.
  • Questions and answers capped at five by a separate hard limit regardless of selection.
  • Panel showed the wrong count, displaying whichever list was longer — the generated one, correctly capped, or the page's own uncapped set.
  • Recipe misdetection on any page mentioning recipes or ingredients. Real recipe markup is now required.
  • Rows labelled “Article” regardless of type. Blog posts and news articles are named as such.

Added

  • Fourteen schema types opened to all users — local business, software application, article, news article, FAQ page, how-to, website, web page, product, organisation, person and item list among them. Administrators retain the full set.
  • A new homepage, rebuilt in the site theme and focused on what the tools actually do: the three main actions inline at the top with duplicates removed, the thousand-item limit attributed to schema generation only rather than implied across everything, the debugger described accurately as checking schema that already exists, llms.txt promoted to a first-class feature, an explanation of type overriding, the ninety-plus schema types grouped into six categories, and both pricing routes stated plainly.
  • A dedicated schema and llms.txt page for the AEO subject, built as a duplicate of the homepage with only the title, description, canonical, social URL, page schema and heading changed, so the two do not compete on layout while targeting different queries.

v1.9.0

Task Add the llms.txt context files, and audit the AI permission checks.

ToolsLLMs.txt GeneratorLLMs.txt AuditorContent toolsSitemap capping

Summary

Where llms.txt is an index of your pages, the context files carry the content itself: every linked page fetched, converted to markdown, and wrapped in the structure models expect. No AI call is involved — it is a mechanical expansion, so the free version builds automatically after generation, and only the full one, which includes optional sections, is charged.

Auditing the existing checks turned up something worse than a missing feature. The test for a required section in AI permission files was written in a way that matched a single character from the section name rather than the section name itself, so it passed almost any text at all. A file reading “hello there, nothing here” scored as valid on three separate checks. The check had been validating nothing since it was written.

Edits

Added

  • llms-ctx.txt and llms-ctx-full.txt, the free one building automatically after generation, the full one charged by size.
  • An HTML-to-markdown converter, stripping scripts, styles, navigation, headers, footers and forms so the output is content rather than page furniture.
  • A link-count control from five up to no limit, mirroring the schema generator's tiers, with the request value authoritative.

Fixed

  • The identity check validated almost anything. It now requires the real section header.
  • ai.txt written in the wrong format. It emitted a structure that could not pass the specification it claimed to follow, and invented a contact URL. Rewritten to the correct sections, with no contact line unless you supply one.
  • llms.txt section ordering. Descriptive prose was written as headed sections, when the specification requires prose above the first heading and headed sections to contain file lists only, with the optional section last.
  • Sitemaps skipped entirely. A sitemap of over eleven hundred URLs is now sliced to the count you choose, as lists and products are, and the output states the true total alongside the number shown.
  • Credit balance did not refresh. Two routes returned the updated balance but nothing applied it, so the figure only changed on reload.
  • Price labels unreadable — inline styles were overriding the class, rendering cost chips at eleven pixels in a generic monospace face.
  • Interface text leaking into output, with cost indicators appearing inside generated headings.
  • Panel copy out of date. The download panel still said three files when there were five, listed only three upload paths, and described ai.txt as following a permissions standard it does not. Corrected on all three counts.

v1.8.0

Task Support question and answer pages end to end.

ToolsAI Schema GeneratorSchema DebuggerSchema Markup BuilderSchema tools panel

Summary

QAPage joined the supported types across detection, generation, the manual builder, the type list and the debugger. Most of the work was not the type itself but the answer text. Question pages are dense with interface — voting widgets, comment counts, filters, author badges, reputation scores — and a naive read picks up any of it. The generator now sources the answer in a defined order, rejects candidates that merely repeat the question or turn out to be stylesheet code, and strips the surrounding furniture before reading. Where no readable question exists it emits a plain WebPage rather than invalid markup.

Edits

Added

  • QAPage end to end — detection, node builder, dropdown, URL-based generation, manual builder, and the enrichment panel with its own description and rich-result explanation.
  • Debugger rule for QAPage, validating the main entity as a single question rather than a list, as the specification requires.
  • Collapsible enrichment panel — sections start closed, open where real page data was found, and carry a badge showing what was found and what was not.

Fixed — getting the answer right

  • Answer sourcing order — existing structured data, then page body, then markdown.
  • Site-agnostic selectors rather than assuming one site's markup conventions.
  • Prose guard rejecting stylesheet or code content picked up as answer text.
  • Distinct-answer guard rejecting candidates that repeat the question, preferring the longest genuine prose.
  • Interface stripping — buttons, forms, voting widgets, comment counts, filters, promotional blocks and page headers removed before reading.
  • Byline stripping — author, profile, avatar, badge and reputation elements removed.
  • Required-field gate — a plain WebPage where no readable question exists, rather than invalid QAPage markup.
  • Event mining suppressed on question, FAQ and forum pages unless a genuine event signal is present.
  • Product selector widened so a genuinely listed product is captured, requiring a name, with price read from a currency symbol.
  • Fourteen page titles brought into range. Titles outside roughly fifty to sixty characters are truncated or padded in search results. A script rewrote them and updated both the title tag and the page's own metadata, backing up each file first.

v1.7.0

Task Merge the how-to-fix pages into the learning hub as searchable cards.

ToolsLearning HubError Docs

Summary

Four hundred and fifty-four how-to-fix pages existed but were only reachable if you knew the URL. They are now searchable cards in the learning hub, bringing it to 801 in total, so an issue in an audit report links to the specific repair rather than a definition. The pages had been written in three different layouts over time, so the extractor reads all three, with a conditional pass for pages that mix them.

Edits

Added

  • 454 pages merged as searchable cards, bringing the hub to 801, averaging nearly nine steps per card. None skipped, none thin.
  • An extractor reading three page layouts — step blocks with preformatted code, numbered headings, and ordered lists under a step-by-step heading.

Fixed

  • Twenty-three thin cards. On mixed-layout pages the extractor found two step blocks and ignored the headings and code holding most of the content. A conditional second pass fires only when the first is thin, with overlap removal so clean pages are not double-counted. Thin cards went from twenty-three to zero.
  • Thirty-eight mislabelled cards — thirty-seven with no type, one miscategorised — which had been rendering with an undefined label.
  • Escaping at the injection points. The escape helper existed only inside the modal, so grid rendering would have failed on any card whose title or summary contained markup.
  • Search routing verified across all thirty-one tool keywords: twenty-seven resolve to a matching card. One was fixed by adding the plain tool key to eleven platform-specific pages. The remaining four have no fix page written yet — a content gap, not a broken link.

Also produced

  • Two product walkthrough videos — a twenty-nine slide piece covering the schema generator, and an eleven slide piece on the llms.txt auditor, each built from real screenshots of the tools rather than mock-ups.
  • Listing and social copy to accompany them.

Code changes

The actual edits behind the releases above, before and after. Where a rule exists in both the server module and the browser copy, both were changed — fixing one leaves the fault live in the other.

v2.9.0

Report text below its own contrast minimum

site-audit/public/performance-tools.html

Report descriptions used fixed grey #94a3b8 (2.6:1 on white) and titles fixed #1e293b (near-black on the dark theme). They now use the theme's own text colours.

Before
<div style="font-size:12px;color:#94a3b8;margin-bottom:4px">${a11yEsc(i.desc)}</div>
After
<div style="font-size:12px;color:var(--text2);margin-bottom:4px">${a11yEsc(i.desc)}</div>
v2.9.0

Site search found on another site's link

site-audit/public/performance-tools.html

A link to any URL containing /search? passed WCAG 2.4.5, including Google Safe Browsing's search page. Links now count only when they point to the same site.

Before
[...doc.querySelectorAll('button, [role="button"], a[href], …')].filter(notConsent).some(c => …
After
[...doc.querySelectorAll('button, [role="button"], a[href], …')].filter(notConsent).filter(c => !c.matches('a[href]') || mwSame(c.getAttribute('href'))).some(c => …
v2.9.0

AAA contrast collected as a note

site-audit/public/performance-tools.html

Text that passes AA but not AAA is now kept and reported as an unscored note.

Before
      else fails.push({ text: …, need: need, fg: a11yHex(fg), bg: a11yHex(bg) });
    }
After
      else fails.push({ text: …, need: need, fg: a11yHex(fg), bg: a11yHex(bg) });
    }
    else if (ratio < (large ? 4.5 : 7)) aaa.push({ text: …, need: large ? 4.5 : 7, … });
v2.8.0

Rate limit keyed on a forgeable header

seo-subscriptions/server.js

X-Forwarded-For is sent by the client, so changing it on every request reset the count. X-Real-IP is set by nginx from the real connection.

Before
const ip = req.headers['x-forwarded-for'] || req.ip;
After
const ip = req.headers['x-real-ip'] || req.ip;
v2.8.0

Tag values inserted as HTML

audit-tools.html

Values from the page being analysed were inserted unescaped, and silently cut at 120 characters. Now decoded, then escaped, and shown in full.

Before
${t.content.substring(0,120)}
After
${escHtml(_dec(t.content))}
v2.8.0

Gaps checked against the top 30 only

site-audit/app.js

A competitor word counted as missing unless it was in your top 30, so words you used were reported as absent.

Before
return !mainTopSet.has(k.keyword)
After
return !mainWordCount[k.keyword]
v2.8.0

Blog descriptions cut mid-word

seo-subscriptions/server.js

A flat character cut. Now the whole excerpt if it fits Google's width, otherwise the last whole word that fits.

Before
const desc = (p.excerpt||p.title).slice(0,155);
After
if (_serpPx(t, 14.1) <= 985) return t;
for (const w of t.split(' ')) {
  const c = out ? out + ' ' + w : w;
  if (_serpPx(c + '…', 14.1) > 985) break;
  out = c;
}
return out + '…';
v2.6.0

Links without an href

lib/accessibility-checker.js + audit-tools.html

No word boundary after the tag name, so the pattern matched any element starting with the letter A.

Before
/<a(?![^>]*href)/gi
After
/<a\b(?![^>]*\bhref\b)/gi
v2.6.0

Legacy format ignored <picture>

lib/image-optimisation-checker.js + audit-tools.html

The rule read the fallback img and never looked for a modern source alongside it.

Before
if (/\.(png|jpe?g)(\?|$)/i.test(src)) {
  issues.push({ type:'warning', label:'Legacy image format' });
}
After
function hasModernSource(img) {
  const parent = img.parentElement;
  if (!parent || parent.tagName !== 'PICTURE') return false;
  return [...parent.querySelectorAll('source')]
    .some(s => /image\/(webp|avif)/i.test(s.getAttribute('type') || ''));
}

if (/\.(png|jpe?g)(\?|$)/i.test(src) && !hasModernSource(img)) {
  issues.push({ type:'warning', label:'Legacy image format' });
}
v2.6.0

Lazy loading advised for the LCP image

lib/image-optimisation-checker.js + audit-tools.html

Every image was flagged, including the one that should load eagerly.

Before
if (loading !== 'lazy') {
  issues.push({ type:'warning', label:'Image not lazy-loaded' });
}
After
const isLcpCandidate = (index === 0) ||
      (img.getAttribute('fetchpriority') || '').toLowerCase() === 'high';

if (loading !== 'lazy' && !isLcpCandidate) {
  issues.push({ type:'warning', label:'Image not lazy-loaded' });
}
v2.6.0

Long sentences undercounted

app.js + audit-tools.html

The count was read from the array after it had been truncated for display.

Before
const longSentences = sentences.filter(s => wordCount(s) > 25).slice(0, 8);
return { longSentences, count: longSentences.length };
After
const allLong = sentences.filter(s => wordCount(s) > 25);
const longSentences = allLong.slice(0, 8);
return { longSentences, longSentenceCount: allLong.length };
v2.6.0

Blocked platforms reported as broken profiles

audit-tools.html

Any non-200 became a red error, so a platform refusing bots looked like a dead profile.

Before
unverified.forEach(p => issues.push({
  type: 'error',
  label: p.label + ' profile not reachable',
  fix: 'Open the URL in a browser to confirm it works. If correct, this
        may be a platform anti-bot block - manually verify it loads.',
}));
After
const SAP_BOT_HOSTILE   = /linkedin|instagram|trustpilot|threads|tiktok|twitter|x\.com/i;
const SAP_BLOCKED_CODES = [401, 403, 405, 429, 451, 999];

unverified.forEach(p => {
  const st = parseInt(p.status, 10) || 0;
  const blocked = SAP_BLOCKED_CODES.indexOf(st) !== -1 ||
                  SAP_BOT_HOSTILE.test((p.label || '') + ' ' + (p.given || ''));
  issues.push(blocked
    ? { type: 'info',  label: p.label + ' could not be verified automatically' }
    : { type: 'error', label: p.label + ' profile not reachable' });
});
v2.6.0

Threads and Google Business Profile not detected

app.js

The domain lists predated Threads moving to threads.com and Google's short-link share formats.

Before
{ key: 'threads',         label: 'Threads',
  domains: ['threads.net'] },
{ key: 'google_business', label: 'Google Business Profile',
  domains: ['google.com', 'g.page', 'maps.google.com'] },
After
{ key: 'threads',         label: 'Threads',
  domains: ['threads.net', 'threads.com'] },
{ key: 'google_business', label: 'Google Business Profile',
  domains: ['google.com', 'g.page', 'maps.google.com',
            'maps.app.goo.gl', 'goo.gl/maps', 'share.google',
            'business.site'] },
v2.6.0

“You're not on any major social platforms”

audit-tools.html

A fixed rewrite replaced an accurate count with a claim that was false for anyone with some profiles but not all.

Before
{ test: /missing presence|missing.*platforms/i,
  label: "You're not on any major social platforms",
  fix: "Claim your handle on the big platforms
        (Facebook, Instagram, LinkedIn, Google Business)." },
After
// Entry removed. The original label - "Missing presence on N major
// platforms" - is already plain English and carries the real count.
v2.6.0

Tool directory ran off the screen

seo-audit-platform.html

Inline styles beat the stylesheet, so no media query could reach them. The chip rows could not shrink below 306px, which set the minimum width of each column.

Before
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0 20px">
  <div style="display:flex;flex-direction:column">
    <div style="display:flex;gap:6px;flex-wrap:nowrap">
After
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:0 20px">
  <div style="display:flex;flex-direction:column;min-width:0">
    <div style="display:flex;gap:6px;flex-wrap:wrap">
v1.9.0

The identity check validated almost anything

app.js

A character class, not a literal. It matched any single letter from i, d, e, n, t or y, so a file reading “hello there” passed.

Before
/^[identity]/m
After
/^\s*\[identity\]\s*$/mi
v2.0.0

Questions and answers capped at five

app.js

A hard-coded limit in the FAQ extractor overrode whatever count was selected.

Before
if (faqPairs.length >= 5) break;
After
if (faqPairs.length >= ITEM_CAP) break;
v2.0.0

ItemList emitted on every page

app.js

Where no list existed, a single item was fabricated from the page title.

Before
if (!items.length) {
  items = [{ '@type': 'ListItem', position: 1, name: pageTitle }];
}
graph.push({ '@type': 'ItemList', itemListElement: items });
After
if (items.length >= 2) {
  graph.push({ '@type': 'ItemList', itemListElement: items });
}

Found something reporting incorrectly? Tell us — audit accuracy fixes go to the front of the queue.