docs: apply sidebar review feedback

This commit is contained in:
Alem Tuzlak
2026-09-02 15:52:14 +02:00
parent f13b594d93
commit 889b3cdf51
33 changed files with 461 additions and 286 deletions
@@ -81,23 +81,6 @@ describe("globals.css cookbook sidebar", () => {
}); });
}); });
describe("globals.css sidebar Intelligence pin", () => {
it("stays muted until hover, then fills like the header Intelligence control", () => {
const idleBlock = globalsCss.match(
/\.shell-docs-intelligence-entry \{[\s\S]*?\n\}/,
)?.[0];
expect(idleBlock).toContain("background: transparent;");
expect(idleBlock).toContain("color: var(--text-muted);");
expect(idleBlock).not.toContain("background: var(--accent);");
const hoverBlock = globalsCss.match(
/\.shell-docs-intelligence-entry:hover,\s*\.shell-docs-intelligence-entry:focus-visible \{[\s\S]*?\n\}/,
)?.[0];
expect(hoverBlock).toContain("background: var(--accent);");
expect(hoverBlock).toContain("color: #fff;");
});
});
describe("globals.css sidebar section folders", () => { describe("globals.css sidebar section folders", () => {
it("styles collapsible section triggers separately from page links", () => { it("styles collapsible section triggers separately from page links", () => {
expect(globalsCss).toContain(".shell-docs-sidebar-section-label"); expect(globalsCss).toContain(".shell-docs-sidebar-section-label");
-76
View File
@@ -314,63 +314,6 @@
color: var(--accent); color: var(--accent);
} }
.shell-docs-intelligence-entry {
display: flex;
align-items: center;
gap: 0.5rem;
min-height: 2.5rem;
padding: 0.5rem 0.75rem;
border-radius: var(--shell-docs-radius-control);
background: transparent;
color: var(--text-muted);
font-size: 0.8125rem;
font-weight: 500;
text-decoration: none;
transition:
background-color 150ms ease-out,
color 150ms ease-out,
transform 100ms ease-out;
}
.shell-docs-intelligence-entry svg {
fill: none;
}
.shell-docs-intelligence-entry:hover,
.shell-docs-intelligence-entry:focus-visible {
background: var(--accent);
color: #fff;
}
.shell-docs-intelligence-entry:hover svg,
.shell-docs-intelligence-entry:focus-visible svg {
fill: currentColor;
}
.shell-docs-intelligence-entry:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.shell-docs-intelligence-entry:active {
transform: scale(0.96);
}
.shell-docs-intelligence-entry-active {
background: color-mix(in oklch, var(--accent) 13%, transparent);
color: var(--accent);
}
.shell-docs-intelligence-entry-active:hover,
.shell-docs-intelligence-entry-active:focus-visible {
background: var(--accent);
color: #fff;
}
.dark .shell-docs-intelligence-entry-active {
background: color-mix(in oklch, var(--accent) 20%, transparent);
}
.shell-docs-mega-menu { .shell-docs-mega-menu {
padding: 1.25rem 1.5rem; padding: 1.25rem 1.5rem;
} }
@@ -455,14 +398,9 @@
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.shell-docs-intelligence-entry,
.shell-docs-mega-menu-link { .shell-docs-mega-menu-link {
transition: none; transition: none;
} }
.shell-docs-intelligence-entry:active {
transform: none;
}
} }
.shell-docs-warning-surface { .shell-docs-warning-surface {
@@ -583,20 +521,6 @@
color: var(--accent); color: var(--accent);
} }
.shell-docs-nav-link-idle.shell-docs-nav-link-intelligence:hover,
.shell-docs-nav-link-active.shell-docs-nav-link-intelligence:hover,
.shell-docs-nav-link-intelligence:focus-visible {
background: var(--accent);
color: #fff;
box-shadow: none;
}
.dark .shell-docs-nav-link-idle.shell-docs-nav-link-intelligence:hover,
.dark .shell-docs-nav-link-active.shell-docs-nav-link-intelligence:hover {
background: var(--accent);
color: #fff;
}
/* shell-docs sidebar overrides. /* shell-docs sidebar overrides.
* *
* Fumadocs's DocsLayout sidebar reserves a header row with the home * Fumadocs's DocsLayout sidebar reserves a header row with the home
@@ -48,6 +48,7 @@ vi.mock("@clerk/nextjs", () => {
}); });
import { BrandNav, buildDocsAuthEntryHref } from "../brand-nav"; import { BrandNav, buildDocsAuthEntryHref } from "../brand-nav";
import { PrimaryDocsTabs } from "../primary-docs-tabs";
import { import {
buildDocsUserMenuHref, buildDocsUserMenuHref,
DocsAuthFallbackBoundary, DocsAuthFallbackBoundary,
@@ -70,28 +71,31 @@ const globalsCss = readFileSync(
"utf8", "utf8",
); );
test("BrandNav opens docs from an Explore docs mega menu", () => { test("BrandNav opens Docs from a mega menu", () => {
expect(brandNavSource).toContain("DocsMegaMenu"); expect(brandNavSource).toContain("DocsMegaMenu");
expect(brandNavSource).not.toContain('label: "Docs"'); expect(brandNavSource).not.toContain('label: "Docs"');
expect(brandNavSource).not.toContain('href: "/"'); expect(brandNavSource).not.toContain('href: "/"');
}); });
test("BrandNav puts Intelligence next to Cookbook", () => { test("BrandNav keeps Intelligence out of the primary header links", () => {
expect(brandNavSource).toContain('label: "Cookbook"'); expect(brandNavSource).toContain('label: "Cookbook"');
expect(brandNavSource).toContain('label: "Intelligence"'); expect(brandNavSource).not.toContain('label: "Intelligence"');
expect(brandNavSource).toContain("INTELLIGENCE_DOCS_HREF"); expect(brandNavSource).not.toContain("INTELLIGENCE_DOCS_HREF");
expect(brandNavSource.indexOf('label: "Cookbook"')).toBeLessThan( });
brandNavSource.indexOf('label: "Intelligence"'),
); test("PrimaryDocsTabs keeps Intelligence out of the mobile links", () => {
const markup = renderToStaticMarkup(<PrimaryDocsTabs />);
expect(markup).toContain(">Docs<");
expect(markup).toContain(">Reference<");
expect(markup).toContain(">Cookbook<");
expect(markup).not.toContain(">Intelligence<");
}); });
test("BrandNav keeps space between the center rail and search", () => { test("BrandNav keeps space between the center rail and search", () => {
expect(brandNavSource).toContain("grid-cols-[auto_minmax(0,1fr)_auto]"); expect(brandNavSource).toContain("grid-cols-[auto_minmax(0,1fr)_auto]");
expect(brandNavSource).toContain("gap-x-8"); expect(brandNavSource).toContain("gap-x-8");
expect(brandNavSource).toContain("pl-4"); expect(brandNavSource).toContain("pl-4");
expect(globalsCss).toContain(
".shell-docs-nav-link-idle.shell-docs-nav-link-intelligence:hover",
);
}); });
test("BrandNav uses the docs grid desktop layout cap", () => { test("BrandNav uses the docs grid desktop layout cap", () => {
@@ -23,9 +23,9 @@ afterEach(cleanup);
test("opens a five-column docs map with Intelligence featured", () => { test("opens a five-column docs map with Intelligence featured", () => {
render(<DocsMegaMenu triggerClassName="shell-docs-nav-link-active" />); render(<DocsMegaMenu triggerClassName="shell-docs-nav-link-active" />);
fireEvent.pointerEnter(screen.getByRole("button", { name: "Explore docs" })); fireEvent.pointerEnter(screen.getByRole("button", { name: "Docs" }));
expect(screen.getByRole("navigation", { name: "Explore docs" })).toBeTruthy(); expect(screen.getByRole("navigation", { name: "Docs" })).toBeTruthy();
expect(screen.getByRole("heading", { name: "Start" })).toBeTruthy(); expect(screen.getByRole("heading", { name: "Start" })).toBeTruthy();
expect(screen.getByRole("heading", { name: "Build" })).toBeTruthy(); expect(screen.getByRole("heading", { name: "Build" })).toBeTruthy();
expect(screen.getByRole("heading", { name: "Connect" })).toBeTruthy(); expect(screen.getByRole("heading", { name: "Connect" })).toBeTruthy();
@@ -37,6 +37,12 @@ test("opens a five-column docs map with Intelligence featured", () => {
expect(intelligence.className).toContain( expect(intelligence.className).toContain(
"shell-docs-mega-menu-link-featured", "shell-docs-mega-menu-link-featured",
); );
expect(intelligence.querySelector("svg")?.getAttribute("viewBox")).toBe(
"0 0 24 24",
);
expect(intelligence.querySelector("svg")?.getAttribute("fill")).toBe(
"currentColor",
);
expect( expect(
screen.getByRole("link", { name: "Threads" }).getAttribute("href"), screen.getByRole("link", { name: "Threads" }).getAttribute("href"),
).toBe("/threads"); ).toBe("/threads");
@@ -6,16 +6,13 @@ const layoutSource = readFileSync(
"utf8", "utf8",
); );
test("sidebar banner puts pickers above the Intelligence pin", () => { test("sidebar banner keeps the pickers above the mobile docs tabs", () => {
const bannerIndex = layoutSource.lastIndexOf("{banner}"); const bannerIndex = layoutSource.lastIndexOf("{banner}");
const intelligenceIndex = layoutSource.lastIndexOf(
"<SidebarIntelligenceEntry",
);
const tabsIndex = layoutSource.indexOf( const tabsIndex = layoutSource.indexOf(
'<PrimaryDocsTabs className="shell-docs-mobile-sidebar-tabs" />', '<PrimaryDocsTabs className="shell-docs-mobile-sidebar-tabs" />',
); );
expect(bannerIndex).toBeGreaterThanOrEqual(0); expect(bannerIndex).toBeGreaterThanOrEqual(0);
expect(intelligenceIndex).toBeGreaterThan(bannerIndex); expect(tabsIndex).toBeGreaterThan(bannerIndex);
expect(tabsIndex).toBeGreaterThan(intelligenceIndex); expect(layoutSource).not.toContain("SidebarIntelligenceEntry");
}); });
@@ -1,51 +0,0 @@
// @vitest-environment jsdom
import React from "react";
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, expect, test, vi } from "vitest";
const navigation = vi.hoisted(() => ({
pathname: "/",
}));
vi.mock("next/navigation", () => ({
usePathname: () => navigation.pathname,
}));
vi.mock("next/link", () => ({
default: ({ children, href, ...props }: React.ComponentProps<"a">) => (
<a href={href} {...props}>
{children}
</a>
),
}));
import { SidebarIntelligenceEntry } from "../sidebar-intelligence-entry";
afterEach(cleanup);
test("pins Intelligence at the top of the sidebar as a docs link", () => {
navigation.pathname = "/quickstart";
render(<SidebarIntelligenceEntry />);
const link = screen.getByRole("link", { name: "Intelligence" });
expect(link.getAttribute("href")).toBe("/intelligence/overview");
expect(link.getAttribute("aria-current")).toBeNull();
expect(link.className).toContain("shell-docs-intelligence-entry");
expect(link.className).not.toContain("shell-docs-intelligence-entry-active");
expect(link.querySelector("svg")?.getAttribute("class")).not.toContain(
"fill-current",
);
});
test("marks the Intelligence pin current on Intelligence docs", () => {
navigation.pathname = "/intelligence/overview";
render(<SidebarIntelligenceEntry />);
const link = screen.getByRole("link", { name: "Intelligence" });
expect(link.getAttribute("aria-current")).toBe("page");
expect(link.className).toContain("shell-docs-intelligence-entry-active");
expect(link.querySelector("svg")?.getAttribute("class")).not.toContain(
"fill-current",
);
});
@@ -3,18 +3,14 @@
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { usePostHog } from "posthog-js/react"; import { usePostHog } from "posthog-js/react";
import { CalendarDays, ChefHat, Star } from "lucide-react"; import { CalendarDays, ChefHat } from "lucide-react";
import { SearchTrigger } from "./search-trigger"; import { SearchTrigger } from "./search-trigger";
import { CopilotKitMark } from "./copilotkit-mark"; import { CopilotKitMark } from "./copilotkit-mark";
import { ThemeSwitch } from "./theme-switch"; import { ThemeSwitch } from "./theme-switch";
import ConsoleIcon from "./icons/console"; import ConsoleIcon from "./icons/console";
import ExternalLinkIcon from "./icons/external-link"; import ExternalLinkIcon from "./icons/external-link";
import { DocsMegaMenu } from "./docs-mega-menu"; import { DocsMegaMenu } from "./docs-mega-menu";
import { import { isDocsExplorePath } from "@/lib/docs-mega-menu";
INTELLIGENCE_DOCS_HREF,
isDocsExplorePath,
isIntelligenceDocsPath,
} from "@/lib/docs-mega-menu";
import { import {
DocsPublicAuthControl, DocsPublicAuthControl,
buildDocsAuthEntryHref, buildDocsAuthEntryHref,
@@ -50,11 +46,6 @@ const LEFT_LINKS: LeftLink[] = [
label: "Cookbook", label: "Cookbook",
href: "/cookbook", href: "/cookbook",
}, },
{
icon: <Star className="w-4 h-4 text-current" />,
label: "Intelligence",
href: INTELLIGENCE_DOCS_HREF,
},
]; ];
export interface BrandNavProps { export interface BrandNavProps {
@@ -69,18 +60,15 @@ export function BrandNav(_props: BrandNavProps = {}) {
const posthog = usePostHog(); const posthog = usePostHog();
const authEntryHref = useDocsAuthEntryHref(); const authEntryHref = useDocsAuthEntryHref();
// Active-route detection: Reference, Cookbook, and Intelligence each own // Active-route detection: Reference and Cookbook each own their prefix.
// their prefix. Everything else (root and framework-scoped pages) // Everything else highlights Docs.
// highlights Explore docs.
const firstSegment = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`; const firstSegment = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`;
const activeRoute = const activeRoute =
firstSegment === "/reference" firstSegment === "/reference"
? "/reference" ? "/reference"
: firstSegment === "/cookbook" : firstSegment === "/cookbook"
? "/cookbook" ? "/cookbook"
: isIntelligenceDocsPath(pathname) : "/";
? INTELLIGENCE_DOCS_HREF
: "/";
const handleTalkToEngineersClick = () => { const handleTalkToEngineersClick = () => {
posthog?.capture("talk_to_us_clicked", { location: "docs_nav" }); posthog?.capture("talk_to_us_clicked", { location: "docs_nav" });
@@ -122,7 +110,6 @@ export function BrandNav(_props: BrandNavProps = {}) {
</li> </li>
{LEFT_LINKS.map((link) => { {LEFT_LINKS.map((link) => {
const isActive = activeRoute === link.href; const isActive = activeRoute === link.href;
const isIntelligence = link.href === INTELLIGENCE_DOCS_HREF;
return ( return (
<li key={link.href} className="relative h-full group"> <li key={link.href} className="relative h-full group">
<Link <Link
@@ -131,7 +118,7 @@ export function BrandNav(_props: BrandNavProps = {}) {
isActive isActive
? "shell-docs-nav-link-active" ? "shell-docs-nav-link-active"
: "shell-docs-nav-link-idle" : "shell-docs-nav-link-idle"
}${isIntelligence ? " shell-docs-nav-link-intelligence" : ""}`} }`}
> >
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<span className="[@media(width<808px)]:hidden"> <span className="[@media(width<808px)]:hidden">
@@ -0,0 +1,21 @@
export function CopilotKitIcon({ className }: { className?: string }) {
return (
<svg
fill="currentColor"
fillRule="evenodd"
viewBox="0 0 24 24"
width="1em"
height="1em"
className={className}
aria-hidden="true"
>
<path d="M11.7 9.938l-.476 1.59h2.622c.07.054.139.107.207.162l.193.158h-3.162L.679 24.1l-.05.04a.16.16 0 01-.194-.246l10.43-12.285.56-1.862.276.19z" />
<path d="M20.654 19.34c.007-.068.097-.09.134-.032l.566.868.009.015c.36.918.15 3.05-1.966 3.05-3.467 0-2.813-3.513-4.423-3.513-2.221 0-1.052 4.333-4.883 4.333-1.565 0-2.875-.738-3.514-2.427l1.231-.173c.138 1.004 1.125 2.463 2.418 2.464 1.962 0 1.552-4.29 4.748-4.29 2.723.001 3.053 3.468 4.423 3.468 1.298 0 1.243-1.763 1.155-2.49a1.021 1.021 0 01-.003-.22l.105-1.053z" />
<path d="M20.19 18.118l.001.003h-.002c-.046-.083-.097-.165-.148-.248a12.717 12.717 0 00-.846-1.205 19.993 19.993 0 00-.845-.998 26.91 26.91 0 00-.865-.92 39.347 39.347 0 00-3.24-2.902h3.315l2.63 6.27z" />
<path d="M23.186 2.776c-1.244 3.158-2.765 8.818-2.823 15.28 0 .004-.002.008-.003.011 0-.006 0-.013-.003-.02l-2.62-6.25a.158.158 0 00-.052-.257l-.061-.013-2.26-5.39c.322-.103.646-.208.967-.319a47.04 47.04 0 006.302-2.72c.166-.087.296-.158.383-.205l.1-.055.025-.015.008-.004a.096.096 0 00.037-.043z" />
<path d="M17.427 11.527h-3.58c-.356-.281-.71-.557-1.063-.819-.365-.27-.729-.527-1.083-.77l.903-3.008c.818-.215 1.697-.46 2.585-.739l2.238 5.336z" />
<path d="M12.775.067a.045.045 0 01.035.003c2.8 1.528 6.037 2.213 10.33 2.575.012 0 .017.01.02.021a.094.094 0 00-.1-.006l-.008.004-.025.015-.099.053c-.086.048-.215.117-.38.205a46.846 46.846 0 01-6.276 2.709 42.38 42.38 0 01-.98.32l-1.397-3.333.05-.161a.16.16 0 00-.108-.2.157.157 0 00-.092.003L12.844.124a.092.092 0 00-.12-.049.091.091 0 00-.049.12l.943 2.248-1.316 4.376a65.208 65.208 0 01-3.36.777c-.224.045-.402.08-.522.102l-.138.026-.035.006a.128.128 0 01-.01.001l-.002.001a.093.093 0 00-.074.106c.009.05.057.083.106.074h.003l.01-.002.035-.007.139-.026c.121-.023.3-.057.524-.103a66.222 66.222 0 003.264-.75l-.818 2.722-.043-.03a26.931 26.931 0 00-1.506-.959c-.614-.36-1.17-.647-1.638-.842l-.002-.002-.037-.016a.09.09 0 01-.036-.14c2.092-2.736 3.83-5.444 4.498-7.63a.093.093 0 01.115-.06z" />
<path d="M15.118 6.021a56.31 56.31 0 01-2.452.705l1.146-3.819 1.306 3.114z" />
</svg>
);
}
@@ -23,16 +23,17 @@ import {
Server, Server,
Sparkles, Sparkles,
Terminal, Terminal,
type LucideIcon,
} from "lucide-react"; } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { CopilotKitIcon } from "./copilotkit-icon";
import { import {
DOCS_MEGA_MENU_COLUMNS, DOCS_MEGA_MENU_COLUMNS,
isDocsExplorePath, isDocsExplorePath,
type MegaMenuIconName,
} from "@/lib/docs-mega-menu"; } from "@/lib/docs-mega-menu";
import type { MegaMenuIconName } from "@/lib/docs-mega-menu";
const MEGA_MENU_ICONS: Record<MegaMenuIconName, LucideIcon> = { const MEGA_MENU_ICONS: Record<Exclude<MegaMenuIconName, "kite">, LucideIcon> = {
book: BookOpen, book: BookOpen,
rocket: Rocket, rocket: Rocket,
terminal: Terminal, terminal: Terminal,
@@ -118,7 +119,7 @@ export function DocsMegaMenu({
className="h-4 w-4 shrink-0 text-current" className="h-4 w-4 shrink-0 text-current"
aria-hidden="true" aria-hidden="true"
/> />
<span>Explore docs</span> <span>Docs</span>
<ChevronDown <ChevronDown
className={`h-3.5 w-3.5 text-current transition-transform duration-150 ${ className={`h-3.5 w-3.5 text-current transition-transform duration-150 ${
open ? "rotate-180" : "" open ? "rotate-180" : ""
@@ -142,14 +143,15 @@ export function DocsMegaMenu({
if (openedByPointer.current) event.preventDefault(); if (openedByPointer.current) event.preventDefault();
}} }}
> >
<nav aria-label="Explore docs"> <nav aria-label="Docs">
<div className="shell-docs-mega-menu-grid"> <div className="shell-docs-mega-menu-grid">
{DOCS_MEGA_MENU_COLUMNS.map((column) => ( {DOCS_MEGA_MENU_COLUMNS.map((column) => (
<section key={column.title} className="min-w-0"> <section key={column.title} className="min-w-0">
<h2 className="shell-docs-mega-menu-heading">{column.title}</h2> <h2 className="shell-docs-mega-menu-heading">{column.title}</h2>
<ul role="list" className="shell-docs-mega-menu-list"> <ul role="list" className="shell-docs-mega-menu-list">
{column.links.map((link) => { {column.links.map((link) => {
const Icon = MEGA_MENU_ICONS[link.icon]; const Icon =
link.icon === "kite" ? null : MEGA_MENU_ICONS[link.icon];
const featured = link.featured === true; const featured = link.featured === true;
return ( return (
<li key={link.href}> <li key={link.href}>
@@ -162,10 +164,14 @@ export function DocsMegaMenu({
: "shell-docs-mega-menu-link" : "shell-docs-mega-menu-link"
} }
> >
<Icon {Icon ? (
className="shell-docs-mega-menu-icon" <Icon
aria-hidden="true" className="shell-docs-mega-menu-icon"
/> aria-hidden="true"
/>
) : (
<CopilotKitIcon className="shell-docs-mega-menu-icon" />
)}
<span className="min-w-0"> <span className="min-w-0">
<span className="block truncate font-medium"> <span className="block truncate font-medium">
{link.label} {link.label}
@@ -2,14 +2,10 @@
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { ChefHat, Star } from "lucide-react"; import { ChefHat } from "lucide-react";
import BookIcon from "./icons/book"; import BookIcon from "./icons/book";
import { DocsMegaMenu } from "./docs-mega-menu"; import { DocsMegaMenu } from "./docs-mega-menu";
import { import { isDocsExplorePath } from "@/lib/docs-mega-menu";
INTELLIGENCE_DOCS_HREF,
isDocsExplorePath,
isIntelligenceDocsPath,
} from "@/lib/docs-mega-menu";
const PRIMARY_DOCS_LINKS = [ const PRIMARY_DOCS_LINKS = [
{ {
@@ -27,11 +23,6 @@ const PRIMARY_DOCS_LINKS = [
label: "Cookbook", label: "Cookbook",
icon: <ChefHat className="h-4 w-4 text-current" />, icon: <ChefHat className="h-4 w-4 text-current" />,
}, },
{
href: INTELLIGENCE_DOCS_HREF,
label: "Intelligence",
icon: <Star className="h-4 w-4 text-current" />,
},
]; ];
function getActiveRoute(pathname: string) { function getActiveRoute(pathname: string) {
@@ -45,10 +36,6 @@ function getActiveRoute(pathname: string) {
return "/cookbook"; return "/cookbook";
} }
if (isIntelligenceDocsPath(pathname)) {
return INTELLIGENCE_DOCS_HREF;
}
return "/"; return "/";
} }
@@ -87,11 +74,7 @@ export function PrimaryDocsTabs({
<Link <Link
key={link.href} key={link.href}
href={link.href} href={link.href}
className={`${tabClassName}${ className={tabClassName}
link.href === INTELLIGENCE_DOCS_HREF
? " shell-docs-nav-link-intelligence"
: ""
}`}
aria-current={isActive ? "page" : undefined} aria-current={isActive ? "page" : undefined}
> >
{link.icon} {link.icon}
@@ -9,7 +9,6 @@ import GithubIcon from "./icons/github";
import DiscordIcon from "./icons/discord"; import DiscordIcon from "./icons/discord";
import { MobileSidebarFooterTalk } from "./mobile-sidebar-footer-talk"; import { MobileSidebarFooterTalk } from "./mobile-sidebar-footer-talk";
import { PrimaryDocsTabs } from "./primary-docs-tabs"; import { PrimaryDocsTabs } from "./primary-docs-tabs";
import { SidebarIntelligenceEntry } from "./sidebar-intelligence-entry";
// Shared Fumadocs `DocsLayout` chrome used by every shell-docs route. // Shared Fumadocs `DocsLayout` chrome used by every shell-docs route.
// All five callers (home overview, framework root, framework-scoped MDX // All five callers (home overview, framework root, framework-scoped MDX
@@ -50,7 +49,6 @@ export function ShellDocsLayout({
banner: ( banner: (
<div key="shell-docs-sidebar-banner" className="flex flex-col gap-2"> <div key="shell-docs-sidebar-banner" className="flex flex-col gap-2">
{banner} {banner}
<SidebarIntelligenceEntry />
<PrimaryDocsTabs className="shell-docs-mobile-sidebar-tabs" /> <PrimaryDocsTabs className="shell-docs-mobile-sidebar-tabs" />
</div> </div>
), ),
@@ -1,27 +0,0 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Star } from "lucide-react";
import {
INTELLIGENCE_DOCS_HREF,
isIntelligenceDocsPath,
} from "@/lib/docs-mega-menu";
export function SidebarIntelligenceEntry() {
const pathname = usePathname() ?? "/";
const active = isIntelligenceDocsPath(pathname);
return (
<Link
href={INTELLIGENCE_DOCS_HREF}
className={`shell-docs-intelligence-entry ${
active ? "shell-docs-intelligence-entry-active" : ""
}`}
aria-current={active ? "page" : undefined}
>
<Star className="h-4 w-4 shrink-0" aria-hidden="true" />
<span className="min-w-0 truncate">Intelligence</span>
</Link>
);
}
@@ -1,6 +1,6 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
@@ -1,7 +1,7 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
<Overview /> <Overview />
@@ -1,6 +1,6 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
<Overview components={props.components} /> <Overview components={props.components} />
@@ -1,9 +1,9 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
import Overview from "@/snippets/shared/intelligence/overview.mdx"; import Overview from "@/snippets/shared/intelligence/overview.mdx";
<Overview components={props.components} /> <Overview components={props.components} />
@@ -1,5 +1,5 @@
{ {
"title": "Intelligence", "title": "Intelligence",
"icon": "lucide/Star", "icon": "custom/copilotkit-kite",
"pages": ["overview", "self-hosting"] "pages": ["overview", "self-hosting"]
} }
@@ -1,6 +1,6 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
@@ -1,6 +1,6 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
<Overview /> <Overview />
@@ -1,6 +1,6 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
<Overview components={props.components} /> <Overview components={props.components} />
@@ -1,9 +1,9 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
import Overview from "@/snippets/shared/intelligence/overview.mdx"; import Overview from "@/snippets/shared/intelligence/overview.mdx";
<Overview components={props.components} /> <Overview components={props.components} />
@@ -1,6 +1,6 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
@@ -1,8 +1,8 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence capabilities. description: CopilotKit Intelligence capabilities.
--- ---
<Overview components={props.components} /> <Overview components={props.components} />
@@ -1,6 +1,6 @@
--- ---
title: CopilotKit Intelligence title: CopilotKit Intelligence
icon: "lucide/Star" icon: "custom/copilotkit-kite"
description: CopilotKit Intelligence overview for CopilotKit — features, cloud-hosted and self-hosted deployment options, threads, hosted inspection, and production operations. description: CopilotKit Intelligence overview for CopilotKit — features, cloud-hosted and self-hosted deployment options, threads, hosted inspection, and production operations.
doc_type: explanation doc_type: explanation
--- ---
@@ -54,6 +54,25 @@ function pageSlugs(nodes: NavNode[]): string[] {
}); });
} }
function sectionTitles(nodes: NavNode[]): string[] {
return nodes.flatMap((node) => (node.type === "section" ? [node.title] : []));
}
function sectionNodes(nodes: NavNode[], section: string): NavNode[] {
const sectionIndex = nodes.findIndex(
(node) => node.type === "section" && node.title === section,
);
if (sectionIndex === -1) return [];
const nextSectionIndex = nodes.findIndex(
(node, index) => index > sectionIndex && node.type === "section",
);
return nodes.slice(
sectionIndex + 1,
nextSectionIndex === -1 ? undefined : nextSectionIndex,
);
}
function getReactNavTree(backendFramework: string | null): NavNode[] { function getReactNavTree(backendFramework: string | null): NavNode[] {
if (!backendFramework) { if (!backendFramework) {
return buildRootSurfaceNav(getDocsFolder(ROOT_FRAMEWORK)); return buildRootSurfaceNav(getDocsFolder(ROOT_FRAMEWORK));
@@ -198,6 +217,53 @@ test("maps every React navigation destination to a published Angular destination
} }
}); });
test("uses the normalized sidebar flow for Angular docs", () => {
const navTree = getAngularDocsNavTree(null);
const titles = sectionTitles(navTree);
const gettingStarted = sectionNodes(navTree, "Getting Started");
const quickstartIndex = gettingStarted.findIndex(
(node) => node.type === "page" && node.title === "Angular quickstart",
);
const intelligenceLink = gettingStarted[quickstartIndex + 1];
const orderedSections = [
"Getting Started",
"Basics",
"Generative UI",
"App Control",
"Intelligence",
"Angular Guides",
"Built-in Agent",
"Deployment",
].filter((title) => titles.includes(title));
expect(titles.filter((title) => orderedSections.includes(title))).toEqual([
"Getting Started",
"Basics",
"Intelligence",
"Angular Guides",
"Built-in Agent",
"Deployment",
]);
expect(titles.filter((title) => title === "Getting Started")).toHaveLength(1);
expect(titles.indexOf("Angular Guides")).toBeGreaterThan(
titles.indexOf("Intelligence"),
);
expect(titles.indexOf("Deployment")).toBeGreaterThan(
titles.indexOf("Angular Guides"),
);
expect(intelligenceLink).toMatchObject({
type: "page",
title: "CopilotKit Intelligence",
icon: "custom/copilotkit-kite",
});
expect(
sectionNodes(navTree, "Intelligence").some(
(node) =>
node.type === "page" && node.title === "CopilotKit Intelligence",
),
).toBe(true);
});
test("resolves canonical contribution aliases through shared Angular content", () => { test("resolves canonical contribution aliases through shared Angular content", () => {
expect( expect(
resolveAngularDoc("claude-sdk-python", "contributing/code-contributions"), resolveAngularDoc("claude-sdk-python", "contributing/code-contributions"),
@@ -28,7 +28,7 @@ describe("docs mega menu data", () => {
expect(intelligence).toEqual({ expect(intelligence).toEqual({
href: "/intelligence/overview", href: "/intelligence/overview",
label: "Intelligence", label: "Intelligence",
icon: "sparkles", icon: "kite",
featured: true, featured: true,
}); });
expect(shipColumn?.links.map((link) => [link.label, link.href])).toEqual([ expect(shipColumn?.links.map((link) => [link.label, link.href])).toEqual([
@@ -57,12 +57,13 @@ describe("isIntelligenceDocsPath", () => {
}); });
describe("isDocsExplorePath", () => { describe("isDocsExplorePath", () => {
it("treats guide pages as Explore docs, not Reference or Cookbook", () => { it("treats Intelligence and guide pages as Docs", () => {
expect(isDocsExplorePath("/")).toBe(true); expect(isDocsExplorePath("/")).toBe(true);
expect(isDocsExplorePath("/quickstart")).toBe(true); expect(isDocsExplorePath("/quickstart")).toBe(true);
expect(isDocsExplorePath("/reference")).toBe(false); expect(isDocsExplorePath("/reference")).toBe(false);
expect(isDocsExplorePath("/reference/hooks/useAgent")).toBe(false); expect(isDocsExplorePath("/reference/hooks/useAgent")).toBe(false);
expect(isDocsExplorePath("/cookbook")).toBe(false); expect(isDocsExplorePath("/cookbook")).toBe(false);
expect(isDocsExplorePath("/intelligence/overview")).toBe(false); expect(isDocsExplorePath("/intelligence/overview")).toBe(true);
expect(isDocsExplorePath("/premium/overview")).toBe(true);
}); });
}); });
@@ -15,6 +15,7 @@ vi.mock("../registry", async (importOriginal) => {
import { import {
buildFrameworkNav, buildFrameworkNav,
buildFrameworkOnlyNav, buildFrameworkOnlyNav,
buildRootSurfaceNav,
CONTENT_DIR, CONTENT_DIR,
inlineSnippets, inlineSnippets,
loadDoc, loadDoc,
@@ -114,6 +115,30 @@ function sectionPages(navTree: NavNode[], section: string): string[] {
return pages; return pages;
} }
function sectionTitles(navTree: NavNode[]): string[] {
return navTree
.filter(
(node): node is Extract<NavNode, { type: "section" }> =>
node.type === "section",
)
.map((node) => node.title);
}
function sectionNodes(navTree: NavNode[], section: string): NavNode[] {
const sectionIndex = navTree.findIndex(
(node) => node.type === "section" && node.title === section,
);
if (sectionIndex === -1) return [];
const nextSectionIndex = navTree.findIndex(
(node, index) => index > sectionIndex && node.type === "section",
);
return navTree.slice(
sectionIndex + 1,
nextSectionIndex === -1 ? undefined : nextSectionIndex,
);
}
function collectMdxFiles(dir: string): string[] { function collectMdxFiles(dir: string): string[] {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const filePath = path.join(dir, entry.name); const filePath = path.join(dir, entry.name);
@@ -201,7 +226,7 @@ describe("loadDoc", () => {
it("resolves clean URLs to files stored under route-group folders", () => { it("resolves clean URLs to files stored under route-group folders", () => {
const doc = loadDoc("integrations/aws-strands/telemetry"); const doc = loadDoc("integrations/aws-strands/telemetry");
expect(doc?.filePath).toContain( expect(doc?.filePath.replaceAll("\\", "/")).toContain(
"integrations/aws-strands/(other)/telemetry/index.mdx", "integrations/aws-strands/(other)/telemetry/index.mdx",
); );
}); });
@@ -466,6 +491,82 @@ describe("cookbook nav", () => {
}); });
describe("framework nav", () => { describe("framework nav", () => {
it("uses the requested section flow on the root docs surface", () => {
const navTree = buildRootSurfaceNav("built-in-agent");
const titles = sectionTitles(navTree);
const gettingStarted = sectionNodes(navTree, "Getting Started");
const quickstartIndex = gettingStarted.findIndex(
(node) => node.type === "page" && node.slug === "quickstart",
);
const deployment = sectionNodes(navTree, "Deployment");
const runtime = deployment.find(
(node) => node.type === "group" && node.title === "Runtime",
);
expect(titles).toEqual([
"Getting Started",
"Basics",
"Generative UI",
"App Control",
"Intelligence",
"Built-in Agent",
"Deployment",
"Concepts",
"Other",
]);
expect(gettingStarted[quickstartIndex + 1]).toMatchObject({
type: "page",
title: "CopilotKit Intelligence",
icon: "custom/copilotkit-kite",
});
expect(runtime).toMatchObject({ type: "group", title: "Runtime" });
expect(
runtime?.type === "group" &&
hasPageTitle(runtime.children, "Copilot Runtime"),
).toBe(true);
expect(titles).not.toContain("Runtime");
expect(titles).not.toContain("Deploy");
});
it("uses the same section flow for generated and authored frameworks", () => {
const generatedNav = buildFrameworkNav(
"langgraph",
"LangGraph (Python)",
"langgraph-python",
);
const authoredNav = buildFrameworkOnlyNav("ag2");
expect(sectionTitles(generatedNav)).toEqual([
"Getting Started",
"Basics",
"Generative UI",
"App Control",
"Intelligence",
"LangGraph (Python)",
"Deployment",
"Concepts",
"Observe & Operate",
"Other",
]);
expect(sectionTitles(authoredNav)).toEqual([
"Getting Started",
"Basics",
"Generative UI",
"App Control",
"Intelligence",
"AG2",
"Deployment",
"Troubleshooting",
"Other",
]);
expect(hasSectionPage(authoredNav, "Deployment", "Copilot Runtime")).toBe(
true,
);
expect(
hasSectionPage(authoredNav, "Getting Started", "CopilotKit Intelligence"),
).toBe(true);
});
it("leaves Slack and Teams platform guides ungated", () => { it("leaves Slack and Teams platform guides ungated", () => {
const slack = loadDoc("frontends/slack"); const slack = loadDoc("frontends/slack");
const teams = loadDoc("frontends/teams"); const teams = loadDoc("frontends/teams");
@@ -25,6 +25,8 @@ function folderNamed(
describe("navTreeToPageTree section folders", () => { describe("navTreeToPageTree section folders", () => {
const navTree: NavNode[] = [ const navTree: NavNode[] = [
{ type: "section", title: "Getting Started", icon: "lucide/Rocket" },
{ type: "page", title: "Introduction", slug: "" },
{ type: "section", title: "Concepts", icon: "lucide/BookOpen" }, { type: "section", title: "Concepts", icon: "lucide/BookOpen" },
{ type: "page", title: "Architecture", slug: "concepts/architecture" }, { type: "page", title: "Architecture", slug: "concepts/architecture" },
{ type: "page", title: "Which Hook", slug: "concepts/which-hook" }, { type: "page", title: "Which Hook", slug: "concepts/which-hook" },
@@ -54,10 +56,12 @@ describe("navTreeToPageTree section folders", () => {
const pageTree = navTreeToPageTree(navTree, ""); const pageTree = navTreeToPageTree(navTree, "");
it("turns labeled sections into collapsed folders", () => { it("opens Getting Started and collapses the other labeled sections", () => {
const gettingStarted = folderNamed(pageTree.children, "Getting Started");
const concepts = folderNamed(pageTree.children, "Concepts"); const concepts = folderNamed(pageTree.children, "Concepts");
const basics = folderNamed(pageTree.children, "Basics"); const basics = folderNamed(pageTree.children, "Basics");
expect(gettingStarted?.defaultOpen).toBe(true);
expect(concepts).toBeDefined(); expect(concepts).toBeDefined();
expect(basics).toBeDefined(); expect(basics).toBeDefined();
expect(concepts?.defaultOpen).toBe(false); expect(concepts?.defaultOpen).toBe(false);
@@ -3,6 +3,7 @@ import {
buildFrameworkOnlyNav, buildFrameworkOnlyNav,
buildRootSurfaceNav, buildRootSurfaceNav,
loadDoc, loadDoc,
normalizeSidebarNav,
} from "./docs-render"; } from "./docs-render";
import type { NavNode } from "./docs-render"; import type { NavNode } from "./docs-render";
import { import {
@@ -102,11 +103,12 @@ function filterResolvableAngularNodes(
if (node.type === "page") { if (node.type === "page") {
if (node.slug === "" || node.slug === "quickstart") return []; if (node.slug === "" || node.slug === "quickstart") return [];
const canonicalSlug = getFrontendCanonicalSlug("angular", node.slug); const canonicalSlug = getFrontendCanonicalSlug("angular", node.slug);
if (publishedSlugs.has(canonicalSlug)) { const isIntelligenceOverview = canonicalSlug === "intelligence/overview";
if (publishedSlugs.has(canonicalSlug) && !isIntelligenceOverview) {
return []; return [];
} }
if (!resolveAngularDoc(backendFramework, canonicalSlug)) return []; if (!resolveAngularDoc(backendFramework, canonicalSlug)) return [];
publishedSlugs.add(canonicalSlug); if (!isIntelligenceOverview) publishedSlugs.add(canonicalSlug);
return [{ ...node, slug: canonicalSlug }]; return [{ ...node, slug: canonicalSlug }];
} }
@@ -178,5 +180,5 @@ export function getAngularDocsNavTree(
), ),
); );
return [...prefixPages, ...backendNodes]; return normalizeSidebarNav([...prefixPages, ...backendNodes]);
} }
@@ -18,7 +18,8 @@ export type MegaMenuIconName =
| "box" | "box"
| "radio" | "radio"
| "brain" | "brain"
| "chart"; | "chart"
| "kite";
export interface MegaMenuLink { export interface MegaMenuLink {
href: string; href: string;
@@ -69,7 +70,7 @@ export const DOCS_MEGA_MENU_COLUMNS: readonly MegaMenuColumn[] = [
{ {
href: INTELLIGENCE_DOCS_HREF, href: INTELLIGENCE_DOCS_HREF,
label: "Intelligence", label: "Intelligence",
icon: "sparkles", icon: "kite",
featured: true, featured: true,
}, },
{ href: "/threads", label: "Threads", icon: "layers" }, { href: "/threads", label: "Threads", icon: "layers" },
@@ -109,9 +110,5 @@ export function isIntelligenceDocsPath(pathname: string) {
export function isDocsExplorePath(pathname: string) { export function isDocsExplorePath(pathname: string) {
const firstSegment = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`; const firstSegment = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`;
return ( return firstSegment !== "/reference" && firstSegment !== "/cookbook";
firstSegment !== "/reference" &&
firstSegment !== "/cookbook" &&
firstSegment !== "/intelligence"
);
} }
+180 -8
View File
@@ -83,9 +83,11 @@ const SECTION_ICONS: Record<string, string> = {
"add agent powers": "lucide/Wand2", "add agent powers": "lucide/Wand2",
runtime: "lucide/Cpu", runtime: "lucide/Cpu",
"observe & operate": "lucide/SearchCheck", "observe & operate": "lucide/SearchCheck",
intelligence: "custom/copilotkit-kite",
"intelligence platform": "custom/copilotkit-kite", "intelligence platform": "custom/copilotkit-kite",
channels: "lucide/MessagesSquare", channels: "lucide/MessagesSquare",
deploy: "lucide/Cloud", deploy: "lucide/Cloud",
deployment: "lucide/Cloud",
other: "lucide/MoreHorizontal", other: "lucide/MoreHorizontal",
// Built-in Agent (authored) sections — match the section names in // Built-in Agent (authored) sections — match the section names in
// `content/docs/integrations/built-in-agent/meta.json`. Adjust here // `content/docs/integrations/built-in-agent/meta.json`. Adjust here
@@ -654,9 +656,11 @@ export function buildFrameworkOnlyNav(
} }
return node; return node;
}; };
return dropEmptySections( return normalizeSidebarNav(
appendSharedThreadArchitecturePage( dropEmptySections(
appendSharedRootSections(nodes.map(rewrite), sharedSections), appendSharedThreadArchitecturePage(
appendSharedRootSections(nodes.map(rewrite), sharedSections),
),
), ),
); );
} }
@@ -757,6 +761,172 @@ function findPageBySlug(navTree: NavNode[], slug: string): NavNode | null {
return null; return null;
} }
type SidebarSection = {
section: Extract<NavNode, { type: "section" }>;
children: NavNode[];
sourceIndex: number;
};
const SIDEBAR_SECTION_TITLES: Record<string, string> = {
"get started": "Getting Started",
"getting started": "Getting Started",
"build chat uis": "Basics",
basics: "Basics",
"build generative ui": "Generative UI",
"generative ui": "Generative UI",
"add agent powers": "App Control",
"app control": "App Control",
intelligence: "Intelligence",
"intelligence platform": "Intelligence",
backend: "Runtime",
runtime: "Runtime",
deploy: "Deployment",
deployment: "Deployment",
};
function sidebarSectionRank(title: string): number {
switch (title) {
case "Getting Started":
return 0;
case "Basics":
return 1;
case "Generative UI":
return 2;
case "App Control":
return 3;
case "Intelligence":
return 4;
case "Deployment":
return 6;
case "Concepts":
case "Observe & Operate":
case "Platforms":
case "Tutorials":
case "Troubleshooting":
case "Other":
return 7;
default:
return 5;
}
}
export function normalizeSidebarNav(navTree: NavNode[]) {
const prefix: NavNode[] = [];
const sections: SidebarSection[] = [];
const sectionsByTitle = new Map<string, SidebarSection>();
let current: SidebarSection | undefined;
for (const node of navTree) {
if (node.type === "section") {
const title =
SIDEBAR_SECTION_TITLES[node.title.toLowerCase()] ?? node.title;
const existing = sectionsByTitle.get(title);
if (existing) {
current = existing;
continue;
}
current = {
section: {
...node,
title,
icon: node.icon ?? sectionIconFor(title),
},
children: [],
sourceIndex: sections.length,
};
sections.push(current);
sectionsByTitle.set(title, current);
} else if (current) {
current.children.push(node);
} else {
prefix.push(node);
}
}
let gettingStarted = sections.find(
({ section }) => section.title === "Getting Started",
);
if (prefix.length > 0) {
if (!gettingStarted) {
gettingStarted = {
section: {
type: "section",
title: "Getting Started",
icon: sectionIconFor("Getting Started"),
},
children: [],
sourceIndex: -1,
};
sections.push(gettingStarted);
}
gettingStarted.children.unshift(...prefix);
}
const runtimeSections = sections.filter(
({ section }) => section.title === "Runtime",
);
const orderedSections = sections.filter(
({ section }) => section.title !== "Runtime",
);
const runtimeChildren = runtimeSections.flatMap(({ children }) => children);
if (runtimeChildren.length > 0) {
let deployment = orderedSections.find(
({ section }) => section.title === "Deployment",
);
if (!deployment) {
deployment = {
section: {
type: "section",
title: "Deployment",
icon: sectionIconFor("Deployment"),
},
children: [],
sourceIndex: sections.length,
};
orderedSections.push(deployment);
}
deployment.children.push({
type: "group",
title: "Runtime",
slug: "sidebar#runtime",
children: runtimeChildren,
});
}
const intelligence = orderedSections.find(
({ section }) => section.title === "Intelligence",
);
const intelligenceOverview = intelligence
? findPageBySlug(intelligence.children, "intelligence/overview")
: null;
if (gettingStarted && intelligenceOverview?.type === "page") {
const existingIndex = gettingStarted.children.findIndex(
(node) => node.type === "page" && node.slug === intelligenceOverview.slug,
);
if (existingIndex !== -1) gettingStarted.children.splice(existingIndex, 1);
const quickstartIndex = gettingStarted.children.findIndex(
(node) =>
node.type === "page" &&
(node.slug === "quickstart" ||
node.title.toLowerCase().endsWith("quickstart")),
);
gettingStarted.children.splice(quickstartIndex + 1, 0, {
...intelligenceOverview,
icon: "custom/copilotkit-kite",
});
}
return orderedSections
.sort((left, right) => {
const rankDifference =
sidebarSectionRank(left.section.title) -
sidebarSectionRank(right.section.title);
return rankDifference || left.sourceIndex - right.sourceIndex;
})
.flatMap(({ section, children }) => [section, ...children]);
}
function isRichThreadsGroup( function isRichThreadsGroup(
node: NavNode, node: NavNode,
): node is Extract<NavNode, { type: "group" }> { ): node is Extract<NavNode, { type: "group" }> {
@@ -977,11 +1147,13 @@ export function buildFrameworkNav(
frameworkName: string, frameworkName: string,
frameworkSlug: string, frameworkSlug: string,
): NavNode[] { ): NavNode[] {
return mergeFrameworkNav( return normalizeSidebarNav(
buildNavTree(CONTENT_DIR), mergeFrameworkNav(
buildFrameworkOverridesNav(docsFolder), buildNavTree(CONTENT_DIR),
frameworkName, buildFrameworkOverridesNav(docsFolder),
frameworkSectionIcon(frameworkSlug), frameworkName,
frameworkSectionIcon(frameworkSlug),
),
); );
} }
@@ -302,7 +302,9 @@ export function wrapSectionSeparatorsAsFolders(
...(pending.icon ...(pending.icon
? { icon: keyedNode(pending.icon, "shell-docs-section-icon") } ? { icon: keyedNode(pending.icon, "shell-docs-section-icon") }
: {}), : {}),
defaultOpen: false, defaultOpen:
typeof pending.name === "string" &&
["Get Started", "Getting Started"].includes(pending.name),
children: pending.children, children: pending.children,
}); });
} }
+3 -4
View File
@@ -13,7 +13,7 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
import { LanggraphIcon } from "@/components/icons/framework-icons"; import { LanggraphIcon } from "@/components/icons/framework-icons";
import { CopilotKitMark } from "@/components/copilotkit-mark"; import { CopilotKitIcon } from "@/components/copilotkit-icon";
import { FrontendLogo } from "@/components/frontend-logo"; import { FrontendLogo } from "@/components/frontend-logo";
import { import {
// Pages / sections // Pages / sections
@@ -140,11 +140,10 @@ const ICONS: Record<string, React.ReactNode> = {
"lucide/Wrench": <Wrench />, "lucide/Wrench": <Wrench />,
"lucide/Zap": <Zap />, "lucide/Zap": <Zap />,
// Custom marks — used by section headers for framework / enterprise // Custom marks — used by section headers for framework / enterprise
// scaffolding. The CopilotKit kite is the inline gradient mark from // scaffolding. The LangGraph mark comes from the framework-icons set so
// BrandNav; the LangGraph mark comes from the framework-icons set so
// its visual treatment matches the framework picker. // its visual treatment matches the framework picker.
"custom/langgraph": <LanggraphIcon />, "custom/langgraph": <LanggraphIcon />,
"custom/copilotkit-kite": <CopilotKitMark />, "custom/copilotkit-kite": <CopilotKitIcon />,
"custom/react": <FrontendLogo icon="react" size={16} />, "custom/react": <FrontendLogo icon="react" size={16} />,
"custom/daytona": ( "custom/daytona": (
<Image <Image