diff --git a/deno.json b/deno.json
index 6b728feaee..b349b334fe 100644
--- a/deno.json
+++ b/deno.json
@@ -1,6 +1,6 @@
{
"name": "veryfront",
- "version": "0.1.1157",
+ "version": "0.1.1158",
"license": "Apache-2.0",
"nodeModulesDir": "auto",
"minimumDependencyAge": {
@@ -472,7 +472,7 @@
"test:all-runtimes": "deno task test:unit && deno task test:node && deno task test:bun",
"test:e2e": "deno task test:e2e:playwright",
"test:e2e:playwright": "PW_DISABLE_TS_ESM=1 npx playwright test --config=tests/e2e/playwright.config.cjs",
- "test:e2e:rsc-browser": "deno task generate && VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --allow-all tests/e2e/regressions/rsc-proxy-hydration.test.ts --unstable-worker-options --unstable-net",
+ "test:e2e:rsc-browser": "deno task generate && VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --allow-all tests/e2e/regressions/rsc-proxy-hydration.test.ts tests/e2e/regressions/2026-07-27-legacy-router-hydration.test.ts --unstable-worker-options --unstable-net",
"test:e2e:binary": "deno task generate && deno test --allow-all tests/integration/compiled-binary-e2e.test.ts",
"test:e2e:binary:fresh": "deno task generate && VERYFRONT_BINARY_FRESH=1 deno test --allow-all tests/integration/compiled-binary-e2e.test.ts",
"test:e2e:templates": "deno run --allow-all scripts/test/template-runtime-e2e.ts",
diff --git a/src/html/hydration-script-builder/dev-client-renderer.ts b/src/html/hydration-script-builder/dev-client-renderer.ts
index 1584a44f16..58aa59810f 100644
--- a/src/html/hydration-script-builder/dev-client-renderer.ts
+++ b/src/html/hydration-script-builder/dev-client-renderer.ts
@@ -1,4 +1,9 @@
-import { getLoaderScript, getRendererScript, getRouterScript } from "./templates/index.ts";
+import {
+ getLoaderScript,
+ getNavigationStoreCompatibilityScript,
+ getRendererScript,
+ getRouterScript,
+} from "./templates/index.ts";
import { buildNonceAttribute } from "../html-escape.ts";
export function generateDevClientRendererScript(nonce?: string): string {
@@ -8,8 +13,10 @@ export function generateDevClientRendererScript(nonce?: string): string {
");
+ if (start === 0 || end < start) throw new Error("Expected a module script tag");
+ return scriptTag.slice(start, end);
+}
+
describe("hydration-script-builder/prod-scripts", () => {
describe("getProdScripts", () => {
it("should return an external module script tag with the versioned runtime path", () => {
@@ -81,3 +137,17 @@ describe("hydration-script-builder/prod-scripts", () => {
});
});
});
+
+Deno.test({
+ name: "generated hydration modules link against router assets from existing releases",
+ async fn() {
+ try {
+ await bundleHydrationModuleAgainstLegacyRouter(generateProdHydrationModule());
+ await bundleHydrationModuleAgainstLegacyRouter(
+ extractModuleScript(generateDevClientRendererScript()),
+ );
+ } finally {
+ await esbuild.stop();
+ }
+ },
+});
diff --git a/src/html/hydration-script-builder/prod-scripts.ts b/src/html/hydration-script-builder/prod-scripts.ts
index 776926a8e7..4c2f353b75 100644
--- a/src/html/hydration-script-builder/prod-scripts.ts
+++ b/src/html/hydration-script-builder/prod-scripts.ts
@@ -1,4 +1,9 @@
-import { getLoaderScript, getRendererScript, getRouterScript } from "./templates/index.ts";
+import {
+ getLoaderScript,
+ getNavigationStoreCompatibilityScript,
+ getRendererScript,
+ getRouterScript,
+} from "./templates/index.ts";
import { buildNonceAttribute } from "../html-escape.ts";
import { fnv1aHash } from "#veryfront/utils/hash-utils.ts";
@@ -12,8 +17,10 @@ export function generateProdHydrationModule(): string {
return [
`import * as React from 'react';`,
`import { createRoot } from 'react-dom/client';`,
- `import { RouterProvider, useRouter as useRouterFromModule, getNavigationStore } from 'veryfront/router';`,
+ `import { RouterProvider, useRouter as useRouterFromModule } from 'veryfront/router';`,
+ `import * as RouterRuntime from 'veryfront/router';`,
`import { PageContextProvider } from 'veryfront/context';`,
+ getNavigationStoreCompatibilityScript().trim(),
getRouterScript().trim(),
getLoaderScript().trim(),
getRendererScript().trim(),
diff --git a/src/html/hydration-script-builder/templates/index.ts b/src/html/hydration-script-builder/templates/index.ts
index 46eafc2e5a..2c28566c1e 100644
--- a/src/html/hydration-script-builder/templates/index.ts
+++ b/src/html/hydration-script-builder/templates/index.ts
@@ -5,6 +5,7 @@
*/
export { getRouterScript } from "./router.ts";
+export { getNavigationStoreCompatibilityScript } from "./navigation-store.ts";
export { getLoaderScript } from "./loader.ts";
export { getRendererScript } from "./renderer.ts";
export { getSpaLoaderScript, getSpaRendererScript } from "./spa-renderer.ts";
diff --git a/src/html/hydration-script-builder/templates/navigation-store.ts b/src/html/hydration-script-builder/templates/navigation-store.ts
new file mode 100644
index 0000000000..5e6d90cec8
--- /dev/null
+++ b/src/html/hydration-script-builder/templates/navigation-store.ts
@@ -0,0 +1,53 @@
+/**
+ * Generate the compatibility bridge between the shared hydration runtime and
+ * release-pinned router assets.
+ *
+ * Router releases since the v1 navigation store landed share the same
+ * Symbol.for registry entry even when they predate the public
+ * getNavigationStore export. Keeping the fallback store shape in sync with the
+ * two v1 implementations lets those releases retain SPA navigation.
+ */
+export const getNavigationStoreCompatibilityScript = () => `
+ const navigationStoreUsesRegistryFallback =
+ typeof RouterRuntime.getNavigationStore !== 'function';
+ const getNavigationStore = navigationStoreUsesRegistryFallback
+ ? () => {
+ const storeKey = Symbol.for('veryfront.navigation.store.v1');
+ const existing = globalThis[storeKey];
+ if (existing) return existing;
+
+ const listeners = new Set();
+ let navigator = null;
+ const store = {
+ subscribe(listener) {
+ listeners.add(listener);
+ return () => listeners.delete(listener);
+ },
+ getHref() {
+ const loc = globalThis.location;
+ return loc ? loc.pathname + loc.search + loc.hash : '/';
+ },
+ notify() {
+ for (const listener of [...listeners]) {
+ try {
+ listener();
+ } catch {
+ // One subscriber must not prevent the others from updating.
+ }
+ }
+ },
+ navigate(href, options) {
+ if (navigator) return navigator(href, options);
+ globalThis.location?.assign(href);
+ return Promise.resolve();
+ },
+ setNavigator(next) {
+ navigator = next;
+ },
+ };
+
+ globalThis[storeKey] = store;
+ return store;
+ }
+ : RouterRuntime.getNavigationStore;
+`;
diff --git a/src/html/hydration-script-builder/templates/router.ts b/src/html/hydration-script-builder/templates/router.ts
index ed8f762da1..b68c40db9e 100644
--- a/src/html/hydration-script-builder/templates/router.ts
+++ b/src/html/hydration-script-builder/templates/router.ts
@@ -1153,11 +1153,19 @@ export const getRouterScript = () => `
// same SPA navigator that intercepts clicks. Without this the shared
// navigation store has no navigator registered and its navigate() falls back
// to a full-page location.assign (finding #7: push() full-reloads).
- getNavigationStore().setNavigator((href, options) => {
- const mode = options && options.history;
- const historyMode = mode === 'replace' ? 'replace' : mode === 'none' ? 'none' : 'push';
- return navigateSPA(href, historyMode);
- });
+ if (
+ typeof navigationStoreUsesRegistryFallback !== 'undefined' &&
+ navigationStoreUsesRegistryFallback
+ ) {
+ log('Router runtime does not export getNavigationStore; using shared v1 registry fallback');
+ }
+ if (typeof getNavigationStore === 'function') {
+ getNavigationStore().setNavigator((href, options) => {
+ const mode = options && options.history;
+ const historyMode = mode === 'replace' ? 'replace' : mode === 'none' ? 'none' : 'push';
+ return navigateSPA(href, historyMode);
+ });
+ }
// ============================================
// Event handlers
diff --git a/src/transforms/mdx/esm-module-loader/cache/index.ts b/src/transforms/mdx/esm-module-loader/cache/index.ts
index 28b9d89d42..948453a5e7 100644
--- a/src/transforms/mdx/esm-module-loader/cache/index.ts
+++ b/src/transforms/mdx/esm-module-loader/cache/index.ts
@@ -485,7 +485,7 @@ export async function clearESMDiskCache(): Promise {
try {
// Remove entire cache directory and recreate it
- // This handles nested project directories like codersociety/local-main/
+ // This handles nested project directories such as customer/local-main/
await fs.remove(cacheDir, { recursive: true });
await fs.mkdir(cacheDir, { recursive: true });
logger.debug(`${LOG_PREFIX_MDX_LOADER} Cleared ESM disk cache`);
diff --git a/src/utils/version-constant.ts b/src/utils/version-constant.ts
index fff3c6910c..520c0fc892 100644
--- a/src/utils/version-constant.ts
+++ b/src/utils/version-constant.ts
@@ -1,4 +1,4 @@
// Keep in sync with deno.json version.
// scripts/release.ts updates this constant during releases.
/** Shared version value. */
-export const VERSION = "0.1.1157";
+export const VERSION = "0.1.1158";
diff --git a/tests/_helpers/playwright.test.ts b/tests/_helpers/playwright.test.ts
index 3a0816b7cd..dfe04f73b0 100644
--- a/tests/_helpers/playwright.test.ts
+++ b/tests/_helpers/playwright.test.ts
@@ -3,6 +3,7 @@ import { describe, it } from "#veryfront/testing/bdd.ts";
import type { Browser } from "npm:playwright@1.60.0";
import {
CHROMIUM_LAUNCH_TIMEOUT_MS,
+ cleanupBrowserBridgeProcess,
launchChromiumWith,
parseBrowserBridgeMessage,
} from "./playwright.ts";
@@ -71,3 +72,28 @@ describe("launchChromiumWith", () => {
assertEquals(browser, null);
});
});
+
+describe("cleanupBrowserBridgeProcess", () => {
+ it("force-kills a bridge that does not exit after stdin closes", async () => {
+ let resolveStatus: (status: Deno.CommandStatus) => void = () => {};
+ const statusPromise = new Promise((resolve) => {
+ resolveStatus = resolve;
+ });
+ const signals: Deno.Signal[] = [];
+
+ await cleanupBrowserBridgeProcess(
+ {
+ stdin: new WritableStream(),
+ kill(signal) {
+ signals.push(signal);
+ resolveStatus({ success: false, code: 137, signal });
+ },
+ },
+ statusPromise,
+ Promise.resolve(""),
+ 10,
+ );
+
+ assertEquals(signals, ["SIGKILL"]);
+ });
+});
diff --git a/tests/_helpers/playwright.ts b/tests/_helpers/playwright.ts
index b7be58352a..994a91fb6e 100644
--- a/tests/_helpers/playwright.ts
+++ b/tests/_helpers/playwright.ts
@@ -22,6 +22,7 @@ export function isMissingBrowserExecutable(error: unknown): boolean {
}
export const CHROMIUM_LAUNCH_TIMEOUT_MS = 15_000;
+const BROWSER_BRIDGE_SHUTDOWN_TIMEOUT_MS = 1_000;
interface ChromiumLauncher {
launch(options: { headless: boolean; timeout: number }): Promise;
@@ -35,6 +36,13 @@ interface BrowserBridgeMessage {
wsEndpoint: string;
}
+interface BrowserBridgeProcess {
+ stdin: WritableStream;
+ kill(signal: Deno.Signal): void;
+}
+
+const browserBridgeCleanups = new WeakMap Promise>();
+
export function parseBrowserBridgeMessage(message: string): BrowserBridgeMessage {
let parsed: unknown;
try {
@@ -61,12 +69,17 @@ async function readBrowserBridgeMessage(
const decoder = new TextDecoder();
let output = "";
- while (true) {
- const { done, value } = await reader.read();
- if (done) throw new Error("Playwright browser bridge exited before reporting an endpoint");
- output += decoder.decode(value, { stream: true });
- const newline = output.indexOf("\n");
- if (newline >= 0) return parseBrowserBridgeMessage(output.slice(0, newline));
+ try {
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) throw new Error("Playwright browser bridge exited before reporting an endpoint");
+ output += decoder.decode(value, { stream: true });
+ const newline = output.indexOf("\n");
+ if (newline >= 0) return parseBrowserBridgeMessage(output.slice(0, newline));
+ }
+ } finally {
+ await reader.cancel();
+ reader.releaseLock();
}
}
@@ -84,6 +97,57 @@ async function withTimeout(promise: Promise, timeoutMs: number, message: s
}
}
+/** @internal Exported for deterministic bridge-cleanup regression coverage. */
+export async function cleanupBrowserBridgeProcess(
+ child: BrowserBridgeProcess,
+ statusPromise: Promise,
+ stderrPromise: Promise,
+ timeoutMs = BROWSER_BRIDGE_SHUTDOWN_TIMEOUT_MS,
+): Promise {
+ try {
+ const writer = child.stdin.getWriter();
+ try {
+ await writer.close();
+ } finally {
+ writer.releaseLock();
+ }
+ } catch {
+ try {
+ child.kill("SIGTERM");
+ } catch {
+ /* expected: bridge may already have exited */
+ }
+ }
+
+ try {
+ await withTimeout(
+ Promise.allSettled([statusPromise, stderrPromise]),
+ timeoutMs,
+ `Playwright browser bridge did not exit within ${timeoutMs}ms`,
+ );
+ return;
+ } catch {
+ try {
+ child.kill("SIGKILL");
+ } catch {
+ /* expected: bridge may have exited as the timeout fired */
+ }
+ }
+
+ await Promise.allSettled([
+ withTimeout(
+ statusPromise,
+ timeoutMs,
+ `Playwright browser bridge did not report exit after SIGKILL within ${timeoutMs}ms`,
+ ),
+ withTimeout(
+ stderrPromise,
+ timeoutMs,
+ `Playwright browser bridge stderr did not close after SIGKILL within ${timeoutMs}ms`,
+ ),
+ ]);
+}
+
async function launchChromiumThroughNode(chromium: ChromiumConnector): Promise {
const bridgePath = fromFileUrl(new URL("./playwright-node-bridge.mjs", import.meta.url));
const child = new Deno.Command("node", {
@@ -92,13 +156,19 @@ async function launchChromiumThroughNode(chromium: ChromiumConnector): Promise | undefined;
+ const cleanup = (): Promise => {
+ cleanupPromise ??= cleanupBrowserBridgeProcess(child, statusPromise, stderrPromise);
+ return cleanupPromise;
+ };
try {
const bridge = await withTimeout(
Promise.race([
readBrowserBridgeMessage(child.stdout),
- child.status.then(async (status) => {
+ statusPromise.then(async (status) => {
const stderr = (await stderrPromise).trim();
throw new Error(
`Playwright browser bridge exited with code ${status.code}${
@@ -114,31 +184,14 @@ async function launchChromiumThroughNode(chromium: ChromiumConnector): Promise {
- if (cleanedUp) return;
- cleanedUp = true;
- void (async () => {
- try {
- const writer = child.stdin.getWriter();
- await writer.close();
- } catch {
- try {
- child.kill("SIGTERM");
- } catch {
- /* expected: bridge may already have exited */
- }
- }
- })();
+ void cleanup();
});
return browser;
} catch (error) {
- try {
- child.kill("SIGTERM");
- } catch {
- /* expected: bridge may already have exited */
- }
+ await cleanup();
throw error;
}
}
@@ -179,6 +232,18 @@ export async function launchChromium(): Promise {
}
}
+/** Close a browser and await any Node bridge subprocess cleanup. */
+export async function closeChromium(browser: Browser | null): Promise {
+ if (!browser) return;
+ const cleanup = browserBridgeCleanups.get(browser);
+ try {
+ await browser.close();
+ } finally {
+ await cleanup?.();
+ browserBridgeCleanups.delete(browser);
+ }
+}
+
export function captureBrowserDiagnostics(page: Page): BrowserDiagnostics {
const consoleMessages: string[] = [];
const pageErrors: string[] = [];
diff --git a/tests/e2e/regressions/2026-07-27-legacy-router-hydration.test.ts b/tests/e2e/regressions/2026-07-27-legacy-router-hydration.test.ts
new file mode 100644
index 0000000000..d118d48a4a
--- /dev/null
+++ b/tests/e2e/regressions/2026-07-27-legacy-router-hydration.test.ts
@@ -0,0 +1,221 @@
+#!/usr/bin/env -S deno test --allow-all
+/**
+ * Regression Test: Shared hydration runtime breaks legacy router assets
+ *
+ * Bug: A current shared hydration runtime statically imported
+ * `getNavigationStore` from a release-pinned `veryfront/router` asset.
+ * Existing releases without that export failed during browser module
+ * linking before hydration could run.
+ * Fixed: 2026-07-27
+ * Related: https://github.com/veryfront/veryfront-issue-inbox/issues/264
+ *
+ * Root Cause:
+ * The shared hydration runtime and project release assets are versioned
+ * independently, but the hydration runtime required a new named export.
+ *
+ * Reproduction:
+ * Serve the latest generated hydration module with a legacy router module
+ * that exports RouterProvider and useRouter, but not getNavigationStore.
+ *
+ * Fix:
+ * Resolve getNavigationStore through the router module namespace and register
+ * the SPA navigator only when that optional export exists.
+ */
+
+import { assertEquals } from "#veryfront/testing/assert.ts";
+import { describe, it } from "#veryfront/testing/bdd.ts";
+import {
+ captureBrowserDiagnostics,
+ closeChromium,
+ getBrowserDiagnosticMessages,
+ launchChromium,
+} from "../../_helpers/playwright.ts";
+import { generateProdHydrationModule } from "../../../src/html/hydration-script-builder/prod-scripts.ts";
+
+const HTML = `
+
+
+
+
+
+ Existing release content
+
+
+
+`;
+
+const REACT_MODULE = `
+export function createElement(type, props, ...children) {
+ return { type, props: props || {}, children };
+}
+`;
+
+const REACT_DOM_CLIENT_MODULE = `
+function markHydrated() {
+ document.documentElement.dataset.hydrated = "yes";
+ return { render: markHydrated };
+}
+export function createRoot() {
+ return { render: markHydrated };
+}
+export function hydrateRoot() {
+ markHydrated();
+ return { render: markHydrated };
+}
+`;
+
+const LEGACY_ROUTER_MODULE = `
+const navigationStoreKey = Symbol.for("veryfront.navigation.store.v1");
+const navigationState = { navigator: null };
+const listeners = new Set();
+const navigationStore = {
+ subscribe(listener) {
+ listeners.add(listener);
+ return () => listeners.delete(listener);
+ },
+ getHref() {
+ return location.pathname + location.search + location.hash;
+ },
+ notify() {
+ for (const listener of listeners) listener();
+ },
+ navigate(href, options) {
+ if (navigationState.navigator) return navigationState.navigator(href, options);
+ location.assign(href);
+ return Promise.resolve();
+ },
+ setNavigator(navigator) {
+ navigationState.navigator = navigator;
+ }
+};
+globalThis[navigationStoreKey] = navigationStore;
+globalThis.__legacyRouterNavigationState = navigationState;
+
+export function RouterProvider({ children }) {
+ return children;
+}
+export function useRouter() {
+ return {};
+}
+`;
+
+const PAGE_CONTEXT_MODULE = `
+export function PageContextProvider({ children }) {
+ return children;
+}
+`;
+
+const PAGE_MODULE = `
+export default function ExistingReleasePage() {
+ return null;
+}
+`;
+
+function javascript(source: string): Response {
+ return new Response(source, {
+ headers: { "content-type": "application/javascript; charset=utf-8" },
+ });
+}
+
+async function waitForHydration(
+ page: import("npm:playwright@1.60.0").Page,
+ diagnostics: ReturnType,
+): Promise {
+ try {
+ await page.waitForFunction(
+ () => document.documentElement.dataset.hydrated === "yes",
+ undefined,
+ { timeout: 5_000 },
+ );
+ } catch (error) {
+ const missingExport = getBrowserDiagnosticMessages(diagnostics).find((message) =>
+ message.includes("does not provide an export named 'getNavigationStore'")
+ );
+ if (missingExport) {
+ throw new Error(`Hydration module linking failed: ${missingExport}`, { cause: error });
+ }
+ throw error;
+ }
+}
+
+describe(
+ "Regression: shared hydration runtime supports legacy router release assets",
+ () => {
+ it("hydrates without requiring getNavigationStore", async () => {
+ const hydrationModule = generateProdHydrationModule();
+ const server = Deno.serve({
+ hostname: "127.0.0.1",
+ port: 0,
+ onListen() {},
+ }, (request) => {
+ const pathname = new URL(request.url).pathname;
+
+ if (pathname === "/") {
+ return new Response(HTML, {
+ headers: { "content-type": "text/html; charset=utf-8" },
+ });
+ }
+ if (pathname === "/hydration-runtime.js") return javascript(hydrationModule);
+ if (pathname === "/react.js") return javascript(REACT_MODULE);
+ if (pathname === "/react-dom-client.js") return javascript(REACT_DOM_CLIENT_MODULE);
+ if (pathname === "/legacy-router.js") return javascript(LEGACY_ROUTER_MODULE);
+ if (pathname === "/context.js") return javascript(PAGE_CONTEXT_MODULE);
+ if (pathname.startsWith("/_vf_modules/")) return javascript(PAGE_MODULE);
+
+ return new Response("Not found", { status: 404 });
+ });
+ const browser = await launchChromium();
+
+ try {
+ if (!browser) return;
+
+ const page = await browser.newPage();
+ const diagnostics = captureBrowserDiagnostics(page);
+ const { port } = server.addr as Deno.NetAddr;
+ const response = await page.goto(`http://127.0.0.1:${port}/`);
+
+ assertEquals(response?.status(), 200);
+ await waitForHydration(page, diagnostics);
+
+ const messages = getBrowserDiagnosticMessages(diagnostics);
+ assertEquals(
+ messages.some((message) =>
+ message.includes(
+ "does not provide an export named 'getNavigationStore'",
+ )
+ ),
+ false,
+ messages.join("\n"),
+ );
+ assertEquals(
+ await page.locator("#root").textContent(),
+ "Existing release content",
+ );
+ assertEquals(
+ await page.evaluate(() => {
+ const state = Reflect.get(globalThis, "__legacyRouterNavigationState") as
+ | { navigator?: unknown }
+ | undefined;
+ return typeof state?.navigator === "function";
+ }),
+ true,
+ );
+ } finally {
+ await closeChromium(browser);
+ await server.shutdown();
+ await server.finished;
+ }
+ });
+ },
+);
diff --git a/tests/integration/compiled-binary-e2e.test.ts b/tests/integration/compiled-binary-e2e.test.ts
index 3ce2095570..d6d9c626cc 100644
--- a/tests/integration/compiled-binary-e2e.test.ts
+++ b/tests/integration/compiled-binary-e2e.test.ts
@@ -2679,7 +2679,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
});
});
- // Test: Layout at components/layouts/ path via config (mimics codersociety production setup)
+ // Test: Layout at components/layouts/ path via config (mirrors a production setup)
// Regression test: layout at components/layouts/DefaultLayout.tsx was not found due to
// path normalization double-stripping in getEntityInfo (components/ prefix matched, then
// layouts/ prefix matched again, corrupting the path).
@@ -2925,7 +2925,7 @@ export default function Home() {
});
// Test: MDX layout at components/layouts/ path via config
- // Tests the exact codersociety pattern: config layout using .mdx file in components/layouts/
+ // Tests the production pattern: config layout using .mdx file in components/layouts/
it("should render MDX layout from components/layouts/ via config", async () => {
const projectDir = await Deno.makeTempDir({ prefix: "vf-e2e-mdx-components-layout-test-" });