diff --git a/src/app/page.tsx b/src/app/page.tsx
index 57b2822f..ebe274ac 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -28,6 +28,8 @@ import {
ZamaIcon,
} from "@/components/icons";
import { DefenderIcon } from "@/components/icons/defender-icon";
+import { latestStable as monitorLatestStable } from "../../content/monitor/latest-versions";
+import { latestStable as relayerLatestStable } from "../../content/relayer/latest-versions";
import { baseOptions } from "./layout.config";
export default function HomePage() {
@@ -137,7 +139,7 @@ export default function HomePage() {
{/* Dual Heroes: Monitor and Relayer */}
}
title="Relayer"
description="Automate onchain transactions to schedule jobs, batch calls, and relay gasless meta transactions within your self-hosted infrastructure."
@@ -145,7 +147,7 @@ export default function HomePage() {
/>
}
title="Monitor"
description="Monitor onchain activity in real time to watch critical events, detect anomalies, trigger alerts on your preferred channels, and set automated responses with Relayer."
diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx
index 1f95d544..550a5276 100644
--- a/src/components/sidebar.tsx
+++ b/src/components/sidebar.tsx
@@ -236,7 +236,7 @@ export function SidebarFooter(props: ComponentProps<"div">) {
export function SidebarViewport(props: ScrollAreaProps) {
return (
-
+
(({ className, children, ...props }, ref) => (
@@ -43,14 +43,16 @@ const ScrollBar = React.forwardRef<
ref={ref}
orientation={orientation}
className={cn(
- "flex select-none data-[state=hidden]:animate-fd-fade-out",
- orientation === "vertical" && "h-full w-1.5",
- orientation === "horizontal" && "h-1.5 flex-col",
+ "flex select-none touch-none p-0.5 transition-colors",
+ orientation === "vertical" &&
+ "h-full w-2.5 border-l border-l-transparent",
+ orientation === "horizontal" &&
+ "h-2.5 flex-col border-t border-t-transparent",
className,
)}
{...props}
>
-
+
));
ScrollBar.displayName = ScrollAreaPrimitive.Scrollbar.displayName;
diff --git a/src/lib/is-active.ts b/src/lib/is-active.ts
index de94c090..78164fc3 100644
--- a/src/lib/is-active.ts
+++ b/src/lib/is-active.ts
@@ -5,6 +5,59 @@ function normalize(url: string) {
return url;
}
+// Products that have versioned paths like /product/X.Y.x/...
+const VERSIONED_PRODUCTS = ["/relayer", "/monitor"];
+
+// Version pattern: matches patterns like "1.3.x", "1.0.x", "2.1.x"
+const VERSION_PATTERN = /^(\d+\.\d+\.x)$/;
+
+interface ParsedPath {
+ base: string;
+ version: string | null; // null means development/unversioned
+ subpath: string;
+}
+
+/**
+ * Parses a path for versioned products.
+ * For "/relayer/1.3.x/quickstart" returns { base: "/relayer", version: "1.3.x", subpath: "/quickstart" }
+ * For "/relayer/quickstart" (development) returns { base: "/relayer", version: null, subpath: "/quickstart" }
+ * For "/relayer" (development root) returns { base: "/relayer", version: null, subpath: "" }
+ * For non-product paths, returns null.
+ */
+function parseProductPath(path: string): ParsedPath | null {
+ const normalizedPath = normalize(path);
+
+ for (const product of VERSIONED_PRODUCTS) {
+ if (normalizedPath === product) {
+ // Exact match to product root (development version)
+ return { base: product, version: null, subpath: "" };
+ }
+
+ if (normalizedPath.startsWith(`${product}/`)) {
+ const remainder = normalizedPath.slice(product.length + 1); // +1 for the slash
+ const segments = remainder.split("/");
+ const firstSegment = segments[0];
+
+ if (firstSegment && VERSION_PATTERN.test(firstSegment)) {
+ // Versioned path: /relayer/1.3.x or /relayer/1.3.x/quickstart
+ return {
+ base: product,
+ version: firstSegment,
+ subpath: segments.length > 1 ? `/${segments.slice(1).join("/")}` : "",
+ };
+ }
+ // Development path with subpath: /relayer/quickstart
+ return {
+ base: product,
+ version: null,
+ subpath: `/${remainder}`,
+ };
+ }
+ }
+
+ return null;
+}
+
export function isActive(
url: string,
pathname: string,
@@ -13,7 +66,39 @@ export function isActive(
url = normalize(url);
pathname = normalize(pathname);
- return url === pathname || (nested && pathname.startsWith(`${url}/`));
+ // Standard exact match or nested match
+ if (url === pathname || (nested && pathname.startsWith(`${url}/`))) {
+ return true;
+ }
+
+ // Check for versioned/development path matching
+ const urlParsed = parseProductPath(url);
+ const pathnameParsed = parseProductPath(pathname);
+
+ if (urlParsed && pathnameParsed) {
+ // Both are paths for the same product
+ if (urlParsed.base === pathnameParsed.base) {
+ // Same subpath means it's the same "page" just different version
+ if (urlParsed.subpath === pathnameParsed.subpath) {
+ return true;
+ }
+
+ // Check if pathname is nested under url's subpath
+ if (
+ nested &&
+ pathnameParsed.subpath.startsWith(`${urlParsed.subpath}/`)
+ ) {
+ return true;
+ }
+
+ // Handle root match: /relayer/1.3.x should match any /relayer/* when nested
+ if (nested && urlParsed.subpath === "") {
+ return true;
+ }
+ }
+ }
+
+ return false;
}
export function isTabActive(tab: SidebarTab, pathname: string) {