fix: move board notifications
- Remove the board-level shell title bar - Move notifications into the board header - Keep sidebar offset only on session routes
This commit is contained in:
parent
78cbe22928
commit
62bee4f568
|
|
@ -6,6 +6,7 @@ import { type AttentionZone, type WorkspaceSession, attentionZone, workerSession
|
|||
import { useSessionScmSummary, type SessionPRSummary } from "../hooks/useSessionScmSummary";
|
||||
import { useWorkspaceQuery, workspaceQueryKey } from "../hooks/useWorkspaceQuery";
|
||||
import { DashboardSubhead } from "./DashboardSubhead";
|
||||
import { NotificationCenter } from "./NotificationCenter";
|
||||
import { OrchestratorIcon } from "./icons";
|
||||
import { NewTaskDialog } from "./NewTaskDialog";
|
||||
import { spawnOrchestrator } from "../lib/spawn-orchestrator";
|
||||
|
|
@ -126,6 +127,7 @@ export function SessionsBoard({ projectId }: SessionsBoardProps) {
|
|||
|
||||
const actions = projectId ? (
|
||||
<>
|
||||
<NotificationCenter />
|
||||
<button
|
||||
aria-label="New task"
|
||||
className="dashboard-app-header__accent-btn"
|
||||
|
|
@ -146,7 +148,9 @@ export function SessionsBoard({ projectId }: SessionsBoardProps) {
|
|||
{isSpawning ? "Spawning..." : orchestrator ? "Orchestrator" : "Spawn Orchestrator"}
|
||||
</button>
|
||||
</>
|
||||
) : undefined;
|
||||
) : (
|
||||
<NotificationCenter />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col bg-background text-foreground">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { GitBranch, LayoutDashboard, PanelRightClose, PanelRightOpen, Plus, Square, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { NotificationCenter } from "./NotificationCenter";
|
||||
import {
|
||||
findProjectOrchestrator,
|
||||
isOrchestratorSession,
|
||||
|
|
@ -70,11 +69,13 @@ 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 projectLabel = session?.workspaceName ?? "";
|
||||
const orchestrator = projectId ? findProjectOrchestrator(all, projectId) : undefined;
|
||||
|
||||
if (!isSessionRoute) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const openBoard = () =>
|
||||
projectId ? void navigate({ to: "/projects/$projectId", params: { projectId } }) : void navigate({ to: "/" });
|
||||
|
||||
|
|
@ -132,7 +133,7 @@ export function ShellTopbar() {
|
|||
return (
|
||||
<header className={cn("dashboard-app-header", isMac && "is-under-titlebar-nav")} style={dragStyle}>
|
||||
<div className="session-topbar__lead">
|
||||
{isSessionRoute && isOrchestrator ? (
|
||||
{isOrchestrator ? (
|
||||
<div className="topbar-project-pills-group">
|
||||
<div className="topbar-project-line">
|
||||
<span className="dashboard-app-header__project">{projectLabel}</span>
|
||||
|
|
@ -145,7 +146,7 @@ export function ShellTopbar() {
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : isSessionRoute ? (
|
||||
) : (
|
||||
<div className="session-topbar__identity">
|
||||
<div className="session-topbar__branch">
|
||||
<GitBranch className="h-3 w-3 shrink-0" aria-hidden="true" />
|
||||
|
|
@ -153,79 +154,70 @@ export function ShellTopbar() {
|
|||
</div>
|
||||
{session ? <SessionStatusPill session={session} /> : null}
|
||||
</div>
|
||||
) : isProjectBoardRoute ? null : (
|
||||
<div className="topbar-project-line">
|
||||
<span className="dashboard-app-header__project">{projectLabel}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="dashboard-app-header__spacer" />
|
||||
|
||||
<div className="dashboard-app-header__actions">
|
||||
<NotificationCenter style={noDragStyle} />
|
||||
{isSessionRoute ? (
|
||||
{isOrchestrator ? (
|
||||
<>
|
||||
{isOrchestrator ? (
|
||||
<>
|
||||
<button
|
||||
aria-label="New task"
|
||||
className="dashboard-app-header__primary-btn"
|
||||
onClick={openNewTask}
|
||||
style={noDragStyle}
|
||||
type="button"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
New task
|
||||
</button>
|
||||
<button
|
||||
aria-label="Open Kanban"
|
||||
className="dashboard-app-header__accent-btn"
|
||||
onClick={openBoard}
|
||||
style={noDragStyle}
|
||||
type="button"
|
||||
>
|
||||
<LayoutDashboard className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
Kanban
|
||||
</button>
|
||||
</>
|
||||
) : null}
|
||||
{/* Kill control sits beside the orchestrator link for active workers —
|
||||
moved here from the inspector's Summary "Danger zone". */}
|
||||
{!isOrchestrator && session && sessionIsActive(session) ? <TopbarKillButton session={session} /> : null}
|
||||
{!isOrchestrator && (
|
||||
<button
|
||||
aria-label="Open orchestrator"
|
||||
className="dashboard-app-header__primary-btn dashboard-app-header__primary-btn--compact"
|
||||
disabled={isSpawning}
|
||||
onClick={() => void openOrchestrator()}
|
||||
style={noDragStyle}
|
||||
type="button"
|
||||
>
|
||||
<OrchestratorIcon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{isSpawning ? "Spawning…" : "Orchestrator"}
|
||||
</button>
|
||||
)}
|
||||
{/* Inspector collapse (worker sessions only — orchestrators have no rail). */}
|
||||
{!isOrchestrator && (
|
||||
<button
|
||||
aria-label={isInspectorOpen ? "Close inspector panel" : "Open inspector panel"}
|
||||
aria-pressed={isInspectorOpen}
|
||||
className="dashboard-app-header__icon-btn"
|
||||
onClick={toggleInspector}
|
||||
style={noDragStyle}
|
||||
title={`${isInspectorOpen ? "Close" : "Open"} inspector · ⌘⇧B`}
|
||||
type="button"
|
||||
>
|
||||
{isInspectorOpen ? (
|
||||
<PanelRightClose className="h-[15px] w-[15px]" aria-hidden="true" />
|
||||
) : (
|
||||
<PanelRightOpen className="h-[15px] w-[15px]" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
aria-label="New task"
|
||||
className="dashboard-app-header__primary-btn"
|
||||
onClick={openNewTask}
|
||||
style={noDragStyle}
|
||||
type="button"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
New task
|
||||
</button>
|
||||
<button
|
||||
aria-label="Open Kanban"
|
||||
className="dashboard-app-header__accent-btn"
|
||||
onClick={openBoard}
|
||||
style={noDragStyle}
|
||||
type="button"
|
||||
>
|
||||
<LayoutDashboard className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
Kanban
|
||||
</button>
|
||||
</>
|
||||
) : null}
|
||||
{/* Kill control sits beside the orchestrator link for active workers —
|
||||
moved here from the inspector's Summary "Danger zone". */}
|
||||
{!isOrchestrator && session && sessionIsActive(session) ? <TopbarKillButton session={session} /> : null}
|
||||
{!isOrchestrator && (
|
||||
<button
|
||||
aria-label="Open orchestrator"
|
||||
className="dashboard-app-header__primary-btn dashboard-app-header__primary-btn--compact"
|
||||
disabled={isSpawning}
|
||||
onClick={() => void openOrchestrator()}
|
||||
style={noDragStyle}
|
||||
type="button"
|
||||
>
|
||||
<OrchestratorIcon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{isSpawning ? "Spawning…" : "Orchestrator"}
|
||||
</button>
|
||||
)}
|
||||
{/* Inspector collapse (worker sessions only — orchestrators have no rail). */}
|
||||
{!isOrchestrator && (
|
||||
<button
|
||||
aria-label={isInspectorOpen ? "Close inspector panel" : "Open inspector panel"}
|
||||
aria-pressed={isInspectorOpen}
|
||||
className="dashboard-app-header__icon-btn"
|
||||
onClick={toggleInspector}
|
||||
style={noDragStyle}
|
||||
title={`${isInspectorOpen ? "Close" : "Open"} inspector · ⌘⇧B`}
|
||||
type="button"
|
||||
>
|
||||
{isInspectorOpen ? (
|
||||
<PanelRightClose className="h-[15px] w-[15px]" aria-hidden="true" />
|
||||
) : (
|
||||
<PanelRightOpen className="h-[15px] w-[15px]" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<NewTaskDialog
|
||||
open={isNewTaskOpen}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createFileRoute, Outlet, useNavigate } from "@tanstack/react-router";
|
||||
import { createFileRoute, Outlet, useMatchRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { type CSSProperties, useCallback, useEffect } from "react";
|
||||
import { ShellTopbar } from "../components/ShellTopbar";
|
||||
|
|
@ -40,11 +40,14 @@ function errorMessage(error: unknown) {
|
|||
// instead of Zustand. The daemon-status effect runs here exactly once.
|
||||
function ShellLayout() {
|
||||
const navigate = useNavigate();
|
||||
const matchRoute = useMatchRoute();
|
||||
const queryClient = useQueryClient();
|
||||
const workspaceQuery = useWorkspaceQuery();
|
||||
const workspaces = workspaceQuery.data ?? [];
|
||||
const daemonStatus = useDaemonStatus(queryClient);
|
||||
const { theme, setTheme, isSidebarOpen, toggleSidebar } = useUiStore();
|
||||
const isSessionRoute = Boolean(matchRoute({ to: "/projects/$projectId/sessions/$sessionId", fuzzy: true })) ||
|
||||
Boolean(matchRoute({ to: "/sessions/$sessionId", fuzzy: true }));
|
||||
|
||||
const updateWorkspaces = useCallback(
|
||||
(updater: (workspaces: WorkspaceSummary[]) => WorkspaceSummary[]) => {
|
||||
|
|
@ -185,7 +188,7 @@ function ShellLayout() {
|
|||
>
|
||||
<Sidebar
|
||||
daemonStatus={daemonStatus}
|
||||
underTopbar
|
||||
underTopbar={isSessionRoute}
|
||||
onCreateProject={createProject}
|
||||
onRemoveProject={removeProject}
|
||||
workspaceError={workspaceQuery.isError ? errorMessage(workspaceQuery.error) : undefined}
|
||||
|
|
|
|||
Loading…
Reference in New Issue