You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
main is red on the In-repo app tests (website + blog + gallery) job. That job passed on 7867caee and fails on e0622b59 (#1379), so the regression is #1379's.
Three assertions in website/test/ssr/seo-infra.test.ts fail:
✖ the declared favicon size matches the real asset and clears Google 48px floor
AssertionError: declares a PNG icon with an explicit sizes attribute
✖ the apple-touch icon points at a correctly sized asset
AssertionError: declares an apple-touch-icon with a size
✖ the raster icon is declared before the SVG
AssertionError: both icons are declared
#1379 moved the website's favicons off hand-written <link rel="icon"> markup in the root layout and onto metadata.icons. It updated the repo-health copy of these assertions (test/repo-health/site-seo-tags.test.mjs) and missed the second copy living in the website's own suite. That copy renders RootLayout(...) on its own and regexes the output for the markup, so it cannot see icons the framework splices into <head> from metadata. The tags are still served correctly; only the test's way of looking for them is stale.
Two things made this easy to miss and are worth fixing in kind, not just patching:
The same SEO invariants are asserted in two places, once in test/repo-health/site-seo-tags.test.mjs and once in website/test/ssr/seo-infra.test.ts. Updating one and not the other is exactly what happened.
Both copies asserted against layout source or a bare layout render, which couples them to an authoring style rather than to what a browser receives. A favicon can legitimately arrive from hand-written markup, from metadata.icons, or from an auto-linked app/icon.* metadata route (feat(server): auto-link app/icon and app/apple-icon metadata routes #1379), and all three are indistinguishable to a browser.
Design / approach
Read the served page rather than the layout render, matching what test/repo-health/site-seo-tags.test.mjs was changed to in #1379: boot the app with createRequestHandler({ appDir, dev: true }), GET /, take everything before </head>, and assert over the icon <link> tags found there.
That is the more honest assertion regardless of this regression, since it asserts what a browser receives and survives any of the three ways icons can be declared.
Parse attributes order-independently. The framework emits rel, href, sizes, type, while the hand-written markup used rel, href, type, sizes. The old regexes pinned the hand-written order, which is a second reason they broke.
While here, add the resolve check the repo-health copy grew in #1379: fetch every icon href the head declares and assert 200. A head naming a URL nothing answers is invisible from the app and only shows up as a missing tab mark, which is precisely how this class of defect reached production on gallery.webjs.dev (#1375, #1377).
The duplication between the two files is real but is not in scope here; collapsing them is a separate call. Flagging it so the next person does not rediscover it the same way.
Implementation notes (for the implementing agent)
Where to edit (all in website/test/ssr/seo-infra.test.ts, anchors against origin/main at e0622b59):
L40 the declared favicon size matches the real asset and clears Google 48px floor
L59 the apple-touch icon points at a correctly sized asset
L67 the raster icon is declared before the SVG
Each opens with const out = await renderToString(RootLayout(layoutProps(html\
x`)));(L41, L60, L70) and then regexesout`. Replace that with a shared helper that boots the app and returns the head's icon tags.
Imports to drop once the three are converted: html from @webjsdev/core, renderToString from @webjsdev/core/server, the default RootLayout from #app/layout.ts, and layoutProps from #test/helpers/layout-props.ts. Add createRequestHandler from @webjsdev/server. Keep the named generateMetadata import: the canonical tests below (L93, L98, L110) still use it, and they should STAY source/metadata-level because they assert how the canonical is DERIVED across URL variants, which one rendered page cannot show.
Do NOT touch favicon.ico exists so the origin-root fallback resolves (L77). Nothing links /favicon.ico on purpose (it is the no-markup crawler fallback served from public/), so it is correctly a filesystem assertion.
Reference implementation: test/repo-health/site-seo-tags.test.mjs at e0622b59 already does exactly this (renderedIconLinks() plus an attr() helper). Copy that shape so the two files agree.
Landmines
Running the website suite in a fresh worktree needs the registry copy first.website/package.jsonwebjs.start.before runs node scripts/copy-registry.mjs, which generates modules/ui/components/** and lib/utils/cn.ts (both gitignored). Without it, 9 unrelated UI-gallery tests fail and webjs typecheck reports ~37 errors, none of which are yours. Run node scripts/copy-registry.mjs in website/ before drawing any conclusion from a red suite. With it, the suite is 471/471.
The CI job runs node AND browser tests (471 total). A node-only local run reports ~223 and is not comparable.
Invariant 9 applies to any .ts doc/test file that builds an html template: a backtick inside the template body closes the literal at parse time.
Invariants to respect
AGENTS.md invariant 8: only the ROOT layout may write a document shell. That is the underlying reason favicons are declared via metadata.icons rather than hand-written, so do not "fix" this by restoring the markup.
AGENTS.md invariant 11 (prose punctuation and brand casing) applies to comments in the test file.
Tests + docs surfaces
Test-only change. No public surface moves, so no doc surface applies (WEBJS_NO_DOC_GATE=1 if the doc gate trips on an unrelated staged file).
Verify with ( cd website && node scripts/copy-registry.mjs && npx webjs test ) and ( cd website && npx webjs check ).
Acceptance criteria
website/test/ssr/seo-infra.test.ts asserts icon links from the SERVED page, not from a bare RootLayout render
Attribute matching is order-independent, so it does not re-pin an emission order
Every icon href the head declares is fetched and asserted 200
The canonical tests (L93 / L98 / L110) still assert derivation and are unchanged
The favicon.ico filesystem assertion (L77) is unchanged
A counterfactual proves the tests still fire: removing an icon from metadata.icons in website/app/layout.ts reds them
( cd website && node scripts/copy-registry.mjs && npx webjs test ) is 471/471
The In-repo app tests CI job is green on main again
Problem
mainis red on the In-repo app tests (website + blog + gallery) job. That job passed on7867caeeand fails one0622b59(#1379), so the regression is #1379's.Three assertions in
website/test/ssr/seo-infra.test.tsfail:#1379 moved the website's favicons off hand-written
<link rel="icon">markup in the root layout and ontometadata.icons. It updated the repo-health copy of these assertions (test/repo-health/site-seo-tags.test.mjs) and missed the second copy living in the website's own suite. That copy rendersRootLayout(...)on its own and regexes the output for the markup, so it cannot see icons the framework splices into<head>from metadata. The tags are still served correctly; only the test's way of looking for them is stale.Two things made this easy to miss and are worth fixing in kind, not just patching:
test/repo-health/site-seo-tags.test.mjsand once inwebsite/test/ssr/seo-infra.test.ts. Updating one and not the other is exactly what happened.metadata.icons, or from an auto-linkedapp/icon.*metadata route (feat(server): auto-link app/icon and app/apple-icon metadata routes #1379), and all three are indistinguishable to a browser.Design / approach
Read the served page rather than the layout render, matching what
test/repo-health/site-seo-tags.test.mjswas changed to in #1379: boot the app withcreateRequestHandler({ appDir, dev: true }),GET /, take everything before</head>, and assert over the icon<link>tags found there.That is the more honest assertion regardless of this regression, since it asserts what a browser receives and survives any of the three ways icons can be declared.
Parse attributes order-independently. The framework emits
rel,href,sizes,type, while the hand-written markup usedrel,href,type,sizes. The old regexes pinned the hand-written order, which is a second reason they broke.While here, add the resolve check the repo-health copy grew in #1379: fetch every icon href the head declares and assert
200. A head naming a URL nothing answers is invisible from the app and only shows up as a missing tab mark, which is precisely how this class of defect reached production on gallery.webjs.dev (#1375, #1377).The duplication between the two files is real but is not in scope here; collapsing them is a separate call. Flagging it so the next person does not rediscover it the same way.
Implementation notes (for the implementing agent)
Where to edit (all in
website/test/ssr/seo-infra.test.ts, anchors againstorigin/mainate0622b59):the declared favicon size matches the real asset and clears Google 48px floorthe apple-touch icon points at a correctly sized assetthe raster icon is declared before the SVGEach opens with
x`)));const out = await renderToString(RootLayout(layoutProps(html\(L41, L60, L70) and then regexesout`. Replace that with a shared helper that boots the app and returns the head's icon tags.Imports to drop once the three are converted:
htmlfrom@webjsdev/core,renderToStringfrom@webjsdev/core/server, the defaultRootLayoutfrom#app/layout.ts, andlayoutPropsfrom#test/helpers/layout-props.ts. AddcreateRequestHandlerfrom@webjsdev/server. Keep the namedgenerateMetadataimport: the canonical tests below (L93, L98, L110) still use it, and they should STAY source/metadata-level because they assert how the canonical is DERIVED across URL variants, which one rendered page cannot show.Do NOT touch
favicon.ico exists so the origin-root fallback resolves(L77). Nothing links/favicon.icoon purpose (it is the no-markup crawler fallback served frompublic/), so it is correctly a filesystem assertion.Reference implementation:
test/repo-health/site-seo-tags.test.mjsate0622b59already does exactly this (renderedIconLinks()plus anattr()helper). Copy that shape so the two files agree.Landmines
website/package.jsonwebjs.start.beforerunsnode scripts/copy-registry.mjs, which generatesmodules/ui/components/**andlib/utils/cn.ts(both gitignored). Without it, 9 unrelated UI-gallery tests fail andwebjs typecheckreports ~37 errors, none of which are yours. Runnode scripts/copy-registry.mjsinwebsite/before drawing any conclusion from a red suite. With it, the suite is 471/471..tsdoc/test file that builds anhtmltemplate: a backtick inside the template body closes the literal at parse time.Invariants to respect
metadata.iconsrather than hand-written, so do not "fix" this by restoring the markup.Tests + docs surfaces
WEBJS_NO_DOC_GATE=1if the doc gate trips on an unrelated staged file).( cd website && node scripts/copy-registry.mjs && npx webjs test )and( cd website && npx webjs check ).Acceptance criteria
website/test/ssr/seo-infra.test.tsasserts icon links from the SERVED page, not from a bareRootLayoutrender200favicon.icofilesystem assertion (L77) is unchangedmetadata.iconsinwebsite/app/layout.tsreds them( cd website && node scripts/copy-registry.mjs && npx webjs test )is 471/471mainagain