TigerSEO Phase 1: the head registry#7
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.phtmlrenders through TigerZF'sheadTitle/headMeta/headLink(shipped forever, never used) instead of a hardcoded<title>+ rawpageHead. Seeds the title from$this->title/siteName, so a<title>always renders — with or without an SEO module.pageHeadstays as the raw escape hatch.PageControllerno 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
seomoduleSeo_Service_Head— the single seam: maps a page's unifiedmeta.seo→ the head containers (title, description, robots [emitted only whennoindex/nofollow], canonical [explicit or self-referencing]). An$overridesarg 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
meta.seo.description(was flat); migration0032moves existing rows — one shape, not two.Tiger_Db_Migratorgains PHP-callable steps (fn($db)alongside SQL strings, split by type) — for JSON data transforms SQL can't express cleanly.Verified
seo.description, blog rows + empty descriptions handled, existingseokeys preserved). ZF1 head-helper API (set/setName/headLink/count) validated against real TigerZF.pagetable), the layout head adoption (/renders 200), and theseomodule loading without breaking boot/serve.Phase 2 (OG/Twitter,
og_image_id→media, hero-as-OG), Phase 4 (robots/sitemap) follow.🤖 Generated with Claude Code