Skip to content

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

Merged
DavidBabinec merged 2 commits into
CoreBunch:mainfrom
mostafasadeghidev:fix/interpolate-entry-tokens-in-page-meta
Aug 30, 2026
Merged

DavidBabinec merged 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 <[email protected]>
…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 <[email protected]>
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 <[email protected]>
@DavidBabinec
DavidBabinec merged commit 18bcbe9 into CoreBunch:main Aug 30, 2026
3 checks passed
mostafasadeghidev added a commit to mostafasadeghidev/Instatic that referenced this pull request Sep 4, 2026
Fourteen files collided across 51 upstream commits. Most collided for the
happy reason: CoreBunch#333, CoreBunch#337, CoreBunch#349, CoreBunch#350 and CoreBunch#353 merged upstream, so this
stack's own code came back reviewed and reorganised. Those took upstream's
copy — the html tag/attribute helpers moved to `@core/htmlAttributes`, the
site-import error classes to their own `errors.ts`, and the lint rule now
reads `CONTENT_ACCESS_MODE_PERMISSIONS` instead of a local table.

Three needed a hand.

MIGRATIONS. Upstream took slot `025` for `025_remove_non_owner_role_management`
while this fork's `025_data_tables_created_by_plugin` is already recorded by
two live installations. Both entries are kept, prefix collision and all. The
runner keys on the full id string, so both run exactly once and neither masks
the other; the prefix is a naming convention, not the key. Renumbering ours
was the one move that had to be refused: the ALTER carries no guard — SQLite
has no `add column if not exists`, as migration 010 already documents — so a
new id would re-run it against a column that exists and take the boot down.
Both dialect files list the two in the same order, which is all the parity
gate actually asserts.

render.ts. Three hunks, resolved one at a time rather than by taking a side.
Two are upstream's (the meta doc comment, which is this stack's own CoreBunch#337 text
now that it landed; and a formatting change). The third keeps ours:
`composeTemplateContext(page, site, options.templateContext, options.mediaAssets)`
is CoreBunch#334's plumbing, still unmerged and still the only thing that turns a bare
asset id into a URL for a non-loop binding. Taking upstream there type-checks,
builds clean, and fails exactly one test — which is how it slipped through on
2026-08-23.

contentSchemas.ts. An adjacent-insert collision sharing one `/**` opener, not
a pick-a-side: `CONTENT_ACCESS_MODE_PERMISSIONS` and `OWN_CREATED_TABLES_MARKER`
are orthogonal and both are kept, each with its own doc block.

Two merge artefacts the compiler caught: `ImportStep.tsx` ended up importing
`rankWarning` from the shared module upstream extracted it into AND keeping a
stale local twin that predates two warning kinds; the local one is gone. And
`jsdom` / `ipaddr.js` are new upstream runtime deps — `bun install` is now
required before a rebuild.

Verified: tsc, build and lint clean. Publisher, templates, loops and
site-import 875/875 with no assertion failures, including the custom-media-cell
guard. fork-stack-capabilities, migration-parity and both size gates 37/37.
Across server/ and admin/ the only assertion failures are the five already red
before this merge (cacheLayout ×2, toolbar ×2, siteExplorerPanel); the rest of
the noise is Windows temp-file locking in test teardown.

Co-Authored-By: Claude Opus 5 <[email protected]>
@mostafasadeghidev
mostafasadeghidev deleted the fix/interpolate-entry-tokens-in-page-meta branch September 30, 2026 07:16
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.

2 participants