diff --git a/apps/webapp/app/components/navigation/DashboardList.tsx b/apps/webapp/app/components/navigation/DashboardList.tsx
index 25de0b4734a..5704334fd67 100644
--- a/apps/webapp/app/components/navigation/DashboardList.tsx
+++ b/apps/webapp/app/components/navigation/DashboardList.tsx
@@ -180,6 +180,7 @@ function DashboardChildMenuItem({
activeIconColor={activeIconColor}
inactiveIconColor={inactiveIconColor}
to={item.path}
+ matchPrefix={true}
isCollapsed={isCollapsed}
disableIconHover
yieldActiveToFavorite
diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx
index a5bfe8d6bf9..662cf0e9ca2 100644
--- a/apps/webapp/app/components/navigation/SideMenu.tsx
+++ b/apps/webapp/app/components/navigation/SideMenu.tsx
@@ -945,6 +945,10 @@ export function SideMenu({
data-action="tasks"
isCollapsed={isCollapsed}
yieldActiveToFavorite
+ nestedPaths={[
+ `${v3EnvironmentPath(organization, project, environment)}/tasks`,
+ `${v3EnvironmentPath(organization, project, environment)}/agents`,
+ ]}
/>
{(isManagedCloud ||
user.admin ||
@@ -980,6 +986,7 @@ export function SideMenu({
badge={}
isCollapsed={isCollapsed}
yieldActiveToFavorite
+ matchPrefix
/>
)}
@@ -1141,7 +1148,8 @@ function CustomizableSideMenuSection({
activeIconColor={item.activeIconColor}
inactiveIconColor={item.inactiveIconColor ?? "text-text-dimmed"}
to={item.to}
- matchPrefix={item.matchPrefix}
+ matchPrefix={item.matchPrefix ?? (item.id !== "dashboards")}
+ nestedPaths={item.nestedPaths}
data-action={item.dataAction}
badge={item.badge}
isCollapsed={isCollapsed}
diff --git a/apps/webapp/app/components/navigation/SideMenuItem.tsx b/apps/webapp/app/components/navigation/SideMenuItem.tsx
index 6b959783c01..bf70fcf85a9 100644
--- a/apps/webapp/app/components/navigation/SideMenuItem.tsx
+++ b/apps/webapp/app/components/navigation/SideMenuItem.tsx
@@ -61,6 +61,93 @@ export function SideMenuLabel({
);
}
+/**
+ * Normalizes a URL path by stripping any hash, query parameters, and trailing slashes.
+ */
+export function normalizePath(path: string): string {
+ const [clean = ""] = path.split("#")[0].split("?");
+ if (clean.length > 1 && clean.endsWith("/")) {
+ return clean.slice(0, -1);
+ }
+ return clean;
+}
+
+/**
+ * Checks whether currentPath matches targetPath, optionally checking nested paths
+ * while respecting segment boundaries.
+ */
+export function matchesPath(currentPath: string, targetPath: string, matchPrefix = false): boolean {
+ const normalizedCurrent = normalizePath(currentPath);
+ const normalizedTarget = normalizePath(targetPath);
+
+ if (normalizedCurrent === normalizedTarget) {
+ return true;
+ }
+
+ if (matchPrefix) {
+ return normalizedCurrent.startsWith(`${normalizedTarget}/`);
+ }
+
+ return false;
+}
+
+export type IsSideMenuItemActiveOptions = {
+ currentPath: string;
+ targetPath: string;
+ matchPrefix?: boolean;
+ nestedPaths?: string[];
+ isActiveOverride?: boolean;
+ yieldActiveToFavorite?: boolean;
+ activeFavoriteId?: string;
+};
+
+/**
+ * Determines whether a sidebar navigation item should be marked active.
+ *
+ * Respects:
+ * - Direct isActive overrides (e.g. from favorites).
+ * - Yielding active highlight to a matching active favorite.
+ * - Exact path matching.
+ * - Segment-boundary-aware prefix matching (e.g. /concurrency does NOT match /concurrency-limits).
+ * - Nested paths for parent items whose subpages live under distinct paths (e.g. Tasks).
+ */
+export function isSideMenuItemActive({
+ currentPath,
+ targetPath,
+ matchPrefix = false,
+ nestedPaths,
+ isActiveOverride,
+ yieldActiveToFavorite = false,
+ activeFavoriteId,
+}: IsSideMenuItemActiveOptions): boolean {
+ if (isActiveOverride !== undefined) {
+ return isActiveOverride;
+ }
+
+ if (yieldActiveToFavorite && activeFavoriteId !== undefined) {
+ return false;
+ }
+
+ if (matchesPath(currentPath, targetPath, matchPrefix)) {
+ return true;
+ }
+
+ if (nestedPaths && nestedPaths.length > 0) {
+ const normalizedTarget = normalizePath(targetPath);
+ for (const nested of nestedPaths) {
+ const resolved =
+ nested.startsWith("/") && !nested.startsWith(normalizedTarget)
+ ? `${normalizedTarget}${nested}`
+ : nested;
+ if (matchesPath(currentPath, resolved, true)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
export function SideMenuItem({
icon,
activeIconColor,
@@ -80,6 +167,7 @@ export function SideMenuItem({
isActive: isActiveOverride,
yieldActiveToFavorite = false,
matchPrefix = false,
+ nestedPaths,
"data-action": dataAction,
}: {
icon?: RenderIcon;
@@ -101,6 +189,8 @@ export function SideMenuItem({
/** Overrides the default pathname === to active check (e.g. favorites match on full URL). */
isActive?: boolean;
matchPrefix?: boolean;
+ /** Additional subpaths (e.g. /tasks, /agents) that should activate this parent item. */
+ nestedPaths?: string[];
/**
* In menus that render the Favorites section (the main project menu), an active favorite owns
* the highlight, so the plain item yields its active state to it. Menus without a favorites
@@ -114,10 +204,15 @@ export function SideMenuItem({
// Only the user's OWN favorites own a view (via the marker param); markers from shared links
// don't count (see useActiveFavoriteId).
const activeFavoriteId = useActiveFavoriteId();
- const isActive =
- isActiveOverride ??
- ((pathName === to || (matchPrefix && pathName.startsWith(`${to}/`))) &&
- (!yieldActiveToFavorite || activeFavoriteId === undefined));
+ const isActive = isSideMenuItemActive({
+ currentPath: pathName,
+ targetPath: to,
+ matchPrefix,
+ nestedPaths,
+ isActiveOverride,
+ yieldActiveToFavorite,
+ activeFavoriteId,
+ });
const isIndented = indented && !isCollapsed;
diff --git a/apps/webapp/app/components/navigation/sideMenuSections.tsx b/apps/webapp/app/components/navigation/sideMenuSections.tsx
index 0d4a2562289..d01b8a8138d 100644
--- a/apps/webapp/app/components/navigation/sideMenuSections.tsx
+++ b/apps/webapp/app/components/navigation/sideMenuSections.tsx
@@ -59,6 +59,7 @@ type SideMenuItemConfig = {
inactiveIconColor?: string;
to: string;
matchPrefix?: boolean;
+ nestedPaths?: string[];
dataAction?: string;
badge?: ReactNode;
trailingIconClassName?: string;
diff --git a/apps/webapp/test/sideMenuNavigation.test.ts b/apps/webapp/test/sideMenuNavigation.test.ts
new file mode 100644
index 00000000000..9b7d52a38f3
--- /dev/null
+++ b/apps/webapp/test/sideMenuNavigation.test.ts
@@ -0,0 +1,639 @@
+import { describe, expect, it } from "vitest";
+import {
+ isSideMenuItemActive,
+ matchesPath,
+ normalizePath,
+} from "~/components/navigation/SideMenuItem";
+
+describe("Sidebar Navigation Route Matching", () => {
+ const envPath = "/orgs/acme/projects/rocket/env/dev";
+ const orgPath = "/orgs/acme";
+
+ describe("normalizePath", () => {
+ it("strips trailing slashes, search params, and hashes", () => {
+ expect(normalizePath(`${envPath}/`)).toBe(envPath);
+ expect(normalizePath(`${envPath}?fav=123`)).toBe(envPath);
+ expect(normalizePath(`${envPath}#section`)).toBe(envPath);
+ expect(normalizePath(`${envPath}/runs/?filter=failed#top`)).toBe(`${envPath}/runs`);
+ expect(normalizePath("/")).toBe("/");
+ });
+ });
+
+ describe("matchesPath", () => {
+ it("matches exact paths", () => {
+ expect(matchesPath(`${envPath}/runs`, `${envPath}/runs`)).toBe(true);
+ expect(matchesPath(`${envPath}/runs/`, `${envPath}/runs`)).toBe(true);
+ expect(matchesPath(`${envPath}/runs`, `${envPath}/runs/`)).toBe(true);
+ expect(matchesPath(`${envPath}/runs`, `${envPath}/sessions`)).toBe(false);
+ });
+
+ it("matches subpaths only when matchPrefix is true", () => {
+ expect(matchesPath(`${envPath}/runs/run_123`, `${envPath}/runs`, false)).toBe(false);
+ expect(matchesPath(`${envPath}/runs/run_123`, `${envPath}/runs`, true)).toBe(true);
+ });
+
+ it("respects segment boundaries when matchPrefix is true", () => {
+ // /concurrency must not match /concurrency-limits
+ expect(matchesPath(`${envPath}/concurrency-limits`, `${envPath}/concurrency`, true)).toBe(false);
+ expect(matchesPath(`${envPath}/concurrency/queue_1`, `${envPath}/concurrency`, true)).toBe(true);
+
+ // /billing must not match /billing-limits
+ expect(matchesPath(`${orgPath}/settings/billing-limits`, `${orgPath}/settings/billing`, true)).toBe(false);
+ expect(matchesPath(`${orgPath}/settings/billing/invoices`, `${orgPath}/settings/billing`, true)).toBe(true);
+ });
+ });
+
+ describe("1. Main section routes", () => {
+ it("highlights the exact section route when visited", () => {
+ // Tasks main route
+ expect(
+ isSideMenuItemActive({
+ currentPath: envPath,
+ targetPath: envPath,
+ nestedPaths: [`${envPath}/tasks`, `${envPath}/agents`],
+ })
+ ).toBe(true);
+
+ // Runs main route
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs`,
+ targetPath: `${envPath}/runs`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Sessions main route
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/sessions`,
+ targetPath: `${envPath}/sessions`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Concurrency main route
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/concurrency`,
+ targetPath: `${envPath}/concurrency`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Deployments main route
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/deployments`,
+ targetPath: `${envPath}/deployments`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+ });
+ });
+
+ describe("2. Nested detail routes", () => {
+ it("keeps parent navigation items highlighted on detail routes", () => {
+ // Sessions detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/sessions/session_123`,
+ targetPath: `${envPath}/sessions`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Webhooks endpoint detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/webhooks/endpoints/ep_abc`,
+ targetPath: `${envPath}/webhooks`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Webhooks delivery detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/webhooks/deliveries/del_xyz`,
+ targetPath: `${envPath}/webhooks`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Prompts detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/prompts/my-prompt`,
+ targetPath: `${envPath}/prompts`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Models detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/models/gpt-4o`,
+ targetPath: `${envPath}/models`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Models compare
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/models/compare`,
+ targetPath: `${envPath}/models`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Alerts detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/alerts/alert_123`,
+ targetPath: `${envPath}/alerts`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Alerts new
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/alerts/new`,
+ targetPath: `${envPath}/alerts`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Errors detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/errors/err_fingerprint_abc`,
+ targetPath: `${envPath}/errors`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Deployments detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/deployments/dep_456`,
+ targetPath: `${envPath}/deployments`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Environment variables new
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/environment-variables/new`,
+ targetPath: `${envPath}/environment-variables`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Waitpoint tokens detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/waitpoints/tokens/wpt_789`,
+ targetPath: `${envPath}/waitpoints/tokens`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Batches detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/batches/batch_123`,
+ targetPath: `${envPath}/batches`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+
+ // Bulk actions detail
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/bulk-actions/action_999`,
+ targetPath: `${envPath}/bulk-actions`,
+ matchPrefix: true,
+ })
+ ).toBe(true);
+ });
+ });
+
+ describe("3. Tasks detail pages", () => {
+ const tasksItemConfig = {
+ targetPath: envPath,
+ nestedPaths: [`${envPath}/tasks`, `${envPath}/agents`],
+ yieldActiveToFavorite: true,
+ };
+
+ it("highlights Tasks on standard task detail page", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/standard/process-order`,
+ ...tasksItemConfig,
+ })
+ ).toBe(true);
+ });
+
+ it("highlights Tasks on scheduled task detail page", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/scheduled/nightly-cleanup`,
+ ...tasksItemConfig,
+ })
+ ).toBe(true);
+ });
+
+ it("highlights Tasks on tasks dashboard and stream pages", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/dashboard`,
+ ...tasksItemConfig,
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/stream`,
+ ...tasksItemConfig,
+ })
+ ).toBe(true);
+ });
+
+ it("highlights Tasks on agent task detail page", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/agents/support-bot`,
+ ...tasksItemConfig,
+ })
+ ).toBe(true);
+ });
+
+ it("does NOT highlight Tasks on other environment sections", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs`,
+ ...tasksItemConfig,
+ })
+ ).toBe(false);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/sessions`,
+ ...tasksItemConfig,
+ })
+ ).toBe(false);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/alerts`,
+ ...tasksItemConfig,
+ })
+ ).toBe(false);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/deployments`,
+ ...tasksItemConfig,
+ })
+ ).toBe(false);
+ });
+
+ it("supports relative nestedPaths for Tasks", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/standard/my-task`,
+ targetPath: envPath,
+ nestedPaths: ["/tasks", "/agents"],
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/agents/agent-task`,
+ targetPath: envPath,
+ nestedPaths: ["/tasks", "/agents"],
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs`,
+ targetPath: envPath,
+ nestedPaths: ["/tasks", "/agents"],
+ })
+ ).toBe(false);
+ });
+ });
+
+ describe("4. Runs detail pages", () => {
+ const runsItemConfig = {
+ targetPath: `${envPath}/runs`,
+ matchPrefix: true,
+ yieldActiveToFavorite: true,
+ };
+
+ it("highlights Runs on individual run detail page", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs/run_cm123456789`,
+ ...runsItemConfig,
+ })
+ ).toBe(true);
+ });
+
+ it("highlights Runs on nested run streaming and span subpages", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs/run_cm123456789/stream`,
+ ...runsItemConfig,
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs/run_cm123456789/spans/span_001`,
+ ...runsItemConfig,
+ })
+ ).toBe(true);
+ });
+
+ it("does NOT highlight Runs on unrelated sibling sections", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/sessions`,
+ ...runsItemConfig,
+ })
+ ).toBe(false);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/standard/test`,
+ ...runsItemConfig,
+ })
+ ).toBe(false);
+ });
+ });
+
+ describe("5. Similar but unrelated route prefixes (Path Boundary Respect)", () => {
+ it("/concurrency does not activate on /concurrency-limits", () => {
+ const concurrencyItem = {
+ targetPath: `${envPath}/concurrency`,
+ matchPrefix: true,
+ };
+
+ const concurrencyLimitsItem = {
+ targetPath: `${envPath}/concurrency-limits`,
+ matchPrefix: true,
+ };
+
+ // On /concurrency-limits:
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/concurrency-limits`,
+ ...concurrencyItem,
+ })
+ ).toBe(false);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/concurrency-limits`,
+ ...concurrencyLimitsItem,
+ })
+ ).toBe(true);
+
+ // On /concurrency/queue_alpha:
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/concurrency/queue_alpha`,
+ ...concurrencyItem,
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/concurrency/queue_alpha`,
+ ...concurrencyLimitsItem,
+ })
+ ).toBe(false);
+ });
+
+ it("/runs does not activate on /runs-archived", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs-archived`,
+ targetPath: `${envPath}/runs`,
+ matchPrefix: true,
+ })
+ ).toBe(false);
+ });
+
+ it("/billing does not activate on /billing-limits", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${orgPath}/settings/billing-limits`,
+ targetPath: `${orgPath}/settings/billing`,
+ matchPrefix: true,
+ })
+ ).toBe(false);
+ });
+ });
+
+ describe("6. Dashboard-specific routes", () => {
+ const dashboardsLandingItem = {
+ targetPath: `${envPath}/dashboards`,
+ matchPrefix: false, // Dashboards landing page only
+ yieldActiveToFavorite: true,
+ };
+
+ const runMetricsItem = {
+ targetPath: `${envPath}/dashboards/overview`,
+ matchPrefix: true,
+ yieldActiveToFavorite: true,
+ };
+
+ const aiMetricsItem = {
+ targetPath: `${envPath}/dashboards/llm`,
+ matchPrefix: true,
+ yieldActiveToFavorite: true,
+ };
+
+ const customDashboardItem = {
+ targetPath: `${envPath}/dashboards/custom/db_sales`,
+ matchPrefix: true,
+ yieldActiveToFavorite: true,
+ };
+
+ it("highlights parent Dashboards item only on the landing page", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards`,
+ ...dashboardsLandingItem,
+ })
+ ).toBe(true);
+
+ // On landing page, specific dashboards are not active
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards`,
+ ...runMetricsItem,
+ })
+ ).toBe(false);
+ });
+
+ it("retains active state on specific dashboard items without activating the landing parent", () => {
+ // Visiting Run metrics:
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/overview`,
+ ...runMetricsItem,
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/overview`,
+ ...dashboardsLandingItem,
+ })
+ ).toBe(false);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/overview`,
+ ...aiMetricsItem,
+ })
+ ).toBe(false);
+
+ // Visiting AI metrics:
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/llm`,
+ ...aiMetricsItem,
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/llm`,
+ ...dashboardsLandingItem,
+ })
+ ).toBe(false);
+
+ // Visiting Custom dashboard:
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/custom/db_sales`,
+ ...customDashboardItem,
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/custom/db_sales`,
+ ...dashboardsLandingItem,
+ })
+ ).toBe(false);
+ });
+
+ it("allows custom dashboard item to retain active state on nested widgets/subroutes", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/custom/db_sales/widgets/w_revenue`,
+ ...customDashboardItem,
+ })
+ ).toBe(true);
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/dashboards/custom/db_sales/widgets/w_revenue`,
+ ...dashboardsLandingItem,
+ })
+ ).toBe(false);
+ });
+ });
+
+ describe("7. Favourite navigation behaviour", () => {
+ it("yields active state to active favorite when yieldActiveToFavorite is true", () => {
+ const runsItem = {
+ targetPath: `${envPath}/runs`,
+ matchPrefix: true,
+ yieldActiveToFavorite: true,
+ };
+
+ // Current view matches an active favorite
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs/run_123`,
+ ...runsItem,
+ activeFavoriteId: "fav_run_123",
+ })
+ ).toBe(false);
+
+ // The favorite item itself uses isActiveOverride: true
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs/run_123`,
+ targetPath: `${envPath}/runs/run_123?fav=fav_run_123`,
+ isActiveOverride: true,
+ })
+ ).toBe(true);
+ });
+
+ it("keeps regular highlight when no favorite owns the view", () => {
+ const runsItem = {
+ targetPath: `${envPath}/runs`,
+ matchPrefix: true,
+ yieldActiveToFavorite: true,
+ };
+
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/runs/run_123`,
+ ...runsItem,
+ activeFavoriteId: undefined,
+ })
+ ).toBe(true);
+ });
+
+ it("yields active state on Tasks detail pages when a favorite is active", () => {
+ const tasksItem = {
+ targetPath: envPath,
+ nestedPaths: [`${envPath}/tasks`, `${envPath}/agents`],
+ yieldActiveToFavorite: true,
+ };
+
+ // Active favorite on task detail page
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/standard/my-task`,
+ ...tasksItem,
+ activeFavoriteId: "fav_task_my_task",
+ })
+ ).toBe(false);
+
+ // Normal navigation without favorite marker
+ expect(
+ isSideMenuItemActive({
+ currentPath: `${envPath}/tasks/standard/my-task`,
+ ...tasksItem,
+ activeFavoriteId: undefined,
+ })
+ ).toBe(true);
+ });
+
+ it("does not yield when yieldActiveToFavorite is false", () => {
+ expect(
+ isSideMenuItemActive({
+ currentPath: "/account",
+ targetPath: "/account",
+ yieldActiveToFavorite: false,
+ activeFavoriteId: "fav_something",
+ })
+ ).toBe(true);
+ });
+ });
+});
+