ReviewOS

stacks/bench-review

stx: @extends silently discards the server-scope head (useSeoMeta/useHead), which is why bench injects SEO at build time

#89
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Framework finding, isolated while auditing bench against stx-standards §4. Filed here because it explains a large piece of this repo's architecture and should not be "cleaned up" by a future reader.

The claim it tests

stx-standards §4.0.1 marks useSeoMeta(config) in a <script server> block as "MUST — the default", working for title, description, all og:* and all twitter:*, with useHead({ link }) for canonical. That table was verified against stx 0.2.113.

bench does none of that. It computes heads in app/Helpers/{seoPages,entitySeo}.ts and splices them into the built HTML afterwards. On the face of it that is reinvention.

It is not. @extends discards the server-scope head.

Three controlled probes through processDirectives on bench's own stx 0.2.176, each with an identical <script server> calling useSeoMeta({ title, description, ogTitle }) and useHead({ link: canonical }):

view shapetitledescription / og / canonical
no @extendsEnrique S. Lamoutte — Judge Profileall present
@extends + @section('title', 'X')Xall gone
@extends, no @sectionstx Appall gone

The third row is the diagnostic one. stx App is tier 5 — generateDocumentShell's hardcoded default — so the chain fell past tier 1 (context.__stx_runtime_head) and tier 4 (config/ui.ts app.head.title). The head is being lost, not overridden.

And it is not a competing shell: resources/layouts/default.stx is ten lines of body content with no <!DOCTYPE>, <html> or <head> of its own.

Classification: stx, not bench. Correctly-authored input — the standards' own MUST — works with autoShell and is silently dropped under @extends, with no warning.

Why this matters here

Every bench view uses @extends. So:

  • The build-time injection layer is a justified workaround for a framework gap, not reinvention. Do not delete it on the strength of §4.0.1.
  • §4.0.1's table is accurate only for layout-less pages. Worth flagging upstream, since an agent following the standards literally will write useSeoMeta in a <script server>, see a plausible-looking title from @section, and ship pages with no description, no og and no canonical — silently.
  • Three SEO fixes landed this week (6b2eb19, 37f9884, 83d0b6d) inside that layer. They were right to be there.

Repro

bun -e '
const { processDirectives, resetHead } = await import("@stacksjs/stx")
const ui = (await import("./config/ui.ts")).default
resetHead()
const out = await processDirectives(await Bun.file(F).text(), {}, F,
  { ...ui, autoShell: true, seo: { enabled: true } }, new Set())
console.log(out.match(/<title[^>]*>[^<]*<\/title>/i), /name="description"/.test(out))'

against two files differing only by an @extends line.

Next

  • File upstream against stx with the three-case repro
  • Until fixed, keep useSeoMeta/useHead out of <script server> in this repo — it reads as working and is not
9 days ago

Closing: the premise does not reproduce.

Re-ran the probe today on the same stx 0.2.176, through the same processDirectives entry point, with an identical <script server> calling useSeoMeta({ title, description, ogTitle }) and useHead({ link: canonical }):

view shapetitledescription / og / canonical
no @extendsProbe Titleall present
@extends + @section('content')Probe Titleall present
@extends + @section('title', 'X')Probe Titleall present
@extends, no @sectionProbe Titleall present

Rows 3 and 4 are the two this issue reported as broken. The head survives all four.

What the original probe most likely measured

My first attempt today reproduced this issue's symptom exactly — title falling back to stx App, description, og and canonical all gone. The cause was that the probe file lived outside the project, so layouts/default did not resolve:

Template not found: layouts/default (referenced from …/scratchpad/extended.stx)
[stx] Layout not found … No .stx layouts found in …/bench-review/layouts

stx fell through to generateDocumentShell's hardcoded default, which is where stx App comes from — tier 5, exactly as this issue described. Moving the same probe inside resources/views/ so the layout resolves makes the head survive every variant.

So the reported symptom has (at least) two causes, and the one this issue attributed it to is not the one I can reproduce. A missing layout and a discarded head look identical from the output.

I cannot prove the original probe had that confound — the repro snippet here leaves F undefined, so the path it ran against isn't recorded. But same version, same entry point, opposite result, and an identical false symptom from a missing layout is enough that the claim shouldn't stand unverified.

What this does NOT mean

The build-time SEO layer is not simply deletable. app/Helpers/{seoPages,entitySeo}.ts also do things useSeoMeta does not cover:

  • injectNoindex over NOINDEX_PAGES
  • JSON-LD (escapeJsonLd, articleAuthor, …)
  • per-entity SEO across generated pages, where a server script would need per-entity data at render time

What's gone is the stated justification — "@extends discards the head, so we have no choice". Whether the layer should shrink to just the parts above is now an open design question rather than a settled constraint, and wants its own look.

Follow-ups

  • Memory corrected, including a rule: when probing stx by calling processDirectives directly, run from a path where layouts and partials resolve, and assert the layout was FOUND before trusting the output.
  • Not filed upstream to stacksjs/stx, since it does not reproduce.
  • stx-standards §4.0.1 (useSeoMeta as the default) appears accurate as written, including under @extends.

Sign in to comment on this issue.