From a8a3056a6bb1c1c537dcb226f388210f6551e928 Mon Sep 17 00:00:00 2001 From: Khushi Diwan Date: Sat, 20 Jun 2026 14:22:05 +0530 Subject: [PATCH] feat(frontend): polish board chrome and task actions Polish the board and task action chrome. - move board actions into the board header and remove empty board topbar space - refine session topbar actions with notification, Kill, and Orchestrator controls - add pointer cursors for clickable controls and clean sidebar child-session styling Verified with frontend typecheck and targeted renderer tests. --- .../renderer/components/DashboardSubhead.tsx | 25 ++++-- .../src/renderer/components/SessionsBoard.tsx | 80 ++++++++++++++++++- .../src/renderer/components/ShellTopbar.tsx | 77 +++++++----------- frontend/src/renderer/components/Sidebar.tsx | 26 ++++-- frontend/src/renderer/routes/_shell.tsx | 7 +- frontend/src/renderer/styles.css | 69 ++++++++++++++-- 6 files changed, 212 insertions(+), 72 deletions(-) diff --git a/frontend/src/renderer/components/DashboardSubhead.tsx b/frontend/src/renderer/components/DashboardSubhead.tsx index d7329221a..4b0ddc7db 100644 --- a/frontend/src/renderer/components/DashboardSubhead.tsx +++ b/frontend/src/renderer/components/DashboardSubhead.tsx @@ -1,11 +1,26 @@ +import type { ReactNode } from "react"; + // The board subhead (mc-board .dashboard-main__subhead): a 21px bold title with // a muted one-line subtitle, optionally a trailing count. -export function DashboardSubhead({ title, subtitle, count }: { title: string; subtitle: string; count?: number }) { +export function DashboardSubhead({ + title, + subtitle, + count, + actions, +}: { + title: string; + subtitle: string; + count?: number; + actions?: ReactNode; +}) { return ( -
-

{title}

- {typeof count === "number" && {count}} - {subtitle} +
+
+

{title}

+ {typeof count === "number" && {count}} + {subtitle} +
+ {actions ?
{actions}
: null}
); } diff --git a/frontend/src/renderer/components/SessionsBoard.tsx b/frontend/src/renderer/components/SessionsBoard.tsx index bc22d8441..9de6b4344 100644 --- a/frontend/src/renderer/components/SessionsBoard.tsx +++ b/frontend/src/renderer/components/SessionsBoard.tsx @@ -1,8 +1,13 @@ import { useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "@tanstack/react-router"; +import { Plus } from "lucide-react"; import { type AttentionZone, type WorkspaceSession, attentionZone, workerSessions } from "../types/workspace"; -import { useWorkspaceQuery } from "../hooks/useWorkspaceQuery"; +import { useWorkspaceQuery, workspaceQueryKey } from "../hooks/useWorkspaceQuery"; import { DashboardSubhead } from "./DashboardSubhead"; +import { OrchestratorIcon } from "./icons"; +import { NewTaskDialog } from "./NewTaskDialog"; +import { spawnOrchestrator } from "../lib/spawn-orchestrator"; import { cn } from "../lib/utils"; type SessionsBoardProps = { @@ -58,10 +63,16 @@ const COLUMNS: Column[] = [ export function SessionsBoard({ projectId }: SessionsBoardProps) { const navigate = useNavigate(); + const queryClient = useQueryClient(); const workspaceQuery = useWorkspaceQuery(); const all = workspaceQuery.data ?? []; const workspaces = projectId ? all.filter((w) => w.id === projectId) : all; const sessions = workspaces.flatMap((w) => workerSessions(w.sessions)); + const orchestrator = projectId + ? workspaces[0]?.sessions.find((session) => session.kind === "orchestrator" && session.status !== "terminated") + : undefined; + const [isNewTaskOpen, setIsNewTaskOpen] = useState(false); + const [isSpawning, setIsSpawning] = useState(false); const byZone = new Map(); for (const session of sessions) { @@ -79,9 +90,68 @@ export function SessionsBoard({ projectId }: SessionsBoardProps) { params: { projectId: session.workspaceId, sessionId: session.id }, }); + const openOrchestrator = async () => { + if (!projectId) return; + if (orchestrator) { + void navigate({ + to: "/projects/$projectId/sessions/$sessionId", + params: { projectId, sessionId: orchestrator.id }, + }); + return; + } + setIsSpawning(true); + try { + const sessionId = await spawnOrchestrator(projectId); + await queryClient.invalidateQueries({ queryKey: workspaceQueryKey }); + void navigate({ + to: "/projects/$projectId/sessions/$sessionId", + params: { projectId, sessionId }, + }); + } finally { + setIsSpawning(false); + } + }; + + const handleTaskCreated = async (sessionId: string) => { + if (!projectId) return; + await queryClient.invalidateQueries({ queryKey: workspaceQueryKey }); + void navigate({ + to: "/projects/$projectId/sessions/$sessionId", + params: { projectId, sessionId }, + }); + }; + + const actions = projectId ? ( + <> + + + + ) : undefined; + return (
- +
{workspaceQuery.isError ? ( @@ -139,6 +209,12 @@ export function SessionsBoard({ projectId }: SessionsBoardProps) { )}
)} + void handleTaskCreated(sessionId)} + onOpenChange={setIsNewTaskOpen} + />
); } diff --git a/frontend/src/renderer/components/ShellTopbar.tsx b/frontend/src/renderer/components/ShellTopbar.tsx index 1a638382e..ff8285de2 100644 --- a/frontend/src/renderer/components/ShellTopbar.tsx +++ b/frontend/src/renderer/components/ShellTopbar.tsx @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate, useParams } from "@tanstack/react-router"; -import { GitBranch, LayoutDashboard, PanelRightClose, PanelRightOpen, Plus, Square } from "lucide-react"; +import { Bell, GitBranch, LayoutDashboard, PanelRightClose, PanelRightOpen, Plus, Square, Trash2 } from "lucide-react"; import { useState } from "react"; import { findProjectOrchestrator, @@ -67,6 +67,7 @@ export function ShellTopbar() { // removed, or data still loading) shows an empty crumb — never the raw // route slug. "agent-orchestrator" is the root-board crumb only. const projectId = session?.workspaceId ?? params.projectId; + const isProjectBoardRoute = !isSessionRoute && Boolean(projectId); const project = projectId ? all.find((workspace) => workspace.id === projectId) : undefined; const projectLabel = project?.name ?? session?.workspaceName ?? (projectId ? "" : "agent-orchestrator"); const orchestrator = projectId ? findProjectOrchestrator(all, projectId) : undefined; @@ -138,7 +139,7 @@ export function ShellTopbar() {
{session ? : null} - ) : ( + ) : isProjectBoardRoute ? null : (
{projectLabel}
@@ -174,21 +175,24 @@ export function ShellTopbar() { ) : ( + + )} + {/* Kill control sits beside the orchestrator link for active workers — + moved here from the inspector's Summary "Danger zone". */} + {!isOrchestrator && session && sessionIsActive(session) ? : null} + {!isOrchestrator && ( )} - {/* Kill control sits beside the orchestrator link for active workers — - moved here from the inspector's Summary "Danger zone". */} - {!isOrchestrator && session && sessionIsActive(session) ? : null} {/* Inspector collapse (worker sessions only — orchestrators have no rail). */} {!isOrchestrator && ( - {orchestrator ? ( - - ) : ( - - )} - ) : null} +