From 363579f70f689bf1c5eafff77b4aa5f353b8507b Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 8 Jun 2026 13:08:26 -0700 Subject: [PATCH 1/4] feat(ui): migrate the Playground page to App Router path routing Add llm-playground -> playground to MIGRATED_PAGES and remove the page's arm from the legacy switch in page.tsx. The playground route already sources all its data from useAuthorized and fetches its own proxy settings, so this is a pure cutover with no prop or state changes. Visiting /ui/?page=llm-playground now redirects to /ui/playground, the sidebar links to and highlights it, and navigating away returns to the legacy switch. Cover the new mapping with a reverse-lookup test so the sidebar highlight is pinned. --- ui/litellm-dashboard/src/app/page.tsx | 3 --- ui/litellm-dashboard/src/utils/migratedPages.test.ts | 2 ++ ui/litellm-dashboard/src/utils/migratedPages.ts | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 12dd39a1c21..996f63966d8 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -2,7 +2,6 @@ import SidebarProvider from "@/app/(dashboard)/components/SidebarProvider"; import OldModelDashboard from "@/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView"; -import PlaygroundPage from "@/app/(dashboard)/playground/page"; import AdminPanel from "@/components/AdminPanel"; import AgentsPanel from "@/components/agents"; import BudgetPanel from "@/components/budgets/budget_panel"; @@ -406,8 +405,6 @@ function CreateKeyPageContent() { premiumUser={premiumUser} teams={teams} /> - ) : page == "llm-playground" ? ( - ) : page == "users" ? ( { expect(MIGRATED_PAGES.api_ref).toBe("api-reference"); expect(MIGRATED_PAGES["api-reference"]).toBe("api-reference"); + expect(MIGRATED_PAGES["llm-playground"]).toBe("playground"); }); }); @@ -49,6 +50,7 @@ describe("legacyKeyForPathname", () => { // Resolves to the sidebar key api_ref, not the hyphenated alias, so highlighting works. expect(legacyKeyForPathname("/ui/api-reference")).toBe("api_ref"); expect(legacyKeyForPathname("/ui/api-reference/")).toBe("api_ref"); + expect(legacyKeyForPathname("/ui/playground")).toBe("llm-playground"); }); it("returns null for a not-yet-migrated path", async () => { diff --git a/ui/litellm-dashboard/src/utils/migratedPages.ts b/ui/litellm-dashboard/src/utils/migratedPages.ts index 2c27e4fee64..e20e8b5a8ea 100644 --- a/ui/litellm-dashboard/src/utils/migratedPages.ts +++ b/ui/litellm-dashboard/src/utils/migratedPages.ts @@ -12,6 +12,7 @@ export const MIGRATED_PAGES: Record = { api_ref: "api-reference", // Legacy alias: older bookmarks used the hyphenated ?page=api-reference form. "api-reference": "api-reference", + "llm-playground": "playground", }; function uiBase(): string { From c6f223cfcd979d049ca9f7a0651629a0e690b4bd Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 8 Jun 2026 14:06:04 -0700 Subject: [PATCH 2/4] test(ui): address review nits on playground migration Add the /ui/playground/ trailing-slash assertion for parity with the api-reference case, and rename the mapping test so its name covers the llm-playground entry it now also asserts. --- ui/litellm-dashboard/src/utils/migratedPages.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/utils/migratedPages.test.ts b/ui/litellm-dashboard/src/utils/migratedPages.test.ts index d614bf7dc6d..432b882b43d 100644 --- a/ui/litellm-dashboard/src/utils/migratedPages.test.ts +++ b/ui/litellm-dashboard/src/utils/migratedPages.test.ts @@ -28,7 +28,7 @@ describe("migratedHref / legacyPageHref", () => { expect(migratedHref("/api-reference")).toBe("/ui/api-reference"); }); - it("maps both the api_ref id and the hyphenated alias to the api-reference route", async () => { + it("maps legacy page ids (and the hyphenated api-reference alias) to their route segments", async () => { vi.doMock("@/components/networking", () => ({ serverRootPath: "/" })); const { MIGRATED_PAGES } = await import("./migratedPages"); @@ -51,6 +51,7 @@ describe("legacyKeyForPathname", () => { expect(legacyKeyForPathname("/ui/api-reference")).toBe("api_ref"); expect(legacyKeyForPathname("/ui/api-reference/")).toBe("api_ref"); expect(legacyKeyForPathname("/ui/playground")).toBe("llm-playground"); + expect(legacyKeyForPathname("/ui/playground/")).toBe("llm-playground"); }); it("returns null for a not-yet-migrated path", async () => { From 4b4c8ba42331a7261434512d1a57fdd8f6471c4a Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 8 Jun 2026 14:43:10 -0700 Subject: [PATCH 3/4] test(ui): assert migrated playground path route in sidebar e2e The playground migration moved LLM Playground from the legacy ?page=llm-playground switch to the /ui/playground/ path route, but sidebar.spec.ts still asserted the query-param URL, so both clicking the sidebar item and the direct-nav helper failed. Drive the expected URL off a small migrated-segment map (mirroring MIGRATED_PAGES) so migrated pages assert their path route while legacy pages keep ?page= --- .../tests/navigation/sidebar.spec.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts index b8fb95b764d..474be30899b 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts @@ -9,6 +9,17 @@ const sidebarButtons = { [Role.ProxyAdmin]: ["Virtual Keys", "Playground", "Models", "Usage", "Teams", "Internal Users", "AI Hub"], }; +// Route segment for pages migrated to path routes; mirror of MIGRATED_PAGES in src/utils/migratedPages.ts. +const migratedPageSegments: Partial> = { + [Page.ApiRef]: "api-reference", + [Page.LlmPlayground]: "playground", +}; + +function expectedUrlPattern(pageKey: Page): RegExp { + const segment = migratedPageSegments[pageKey]; + return segment ? new RegExp(`/ui/${segment}/?($|\\?)`) : new RegExp(`[?&]page=${pageKey}(&|$)`); +} + const roles = [{ role: Role.ProxyAdmin, storage: ADMIN_STORAGE_PATH }]; for (const { role, storage } of roles) { @@ -35,8 +46,7 @@ for (const { role, storage } of roles) { await tab.click(); - // Verify URL contains the correct page query parameter - await expect(page).toHaveURL(new RegExp(`[?&]page=${expectedPage}(&|$)`)); + await expect(page).toHaveURL(expectedUrlPattern(expectedPage)); } }); @@ -50,13 +60,13 @@ for (const { role, storage } of roles) { // Test direct navigation to verify the helper function works await navigateToPage(page, Page.ApiKeys); - await expect(page).toHaveURL(new RegExp(`[?&]page=${Page.ApiKeys}(&|$)`)); + await expect(page).toHaveURL(expectedUrlPattern(Page.ApiKeys)); await navigateToPage(page, Page.Models); - await expect(page).toHaveURL(new RegExp(`[?&]page=${Page.Models}(&|$)`)); + await expect(page).toHaveURL(expectedUrlPattern(Page.Models)); await navigateToPage(page, Page.LlmPlayground); - await expect(page).toHaveURL(new RegExp(`[?&]page=${Page.LlmPlayground}(&|$)`)); + await expect(page).toHaveURL(expectedUrlPattern(Page.LlmPlayground)); }); }); } From 455eba04f727481e1f688bc500a4f78781d0aa34 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Tue, 9 Jun 2026 10:51:03 -0700 Subject: [PATCH 4/4] test(ui): add playground to the App Router migration smoke Append "playground" to MIGRATED_E2E_SEGMENTS so the data-driven smoke covers the migrated Playground page in both mounts, and the migrated->migrated navigation test now runs (api-reference <-> playground) instead of skipping. --- .../e2e_tests/fixtures/migratedPages.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts b/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts index f2ba66147ea..b14c9c36577 100644 --- a/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts +++ b/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts @@ -7,10 +7,10 @@ * - server-root-path mount: SERVER_ROOT_PATH=/ npm run e2e:migration:root * * Keep this in lockstep with MIGRATED_PAGES in src/utils/migratedPages.ts. - * Pending (uncomment as each PR lands): playground, and the leaf-pages batch - * (budgets, caching, cost-tracking, guardrails, guardrails-monitor, logs, - * mcp-servers, memory, policies, projects, prompts, search-tools, skills, - * tag-management, tool-policies, transform-request, ui-theme, vector-stores, - * workflows, access-groups). + * Pending (add as each PR lands): the leaf-pages batch (budgets, caching, + * cost-tracking, guardrails, guardrails-monitor, logs, mcp-servers, memory, + * policies, projects, prompts, search-tools, skills, tag-management, + * tool-policies, transform-request, ui-theme, vector-stores, workflows, + * access-groups). */ -export const MIGRATED_E2E_SEGMENTS: string[] = ["api-reference"]; +export const MIGRATED_E2E_SEGMENTS: string[] = ["api-reference", "playground"];