From c4853ea10398be636e3357bde8b56d30c1a39b87 Mon Sep 17 00:00:00 2001 From: yatishgoel Date: Sun, 21 Jun 2026 04:35:24 +0530 Subject: [PATCH 1/3] feat(url): add resolveIframeUrl function and enhance URL handling for subpath hosting --- code/core/src/manager-api/modules/url.ts | 9 ++- code/core/src/manager-api/tests/url.test.js | 85 +++++++++++++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/code/core/src/manager-api/modules/url.ts b/code/core/src/manager-api/modules/url.ts index 67b94017b0bf..a421a21fd866 100644 --- a/code/core/src/manager-api/modules/url.ts +++ b/code/core/src/manager-api/modules/url.ts @@ -57,6 +57,11 @@ const mergeSerializedParams = (params: string, extraParams: string) => { .join(';'); }; +const resolveIframeUrl = (baseUrl: string, { absolute }: { absolute: boolean }): string => { + const resolved = new URL('iframe.html', baseUrl); + return absolute ? resolved.href : `${resolved.pathname}${resolved.search}${resolved.hash}`; +}; + // Initialize the state based on the URL. // NOTE: // Although we don't change the URL when you change the state, we do support setting initial state @@ -256,10 +261,10 @@ export const init: ModuleFn = (moduleArgs) => { const networkAddress = global.STORYBOOK_NETWORK_ADDRESS ?? originAddress; const managerBase = base === 'origin' ? originAddress : base === 'network' ? networkAddress : pathname; + const previewBaseUrl = base ? managerBase : global.document?.baseURI || originAddress; const previewBase = refId ? refs[refId].url + '/iframe.html' - : global.PREVIEW_URL || - `${managerBase.replace(/\/[^/]*\.html$/, '').replace(/\/?$/, '/')}iframe.html`; + : global.PREVIEW_URL || resolveIframeUrl(previewBaseUrl, { absolute: Boolean(base) }); const refParam = refId ? `&refId=${encodeURIComponent(refId)}` : ''; const { args = '', globals = '', ...otherParams } = queryParams; diff --git a/code/core/src/manager-api/tests/url.test.js b/code/core/src/manager-api/tests/url.test.js index 30485a43c7a6..92fb70516466 100644 --- a/code/core/src/manager-api/tests/url.test.js +++ b/code/core/src/manager-api/tests/url.test.js @@ -22,6 +22,9 @@ vi.mock('@storybook/global', () => ({ origin: 'http://localhost:6006', }, }, + document: { + baseURI: 'http://localhost:6006/', + }, STORYBOOK_NETWORK_ADDRESS: 'http://192.168.1.1:6006/', }, })); @@ -295,6 +298,10 @@ describe('getStoryHrefs', () => { getState: () => state, }; + beforeEach(() => { + global.document.baseURI = 'http://localhost:6006/'; + }); + it('returns manager and preview URLs for a story', () => { const { api, state } = initURL({ store, @@ -513,28 +520,45 @@ describe('getStoryHrefs', () => { fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, }); store.setState(state); + global.document.baseURI = 'http://localhost:6006/index.html'; const { managerHref, previewHref } = api.getStoryHrefs('test--story'); expect(managerHref).toEqual('/index.html?path=/story/test--story'); expect(previewHref).toEqual('/iframe.html?id=test--story&viewMode=story'); }); - it('correctly links when hosted at a subpath without trailing slash', () => { + it('honors a custom for subpath hosting, independent of the router pathname (#33848)', () => { const { api, state } = initURL({ store, provider: { channel: new EventEmitter() }, - state: { location: { pathname: '/design-system', search: '' } }, + state: { location: { pathname: '/', search: '' } }, navigate: vi.fn(), fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, }); store.setState(state); + global.document.baseURI = 'http://localhost:6006/design-system/'; - const { managerHref, previewHref } = api.getStoryHrefs('test--story'); - expect(managerHref).toEqual('/design-system?path=/story/test--story'); + const { previewHref } = api.getStoryHrefs('test--story'); expect(previewHref).toEqual('/design-system/iframe.html?id=test--story&viewMode=story'); }); - it('correctly links when hosted at a subpath with trailing slash', () => { + it('resolves to the parent directory at a subpath without a trailing slash (#34733)', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/storybook', search: '' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + global.document.baseURI = 'http://localhost:6006/storybook'; + + const { managerHref, previewHref } = api.getStoryHrefs('test--story'); + expect(managerHref).toEqual('/storybook?path=/story/test--story'); + expect(previewHref).toEqual('/iframe.html?id=test--story&viewMode=story'); + }); + + it('correctly links when hosted at a subpath with a trailing slash', () => { const { api, state } = initURL({ store, provider: { channel: new EventEmitter() }, @@ -543,13 +567,14 @@ describe('getStoryHrefs', () => { fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, }); store.setState(state); + global.document.baseURI = 'http://localhost:6006/design-system/'; const { managerHref, previewHref } = api.getStoryHrefs('test--story'); expect(managerHref).toEqual('/design-system/?path=/story/test--story'); expect(previewHref).toEqual('/design-system/iframe.html?id=test--story&viewMode=story'); }); - it('correctly links when hosted at a subpath with index.html', () => { + it('correctly links when hosted at a subpath served via index.html', () => { const { api, state } = initURL({ store, provider: { channel: new EventEmitter() }, @@ -558,9 +583,57 @@ describe('getStoryHrefs', () => { fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, }); store.setState(state); + global.document.baseURI = 'http://localhost:6006/design-system/index.html'; const { managerHref, previewHref } = api.getStoryHrefs('test--story'); expect(managerHref).toEqual('/design-system/index.html?path=/story/test--story'); expect(previewHref).toEqual('/design-system/iframe.html?id=test--story&viewMode=story'); }); + + it('keeps the full prefix for a nested subpath with a trailing slash', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/a/b/c/', search: '' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + global.document.baseURI = 'http://localhost:6006/a/b/c/'; + + const { previewHref } = api.getStoryHrefs('test--story'); + expect(previewHref).toEqual('/a/b/c/iframe.html?id=test--story&viewMode=story'); + }); + + it('drops only the last segment for a nested subpath without a trailing slash', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/a/b/c', search: '' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + global.document.baseURI = 'http://localhost:6006/a/b/c'; + + const { previewHref } = api.getStoryHrefs('test--story'); + expect(previewHref).toEqual('/a/b/iframe.html?id=test--story&viewMode=story'); + }); + + it('resolves absolute base URLs at a subpath', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/design-system/', search: '' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const origin = api.getStoryHrefs('test--story', { base: 'origin' }); + expect(origin.previewHref).toContain('http://localhost:6006/design-system/iframe.html'); + + const network = api.getStoryHrefs('test--story', { base: 'network' }); + expect(network.previewHref).toContain('http://192.168.1.1:6006/iframe.html'); + }); }); From 64b2b4bbdd4abd9de0449a9f298fe18ad1ac85dd Mon Sep 17 00:00:00 2001 From: yatishgoel Date: Sun, 21 Jun 2026 04:37:21 +0530 Subject: [PATCH 2/3] fix(tests): update test description for clarity in URL handling --- code/core/src/manager-api/tests/url.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/manager-api/tests/url.test.js b/code/core/src/manager-api/tests/url.test.js index 92fb70516466..c2f814bcc324 100644 --- a/code/core/src/manager-api/tests/url.test.js +++ b/code/core/src/manager-api/tests/url.test.js @@ -542,7 +542,7 @@ describe('getStoryHrefs', () => { expect(previewHref).toEqual('/design-system/iframe.html?id=test--story&viewMode=story'); }); - it('resolves to the parent directory at a subpath without a trailing slash (#34733)', () => { + it('resolves to the parent directory at a subpath without a trailing slash', () => { const { api, state } = initURL({ store, provider: { channel: new EventEmitter() }, From 07b1e91964ce1e045330a51d32c458d2df4edd3a Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Thu, 10 Sep 2026 18:30:16 +0200 Subject: [PATCH 3/3] Revert formatting of unwatched files to keep PR clean --- .../evals/812-first-story-empty-project/package.json | 4 ++-- agent-eval/evals/821-upgrade-from-sb9/package.json | 4 ++-- agent-eval/evals/822-upgrade-from-stable/package.json | 4 ++-- .../evals/823-setup-outdated-storybook/package.json | 4 ++-- agent-eval/templates/monorepo/package.json | 10 +++++----- agent-eval/templates/monorepo/packages/ui/package.json | 2 +- agent-eval/templates/reshaped-storybook/package.json | 8 ++++---- agent-eval/templates/vite-app/package.json | 2 +- code/lib/codex-plugin/plugins/storybook/.mcp.json | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/agent-eval/evals/812-first-story-empty-project/package.json b/agent-eval/evals/812-first-story-empty-project/package.json index 8559c519458c..59e4672c72d6 100644 --- a/agent-eval/evals/812-first-story-empty-project/package.json +++ b/agent-eval/evals/812-first-story-empty-project/package.json @@ -2,10 +2,10 @@ "name": "812-first-story-empty-project", "type": "module", "scripts": { - "build-storybook": "storybook build", + "test:stories": "vitest run --config ./vitest.storybook.config.ts --project storybook", "postinstall": "playwright install chromium && node ./scripts/start-storybook-mcp.mjs", "storybook": "storybook dev", - "test:stories": "vitest run --config ./vitest.storybook.config.ts --project storybook" + "build-storybook": "storybook build" }, "devDependencies": { "@storybook/addon-a11y": "next", diff --git a/agent-eval/evals/821-upgrade-from-sb9/package.json b/agent-eval/evals/821-upgrade-from-sb9/package.json index a3c3a58e029f..948188fd55ce 100644 --- a/agent-eval/evals/821-upgrade-from-sb9/package.json +++ b/agent-eval/evals/821-upgrade-from-sb9/package.json @@ -2,8 +2,8 @@ "name": "821-upgrade-from-sb9", "type": "module", "scripts": { - "build-storybook": "storybook build", - "storybook": "storybook dev" + "storybook": "storybook dev", + "build-storybook": "storybook build" }, "devDependencies": { "@storybook/react": "9.1.20", diff --git a/agent-eval/evals/822-upgrade-from-stable/package.json b/agent-eval/evals/822-upgrade-from-stable/package.json index 7cec976e8cf3..1c71d4a2c9d1 100644 --- a/agent-eval/evals/822-upgrade-from-stable/package.json +++ b/agent-eval/evals/822-upgrade-from-stable/package.json @@ -2,8 +2,8 @@ "name": "822-upgrade-from-stable", "type": "module", "scripts": { - "build-storybook": "storybook build", - "storybook": "storybook dev" + "storybook": "storybook dev", + "build-storybook": "storybook build" }, "devDependencies": { "@storybook/react": "10.4.0", diff --git a/agent-eval/evals/823-setup-outdated-storybook/package.json b/agent-eval/evals/823-setup-outdated-storybook/package.json index 6128947b03fc..f8a34f4ad734 100644 --- a/agent-eval/evals/823-setup-outdated-storybook/package.json +++ b/agent-eval/evals/823-setup-outdated-storybook/package.json @@ -2,8 +2,8 @@ "name": "823-setup-outdated-storybook", "type": "module", "scripts": { - "build-storybook": "storybook build", - "storybook": "storybook dev" + "storybook": "storybook dev", + "build-storybook": "storybook build" }, "devDependencies": { "@storybook/react": "10.4.0", diff --git a/agent-eval/templates/monorepo/package.json b/agent-eval/templates/monorepo/package.json index b0c2aad6683d..1d2fa011415b 100644 --- a/agent-eval/templates/monorepo/package.json +++ b/agent-eval/templates/monorepo/package.json @@ -2,16 +2,16 @@ "name": "@storybook/mcp-eval-template-monorepo", "version": "0.0.0", "private": true, + "workspaces": [ + "packages/*" + ], "type": "module", "imports": { "#test-utils": "./__agent_eval__/test-utils.ts" }, - "workspaces": [ - "packages/*" - ], "scripts": { - "postinstall": "playwright install chromium && node ./scripts/start-storybook-mcp.mjs", - "storybook": "npm run storybook --workspace @acme/ui --" + "storybook": "npm run storybook --workspace @acme/ui --", + "postinstall": "playwright install chromium && node ./scripts/start-storybook-mcp.mjs" }, "devDependencies": { "@vercel/agent-eval": "1.2.0", diff --git a/agent-eval/templates/monorepo/packages/ui/package.json b/agent-eval/templates/monorepo/packages/ui/package.json index fa3d30abc7ea..64e26d16d2b9 100644 --- a/agent-eval/templates/monorepo/packages/ui/package.json +++ b/agent-eval/templates/monorepo/packages/ui/package.json @@ -5,8 +5,8 @@ "type": "module", "main": "./src/index.ts", "scripts": { - "build-storybook": "storybook build", "storybook": "storybook dev", + "build-storybook": "storybook build", "test:stories": "vitest run --config ./vitest.storybook.config.ts --project storybook" }, "dependencies": { diff --git a/agent-eval/templates/reshaped-storybook/package.json b/agent-eval/templates/reshaped-storybook/package.json index bd2f76bcc14d..83c078441a2c 100644 --- a/agent-eval/templates/reshaped-storybook/package.json +++ b/agent-eval/templates/reshaped-storybook/package.json @@ -8,13 +8,13 @@ }, "scripts": { "build": "vite build", - "build-storybook": "storybook build", - "postinstall": "playwright install chromium && node ./scripts/start-storybook-mcp.mjs", + "typecheck": "tsc --noEmit --project ./tsconfig.app.json", "lint": "eslint .", - "storybook": "storybook dev", "test:stories": "vitest run --config ./vitest.storybook.config.ts --project storybook", "test:stories:coverage": "vitest run --config ./vitest.storybook.config.ts --project storybook --coverage", - "typecheck": "tsc --noEmit --project ./tsconfig.app.json" + "postinstall": "playwright install chromium && node ./scripts/start-storybook-mcp.mjs", + "storybook": "storybook dev", + "build-storybook": "storybook build" }, "dependencies": { "react": "19.2.0", diff --git a/agent-eval/templates/vite-app/package.json b/agent-eval/templates/vite-app/package.json index fb904bd49b72..5bf95f6acf48 100644 --- a/agent-eval/templates/vite-app/package.json +++ b/agent-eval/templates/vite-app/package.json @@ -7,8 +7,8 @@ "#test-utils": "./__agent_eval__/test-utils.ts" }, "scripts": { - "build": "vite build", "dev": "vite", + "build": "vite build", "typecheck": "tsc --noEmit --project ./tsconfig.app.json" }, "dependencies": { diff --git a/code/lib/codex-plugin/plugins/storybook/.mcp.json b/code/lib/codex-plugin/plugins/storybook/.mcp.json index da39e4ffafe8..47925bfecab0 100644 --- a/code/lib/codex-plugin/plugins/storybook/.mcp.json +++ b/code/lib/codex-plugin/plugins/storybook/.mcp.json @@ -1,3 +1,3 @@ { - "mcpServers": {} + "mcpServers": {} }