Skip to content

seo: three pages shared one description and none had a canonical - #33

Merged
ChelseaKR merged 1 commit into
mainfrom
seo/per-page-canonical-and-description
Aug 29, 2026
Merged

seo: three pages shared one description and none had a canonical#33
ChelseaKR merged 1 commit into
mainfrom
seo/per-page-canonical-and-description

Conversation

@ChelseaKR

Copy link
Copy Markdown
Owner

What was wrong

render.py hardcoded one sentence into the head of all three pages:

Coverage and completeness counts over California's public wildfire datasets. Unofficial.

So a search result for the DINS page and a search result for the perimeters page were indistinguishable, and neither said what its own page counts. There was also no canonical and no Open Graph on any page.

What this changes

page() now takes a description per page, each restating what that page already says of itself in its own standfirst, plus a canonical, og:* and twitter:card.

page canonical
index.html https://chelseakr.github.io/perimeter/
perimeters.html https://chelseakr.github.io/perimeter/perimeters.html
dins.html https://chelseakr.github.io/perimeter/dins.html

SITE_URL and CANONICAL_PATH are keyed by active, the value each page already uses to mark its own nav entry, so the canonical and the highlighted tab cannot disagree about which page this is.

The descriptions state no count

Deliberately. The committed site/ is built from data/raw/; the test build is built from the fixtures. A figure hardcoded in render.py would be true of one build and false of the other. CONTRIBUTING's rule is that a number is counted or it is not published, and these counts belong to CAL FIRE's files, which change underneath us. Nothing added here says how many records, how many incidents, or how much is covered. No og:image either: this repository ships no image, and twitter:card is summary, which promises none.

Why a missing canonical matters more here

These pages are one of six project sites on the shared chelseakr.github.io origin, served from paths rather than domains of their own. A canonical of / is not this site's root; it is a different address, it 404s, and all six sites would claim it. test_no_published_link_is_rooted_at_the_server already holds that property for href but cannot catch a canonical, because a canonical is an absolute URL rather than a rooted path.

The gate

The parser in tests/test_pages_html.py recorded name= metas only, so every property="og:..." tag was invisible to it and <link rel="canonical"> is not a meta tag at all. It now records both. The existing title-and-description checks were extended, not joined by a parallel SEO suite. tests/test_published_site.py gains the same assertion over the committed bytes.

break failure
canonical line deleted index.html has no canonical URL
SITE_URL set to the bare origin index.html points at the shared origin, which is a different site
CANONICAL_PATH collapsed to one URL assert 1 == 3
the original defect restored, one shared description assert 1 == 3
committed site/index.html canonical hand-edited to the origin index.html canonicalises to 'https://chelseakr.github.io/'

Restored, make verify exits 0, including html-validate, axe, the Playwright reflow specs and the determinism check.

make site was run from the real data

  • A make site on the unmodified tree first produced an empty diff, so the committed figures already matched data/raw/.
  • After the change, the site/ diff is head-only: every changed line is a canonical, og:*, twitter:card or description tag. Not one published figure moved.

Not changed

No repository setting, Pages config, ruleset or DNS. No sitemap.xml or robots.txt was added: robots.txt under a project path is inert, because only the origin root is honoured and https://chelseakr.github.io/robots.txt 404s.

🤖 Generated with Claude Code

`render.py` hardcoded a single sentence into the head of all three pages:
"Coverage and completeness counts over California's public wildfire datasets.
Unofficial." A search result for the DINS page and a search result for the
perimeters page were therefore indistinguishable, and neither said what its own
page counts. `page()` now takes a description per page, each restating what that
page already says of itself in its own standfirst.

The descriptions state NO count. The committed `site/` is built from `data/raw/`
and the test build is built from the fixtures, so a figure hardcoded in
`render.py` would be true of one build and false of the other. CONTRIBUTING's
rule is that a number is counted or it is not published, and these counts belong
to CAL FIRE's files, which change underneath us. Nothing here says how many
records, how many incidents or how much is covered.

There was also no canonical, and no Open Graph, on any page. These pages are one
of six project sites on the shared `chelseakr.github.io` origin, served from
PATHS rather than domains of their own, so the canonical a single-domain habit
produces ("/") is not this site's root: it is a different address, it 404s, and
all six sites would claim it. A crawler that believes them folds six unrelated
projects into one document. `SITE_URL` and `CANONICAL_PATH` are keyed by
`active`, the value each page already uses to mark its own nav entry, so the
canonical and the highlighted tab cannot disagree about which page this is.

No `og:image`: this repository ships no image, and inventing one would be adding
the first. `twitter:card` is `summary`, which promises none.

The page parser in `tests/test_pages_html.py` could not see any of this. It
recorded `name=` metas only, so every `property="og:..."` tag was invisible to
it, and `<link rel="canonical">` is not a meta tag at all. It now records both,
and the existing title-and-description checks were extended rather than joined
by a parallel SEO suite. `tests/test_published_site.py` gains the same assertion
over the COMMITTED bytes, beside the rooted-link check that already holds the
same subpath property for `href` and cannot catch a canonical, because a
canonical is an absolute URL rather than a rooted path.

Observed, by breaking it: canonical line deleted; `SITE_URL` set to the bare
origin; `CANONICAL_PATH` collapsed so all three pages share one canonical; and
the original defect restored, one description for all three. A fifth, hand
editing the committed `site/index.html` canonical to the shared origin, fails
the published-bytes check. Restored, `make verify` exits 0, including
html-validate, axe, the Playwright reflow specs and the determinism check.

`make site` was run from the real `data/raw/` files. Its diff is head-only:
every changed line is a `canonical`, `og:*`, `twitter:card` or `description`
tag, and not one published figure moved. A `make site` on the unmodified tree
beforehand produced an empty diff, so the committed figures already matched the
data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ChelseaKR
ChelseaKR force-pushed the seo/per-page-canonical-and-description branch from fb96c9e to c4422eb Compare August 29, 2026 16:47
@ChelseaKR
ChelseaKR merged commit 8a7eeba into main Aug 29, 2026
5 checks passed
@ChelseaKR
ChelseaKR deleted the seo/per-page-canonical-and-description branch August 29, 2026 16:47
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