Skip to content

Commit d29f3a2

Browse files
fix: harden Windows frontend stability (#125)
1 parent a6ba607 commit d29f3a2

59 files changed

Lines changed: 1787 additions & 307 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</p>
2424

2525
<p align="center">
26-
<sub>Free desktop app for AI agent teams. Auto-detects Claude/Codex/OpenCode (75+ LLM providers). Use the provider access you already have - subscriptions or API keys. Not just coding agents.</sub>
26+
<sub>Free desktop app for AI agent teams. Auto-detects Claude/Codex/OpenCode (200+ models). Use the provider access you already have - subscriptions or API keys. Not just coding agents.</sub>
2727
</p>
2828

2929
<img width="1304" height="820" alt="image" src="https://github.com/user-attachments/assets/dea53a01-68b3-4c36-bcf6-e4d1ad4cdb31" />

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"@radix-ui/react-collapsible": "^1.1.12",
128128
"@radix-ui/react-context-menu": "^2.2.16",
129129
"@radix-ui/react-dialog": "^1.1.15",
130+
"@radix-ui/react-dropdown-menu": "2.1.16",
130131
"@radix-ui/react-hover-card": "^1.1.15",
131132
"@radix-ui/react-label": "^2.1.8",
132133
"@radix-ui/react-popover": "^1.1.15",

pnpm-lock.yaml

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { type ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
2+
3+
import { MessagesPanel } from '@renderer/components/team/messages/MessagesPanel';
4+
import {
5+
getTeamPendingRepliesState,
6+
setTeamPendingRepliesState,
7+
} from '@renderer/components/team/sidebar/teamSidebarUiState';
8+
import { useStore } from '@renderer/store';
9+
import {
10+
selectResolvedMembersForTeamName,
11+
selectTeamDataForName,
12+
} from '@renderer/store/slices/teamSlice';
13+
import { useShallow } from 'zustand/react/shallow';
14+
15+
interface UseGraphMessagesPanelInput {
16+
teamName: string;
17+
enabled?: boolean;
18+
mountPoint?: Element | null;
19+
onOpenMemberProfile: (memberName: string) => void;
20+
onOpenTaskDetail: (taskId: string) => void;
21+
}
22+
23+
export function useGraphMessagesPanel({
24+
teamName,
25+
enabled = true,
26+
mountPoint,
27+
onOpenMemberProfile,
28+
onOpenTaskDetail,
29+
}: UseGraphMessagesPanelInput): ReactElement {
30+
const [pendingRepliesByMember, setPendingRepliesByMember] = useState(() =>
31+
getTeamPendingRepliesState(teamName)
32+
);
33+
const { messagesPanelMode, setMessagesPanelMode, members, tasks, isTeamAlive } = useStore(
34+
useShallow((state) => {
35+
const teamData = selectTeamDataForName(state, teamName);
36+
return {
37+
messagesPanelMode: state.messagesPanelMode,
38+
setMessagesPanelMode: state.setMessagesPanelMode,
39+
members: selectResolvedMembersForTeamName(state, teamName),
40+
tasks: teamData?.tasks ?? [],
41+
isTeamAlive: teamData?.isAlive,
42+
};
43+
})
44+
);
45+
const activeMembers = useMemo(() => members.filter((member) => !member.removedAt), [members]);
46+
47+
useEffect(() => {
48+
setPendingRepliesByMember(getTeamPendingRepliesState(teamName));
49+
}, [teamName]);
50+
51+
useEffect(() => {
52+
setTeamPendingRepliesState(teamName, pendingRepliesByMember);
53+
}, [pendingRepliesByMember, teamName]);
54+
55+
const handlePendingReplyChange = useCallback(
56+
(updater: (prev: Record<string, number>) => Record<string, number>) => {
57+
setPendingRepliesByMember(updater);
58+
},
59+
[]
60+
);
61+
62+
if (
63+
!enabled ||
64+
(messagesPanelMode !== 'floating-composer' && messagesPanelMode !== 'bottom-sheet')
65+
) {
66+
return <></>;
67+
}
68+
69+
return (
70+
<MessagesPanel
71+
teamName={teamName}
72+
position={messagesPanelMode}
73+
onPositionChange={setMessagesPanelMode}
74+
mountPoint={messagesPanelMode === 'bottom-sheet' ? mountPoint : undefined}
75+
members={activeMembers}
76+
tasks={tasks}
77+
isTeamAlive={isTeamAlive}
78+
timeWindow={null}
79+
pendingRepliesByMember={pendingRepliesByMember}
80+
onPendingReplyChange={handlePendingReplyChange}
81+
onMemberClick={(member) => onOpenMemberProfile(member.name)}
82+
onTaskClick={(task) => onOpenTaskDetail(task.id)}
83+
onTaskIdClick={onOpenTaskDetail}
84+
/>
85+
);
86+
}

src/features/agent-graph/renderer/ui/TeamGraphOverlay.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
* Follows the exact ProjectEditorOverlay pattern (lazy-loaded, fixed z-50).
44
*/
55

6-
import { useCallback, useMemo } from 'react';
6+
import { useCallback, useMemo, useState } from 'react';
77

88
import { GraphView } from '@claude-teams/agent-graph';
99
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost';
1010

1111
import { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog';
12+
import { useGraphMessagesPanel } from '../hooks/useGraphMessagesPanel';
1213
import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility';
1314
import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter';
1415
import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions';
@@ -36,6 +37,7 @@ export interface TeamGraphOverlayProps {
3637
onPinAsTab?: () => void;
3738
sidebarVisible?: boolean;
3839
onToggleSidebar?: () => void;
40+
messagesPanelEnabled?: boolean;
3941
onSendMessage?: (memberName: string) => void;
4042
onOpenTaskDetail?: (taskId: string) => void;
4143
onOpenMemberProfile?: (
@@ -53,6 +55,7 @@ export const TeamGraphOverlay = ({
5355
onPinAsTab,
5456
sidebarVisible,
5557
onToggleSidebar,
58+
messagesPanelEnabled = true,
5659
onSendMessage,
5760
onOpenTaskDetail,
5861
onOpenMemberProfile,
@@ -67,8 +70,18 @@ export const TeamGraphOverlay = ({
6770
const { sidebarVisible: persistedSidebarVisible, toggleSidebarVisible } =
6871
useGraphSidebarVisibility();
6972
const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName);
73+
const [messagesPanelMountPoint, setMessagesPanelMountPoint] = useState<HTMLDivElement | null>(
74+
null
75+
);
7076
const effectiveSidebarVisible = sidebarVisible ?? persistedSidebarVisible;
7177
const handleToggleSidebar = onToggleSidebar ?? toggleSidebarVisible;
78+
const graphMessagesPanel = useGraphMessagesPanel({
79+
teamName,
80+
enabled: messagesPanelEnabled,
81+
mountPoint: messagesPanelMountPoint,
82+
onOpenMemberProfile: (memberName) => onOpenMemberProfile?.(memberName),
83+
onOpenTaskDetail: (taskId) => onOpenTaskDetail?.(taskId),
84+
});
7285

7386
// Task action dispatchers (same pattern as TeamGraphTab)
7487
const dispatchTaskAction = useCallback(
@@ -242,6 +255,13 @@ export const TeamGraphOverlay = ({
242255
/>
243256
)}
244257
/>
258+
{messagesPanelEnabled ? (
259+
<div
260+
ref={setMessagesPanelMountPoint}
261+
className="pointer-events-none absolute inset-0 z-30"
262+
/>
263+
) : null}
264+
{graphMessagesPanel}
245265
{createTaskDialog}
246266
</div>
247267
);

src/features/agent-graph/renderer/ui/TeamGraphTab.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { GraphView } from '@claude-teams/agent-graph';
99
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost';
1010

1111
import { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog';
12+
import { useGraphMessagesPanel } from '../hooks/useGraphMessagesPanel';
1213
import { useGraphSidebarVisibility } from '../hooks/useGraphSidebarVisibility';
1314
import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter';
1415
import { useTeamGraphSurfaceActions } from '../hooks/useTeamGraphSurfaceActions';
@@ -54,6 +55,9 @@ export const TeamGraphTab = ({
5455
const { openTeamPage, commitOwnerSlotDrop, commitOwnerGridOrderDrop, setLayoutMode } =
5556
useTeamGraphSurfaceActions(teamName);
5657
const [fullscreen, setFullscreen] = useState(false);
58+
const [messagesPanelMountPoint, setMessagesPanelMountPoint] = useState<HTMLDivElement | null>(
59+
null
60+
);
5761
const { sidebarVisible, toggleSidebarVisible } = useGraphSidebarVisibility();
5862
const { dialog: createTaskDialog, openCreateTaskDialog } = useGraphCreateTaskDialog(teamName);
5963

@@ -129,9 +133,16 @@ export const TeamGraphTab = ({
129133
[dispatchOpenProfile]
130134
),
131135
};
136+
const graphMessagesPanel = useGraphMessagesPanel({
137+
teamName,
138+
enabled: isActive && isPaneFocused && !fullscreen,
139+
mountPoint: messagesPanelMountPoint,
140+
onOpenMemberProfile: dispatchOpenProfile,
141+
onOpenTaskDetail: dispatchOpenTask,
142+
});
132143

133144
return (
134-
<div className="flex size-full overflow-hidden" style={{ background: '#050510' }}>
145+
<div className="relative flex size-full overflow-hidden" style={{ background: '#050510' }}>
135146
{sidebarVisible ? (
136147
<TeamSidebarHost
137148
teamName={teamName}
@@ -262,6 +273,13 @@ export const TeamGraphTab = ({
262273
)}
263274
/>
264275
</div>
276+
{isActive && isPaneFocused && !fullscreen ? (
277+
<div
278+
ref={setMessagesPanelMountPoint}
279+
className="pointer-events-none absolute inset-0 z-30"
280+
/>
281+
) : null}
282+
{graphMessagesPanel}
265283
{createTaskDialog}
266284
{fullscreen && (
267285
<Suspense fallback={null}>
@@ -273,6 +291,7 @@ export const TeamGraphTab = ({
273291
onSendMessage={dispatchSendMessage}
274292
onOpenTaskDetail={dispatchOpenTask}
275293
onOpenMemberProfile={dispatchOpenProfile}
294+
messagesPanelEnabled={isActive && isPaneFocused}
276295
/>
277296
</Suspense>
278297
)}

0 commit comments

Comments
 (0)