diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index 28d6cd70..0c6dbbf6 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -114,7 +114,6 @@ import type { TeamCreateRequest, TeamCreateResponse, TeamData, - TeamGetDataOptions, TeamLaunchRequest, TeamLaunchResponse, TeamMessageNotificationData, @@ -378,23 +377,17 @@ async function handleListTeams(_event: IpcMainInvokeEvent): Promise> { const validated = validateTeamName(teamName); if (!validated.valid) { return { success: false, error: validated.error ?? 'Invalid teamName' }; } const tn = validated.value!; - const includeMessages = - !options || - typeof options !== 'object' || - !('includeMessages' in options) || - (options as TeamGetDataOptions).includeMessages !== false; const startedAt = Date.now(); let data: TeamData; try { - data = await getTeamDataService().getTeamData(tn, { includeMessages }); + data = await getTeamDataService().getTeamData(tn); } catch (error) { const message = error instanceof Error ? error.message : String(error); if ( @@ -416,10 +409,6 @@ async function handleGetData( const displayName = data.config.name || tn; const projectPath = data.config.projectPath; - if (!includeMessages) { - return { success: true, data: { ...data, isAlive } }; - } - const live = provisioning.getLiveLeadProcessMessages(tn); if (live.length === 0) { checkRateLimitMessages(data.messages, tn, displayName, projectPath); diff --git a/src/main/services/team/TeamDataService.ts b/src/main/services/team/TeamDataService.ts index fa6ae4c9..ab9e2c29 100644 --- a/src/main/services/team/TeamDataService.ts +++ b/src/main/services/team/TeamDataService.ts @@ -53,7 +53,6 @@ import type { TeamConfig, TeamCreateConfigRequest, TeamData, - TeamGetDataOptions, TeamMember, TeamProcess, TeamSummary, @@ -248,9 +247,8 @@ export class TeamDataService { await fs.promises.rm(tasksDir, { recursive: true, force: true }); } - async getTeamData(teamName: string, options?: TeamGetDataOptions): Promise { + async getTeamData(teamName: string): Promise { const startedAt = Date.now(); - const includeMessages = options?.includeMessages !== false; const marks: Record = {}; const mark = (label: string): void => { marks[label] = Date.now(); @@ -285,38 +283,32 @@ export class TeamDataService { mark('inboxNames'); let messages: InboxMessage[] = []; - if (includeMessages) { - try { - messages = await this.inboxReader.getMessages(teamName); - } catch { - warnings.push('Messages failed to load'); - } + try { + messages = await this.inboxReader.getMessages(teamName); + } catch { + warnings.push('Messages failed to load'); } mark('messages'); let leadTexts: InboxMessage[] = []; - if (includeMessages) { - try { - leadTexts = await this.extractLeadSessionTexts(config); - if (leadTexts.length > 0) { - messages = [...messages, ...leadTexts]; - } - } catch { - warnings.push('Lead session texts failed to load'); + try { + leadTexts = await this.extractLeadSessionTexts(config); + if (leadTexts.length > 0) { + messages = [...messages, ...leadTexts]; } + } catch { + warnings.push('Lead session texts failed to load'); } mark('leadTexts'); let sentMessages: InboxMessage[] = []; - if (includeMessages) { - try { - sentMessages = await this.sentMessagesStore.readMessages(teamName); - if (sentMessages.length > 0) { - messages = [...messages, ...sentMessages]; - } - } catch { - warnings.push('Sent messages failed to load'); + try { + sentMessages = await this.sentMessagesStore.readMessages(teamName); + if (sentMessages.length > 0) { + messages = [...messages, ...sentMessages]; } + } catch { + warnings.push('Sent messages failed to load'); } mark('sentMessages'); @@ -339,57 +331,55 @@ export class TeamDataService { }); } - if (includeMessages) { - // Enrich inbox messages without leadSessionId by assigning the nearest neighbor's - // session ID (by timestamp). This avoids the old forward-only propagation bug. - if (config.leadSessionId || messages.some((m) => m.leadSessionId)) { - messages.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)); + // Enrich inbox messages without leadSessionId by assigning the nearest neighbor's + // session ID (by timestamp). This avoids the old forward-only propagation bug. + if (config.leadSessionId || messages.some((m) => m.leadSessionId)) { + messages.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)); - const anchors: { index: number; time: number; sessionId: string }[] = []; - for (let i = 0; i < messages.length; i++) { - if (messages[i].leadSessionId) { - anchors.push({ - index: i, - time: Date.parse(messages[i].timestamp), - sessionId: messages[i].leadSessionId!, - }); - } - } - - if (anchors.length > 0) { - let anchorIdx = 0; - for (let i = 0; i < messages.length; i++) { - if (messages[i].leadSessionId) { - while (anchorIdx < anchors.length - 1 && anchors[anchorIdx].index < i) { - anchorIdx++; - } - continue; - } - - const msgTime = Date.parse(messages[i].timestamp); - let bestAnchor = anchors[0]; - let bestDist = Math.abs(msgTime - bestAnchor.time); - for (const anchor of anchors) { - const dist = Math.abs(msgTime - anchor.time); - if (dist < bestDist) { - bestDist = dist; - bestAnchor = anchor; - } else if (dist > bestDist && anchor.time > msgTime) { - break; - } - } - messages[i].leadSessionId = bestAnchor.sessionId; - } - } else if (config.leadSessionId) { - for (const msg of messages) { - msg.leadSessionId = config.leadSessionId; - } + const anchors: { index: number; time: number; sessionId: string }[] = []; + for (let i = 0; i < messages.length; i++) { + if (messages[i].leadSessionId) { + anchors.push({ + index: i, + time: Date.parse(messages[i].timestamp), + sessionId: messages[i].leadSessionId!, + }); } } - messages.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)); + if (anchors.length > 0) { + let anchorIdx = 0; + for (let i = 0; i < messages.length; i++) { + if (messages[i].leadSessionId) { + while (anchorIdx < anchors.length - 1 && anchors[anchorIdx].index < i) { + anchorIdx++; + } + continue; + } + + const msgTime = Date.parse(messages[i].timestamp); + let bestAnchor = anchors[0]; + let bestDist = Math.abs(msgTime - bestAnchor.time); + for (const anchor of anchors) { + const dist = Math.abs(msgTime - anchor.time); + if (dist < bestDist) { + bestDist = dist; + bestAnchor = anchor; + } else if (dist > bestDist && anchor.time > msgTime) { + break; + } + } + messages[i].leadSessionId = bestAnchor.sessionId; + } + } else if (config.leadSessionId) { + for (const msg of messages) { + msg.leadSessionId = config.leadSessionId; + } + } } + messages.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)); + let metaMembers: TeamConfig['members'] = []; try { metaMembers = await this.membersMetaStore.getMembers(teamName); diff --git a/src/preload/index.ts b/src/preload/index.ts index 2379e40e..1e8baf6b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -233,7 +233,6 @@ import type { TeamCreateRequest, TeamCreateResponse, TeamData, - TeamGetDataOptions, TeamLaunchRequest, TeamLaunchResponse, TeamMessageNotificationData, @@ -741,8 +740,8 @@ const electronAPI: ElectronAPI = { list: async () => { return invokeIpcWithResult(TEAM_LIST); }, - getData: async (teamName: string, options?: TeamGetDataOptions) => { - return invokeIpcWithResult(TEAM_GET_DATA, teamName, options); + getData: async (teamName: string) => { + return invokeIpcWithResult(TEAM_GET_DATA, teamName); }, getClaudeLogs: async (teamName: string, query?: TeamClaudeLogsQuery) => { return invokeIpcWithResult(TEAM_GET_CLAUDE_LOGS, teamName, query); diff --git a/src/renderer/components/team/TeamDetailView.tsx b/src/renderer/components/team/TeamDetailView.tsx index 20512114..8f0622d8 100644 --- a/src/renderer/components/team/TeamDetailView.tsx +++ b/src/renderer/components/team/TeamDetailView.tsx @@ -99,7 +99,6 @@ interface TeamDetailViewProps { } const ACTIVE_PROVISIONING_STATES = new Set(['validating', 'spawning', 'monitoring', 'verifying']); -const MESSAGE_LOAD_DELAY_MS = 2_000; interface CreateTaskDialogState { open: boolean; @@ -203,7 +202,6 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele const { data, loading, - messagesLoading, error, projects, repositoryGroups, @@ -244,7 +242,6 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele useShallow((s) => ({ data: s.selectedTeamData, loading: s.selectedTeamLoading, - messagesLoading: s.selectedTeamMessagesLoading, error: s.selectedTeamError, projects: s.projects, repositoryGroups: s.repositoryGroups, @@ -336,20 +333,6 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele void fetchDeletedTasks(teamName); }, [teamName, selectTeam, fetchDeletedTasks]); - useEffect(() => { - if (!teamName || loading || !data || data.teamName !== teamName || !messagesLoading) { - return; - } - - const timeoutId = window.setTimeout(() => { - void refreshTeamData(teamName, { includeMessages: true, messagesLoading: true }); - }, MESSAGE_LOAD_DELAY_MS); - - return () => { - window.clearTimeout(timeoutId); - }; - }, [teamName, loading, data, messagesLoading, refreshTeamData]); - // Fetch active teams when launch dialog opens (for conflict warning) useEffect(() => { if (!launchDialogOpen) return; @@ -1459,14 +1442,14 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele sectionId="messages" title="Messages" icon={} - badge={messagesLoading ? '...' : filteredMessages.length} + badge={filteredMessages.length} secondaryBadge={ - !messagesLoading && filteredMessages.length > 0 && messagesUnreadCount > 0 + filteredMessages.length > 0 && messagesUnreadCount > 0 ? messagesUnreadCount : undefined } afterBadge={ - !messagesLoading && messagesUnreadCount > 0 ? ( + messagesUnreadCount > 0 ? (