diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24dfccaf4..9669b585a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,10 @@ This project is pre-1.0. Breaking changes may appear in minor or patch releases
- Added a condition to data-row loops so a list can show a subset of a table rather than always its newest rows — pick one of the table's own fields and require it to be checked, unchecked, equal to a value, or to have any value at all. A relation field offers its rows by name instead of asking for an id. The condition applies on the canvas, on published pages, and in the "load more" endpoint, and the item count follows it so pagination never advertises rows the page drops.
- Added the table's own fields to a data-row loop's "Order by" list, so a list can follow a real date, title, or rank stored in the row instead of only the row's built-in columns. Values compare as text, which sorts ISO dates chronologically.
+### Content and publishing
+
+- Fixed the SEO title and SEO description fields on a post so they reach the published page. Both were editable in the Content settings panel but never emitted anything: an entry's `
` always showed its plain title, and no description tag was written at all. An authored value now drives `` / `` and outranks the site-wide Meta Title and Meta Description, on the published page and in the Content editor's Live preview alike. The on-page `{page.title}` and `{currentEntry.title}` bindings keep rendering the entry's real title.
+
## 0.0.16 - 2026-08-11
### Media and integrations
diff --git a/docs/e2e/feature-matrix.md b/docs/e2e/feature-matrix.md
index 407196479..130281443 100644
--- a/docs/e2e/feature-matrix.md
+++ b/docs/e2e/feature-matrix.md
@@ -254,6 +254,7 @@ MEDIA-007 note: unsafe SVG upload sanitization and public `/uploads` serving are
| CONTENT-007 | P2 | partial | AI | Use the content AI assistant panel | AI chat permission | Content AI panel | No-provider guidance or chat flow is understandable | provider failure, write-tool permission leaks |
| CONTENT-008 | P1 | ✅ | Custom fields | Edit custom post fields and use them in Site templates | Logged in | Data Posts fields → Content settings → Site binding picker → publish post route | Custom field appears in the Content entry settings panel, persists after reload, is offered as a scoped currentEntry binding, resolves in canvas preview, and renders on the public post route | stale table metadata, unresolved custom tokens, save/publish ordering |
| CONTENT-009 | P1 | ✅ | Entries | Keep the stored title in the sidebar when an entries list load resolves after a create | Logged in | Content page → New post while the entries list request is still in flight | Sidebar row shows the stored "Untitled" while the editor keeps its blank title field | request ordering, editor-only row views leaking into the list |
+| CONTENT-010 | P1 | ✅ | Entries | Publish an entry whose SEO title and SEO description differ from its title | Posts entry template published | Content settings panel → SEO title / SEO description → publish post → public `/posts/` | Served `` carries the authored `` and `` while the on-page H1 keeps the entry's own title | site-level Meta Title shadowing the entry, SEO override leaking into `{page.title}`, blank SEO field overriding with an empty value |
CONTENT-003 note: slash-menu Heading 2 and Data token placeholder insertion with save/reload persistence are automated in `content.e2e.ts`; media picker insertion and sanitization edge cases remain lower-level or future browser coverage.
@@ -267,6 +268,8 @@ CONTENT-008 note: `content.e2e.ts` adds a custom text field to the system Posts
CONTENT-009 note: `content-create-race.e2e.ts` holds the first entries-list GET until the create POST has landed, forcing the ordering that previously merged the editor's blank-title view into the sidebar and rendered a nameless row. Verified to fail without the fix and pass with it; `contentAdmin.test.tsx` pins the same path at unit level.
+CONTENT-010 note: `content-seo-meta.e2e.ts` authors and publishes a Posts entry template, fills the Content settings panel's SEO title and SEO description with values distinct from the entry title, publishes the post, and reads the served HTML on the anonymous public route. Verified to fail on the pre-fix build (`` showed the plain entry title and no description tag was emitted). Entry-vs-site-setting precedence and the `{page.title}` binding guard are pinned at render level in `publicRendering.test.ts`.
+
## AI Workspace
| ID | Priority | Auto | Area | User Goal | Setup | Path | Expected Outcome | Watch For |
diff --git a/docs/e2e/feature-validation.tsv b/docs/e2e/feature-validation.tsv
index 3f1910e0a..551cde0ef 100644
--- a/docs/e2e/feature-validation.tsv
+++ b/docs/e2e/feature-validation.tsv
@@ -146,3 +146,4 @@ SPOT-011 Keyboard-only Spotlight execution As a keyboard user, I want to open, s
SPOT-012 Spotlight reduced-motion behaviour As a user who prefers reduced motion, I want Spotlight open, close, and nested-scope transitions to avoid slide animation so the palette respects my OS accessibility setting. With reduced motion emulated, opening Spotlight and drilling into a nested scope avoids slide or translateY animation and uses opacity-only or instant transition behavior. Reduced-motion preference ignored; slide animation still plays; nested scope animates horizontally; page jumps on open; tests miss animation due timing. CSS and state transitions branch on prefers-reduced-motion while preserving palette functionality. tests/e2e/command-palette.e2e.ts; src/admin/spotlight; spotlight CSS modules The regression uses browser media emulation and style assertions; it does not visually diff every animation frame. Happy: reduced-motion palette opens and drills into scope without slide animation. Error: transform animation present. Boundary: nested scope transition. Invalid: normal motion mode still allowed separately. Permission: unaffected by capabilities. Performance: no delay introduced. Mobile: reduced-motion media emulation only. Focused capability/responsive/spotlight Playwright regression passed 2026-06-23; canonical spreadsheet row added after matrix gap discovery 0 None Verification: command-palette.e2e.ts passed inside the combined focused run. SPOT-012 covers reduced-motion behavior for palette open and nested scope transitions. 2026-06-23
SPOT-013 Spotlight high-contrast highlight visibility As a high-contrast user, I want the active Spotlight row and matched text marks to stay visually distinct so search results remain readable. With high-contrast media emulated, opening Spotlight, searching, and arrowing through results shows a visible active-row outline and readable match marks. Highlight invisible; outline too faint; match marks blend into background; keyboard active row differs from visual row; contrast mode breaks selection styling. Spotlight CSS uses tokenized high-contrast styles for active rows and marks while preserving keyboard active-state semantics. tests/e2e/command-palette.e2e.ts; src/admin/spotlight; spotlight CSS modules The regression uses browser media emulation and computed-style checks; full manual visual contrast audit remains future review. Happy: active row and match marks remain legible. Error: no visible outline or unreadable mark. Boundary: one searched result with arrow navigation. Invalid: high contrast should not hide text. Permission: unaffected by capabilities. Performance: no render delay. Mobile: media emulation only. Focused capability/responsive/spotlight Playwright regression passed 2026-06-23; canonical spreadsheet row added after matrix gap discovery 0 None Verification: command-palette.e2e.ts passed inside the combined focused run. SPOT-013 covers high-contrast active-row outline and match-mark legibility. 2026-06-23
CONTENT-009 Freshly created entry keeps its stored title in the sidebar As a content author, I want a post I just created to show its name in the sidebar so I can find it. createUntitledEntry stores "Untitled" on the server and hands the editor a blank-title view; the sidebar list renders the stored row even when an in-flight entries list load resolves after the create. List load resolving after the create; an MCP save landing during a list load; the server list omitting the new row. Rows loaded through the cmsData client with schema validation; the selection may hold an editor-local view that must never reach the list. src/admin/pages/content/hooks/useContentWorkspace.ts; src/__tests__/data/contentAdmin.test.tsx; tests/e2e/content-create-race.e2e.ts The posts rows endpoint is stateful, so a row created by POST is visible to every later GET. Happy: create a post with the list load held and the sidebar shows Untitled. Regression: without the fix the row renders nameless. Boundary: when the server list omits the row the selection is still merged in. Permission/Performance/Mobile: not applicable. Automated Playwright E2E passed; verified to fail without the fix 0 None Pins an ordering that previously decided the outcome at random. 2026-08-18
+CONTENT-010 Entry SEO title and description reach the published As a content author, I want the SEO title and SEO description I fill in for a post to appear in the published page’s so search engines show what I wrote. renderPublishedDataRowTemplate and handleRowPreview pass the row’s seoTitle/seoDescription to publishPage as documentMeta; buildDocumentMetaTags prefers them over the site-level metaTitle/metaDescription, and page.title keeps the entry’s own title for the {page.title} binding. Blank or whitespace-only SEO field; site-level Meta Title configured; entry with no SEO fields at all; collection with SEO fields disabled; Live preview vs published parity. seoTitle/seoDescription are optional post-type built-ins read through readEntrySeoOverride; a blank value is not an override; the override reaches the only, never a binding frame. server/publish/publicRenderer.ts; server/handlers/cms/data/preview.ts; src/core/publisher/render.ts; src/core/data/cells.ts; tests/e2e/content-seo-meta.e2e.ts The public entry route needs a published Posts entry template; the spec authors and publishes its own. Happy: authored SEO title and description appear in . Regression: pre-fix build emits the plain title and no description. Boundary: blank SEO field falls through to site settings. Invalid: non-string cell ignored. Permission: publishing requires step-up. Performance: n/a. Mobile: n/a. Automated Playwright E2E passed; verified to fail without the fix 0 None Closes the gap that let both SEO fields ship inert: nothing checked the published . 2026-08-20
diff --git a/docs/features/publisher.md b/docs/features/publisher.md
index 7a729b533..5084289de 100644
--- a/docs/features/publisher.md
+++ b/docs/features/publisher.md
@@ -327,8 +327,8 @@ The publisher emits `` in this order:
1. ``
2. ``
-3. `` from `page.title`
-4. `` if present in page settings
+3. `` — the entry's `seoTitle` (post-type entries only) → `settings.metaTitle` → `page.title` → site name
+4. `` — the entry's `seoDescription` (post-type entries only) → `settings.metaDescription`; omitted when neither is set
5. `` if a favicon is configured
6. `