diff --git a/web/src/app/components/ui/AgentToolsPanel.tsx b/web/src/app/components/ui/AgentToolsPanel.tsx index 56fb1171b..1e249c688 100644 --- a/web/src/app/components/ui/AgentToolsPanel.tsx +++ b/web/src/app/components/ui/AgentToolsPanel.tsx @@ -319,7 +319,7 @@ export function AgentToolsPanel({
- - - - + {cells.map((cell) => ( + + ))}
); diff --git a/web/src/app/features/chat/components/ChatAgentPanel.tsx b/web/src/app/features/chat/components/ChatAgentPanel.tsx index a148ffb32..395dbca40 100644 --- a/web/src/app/features/chat/components/ChatAgentPanel.tsx +++ b/web/src/app/features/chat/components/ChatAgentPanel.tsx @@ -126,7 +126,7 @@ export function ChatAgentPanel({ onChange={setQuery} /> - - {shell.pickerId === "gsv" ? ( -
- shell.setPickerId(null)} - onFiles={() => openShellSurface("files")} - onRepositories={() => openShellSurface("repositories")} - onLibrary={() => openShellSurface("library")} - onTerminal={() => openShellSurface("terminal")} - onSettings={() => openShellSurface("settings")} - /> -
- ) : null} - - - ) : null} @@ -624,9 +594,9 @@ export function GsvShell({ onResizeStart={shell.startChatDrag} onToggleOpen={() => shell.setChatOpen((value) => !value)} onToggleMax={shell.toggleChatMax} - onOpenCrew={() => openSettingsRoute("crew")} - onOpenModels={() => openSettingsRoute("models")} - onOpenTasks={() => openSettingsRoute("tasks")} + onOpenCrew={() => openShellSurface("crew")} + onOpenModels={() => openShellSurface("models")} + onOpenTasks={() => openShellSurface("runtime")} onProcessStarted={selectStartedChatProcess} onSelectConversation={setSelectedChatConversationId} title={activeChatProcess?.title ?? "Chat"} diff --git a/web/src/app/features/gsv-shell/desktop/GsvDesktop.tsx b/web/src/app/features/gsv-shell/desktop/GsvDesktop.tsx index 3cd12382a..49065a601 100644 --- a/web/src/app/features/gsv-shell/desktop/GsvDesktop.tsx +++ b/web/src/app/features/gsv-shell/desktop/GsvDesktop.tsx @@ -3,9 +3,10 @@ import { useEffect, useRef, useState } from "preact/hooks"; import { AddAction } from "../../../components/ui/AddAction"; import { GsvMark } from "../../../components/ui/GsvMark"; import { DesktopHint } from "./DesktopHint"; -import { IconMenu } from "../../../components/ui/IconMenu"; +import { Icon } from "../../../components/ui/Icon"; import { ObjectCard } from "../../../components/ui/ObjectCard"; import { StatusDot } from "../../../components/ui/StatusDot"; +import { Tag } from "../../../components/ui/Tag"; import { Tile } from "../../../components/ui/Tile"; import { type DesktopChildObject, @@ -17,18 +18,17 @@ import { export type DesktopInventoryState = "ready" | "loading" | "offline" | "error"; -const DESKTOP_HINT = ["> CLICK A NODE TO EXPLORE", "> CLICK GSV FOR CONTROLS"]; -const DESKTOP_HINT_MIN = "CLICK A NODE TO EXPLORE · CLICK GSV FOR CONTROLS"; +const DESKTOP_HINT = ["> CLICK A NODE TO EXPLORE", "> CLICK GSV FOR SHIP OVERVIEW"]; +const DESKTOP_HINT_MIN = "CLICK A NODE TO EXPLORE · CLICK GSV FOR SHIP OVERVIEW"; type GsvDesktopProps = { desktopObjects: readonly DesktopObject[]; + /** Crew agents (machine accounts) — shown in the HUD beside the machine count. */ + agentCount: number; inventoryMessage: string; inventoryState: DesktopInventoryState; selectedObjectId: DesktopObjectId | null; - gsvOpen: boolean; onSelectObject: (id: DesktopObjectId | null) => void; - onToggleGsv: () => void; - onCloseGsv: () => void; onCreateObject: (id: DesktopObjectId) => void; onOpenObject: (child: DesktopChildObject) => void; onOpenSurface: (surface: ShellSurfaceId) => void; @@ -87,33 +87,33 @@ function addObjectLabel(id: DesktopObjectId): string { return "CONNECT NEW MACHINE"; } if (id === "integrations") { - return "NEW INTEGRATION"; + return "CONNECT NEW INTEGRATION"; } - return "NEW APPLICATION"; + return "CONNECT NEW APPLICATION"; } -function inventoryTitle(state: DesktopInventoryState, totalObjects: number): string { - if (state === "loading") { - return "GSV · LOADING"; - } +/** HUD title while the live inventory is not ready (ready shows counts). */ +function inventoryTitle(state: DesktopInventoryState): string { if (state === "offline") { return "GSV · OFFLINE"; } if (state === "error") { return "GSV · ERROR"; } - return `GSV · ${totalObjects} OBJECTS`; + return "GSV · LOADING"; +} + +function countLine(count: number, noun: string): string { + return `${count} ${noun}${count === 1 ? "" : "S"}`; } export function GsvDesktop({ desktopObjects, + agentCount, inventoryMessage, inventoryState, selectedObjectId, - gsvOpen, onSelectObject, - onToggleGsv, - onCloseGsv, onCreateObject, onOpenObject, onOpenSurface, @@ -137,42 +137,15 @@ export function GsvDesktop({ ? desktopObjects.find((object) => object.id === activeObjectId) ?? null : null; - // Hovering the GSV mark previews the controls; clicking persists them. A short - // grace delay lets the pointer travel from the mark into the popover. - const [gsvHovered, setGsvHovered] = useState(false); - const gsvHoverTimer = useRef | undefined>(undefined); - const openGsvHover = () => { - if (gsvHoverTimer.current) clearTimeout(gsvHoverTimer.current); - setGsvHovered(true); - }; - const closeGsvHover = () => { - if (gsvHoverTimer.current) clearTimeout(gsvHoverTimer.current); - gsvHoverTimer.current = setTimeout(() => setGsvHovered(false), 140); - }; - const closeGsvControls = () => { - if (gsvHoverTimer.current) clearTimeout(gsvHoverTimer.current); - setGsvHovered(false); - // Idempotent close — not the functional toggle. Outside-click also calls - // onSelectObject(null), which already clears gsvOpen in the parent; a toggle - // here would flip it back open within the same batched update. - if (gsvOpen) onCloseGsv(); - }; - useEffect(() => () => { - if (gsvHoverTimer.current) clearTimeout(gsvHoverTimer.current); - }, []); - const gsvActive = gsvOpen || gsvHovered; - const branchCount = Math.max(desktopObjects.length, 1); - const totalObjects = desktopObjects.reduce((sum, object) => sum + object.children.length, 0); - const desktopStateClass = `${selectedObject ? " has-selected-object" : ""}${gsvOpen ? " has-gsv-open" : ""}`; + const machineCount = desktopObjects.find((object) => object.id === "machines")?.children.length ?? 0; + const desktopStateClass = selectedObject ? " has-selected-object" : ""; const inventoryReady = inventoryState === "ready"; const hudStatus = selectedObject ? selectedObject.statusLabel - : gsvOpen - ? "GSV / CONTROL" - : inventoryReady - ? "SYSTEM MAP" - : inventoryState.toUpperCase(); + : inventoryReady + ? "SYSTEM MAP" + : inventoryState.toUpperCase(); // When a node is selected, settle the view so its objects are readable: scroll // up to centre the strip, but never past the point where the tiles row sits at @@ -253,7 +226,6 @@ export function GsvDesktop({ aria-label="GSV desktop" onClick={() => { onSelectObject(null); - closeGsvControls(); }} >