diff --git a/apps/web/components/layout/AppShell.tsx b/apps/web/components/layout/AppShell.tsx
index 3615aae08..4984eb0d6 100644
--- a/apps/web/components/layout/AppShell.tsx
+++ b/apps/web/components/layout/AppShell.tsx
@@ -80,7 +80,12 @@ export function AppShell({ children, noSidebarPaths = [] }: AppShellProps) {
|| pathMatchesPrefixes(pathname, standalonePrefixes)
|| pathMatchesPrefixes(pathname, noSidebarPaths);
const noDock = pathMatchesPrefixes(pathname, noDockPrefixes);
- const fullBleed = pathMatchesPrefixes(pathname, fullBleedCollectionPaths);
+ // `/runs` is a collection that owns a full-height split layout, while
+ // `/runs/[id]` is a standard detail page. Prefix matching alone classified
+ // both as full-bleed, stripping the detail page's gutters and scroll pane.
+ const isDedicatedRunDetail = /^\/runs\/[^/]+\/?$/.test(pathname);
+ const fullBleed = !isDedicatedRunDetail
+ && pathMatchesPrefixes(pathname, fullBleedCollectionPaths);
if (standalone) {
return (
diff --git a/apps/web/tests/app-shell-run-detail-layout.dom.test.tsx b/apps/web/tests/app-shell-run-detail-layout.dom.test.tsx
new file mode 100644
index 000000000..597c5478b
--- /dev/null
+++ b/apps/web/tests/app-shell-run-detail-layout.dom.test.tsx
@@ -0,0 +1,83 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { render } from "@testing-library/react";
+
+const { pathname } = vi.hoisted(() => ({ pathname: vi.fn(() => "/runs/run_123") }));
+
+vi.mock("next/navigation", () => ({
+ usePathname: () => pathname(),
+}));
+
+vi.mock("@/components/layout/sidebar", () => ({
+ Sidebar: () => ,
+ FloomMark: () => null,
+}));
+
+vi.mock("@/components/emily/EmilyChat", () => ({
+ EmilyDock: () => null,
+ EmilyMobileSheet: () => null,
+}));
+
+vi.mock("@/components/CommandPalette", () => ({ CommandPalette: () => null }));
+vi.mock("@/components/Ambient", () => ({ Ambient: () => null }));
+vi.mock("@/components/IconSprite", () => ({ IconSprite: () => null }));
+vi.mock("@/components/layout/DeepLinkRouter", () => ({ DeepLinkRouter: () => null }));
+vi.mock("@/components/layout/BootSplash", () => ({ BootSplash: () => null }));
+vi.mock("@/components/ui/sonner", () => ({ Toaster: () => null }));
+vi.mock("@/components/TermsAcceptanceGate", () => ({ TermsAcceptanceGate: () => null }));
+
+function renderShell() {
+ return import("@/components/layout/AppShell").then(({ AppShell }) => render(
+
+ route content
+ ,
+ ));
+}
+
+describe("AppShell run detail layout", () => {
+ beforeEach(() => {
+ pathname.mockReturnValue("/runs/run_123");
+ vi.stubGlobal(
+ "matchMedia",
+ vi.fn(() => ({
+ matches: true,
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ })),
+ );
+ });
+
+ it("renders /runs/[id] in the padded, vertically scrollable standard pane", async () => {
+ const { container, getByTestId } = await renderShell();
+ const main = container.querySelector("main");
+ const wrapper = getByTestId("route-content").parentElement;
+
+ expect(main).not.toBeNull();
+ expect(main?.className).toContain("overflow-y-auto");
+ expect(main?.className).not.toContain("overflow-hidden");
+ expect(wrapper?.className).toContain("max-w-7xl");
+ expect(wrapper?.className).toContain("px-4");
+ expect(wrapper?.className).toContain("sm:px-6");
+ expect(wrapper?.className).toContain("py-6");
+ expect(wrapper?.className).toContain("sm:py-8");
+ });
+
+ it.each(["/runs", "/runs/"])("keeps the %s collection full-bleed", async (path) => {
+ pathname.mockReturnValue(path);
+ const { container, getByTestId } = await renderShell();
+ const main = container.querySelector("main");
+
+ expect(main?.className).toContain("overflow-hidden");
+ expect(main?.className).not.toContain("overflow-y-auto");
+ expect(getByTestId("route-content").parentElement).toBe(main);
+ });
+
+ it("keeps the standalone /run/[id] share route outside authenticated chrome", async () => {
+ pathname.mockReturnValue("/run/run_123");
+ const { container, getByTestId } = await renderShell();
+ const main = container.querySelector("main");
+
+ expect(main?.className).toContain("overflow-y-auto");
+ expect(getByTestId("route-content").parentElement).toBe(main);
+ expect(container.querySelector("aside")).toBeNull();
+ });
+});