Skip to content

TigerSEO Phase 1: the head registry#7

Merged
WebTigers merged 1 commit into
mainfrom
feat/seo-phase-1
Jul 17, 2026
Merged

TigerSEO Phase 1: the head registry#7
WebTigers merged 1 commit into
mainfrom
feat/seo-phase-1

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

First phase of the native SEO module (design-of-record in modules/seo/{README,ARCHITECTURE,FEATURES,AGENTS}.md). Makes the data Tiger already collects but throws away actually reach the <head>.

Core hygiene (public layout only — the "gate")

  • themes/puma/layouts/scripts/layout.phtml renders through TigerZF's headTitle/headMeta/headLink (shipped forever, never used) instead of a hardcoded <title> + raw pageHead. Seeds the title from $this->title/siteName, so a <title> always renders — with or without an SEO module. pageHead stays as the raw escape hatch.
  • Core PageController no longer synthesizes a <meta name="description"> string; for self-contained (layout_key) CMS pages that bypass the theme layout, it splices the populated head registry into their own </head>.

New bundled seo module

  • Seo_Service_Head — the single seam: maps a page's unified meta.seo → the head containers (title, description, robots [emitted only when noindex/nofollow], canonical [explicit or self-referencing]). An $overrides arg fills blanks (blog passes the excerpt as the description fallback) without the resolver ever knowing content types.
  • Seo_Plugin_Head — CMS pages (PageDispatch → cms_page_id) at preDispatch; blog articles call the resolver directly, class_exists-guarded. Adds no registry of its own — uninstall it and the head still renders, with less.

Unification + migration

  • CMS save writes meta.seo.description (was flat); migration 0032 moves existing rows — one shape, not two.
  • Tiger_Db_Migrator gains PHP-callable steps (fn($db) alongside SQL strings, split by type) — for JSON data transforms SQL can't express cleanly.

Verified

  • All files lint. Migration transform unit-tested (CMS flat→seo.description, blog rows + empty descriptions handled, existing seo keys preserved). ZF1 head-helper API (set/setName/headLink/count) validated against real TigerZF.
  • Smoke will exercise: the callable-migration path (0032 on an empty page table), the layout head adoption (/ renders 200), and the seo module loading without breaking boot/serve.
  • Full SEO rendering with live page/article data is browser-only (admin/public render is auth-gated / outside smoke) — I'll verify on tiger-dev once merged + released.

Phase 2 (OG/Twitter, og_image_id→media, hero-as-OG), Phase 4 (robots/sitemap) follow.

🤖 Generated with Claude Code

…dy collects

Makes Tiger's <head> a registry (TigerZF's own headTitle/headMeta/headLink, which
shipped forever and were never used) instead of a hardcoded <title> + a raw
pageHead echo. The data Tiger already asks authors to type — a meta description on
CMS pages, seo.{title,description,canonical} on blog articles — finally renders.

Core hygiene (public layout only):
- themes/puma/.../layout.phtml renders headTitle/headMeta/headLink; seeds the title
  from $this->title/siteName so a <title> always renders with or without an SEO
  module. Keeps pageHead as the raw escape hatch.
- core PageController no longer synthesizes a <meta name="description"> string; for
  self-contained (layout_key) CMS pages that bypass the theme layout, it splices the
  populated head registry into their own </head>.

New bundled `seo` module (modules/seo/):
- Seo_Service_Head — the one seam: maps a page row's meta.seo → the head containers
  (title, description, robots [emitted only when noindex/nofollow], canonical
  [explicit or self-referencing]). An $overrides arg lets a caller fill blanks
  (blog passes the excerpt as the description fallback) without the resolver knowing
  content types.
- Seo_Plugin_Head — populates the head for CMS pages (dispatched via PageDispatch →
  cms_page_id) at preDispatch. Blog articles render via their own controller and
  call the resolver directly, class_exists-guarded so blog never hard-depends on SEO.
- Adds no head registry of its own; uninstall it and the head still renders, less.

Unification + migration:
- CMS save now writes meta.seo.description (was flat meta.description); migration 0032
  moves existing rows. One shape, not two — no tolerant reader kept around.
- Tiger_Db_Migrator gains PHP-callable steps (fn($db)) alongside SQL strings (split by
  type), which the JSON data migration needs.

Verified: all files lint; the migration transform unit-tested (CMS flat→seo.description,
blog rows + empty descriptions handled, existing seo keys preserved); the ZF1 head-helper
API (set/setName/headLink/count) validated against real TigerZF. Full SEO rendering with
live pages is browser-verified post-deploy (admin/public render is outside the smoke suite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@WebTigers
WebTigers merged commit cc98831 into main Jul 17, 2026
5 checks passed
@WebTigers
WebTigers deleted the feat/seo-phase-1 branch July 17, 2026 10:06
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