From 840bb2ff580c8f479880ad2c2c0c018634bdee7b Mon Sep 17 00:00:00 2001 From: Sridhar Maskeri Date: Fri, 27 Feb 2026 12:08:17 +0530 Subject: [PATCH 1/5] feat[gen2]: ENG-11602 add support for global symbols --- packages/sdks/src/blocks/symbol/symbol.helpers.ts | 3 ++- packages/sdks/src/blocks/symbol/symbol.lite.tsx | 2 +- packages/sdks/src/blocks/symbol/symbol.types.ts | 1 + packages/sdks/src/scripts/init-editing.ts | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/sdks/src/blocks/symbol/symbol.helpers.ts b/packages/sdks/src/blocks/symbol/symbol.helpers.ts index 762ff853a32..3d1dbcdc95f 100644 --- a/packages/sdks/src/blocks/symbol/symbol.helpers.ts +++ b/packages/sdks/src/blocks/symbol/symbol.helpers.ts @@ -10,6 +10,7 @@ export interface SymbolInfo { content?: BuilderContent; inline?: boolean; dynamic?: boolean; + ownerId?: string; } export const fetchSymbolContent = async ({ @@ -35,7 +36,7 @@ export const fetchSymbolContent = async ({ ) { return fetchOneEntry({ model: symbol.model, - apiKey: builderContextValue.apiKey, + apiKey: symbol.ownerId || builderContextValue.apiKey, apiVersion: builderContextValue.apiVersion, ...(symbol?.entry && { query: { diff --git a/packages/sdks/src/blocks/symbol/symbol.lite.tsx b/packages/sdks/src/blocks/symbol/symbol.lite.tsx index 10ed644a73b..1189e0ee4fa 100644 --- a/packages/sdks/src/blocks/symbol/symbol.lite.tsx +++ b/packages/sdks/src/blocks/symbol/symbol.lite.tsx @@ -132,7 +132,7 @@ export default function Symbol(props: SymbolProps) { nonce={props.builderContext.value.nonce} isNestedRender apiVersion={props.builderContext.value.apiVersion} - apiKey={props.builderContext.value.apiKey!} + apiKey={props.symbol?.ownerId || props.builderContext.value.apiKey!} context={{ ...props.builderContext.value.context, symbolId: props.builderBlock?.id, diff --git a/packages/sdks/src/blocks/symbol/symbol.types.ts b/packages/sdks/src/blocks/symbol/symbol.types.ts index 80b0b4f5fe7..c19b5b2dd6e 100644 --- a/packages/sdks/src/blocks/symbol/symbol.types.ts +++ b/packages/sdks/src/blocks/symbol/symbol.types.ts @@ -12,6 +12,7 @@ export interface SymbolInfo { content?: BuilderContent; inline?: boolean; dynamic?: boolean; + ownerId?: string; } export interface SymbolProps diff --git a/packages/sdks/src/scripts/init-editing.ts b/packages/sdks/src/scripts/init-editing.ts index b080c206404..055a85b019a 100644 --- a/packages/sdks/src/scripts/init-editing.ts +++ b/packages/sdks/src/scripts/init-editing.ts @@ -32,6 +32,7 @@ export const setupBrowserForEditing = (options: { apiKey: options.apiKey, supportsXSmallBreakpoint: TARGET === 'reactNative' ? false : true, blockLevelPersonalization: true, + supportsGlobalSymbols: true, }, }, '*' From 48135ef1e26da5a29e8727c4a584f3aa7b8d9cf0 Mon Sep 17 00:00:00 2001 From: Sridhar Maskeri Date: Fri, 27 Feb 2026 13:28:26 +0530 Subject: [PATCH 2/5] Add strict check for symbols with ownerId so that we only check when they are truly global --- packages/sdks/src/blocks/symbol/symbol.helpers.ts | 3 ++- packages/sdks/src/blocks/symbol/symbol.lite.tsx | 2 +- packages/sdks/src/blocks/symbol/symbol.types.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sdks/src/blocks/symbol/symbol.helpers.ts b/packages/sdks/src/blocks/symbol/symbol.helpers.ts index 3d1dbcdc95f..d42406a87ef 100644 --- a/packages/sdks/src/blocks/symbol/symbol.helpers.ts +++ b/packages/sdks/src/blocks/symbol/symbol.helpers.ts @@ -11,6 +11,7 @@ export interface SymbolInfo { inline?: boolean; dynamic?: boolean; ownerId?: string; + global?: boolean; } export const fetchSymbolContent = async ({ @@ -36,7 +37,7 @@ export const fetchSymbolContent = async ({ ) { return fetchOneEntry({ model: symbol.model, - apiKey: symbol.ownerId || builderContextValue.apiKey, + apiKey: (symbol.global && symbol.ownerId) ? symbol.ownerId : builderContextValue.apiKey, apiVersion: builderContextValue.apiVersion, ...(symbol?.entry && { query: { diff --git a/packages/sdks/src/blocks/symbol/symbol.lite.tsx b/packages/sdks/src/blocks/symbol/symbol.lite.tsx index 1189e0ee4fa..7eced0b524d 100644 --- a/packages/sdks/src/blocks/symbol/symbol.lite.tsx +++ b/packages/sdks/src/blocks/symbol/symbol.lite.tsx @@ -132,7 +132,7 @@ export default function Symbol(props: SymbolProps) { nonce={props.builderContext.value.nonce} isNestedRender apiVersion={props.builderContext.value.apiVersion} - apiKey={props.symbol?.ownerId || props.builderContext.value.apiKey!} + apiKey={(props.symbol?.global && props.symbol?.ownerId) ? props.symbol.ownerId : props.builderContext.value.apiKey!} context={{ ...props.builderContext.value.context, symbolId: props.builderBlock?.id, diff --git a/packages/sdks/src/blocks/symbol/symbol.types.ts b/packages/sdks/src/blocks/symbol/symbol.types.ts index c19b5b2dd6e..3904e35655a 100644 --- a/packages/sdks/src/blocks/symbol/symbol.types.ts +++ b/packages/sdks/src/blocks/symbol/symbol.types.ts @@ -13,6 +13,7 @@ export interface SymbolInfo { inline?: boolean; dynamic?: boolean; ownerId?: string; + global?: boolean; } export interface SymbolProps From 9214940895a9c48b20a156ee4d8ddf4d69e315d1 Mon Sep 17 00:00:00 2001 From: Sridhar Maskeri Date: Fri, 27 Feb 2026 13:52:13 +0530 Subject: [PATCH 3/5] reverted unwanted change --- packages/sdks/src/scripts/init-editing.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sdks/src/scripts/init-editing.ts b/packages/sdks/src/scripts/init-editing.ts index 055a85b019a..b080c206404 100644 --- a/packages/sdks/src/scripts/init-editing.ts +++ b/packages/sdks/src/scripts/init-editing.ts @@ -32,7 +32,6 @@ export const setupBrowserForEditing = (options: { apiKey: options.apiKey, supportsXSmallBreakpoint: TARGET === 'reactNative' ? false : true, blockLevelPersonalization: true, - supportsGlobalSymbols: true, }, }, '*' From dc4422b82cfe2a79ec4475ea787428565068093e Mon Sep 17 00:00:00 2001 From: Sridhar Maskeri Date: Fri, 27 Feb 2026 15:25:59 +0530 Subject: [PATCH 4/5] add unit tests to cover - global symbol support code changes --- .../sdks-tests/src/e2e-tests/symbols.spec.ts | 81 ++++++++++++++++++- packages/sdks-tests/src/specs/index.ts | 3 +- packages/sdks-tests/src/specs/symbols.ts | 20 +++++ 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/packages/sdks-tests/src/e2e-tests/symbols.spec.ts b/packages/sdks-tests/src/e2e-tests/symbols.spec.ts index 3affc18bbc2..837e51116d5 100644 --- a/packages/sdks-tests/src/e2e-tests/symbols.spec.ts +++ b/packages/sdks-tests/src/e2e-tests/symbols.spec.ts @@ -1,7 +1,11 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; import { DEFAULT_TEXT_SYMBOL, FRENCH_TEXT_SYMBOL } from '../specs/symbol-with-locale.js'; -import { FIRST_SYMBOL_CONTENT, SECOND_SYMBOL_CONTENT } from '../specs/symbols.js'; +import { + FIRST_SYMBOL_CONTENT, + SECOND_SYMBOL_CONTENT, + GLOBAL_SYMBOL_OWNER_ID, +} from '../specs/symbols.js'; import { excludeGen2, checkIsGen1React, @@ -245,4 +249,79 @@ test.describe('Symbols', () => { const symbols = page.locator(selector); await expect(symbols).toHaveCount(2); }); + + test.describe('global symbols', () => { + const symbolUrlMatch = /https:\/\/cdn\.builder\.io\/api\/v3\/content\/symbol\.*/; + + /** + * When a symbol block has `global: true` and `ownerId` set to a different space, + * the SDK must use `ownerId` as the apiKey in the CDN fetch — not the current space's apiKey. + * + * Uses `/symbols-with-global` which has block[1] marked as global with GLOBAL_SYMBOL_OWNER_ID. + */ + test('uses ownerId as apiKey when fetching a global symbol', async ({ + page, + packageName, + sdk, + }) => { + test.skip(checkIsGen1React(sdk)); + test.fail(SSR_FETCHING_PACKAGES.includes(packageName)); + + const capturedApiKeys: string[] = []; + + await page.route(symbolUrlMatch, route => { + const url = new URL(route.request().url()); + capturedApiKeys.push(url.searchParams.get('apiKey') || ''); + + return route.fulfill({ + status: 200, + json: { results: [FIRST_SYMBOL_CONTENT] }, + }); + }); + + await page.goto('/symbols-with-global'); + + // Wait for symbols to render (ensures the CDN fetch has completed) + await expect(page.getByText('default description').locator('visible=true')).toBeVisible(); + + // At least one fetch must have used the global symbol owner's apiKey + expect(capturedApiKeys).toContain(GLOBAL_SYMBOL_OWNER_ID); + }); + + /** + * When a symbol block does not have the `global` attribute, the SDK must always use the current + * space's apiKey — even if `ownerId` is present on the symbol data. + * + * Uses `/symbols-without-content` which has no global symbols (no `global: true`). + * All fetches must use the same apiKey (the current space key). + */ + test('non-global symbol does not use ownerId as apiKey', async ({ page, packageName, sdk }) => { + test.skip(checkIsGen1React(sdk)); + test.fail(SSR_FETCHING_PACKAGES.includes(packageName)); + + const capturedApiKeys: string[] = []; + + await page.route(symbolUrlMatch, route => { + const url = new URL(route.request().url()); + capturedApiKeys.push(url.searchParams.get('apiKey') || ''); + + return route.fulfill({ + status: 200, + json: { results: [FIRST_SYMBOL_CONTENT] }, + }); + }); + + await page.goto('/symbols-without-content'); + + // Wait for symbols to render (ensures the CDN fetch has completed) + await expect(page.getByText('default description').locator('visible=true')).toBeVisible(); + + await expect(capturedApiKeys.length).toBeGreaterThanOrEqual(1); + + // All fetches must use the same apiKey — no cross-space key should appear + const uniqueKeys = Array.from(new Set(capturedApiKeys)); + expect(uniqueKeys.length).toBe(1); + expect(uniqueKeys[0]).not.toBe(GLOBAL_SYMBOL_OWNER_ID); + }); + }); }); diff --git a/packages/sdks-tests/src/specs/index.ts b/packages/sdks-tests/src/specs/index.ts index 49e3fa443a0..5ae41bee3a5 100644 --- a/packages/sdks-tests/src/specs/index.ts +++ b/packages/sdks-tests/src/specs/index.ts @@ -46,7 +46,7 @@ import { CONTENT as symbolAbTest } from './symbol-ab-test.js'; import { CONTENT as symbolBindings } from './symbol-bindings.js'; import { CONTENT as symbolWithInputBinding } from './symbol-with-input-binding.js'; import { CONTENT as symbolWithLocale } from './symbol-with-locale.js'; -import { CONTENT_WITHOUT_SYMBOLS, CONTENT as symbols } from './symbols.js'; +import { CONTENT_WITHOUT_SYMBOLS, CONTENT_WITH_GLOBAL_SYMBOL, CONTENT as symbols } from './symbols.js'; import { TABS } from './tabs.js'; import { CONTENT as textBlock } from './text-block.js'; import { CONTENT as textEval } from './text-eval.js'; @@ -137,6 +137,7 @@ export const PAGES: Record = { '/symbols': { content: symbols }, '/js-code': { content: JS_CODE_CONTENT }, '/symbols-without-content': { content: CONTENT_WITHOUT_SYMBOLS }, + '/symbols-with-global': { content: CONTENT_WITH_GLOBAL_SYMBOL }, '/symbol-bindings': { content: symbolBindings }, '/symbol-with-locale': { content: symbolWithLocale }, '/link-url': { content: linkUrl }, diff --git a/packages/sdks-tests/src/specs/symbols.ts b/packages/sdks-tests/src/specs/symbols.ts index 05e1661804a..0bd303d8152 100644 --- a/packages/sdks-tests/src/specs/symbols.ts +++ b/packages/sdks-tests/src/specs/symbols.ts @@ -738,3 +738,23 @@ const splitUpContent = () => { export const { CONTENT_WITHOUT_SYMBOLS, FIRST_SYMBOL_CONTENT, SECOND_SYMBOL_CONTENT } = splitUpContent(); + +/** + * The API key of the space that owns the global symbol — different from the current space. + */ +export const GLOBAL_SYMBOL_OWNER_ID = 'global-owner-space-api-key-abc123'; + +/** + * A page content where: + * - block[1] is a GLOBAL symbol (global: true, ownerId = GLOBAL_SYMBOL_OWNER_ID) + * - block[2] is a LOCAL symbol (global: undefined, no ownerId override) + */ +export const CONTENT_WITH_GLOBAL_SYMBOL = (() => { + const clone = JSON.parse(JSON.stringify(CONTENT_WITHOUT_SYMBOLS)); + // Mark the first symbol as global, owned by a different space + clone.data.blocks[1].component.options.symbol.global = true; + clone.data.blocks[1].component.options.symbol.ownerId = GLOBAL_SYMBOL_OWNER_ID; + // Explicitly mark the second symbol as non-global + clone.data.blocks[2].component.options.symbol.global = undefined; + return clone; +})(); From 7657fe443e4c5dd18dbf88fc973bb06cb5564069 Mon Sep 17 00:00:00 2001 From: Sridhar Maskeri Date: Fri, 27 Feb 2026 15:59:35 +0530 Subject: [PATCH 5/5] update the spec to fix issue with serialization --- packages/sdks-tests/src/specs/symbols.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/sdks-tests/src/specs/symbols.ts b/packages/sdks-tests/src/specs/symbols.ts index 0bd303d8152..cccf3edd8f4 100644 --- a/packages/sdks-tests/src/specs/symbols.ts +++ b/packages/sdks-tests/src/specs/symbols.ts @@ -747,14 +747,12 @@ export const GLOBAL_SYMBOL_OWNER_ID = 'global-owner-space-api-key-abc123'; /** * A page content where: * - block[1] is a GLOBAL symbol (global: true, ownerId = GLOBAL_SYMBOL_OWNER_ID) - * - block[2] is a LOCAL symbol (global: undefined, no ownerId override) + * - block[2] is a LOCAL symbol (no global/ownerId — uses current space apiKey by default) */ export const CONTENT_WITH_GLOBAL_SYMBOL = (() => { const clone = JSON.parse(JSON.stringify(CONTENT_WITHOUT_SYMBOLS)); - // Mark the first symbol as global, owned by a different space + // Mark the first symbol as global, owned by a different space. clone.data.blocks[1].component.options.symbol.global = true; clone.data.blocks[1].component.options.symbol.ownerId = GLOBAL_SYMBOL_OWNER_ID; - // Explicitly mark the second symbol as non-global - clone.data.blocks[2].component.options.symbol.global = undefined; return clone; })();