fix(ci): resolve frontend validation failures

This commit is contained in:
777genius 2026-05-02 00:12:02 +03:00
parent 01b8161f41
commit 46df757f49
10 changed files with 126 additions and 82 deletions

View file

@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useState } from 'react';
import { api } from '@renderer/api';
import { CreateTaskDialog } from '@renderer/components/team/dialogs/CreateTaskDialog';

View file

@ -6,29 +6,53 @@ import {
} from '@renderer/store/slices/teamSlice';
import { useShallow } from 'zustand/react/shallow';
import type { TeamGraphData } from '../adapters/TeamGraphAdapter';
import type { AppState } from '@renderer/store/types';
export function useGraphMemberPopoverContext(teamName: string, memberName: string) {
interface GraphMemberPopoverContext {
teamData:
| (NonNullable<ReturnType<typeof selectTeamDataForName>> & {
members: ReturnType<typeof selectResolvedMembersForTeamName>;
messageFeed: [];
})
| null;
teamMembers: ReturnType<typeof selectResolvedMembersForTeamName>;
spawnEntry: AppState['memberSpawnStatusesByTeam'][string][string] | undefined;
leadActivity: AppState['leadActivityByTeam'][string] | undefined;
progress: ReturnType<typeof getCurrentProvisioningProgressForTeam> | null;
memberSpawnSnapshot: AppState['memberSpawnSnapshotsByTeam'][string] | undefined;
memberSpawnStatuses: AppState['memberSpawnStatusesByTeam'][string] | undefined;
}
function selectGraphMemberPopoverContext(
state: AppState,
teamName: string,
memberName: string
): GraphMemberPopoverContext {
const snapshot = teamName ? selectTeamDataForName(state, teamName) : null;
const teamMembers = teamName ? selectResolvedMembersForTeamName(state, teamName) : [];
return {
teamData: snapshot
? {
...snapshot,
members: teamMembers,
messageFeed: [],
}
: null,
teamMembers,
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,
};
}
export function useGraphMemberPopoverContext(
teamName: string,
memberName: string
): ReturnType<typeof selectGraphMemberPopoverContext> {
return useStore(
useShallow((state) => {
const snapshot = teamName ? selectTeamDataForName(state, teamName) : null;
const teamMembers = teamName ? selectResolvedMembersForTeamName(state, teamName) : [];
return {
teamData: snapshot
? {
...snapshot,
members: teamMembers,
messageFeed: [],
}
: null,
teamMembers,
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,
};
})
useShallow((state) => selectGraphMemberPopoverContext(state, teamName, memberName))
);
}

View file

@ -289,6 +289,15 @@ export const GraphActivityHud = ({
const handleMessageClick = useCallback((item: TimelineItem) => {
setExpandedItem(item);
}, []);
const handleMessageKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>, item: TimelineItem): void => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleMessageClick(item);
}
},
[handleMessageClick]
);
const handleMemberClick = useCallback(
(member: ResolvedTeamMember) => {
@ -360,6 +369,52 @@ export const GraphActivityHud = ({
};
}, [enabled, forwardWheelToGraph, visibleLanes]);
const renderLaneEntry = useCallback(
(entry: InlineActivityEntry, index: number): React.JSX.Element => {
const messageKey = toMessageKey(entry.message);
const timelineItem: TimelineItem = {
type: 'message',
message: entry.message,
};
const isUnread = !entry.message.read && !readSet.has(messageKey);
return (
<div
key={entry.graphItem.id}
className="h-[72px] min-h-[72px] min-w-0 max-w-full cursor-pointer overflow-hidden"
role="button"
tabIndex={0}
onClick={() => handleMessageClick(timelineItem)}
onKeyDown={(event) => handleMessageKeyDown(event, timelineItem)}
>
<GraphActivityCard
message={entry.message}
teamName={teamName}
messageContext={messageContext}
teamNames={teamNames}
teamColorByName={teamColorByName}
isUnread={isUnread}
zebraShade={index % 2 === 1}
onClick={() => handleMessageClick(timelineItem)}
onOpenTaskDetail={onOpenTaskDetail}
onOpenMemberProfile={onOpenMemberProfile}
/>
</div>
);
},
[
handleMessageClick,
handleMessageKeyDown,
messageContext,
onOpenMemberProfile,
onOpenTaskDetail,
readSet,
teamColorByName,
teamName,
teamNames,
]
);
if (!enabled || !teamSnapshot || visibleLanes.length === 0) {
return null;
}
@ -421,43 +476,7 @@ export const GraphActivityHud = ({
No recent activity
</div>
) : null}
{lane.entries.map((entry, index) => {
const messageKey = toMessageKey(entry.message);
const timelineItem: TimelineItem = {
type: 'message',
message: entry.message,
};
const isUnread = !entry.message.read && !readSet.has(messageKey);
return (
<div
key={entry.graphItem.id}
className="h-[72px] min-h-[72px] min-w-0 max-w-full cursor-pointer overflow-hidden"
role="button"
tabIndex={0}
onClick={() => handleMessageClick(timelineItem)}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleMessageClick(timelineItem);
}
}}
>
<GraphActivityCard
message={entry.message}
teamName={teamName}
messageContext={messageContext}
teamNames={teamNames}
teamColorByName={teamColorByName}
isUnread={isUnread}
zebraShade={index % 2 === 1}
onClick={() => handleMessageClick(timelineItem)}
onOpenTaskDetail={onOpenTaskDetail}
onOpenMemberProfile={onOpenMemberProfile}
/>
</div>
);
})}
{lane.entries.map(renderLaneEntry)}
{lane.overflowCount > 0 ? (
<button

View file

@ -19,7 +19,11 @@ import { GraphNodePopover } from './GraphNodePopover';
import { GraphProvisioningHud } from './GraphProvisioningHud';
import { GraphTransientHandoffHud } from './GraphTransientHandoffHud';
import type { GraphDomainRef, GraphEventPort } from '@claude-teams/agent-graph';
import type {
GraphDomainRef,
GraphEventPort,
TransientHandoffCard,
} from '@claude-teams/agent-graph';
import type {
MemberActivityFilter,
MemberDetailTab,
@ -149,7 +153,7 @@ export const TeamGraphOverlay = ({
focusNodeIds?: ReadonlySet<string> | null;
focusEdgeIds?: ReadonlySet<string> | null;
}) => {
cards: import('@claude-teams/agent-graph').TransientHandoffCard[];
cards: TransientHandoffCard[];
time: number;
};
worldToScreen?: (x: number, y: number) => { x: number; y: number };

View file

@ -19,7 +19,11 @@ import { GraphNodePopover } from './GraphNodePopover';
import { GraphProvisioningHud } from './GraphProvisioningHud';
import { GraphTransientHandoffHud } from './GraphTransientHandoffHud';
import type { GraphDomainRef, GraphEventPort } from '@claude-teams/agent-graph';
import type {
GraphDomainRef,
GraphEventPort,
TransientHandoffCard,
} from '@claude-teams/agent-graph';
import type {
MemberActivityFilter,
MemberDetailTab,
@ -169,7 +173,7 @@ export const TeamGraphTab = ({
focusNodeIds?: ReadonlySet<string> | null;
focusEdgeIds?: ReadonlySet<string> | null;
}) => {
cards: import('@claude-teams/agent-graph').TransientHandoffCard[];
cards: TransientHandoffCard[];
time: number;
};
worldToScreen?: (x: number, y: number) => { x: number; y: number };

View file

@ -15,15 +15,15 @@ import { BoardTaskExactLogSummarySelector } from '../exact/BoardTaskExactLogSumm
import { isBoardTaskExactLogsReadEnabled } from '../exact/featureGates';
import { getBoardTaskExactLogFileVersions } from '../exact/fileVersions';
import { OpenCodeTaskLogStreamSource } from './OpenCodeTaskLogStreamSource';
import { CodexNativeTaskLogStreamSource } from './CodexNativeTaskLogStreamSource';
import { buildCodexNativeToolSignature } from './CodexNativeTraceProjector';
import { HistoricalBoardMcpRawProbe } from './HistoricalBoardMcpRawProbe';
import { TaskLogTranscriptCandidateSelector } from './TaskLogTranscriptCandidateSelector';
import {
canonicalizeBoardTaskLogToolName,
isBoardTaskLogMcpToolName,
} from './boardTaskLogToolNames';
import { CodexNativeTaskLogStreamSource } from './CodexNativeTaskLogStreamSource';
import { buildCodexNativeToolSignature } from './CodexNativeTraceProjector';
import { HistoricalBoardMcpRawProbe } from './HistoricalBoardMcpRawProbe';
import { OpenCodeTaskLogStreamSource } from './OpenCodeTaskLogStreamSource';
import { TaskLogTranscriptCandidateSelector } from './TaskLogTranscriptCandidateSelector';
import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord';
import type { BoardTaskExactLogDetailCandidate } from '../exact/BoardTaskExactLogTypes';
@ -1684,12 +1684,12 @@ export class BoardTaskLogStreamService {
const cacheKey = this.buildLayoutCacheKey(teamName, taskId);
const generation = this.getTranscriptDiscoveryGeneration(teamName);
const cached = this.layoutCache.get(cacheKey);
if (cached && cached.generation === generation && cached.expiresAt > Date.now()) {
if (cached?.generation === generation && cached.expiresAt > Date.now()) {
return cached.layout;
}
const existingInFlight = this.layoutInFlight.get(cacheKey);
if (existingInFlight && existingInFlight.generation === generation) {
if (existingInFlight?.generation === generation) {
return await existingInFlight.promise;
}

View file

@ -1,8 +1,7 @@
import { getTeamsBasePath } from '@main/utils/pathDecoder';
import * as fs from 'fs/promises';
import * as path from 'path';
import { getTeamsBasePath } from '@main/utils/pathDecoder';
const TRACE_ROOT_SEGMENT = path.join('.member-work-sync', 'runtime-hooks', 'codex-native-traces');
export interface CodexNativeTraceProjection {

View file

@ -1,8 +1,7 @@
import { getTaskDisplayId } from '@shared/utils/taskIdentity';
import { createReadStream } from 'fs';
import * as readline from 'readline';
import { getTaskDisplayId } from '@shared/utils/taskIdentity';
import type { TeamTask } from '@shared/types';
const RAW_PROBE_CONCURRENCY = process.platform === 'win32' ? 4 : 8;

View file

@ -45,10 +45,5 @@ export function isReadOnlyBoardTaskLogToolName(toolName: string | undefined): bo
}
export function isRecoverableHistoricalBoardTaskLogToolName(toolName: string | undefined): boolean {
const canonical = canonicalizeBoardTaskLogToolName(toolName);
return (
canonical !== null &&
(HISTORICAL_BOARD_LIFECYCLE_TOOL_NAMES.has(canonical) ||
HISTORICAL_BOARD_ACTION_TOOL_NAMES.has(canonical))
);
return isBoardTaskLogMcpToolName(toolName);
}

View file

@ -19,9 +19,9 @@ import { AlertCircle, Clock, FileText, Loader2 } from 'lucide-react';
import type {
BoardTaskLogActor,
ResolvedTeamMember,
BoardTaskLogSegment,
BoardTaskLogStreamResponse,
ResolvedTeamMember,
} from '@shared/types';
interface TaskLogStreamSectionProps {