From 172a7f2204538103798de65d660ce91fab7f2113 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 12 Jan 2026 18:36:43 -0300 Subject: [PATCH 1/8] fix: Fixing navigation --- src/app/page.tsx | 2 +- src/components/sidebar.tsx | 2 +- src/components/ui/scroll-area.tsx | 12 +-- src/lib/is-active.ts | 117 ++++++++++++++++++++++++++---- 4 files changed, 110 insertions(+), 23 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 57b2822f..14e31187 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -137,7 +137,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." 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..ed3ffd17 100644 --- a/src/lib/is-active.ts +++ b/src/lib/is-active.ts @@ -1,28 +1,113 @@ import type { SidebarTab } from "fumadocs-ui/utils/get-sidebar-tabs"; function normalize(url: string) { - if (url.length > 1 && url.endsWith("/")) return url.slice(0, -1); - return url; + if (url.length > 1 && url.endsWith("/")) return url.slice(0, -1); + 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, - nested = true, + url: string, + pathname: string, + nested = true ): boolean { - url = normalize(url); - pathname = normalize(pathname); + url = normalize(url); + pathname = normalize(pathname); + + // 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); - return url === pathname || (nested && pathname.startsWith(`${url}/`)); + 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) { - if (tab.urls) { - const normalizedPathname = normalize(pathname); - return Array.from(tab.urls).some((url) => - isActive(url, normalizedPathname, true), - ); - } - - return isActive(tab.url, pathname, true); + if (tab.urls) { + const normalizedPathname = normalize(pathname); + return Array.from(tab.urls).some((url) => + isActive(url, normalizedPathname, true) + ); + } + + return isActive(tab.url, pathname, true); } From 741f7046d20ebf535e609a3717b8bff1d600a506 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 12 Jan 2026 18:41:15 -0300 Subject: [PATCH 2/8] fix: Fixing identation --- src/lib/is-active.ts | 166 +++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/src/lib/is-active.ts b/src/lib/is-active.ts index ed3ffd17..78164fc3 100644 --- a/src/lib/is-active.ts +++ b/src/lib/is-active.ts @@ -1,8 +1,8 @@ import type { SidebarTab } from "fumadocs-ui/utils/get-sidebar-tabs"; function normalize(url: string) { - if (url.length > 1 && url.endsWith("/")) return url.slice(0, -1); - return url; + if (url.length > 1 && url.endsWith("/")) return url.slice(0, -1); + return url; } // Products that have versioned paths like /product/X.Y.x/... @@ -12,9 +12,9 @@ const VERSIONED_PRODUCTS = ["/relayer", "/monitor"]; const VERSION_PATTERN = /^(\d+\.\d+\.x)$/; interface ParsedPath { - base: string; - version: string | null; // null means development/unversioned - subpath: string; + base: string; + version: string | null; // null means development/unversioned + subpath: string; } /** @@ -25,89 +25,89 @@ interface ParsedPath { * 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; + 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, - nested = true + url: string, + pathname: string, + nested = true, ): boolean { - url = normalize(url); - pathname = normalize(pathname); - - // 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; + url = normalize(url); + pathname = normalize(pathname); + + // 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) { - if (tab.urls) { - const normalizedPathname = normalize(pathname); - return Array.from(tab.urls).some((url) => - isActive(url, normalizedPathname, true) - ); - } - - return isActive(tab.url, pathname, true); + if (tab.urls) { + const normalizedPathname = normalize(pathname); + return Array.from(tab.urls).some((url) => + isActive(url, normalizedPathname, true), + ); + } + + return isActive(tab.url, pathname, true); } From a0465169fe17885b3464a01307f9b49142d894aa Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 12 Jan 2026 18:51:03 -0300 Subject: [PATCH 3/8] fix: Fixing plugin page --- content/relayer/api/callPlugin.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/relayer/api/callPlugin.mdx b/content/relayer/api/callPlugin.mdx index 85483a86..28418da1 100644 --- a/content/relayer/api/callPlugin.mdx +++ b/content/relayer/api/callPlugin.mdx @@ -25,4 +25,4 @@ Logs and traces are only returned when the plugin is configured with `emit_logs` Plugin-provided errors are normalized into a consistent payload (`code`, `details`) and a derived message so downstream clients receive a stable shape regardless of how the handler threw. - \ No newline at end of file + \ No newline at end of file From 6da07eb69ca3577ab1051db580b57eefd08280fb Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 12 Jan 2026 18:58:14 -0300 Subject: [PATCH 4/8] fix: Fixing plugin page --- content/relayer/1.1.x/api/callPlugin.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/relayer/1.1.x/api/callPlugin.mdx b/content/relayer/1.1.x/api/callPlugin.mdx index e80f5752..cb0b2826 100644 --- a/content/relayer/1.1.x/api/callPlugin.mdx +++ b/content/relayer/1.1.x/api/callPlugin.mdx @@ -12,4 +12,4 @@ _openapi: {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} - + From d66dd9142cc87db7266dd368999981a7ea18adc4 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 12 Jan 2026 18:59:24 -0300 Subject: [PATCH 5/8] fix: Fixing plugin page --- content/relayer/api/callPlugin.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/relayer/api/callPlugin.mdx b/content/relayer/api/callPlugin.mdx index 28418da1..524e8870 100644 --- a/content/relayer/api/callPlugin.mdx +++ b/content/relayer/api/callPlugin.mdx @@ -25,4 +25,4 @@ Logs and traces are only returned when the plugin is configured with `emit_logs` Plugin-provided errors are normalized into a consistent payload (`code`, `details`) and a derived message so downstream clients receive a stable shape regardless of how the handler threw. - \ No newline at end of file + \ No newline at end of file From bd23dcf37f3d3f5fe90c5afd6960e5bdbd2149a2 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 13 Jan 2026 08:42:43 -0300 Subject: [PATCH 6/8] fix: Fixing navigation --- content/relayer/1.1.x/api/callPlugin.mdx | 2 +- src/app/page.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/content/relayer/1.1.x/api/callPlugin.mdx b/content/relayer/1.1.x/api/callPlugin.mdx index cb0b2826..e80f5752 100644 --- a/content/relayer/1.1.x/api/callPlugin.mdx +++ b/content/relayer/1.1.x/api/callPlugin.mdx @@ -12,4 +12,4 @@ _openapi: {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} - + diff --git a/src/app/page.tsx b/src/app/page.tsx index 14e31187..80c27fc6 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -29,6 +29,8 @@ import { } from "@/components/icons"; import { DefenderIcon } from "@/components/icons/defender-icon"; import { baseOptions } from "./layout.config"; +import { latestStable as relayerLatestStable } from "../../content/relayer/latest-versions"; +import { latestStable as monitorLatestStable } from "../../content/monitor/latest-versions"; export default function HomePage() { return ( @@ -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." From 5956d036ef2666cdb2e53a9da7567707ed6486fc Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 13 Jan 2026 08:45:35 -0300 Subject: [PATCH 7/8] fix: Fix linting --- src/app/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 80c27fc6..ebe274ac 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -28,9 +28,9 @@ import { ZamaIcon, } from "@/components/icons"; import { DefenderIcon } from "@/components/icons/defender-icon"; -import { baseOptions } from "./layout.config"; -import { latestStable as relayerLatestStable } from "../../content/relayer/latest-versions"; 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() { return ( From f753e70faca0b892e8bd733d27507ba10f521307 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 13 Jan 2026 08:50:59 -0300 Subject: [PATCH 8/8] fix: Fix mdx file --- content/relayer/api/callPlugin.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/relayer/api/callPlugin.mdx b/content/relayer/api/callPlugin.mdx index 524e8870..85483a86 100644 --- a/content/relayer/api/callPlugin.mdx +++ b/content/relayer/api/callPlugin.mdx @@ -25,4 +25,4 @@ Logs and traces are only returned when the plugin is configured with `emit_logs` Plugin-provided errors are normalized into a consistent payload (`code`, `details`) and a derived message so downstream clients receive a stable shape regardless of how the handler threw. - \ No newline at end of file + \ No newline at end of file