1.32.0 — five things a real site found
Added
-
twitter-image-broken— a declaredtwitter:imagethat does not load.
The absence of a Twitter card stays unreported and always will: X falls back
to Open Graph correctly, so reporting it would invent a defect. This is the
opposite case, and the roadmap's rejection of the first did not cover it —
nothing falls back to anything when the tag is present and 404s, and the one
platform handed its own tag previews blank.Only when it differs from
og:image, so one picture never produces two
findings, and only on 404/410/no-answer, because a 403 is hotlink protection
working. All four silent cases are tested: same picture, no tag at all, a
403, and a redirect to the real file. -
body-not-html— a URL the server calls HTML that is not HTML. The body
is already read, so it costs no request, and it replaces the checks it
silences rather than joining them. A real site serves an XML document at
/locations.kmlwithContent-Type: text/html; the crawl believed the header
and reported thirteen things — no title, no h1, no viewport, no charset, thin
content, three Open Graph tags — every one of them true about a document that
was never a page, and not one of them the thing to fix. Now it is one finding
saying what is actually wrong, and the reason it matters: Google indexes
whatever comes back undertext/html.It answers on positive evidence only. XHTML opens with an XML prologue and is
HTML; a fragment with no<html>wrapper is HTML; a byte-order mark before
the doctype is HTML; something merely starting with a brace is not JSON. All
four are tested, because guessing wrong here would silence every check on a
real page — a far worse failure than the noise it removes.
Fixed
-
A dead site with an expired certificate is told what is actually wrong.
expired.badssl.comcame back as "The site did not answer a single request …
The TLS connection succeeds but no response arrives … Cloudflare Bot Fight
Mode does exactly this." Both halves were wrong: the TLS connection does not
succeed, and the cause is a certificate that ran out in 2015. The site checks
that would have named it never run, because the crawl gives up first — and a
browser refuses an expired certificate exactly asfetchdoes, so from the
crawl's side "nothing answered" and "the certificate lapsed" are the same
silence.The certificate is now read before bot protection is blamed, over the same
non-validating socket the check already used, and the report says
"The TLS certificate expired 4151 day(s) ago … which is why nothing here
could be fetched." Found by asking whether the Raycast extension really gets
the certificate checks: it does, and this hole was underneath the question.Where the certificate cannot be read at all — the hosted Worker has no socket
— the old wording stands unchanged, minus the sentence claiming the
certificate is fine. A runtime that cannot run a check says so rather than
implying a result. -
One row named one Open Graph tag and counted three. A run against a real
site printedMissing og:description ×6over four pages — three of which were
missing only the description, one missing all three tags — and listed that one
page three times. Every finding under it was true. The sentence above them was
not, and a summary that misreports is the thing grouping exists to prevent.The cause:
og:title,og:descriptionandog:imagewere pushed under one
id, and a group takes its title from the finding it saw first. They are now
og-title-missing,og-description-missingandog-image-missing— three
ids, because they are three different repairs. A missingog:imageis a
picture somebody has to make; a missingog:titleis one line of template.
Splitting them also makes each separately ignorable, so a site that ships no
og:imageon purpose can silence that without silencing the other two.--ignore og-missingkeeps working and still silences all three. Splitting a
check is our decision, not the decision of whoever wrote that config, and an
upgrade that promises to be compatible should not start failing their build.Not generalised, and the reason is worth writing down: four other ids also
carry more than one title —img-alt-long,img-dimensions,heading-skip,
anchor-ambiguous— and all four are correct. They are the same problem with
the page's own numbers in the title. A test asserting one id, one title would
have flagged the four right ones and this was the only wrong one. -
A Raycast preference could do nothing and nobody would know. The
extension's hand-writtenPreferencestype listed three of the thirteen
preferencescrawlOptions()actually reads; the other ten were invisible to
every caller. It is nowExtensionPreferences, which the Raycast build
generates from the manifest, so it cannot disagree with what the manifest
says — and the two components that read preferences had been calling
getPreferenceValues()untyped, which is what the change surfaced
immediately.Arguments.Auditreplaces a second hand-copied shape.Types only cover half of it:
present.mjsis plain ESM sonode --testcan
run it, so a test now checks in both directions that every preference the
manifest declares is read and that nothing reads a preference the manifest
does not declare. A setting that quietly does nothing is the same failure
src/options.mjsexists to prevent for the macOS window.Found by Greptile on the Store submission, which is worth recording: it
called the type duplication a maintainability nit, and the count made it a
defect. -
A release no longer starts by failing. The macOS job builds on a tag and
attaches the app to that tag's release — a release the documented procedure
created afterwards, by hand. So every release began the same way: a red
release not found, agh release create, a re-run. A failure that is part
of the normal procedure is a failure nobody reads, and the next one, which is
real, reads exactly the same.It now creates the release when it is missing, titled from the tag's own
annotation with that version's CHANGELOG section as the notes — both already
written by the time a tag is pushed, so neither becomes a second copy to keep
in step. A tag with no CHANGELOG section gets a warning and generated notes
rather than a lost build, because by then the app is already sitting there
signed.The section is found with
index()rather than a regex, which is the bug that
was there to be written:## [1.31.0]interpolated into a pattern makes
[1.31.0]a character class matching the digits0,1and3. It would
not have errored. It would have published the wrong section, or none.
Changed
npx @nurkamol/seo-auditis the install the site and README lead with.
The repository form still works and is still documented, pinned — but it
clones 16 MB of app sources, screenshots and tests to reach a 115 kB crawler,
measured rather than guessed.npm i -gis written down too, for anyone who
would rather typeseo-audit.