diff --git a/packages/base/realm-config.gts b/packages/base/realm-config.gts index df7706865c1..cad1a6be87f 100644 --- a/packages/base/realm-config.gts +++ b/packages/base/realm-config.gts @@ -6,6 +6,7 @@ import { containsMany, linksTo, } from './card-api'; +import BooleanField from './boolean'; import StringField from './string'; import FileSettingsIcon from '@cardstack/boxel-icons/file-settings'; import LinkIcon from '@cardstack/boxel-icons/link'; @@ -31,6 +32,17 @@ export class RealmConfig extends CardDef { @field backgroundURL = contains(StringField); @field iconURL = contains(StringField); @field hostRoutingRules = containsMany(RoutingRuleField); + // Opt-in to keeping the full prerendered isolated HTML for the + // realm's default CardsGrid index card. Default behaviour for this + // card writes a small boilerplate placeholder instead — the + // CardsGrid isolated render fans out into a fitted render per card + // in the realm and dominates indexing wall-clock on larger realms, + // and nothing reads its isolated HTML in production for an + // unpublished realm. Set this to `true` when the realm's index is + // served as published-realm SSR (the publish handler writes it + // automatically in that case) or when an operator otherwise needs + // the full isolated render present in the index. + @field includePrerenderedDefaultRealmIndex = contains(BooleanField); @field cardTitle = contains(StringField, { computeVia: function (this: RealmConfig) { diff --git a/packages/host/app/components/card-prerender.gts b/packages/host/app/components/card-prerender.gts index fe70f5d5852..69da20f822d 100644 --- a/packages/host/app/components/card-prerender.gts +++ b/packages/host/app/components/card-prerender.gts @@ -44,6 +44,7 @@ import { } from '../routes/module'; import { createAuthErrorGuard } from '../utils/auth-error-guard'; +import { REALM_INDEX_BOILERPLATE_HTML } from '../utils/realm-index-boilerplate'; import { RenderCardTypeTracker, type CardRenderContext, @@ -57,6 +58,8 @@ import { withTimersBlocked, } from '../utils/render-timer-stub'; +import type { Model as HtmlRouteModel } from '../routes/render/html'; + import type LoaderService from '../services/loader-service'; import type LocalIndexer from '../services/local-indexer'; import type NetworkService from '../services/network'; @@ -600,6 +603,19 @@ export default class CardPrerender extends Component { if (this.localIndexer.renderError) { throw new Error(this.localIndexer.renderError); } + // The html sub-route flags this when the card is the realm's + // default CardsGrid index and the realm has not opted in to + // keeping its prerendered isolated HTML. Short-circuit the + // Glimmer render entirely and return the boilerplate so the + // indexer pays a constant write cost regardless of realm size. + if ( + format === 'isolated' && + ancestorLevel === 0 && + (routeInfo.attributes as HtmlRouteModel).useRealmIndexBoilerplate + ) { + await this.#ensureRenderReady(routeInfo); + return REALM_INDEX_BOILERPLATE_HTML; + } let component = routeInfo.attributes.Component; this.#renderErrorPayload = undefined; let captureMode: 'innerHTML' | 'outerHTML' | 'textContent'; diff --git a/packages/host/app/routes/render/html.ts b/packages/host/app/routes/render/html.ts index 15498b21976..827040a8769 100644 --- a/packages/host/app/routes/render/html.ts +++ b/packages/host/app/routes/render/html.ts @@ -3,7 +3,16 @@ import type RouterService from '@ember/routing/router-service'; import type Transition from '@ember/routing/transition'; import { service } from '@ember/service'; -import { isValidFormat } from '@cardstack/runtime-common'; +import { + internalKeyFor, + isRealmIndexCardId, + isValidFormat, + realmURL, + type CodeRef, + type ResolvedCodeRef, +} from '@cardstack/runtime-common'; + +import type RealmService from '@cardstack/host/services/realm'; import type { BoxComponent, @@ -15,14 +24,34 @@ import { getClass, getTypes } from './meta'; import type { Model as ParentModel } from '../render'; +// Stable internal key for the base CardsGrid type. We compare against +// the internalKeyFor representation of the cards-grid module + name so +// the check tolerates whatever resolved form the host's identify path +// produces (e.g. realm-aliased base URLs). +const CARDS_GRID_REF = { + module: 'https://cardstack.com/base/cards-grid', + name: 'CardsGrid', +} as ResolvedCodeRef; +const CARDS_GRID_INTERNAL_KEY = internalKeyFor(CARDS_GRID_REF, undefined); + export interface Model { instance: CardDef; format: Format; Component: BoxComponent; + // True when this render should be short-circuited to the realm- + // index boilerplate placeholder instead of running through Glimmer. + // Set only when `format === 'isolated'`, the card is the realm's + // default index, the type chain is base CardsGrid, and the realm + // has not opted in via `includePrerenderedDefaultRealmIndex` on its + // RealmConfig card. The orchestrator in `card-prerender.gts` + // honours this flag by substituting the boilerplate string and + // skipping the actual Glimmer render. + useRealmIndexBoilerplate?: boolean; } export default class RenderHtmlRoute extends Route { @service declare router: RouterService; + @service declare realm: RealmService; beforeModel(transition: Transition) { let parentModel = this.modelFor('render') as ParentModel | undefined; @@ -69,7 +98,8 @@ export default class RenderHtmlRoute extends Route { if (isNaN(level)) { throw new Error('not a valid ancestor_level'); } - let componentCodeRef = getTypes(getClass(instance))[level]; + let types = getTypes(getClass(instance)); + let componentCodeRef = types[level]; if (!componentCodeRef) { throw new Error(`ancestor_level ${level} does not exist`); } @@ -77,6 +107,37 @@ export default class RenderHtmlRoute extends Route { let Component = instance.constructor.getComponent(instance, undefined, { componentCodeRef, }); - return { format, instance, Component }; + + let useRealmIndexBoilerplate = + format === 'isolated' && + level === 0 && + this.#isDefaultRealmCardsGridIndex(instance, types); + + return { format, instance, Component, useRealmIndexBoilerplate }; + } + + // True when the card under render is the realm's default index card + // AND its type chain begins with the base CardsGrid AND the realm + // has NOT opted in to keeping its prerendered isolated HTML via + // `RealmInfo.includePrerenderedDefaultRealmIndex`. The orchestrator + // substitutes a boilerplate placeholder for the captured HTML in + // that case so the indexer doesn't pay for the (expensive) grid + // fan-out render of every card in the realm. Published realms + // receive the opt-in automatically from the publish handler. + #isDefaultRealmCardsGridIndex(instance: CardDef, types: CodeRef[]): boolean { + let cardRealmURL = instance[realmURL]; + if (!cardRealmURL || !isRealmIndexCardId(instance.id, cardRealmURL)) { + return false; + } + let topType = types[0]; + if (!topType) { + return false; + } + let topKey = internalKeyFor(topType, undefined); + if (topKey !== CARDS_GRID_INTERNAL_KEY) { + return false; + } + let info = this.realm.info(cardRealmURL.href); + return info?.includePrerenderedDefaultRealmIndex !== true; } } diff --git a/packages/host/app/utils/realm-index-boilerplate.ts b/packages/host/app/utils/realm-index-boilerplate.ts new file mode 100644 index 00000000000..665afea85ec --- /dev/null +++ b/packages/host/app/utils/realm-index-boilerplate.ts @@ -0,0 +1,27 @@ +// Placeholder content the host returns as `isolatedHTML` when it +// decides to skip the full isolated render for the realm's default +// CardsGrid index card. The decision is made per-render in +// `card-prerender.gts` based on the card URL, its adoptsFrom chain, +// and the realm's `includePrerenderedDefaultRealmIndex` config field +// — see the realm-index opt-in PR for the surrounding rationale. +// +// The boilerplate is intentionally minimal. Anywhere the placeholder +// could be visible (the published-realm SSR injection slot, the +// error-page `lastKnownGoodHtml` fallback for an erroring realm +// index, or any future consumer reading `isolated_html` directly) is +// out of the search-result hot path; we just need valid HTML that +// the Ember runtime can replace cleanly on hydration. The wrapper +// shape matches what `withTimeout` in +// `packages/realm-server/prerender/utils.ts` captures from a real +// render so downstream consumers don't see a structurally different +// payload. +export const REALM_INDEX_BOILERPLATE_HTML = `
+
+ Prerendered HTML for default realm index is disabled (can be configured in realm.json) +
+
+`; diff --git a/packages/host/tests/integration/realm-indexing-test.gts b/packages/host/tests/integration/realm-indexing-test.gts index 6592fc5cbd8..d3035e54b30 100644 --- a/packages/host/tests/integration/realm-indexing-test.gts +++ b/packages/host/tests/integration/realm-indexing-test.gts @@ -21,6 +21,7 @@ import stripScopedCSSAttributes from '@cardstack/runtime-common/helpers/strip-sc import type { Loader } from '@cardstack/runtime-common/loader'; import { windowErrorHandler } from '@cardstack/host/lib/window-error-handler'; +import { REALM_INDEX_BOILERPLATE_HTML } from '@cardstack/host/utils/realm-index-boilerplate'; import { testRealmURL, @@ -161,6 +162,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -435,6 +437,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -530,6 +533,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -587,6 +591,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -805,6 +810,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -902,6 +908,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -996,6 +1003,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -1126,6 +1134,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -1200,6 +1209,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -2398,6 +2408,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -2440,6 +2451,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -2481,6 +2493,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -2946,6 +2959,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -2986,6 +3000,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3028,6 +3043,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3150,6 +3166,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3286,6 +3303,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3451,6 +3469,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3602,6 +3621,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3653,6 +3673,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3750,6 +3771,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3801,6 +3823,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -3928,6 +3951,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4077,6 +4101,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4123,6 +4148,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4163,6 +4189,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4252,6 +4279,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4301,6 +4329,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4341,6 +4370,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4436,6 +4466,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4485,6 +4516,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -4525,6 +4557,7 @@ module(`Integration | realm indexing`, function (hooks) { backgroundURL: null, hostHome: null, iconURL: null, + includePrerenderedDefaultRealmIndex: null, interactHome: null, lastPublishedAt: null, name: 'Unnamed Workspace', @@ -5036,4 +5069,149 @@ posts/ignore-me.json 'no instances were processed', ); }); + + test('isolated HTML for the default CardsGrid realm-index card is replaced with the boilerplate when realm has not opted in', async function (assert) { + let { realm } = await setupIntegrationTestRealm({ + mockMatrixUtils, + contents: { + 'index.json': { + data: { + type: 'card', + attributes: {}, + meta: { + adoptsFrom: { + module: 'https://cardstack.com/base/cards-grid', + name: 'CardsGrid', + }, + }, + }, + }, + 'realm.json': { + data: { + type: 'card', + attributes: {}, + meta: { + adoptsFrom: { + module: 'https://cardstack.com/base/realm-config', + name: 'RealmConfig', + }, + }, + }, + }, + }, + }); + let entry = await getInstance(realm, new URL(`${testRealmURL}index`)); + assert.ok(entry, 'realm index card is indexed'); + assert.strictEqual( + entry?.isolatedHtml, + REALM_INDEX_BOILERPLATE_HTML, + 'isolated_html is the boilerplate constant when the realm has not opted in', + ); + let embeddedHtml = entry?.embeddedHtml ?? {}; + assert.ok( + Object.keys(embeddedHtml).length > 0, + 'embedded HTML map is populated', + ); + assert.ok( + Object.values(embeddedHtml).every((html) => html.length > 0), + 'every embedded HTML entry has real content', + ); + let fittedHtml = entry?.fittedHtml ?? {}; + assert.ok( + Object.keys(fittedHtml).length > 0, + 'fitted HTML map is populated', + ); + assert.ok( + Object.values(fittedHtml).every((html) => html.length > 0), + 'every fitted HTML entry has real content', + ); + }); + + test('isolated HTML for the default CardsGrid realm-index card is rendered normally when realm has opted in', async function (assert) { + let { realm } = await setupIntegrationTestRealm({ + mockMatrixUtils, + contents: { + 'index.json': { + data: { + type: 'card', + attributes: {}, + meta: { + adoptsFrom: { + module: 'https://cardstack.com/base/cards-grid', + name: 'CardsGrid', + }, + }, + }, + }, + 'realm.json': { + data: { + type: 'card', + attributes: { + includePrerenderedDefaultRealmIndex: true, + }, + meta: { + adoptsFrom: { + module: 'https://cardstack.com/base/realm-config', + name: 'RealmConfig', + }, + }, + }, + }, + }, + }); + let entry = await getInstance(realm, new URL(`${testRealmURL}index`)); + assert.ok(entry, 'realm index card is indexed'); + assert.notStrictEqual( + entry?.isolatedHtml, + REALM_INDEX_BOILERPLATE_HTML, + 'isolated_html is not the boilerplate when the realm has opted in', + ); + assert.ok(entry?.isolatedHtml, 'isolated_html is set'); + assert.ok( + (entry?.isolatedHtml ?? '').length > 0, + 'isolated_html has real content', + ); + }); + + test('isolated HTML for a non-CardsGrid index card is rendered normally regardless of opt-in', async function (assert) { + class PlainHomePage extends CardDef { + static displayName = 'Plain Home Page'; + @field title = contains(StringField); + static isolated = class Isolated extends Component { + + }; + } + let { realm } = await setupIntegrationTestRealm({ + mockMatrixUtils, + contents: { + 'home.gts': { PlainHomePage }, + 'index.json': { + data: { + type: 'card', + attributes: { title: 'Welcome' }, + meta: { + adoptsFrom: { + module: `${testRealmURL}home`, + name: 'PlainHomePage', + }, + }, + }, + }, + }, + }); + let entry = await getInstance(realm, new URL(`${testRealmURL}index`)); + assert.ok(entry, 'realm index card is indexed'); + assert.notStrictEqual( + entry?.isolatedHtml, + REALM_INDEX_BOILERPLATE_HTML, + 'isolated_html is not the boilerplate when the index is a non-CardsGrid card', + ); + assertInnerHtmlMatches( + assert, + entry?.isolatedHtml, + `

Welcome

`, + ); + }); }); diff --git a/packages/host/tests/integration/realm-test.gts b/packages/host/tests/integration/realm-test.gts index 31616d38f37..5b64674948c 100644 --- a/packages/host/tests/integration/realm-test.gts +++ b/packages/host/tests/integration/realm-test.gts @@ -3465,6 +3465,7 @@ posts/ignore-me.gts lastPublishedAt: null, interactHome: null, hostHome: null, + includePrerenderedDefaultRealmIndex: null, }, }, }, diff --git a/packages/realm-server/tests/helpers/index.ts b/packages/realm-server/tests/helpers/index.ts index a7ad7aed47a..de7287788d8 100644 --- a/packages/realm-server/tests/helpers/index.ts +++ b/packages/realm-server/tests/helpers/index.ts @@ -208,6 +208,7 @@ export const testRealmInfo = { realmUserId: testRealmServerMatrixUserId, publishable: null, lastPublishedAt: null, + includePrerenderedDefaultRealmIndex: false, }; export const realmServerTestMatrix: MatrixConfig = { diff --git a/packages/runtime-common/helpers/const.ts b/packages/runtime-common/helpers/const.ts index f6ba09396b8..d1154bfc43f 100644 --- a/packages/runtime-common/helpers/const.ts +++ b/packages/runtime-common/helpers/const.ts @@ -27,4 +27,5 @@ export const testRealmInfo: RealmInfo = { realmUserId: '@realm_server:localhost', publishable: null, lastPublishedAt: null, + includePrerenderedDefaultRealmIndex: null, }; diff --git a/packages/runtime-common/realm.ts b/packages/runtime-common/realm.ts index 7d2a1eaa335..27e3b72323a 100644 --- a/packages/runtime-common/realm.ts +++ b/packages/runtime-common/realm.ts @@ -197,6 +197,16 @@ export type RealmInfo = { realmUserId?: string; publishable: boolean | null; lastPublishedAt: string | Record | null; + // Opt-in to producing the full prerendered isolated HTML for the + // realm's default CardsGrid index card. When undefined / null / + // false the host's render route substitutes a small boilerplate + // placeholder instead and skips the (expensive) isolated render. + // The lever is primarily set by the publish handler on the + // published realm snapshot so anonymous-visitor SSR injection has + // real content; unpublished realms typically have nothing reading + // the index's isolated HTML. Optional to avoid forcing every + // RealmInfo fixture to update. + includePrerenderedDefaultRealmIndex?: boolean | null; }; const PROTECTED_REALM_CONFIG_PROPERTIES = ['showAsCatalog']; @@ -209,6 +219,7 @@ const REALM_CONFIG_CARD_PROPERTIES = new Set([ 'backgroundURL', 'iconURL', 'hostRoutingRules', + 'includePrerenderedDefaultRealmIndex', ]); // Fields owned by the realm_metadata DB table. Routes through @@ -5346,6 +5357,7 @@ export class Realm { ), publishable: null, lastPublishedAt, + includePrerenderedDefaultRealmIndex: null, }; if (realmConfig) { @@ -5418,6 +5430,12 @@ export class Realm { realmInfo.iconURL = typeof attrs.iconURL === 'string' ? attrs.iconURL : null; } + if ('includePrerenderedDefaultRealmIndex' in attrs) { + realmInfo.includePrerenderedDefaultRealmIndex = + typeof attrs.includePrerenderedDefaultRealmIndex === 'boolean' + ? attrs.includePrerenderedDefaultRealmIndex + : null; + } } } catch (e) { this.#log.warn(`failed to read RealmConfig card from disk: ${e}`); @@ -5450,6 +5468,12 @@ export class Realm { realmInfo.iconURL = typeof attrs.iconURL === 'string' ? attrs.iconURL : null; } + if ('includePrerenderedDefaultRealmIndex' in attrs) { + realmInfo.includePrerenderedDefaultRealmIndex = + typeof attrs.includePrerenderedDefaultRealmIndex === 'boolean' + ? attrs.includePrerenderedDefaultRealmIndex + : null; + } } } catch (e) { this.#log.warn(`failed to read RealmConfig card from index: ${e}`);