Skip to content

fix(publish): interpolate entry tokens in page meta title and description - #337

Open
mostafasadeghidev wants to merge 2 commits into
CoreBunch:mainfrom
mostafasadeghidev:fix/interpolate-entry-tokens-in-page-meta
Open

fix(publish): interpolate entry tokens in page meta title and description#337
mostafasadeghidev wants to merge 2 commits into
CoreBunch:mainfrom
mostafasadeghidev:fix/interpolate-entry-tokens-in-page-meta

Conversation

@mostafasadeghidev

Copy link
Copy Markdown
Contributor

What

{currentEntry.*} / {page.*} / {site.*} tokens in the meta title, meta description, and the page-title fallback were published verbatim: buildDocumentMetaTags read settings.metaTitle ?? page.title ?? site.name raw, so every CMS entry route (e.g. /articles/:slug) rendered the template page's static <title>. It now receives the composed TemplateRenderDataContext and runs the title + description through interpolateTokens before escapeHtml.

Why

SEO: entry routes shipped the template's own title (e.g. <title>Articles — Template</title> on every article) instead of a per-entry one. The data needed to fix it — the entryStack the server already seeds for dynamic bindings — was composed a few lines above and simply not threaded into the meta builder.

Impact

Authors can write {currentEntry.name} | Acme in the page's meta title (or title a template page with tokens) and get per-entry <title> / description. Plain pages resolve entry tokens to '' or the token's own |fallback; token-free strings are byte-identical via the containsTokens fast path. slugToFilename needs no treatment (entry-route paths come from the route table; the whitelist-strip already sanitizes brace syntax) and the builder has no OG tags to treat.

Verification

  • bun test src/__tests__/publisher/ src/__tests__/templates/ — 484 pass, 0 fail (5 new meta-interpolation tests, including XSS escaping of interpolated values)
  • bun run build (tsc + vite) and bun run lint — clean
  • Full bun test on the Windows dev box: 318 pre-existing environment failures (EBUSY tmp-db cleanup / EPERM symlink), verified identical on the base commit with this diff stashed

…tion

{currentEntry.*} / {page.*} / {site.*} tokens in site metaTitle,
metaDescription, and the page-title fallback were published verbatim
(and pre-escape, effectively static), so every CMS entry route rendered
the template page's static <title> instead of a per-entry SEO title.

buildDocumentMetaTags now receives the composed TemplateRenderDataContext
(the same frames dynamic text bindings resolve against) and runs the
title + description through interpolateTokens before escapeHtml. Entry
routes resolve {currentEntry.name} from the entryStack seeded by
renderPublishedDataRowTemplate; plain pages resolve entry tokens to ''
(or the token's own |fallback) with no leaked placeholder syntax, and
token-free strings are untouched via the containsTokens fast path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ompose

CoreBunch#419 landed while this sat open and rewrote the same function. It gives
`buildDocumentMetaTags` a `documentMeta` override so a post-type entry's
authored `seoTitle` / `seoDescription` reach the published `<head>`.

That is a different feature from this branch's, not a replacement. CoreBunch#419
answers "this row has its own title"; this answers "every row of this
template shares one pattern". An author wants whichever suits the site,
and on most sites both: a handful of rows worth naming individually,
the rest covered by `{currentEntry.name} | Acme`.

So the two compose rather than pick a winner. The fallback chain is
upstream's — override → metaTitle → page.title → site.name — and whatever
wins is then interpolated against the render context. A row's authored SEO
title may itself carry tokens, which neither branch offered alone.

418 publisher tests green, covering both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mostafasadeghidev added a commit to mostafasadeghidev/Instatic that referenced this pull request Aug 22, 2026
Seven files collided, and six of them for a happy reason: CoreBunch#348 merged, so
upstream now carries this stack's own loop-filter code. Those took
upstream's copy — which is ours plus what review added on top of it:
`isCellComparableField` and `withoutCellFilter`, field pickers that hide
fields a cell condition cannot address, clearer operator labels, and a
dual-dialect test. `cellFilterMatches` went with it; nothing imported it.

The seventh is `render.ts`, where CoreBunch#419 gave `buildDocumentMetaTags` a
`documentMeta` override so an entry's authored SEO title reaches the
`<head>`. That answers "this row has its own title"; the stack's CoreBunch#337
answers "every row of this template shares one pattern". Both are wanted,
so the fallback chain is upstream's and whatever wins is then interpolated
against the render context — a row's authored SEO title may itself carry
tokens, which neither side offered alone.

The README's pending table drops CoreBunch#348 into the landed section.

Verified: tsc, build and lint clean; publisher and templates 505/505;
fork-stack-capabilities 12/12. The remaining noise is Windows temp-file
locking in test teardown, which reproduces on an untouched upstream
checkout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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