Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions web/app/[locale]/dashboard/coderouter/loading.tsx

This file was deleted.

4 changes: 4 additions & 0 deletions web/app/[locale]/dashboard/coderouter/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import {
DeleteAiAccountButton,
} from "../components/ai-account-forms";

// Account authorization and the hosted account list must stay fresh for each
// request. Keep the current tab visible while this page resolves instead of
// caching mutable per-user data just to satisfy instant-navigation validation.
export const instant = false;

type PageProps = {
params: Promise<{ locale: string }>;
Expand Down
6 changes: 5 additions & 1 deletion web/app/[locale]/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ export default async function DashboardLayout({
<StackProvider app={getStackServerApp()}>
<StackTheme>
<DashboardQueryProvider>
{/* Keep the current tab mounted while fresh page data resolves.
A nested full-page Suspense fallback makes rapid tab switches
flash the dashboard skeleton. The dashboard loading boundary
above still covers first entry into the dashboard. */}
<DashboardShell vaultEnabled={isVaultEnabled()}>
<Suspense fallback={<DashboardSkeleton />}>{children}</Suspense>
{children}
</DashboardShell>
</DashboardQueryProvider>
</StackTheme>
Expand Down
5 changes: 0 additions & 5 deletions web/app/[locale]/dashboard/testflight/loading.tsx

This file was deleted.

3 changes: 3 additions & 0 deletions web/app/[locale]/dashboard/testflight/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { testerGroupStatus } from "@/services/asc/testflight";
import { isTestflightEligible } from "@/services/billing/pro";
import { captureAscError } from "@/services/errors";

// Eligibility and App Store status are request-fresh values. Keep the current
// tab visible while this page resolves instead of caching mutable user data.
export const instant = false;

type SearchParams = {
testflight?: string | string[];
Expand Down
96 changes: 51 additions & 45 deletions web/bun.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"freestyle": "0.1.51",
"freestyle-beta": "npm:freestyle@0.2.0-beta.7",
"fzstd": "^0.1.1",
"next": "16.3.0",
"next": "16.3.4",
"next-intl": "^4.11.2",
"next-themes": "^0.4.6",
"pg": "^8.20.0",
Expand All @@ -89,7 +89,7 @@
"zod": "^4.3.6"
},
"devDependencies": {
"@next/playwright": "16.3.0",
"@next/playwright": "16.3.4",
"@opentelemetry/context-async-hooks": "^2.7.1",
"@playwright/test": "1.62.1",
"@tailwindcss/postcss": "^4",
Expand All @@ -102,7 +102,7 @@
"@typescript/native-preview": "7.0.0-dev.20260616.1",
"drizzle-kit": "1.0.0-beta.23",
"eslint": "^9.39.4",
"eslint-config-next": "16.3.0",
"eslint-config-next": "16.3.4",
"pagefind": "^1.5.2",
"sharp": "0.34.5",
"tailwindcss": "^4",
Expand Down
35 changes: 32 additions & 3 deletions web/tests/dashboard-layout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { renderToStaticMarkup } from "react-dom/server";
import type React from "react";

const pendingProvider = new Promise<never>(() => {});
let stackProviderPending = true;
let dashboardChildren: React.ReactNode;

mock.module("@stackframe/stack", () => ({
StackProvider: ({ children }: React.PropsWithChildren) => {
void children;
throw pendingProvider;
if (stackProviderPending) {
void children;
throw pendingProvider;
}
return children;
},
StackTheme: ({ children }: React.PropsWithChildren) => children,
}));
Expand All @@ -34,14 +39,18 @@ mock.module(
);

mock.module("../app/[locale]/dashboard/dashboard-shell", () => ({
DashboardShell: ({ children }: React.PropsWithChildren) => children,
DashboardShell: ({ children }: React.PropsWithChildren) => {
dashboardChildren = children;
return children;
},
}));

const { default: DashboardLayout } = await import(
"../app/[locale]/dashboard/layout"
);

test("keeps Stack provider suspension inside the dashboard fallback", async () => {
stackProviderPending = true;
const html = renderToStaticMarkup(
await DashboardLayout({
children: <main>Dashboard content</main>,
Expand All @@ -52,3 +61,23 @@ test("keeps Stack provider suspension inside the dashboard fallback", async () =
expect(html).toContain('data-testid="dashboard-suspense-fallback"');
expect(html).not.toContain("Dashboard content");
});

test("passes page content directly to the shared dashboard shell", async () => {
stackProviderPending = false;
const content = <main>Dashboard content</main>;

try {
const html = renderToStaticMarkup(
await DashboardLayout({
children: content,
params: Promise.resolve({ locale: "en" }),
}),
);

expect(dashboardChildren).toBe(content);
expect(html).toContain("Dashboard content");
} finally {
stackProviderPending = true;
dashboardChildren = undefined;
}
});
Loading