refactor(agent-graph): replace store usage with context hooks for team data retrieval
- Updated components in the agent-graph renderer to utilize context hooks instead of the store for accessing team data. - Introduced `useGraphActivityContext` and `useGraphMemberPopoverContext` hooks to streamline data management. - Refactored `GraphBlockingEdgePopover`, `GraphNodePopover`, and `GraphTaskCard` components for improved performance and readability. - Enhanced imports in `MemberDetailDialog` for better organization.
This commit is contained in:
parent
aed08113e6
commit
03dda6b486
17 changed files with 73 additions and 64 deletions
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useStore } from '@renderer/store';
|
||||||
|
import {
|
||||||
|
getCurrentProvisioningProgressForTeam,
|
||||||
|
selectTeamDataForName,
|
||||||
|
} from '@renderer/store/slices/teamSlice';
|
||||||
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
|
|
||||||
|
export function useGraphMemberPopoverContext(teamName: string, memberName: string) {
|
||||||
|
return useStore(
|
||||||
|
useShallow((state) => ({
|
||||||
|
teamData: teamName ? selectTeamDataForName(state, teamName) : null,
|
||||||
|
spawnEntry: teamName ? state.memberSpawnStatusesByTeam[teamName]?.[memberName] : undefined,
|
||||||
|
leadActivity: teamName ? state.leadActivityByTeam[teamName] : undefined,
|
||||||
|
progress: teamName ? getCurrentProvisioningProgressForTeam(state, teamName) : null,
|
||||||
|
memberSpawnSnapshot: teamName ? state.memberSpawnSnapshotsByTeam[teamName] : undefined,
|
||||||
|
memberSpawnStatuses: teamName ? state.memberSpawnStatusesByTeam[teamName] : undefined,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { buildInlineActivityEntries } from '../core/domain/buildInlineActivityEntries';
|
export { buildInlineActivityEntries } from '../core/domain/buildInlineActivityEntries';
|
||||||
|
export { buildGraphMemberNodeIdForMember } from '../core/domain/graphOwnerIdentity';
|
||||||
export { TeamGraphAdapter } from './adapters/TeamGraphAdapter';
|
export { TeamGraphAdapter } from './adapters/TeamGraphAdapter';
|
||||||
export type { TeamGraphOverlayProps } from './ui/TeamGraphOverlay';
|
export type { TeamGraphOverlayProps } from './ui/TeamGraphOverlay';
|
||||||
export { TeamGraphOverlay } from './ui/TeamGraphOverlay';
|
export { TeamGraphOverlay } from './ui/TeamGraphOverlay';
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { useMemo } from 'react';
|
||||||
|
|
||||||
import { Badge } from '@renderer/components/ui/badge';
|
import { Badge } from '@renderer/components/ui/badge';
|
||||||
import { Button } from '@renderer/components/ui/button';
|
import { Button } from '@renderer/components/ui/button';
|
||||||
import { useStore } from '@renderer/store';
|
|
||||||
import { selectTeamDataForName } from '@renderer/store/slices/teamSlice';
|
import { useGraphActivityContext } from '../hooks/useGraphActivityContext';
|
||||||
|
|
||||||
import type { GraphEdge, GraphNode } from '@claude-teams/agent-graph';
|
import type { GraphEdge, GraphNode } from '@claude-teams/agent-graph';
|
||||||
import type { TeamTaskWithKanban } from '@shared/types';
|
import type { TeamTaskWithKanban } from '@shared/types';
|
||||||
|
|
@ -63,7 +63,7 @@ export const GraphBlockingEdgePopover = ({
|
||||||
onSelectNode,
|
onSelectNode,
|
||||||
onOpenTaskDetail,
|
onOpenTaskDetail,
|
||||||
}: GraphBlockingEdgePopoverProps): React.JSX.Element => {
|
}: GraphBlockingEdgePopoverProps): React.JSX.Element => {
|
||||||
const teamData = useStore((state) => selectTeamDataForName(state, teamName));
|
const { teamData } = useGraphActivityContext(teamName);
|
||||||
const tasksById = useMemo(
|
const tasksById = useMemo(
|
||||||
() => new Map((teamData?.tasks ?? []).map((task) => [task.id, task] as const)),
|
() => new Map((teamData?.tasks ?? []).map((task) => [task.id, task] as const)),
|
||||||
[teamData?.tasks]
|
[teamData?.tasks]
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,13 @@
|
||||||
|
|
||||||
import { Badge } from '@renderer/components/ui/badge';
|
import { Badge } from '@renderer/components/ui/badge';
|
||||||
import { Button } from '@renderer/components/ui/button';
|
import { Button } from '@renderer/components/ui/button';
|
||||||
import { useStore } from '@renderer/store';
|
|
||||||
import {
|
|
||||||
getCurrentProvisioningProgressForTeam,
|
|
||||||
selectTeamDataForName,
|
|
||||||
} from '@renderer/store/slices/teamSlice';
|
|
||||||
import { agentAvatarUrl, buildMemberLaunchPresentation } from '@renderer/utils/memberHelpers';
|
import { agentAvatarUrl, buildMemberLaunchPresentation } from '@renderer/utils/memberHelpers';
|
||||||
import { buildTeamProvisioningPresentation } from '@renderer/utils/teamProvisioningPresentation';
|
import { buildTeamProvisioningPresentation } from '@renderer/utils/teamProvisioningPresentation';
|
||||||
import { ExternalLink, Loader2, MessageSquare, Plus, User } from 'lucide-react';
|
import { ExternalLink, Loader2, MessageSquare, Plus, User } from 'lucide-react';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
|
||||||
|
|
||||||
import { isTaskInReviewCycle, resolveTaskReviewer } from '../../core/domain/taskGraphSemantics';
|
import { isTaskInReviewCycle, resolveTaskReviewer } from '../../core/domain/taskGraphSemantics';
|
||||||
|
import { useGraphActivityContext } from '../hooks/useGraphActivityContext';
|
||||||
|
import { useGraphMemberPopoverContext } from '../hooks/useGraphMemberPopoverContext';
|
||||||
|
|
||||||
import { GraphTaskCard } from './GraphTaskCard';
|
import { GraphTaskCard } from './GraphTaskCard';
|
||||||
|
|
||||||
|
|
@ -213,7 +209,7 @@ const OverflowPopoverContent = ({
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onOpenTaskDetail?: (taskId: string) => void;
|
onOpenTaskDetail?: (taskId: string) => void;
|
||||||
}): React.JSX.Element => {
|
}): React.JSX.Element => {
|
||||||
const teamData = useStore((state) => selectTeamDataForName(state, teamName));
|
const { teamData } = useGraphActivityContext(teamName);
|
||||||
const tasksById = new Map((teamData?.tasks ?? []).map((task) => [task.id, task]));
|
const tasksById = new Map((teamData?.tasks ?? []).map((task) => [task.id, task]));
|
||||||
const hiddenTasks = (node.overflowTaskIds ?? [])
|
const hiddenTasks = (node.overflowTaskIds ?? [])
|
||||||
.map((taskId) => tasksById.get(taskId) ?? null)
|
.map((taskId) => tasksById.get(taskId) ?? null)
|
||||||
|
|
@ -297,16 +293,7 @@ const MemberPopoverContent = ({
|
||||||
: '';
|
: '';
|
||||||
const avatarSrc = node.avatarUrl ?? agentAvatarUrl(memberName, 64);
|
const avatarSrc = node.avatarUrl ?? agentAvatarUrl(memberName, 64);
|
||||||
const { teamData, spawnEntry, leadActivity, progress, memberSpawnSnapshot, memberSpawnStatuses } =
|
const { teamData, spawnEntry, leadActivity, progress, memberSpawnSnapshot, memberSpawnStatuses } =
|
||||||
useStore(
|
useGraphMemberPopoverContext(teamName, memberName);
|
||||||
useShallow((state) => ({
|
|
||||||
teamData: teamName ? selectTeamDataForName(state, teamName) : null,
|
|
||||||
spawnEntry: teamName ? state.memberSpawnStatusesByTeam[teamName]?.[memberName] : undefined,
|
|
||||||
leadActivity: teamName ? state.leadActivityByTeam[teamName] : undefined,
|
|
||||||
progress: teamName ? getCurrentProvisioningProgressForTeam(state, teamName) : null,
|
|
||||||
memberSpawnSnapshot: teamName ? state.memberSpawnSnapshotsByTeam[teamName] : undefined,
|
|
||||||
memberSpawnStatuses: teamName ? state.memberSpawnStatusesByTeam[teamName] : undefined,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
const member = teamData?.members.find((candidate) => candidate.name === memberName) ?? null;
|
const member = teamData?.members.find((candidate) => candidate.name === memberName) ?? null;
|
||||||
const provisioningPresentation =
|
const provisioningPresentation =
|
||||||
teamData && teamName
|
teamData && teamName
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { KanbanTaskCard } from '@renderer/components/team/kanban/KanbanTaskCard';
|
import { KanbanTaskCard } from '@renderer/components/team/kanban/KanbanTaskCard';
|
||||||
import { useStore } from '@renderer/store';
|
|
||||||
import { selectTeamDataForName } from '@renderer/store/slices/teamSlice';
|
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
|
||||||
|
|
||||||
import { isTaskBlocked, resolveTaskGraphColumn } from '../../core/domain/taskGraphSemantics';
|
import { isTaskBlocked, resolveTaskGraphColumn } from '../../core/domain/taskGraphSemantics';
|
||||||
|
import { useGraphActivityContext } from '../hooks/useGraphActivityContext';
|
||||||
|
|
||||||
import type { GraphNode } from '@claude-teams/agent-graph';
|
import type { GraphNode } from '@claude-teams/agent-graph';
|
||||||
import type { KanbanColumnId, TeamTask } from '@shared/types';
|
import type { KanbanColumnId, TeamTask } from '@shared/types';
|
||||||
|
|
@ -84,14 +82,10 @@ export const GraphTaskCard = ({
|
||||||
onDeleteTask,
|
onDeleteTask,
|
||||||
}: GraphTaskCardProps): React.JSX.Element => {
|
}: GraphTaskCardProps): React.JSX.Element => {
|
||||||
const taskId = node.domainRef.kind === 'task' ? node.domainRef.taskId : '';
|
const taskId = node.domainRef.kind === 'task' ? node.domainRef.taskId : '';
|
||||||
|
const { teamData } = useGraphActivityContext(teamName);
|
||||||
const { task, tasks, members } = useStore(
|
const tasks = teamData?.tasks ?? [];
|
||||||
useShallow((s) => ({
|
const members = teamData?.members ?? [];
|
||||||
tasks: selectTeamDataForName(s, teamName)?.tasks ?? [],
|
const task = tasks.find((candidate) => candidate.id === taskId);
|
||||||
members: selectTeamDataForName(s, teamName)?.members ?? [],
|
|
||||||
task: selectTeamDataForName(s, teamName)?.tasks.find((t) => t.id === taskId),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const taskMap = useMemo(() => {
|
const taskMap = useMemo(() => {
|
||||||
const map = new Map<string, TeamTask>();
|
const map = new Map<string, TeamTask>();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { TmuxInstallerRunnerAdapter } from '../TmuxInstallerRunnerAdapter';
|
import { TmuxInstallerRunnerAdapter } from '../TmuxInstallerRunnerAdapter';
|
||||||
|
|
@ -556,7 +558,7 @@ describe('TmuxInstallerRunnerAdapter', () => {
|
||||||
restartRequired: 'Possible',
|
restartRequired: 'Possible',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
resultFilePath: 'C:\\temp\\result.json',
|
resultFilePath: path.join(process.cwd(), 'tmp', 'result.json'),
|
||||||
})),
|
})),
|
||||||
} as never
|
} as never
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import * as fsp from 'node:fs/promises';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { decodeInstallerProcessOutput } from '../runtime/decodeInstallerProcessOutput';
|
|
||||||
|
|
||||||
import { createLogger } from '@shared/utils/logger';
|
import { createLogger } from '@shared/utils/logger';
|
||||||
|
|
||||||
|
import { decodeInstallerProcessOutput } from '../runtime/decodeInstallerProcessOutput';
|
||||||
|
|
||||||
const logger = createLogger('Feature:tmux-installer:windows-elevation');
|
const logger = createLogger('Feature:tmux-installer:windows-elevation');
|
||||||
|
|
||||||
interface ExecResult {
|
interface ExecResult {
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,8 @@ import {
|
||||||
} from './guards';
|
} from './guards';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BoardTaskActivityService,
|
|
||||||
BoardTaskActivityDetailService,
|
BoardTaskActivityDetailService,
|
||||||
|
BoardTaskActivityService,
|
||||||
BoardTaskExactLogDetailService,
|
BoardTaskExactLogDetailService,
|
||||||
BoardTaskExactLogsService,
|
BoardTaskExactLogsService,
|
||||||
BoardTaskLogStreamService,
|
BoardTaskLogStreamService,
|
||||||
|
|
@ -141,8 +141,8 @@ import type {
|
||||||
AttachmentFileData,
|
AttachmentFileData,
|
||||||
AttachmentMeta,
|
AttachmentMeta,
|
||||||
AttachmentPayload,
|
AttachmentPayload,
|
||||||
BoardTaskActivityEntry,
|
|
||||||
BoardTaskActivityDetailResult,
|
BoardTaskActivityDetailResult,
|
||||||
|
BoardTaskActivityEntry,
|
||||||
BoardTaskExactLogDetailResult,
|
BoardTaskExactLogDetailResult,
|
||||||
BoardTaskExactLogSummariesResponse,
|
BoardTaskExactLogSummariesResponse,
|
||||||
BoardTaskLogStreamResponse,
|
BoardTaskLogStreamResponse,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import { safeSendToRenderer } from '@main/utils/safeWebContentsSend';
|
||||||
import { stripMarkdown } from '@main/utils/textFormatting';
|
import { stripMarkdown } from '@main/utils/textFormatting';
|
||||||
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
|
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
|
||||||
import { createLogger } from '@shared/utils/logger';
|
import { createLogger } from '@shared/utils/logger';
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
import { Notification as ElectronNotification } from 'electron';
|
import { Notification as ElectronNotification } from 'electron';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
import * as fsp from 'fs/promises';
|
import * as fsp from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ export { HunkSnippetMatcher } from './HunkSnippetMatcher';
|
||||||
export { MemberStatsComputer } from './MemberStatsComputer';
|
export { MemberStatsComputer } from './MemberStatsComputer';
|
||||||
export { ReviewApplierService } from './ReviewApplierService';
|
export { ReviewApplierService } from './ReviewApplierService';
|
||||||
export { TaskBoundaryParser } from './TaskBoundaryParser';
|
export { TaskBoundaryParser } from './TaskBoundaryParser';
|
||||||
export { BoardTaskActivityRecordSource } from './taskLogs/activity/BoardTaskActivityRecordSource';
|
|
||||||
export { BoardTaskActivityDetailService } from './taskLogs/activity/BoardTaskActivityDetailService';
|
export { BoardTaskActivityDetailService } from './taskLogs/activity/BoardTaskActivityDetailService';
|
||||||
|
export { BoardTaskActivityRecordSource } from './taskLogs/activity/BoardTaskActivityRecordSource';
|
||||||
export { BoardTaskActivityService } from './taskLogs/activity/BoardTaskActivityService';
|
export { BoardTaskActivityService } from './taskLogs/activity/BoardTaskActivityService';
|
||||||
export { BoardTaskExactLogDetailService } from './taskLogs/exact/BoardTaskExactLogDetailService';
|
export { BoardTaskExactLogDetailService } from './taskLogs/exact/BoardTaskExactLogDetailService';
|
||||||
export { BoardTaskExactLogsService } from './taskLogs/exact/BoardTaskExactLogsService';
|
export { BoardTaskExactLogsService } from './taskLogs/exact/BoardTaskExactLogsService';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { isEnhancedAIChunk } from '@main/types';
|
||||||
import {
|
import {
|
||||||
describeBoardTaskActivityLabel,
|
describeBoardTaskActivityLabel,
|
||||||
formatBoardTaskActivityTaskLabel,
|
formatBoardTaskActivityTaskLabel,
|
||||||
|
|
@ -6,21 +7,21 @@ import {
|
||||||
describeBoardTaskActivityActorLabel,
|
describeBoardTaskActivityActorLabel,
|
||||||
describeBoardTaskActivityContextLines,
|
describeBoardTaskActivityContextLines,
|
||||||
} from '@shared/utils/boardTaskActivityPresentation';
|
} from '@shared/utils/boardTaskActivityPresentation';
|
||||||
import { isEnhancedAIChunk } from '@main/types';
|
|
||||||
|
|
||||||
import { BoardTaskActivityRecordSource } from './BoardTaskActivityRecordSource';
|
|
||||||
import { BoardTaskExactLogChunkBuilder } from '../exact/BoardTaskExactLogChunkBuilder';
|
import { BoardTaskExactLogChunkBuilder } from '../exact/BoardTaskExactLogChunkBuilder';
|
||||||
import { BoardTaskExactLogDetailSelector } from '../exact/BoardTaskExactLogDetailSelector';
|
import { BoardTaskExactLogDetailSelector } from '../exact/BoardTaskExactLogDetailSelector';
|
||||||
import { BoardTaskExactLogStrictParser } from '../exact/BoardTaskExactLogStrictParser';
|
import { BoardTaskExactLogStrictParser } from '../exact/BoardTaskExactLogStrictParser';
|
||||||
|
|
||||||
|
import { BoardTaskActivityRecordSource } from './BoardTaskActivityRecordSource';
|
||||||
|
|
||||||
|
import type { BoardTaskExactLogBundleCandidate } from '../exact/BoardTaskExactLogTypes';
|
||||||
import type { BoardTaskActivityRecord } from './BoardTaskActivityRecord';
|
import type { BoardTaskActivityRecord } from './BoardTaskActivityRecord';
|
||||||
|
import type { ContentBlock, EnhancedChunk, ParsedMessage, ToolUseResultData } from '@main/types';
|
||||||
import type {
|
import type {
|
||||||
BoardTaskActivityDetail,
|
BoardTaskActivityDetail,
|
||||||
BoardTaskActivityDetailMetadataRow,
|
BoardTaskActivityDetailMetadataRow,
|
||||||
BoardTaskActivityDetailResult,
|
BoardTaskActivityDetailResult,
|
||||||
} from '@shared/types';
|
} from '@shared/types';
|
||||||
import type { BoardTaskExactLogBundleCandidate } from '../exact/BoardTaskExactLogTypes';
|
|
||||||
import type { ContentBlock, EnhancedChunk, ParsedMessage, ToolUseResultData } from '@main/types';
|
|
||||||
|
|
||||||
const READ_ONLY_TOOL_NAMES = new Set(['task_get', 'task_get_comment']);
|
const READ_ONLY_TOOL_NAMES = new Set(['task_get', 'task_get_comment']);
|
||||||
|
|
||||||
|
|
@ -236,7 +237,7 @@ function cloneBlock<T extends ContentBlock>(block: T): T {
|
||||||
} as T;
|
} as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...block } as T;
|
return { ...block };
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeToolResultContent(
|
function sanitizeToolResultContent(
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,9 @@ import type {
|
||||||
TmuxAPI,
|
TmuxAPI,
|
||||||
TmuxStatus,
|
TmuxStatus,
|
||||||
TriggerTestResult,
|
TriggerTestResult,
|
||||||
UpdateSchedulePatch,
|
|
||||||
UpdateKanbanPatch,
|
UpdateKanbanPatch,
|
||||||
UpdaterAPI,
|
UpdaterAPI,
|
||||||
|
UpdateSchedulePatch,
|
||||||
WaterfallData,
|
WaterfallData,
|
||||||
WslClaudeRootCandidate,
|
WslClaudeRootCandidate,
|
||||||
} from '@shared/types';
|
} from '@shared/types';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { buildGraphMemberNodeIdForMember } from '@features/agent-graph/core/domain/graphOwnerIdentity';
|
import {
|
||||||
import { buildInlineActivityEntries } from '@features/agent-graph/renderer';
|
buildGraphMemberNodeIdForMember,
|
||||||
|
buildInlineActivityEntries,
|
||||||
|
} from '@features/agent-graph/renderer';
|
||||||
import { Button } from '@renderer/components/ui/button';
|
import { Button } from '@renderer/components/ui/button';
|
||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog';
|
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ import { AlertCircle, ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
import { TaskActivityLinkedToolCard } from './TaskActivityLinkedToolCard';
|
import { TaskActivityLinkedToolCard } from './TaskActivityLinkedToolCard';
|
||||||
|
|
||||||
|
import type { AIGroupDisplayItem, LinkedToolItem } from '@renderer/types/groups';
|
||||||
import type {
|
import type {
|
||||||
BoardTaskActivityDetail,
|
BoardTaskActivityDetail,
|
||||||
BoardTaskActivityEntry,
|
BoardTaskActivityEntry,
|
||||||
BoardTaskActivityTaskRef,
|
BoardTaskActivityTaskRef,
|
||||||
} from '@shared/types';
|
} from '@shared/types';
|
||||||
import type { AIGroupDisplayItem, LinkedToolItem } from '@renderer/types/groups';
|
|
||||||
|
|
||||||
interface TaskActivitySectionProps {
|
interface TaskActivitySectionProps {
|
||||||
teamName: string;
|
teamName: string;
|
||||||
|
|
@ -80,6 +80,14 @@ type ActivityDetailState =
|
||||||
| { status: 'error'; error: string }
|
| { status: 'error'; error: string }
|
||||||
| { status: 'ok'; detail: BoardTaskActivityDetail };
|
| { status: 'ok'; detail: BoardTaskActivityDetail };
|
||||||
|
|
||||||
|
type ActivityMetadataProps = Readonly<{
|
||||||
|
detail: BoardTaskActivityDetail;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type ActivityDetailPanelProps = Readonly<{
|
||||||
|
detailState: ActivityDetailState;
|
||||||
|
}>;
|
||||||
|
|
||||||
function normalizeDetail(detail: BoardTaskActivityDetail): BoardTaskActivityDetail {
|
function normalizeDetail(detail: BoardTaskActivityDetail): BoardTaskActivityDetail {
|
||||||
if (!detail.logDetail) {
|
if (!detail.logDetail) {
|
||||||
return detail;
|
return detail;
|
||||||
|
|
@ -117,11 +125,7 @@ function getFirstRenderableLinkedTool(detail: BoardTaskActivityDetail): LinkedTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ActivityMetadata({
|
const ActivityMetadata = ({ detail }: ActivityMetadataProps): React.JSX.Element | null => {
|
||||||
detail,
|
|
||||||
}: {
|
|
||||||
detail: BoardTaskActivityDetail;
|
|
||||||
}): React.JSX.Element | null {
|
|
||||||
const hasMetadata = detail.metadataRows.length > 0;
|
const hasMetadata = detail.metadataRows.length > 0;
|
||||||
const hasContext = detail.contextLines.length > 0;
|
const hasContext = detail.contextLines.length > 0;
|
||||||
|
|
||||||
|
|
@ -155,16 +159,12 @@ function ActivityMetadata({
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function ActivityDetailPanel({
|
const ActivityDetailPanel = ({ detailState }: ActivityDetailPanelProps): React.JSX.Element => {
|
||||||
detailState,
|
|
||||||
}: {
|
|
||||||
detailState: ActivityDetailState;
|
|
||||||
}): React.JSX.Element {
|
|
||||||
if (detailState.status === 'loading') {
|
if (detailState.status === 'loading') {
|
||||||
return (
|
return (
|
||||||
<div className="border-[var(--color-border)]/20 bg-[var(--color-bg-elevated)]/18 flex items-center gap-2 rounded-md border px-3 py-3 text-xs text-[var(--color-text-muted)]">
|
<div className="border-[var(--color-border)]/20 bg-[var(--color-bg-elevated)]/18 flex items-center gap-2 rounded-md border p-3 text-xs text-[var(--color-text-muted)]">
|
||||||
<Loader2 size={12} className="animate-spin" />
|
<Loader2 size={12} className="animate-spin" />
|
||||||
Loading activity details...
|
Loading activity details...
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -182,7 +182,7 @@ function ActivityDetailPanel({
|
||||||
|
|
||||||
if (detailState.status === 'missing') {
|
if (detailState.status === 'missing') {
|
||||||
return (
|
return (
|
||||||
<div className="border-[var(--color-border)]/20 bg-[var(--color-bg-elevated)]/18 rounded-md border px-3 py-3 text-xs text-[var(--color-text-muted)]">
|
<div className="border-[var(--color-border)]/20 bg-[var(--color-bg-elevated)]/18 rounded-md border p-3 text-xs text-[var(--color-text-muted)]">
|
||||||
Detailed transcript context is no longer available for this activity.
|
Detailed transcript context is no longer available for this activity.
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -202,7 +202,7 @@ function ActivityDetailPanel({
|
||||||
{linkedTool ? <TaskActivityLinkedToolCard linkedTool={linkedTool} /> : null}
|
{linkedTool ? <TaskActivityLinkedToolCard linkedTool={linkedTool} /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const Row = ({
|
const Row = ({
|
||||||
detailState,
|
detailState,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
|
||||||
|
|
||||||
import { ExecutionSessionsSection } from './ExecutionSessionsSection';
|
import { ExecutionSessionsSection } from './ExecutionSessionsSection';
|
||||||
import { isBoardTaskActivityUiEnabled, isBoardTaskExactLogsUiEnabled } from './featureGates';
|
import { isBoardTaskActivityUiEnabled, isBoardTaskExactLogsUiEnabled } from './featureGates';
|
||||||
import { TaskActivitySection } from './TaskActivitySection';
|
import { TaskActivitySection } from './TaskActivitySection';
|
||||||
import { TaskLogStreamSection } from './TaskLogStreamSection';
|
import { TaskLogStreamSection } from './TaskLogStreamSection';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
|
|
||||||
|
|
||||||
import type { TeamTaskWithKanban } from '@shared/types';
|
import type { TeamTaskWithKanban } from '@shared/types';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
import { api } from '@renderer/api';
|
import { api } from '@renderer/api';
|
||||||
import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli';
|
import { CLI_NOT_FOUND_MESSAGE } from '@shared/constants/cli';
|
||||||
|
|
||||||
import type { AppState } from '../types';
|
|
||||||
import { findPaneByTabId, updatePane } from '../utils/paneHelpers';
|
import { findPaneByTabId, updatePane } from '../utils/paneHelpers';
|
||||||
|
|
||||||
|
import type { AppState } from '../types';
|
||||||
import type {
|
import type {
|
||||||
ApiKeyEntry,
|
ApiKeyEntry,
|
||||||
ApiKeySaveRequest,
|
ApiKeySaveRequest,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ describe('TeamMemberResolver', () => {
|
||||||
const members = resolver.resolveMembers(config, metaMembers, inboxNames, tasks, messages);
|
const members = resolver.resolveMembers(config, metaMembers, inboxNames, tasks, messages);
|
||||||
const names = members.map((member) => member.name);
|
const names = members.map((member) => member.name);
|
||||||
|
|
||||||
expect(names).toEqual(['alice', 'bob', 'team-lead']);
|
expect(names).toHaveLength(3);
|
||||||
|
expect(names).toEqual(expect.arrayContaining(['alice', 'bob', 'team-lead']));
|
||||||
expect(names).not.toContain('stranger');
|
expect(names).not.toContain('stranger');
|
||||||
expect(names).not.toContain('user');
|
expect(names).not.toContain('user');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue