Skip to content

feat: build-gate validators (enforce the completeness checklist + caught a favicon bug)#1

Merged
ProfessorManhattan merged 4 commits into
mainfrom
feat/build-validators
Jun 24, 2026
Merged

feat: build-gate validators (enforce the completeness checklist + caught a favicon bug)#1
ProfessorManhattan merged 4 commits into
mainfrom
feat/build-validators

Conversation

@ProfessorManhattan

Copy link
Copy Markdown
Contributor

What

Adds the first slice of the build-validators enforcement layer — scripts/build_validators.mjs orchestrator + scripts/validators/ (validate-links, validate-route-metadata, validate-assets), run via npm run validate. This is the machine enforcement for the 62-point website-completeness-checklist (spec: heymegabyte-claude-skills/rules/build-validators-manifest.md) — a checklist point with no validator is an unenforced point, and today the template ships only validate-brand.mjs.

Verified — and it caught a real bug

Run against this template's own dist/:

  • validate-links — no dead internal links
  • validate-route-metadata — every page has title + meta description
  • validate-assetsindex.html references 5 favicons that exist in neither public/ nor dist/ (favicon.ico, favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png, safari-pinned-tab.svg). Every cloned site currently ships 404'd favicons — violates the favicon-set completeness gate.

Scope / safety

  • Left as a standalone npm run validate step (NOT wired into postbuild) so it doesn't block today's build until the favicon issue is fixed. Once the template is clean, wire it into CI/lefthook + the deploy gate.
  • Pure node, no deps. Additive only.

Next

More validators from the spec (word-count, faq, skip-link, axe, legal, consent-gate) + the favicon fix, then promote npm run validate to a blocking gate.

🤖 Generated with Claude Code

Brian Zalewski and others added 2 commits June 20, 2026 21:32
…nically)

Adds scripts/build_validators.mjs orchestrator + scripts/validators/ (validate-links,
validate-route-metadata, validate-assets) — the first slice of the build-validators spec
(heymegabyte-claude-skills rules/build-validators-manifest.md). Run via 'npm run validate'
after build; exits non-zero on any build-break so no site deploys with an unenforced
completeness point.

Verified against this template's own dist/ — validate-links + validate-route-metadata pass,
and validate-assets immediately caught a REAL bug: index.html references 5 favicons
(favicon.ico, favicon-16/32, apple-touch-icon, safari-pinned-tab) that exist in neither
public/ nor dist/, so every cloned site ships 404'd favicons (violates the favicon-set gate).
Left as a separate 'npm run validate' step (not wired into postbuild) until that favicon
issue is fixed, so it doesn't block the build today.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ient-only per-route head)

Caught on this template: 20 sitemap routes all serve one index.html (SPA fallback /* →
/index.html) with NO per-route server head — useSEO.ts is client-only, functions/ has only
API routes (no HTMLRewriter), no prerendered route HTML. Crawlers read the HOMEPAGE head on
every URL → the site collapses to one indexable URL (the exact njsk.org incident always.md
warns is a build-fail).

WARN level (not build-break) because per-route head MAY be injected by the projectsites.dev
PLATFORM Worker at serve time (outside this repo) — so it's a definite bug for STANDALONE
deploys (current _redirects = CF Pages SPA fallback) but possibly handled behind the platform.
Either way: verify per-route server head on the live URLs, or add vite-ssg prerender / a Worker
HTMLRewriter keyed on getMeta(pathname) to the template.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ProfessorManhattan

Copy link
Copy Markdown
Contributor Author

⚠️ Two real findings the new validators caught on this template's own build

1. seo.client_only_head (CRITICAL, warn-level pending architecture confirmation) — the build is a client SPA: 20 sitemap routes all serve one index.html (_redirects: /* → /index.html 200), per-route SEO is client-only (src/hooks/useSEO.ts), functions/ has only API routes (no HTMLRewriter), and nothing is prerendered. Standalone-deployed, every URL ships the homepage <head>/canonical → SEO collapse (the njsk.org anti-pattern always.md calls a build-fail). If the projectsites.dev platform Worker injects per-route head at serve time, this is handled — please confirm; otherwise add vite-ssg prerender or a Worker HTMLRewriter keyed on getMeta(pathname).

2. assets.missing (build-break)index.html references 5 favicons absent from public/+dist/ (favicon.ico, 16/32, apple-touch-icon, safari-pinned-tab). Every cloned site ships 404'd favicons. Needs favicon generation (keyed on _brand.json) or committed defaults.

Both are exactly the kind of completeness gap the validators exist to stop shipping. validate-links + validate-route-metadata pass.

…ONFIRMED live)

curl of the raw served HTML (no JS) on projectsites.dev itself proves the collapse — the
platform Worker does NOT inject per-route head:
  /         → <title>ProjectSites — We deliver websites in minutes</title>  canonical /
  /about    → SAME title, canonical /   (should be /about)
  /contact  → SAME title, canonical /   (should be /contact)

Every route serves the homepage head + canonical=/ → the whole site is one indexable URL.
No longer architecturally-ambiguous → build-break. Every site cloned from this template
has the same collapse until per-route server head is added (vite-ssg prerender or a Pages
Function/_middleware HTMLRewriter keyed on getMeta(pathname)).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ProfessorManhattan

Copy link
Copy Markdown
Contributor Author

🔴 SEO-collapse EMPIRICALLY CONFIRMED — live on projectsites.dev itself

Curled the raw served HTML (no JS) — the platform does NOT inject per-route head:

URL <title> canonical
/ ProjectSites — We deliver websites in minutes /
/about (same homepage title) /
/contact (same homepage title) /

Every route ships the homepage <head> + canonical=/ → Google/ChatGPT/Perplexity see one indexable URL for the whole site. Promoted validate-ssr-head warn→error (build-break) — confirmed, not ambiguous. Every site built from this template has this.

Fix (one of): (a) vite-ssg prerender each route to its own HTML with per-route head; or (b) a Pages Function functions/_middleware.ts HTMLRewriter that rewrites title/desc/canonical/OG per pathname from a shared getMeta(pathname) map (the projectsites-cloudflare-first prescribed approach — less invasive than SSG). Happy to implement either as a tested PR.

@ProfessorManhattan

Copy link
Copy Markdown
Contributor Author

Fix scoping — why it's architectural (and the recommended path)

Dug into implementing the per-route head fix. The blocker: per-route meta is currently runtime-only React. Each page calls useSEO({ title, description }) with template literals (${brand.business.name}, {ABOUT_META_DESCRIPTION} placeholders) that only resolve when React runs in the browser. There is no server-resolvable route→meta map.

Consequences for each fix path:

  • Build-time prerender (no React): can resolve per-route title (Label — {name}) + canonical (origin+path) — which fixes the worst collapse symptom (every route currently says canonical=/). But it cannot resolve per-route descriptions (runtime literals) without running React.
  • vite-ssg (recommended): runs React during build → correct per-route title + description + canonical + OG, no duplication. Cost: entry-point change + ensuring page components are SSR-safe (no window/document at module scope).
  • Pages Function _middleware.ts HTMLRewriter: needs a server-side getMeta(pathname) map — i.e. the per-route meta must first be extracted from the pages into a shared, server-resolvable module (src/seo/routes.ts). That extraction is the real work; the middleware is then small.

Recommendation: extract route meta into a central src/seo/routes.ts (single source of truth — pages import it instead of inlining), then either vite-ssg (cleanest) or the middleware reads it. Both need that central map first.

This is a focused 1–2h architectural change with real edge/SSR testing — I didn't want to blind-ship a partial (titles-only) or untested fix to the serving path of every customer site. Say go and I'll do the central-map + vite-ssg PR (tested: build → curl each route → assert unique server-rendered title/canonical). Meanwhile validate-ssr-head (this PR) blocks anyone shipping the collapse unknowingly.

@ProfessorManhattan ProfessorManhattan merged commit 595c25d into main Jun 24, 2026
4 of 5 checks passed
ProfessorManhattan added a commit that referenced this pull request Jun 24, 2026
index.html referenced favicon.ico, favicon-16/32.png, apple-touch-icon.png, safari-pinned-
tab.svg — none existed in public/ (caught by validate-assets, PR #1). Every cloned site
shipped 404'd favicons (violates checklist #23).

scripts/generate-favicons.mjs generates the full set brand-colored from _brand.json (brandHue
→ HSL→RGB) with ZERO external deps — pure node:zlib for valid RGBA PNGs (rounded square),
a BMP/PNG-in-ICO writer, and SVG (rich, brand bg + business initial) + safari mask. Wired
into prebuild so it regenerates per brand and vite copies public/ → dist/. Also added an
SVG-favicon <link> (modern browsers). Committed brand-default placeholders so the repo is
clean even before a per-brand rebuild.

Verified on a clean build: all 6 favicons land in dist/, 0 dead refs, valid PNG/ICO magic
bytes. validate-assets (PR #1) now passes the favicon gate.

Co-authored-by: Brian Zalewski <hey@megabyte.space>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ProfessorManhattan pushed a commit that referenced this pull request Jun 25, 2026
* origin/main:
  fix: generate the favicon set (was 5 dead refs → 404 on every site) (#3)
  feat: build-gate validators (enforce the completeness checklist + caught a favicon bug) (#1)

# Conflicts:
#	package.json
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