Skip to content

Refactor prerender in order to capture links in non-isolated format#4079

Open
habdelra wants to merge 5 commits intomainfrom
cs-10228-prerendering-doesnt-capture-relationships-in-non-isolated-II
Open

Refactor prerender in order to capture links in non-isolated format#4079
habdelra wants to merge 5 commits intomainfrom
cs-10228-prerendering-doesnt-capture-relationships-in-non-isolated-II

Conversation

@habdelra
Copy link
Contributor

@habdelra habdelra commented Feb 26, 2026

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: true workaround that we added to the CardInfo.

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).

Comment on lines +2446 to +2461
'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` },
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test data emulates the failure that we were seeing around not being able to use the theme relationship in the default head template

Comment on lines +2975 to +2981
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}`,
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is the key assertion where we assert that the favicon was picked up in the head template via the cardInfo.theme relationship.

Comment on lines +3029 to +3058
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',
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additionally the searchDoc now includes relationships that only exist in non-isolated templates

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

@github-actions
Copy link

Preview deployments

@field summary = contains(StringField);
@field cardThumbnailURL = contains(MaybeBase64Field);
@field theme = linksTo(() => Theme, { isUsed: true });
@field theme = linksTo(() => Theme);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have backed out the isUsed: true in the CardInfo.theme field

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: true from CardInfo.theme and 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.

@github-actions
Copy link

Host Test Results

    1 files  ±0      1 suites  ±0   1h 45m 1s ⏱️ - 2m 47s
1 912 tests ±0  1 897 ✅ ±0  15 💤 ±0  0 ❌ ±0 
1 927 runs  ±0  1 912 ✅ ±0  15 💤 ±0  0 ❌ ±0 

Results for commit b19c7a3. ± Comparison against base commit 77d2151.

@habdelra habdelra requested a review from a team February 27, 2026 00:51
@backspace
Copy link
Contributor

My environment is still in chaos, now with @cardstack/base conversion, but I wasn’t able to get the theme icons with a published version of this realm: user-icons20260225.zip

e8c0a21ef0e0f5101a0d9e590435203ca7bf96af 2026-02-26 20-51-43

The theme should cause the icons to use this URL:

7a0d1636-594d-427e-899d-4307be8f9018 json in icons20260225 2026-02-26 20-52-29

I can’t dispute that isUsed isn’t needed anymore but the underlying problem that it was meant to fix still remains 😞 (unless it‘s just my machine, of course)

@habdelra
Copy link
Contributor Author

My environment is still in chaos, now with @cardstack/base conversion, but I wasn’t able to get the theme icons with a published version of this realm: user-icons20260225.zip

e8c0a21ef0e0f5101a0d9e590435203ca7bf96af 2026-02-26 20-51-43 The theme should cause the icons to use this URL: 7a0d1636-594d-427e-899d-4307be8f9018 json in icons20260225 2026-02-26 20-52-29 I can’t dispute that `isUsed` isn’t needed anymore but the underlying problem that it was meant to fix still remains 😞 (unless it‘s just my machine, of course)

i’ll look into it

@habdelra
Copy link
Contributor Author

My environment is still in chaos, now with @cardstack/base conversion, but I wasn’t able to get the theme icons with a published version of this realm: user-icons20260225.zip

e8c0a21ef0e0f5101a0d9e590435203ca7bf96af 2026-02-26 20-51-43 The theme should cause the icons to use this URL: 7a0d1636-594d-427e-899d-4307be8f9018 json in icons20260225 2026-02-26 20-52-29 I can’t dispute that `isUsed` isn’t needed anymore but the underlying problem that it was meant to fix still remains 😞 (unless it‘s just my machine, of course)

@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:

image

so it looks like it's all working to me.

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.

3 participants