diff --git a/apps/web/src/app.css b/apps/web/src/app.css index ce9dd0caf..f43b1bd38 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -1181,8 +1181,12 @@ select:disabled, justify-content: space-between; padding-right: 0.35rem; } -.shell-sidebar-brand-row .shell-sidebar-brand-mark { - width: auto; +.shell-sidebar-wordmark { + flex: 1; + padding-left: 0.5rem; + font-weight: 800; + font-size: 15px; + letter-spacing: -0.01em; } .shell-sidebar-account-btn { diff --git a/apps/web/src/shell/brand-mark.tsx b/apps/web/src/shell/brand-mark.tsx deleted file mode 100644 index 61024cda1..000000000 --- a/apps/web/src/shell/brand-mark.tsx +++ /dev/null @@ -1,13 +0,0 @@ -// Static, not the animated dither background: its grid needs real screen -// real estate this size doesn't have. - -import { CorbitsMark } from "@corbits/react-ui"; - -/** Small and quiet — the mark is the brand moment now, not an orange rail. */ -export function SidebarBrandMark() { - return ( - - ); -} diff --git a/apps/web/src/shell/sidebar.tsx b/apps/web/src/shell/sidebar.tsx index 6cb787448..04db4b497 100644 --- a/apps/web/src/shell/sidebar.tsx +++ b/apps/web/src/shell/sidebar.tsx @@ -1,23 +1,11 @@ // No bench switcher: a multi-bench install still resolves via the command // palette's hidden "Switch workbench" action. -import { - Button, - Menu, - MenuContent, - MenuItem, - MenuTrigger, - SidebarPanel, - SidebarPanelBody, -} from "@corbits/react-ui"; -import { Plus } from "@/lib/icons"; -import { useState } from "react"; +import { Button, SidebarPanel, SidebarPanelBody } from "@corbits/react-ui"; +import { MagnifyingGlass, Plus } from "@/lib/icons"; -import { useBench } from "../bench-context"; -import { CreateAgentPanel } from "../pages/create-agent-panel"; -import { AGENTS_PATH_PREFIX } from "../path-ids"; +import { openCommandPalette } from "../command-palette-open-store"; import { NEW_WORKBENCH_PATH } from "../routes"; -import { SidebarBrandMark } from "./brand-mark"; import { SidebarFooter } from "./sidebar-footer"; import { WorkbenchList } from "./workbench-list"; @@ -28,61 +16,35 @@ export function Sidebar({ readonly path: string; readonly onNavigate: (to: string) => void; }) { - const { selectedTenantId } = useBench(); - const [createAgentOpen, setCreateAgentOpen] = useState(false); - return ( - - {/* Owner's shape: logo with "+" on the first row, the search box - (inside the list) below, then Agents and Channels. No - header icon cluster — search is the box. The "+" now opens a - dropdown (New Agent / New Workbench) instead of - jumping straight to New chat. */} +
- - - - - - - setCreateAgentOpen(true)} - > - New Agent - - onNavigate(NEW_WORKBENCH_PATH)}>New Workbench - - + Workbench + +
- {/* Agents and Channels labels render inside the list, below its - search box (owner's order: logo · search · sections · rows). */} - {selectedTenantId === null ? null : ( - onNavigate(AGENTS_PATH_PREFIX)} - /> - )} -
); diff --git a/apps/web/src/shell/workbench-list.tsx b/apps/web/src/shell/workbench-list.tsx index 3eb45cb09..35b2d4cfb 100644 --- a/apps/web/src/shell/workbench-list.tsx +++ b/apps/web/src/shell/workbench-list.tsx @@ -1,21 +1,20 @@ // One section and nothing else: Workbenches. Agent membership is a // workbench concept. -import { EmptyState, Input, Skeleton } from "@corbits/react-ui"; -import { Hash, MagnifyingGlass } from "@/lib/icons"; -import { useState } from "react"; +import { EmptyState, Skeleton } from "@corbits/react-ui"; +import { useQuery } from "@tanstack/react-query"; +import { WorkbenchAvatar } from "@/chat/avatar"; +import { listChatAgents } from "@/chat/threads-api"; +import { Hash } from "@/lib/icons"; import { useBench } from "../bench-context"; +import { tenantKeys } from "../query-client"; import { workbenchIdFromPath, workbenchPath } from "../workbench-path"; import type { HubTenant } from "../needs-converge"; import { useSidebarSections } from "./sidebar-sections"; export const SIDEBAR_EMPTY_COPY = "No workbenches yet"; -function matches(text: string, needle: string): boolean { - return text.toLowerCase().includes(needle); -} - function SectionLabel({ children }: { readonly children: string }) { return
{children}
; } @@ -60,7 +59,6 @@ export function WorkbenchList({ }) { const { selectedTenantId } = useBench(); const sections = useSidebarSections(selectedTenantId); - const [query, setQuery] = useState(""); if (sections.kind === "loading") { return ( @@ -81,41 +79,71 @@ export function WorkbenchList({ ); } - const needle = query.trim().toLowerCase(); - const workbenches = sections.workbenches.filter( - (tenant) => needle === "" || matches(tenant.name, needle), - ); const activeWorkbenchId = workbenchIdFromPath(path); + const { workbenches } = sections; return ( -
- - - {workbenches.length === 0 ? ( -

{SIDEBAR_EMPTY_COPY}

- ) : null} +
+
+ Workbenches + {workbenches.length === 0 ? ( +

{SIDEBAR_EMPTY_COPY}

+ ) : null} + {workbenches.map((tenant) => ( + onNavigate(workbenchPath(tenant.id))} + /> + ))} +
+ +
+ ); +} - {workbenches.length === 0 ? null : ( -
- Workbenches - {workbenches.map((tenant) => ( - onNavigate(workbenchPath(tenant.id))} - /> - ))} -
- )} +// No client-side in-flight-turn state is queryable here, so every worker +// renders idle. +function WorkerGroup({ + path, + onNavigate, +}: { + readonly path: string; + readonly onNavigate: (to: string) => void; +}) { + const { selectedTenantId } = useBench(); + const agents = useQuery({ + queryKey: tenantKeys.agents(selectedTenantId ?? "none"), + enabled: selectedTenantId !== null, + queryFn: () => listChatAgents(selectedTenantId as string), + }); + const workers = agents.data ?? []; + if (workers.length === 0) return null; + return ( +
+ Workers + {workers.map((agent) => { + const to = `/workers/${agent.id}`; + const active = path === to; + return ( + + ); + })}
); }