Skip to content

Auto-opt-in published realms with default CardsGrid index#4770

Open
habdelra wants to merge 6 commits into
indexer-perf/realm-index-opt-infrom
indexer-perf/publish-default-index-isolated
Open

Auto-opt-in published realms with default CardsGrid index#4770
habdelra wants to merge 6 commits into
indexer-perf/realm-index-opt-infrom
indexer-perf/publish-default-index-isolated

Conversation

@habdelra
Copy link
Copy Markdown
Contributor

@habdelra habdelra commented May 11, 2026

Summary

Stacked on top of #4769. When publishing a source realm whose index.json is the default CardsGrid (no custom landing card), the publish handler writes includePrerenderedDefaultRealmIndex: true into the published realm's realm.json before triggering the post-publish reindex. The reindex picks up the new flag through parseRealmInfo's existing disk overlay and the host's render route produces a real isolated HTML for the index card instead of the boilerplate placeholder.

The write is bound to the published snapshot specifically — source realms are left alone (their owners have JS loaded and don't need SSR for the homepage).

What this depends on

Why a structural index.json check

The detection reads published-realm/index.json after the directory swap and checks data.meta.adoptsFrom for exactly:

{ "module": "https://cardstack.com/base/cards-grid", "name": "CardsGrid" }

(or the equivalent @cardstack/base/cards-grid RRI form once that prefix is registered)

If the source realm has customised its index to a different CardDef (a bespoke landing card), we don't set the field. Custom landing pages were curated for a reason — render them, don't replace with a placeholder. The publisher who put work into a custom homepage almost certainly wants the SSR injection to show real content, and there's no upside to skipping that render anyway since fan-out doesn't apply.

Timing

The write happens:

  • After the directory swap (moveSync into publishedRealmPath) so it lands on the canonical published path, not the .tmp staging dir.
  • Before enqueueReindexRealmJob so the same reindex pass picks up the new flag via parseRealmInfo reading realm.json from disk.
  • Outside the published-realm registry insert, so a failure to write the field doesn't roll back the publish itself.

Unpublish

No cleanup needed. packages/realm-server/handlers/handle-unpublish-realm.ts:159 runs removeRealmFiles(publishedRealmPath) which deletes the entire published directory including the realm.json carrying the flag. The DB-side state is tombstoned in the same handler and the registry row is deleted. There's no orphan state to worry about.

Backfill

Existing published realms (already on disk before this PR lands) keep serving the boilerplate placeholder for their index until someone republishes them from source. No migration script. Republishing from source after this PR lands picks up the new behaviour automatically.

Test plan

  • pnpm lint:types clean in @cardstack/realm-server.
  • pnpm eslint clean for packages/realm-server/handlers/handle-publish-realm.ts.
  • Integration test: publish a source realm whose index.json is default CardsGrid → published realm's realm.json on disk contains includePrerenderedDefaultRealmIndex: true; behaviour matches pre-this-PR.
  • Integration test: publish a source realm with a custom (non-CardsGrid) index card → published realm's realm.json does NOT contain the field; behaviour matches pre-this-PR.

🤖 Generated with Claude Code

@habdelra habdelra requested a review from Copilot May 11, 2026 19:56
Copy link
Copy Markdown
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

Note

Copilot was unable to run its full agentic suite in this review.

Adds an automatic opt-in for published realms whose index.json is the default CardsGrid so the publish flow writes includePrerenderedDefaultRealmIndex: true into the published realm.json before triggering the post-publish reindex, enabling real prerendered isolated HTML instead of a boilerplate placeholder.

Changes:

  • Detects whether the published realm’s index.json is an adopted default CardsGrid.
  • Writes includePrerenderedDefaultRealmIndex: true into the published realm’s realm.json on disk when applicable.
  • Invokes the opt-in write after the directory swap and before enqueueing the reindex job.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/realm-server/handlers/handle-publish-realm.ts
Comment thread packages/realm-server/handlers/handle-publish-realm.ts
Comment thread packages/realm-server/handlers/handle-publish-realm.ts Outdated
@habdelra habdelra force-pushed the indexer-perf/realm-index-opt-in branch from 7cde395 to 5a2fa61 Compare May 11, 2026 21:50
habdelra and others added 4 commits May 11, 2026 17:50
…rendered isolated HTML

When publishing a source realm whose `index.json` is the default
CardsGrid (no custom landing card), the publish handler now writes
`includePrerenderedDefaultRealmIndex: true` into the published
realm's `realm.json` before kicking off the post-publish reindex.

Why: anonymous visitors of a published realm's homepage hit the SSR
injection path (`retrieveIsolatedHTML` → `injectIsolatedHTML` in
`server.ts`). Without the opt-in, the host's render route substitutes
a boilerplate placeholder for the realm-index card's isolated HTML
and the SSR shell would inject the placeholder instead of a real
grid view. Anonymous visitors would see the placeholder until JS
boots and re-renders.

By setting the field on the published snapshot specifically (not the
source realm), we keep two properties:

1. Source realms continue to skip the (expensive) realm-index
   isolated render — owners and editors who view the source-realm
   homepage already have JS loaded and don't need SSR.
2. Published realms get the full isolated render automatically,
   without operators having to remember to flip the realm.json field
   by hand.

Detection is a structural read of the published realm's
`index.json`: if its `data.meta.adoptsFrom` is exactly
`{ module: 'https://cardstack.com/base/cards-grid', name: 'CardsGrid' }`
the helper sets the field. A published realm that has customised its
index to a different CardDef is left alone (its isolated render is
presumably the bespoke landing page the publisher curated).

The write happens after the directory swap (so it lands on the
already-canonical `publishedRealmPath`) and before
`enqueueReindexRealmJob` so the same reindex pass picks up the new
flag through `parseRealmInfo`'s realm.json overlay. No cleanup is
needed on unpublish: `handle-unpublish-realm.ts:159`
(`removeRealmFiles(publishedRealmPath)`) deletes the entire published
directory, including the realm.json carrying the field.

Existing published realms (already on disk before this PR lands) are
unaffected; they keep serving the boilerplate placeholder for the
realm index until someone republishes them from source. No
migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The detection in `ensureRealmIndexBoilerplateOptIn` originally only
matched the absolute base-realm URL form
(`https://cardstack.com/base/cards-grid`). Cardstack's RRI scheme
also supports `@cardstack/base/cards-grid` as a valid module
reference once the `@cardstack/base/` prefix is wired as a
virtual-network mapping. That prefix isn't registered in production
today — only test fixtures register it — so the absolute URL is the
canonical form on disk for now, but listing both keeps the detection
future-proof.

Also tighten the adoptsFrom validation: use `isResolvedCodeRef` from
runtime-common instead of an inline structural cast, so the parsed
JSON has to actually present a `{ module, name }` shape before we
even read its fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Copilot review: `${e}` on a non-Error throw can produce
`[object Object]` and elides `.message` / `.stack`. Normalize to
`e instanceof Error ? e.message : String(e)` across the three warn
sites in `ensureRealmIndexBoilerplateOptIn` so the log output is
consistent regardless of what was thrown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two tests in publish-unpublish-realm-test.ts:

1. Publishing a realm with the default CardsGrid index (the shape
   handle-create-realm seeds) → asserts the published realm's
   realm.json on disk has `includePrerenderedDefaultRealmIndex: true`
   set by the publish handler.
2. Publishing a realm whose index.json has been replaced with a
   non-CardsGrid CardDef → asserts the published realm.json does NOT
   carry the field. Confirms the detection only fires for the
   default CardsGrid case and leaves bespoke landing pages alone.

Both tests reuse the existing `with a publishable source realm`
hooks scaffold, so they share the freshly-created source realm setup
and the cleanup path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@habdelra habdelra force-pushed the indexer-perf/publish-default-index-isolated branch from 45a6787 to 9be90bf Compare May 11, 2026 21:52
@habdelra
Copy link
Copy Markdown
Contributor Author

[Claude Code]

Rebased this PR onto the new PR #4769 (which itself now stacks directly on main). PR #4767 (parallelism) is no longer in this stack — it's being parked for further investigation after benchmarks showed it causes a self-referential prerender deadlock on realms with query-backed linksToMany aggregators.

This PR is unchanged in scope — publish handler writes the realm-index opt-in flag for published default-CardsGrid realms so SSR injection works. Files changed:

packages/realm-server/handlers/handle-publish-realm.ts  (+100)
packages/realm-server/tests/publish-unpublish-realm-test.ts (+116)

Fresh CI run on 9be90bf8 should be clean (the earlier red checks were on the pre-rebase SHA that carried PR1's regression).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

Host Test Results

    1 files  ±0      1 suites  ±0   1h 46m 37s ⏱️ - 1m 58s
2 657 tests ±0  2 642 ✅ +30  15 💤 ±0  0 ❌ ± 0 
2 676 runs  ±0  2 661 ✅ +60  15 💤 ±0  0 ❌  - 30 

Results for commit 18c1e08. ± Comparison against earlier commit e0185da.

Realm Server Test Results

    1 files  ±0      1 suites  ±0   13m 13s ⏱️ +36s
1 323 tests ±0  1 323 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 402 runs  ±0  1 402 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 18c1e08. ± Comparison against earlier commit e0185da.

@habdelra habdelra requested a review from a team May 12, 2026 02:48
… into indexer-perf/publish-default-index-isolated
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