From be657f6e3c8843687afa801ab7e2ad1d3a0d8dd3 Mon Sep 17 00:00:00 2001 From: cynarAI <155274210+cynarAI@users.noreply.github.com> Date: Sun, 18 Oct 2026 22:07:53 +0200 Subject: [PATCH] feat(web): remove redundant live progress card and add adaptive project vs 1:1 agent overview sidebar --- apps/web/src/pages/Shell.tsx | 415 ++++++++++-------- apps/web/src/pages/shell/project-overview.tsx | 278 +++++++++++- .../web/src/pages/shell/side-panel-header.tsx | 50 +-- apps/web/src/pages/shell/transcript.tsx | 44 +- 4 files changed, 529 insertions(+), 258 deletions(-) diff --git a/apps/web/src/pages/Shell.tsx b/apps/web/src/pages/Shell.tsx index 627d0dc..a917c5d 100644 --- a/apps/web/src/pages/Shell.tsx +++ b/apps/web/src/pages/Shell.tsx @@ -286,7 +286,7 @@ export type NavPillar = "heute" | "team" | "projekte"; import { SortableGroup } from "../components/SortableGroup"; import { useRunActivity } from "../lib/run-activity"; import { ProjectLookDialog } from "./shell/project-look-dialog"; -import { ProjectOverview } from "./shell/project-overview"; +import { AgentOverview, ProjectOverview } from "./shell/project-overview"; import { ProjectRow } from "./shell/project-row"; export function ShellPage() { @@ -3188,8 +3188,186 @@ export function ShellPage() { +
+ {/* PROJEKTE SECTION */} +
+
+
{t`Projects`}
+

+ {t`Threads by topic · folders`} +

+
+
+ +
+
+ {projectSidebarGroups.length === 0 ? ( +
+

{t`No projects yet.`}

+ +
+ ) : null} + + g.title).map((g) => g.spaceId)} + strategy={verticalListSortingStrategy} + > + {projectSidebarGroups.map((group) => { + const projectSpace = group.key.startsWith("project-space:") + ? spaces.find((space) => space.id === group.spaceId) + : undefined; + if (projectSpace) { + const threads = projectThreads(runActivity, { + spaceId: projectSpace.id, + }); + const projectSelected = + activeNavPillar === "projekte" && + bootstrapMe?.spaceId === projectSpace.id; + // Other chats in the project show only while it is open. + const projectChats = projectSelected + ? group.bots.filter((item) => item.chat.kind !== "main") + : []; + return ( + + ({ + botId: b.id, + name: + b.name === "Chief" || b.name === "Projektkoordinator" + ? "Projektmanager" + : b.name, + color: b.color || FALLBACK_BOT_COLOR, + status: b.status, + }))} + hasMenu={ + projectSpace.canEdit === true || projectSpace.canDelete === true + } + selected={projectSelected} + unread={group.bots.some((item) => item.chat.unread)} + needsYou={threads.needsYou.length} + running={threads.running.length > 0} + onOpen={() => { + setMobileSidebarOpen(false); + void startProject(projectSpace.id); + }} + onMenu={(menu, anchor) => { + spaceMenuAnchor.current = anchor; + setSpaceMenu(menu); + }} + /> + {projectChats.length > 0 ? ( +
+ {projectChats.map((item) => ( + { + setActiveNavPillar("projekte"); + openSpaceChat(item.chat.spaceId, `/app/g/${item.chat.id}`); + }} + activeSpaceId={bootstrapMe?.spaceId} + menuAnchor={botMenuAnchor} + onMenu={setBotMenu} + /> + ))} +
+ ) : null} +
+ ); + } + const collapsed = + Boolean(group.title) && collapsedSidebarSections.has(group.key); + return ( + +
+ {group.title ? ( + void startProject(spaceId)} + onToggle={toggleSidebarSection} + sectionMenuAnchor={sectionMenuAnchor} + onSectionMenu={setSectionMenu} + spaceMenuAnchor={spaceMenuAnchor} + onSpaceMenu={setSpaceMenu} + /> + ) : null} + {!collapsed && group.startSpaceId ? ( +
+ +
+ ) : null} + {!collapsed && + group.bots.map((item) => ( + { + setActiveNavPillar("projekte"); + openSpaceChat( + item.chat.spaceId, + item.kind === "bot" + ? `/app/${item.chat.id}` + : `/app/g/${item.chat.id}`, + ); + }} + activeSpaceId={bootstrapMe?.spaceId} + menuAnchor={botMenuAnchor} + onMenu={setBotMenu} + /> + ))} +
+
+ ); + })} +
+
+
-
+
{t`Talk to your Oos`}
@@ -3319,184 +3497,6 @@ export function ShellPage() {
); })} -
- {/* PROJEKTE SECTION */} -
-
-
{t`Projects`}
-

- {t`Threads by topic · folders`} -

-
-
- -
-
- {projectSidebarGroups.length === 0 ? ( -
-

{t`No projects yet.`}

- -
- ) : null} - - g.title).map((g) => g.spaceId)} - strategy={verticalListSortingStrategy} - > - {projectSidebarGroups.map((group) => { - const projectSpace = group.key.startsWith("project-space:") - ? spaces.find((space) => space.id === group.spaceId) - : undefined; - if (projectSpace) { - const threads = projectThreads(runActivity, { - spaceId: projectSpace.id, - }); - const projectSelected = - activeNavPillar === "projekte" && - bootstrapMe?.spaceId === projectSpace.id; - // Other chats in the project show only while it is open. - const projectChats = projectSelected - ? group.bots.filter((item) => item.chat.kind !== "main") - : []; - return ( - - ({ - botId: b.id, - name: - b.name === "Chief" || b.name === "Projektkoordinator" - ? "Projektmanager" - : b.name, - color: b.color || FALLBACK_BOT_COLOR, - status: b.status, - }))} - hasMenu={ - projectSpace.canEdit === true || projectSpace.canDelete === true - } - selected={projectSelected} - unread={group.bots.some((item) => item.chat.unread)} - needsYou={threads.needsYou.length} - running={threads.running.length > 0} - onOpen={() => { - setMobileSidebarOpen(false); - void startProject(projectSpace.id); - }} - onMenu={(menu, anchor) => { - spaceMenuAnchor.current = anchor; - setSpaceMenu(menu); - }} - /> - {projectChats.length > 0 ? ( -
- {projectChats.map((item) => ( - { - setActiveNavPillar("projekte"); - openSpaceChat(item.chat.spaceId, `/app/g/${item.chat.id}`); - }} - activeSpaceId={bootstrapMe?.spaceId} - menuAnchor={botMenuAnchor} - onMenu={setBotMenu} - /> - ))} -
- ) : null} -
- ); - } - const collapsed = - Boolean(group.title) && collapsedSidebarSections.has(group.key); - return ( - -
- {group.title ? ( - void startProject(spaceId)} - onToggle={toggleSidebarSection} - sectionMenuAnchor={sectionMenuAnchor} - onSectionMenu={setSectionMenu} - spaceMenuAnchor={spaceMenuAnchor} - onSpaceMenu={setSpaceMenu} - /> - ) : null} - {!collapsed && group.startSpaceId ? ( -
- -
- ) : null} - {!collapsed && - group.bots.map((item) => ( - { - setActiveNavPillar("projekte"); - openSpaceChat( - item.chat.spaceId, - item.kind === "bot" - ? `/app/${item.chat.id}` - : `/app/g/${item.chat.id}`, - ); - }} - activeSpaceId={bootstrapMe?.spaceId} - menuAnchor={botMenuAnchor} - onMenu={setBotMenu} - /> - ))} -
-
- ); - })} -
-
-
)} @@ -4183,17 +4183,17 @@ export function ShellPage() { setCanvasTab(tab); if (tab === "browser") { setPanel("browser"); - } else if (tab === "sandbox") { - setPanel("computer"); - } else { + } else if (inProject && !dualSurface.isMobile && tab !== "sandbox") { setPanel(null); + } else { + setPanel("computer"); } }} inProject={inProject} /> ) : null} - {inProject && !panel && effectiveTab === "overview" && activeSpace ? ( + {inProject && panel !== "settings" && effectiveTab === "overview" && activeSpace ? (
setNewInquiryOpen(true)} onEditProject={() => { setLookTarget(activeSpace); }} />
+ ) : !inProject && + panel !== "settings" && + effectiveTab === "overview" && + (active ?? coordinatorBot) ? ( +
+ + void setRoutineActive(routine, next) + } + onOpenRoutine={(routine) => { + setRoutineDraft(draftFromRoutine(routine)); + setRoutineWebhookSecret(null); + setEditingRoutine(routine); + setRoutineError(null); + setPanel("routine"); + }} + onAddRoutine={ + active + ? () => { + setRoutineDraft(emptyRoutineDraft()); + setRoutineWebhookSecret(null); + setEditingRoutine(null); + setRoutineError(null); + setPanel("routine"); + } + : undefined + } + onStopRun={() => void stopRun()} + onOpenRun={(run) => + openSpaceChat( + run.spaceId, + run.groupId ? `/app/g/${run.groupId}` : `/app/${run.botId}`, + ) + } + onOpenSettings={() => setPanel("settings")} + /> +
) : panel === "browser" || (effectiveTab === "browser" && panel !== "settings") ? (
void; + onOpenBot?: (botId: string) => void; onOpenNewInquiry?: () => void; onEditProject?: () => void; }) { @@ -56,13 +61,16 @@ export function ProjectOverview({ // If not a project space and no runs, keep hidden if (!isProjectSpace && visible.length === 0) return null; - // Waiting threads are what the user came for, so they start open. - const shown = open ?? (threads.needsYou.length > 0 ? "needsYou" : null); + // Waiting or actively running threads start open automatically so progress is visible right away. + const shown = + open ?? + (threads.needsYou.length > 0 ? "needsYou" : threads.running.length > 0 ? "running" : null); const current = visible.find((section) => section.id === shown); const folderPath = space?.workspace?.path; const gitRemote = space?.workspace?.git?.remoteUrl; const goal = space?.goal; + const projectBots = space?.bots ?? []; return (
+ ) : onEditProject ? ( + ) : null} {folderPath || gitRemote ? ( @@ -139,6 +158,55 @@ export function ProjectOverview({ ) : null}
) : null} + + {projectBots.length > 0 ? ( +
+
+ Team +
+
+ {projectBots.map((member) => { + const displayName = + member.name === "Chief" || member.name === "Projektkoordinator" + ? "Projektmanager" + : member.name; + const isWorking = Boolean( + member.status && + member.status !== "idle" && + member.status !== "ready" && + member.status !== "done", + ); + return ( + + ); + })} +
+
+ ) : null}
) : null} @@ -168,7 +236,205 @@ export function ProjectOverview({
) : null} + ) : isProjectSpace ? ( +
+ No runs yet +
) : null} ); } + +/** + * 1:1 Agent overview in the side panel: agent identity & status, active and recent + * task runs for this specific agent, and its scheduled routines. + */ +export function AgentOverview({ + bot, + running = false, + routines = [], + runningRoutineId, + togglingRoutineId, + onToggleRoutineActive, + onOpenRoutine, + onAddRoutine, + onStopRun, + onOpenRun, + onOpenSettings, +}: { + bot: Bot; + running?: boolean; + routines?: readonly Routine[]; + runningRoutineId?: string | null; + togglingRoutineId?: string | null; + onToggleRoutineActive?: (routine: Routine, active: boolean) => void; + onOpenRoutine?: (routine: Routine) => void; + onAddRoutine?: () => void; + onStopRun?: () => void; + onOpenRun: (run: RunActivityRow) => void; + onOpenSettings?: () => void; +}) { + const { t } = useLingui(); + const { active: allActiveRuns, recent: allRecentRuns } = useRunActivity(); + + const botActiveRuns = allActiveRuns.filter((run) => run.botId === bot.id); + const botRecentRuns = allRecentRuns.filter((run) => run.botId === bot.id); + const needsYouCount = botActiveRuns.filter((run) => isNeedsYouStatus(run.status)).length; + const isWorking = running || botActiveRuns.length > 0; + + return ( +
+ {/* Agent Identity & Status Card */} +
+
+
+
+ +
+
+
+ + {bot.name} + + {bot.title ? ( + + {bot.title} + + ) : null} +
+
+ {needsYouCount > 0 ? ( + + Needs you + + ) : isWorking ? ( + + Working + + ) : ( + + Ready + + )} +
+
+
+ + {onOpenSettings ? ( + + ) : null} +
+ + {bot.description ? ( +

+ {bot.description} +

+ ) : null} +
+ + {/* Agent Runs & Tasks */} +
+
+ + Run history + +
+ + {botActiveRuns.length > 0 ? ( +
+
+ Now +
+ {botActiveRuns.map((run) => ( + onOpenRun(run)} /> + ))} +
+ ) : null} + + {botRecentRuns.length > 0 ? ( +
+ {botActiveRuns.length > 0 ? ( +
+ Recent +
+ ) : null} + {botRecentRuns.slice(0, 8).map((run) => ( + onOpenRun(run)} /> + ))} +
+ ) : botActiveRuns.length === 0 ? ( +
+ No runs yet +
+ ) : null} +
+ + {/* Scheduled Routines for this 1:1 Agent */} +
+
+ + Routines + + {onAddRoutine ? ( + + ) : null} +
+ + {routines.length > 0 ? ( +
+ {routines.map((routine) => ( + onToggleRoutineActive?.(routine, next)} + onOpen={() => onOpenRoutine?.(routine)} + onStop={() => onStopRun?.()} + /> + ))} +
+ ) : ( +
+ + + On a schedule + +
+ )} +
+
+ ); +} diff --git a/apps/web/src/pages/shell/side-panel-header.tsx b/apps/web/src/pages/shell/side-panel-header.tsx index f2525b7..6c3cecc 100644 --- a/apps/web/src/pages/shell/side-panel-header.tsx +++ b/apps/web/src/pages/shell/side-panel-header.tsx @@ -42,8 +42,10 @@ export function SidePanelHeader({ {panel === "settings" ? ( Settings - ) : inProject && (!panel || activeTab === "overview") ? ( + ) : inProject && activeTab === "overview" ? ( Project overview + ) : !inProject && activeTab === "overview" ? ( + Overview ) : ( Living Canvas )} @@ -112,33 +114,31 @@ export function SidePanelHeader({ {/* Surface Switcher Tabs */} - {active && panel !== "settings" && onSelectTab ? ( + {active && onSelectTab ? (
- {inProject ? ( - - ) : null} +
); })} - {(running || messages.some((m) => m.id.startsWith("progress:"))) && !encounterMode ? ( -
- -
- ) : null} {quoteDraft ? (