Post-release hardening: SEO correctness, reliability, chrome autosave - #2
Merged
Merged
Conversation
…uards Three post-release review fixes for the SEO-critical path: - Full-page cache now preserves X-Robots-Tag: PageCacheService stored only Content-Type, so a noindex page could be served indexable from a HIT. - Sitemap/llms.txt caches are invalidated on content mutations: every flushAll() (create/update/publish/delete/scheduled publish) now also drops the SEO endpoint caches via the new SeoCacheKeys registry — previously a just-published page stayed out of the sitemap for the full TTL. The sitemap index is cached too and advertises the real latest content change per locale instead of now() on every crawl. - Meta length guards: meta_description capped at max:1000 in all six admin input paths, and SeoResolverService clamps title/description at render time (255/500) so no data source can ship multi-kilobyte meta tags. The per-entity SeoOverride lookup is also cached now (busted by model events on save/delete), removing a query from every public render. Tests: SeoCacheHardeningTest covers the HIT robots header, service-level cache busting, honest lastmod, clamping, and override-cache invalidation. Full suite green (170); phpstan clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- BlockSchemaValidator now has per-type data schemas for the five landing leaf blocks (stats/features/testimonial/pricing items + hero fields), so malformed payloads are rejected server-side instead of stored as-is. - Self-update health check gains CMS_UPDATE_HEALTH_STRICT: when on, an unreachable health endpoint counts as failed and triggers rollback (off by default — hosts with blocked loopback must not roll back healthy updates). An explicitly configured health_check_url now also works under the testing env, making the rollback path exercisable. - The previously-untested failure paths are now covered: a 5xx health check auto-rolls back, strict-mode unreachable rolls back, default unreachable is tolerated (FilesystemUpdaterSharedHostingTest). - DB restore: restoreSnapshot no longer silently skips a recorded dump whose file is missing — that's a data trap (code restored without its DB); it throws instead. CoreBackupRestoreTest exercises the sqlite dump+restore end-to-end through the public API for the first time. Full suite green (181); phpstan clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The theme/chrome builder was the last editor without draft protection: a crashed tab lost every unsaved header/footer/search edit. It now snapshots state to localStorage (debounced through the existing syncPayload funnel, gated until init completes), offers to restore a differing draft on load, force-saves on beforeunload, and — mirroring the page/post editors — drops the stale snapshot on the load right after a successful save via a chrome_saved flash surfaced as boot.justSaved. Tests: ChromeAutosaveSignalTest covers the flash → boot signal round-trip; the JS block passed an adversarial scope/TDZ/flow review (7/7). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The composite status/published_at index migration now guards with Schema::hasIndex both ways, so a hand-created index (or a kept index with a lost ledger row) no longer hard-fails a deploy. - ResolvedChromeViewModelFactory memoizes menu link-target lookups per build: header/footer/legal menus referencing the same page cost one query instead of one per occurrence. - AuthSmokeTest gives the admin login gate its first dedicated coverage: guest redirect, wrong password, success + last_login_at, blocked account, logout. Full suite green (187); phpstan clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A follow-up audit of PR #2's own diff surfaced defects the fixes introduced; all are corrected here before merge: - [HIGH] Rollback mislabeled as failed: apply()'s try wrapped both restoreSnapshot() and the post-restore health check, so under strict mode a whole-window endpoint outage marked a SUCCESSFUL restore as status=failed/restore_status=failed — panicking an operator over a healthy site. rollback() now fails only when restoreSnapshot() itself throws; a post-restore health-check throw yields status=rolled_back / restore_status=health_unverified and still resyncs installed_version to the reverted code. (Also fixes the installed_version desync + double-jeopardy.) - [HIGH] Stale SeoOverride cache: ContentEntityCleanupObserver purges seo_overrides via a query-builder delete, which fires no model event, so the new resolver override-cache was never busted on parent delete — a reused id could serve a deleted override's meta/robots for the full TTL. The observer now forgets the override keys across locales. - [MED] Meta clamp used Str::limit (display width), over-truncating CJK titles/descriptions that passed max:N validation; now clamps by character count (mb_substr). META_DESCRIPTION_MAX raised 500→1000 to match the write-path validation ceiling so saved values aren't silently cut. - [MED] Landing block schemas now cap item count (60) and per-field string length (2000) — an authenticated writer could otherwise store a multi-megabyte block expanded into every cached render. Tests: whole-window-outage rollback, override-cache bust on delete, char-vs-width clamp, block count/length caps. Full suite green (193); phpstan clean. Co-Authored-By: Claude Fable 5 <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.
Summary
Closes the actionable items from the post-v1.1.0 project review (3-agent verified backlog). Four commits, each a reviewed cluster:
SEO correctness (29344f1)
X-Robots-Tag— a noindex page could previously be served indexable from a cache HIT (onlyContent-Typewas stored).flushAll()) now also drops the SEO endpoint caches via the newSeoCacheKeysregistry; previously a just-published page stayed out of the sitemap for the full TTL. The sitemap index advertises the real latest content change per locale instead ofnow()on every crawl.meta_descriptioncapped (max:1000) in all six admin input paths;SeoResolverServiceclamps title/description (255/500) at render time. The per-entitySeoOverridelookup is now cached (busted by model events).Reliability (0b83807)
CMS_UPDATE_HEALTH_STRICT— opt-in: an unreachable health endpoint counts as failed and triggers rollback (default stays tolerant for hosts with blocked loopback). The previously-untested failure paths are covered: 5xx → auto-rollback, strict unreachable → rollback, default unreachable → tolerated.Chrome builder autosave (da409d0)
The last editor without draft protection: snapshots to localStorage, restore prompt on load,
beforeunloadflush, and stale-snapshot drop after a successful save (chrome_savedflash →boot.justSaved). JS passed an adversarial scope/TDZ review (7/7).Quick wins (33a0150)
Idempotent index migration (
Schema::hasIndexguards), per-build memo for menu link targets, first dedicated auth-gate coverage (AuthSmokeTest).Consciously skipped (with rationale):
platform.phpbump to 8.3 (repo supports 8.2 perrequire ^8.2+ CI matrix),SeoSetting::global()caching (one PK query, memo risks test isolation), admin RU strings i18n (admin is RU-first throughout — needs a dedicated i18n pass), phpstan baseline cleanup / image pipeline / vitest de-IIFE (dedicated sessions per prior agreement).Test plan
SeoCacheHardeningTest,LandingBlockSchemaTest,FilesystemUpdaterSharedHostingTest(+3),CoreBackupRestoreTest,ChromeAutosaveSignalTest,AuthSmokeTest.composer analyse(phpstan L5) clean, Pint clean,node --checkclean on the edited JS.🤖 Generated with Claude Code