From e8eeb6002caff38e2c22f16a488055c71b7fbf0a Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 30 Jul 2026 10:00:46 -0500 Subject: [PATCH 1/3] feat(nav): link Catalogs from the side menu (chat#1912 row 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The app's only navigation listed Agents / Tasks / Files, so Catalogs — the valuation payoff surface and the funnel's conversion target — was reachable only by direct URL or its own back-link. Adds CatalogsNavItem and renders it in both nav surfaces: SecondaryNav (desktop rail, via Menu) and SideMenu (mobile), each with the pathname-derived active state the sibling items already use. Reported by a live customer 2026-07-29: "I'm not sure where to find the tooling in the site ... I guess I'm waiting on the newsletter." Co-Authored-By: Claude Opus 5 (1M context) --- components/SideMenu/SideMenu.tsx | 6 +++ components/Sidebar/CatalogsNavItem.tsx | 24 +++++++++ components/Sidebar/Menu.tsx | 2 + components/Sidebar/SecondaryNav.tsx | 4 ++ .../Sidebar/__tests__/SecondaryNav.test.tsx | 54 +++++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 components/Sidebar/CatalogsNavItem.tsx create mode 100644 components/Sidebar/__tests__/SecondaryNav.test.tsx diff --git a/components/SideMenu/SideMenu.tsx b/components/SideMenu/SideMenu.tsx index 5a1adbf14..8a3a6bc5a 100644 --- a/components/SideMenu/SideMenu.tsx +++ b/components/SideMenu/SideMenu.tsx @@ -14,6 +14,7 @@ import AgentsNavItem from "../Sidebar/AgentsNavItem"; import { usePathname } from "next/navigation"; import TasksNavItem from "../Sidebar/TasksNavItem"; import FilesNavItem from "../Sidebar/FilesNavItem"; +import CatalogsNavItem from "../Sidebar/CatalogsNavItem"; const SideMenu = ({ isVisible, @@ -33,6 +34,7 @@ const SideMenu = ({ const isAgents = pathname.includes("/agents"); const isTasks = pathname.includes("/tasks"); const isFiles = pathname.includes("/files"); + const isCatalogs = pathname.includes("/catalogs"); const goToItem = (link?: string) => { if (isPrepared()) { @@ -88,6 +90,10 @@ const SideMenu = ({ )}
+ goToItem("catalogs")} + /> goToItem("agents")} diff --git a/components/Sidebar/CatalogsNavItem.tsx b/components/Sidebar/CatalogsNavItem.tsx new file mode 100644 index 000000000..0b3e25746 --- /dev/null +++ b/components/Sidebar/CatalogsNavItem.tsx @@ -0,0 +1,24 @@ +import NavButton from "./NavButton"; + +const CatalogsNavItem = ({ + isActive, + isExpanded, + onClick, +}: { + isActive: boolean; + isExpanded?: boolean; + onClick: () => void; +}) => { + return ( + + ); +}; + +export default CatalogsNavItem; diff --git a/components/Sidebar/Menu.tsx b/components/Sidebar/Menu.tsx index a1e6f9354..79bc500f0 100644 --- a/components/Sidebar/Menu.tsx +++ b/components/Sidebar/Menu.tsx @@ -24,6 +24,7 @@ const Menu = ({ isExpanded, isPinned = false, onTogglePin }: MenuProps) => { const isAgents = pathname.includes("/agents"); const isTasks = pathname.includes("/tasks"); const isFiles = pathname.includes("/files"); + const isCatalogs = pathname.includes("/catalogs"); const goToItem = (link?: string) => { if (isPrepared()) { @@ -47,6 +48,7 @@ const Menu = ({ isExpanded, isPinned = false, onTogglePin }: MenuProps) => { isAgents={isAgents} isTasks={isTasks} isFiles={isFiles} + isCatalogs={isCatalogs} onNavigate={goToItem} /> diff --git a/components/Sidebar/SecondaryNav.tsx b/components/Sidebar/SecondaryNav.tsx index a506270b3..6888c5698 100644 --- a/components/Sidebar/SecondaryNav.tsx +++ b/components/Sidebar/SecondaryNav.tsx @@ -1,12 +1,14 @@ import AgentsNavItem from "./AgentsNavItem"; import TasksNavItem from "./TasksNavItem"; import FilesNavItem from "./FilesNavItem"; +import CatalogsNavItem from "./CatalogsNavItem"; interface SecondaryNavProps { isExpanded: boolean; isAgents: boolean; isTasks: boolean; isFiles: boolean; + isCatalogs: boolean; onNavigate: (path: string) => void; } @@ -15,9 +17,11 @@ const SecondaryNav = ({ isAgents, isTasks, isFiles, + isCatalogs, onNavigate, }: SecondaryNavProps) => (
+ onNavigate("catalogs")} /> onNavigate("agents")} /> onNavigate("tasks")} /> onNavigate("files")} /> diff --git a/components/Sidebar/__tests__/SecondaryNav.test.tsx b/components/Sidebar/__tests__/SecondaryNav.test.tsx new file mode 100644 index 000000000..6a7998724 --- /dev/null +++ b/components/Sidebar/__tests__/SecondaryNav.test.tsx @@ -0,0 +1,54 @@ +// @vitest-environment jsdom +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import SecondaryNav from "@/components/Sidebar/SecondaryNav"; + +vi.mock("@/components/Agents/useAgentData", () => ({ + useAgentData: () => ({ prefetchAgents: vi.fn() }), +})); + +const renderNav = (overrides = {}) => { + const onNavigate = vi.fn(); + render( + , + ); + return { onNavigate }; +}; + +describe("SecondaryNav", () => { + // Ben Hanchett, 2026-07-29: "I'm not sure where to find the tooling in the + // site ... I guess I'm waiting on the newsletter." Catalogs is the valuation + // payoff surface and was reachable only by direct URL (chat#1912 row 3). + it("links Catalogs alongside the other tools", () => { + renderNav(); + + expect( + screen.getByRole("button", { name: /view catalogs/i }), + ).toBeDefined(); + }); + + it("navigates to the catalogs route when Catalogs is clicked", () => { + const { onNavigate } = renderNav(); + + fireEvent.click(screen.getByRole("button", { name: /view catalogs/i })); + + expect(onNavigate).toHaveBeenCalledWith("catalogs"); + }); + + it("still links the pre-existing tools", () => { + renderNav(); + + expect(screen.getByRole("button", { name: /view agents/i })).toBeDefined(); + expect(screen.getByRole("button", { name: /view tasks/i })).toBeDefined(); + expect(screen.getByRole("button", { name: /view files/i })).toBeDefined(); + }); +}); From 0f7c8f19a0f2a094b375e206c2f8c7a7425f4678 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 30 Jul 2026 13:12:13 -0500 Subject: [PATCH 2/3] fix(nav): theme-aware Catalogs icon, named props, no customer name in tests Three review findings, all validated first: - codex P2: the book icon's path hard-coded fill="black" while every sibling nav icon uses stroke="currentColor", so Catalogs rendered black on the dark sidebar. NavButton's text-foreground cannot recolor a hard-coded fill. The icon has no other consumer, so switching it to currentColor is contained. - coderabbit: named CatalogsNavItemProps interface, per the repo's own .coderabbit.yaml guideline ("Use TypeScript interfaces for props"). - codex P2: dropped a customer's real name and quoted feedback from a test comment; the reporter and quote belong on chat#1912, not in source. Co-Authored-By: Claude Opus 5 (1M context) --- components/Icon/resolver.tsx | 4 ++-- components/Sidebar/CatalogsNavItem.tsx | 12 +++++++----- components/Sidebar/__tests__/SecondaryNav.test.tsx | 6 +++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/components/Icon/resolver.tsx b/components/Icon/resolver.tsx index a33aa707c..10be27fbf 100644 --- a/components/Icon/resolver.tsx +++ b/components/Icon/resolver.tsx @@ -251,7 +251,7 @@ export const Icons = { > ), @@ -265,7 +265,7 @@ export const Icons = { > ), diff --git a/components/Sidebar/CatalogsNavItem.tsx b/components/Sidebar/CatalogsNavItem.tsx index 0b3e25746..6cdf1a6cc 100644 --- a/components/Sidebar/CatalogsNavItem.tsx +++ b/components/Sidebar/CatalogsNavItem.tsx @@ -1,14 +1,16 @@ import NavButton from "./NavButton"; +export interface CatalogsNavItemProps { + isActive: boolean; + isExpanded?: boolean; + onClick: () => void; +} + const CatalogsNavItem = ({ isActive, isExpanded, onClick, -}: { - isActive: boolean; - isExpanded?: boolean; - onClick: () => void; -}) => { +}: CatalogsNavItemProps) => { return ( { }; describe("SecondaryNav", () => { - // Ben Hanchett, 2026-07-29: "I'm not sure where to find the tooling in the - // site ... I guess I'm waiting on the newsletter." Catalogs is the valuation - // payoff surface and was reachable only by direct URL (chat#1912 row 3). + // chat#1912 row 3: Catalogs is the valuation payoff surface and was + // reachable only by direct URL, so a customer could not find it from the + // app's own navigation. Reporter and quote live on the issue, not here. it("links Catalogs alongside the other tools", () => { renderNav(); From a934e11c50fcbb601065d5082537e086398aaebf Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 30 Jul 2026 13:24:13 -0500 Subject: [PATCH 3/3] feat(nav): Catalogs uses a disc icon instead of a book MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review (@sweetmantech): a book reads as reading material; a disc reads as recordings, which is what a catalog is. Adds a `disc` entry to the icon resolver, inlined from lucide's Disc3 in the same style as the neighbouring files and clock icons. It ships stroke="currentColor", so it inherits NavButton's text-foreground and is theme-aware by construction — which also resolves the codex finding about the book glyph's hard-coded fill="black" without touching an icon this PR no longer uses. Co-Authored-By: Claude Opus 5 (1M context) --- components/Icon/resolver.tsx | 7 +++++-- components/Sidebar/CatalogsNavItem.tsx | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/Icon/resolver.tsx b/components/Icon/resolver.tsx index 10be27fbf..45900af60 100644 --- a/components/Icon/resolver.tsx +++ b/components/Icon/resolver.tsx @@ -251,7 +251,7 @@ export const Icons = { > ), @@ -265,7 +265,7 @@ export const Icons = { > ), @@ -276,6 +276,9 @@ export const Icons = { clock: () => ( ), + disc: () => ( + + ), files: () => ( ), diff --git a/components/Sidebar/CatalogsNavItem.tsx b/components/Sidebar/CatalogsNavItem.tsx index 6cdf1a6cc..028a6281c 100644 --- a/components/Sidebar/CatalogsNavItem.tsx +++ b/components/Sidebar/CatalogsNavItem.tsx @@ -13,7 +13,7 @@ const CatalogsNavItem = ({ }: CatalogsNavItemProps) => { return (