Refactor prerender in order to capture links in non-isolated format#4079
Refactor prerender in order to capture links in non-isolated format#4079
Conversation
| 'non-isolated-links.json': { | ||
| data: { | ||
| attributes: { | ||
| name: 'Mango Non Isolated', | ||
| profile: {}, | ||
| cardInfo: { | ||
| name: null, | ||
| summary: null, | ||
| cardThumbnailURL: null, | ||
| notes: null, | ||
| }, | ||
| }, | ||
| relationships: { | ||
| 'cardInfo.theme': { | ||
| links: { self: `${realmURL2}non-isolated-theme` }, | ||
| }, |
There was a problem hiding this comment.
this test data emulates the failure that we were seeing around not being able to use the theme relationship in the default head template
| let headHTML = cleanWhiteSpace(response.headHTML ?? ''); | ||
| assert.ok( | ||
| /<link rel="icon" href="https:\/\/example\.com\/non-isolated-social-icon\.png"/.test( | ||
| headHTML, | ||
| ), | ||
| `head html includes favicon from cardInfo.theme: ${headHTML}`, | ||
| ); |
There was a problem hiding this comment.
here is the key assertion where we assert that the favicon was picked up in the head template via the cardInfo.theme relationship.
| assert.strictEqual( | ||
| response.searchDoc?.owner?.name, | ||
| 'Hassan', | ||
| 'searchDoc includes direct linksTo data from non-isolated render', | ||
| ); | ||
| assert.strictEqual( | ||
| response.searchDoc?.owners?.[0]?.name, | ||
| 'Hassan', | ||
| 'searchDoc includes direct linksToMany first value from non-isolated render', | ||
| ); | ||
| assert.strictEqual( | ||
| response.searchDoc?.owners?.[1]?.name, | ||
| 'Mango', | ||
| 'searchDoc includes direct linksToMany second value from non-isolated render', | ||
| ); | ||
| assert.strictEqual( | ||
| response.searchDoc?.profile?.lead?.name, | ||
| 'Hassan', | ||
| 'searchDoc includes nested FieldDef linksTo data from non-isolated render', | ||
| ); | ||
| assert.strictEqual( | ||
| response.searchDoc?.profile?.members?.[0]?.name, | ||
| 'Hassan', | ||
| 'searchDoc includes nested FieldDef linksToMany first value from non-isolated render', | ||
| ); | ||
| assert.strictEqual( | ||
| response.searchDoc?.profile?.members?.[1]?.name, | ||
| 'Mango', | ||
| 'searchDoc includes nested FieldDef linksToMany second value from non-isolated render', | ||
| ); |
There was a problem hiding this comment.
additionally the searchDoc now includes relationships that only exist in non-isolated templates
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0617519753
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Preview deployments |
| @field summary = contains(StringField); | ||
| @field cardThumbnailURL = contains(MaybeBase64Field); | ||
| @field theme = linksTo(() => Theme, { isUsed: true }); | ||
| @field theme = linksTo(() => Theme); |
There was a problem hiding this comment.
we have backed out the isUsed: true in the CardInfo.theme field
There was a problem hiding this comment.
Pull request overview
This PR refactors the prerender pipeline to ensure link capture includes non-isolated formats, removes the CardInfo.theme isUsed: true workaround, and improves prerender test reliability/performance (including addressing orphaned prerender/Chrome processes between test runs).
Changes:
- Update prerender runner sequencing and add consistent “route reached + settle” barriers across HTML/icon/meta captures.
- Add orphaned Puppeteer profile/Chrome cleanup logic and test reorganization to reduce runtime and prevent leaks.
- Relax head HTML sanitization to keep allowlisted tags nested inside wrapper elements; remove
isUsed: truefromCardInfo.themeand update related host tests.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/helpers/sanitize-head-html.ts | Allows allowlisted head tags to be captured even when nested inside wrapper elements. |
| packages/realm-server/prerender/utils.ts | Adds new async barriers (waitForRoutePathSuffix, waitForPrerenderSettle) and improves capture behavior/logging. |
| packages/realm-server/prerender/render-runner.ts | Reorders and standardizes prerender steps; captures meta after non-isolated formats to include their linked-field loads. |
| packages/realm-server/prerender/browser-manager.ts | Cleans up stale Puppeteer profiles and terminates orphaned Chrome processes tied to stale profiles. |
| packages/host/app/routes/render.ts | Exposes __waitForRenderLoadStability hook for prerender settling and clears it on deactivate. |
| packages/base/card-api.gts | Removes isUsed: true from CardInfoField.theme. |
| packages/realm-server/tests/prerendering-test.ts | Reorganizes prerender tests, adds coverage for non-isolated link capture, and improves realm setup/teardown patterns. |
| packages/host/tests/integration/components/serialization-test.gts | Updates expectations to remove cardInfo.theme relationship scaffolding where no longer emitted. |
| packages/host/tests/acceptance/prerender-meta-test.gts | Updates meta expectations for cardInfo output changes. |
| packages/host/tests/acceptance/code-submode/create-file-test.gts | Updates relationship expectations consistent with serialization changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
My environment is still in chaos, now with
The theme should cause the icons to use this URL:
I can’t dispute that |
i’ll look into it |
@backspace The issue is in index.json it only has the dotted relationship, no attributes.cardInfo object. specifically you have this: {
"data":{
"type":"card",
"meta":{
"adoptsFrom":{
"module":"./home",
"name":"Home"
}
},
"relationships": {
"cardInfo.theme": {
"links": {
"self": "./CardDef/7a0d1636-594d-427e-899d-4307be8f9018"
}
}
}
}
}it should be this: {
"data": {
"type": "card",
"meta": {
"adoptsFrom": {
"module": "./home",
"name": "Home"
}
},
"attributes": {
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"relationships": {
"cardInfo.theme": {
"links": {
"self": "./CardDef/7a0d1636-594d-427e-899d-4307be8f9018"
}
}
}
}
}the relationship needs to hang off of the cardInfo FieldDef, which if not specified is undefined. so the relationship is kinda hanging out in outerspace. you need to make a FieldDef instance in the json, and then the relationship can hang off of it. After I do that, I see the head HTML in the index look like this: <div class="ember-view boxel-card-container boxel-card-container--boundaries boxel-card-container--themed field-component-card head-format display-container-true" data-scopedcss-1f5a67e68b-273efce7f8 data-test-boxel-card-container style="--accent: oklch(0.931 0 0); --accent-foreground: oklch(0 0 0); --background: oklch(1 0 0); --border: oklch(0.8669 0 0); --brand-accent: #e8e8e8; --brand-body-font-family: IBM Plex sans; --brand-dark: #000000; --brand-heading-font-family: IBM Plex sans; --brand-heading-font-size: 22px; --brand-heading-font-weight: 700; --brand-light: #ffffff; --brand-primary: #00ffba; --brand-primary-mark-1: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-primary-mark-1.svg; --brand-primary-mark-2: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-primary-mark-2.svg; --brand-primary-mark-clearance-ratio: 1; --brand-primary-mark-greyscale-1: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-greyscale-primary-black.svg; --brand-primary-mark-greyscale-2: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-greyscale-primary-white.svg; --brand-primary-mark-min-height: 1.25rem; --brand-secondary: #00EBE5; --brand-secondary-mark-1: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-secondary-mark-1.svg; --brand-secondary-mark-2: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-secondary-mark-2.svg; --brand-secondary-mark-clearance-ratio: 0.5; --brand-secondary-mark-greyscale-1: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-greyscale-secondary-black.svg; --brand-secondary-mark-greyscale-2: https://app-assets-cardstack.s3.us-east-1.amazonaws.com/cardstack-brand-guide/cardstack-logo-greyscale-secondary-white.svg; --brand-secondary-mark-min-height: 2.5rem; --brand-social-media-profile-icon: https://chromatin.ca/content/images/size/w256h256/2022/02/chromatin-fav.png; --card: oklch(1 0 0); --card-foreground: oklch(0 0 0); --chart-1: oklch(0.5838 0.299 307.6101); --chart-2: oklch(0.5354 0.2684 283.1486); --chart-3: oklch(0.9194 0.2182 125.1586); --chart-4: oklch(0.5641 0.2301 259.998); --chart-5: oklch(0.8277 0.2119 149.9571); --destructive: oklch(0.6763 0.2115 24.8106); --destructive-foreground: oklch(0.9672 0 0); --font-mono: 'IBM Plex Mono', 'Menlo', 'Courier New', Courier, ui-monospace, monospace; --font-sans: 'IBM Plex Sans', 'Helvetica Neue', Arial, ui-sans-serif, sans-serif, system-ui; --font-serif: 'IBM Plex Serif', 'Georgia', Times, serif; --foreground: oklch(0 0 0); --input: oklch(1 0 0); --muted: oklch(0.9777 0.0041 301.4256); --muted-foreground: oklch(0.4495 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0 0 0); --primary: oklch(0.833 0.174691 165.1921); --primary-foreground: oklch(0 0 0); --radius: 0.625rem; --ring: oklch(0.8859 0.1865 164.8865); --secondary: oklch(1 0 0); --secondary-foreground: oklch(0 0 0); --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1); --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1); --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1); --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1); --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1); --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); --sidebar: oklch(1 0 0); --sidebar-accent: oklch(0.931 0 0); --sidebar-accent-foreground: oklch(0 0 0); --sidebar-border: oklch(0.8669 0 0); --sidebar-foreground: oklch(0 0 0); --sidebar-primary: oklch(0 0 0); --sidebar-primary-foreground: oklch(1 0 0); --sidebar-ring: oklch(0.8859 0.1865 164.8865); --spacing: 0.25rem; --tracking-normal: 0.01em" data-test-card="http://localhost:4201/user/subsequent-earwig-38/index" data-test-card-format="head" data-test-field-component-card data-scopedcss-e887f5899e-4af3416a55>
<title data-test-card-head-title>Untitled home</title>
<meta property="og:title" content="Untitled home">
<meta name="twitter:title" content="Untitled home">
<meta property="og:url" content="http://localhost:4201/user/subsequent-earwig-38/index">
<!---->
<meta name="twitter:card" content="summary">
<link rel="icon" href="https://chromatin.ca/content/images/size/w256h256/2022/02/chromatin-fav.png">
<link rel="apple-touch-icon" href="https://chromatin.ca/content/images/size/w256h256/2022/02/chromatin-fav.png">
<meta property="og:type" content="website">
<!----> </div>and the published site looks like this:
so it looks like it's all working to me. |







This PR refactors the order in which we do prerendering as well as uses consistent async barriers across the different format rendering so that we capture all of the links from non-isolated formats. This means that we can back out the
isUsed: trueworkaround that we added to theCardInfo.Additionally, this PR fixes a memory leak around AI stopping and starting tests leaving orphaned prerendering processes still running.
This PR also reorganizes the prerender tests to use realm setup/teardown more efficiently, such that the tests now run in less than 1/2 the amount of time as they used to (sorry for all the test churn--i pointed out in comments the new key assertions).