chore: merge origin/dev into dev

This commit is contained in:
777genius 2026-04-13 18:43:00 +03:00
commit 5b1f369950
54 changed files with 218 additions and 232 deletions

View file

@ -12,8 +12,8 @@
} }
}, },
"peerDependencies": { "peerDependencies": {
"react": "^18.0.0", "react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0 || ^19.0.0",
"lucide-react": ">=0.300.0" "lucide-react": ">=0.300.0"
}, },
"dependencies": { "dependencies": {

View file

@ -531,13 +531,13 @@ importers:
version: 3.0.0 version: 3.0.0
lucide-react: lucide-react:
specifier: '>=0.300.0' specifier: '>=0.300.0'
version: 0.577.0(react@18.3.1) version: 0.577.0(react@19.2.4)
react: react:
specifier: ^18.0.0 specifier: ^18.0.0 || ^19.0.0
version: 18.3.1 version: 19.2.4
react-dom: react-dom:
specifier: ^18.0.0 specifier: ^18.0.0 || ^19.0.0
version: 18.3.1(react@18.3.1) version: 19.2.4(react@19.2.4)
devDependencies: devDependencies:
'@types/d3-force': '@types/d3-force':
specifier: ^3.0.10 specifier: ^3.0.10
@ -9108,11 +9108,6 @@ packages:
rc9@3.0.0: rc9@3.0.0:
resolution: {integrity: sha512-MGOue0VqscKWQ104udASX/3GYDcKyPI4j4F8gu/jHHzglpmy9a/anZK3PNe8ug6aZFl+9GxLtdhe3kVZuMaQbA==} resolution: {integrity: sha512-MGOue0VqscKWQ104udASX/3GYDcKyPI4j4F8gu/jHHzglpmy9a/anZK3PNe8ug6aZFl+9GxLtdhe3kVZuMaQbA==}
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
react: ^18.3.1
react-dom@19.2.4: react-dom@19.2.4:
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies: peerDependencies:
@ -9195,10 +9190,6 @@ packages:
react-dom: react-dom:
optional: true optional: true
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
react@19.2.4: react@19.2.4:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -9465,9 +9456,6 @@ packages:
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
engines: {node: '>=11.0.0'} engines: {node: '>=11.0.0'}
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
scheduler@0.27.0: scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@ -19356,10 +19344,6 @@ snapshots:
dependencies: dependencies:
yallist: 4.0.0 yallist: 4.0.0
lucide-react@0.577.0(react@18.3.1):
dependencies:
react: 18.3.1
lucide-react@0.577.0(react@19.2.4): lucide-react@0.577.0(react@19.2.4):
dependencies: dependencies:
react: 19.2.4 react: 19.2.4
@ -21160,12 +21144,6 @@ snapshots:
defu: 6.1.4 defu: 6.1.4
destr: 2.0.5 destr: 2.0.5
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
react: 18.3.1
scheduler: 0.23.2
react-dom@19.2.4(react@19.2.4): react-dom@19.2.4(react@19.2.4):
dependencies: dependencies:
react: 19.2.4 react: 19.2.4
@ -21259,10 +21237,6 @@ snapshots:
optionalDependencies: optionalDependencies:
react-dom: 19.2.4(react@19.2.4) react-dom: 19.2.4(react@19.2.4)
react@18.3.1:
dependencies:
loose-envify: 1.4.0
react@19.2.4: {} react@19.2.4: {}
read-binary-file-arch@1.0.6: read-binary-file-arch@1.0.6:
@ -21638,10 +21612,6 @@ snapshots:
sax@1.6.0: {} sax@1.6.0: {}
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
scheduler@0.27.0: {} scheduler@0.27.0: {}
scslre@0.3.0: scslre@0.3.0:

View file

@ -102,12 +102,12 @@ import {
} from './utils/safeWebContentsSend'; } from './utils/safeWebContentsSend';
import { syncTelemetryFlag } from './sentry'; import { syncTelemetryFlag } from './sentry';
import { import {
BranchStatusService,
BoardTaskActivityRecordSource, BoardTaskActivityRecordSource,
BoardTaskActivityService, BoardTaskActivityService,
BoardTaskExactLogDetailService, BoardTaskExactLogDetailService,
BoardTaskExactLogsService, BoardTaskExactLogsService,
BoardTaskLogStreamService, BoardTaskLogStreamService,
BranchStatusService,
CliInstallerService, CliInstallerService,
configManager, configManager,
LocalFileSystemProvider, LocalFileSystemProvider,

View file

@ -89,11 +89,11 @@ import { registerValidationHandlers, removeValidationHandlers } from './validati
import { registerWindowHandlers, removeWindowHandlers } from './window'; import { registerWindowHandlers, removeWindowHandlers } from './window';
import type { import type {
BranchStatusService,
BoardTaskActivityService, BoardTaskActivityService,
BoardTaskLogStreamService,
BoardTaskExactLogDetailService, BoardTaskExactLogDetailService,
BoardTaskExactLogsService, BoardTaskExactLogsService,
BoardTaskLogStreamService,
BranchStatusService,
ChangeExtractorService, ChangeExtractorService,
CliInstallerService, CliInstallerService,
FileContentResolver, FileContentResolver,

View file

@ -22,17 +22,17 @@ import {
TEAM_GET_DATA, TEAM_GET_DATA,
TEAM_GET_DELETED_TASKS, TEAM_GET_DELETED_TASKS,
TEAM_GET_LOGS_FOR_TASK, TEAM_GET_LOGS_FOR_TASK,
TEAM_GET_TASK_ACTIVITY,
TEAM_GET_TASK_LOG_STREAM,
TEAM_GET_TASK_EXACT_LOG_DETAIL,
TEAM_GET_TASK_EXACT_LOG_SUMMARIES,
TEAM_GET_MEMBER_LOGS, TEAM_GET_MEMBER_LOGS,
TEAM_GET_MEMBER_STATS, TEAM_GET_MEMBER_STATS,
TEAM_GET_MESSAGES_PAGE, TEAM_GET_MESSAGES_PAGE,
TEAM_GET_PROJECT_BRANCH, TEAM_GET_PROJECT_BRANCH,
TEAM_GET_SAVED_REQUEST, TEAM_GET_SAVED_REQUEST,
TEAM_GET_TASK_ACTIVITY,
TEAM_GET_TASK_ATTACHMENT, TEAM_GET_TASK_ATTACHMENT,
TEAM_GET_TASK_CHANGE_PRESENCE, TEAM_GET_TASK_CHANGE_PRESENCE,
TEAM_GET_TASK_EXACT_LOG_DETAIL,
TEAM_GET_TASK_EXACT_LOG_SUMMARIES,
TEAM_GET_TASK_LOG_STREAM,
TEAM_KILL_PROCESS, TEAM_KILL_PROCESS,
TEAM_LAUNCH, TEAM_LAUNCH,
TEAM_LEAD_ACTIVITY, TEAM_LEAD_ACTIVITY,
@ -121,11 +121,11 @@ import {
} from './guards'; } from './guards';
import type { import type {
BranchStatusService,
BoardTaskActivityService, BoardTaskActivityService,
BoardTaskLogStreamService,
BoardTaskExactLogDetailService, BoardTaskExactLogDetailService,
BoardTaskExactLogsService, BoardTaskExactLogsService,
BoardTaskLogStreamService,
BranchStatusService,
MemberStatsComputer, MemberStatsComputer,
TeamDataService, TeamDataService,
TeammateToolTracker, TeammateToolTracker,
@ -140,9 +140,9 @@ import type {
AttachmentMeta, AttachmentMeta,
AttachmentPayload, AttachmentPayload,
BoardTaskActivityEntry, BoardTaskActivityEntry,
BoardTaskLogStreamResponse,
BoardTaskExactLogDetailResult, BoardTaskExactLogDetailResult,
BoardTaskExactLogSummariesResponse, BoardTaskExactLogSummariesResponse,
BoardTaskLogStreamResponse,
CreateTaskRequest, CreateTaskRequest,
EffortLevel, EffortLevel,
GlobalTask, GlobalTask,

View file

@ -7,13 +7,13 @@ import * as fs from 'fs/promises';
import * as path from 'path'; import * as path from 'path';
import * as readline from 'readline'; import * as readline from 'readline';
import { TeamConfigReader } from './TeamConfigReader';
import { TeamInboxReader } from './TeamInboxReader';
import { TeamMembersMetaStore } from './TeamMembersMetaStore';
import { import {
canonicalizeAgentTeamsToolName, canonicalizeAgentTeamsToolName,
lineHasAgentTeamsTaskBoundaryToolName, lineHasAgentTeamsTaskBoundaryToolName,
} from './agentTeamsToolNames'; } from './agentTeamsToolNames';
import { TeamConfigReader } from './TeamConfigReader';
import { TeamInboxReader } from './TeamInboxReader';
import { TeamMembersMetaStore } from './TeamMembersMetaStore';
import type { MemberLogSummary, MemberSubagentLogSummary } from '@shared/types'; import type { MemberLogSummary, MemberSubagentLogSummary } from '@shared/types';

View file

@ -1,9 +1,4 @@
export { BranchStatusService } from './BranchStatusService'; export { BranchStatusService } from './BranchStatusService';
export { BoardTaskActivityRecordSource } from './taskLogs/activity/BoardTaskActivityRecordSource';
export { BoardTaskActivityService } from './taskLogs/activity/BoardTaskActivityService';
export { BoardTaskExactLogsService } from './taskLogs/exact/BoardTaskExactLogsService';
export { BoardTaskExactLogDetailService } from './taskLogs/exact/BoardTaskExactLogDetailService';
export { BoardTaskLogStreamService } from './taskLogs/stream/BoardTaskLogStreamService';
export { CascadeGuard } from './CascadeGuard'; export { CascadeGuard } from './CascadeGuard';
export { ChangeExtractorService } from './ChangeExtractorService'; export { ChangeExtractorService } from './ChangeExtractorService';
export { ClaudeBinaryResolver } from './ClaudeBinaryResolver'; export { ClaudeBinaryResolver } from './ClaudeBinaryResolver';
@ -15,6 +10,11 @@ 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 { BoardTaskActivityService } from './taskLogs/activity/BoardTaskActivityService';
export { BoardTaskExactLogDetailService } from './taskLogs/exact/BoardTaskExactLogDetailService';
export { BoardTaskExactLogsService } from './taskLogs/exact/BoardTaskExactLogsService';
export { BoardTaskLogStreamService } from './taskLogs/stream/BoardTaskLogStreamService';
export { TeamAttachmentStore } from './TeamAttachmentStore'; export { TeamAttachmentStore } from './TeamAttachmentStore';
export { TeamBackupService } from './TeamBackupService'; export { TeamBackupService } from './TeamBackupService';
export { TeamConfigReader } from './TeamConfigReader'; export { TeamConfigReader } from './TeamConfigReader';

View file

@ -1,8 +1,8 @@
import { BoardTaskActivityRecordBuilder } from './BoardTaskActivityRecordBuilder'; import { BoardTaskActivityRecordBuilder } from './BoardTaskActivityRecordBuilder';
import type { BoardTaskActivityEntry, TeamTask } from '@shared/types';
import type { RawTaskActivityMessage } from './BoardTaskActivityTranscriptReader';
import type { BoardTaskActivityRecord } from './BoardTaskActivityRecord'; import type { BoardTaskActivityRecord } from './BoardTaskActivityRecord';
import type { RawTaskActivityMessage } from './BoardTaskActivityTranscriptReader';
import type { BoardTaskActivityEntry, TeamTask } from '@shared/types';
function cloneTaskRef(task: BoardTaskActivityRecord['task']): BoardTaskActivityEntry['task'] { function cloneTaskRef(task: BoardTaskActivityRecord['task']): BoardTaskActivityEntry['task'] {
return { return {

View file

@ -1,6 +1,12 @@
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { getTaskDisplayId } from '@shared/utils/taskIdentity'; import { getTaskDisplayId } from '@shared/utils/taskIdentity';
import type {
ParsedBoardTaskLink,
ParsedBoardTaskToolAction,
} from '../contract/BoardTaskTranscriptContract';
import type { BoardTaskActivityRecord } from './BoardTaskActivityRecord';
import type { RawTaskActivityMessage } from './BoardTaskActivityTranscriptReader';
import type { import type {
BoardTaskActivityAction, BoardTaskActivityAction,
BoardTaskActivityActor, BoardTaskActivityActor,
@ -10,12 +16,6 @@ import type {
TaskRef, TaskRef,
TeamTask, TeamTask,
} from '@shared/types'; } from '@shared/types';
import type { RawTaskActivityMessage } from './BoardTaskActivityTranscriptReader';
import type {
ParsedBoardTaskLink,
ParsedBoardTaskToolAction,
} from '../contract/BoardTaskTranscriptContract';
import type { BoardTaskActivityRecord } from './BoardTaskActivityRecord';
interface TaskLookup { interface TaskLookup {
byId: Map<string, TeamTask>; byId: Map<string, TeamTask>;

View file

@ -1,5 +1,6 @@
import { TeamTaskReader } from '../../TeamTaskReader'; import { TeamTaskReader } from '../../TeamTaskReader';
import { TeamTranscriptSourceLocator } from '../discovery/TeamTranscriptSourceLocator'; import { TeamTranscriptSourceLocator } from '../discovery/TeamTranscriptSourceLocator';
import { BoardTaskActivityRecordBuilder } from './BoardTaskActivityRecordBuilder'; import { BoardTaskActivityRecordBuilder } from './BoardTaskActivityRecordBuilder';
import { BoardTaskActivityTranscriptReader } from './BoardTaskActivityTranscriptReader'; import { BoardTaskActivityTranscriptReader } from './BoardTaskActivityTranscriptReader';

View file

@ -1,11 +1,9 @@
import { yieldToEventLoop } from '@main/utils/asyncYield';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { createReadStream } from 'fs'; import { createReadStream } from 'fs';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as readline from 'readline'; import * as readline from 'readline';
import { yieldToEventLoop } from '@main/utils/asyncYield';
import { BoardTaskActivityParseCache } from './BoardTaskActivityParseCache';
import { import {
parseBoardTaskLinks, parseBoardTaskLinks,
parseBoardTaskToolActions, parseBoardTaskToolActions,
@ -13,6 +11,8 @@ import {
type ParsedBoardTaskToolAction, type ParsedBoardTaskToolAction,
} from '../contract/BoardTaskTranscriptContract'; } from '../contract/BoardTaskTranscriptContract';
import { BoardTaskActivityParseCache } from './BoardTaskActivityParseCache';
const logger = createLogger('Service:BoardTaskActivityTranscriptReader'); const logger = createLogger('Service:BoardTaskActivityTranscriptReader');
export interface RawTaskActivityMessage { export interface RawTaskActivityMessage {

View file

@ -1,4 +1,5 @@
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import type { import type {
BoardTaskActivityLinkKind, BoardTaskActivityLinkKind,
BoardTaskActivityPhase, BoardTaskActivityPhase,

View file

@ -1,13 +1,14 @@
import { getTaskDisplayId, taskMatchesRef } from '@shared/utils/taskIdentity';
import { TeamTaskReader } from '../../TeamTaskReader'; import { TeamTaskReader } from '../../TeamTaskReader';
import { BoardTaskActivityRecordSource } from '../activity/BoardTaskActivityRecordSource'; import { BoardTaskActivityRecordSource } from '../activity/BoardTaskActivityRecordSource';
import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord';
import { TeamTranscriptSourceLocator } from '../discovery/TeamTranscriptSourceLocator'; import { TeamTranscriptSourceLocator } from '../discovery/TeamTranscriptSourceLocator';
import { BoardTaskExactLogStrictParser } from '../exact/BoardTaskExactLogStrictParser'; import { BoardTaskExactLogStrictParser } from '../exact/BoardTaskExactLogStrictParser';
import { BoardTaskLogStreamService } from '../stream/BoardTaskLogStreamService'; import { BoardTaskLogStreamService } from '../stream/BoardTaskLogStreamService';
import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord';
import type { ParsedMessage } from '@main/types'; import type { ParsedMessage } from '@main/types';
import type { TeamTask, TaskWorkInterval } from '@shared/types'; import type { TaskWorkInterval, TeamTask } from '@shared/types';
import { getTaskDisplayId, taskMatchesRef } from '@shared/utils/taskIdentity';
const BOARD_MCP_TOOL_PREFIXES = ['mcp__agent-teams__', 'mcp__agent_teams__'] as const; const BOARD_MCP_TOOL_PREFIXES = ['mcp__agent-teams__', 'mcp__agent_teams__'] as const;
const MAX_EXAMPLES = 10; const MAX_EXAMPLES = 10;

View file

@ -1,12 +1,12 @@
import { extractToolCalls, extractToolResults } from '@main/utils/toolExtraction'; import { extractToolCalls, extractToolResults } from '@main/utils/toolExtraction';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import type { ContentBlock, ParsedMessage } from '@main/types';
import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord'; import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord';
import type { import type {
BoardTaskExactLogDetailCandidate,
BoardTaskExactLogBundleCandidate, BoardTaskExactLogBundleCandidate,
BoardTaskExactLogDetailCandidate,
} from './BoardTaskExactLogTypes'; } from './BoardTaskExactLogTypes';
import type { ContentBlock, ParsedMessage } from '@main/types';
const logger = createLogger('Service:BoardTaskExactLogDetailSelector'); const logger = createLogger('Service:BoardTaskExactLogDetailSelector');
@ -63,7 +63,7 @@ function cloneBlock<T extends ContentBlock>(block: T): T {
} as T; } as T;
} }
return { ...block } as T; return { ...block };
} }
function filterAssistantContent( function filterAssistantContent(
@ -249,7 +249,7 @@ function deduplicateAssistantMessagesByRequestId(
continue; continue;
} }
const existingRank = anchorEvidenceRank(messages[existingIndex]!, toolUseId); const existingRank = anchorEvidenceRank(messages[existingIndex], toolUseId);
const nextRank = anchorEvidenceRank(message, toolUseId); const nextRank = anchorEvidenceRank(message, toolUseId);
if (nextRank > existingRank || (nextRank === existingRank && i > existingIndex)) { if (nextRank > existingRank || (nextRank === existingRank && i > existingIndex)) {
preferredAssistantIndexByRequestId.set(message.requestId, i); preferredAssistantIndexByRequestId.set(message.requestId, i);

View file

@ -1,10 +1,11 @@
import { BoardTaskActivityRecordSource } from '../activity/BoardTaskActivityRecordSource'; import { BoardTaskActivityRecordSource } from '../activity/BoardTaskActivityRecordSource';
import { BoardTaskExactLogChunkBuilder } from './BoardTaskExactLogChunkBuilder'; import { BoardTaskExactLogChunkBuilder } from './BoardTaskExactLogChunkBuilder';
import { BoardTaskExactLogDetailSelector } from './BoardTaskExactLogDetailSelector'; import { BoardTaskExactLogDetailSelector } from './BoardTaskExactLogDetailSelector';
import { BoardTaskExactLogStrictParser } from './BoardTaskExactLogStrictParser'; import { BoardTaskExactLogStrictParser } from './BoardTaskExactLogStrictParser';
import { BoardTaskExactLogSummarySelector } from './BoardTaskExactLogSummarySelector';
import { isBoardTaskExactLogsReadEnabled } from './featureGates'; import { isBoardTaskExactLogsReadEnabled } from './featureGates';
import { getBoardTaskExactLogFileVersions } from './fileVersions'; import { getBoardTaskExactLogFileVersions } from './fileVersions';
import { BoardTaskExactLogSummarySelector } from './BoardTaskExactLogSummarySelector';
import type { BoardTaskExactLogDetailResult } from '@shared/types'; import type { BoardTaskExactLogDetailResult } from '@shared/types';

View file

@ -1,11 +1,10 @@
import { yieldToEventLoop } from '@main/utils/asyncYield';
import { parseJsonlLine } from '@main/utils/jsonl';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { createReadStream } from 'fs'; import { createReadStream } from 'fs';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as readline from 'readline'; import * as readline from 'readline';
import { yieldToEventLoop } from '@main/utils/asyncYield';
import { parseJsonlLine } from '@main/utils/jsonl';
import { BoardTaskExactLogsParseCache } from './BoardTaskExactLogsParseCache'; import { BoardTaskExactLogsParseCache } from './BoardTaskExactLogsParseCache';
import type { ParsedMessage } from '@main/types'; import type { ParsedMessage } from '@main/types';

View file

@ -1,7 +1,6 @@
import { createHash } from 'crypto';
import { describeBoardTaskActivityLabel } from '@shared/utils/boardTaskActivityLabels'; import { describeBoardTaskActivityLabel } from '@shared/utils/boardTaskActivityLabels';
import { createLogger } from '@shared/utils/logger'; import { createLogger } from '@shared/utils/logger';
import { createHash } from 'crypto';
import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord'; import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord';
import type { import type {
@ -75,7 +74,7 @@ function sourceGenerationFor(
version: BoardTaskExactLogFileVersion | undefined version: BoardTaskExactLogFileVersion | undefined
): string | null { ): string | null {
if (!version) return null; if (!version) return null;
const hash = createHash('sha1'); const hash = createHash('sha256');
hash.update(anchor.filePath); hash.update(anchor.filePath);
hash.update('\0'); hash.update('\0');
hash.update(String(version.size)); hash.update(String(version.size));

View file

@ -1,3 +1,4 @@
import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord';
import type { ParsedMessage } from '@main/types'; import type { ParsedMessage } from '@main/types';
import type { import type {
BoardTaskActivityCategory, BoardTaskActivityCategory,
@ -7,7 +8,6 @@ import type {
BoardTaskExactLogSource, BoardTaskExactLogSource,
BoardTaskExactLogSummary, BoardTaskExactLogSummary,
} from '@shared/types'; } from '@shared/types';
import type { BoardTaskActivityRecord } from '../activity/BoardTaskActivityRecord';
export interface BoardTaskExactLogFileVersion { export interface BoardTaskExactLogFileVersion {
filePath: string; filePath: string;

View file

@ -1,8 +1,9 @@
import { BoardTaskActivityRecordSource } from '../activity/BoardTaskActivityRecordSource'; import { BoardTaskActivityRecordSource } from '../activity/BoardTaskActivityRecordSource';
import { isBoardTaskExactLogsReadEnabled } from './featureGates';
import { getBoardTaskExactLogFileVersions } from './fileVersions';
import { BoardTaskExactLogSummarySelector } from './BoardTaskExactLogSummarySelector'; import { BoardTaskExactLogSummarySelector } from './BoardTaskExactLogSummarySelector';
import { mapCandidateToSummary } from './BoardTaskExactLogTypes'; import { mapCandidateToSummary } from './BoardTaskExactLogTypes';
import { isBoardTaskExactLogsReadEnabled } from './featureGates';
import { getBoardTaskExactLogFileVersions } from './fileVersions';
import type { BoardTaskExactLogSummariesResponse } from '@shared/types'; import type { BoardTaskExactLogSummariesResponse } from '@shared/types';

View file

@ -4,10 +4,11 @@ import { BoardTaskActivityRecordSource } from '../activity/BoardTaskActivityReco
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 { BoardTaskExactLogSummarySelector } from '../exact/BoardTaskExactLogSummarySelector';
import { isBoardTaskExactLogsReadEnabled } from '../exact/featureGates'; import { isBoardTaskExactLogsReadEnabled } from '../exact/featureGates';
import { getBoardTaskExactLogFileVersions } from '../exact/fileVersions'; import { getBoardTaskExactLogFileVersions } from '../exact/fileVersions';
import { BoardTaskExactLogSummarySelector } from '../exact/BoardTaskExactLogSummarySelector';
import type { BoardTaskExactLogDetailCandidate } from '../exact/BoardTaskExactLogTypes';
import type { ContentBlock, ParsedMessage, ToolUseResultData } from '@main/types'; import type { ContentBlock, ParsedMessage, ToolUseResultData } from '@main/types';
import type { import type {
BoardTaskActivityCategory, BoardTaskActivityCategory,
@ -16,7 +17,6 @@ import type {
BoardTaskLogSegment, BoardTaskLogSegment,
BoardTaskLogStreamResponse, BoardTaskLogStreamResponse,
} from '@shared/types'; } from '@shared/types';
import type { BoardTaskExactLogDetailCandidate } from '../exact/BoardTaskExactLogTypes';
interface StreamSlice { interface StreamSlice {
id: string; id: string;
@ -533,7 +533,7 @@ function cloneBlock<T extends ContentBlock>(block: T): T {
} as T; } as T;
} }
return { ...block } as T; return { ...block };
} }
function cloneMessageContent(content: ParsedMessage['content']): ParsedMessage['content'] { function cloneMessageContent(content: ParsedMessage['content']): ParsedMessage['content'] {
@ -655,7 +655,7 @@ function rebuildMergedMessage(
} }
function mergeMessages( function mergeMessages(
details: Array<{ filePath: string; filteredMessages: ParsedMessage[] }> details: { filePath: string; filteredMessages: ParsedMessage[] }[]
): ParsedMessage[] { ): ParsedMessage[] {
const byMessageKey = new Map<string, MergedMessageAccumulator>(); const byMessageKey = new Map<string, MergedMessageAccumulator>();
let order = 0; let order = 0;
@ -808,8 +808,8 @@ export class BoardTaskLogStreamService {
const flushSegment = (): void => { const flushSegment = (): void => {
if (currentSegmentSlices.length === 0) return; if (currentSegmentSlices.length === 0) return;
const participantKey = currentSegmentSlices[0]!.participantKey; const participantKey = currentSegmentSlices[0].participantKey;
const actor = currentSegmentSlices[0]!.actor; const actor = currentSegmentSlices[0].actor;
const mergedMessages = mergeMessages( const mergedMessages = mergeMessages(
currentSegmentSlices.map((slice) => ({ currentSegmentSlices.map((slice) => ({
filePath: slice.filePath, filePath: slice.filePath,
@ -827,8 +827,8 @@ export class BoardTaskLogStreamService {
id: buildSegmentId(participantKey, currentSegmentSlices), id: buildSegmentId(participantKey, currentSegmentSlices),
participantKey, participantKey,
actor, actor,
startTimestamp: currentSegmentSlices[0]!.timestamp, startTimestamp: currentSegmentSlices[0].timestamp,
endTimestamp: currentSegmentSlices[currentSegmentSlices.length - 1]!.timestamp, endTimestamp: currentSegmentSlices[currentSegmentSlices.length - 1].timestamp,
chunks, chunks,
}); });
} }
@ -838,7 +838,7 @@ export class BoardTaskLogStreamService {
for (const slice of visibleSlices) { for (const slice of visibleSlices) {
if ( if (
currentSegmentSlices.length > 0 && currentSegmentSlices.length > 0 &&
currentSegmentSlices[0]!.participantKey !== slice.participantKey currentSegmentSlices[0].participantKey !== slice.participantKey
) { ) {
flushSegment(); flushSegment();
} }
@ -847,7 +847,7 @@ export class BoardTaskLogStreamService {
flushSegment(); flushSegment();
const namedParticipants = orderedParticipants.filter((participant) => !participant.isLead); const namedParticipants = orderedParticipants.filter((participant) => !participant.isLead);
const defaultFilter = namedParticipants.length === 1 ? namedParticipants[0]!.key : 'all'; const defaultFilter = namedParticipants.length === 1 ? namedParticipants[0].key : 'all';
return { return {
participants: orderedParticipants, participants: orderedParticipants,

View file

@ -124,17 +124,17 @@ import {
TEAM_GET_DATA, TEAM_GET_DATA,
TEAM_GET_DELETED_TASKS, TEAM_GET_DELETED_TASKS,
TEAM_GET_LOGS_FOR_TASK, TEAM_GET_LOGS_FOR_TASK,
TEAM_GET_TASK_ACTIVITY,
TEAM_GET_TASK_LOG_STREAM,
TEAM_GET_TASK_EXACT_LOG_DETAIL,
TEAM_GET_TASK_EXACT_LOG_SUMMARIES,
TEAM_GET_MEMBER_LOGS, TEAM_GET_MEMBER_LOGS,
TEAM_GET_MEMBER_STATS, TEAM_GET_MEMBER_STATS,
TEAM_GET_MESSAGES_PAGE, TEAM_GET_MESSAGES_PAGE,
TEAM_GET_PROJECT_BRANCH, TEAM_GET_PROJECT_BRANCH,
TEAM_GET_SAVED_REQUEST, TEAM_GET_SAVED_REQUEST,
TEAM_GET_TASK_ACTIVITY,
TEAM_GET_TASK_ATTACHMENT, TEAM_GET_TASK_ATTACHMENT,
TEAM_GET_TASK_CHANGE_PRESENCE, TEAM_GET_TASK_CHANGE_PRESENCE,
TEAM_GET_TASK_EXACT_LOG_DETAIL,
TEAM_GET_TASK_EXACT_LOG_SUMMARIES,
TEAM_GET_TASK_LOG_STREAM,
TEAM_KILL_PROCESS, TEAM_KILL_PROCESS,
TEAM_LAUNCH, TEAM_LAUNCH,
TEAM_LEAD_ACTIVITY, TEAM_LEAD_ACTIVITY,
@ -233,9 +233,9 @@ import type {
ApplyReviewResult, ApplyReviewResult,
AttachmentFileData, AttachmentFileData,
BoardTaskActivityEntry, BoardTaskActivityEntry,
BoardTaskLogStreamResponse,
BoardTaskExactLogDetailResult, BoardTaskExactLogDetailResult,
BoardTaskExactLogSummariesResponse, BoardTaskExactLogSummariesResponse,
BoardTaskLogStreamResponse,
ChangeStats, ChangeStats,
ClaudeRootFolderSelection, ClaudeRootFolderSelection,
ClaudeRootInfo, ClaudeRootInfo,

View file

@ -9,9 +9,9 @@
import type { import type {
AppConfig, AppConfig,
AttachmentFileData, AttachmentFileData,
BoardTaskLogStreamResponse,
BoardTaskExactLogDetailResult, BoardTaskExactLogDetailResult,
BoardTaskExactLogSummariesResponse, BoardTaskExactLogSummariesResponse,
BoardTaskLogStreamResponse,
ClaudeMdFileInfo, ClaudeMdFileInfo,
ClaudeRootFolderSelection, ClaudeRootFolderSelection,
ClaudeRootInfo, ClaudeRootInfo,

View file

@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Button } from '@renderer/components/ui/button';
import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo'; import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
import { Button } from '@renderer/components/ui/button';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -1059,7 +1059,7 @@ export const ProviderRuntimeSettingsDialog = ({
) : null} ) : null}
</div> </div>
{showApiKeySection ? ( {showApiKeySection && apiKeyConfig ? (
<div <div
className="space-y-3 rounded-md border p-3" className="space-y-3 rounded-md border p-3"
style={{ borderColor: 'var(--color-border-subtle)' }} style={{ borderColor: 'var(--color-border-subtle)' }}
@ -1069,7 +1069,7 @@ export const ProviderRuntimeSettingsDialog = ({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
data-testid="provider-api-key-icon" data-testid="provider-api-key-icon"
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border" className="flex size-8 shrink-0 items-center justify-center rounded-md border"
style={{ style={{
borderColor: 'var(--color-border-subtle)', borderColor: 'var(--color-border-subtle)',
backgroundColor: 'rgba(255,255,255,0.03)', backgroundColor: 'rgba(255,255,255,0.03)',

View file

@ -1,4 +1,4 @@
import { useState, type ReactNode } from 'react'; import { type ReactNode, useState } from 'react';
import { CARD_BG, CARD_BORDER_STYLE, CARD_ICON_MUTED } from '@renderer/constants/cssVariables'; import { CARD_BG, CARD_BORDER_STYLE, CARD_ICON_MUTED } from '@renderer/constants/cssVariables';
import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors'; import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors';

View file

@ -575,17 +575,17 @@ function renderInlineBoldSummary(
}); });
} }
function TaskRecipientBadge({ const TaskRecipientBadge = ({
taskId, taskId,
displayId, displayId,
teamName, teamName,
onTaskIdClick, onTaskIdClick,
}: { }: Readonly<{
taskId: string; taskId: string;
displayId: string; displayId: string;
teamName?: string; teamName?: string;
onTaskIdClick?: (taskId: string) => void; onTaskIdClick?: (taskId: string) => void;
}): React.JSX.Element { }>): React.JSX.Element => {
const content = ( const content = (
<span <span
className="inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium tracking-wide" className="inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-medium tracking-wide"
@ -617,10 +617,10 @@ function TaskRecipientBadge({
</button> </button>
</TaskTooltip> </TaskTooltip>
); );
} };
export const ActivityItem = memo( export const ActivityItem = memo(
function ActivityItem({ ({
message, message,
teamName, teamName,
localMemberNames, localMemberNames,
@ -647,7 +647,7 @@ export const ActivityItem = memo(
onExpand, onExpand,
expandItemKey, expandItemKey,
onExpandContent, onExpandContent,
}: ActivityItemProps): React.JSX.Element { }: Readonly<ActivityItemProps>): React.JSX.Element => {
const colors = getTeamColorSet(memberColor ?? message.color ?? ''); const colors = getTeamColorSet(memberColor ?? message.color ?? '');
const { isLight } = useTheme(); const { isLight } = useTheme();
// Hide role when it matches the sender name (avoids "lead" badge + "Team Lead" text duplication) // Hide role when it matches the sender name (avoids "lead" badge + "Team Lead" text duplication)
@ -1489,3 +1489,5 @@ export const ActivityItem = memo(
prev.onExpandContent === next.onExpandContent && prev.onExpandContent === next.onExpandContent &&
areMessagesEquivalentForActivityItem(prev.message, next.message) areMessagesEquivalentForActivityItem(prev.message, next.message)
); );
ActivityItem.displayName = 'ActivityItem';

View file

@ -41,8 +41,8 @@ import {
normalizeCreateLaunchProviderForUi, normalizeCreateLaunchProviderForUi,
} from '@renderer/utils/geminiUiFreeze'; } from '@renderer/utils/geminiUiFreeze';
import { normalizePath } from '@renderer/utils/pathNormalize'; import { normalizePath } from '@renderer/utils/pathNormalize';
import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog';
import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability'; import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability';
import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog';
import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
import { AlertTriangle, CheckCircle2, Info, Loader2, X } from 'lucide-react'; import { AlertTriangle, CheckCircle2, Info, Loader2, X } from 'lucide-react';

View file

@ -43,8 +43,8 @@ import {
} from '@renderer/utils/geminiUiFreeze'; } from '@renderer/utils/geminiUiFreeze';
import { normalizePath } from '@renderer/utils/pathNormalize'; import { normalizePath } from '@renderer/utils/pathNormalize';
import { nameColorSet } from '@renderer/utils/projectColor'; import { nameColorSet } from '@renderer/utils/projectColor';
import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog';
import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability'; import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability';
import { getTeamProviderLabel as getCatalogTeamProviderLabel } from '@renderer/utils/teamModelCatalog';
import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider'; import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
import { import {
AlertTriangle, AlertTriangle,

View file

@ -159,7 +159,7 @@ function estimateGridSkeletonCardHeight(
if ((task.blockedBy?.length ?? 0) > 0) height += 18; if ((task.blockedBy?.length ?? 0) > 0) height += 18;
if ((task.blocks?.length ?? 0) > 0) height += 18; if ((task.blocks?.length ?? 0) > 0) height += 18;
const effectiveReviewer = (kanbanState.tasks[task.id]?.reviewer ?? task.reviewer ?? '').trim(); const effectiveReviewer = (kanbanState.tasks[task.id]?.reviewer ?? '').trim();
if (columnId === 'review' && !hasReviewers && effectiveReviewer.length === 0) { if (columnId === 'review' && !hasReviewers && effectiveReviewer.length === 0) {
height += 14; height += 14;
} }

View file

@ -39,10 +39,10 @@ export interface KanbanGridColumn {
bodyBg?: string; bodyBg?: string;
content: React.ReactNode; content: React.ReactNode;
showAddButton?: boolean; showAddButton?: boolean;
skeletonCards?: Array<{ skeletonCards?: {
key: string; key: string;
height: number; height: number;
}>; }[];
} }
interface KanbanGridLayoutProps { interface KanbanGridLayoutProps {

View file

@ -3,18 +3,18 @@ import { useEffect, useMemo, useState } from 'react';
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';
import { buildInlineActivityEntries } from '@renderer/features/agent-graph/utils/buildInlineActivityEntries';
import { useMemberStats } from '@renderer/hooks/useMemberStats'; import { useMemberStats } from '@renderer/hooks/useMemberStats';
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react'; import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react';
import { buildInlineActivityEntries } from '@renderer/features/agent-graph/utils/buildInlineActivityEntries';
import { MemberDetailHeader } from './MemberDetailHeader'; import { MemberDetailHeader } from './MemberDetailHeader';
import { MemberDetailStats } from './MemberDetailStats'; import { MemberDetailStats } from './MemberDetailStats';
import { type MemberActivityFilter, type MemberDetailTab } from './memberDetailTypes';
import { MemberLogsTab } from './MemberLogsTab'; import { MemberLogsTab } from './MemberLogsTab';
import { MemberMessagesTab } from './MemberMessagesTab'; import { MemberMessagesTab } from './MemberMessagesTab';
import { MemberStatsTab } from './MemberStatsTab'; import { MemberStatsTab } from './MemberStatsTab';
import { MemberTasksTab } from './MemberTasksTab'; import { MemberTasksTab } from './MemberTasksTab';
import { type MemberActivityFilter, type MemberDetailTab } from './memberDetailTypes';
import type { import type {
InboxMessage, InboxMessage,

View file

@ -8,15 +8,15 @@ import {
} from '@renderer/components/team/activity/activityMessageContext'; } from '@renderer/components/team/activity/activityMessageContext';
import { MessageExpandDialog } from '@renderer/components/team/activity/MessageExpandDialog'; import { MessageExpandDialog } from '@renderer/components/team/activity/MessageExpandDialog';
import { Button } from '@renderer/components/ui/button'; import { Button } from '@renderer/components/ui/button';
import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead';
import { buildInlineActivityEntries } from '@renderer/features/agent-graph/utils/buildInlineActivityEntries'; import { buildInlineActivityEntries } from '@renderer/features/agent-graph/utils/buildInlineActivityEntries';
import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead';
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages'; import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
import { toMessageKey } from '@renderer/utils/teamMessageKey';
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering'; import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
import { toMessageKey } from '@renderer/utils/teamMessageKey';
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import type { TimelineItem } from '@renderer/components/team/activity/LeadThoughtsGroup';
import type { MemberActivityFilter } from './memberDetailTypes'; import type { MemberActivityFilter } from './memberDetailTypes';
import type { TimelineItem } from '@renderer/components/team/activity/LeadThoughtsGroup';
import type { InboxMessage, ResolvedTeamMember, TeamTaskWithKanban } from '@shared/types'; import type { InboxMessage, ResolvedTeamMember, TeamTaskWithKanban } from '@shared/types';
interface MemberMessagesTabProps { interface MemberMessagesTabProps {
@ -32,7 +32,7 @@ interface MemberMessagesTabProps {
const MAX_MESSAGES = 100; const MAX_MESSAGES = 100;
const MEMBER_MESSAGES_PAGE_SIZE = 50; const MEMBER_MESSAGES_PAGE_SIZE = 50;
const FILTER_OPTIONS: ReadonlyArray<{ value: MemberActivityFilter; label: string }> = [ const FILTER_OPTIONS: readonly { value: MemberActivityFilter; label: string }[] = [
{ value: 'all', label: 'All' }, { value: 'all', label: 'All' },
{ value: 'messages', label: 'Messages' }, { value: 'messages', label: 'Messages' },
{ value: 'comments', label: 'Comments' }, { value: 'comments', label: 'Comments' },

View file

@ -298,7 +298,7 @@ export const MessagesPanel = memo(function MessagesPanel({
if (position !== 'bottom-sheet' || typeof ResizeObserver === 'undefined') return; if (position !== 'bottom-sheet' || typeof ResizeObserver === 'undefined') return;
const mountPointElement = mountPoint instanceof HTMLElement ? mountPoint : null; const mountPointElement = mountPoint instanceof HTMLElement ? mountPoint : null;
const observedEntries: Array<[Element | null, (height: number) => void]> = [ const observedEntries: [Element | null, (height: number) => void][] = [
[bottomSheetStickyTopRef.current, setBottomSheetStickyTopHeight], [bottomSheetStickyTopRef.current, setBottomSheetStickyTopHeight],
[mountPointElement, setBottomSheetMountHeight], [mountPointElement, setBottomSheetMountHeight],
]; ];
@ -1096,7 +1096,7 @@ export const MessagesPanel = memo(function MessagesPanel({
{searchAndFilterControls} {searchAndFilterControls}
</div> </div>
)} )}
<div className="px-3 pb-3 pt-3"> <div className="p-3">
<MessageComposer <MessageComposer
teamName={teamName} teamName={teamName}
layout="compact" layout="compact"

View file

@ -1,7 +1,7 @@
import { MemberExecutionLog } from '@renderer/components/team/members/MemberExecutionLog'; import { MemberExecutionLog } from '@renderer/components/team/members/MemberExecutionLog';
import { asEnhancedChunkArray } from '@renderer/types/data';
import { ChevronDown, ChevronRight, Clock, FileText, Loader2 } from 'lucide-react'; import { ChevronDown, ChevronRight, Clock, FileText, Loader2 } from 'lucide-react';
import type { asEnhancedChunkArray } from '@renderer/types/data';
import type { BoardTaskExactLogSummary } from '@shared/types'; import type { BoardTaskExactLogSummary } from '@shared/types';
export interface ExactTaskLogDetailState { export interface ExactTaskLogDetailState {
@ -61,12 +61,12 @@ interface ExactTaskLogCardProps {
onToggle: () => void; onToggle: () => void;
} }
export function ExactTaskLogCard({ export const ExactTaskLogCard = ({
summary, summary,
expanded, expanded,
detailState, detailState,
onToggle, onToggle,
}: ExactTaskLogCardProps): React.JSX.Element { }: ExactTaskLogCardProps): React.JSX.Element => {
const loadStateText = describeDetailState(detailState); const loadStateText = describeDetailState(detailState);
return ( return (
@ -129,4 +129,4 @@ export function ExactTaskLogCard({
) : null} ) : null}
</div> </div>
); );
} };

View file

@ -13,10 +13,10 @@ interface ExactTaskLogsSectionProps {
taskId: string; taskId: string;
} }
export function ExactTaskLogsSection({ export const ExactTaskLogsSection = ({
teamName, teamName,
taskId, taskId,
}: ExactTaskLogsSectionProps): React.JSX.Element { }: ExactTaskLogsSectionProps): React.JSX.Element => {
const [summaries, setSummaries] = useState<BoardTaskExactLogSummary[]>([]); const [summaries, setSummaries] = useState<BoardTaskExactLogSummary[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -259,4 +259,4 @@ export function ExactTaskLogsSection({
)} )}
</div> </div>
); );
} };

View file

@ -1,18 +1,18 @@
import type { ComponentProps } from 'react';
import { MemberLogsTab } from '@renderer/components/team/members/MemberLogsTab'; import { MemberLogsTab } from '@renderer/components/team/members/MemberLogsTab';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import type { ComponentProps } from 'react';
interface ExecutionSessionsSectionProps extends ComponentProps<typeof MemberLogsTab> { interface ExecutionSessionsSectionProps extends ComponentProps<typeof MemberLogsTab> {
isRefreshing?: boolean; isRefreshing?: boolean;
isPreviewOnline?: boolean; isPreviewOnline?: boolean;
} }
export function ExecutionSessionsSection({ export const ExecutionSessionsSection = ({
isRefreshing = false, isRefreshing = false,
isPreviewOnline = false, isPreviewOnline = false,
...props ...props
}: ExecutionSessionsSectionProps): React.JSX.Element { }: ExecutionSessionsSectionProps): React.JSX.Element => {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -45,4 +45,4 @@ export function ExecutionSessionsSection({
<MemberLogsTab {...props} /> <MemberLogsTab {...props} />
</div> </div>
); );
} };

View file

@ -1,10 +1,11 @@
import { api } from '@renderer/api';
import { AlertCircle, Loader2 } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { api } from '@renderer/api';
import { import {
describeBoardTaskActivityLabel, describeBoardTaskActivityLabel,
formatBoardTaskActivityTaskLabel, formatBoardTaskActivityTaskLabel,
} from '@shared/utils/boardTaskActivityLabels'; } from '@shared/utils/boardTaskActivityLabels';
import { AlertCircle, Loader2 } from 'lucide-react';
import type { BoardTaskActivityEntry, BoardTaskActivityTaskRef } from '@shared/types'; import type { BoardTaskActivityEntry, BoardTaskActivityTaskRef } from '@shared/types';
@ -109,7 +110,7 @@ function actorLabel(entry: BoardTaskActivityEntry): string {
return 'unknown actor'; return 'unknown actor';
} }
function Row({ entry }: { entry: BoardTaskActivityEntry }): React.JSX.Element { const Row = ({ entry }: { entry: BoardTaskActivityEntry }): React.JSX.Element => {
const context = describeContext(entry); const context = describeContext(entry);
const tone = const tone =
entry.task.resolution === 'resolved' entry.task.resolution === 'resolved'
@ -135,12 +136,12 @@ function Row({ entry }: { entry: BoardTaskActivityEntry }): React.JSX.Element {
</div> </div>
</div> </div>
); );
} };
export function TaskActivitySection({ export const TaskActivitySection = ({
teamName, teamName,
taskId, taskId,
}: TaskActivitySectionProps): React.JSX.Element { }: TaskActivitySectionProps): React.JSX.Element => {
const [entries, setEntries] = useState<BoardTaskActivityEntry[]>([]); const [entries, setEntries] = useState<BoardTaskActivityEntry[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -243,4 +244,4 @@ export function TaskActivitySection({
{content} {content}
</div> </div>
); );
} };

View file

@ -54,7 +54,7 @@ function normalizeResponse(response: BoardTaskLogStreamResponse): BoardTaskLogSt
}; };
} }
function SegmentMarker({ segment }: { segment: BoardTaskLogSegment }): React.JSX.Element { const SegmentMarker = ({ segment }: { segment: BoardTaskLogSegment }): React.JSX.Element => {
return ( return (
<div className="mb-2 flex items-center gap-2 text-[10px] text-[var(--color-text-muted)]"> <div className="mb-2 flex items-center gap-2 text-[10px] text-[var(--color-text-muted)]">
<span className="rounded-full border border-[var(--color-border)] px-2 py-0.5 font-medium text-[var(--color-text-secondary)]"> <span className="rounded-full border border-[var(--color-border)] px-2 py-0.5 font-medium text-[var(--color-text-secondary)]">
@ -66,27 +66,27 @@ function SegmentMarker({ segment }: { segment: BoardTaskLogSegment }): React.JSX
</span> </span>
</div> </div>
); );
} };
function SegmentBlock({ const SegmentBlock = ({
segment, segment,
showHeader, showHeader,
}: { }: {
segment: BoardTaskLogSegment; segment: BoardTaskLogSegment;
showHeader: boolean; showHeader: boolean;
}): React.JSX.Element { }): React.JSX.Element => {
return ( return (
<div className="min-w-0 overflow-hidden"> <div className="min-w-0 overflow-hidden">
{showHeader ? <SegmentMarker segment={segment} /> : null} {showHeader ? <SegmentMarker segment={segment} /> : null}
<MemberExecutionLog chunks={segment.chunks} memberName={segment.actor.memberName} /> <MemberExecutionLog chunks={segment.chunks} memberName={segment.actor.memberName} />
</div> </div>
); );
} };
export function TaskLogStreamSection({ export const TaskLogStreamSection = ({
teamName, teamName,
taskId, taskId,
}: TaskLogStreamSectionProps): React.JSX.Element { }: TaskLogStreamSectionProps): React.JSX.Element => {
const [stream, setStream] = useState<BoardTaskLogStreamResponse | null>(null); const [stream, setStream] = useState<BoardTaskLogStreamResponse | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -219,4 +219,4 @@ export function TaskLogStreamSection({
)} )}
</div> </div>
); );
} };

View file

@ -1,7 +1,7 @@
import { ExecutionSessionsSection } from './ExecutionSessionsSection'; import { ExecutionSessionsSection } from './ExecutionSessionsSection';
import { isBoardTaskActivityUiEnabled, isBoardTaskExactLogsUiEnabled } from './featureGates';
import { TaskActivitySection } from './TaskActivitySection'; import { TaskActivitySection } from './TaskActivitySection';
import { TaskLogStreamSection } from './TaskLogStreamSection'; import { TaskLogStreamSection } from './TaskLogStreamSection';
import { isBoardTaskActivityUiEnabled, isBoardTaskExactLogsUiEnabled } from './featureGates';
import type { TeamTaskWithKanban } from '@shared/types'; import type { TeamTaskWithKanban } from '@shared/types';
@ -17,7 +17,7 @@ interface TaskLogsPanelProps {
onPreviewOnlineChange?: (isOnline: boolean) => void; onPreviewOnlineChange?: (isOnline: boolean) => void;
} }
export function TaskLogsPanel({ export const TaskLogsPanel = ({
teamName, teamName,
task, task,
taskSince, taskSince,
@ -27,7 +27,7 @@ export function TaskLogsPanel({
showSubagentPreview = false, showSubagentPreview = false,
showLeadPreview = false, showLeadPreview = false,
onPreviewOnlineChange, onPreviewOnlineChange,
}: TaskLogsPanelProps): React.JSX.Element { }: TaskLogsPanelProps): React.JSX.Element => {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{isBoardTaskActivityUiEnabled() ? ( {isBoardTaskActivityUiEnabled() ? (
@ -52,4 +52,4 @@ export function TaskLogsPanel({
/> />
</div> </div>
); );
} };

View file

@ -2,8 +2,8 @@ import { useMemo } from 'react';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { import {
selectTeamDataForName,
getCurrentProvisioningProgressForTeam, getCurrentProvisioningProgressForTeam,
selectTeamDataForName,
} from '@renderer/store/slices/teamSlice'; } from '@renderer/store/slices/teamSlice';
import { buildTeamProvisioningPresentation } from '@renderer/utils/teamProvisioningPresentation'; import { buildTeamProvisioningPresentation } from '@renderer/utils/teamProvisioningPresentation';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';

View file

@ -1134,7 +1134,7 @@ export const MentionableTextarea = React.forwardRef<HTMLTextAreaElement, Mention
surfaceShellMetrics.width > 0 && surfaceShellMetrics.width > 0 &&
surfaceShellMetrics.height > 0 ? ( surfaceShellMetrics.height > 0 ? (
<svg <svg
className="message-composer-orbit-svg pointer-events-none absolute inset-0 z-[16] h-full w-full" className="message-composer-orbit-svg pointer-events-none absolute inset-0 z-[16] size-full"
viewBox={`0 0 ${surfaceShellMetrics.width} ${surfaceShellMetrics.height}`} viewBox={`0 0 ${surfaceShellMetrics.width} ${surfaceShellMetrics.height}`}
aria-hidden="true" aria-hidden="true"
> >

View file

@ -23,11 +23,11 @@ import {
import { isInboxNoiseMessage } from '@shared/utils/inboxNoise'; import { isInboxNoiseMessage } from '@shared/utils/inboxNoise';
import { isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import { collapseOverflowStacksWithMeta } from '../utils/collapseOverflowStacks';
import { import {
buildInlineActivityEntries, buildInlineActivityEntries,
getGraphLeadMemberName, getGraphLeadMemberName,
} from '../utils/buildInlineActivityEntries'; } from '../utils/buildInlineActivityEntries';
import { collapseOverflowStacksWithMeta } from '../utils/collapseOverflowStacks';
import { import {
isTaskBlocked, isTaskBlocked,
isTaskInReviewCycle, isTaskInReviewCycle,
@ -35,8 +35,8 @@ import {
} from '../utils/taskGraphSemantics'; } from '../utils/taskGraphSemantics';
import type { import type {
GraphDataPort,
GraphActivityItem, GraphActivityItem,
GraphDataPort,
GraphEdge, GraphEdge,
GraphNode, GraphNode,
GraphNodeState, GraphNodeState,
@ -48,8 +48,8 @@ import type {
LeadActivityState, LeadActivityState,
MemberSpawnStatusEntry, MemberSpawnStatusEntry,
MemberSpawnStatusesSnapshot, MemberSpawnStatusesSnapshot,
TeamProvisioningProgress,
TeamData, TeamData,
TeamProvisioningProgress,
} from '@shared/types/team'; } from '@shared/types/team';
import type { LeadContextUsage } from '@shared/types/team'; import type { LeadContextUsage } from '@shared/types/team';

View file

@ -6,8 +6,8 @@ import {
resolveMessageRenderProps, resolveMessageRenderProps,
} from '@renderer/components/team/activity/activityMessageContext'; } from '@renderer/components/team/activity/activityMessageContext';
import { MessageExpandDialog } from '@renderer/components/team/activity/MessageExpandDialog'; import { MessageExpandDialog } from '@renderer/components/team/activity/MessageExpandDialog';
import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead';
import { useStableTeamMentionMeta } from '@renderer/hooks/useStableTeamMentionMeta'; import { useStableTeamMentionMeta } from '@renderer/hooks/useStableTeamMentionMeta';
import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import { selectTeamDataForName } from '@renderer/store/slices/teamSlice'; import { selectTeamDataForName } from '@renderer/store/slices/teamSlice';
import { toMessageKey } from '@renderer/utils/teamMessageKey'; import { toMessageKey } from '@renderer/utils/teamMessageKey';
@ -19,12 +19,12 @@ import {
type InlineActivityEntry, type InlineActivityEntry,
} from '../utils/buildInlineActivityEntries'; } from '../utils/buildInlineActivityEntries';
import type { GraphNode } from '@claude-teams/agent-graph';
import type { TimelineItem } from '@renderer/components/team/activity/LeadThoughtsGroup'; import type { TimelineItem } from '@renderer/components/team/activity/LeadThoughtsGroup';
import type { import type {
MemberActivityFilter, MemberActivityFilter,
MemberDetailTab, MemberDetailTab,
} from '@renderer/components/team/members/memberDetailTypes'; } from '@renderer/components/team/members/memberDetailTypes';
import type { GraphNode } from '@claude-teams/agent-graph';
import type { ResolvedTeamMember } from '@shared/types/team'; import type { ResolvedTeamMember } from '@shared/types/team';
interface GraphActivityHudProps { interface GraphActivityHudProps {
@ -46,7 +46,7 @@ interface GraphActivityHudProps {
) => void; ) => void;
} }
export function GraphActivityHud({ export const GraphActivityHud = ({
teamName, teamName,
nodes, nodes,
getActivityAnchorScreenPlacement, getActivityAnchorScreenPlacement,
@ -55,7 +55,7 @@ export function GraphActivityHud({
enabled = true, enabled = true,
onOpenTaskDetail, onOpenTaskDetail,
onOpenMemberProfile, onOpenMemberProfile,
}: GraphActivityHudProps): React.JSX.Element | null { }: GraphActivityHudProps): React.JSX.Element | null => {
const shellRefs = useRef(new Map<string, HTMLDivElement | null>()); const shellRefs = useRef(new Map<string, HTMLDivElement | null>());
const connectorRefs = useRef(new Map<string, SVGSVGElement | null>()); const connectorRefs = useRef(new Map<string, SVGSVGElement | null>());
const connectorPathRefs = useRef(new Map<string, SVGPathElement | null>()); const connectorPathRefs = useRef(new Map<string, SVGPathElement | null>());
@ -149,8 +149,7 @@ export function GraphActivityHud({
const connectorPath = connectorPathRefs.current.get(lane.node.id) ?? null; const connectorPath = connectorPathRefs.current.get(lane.node.id) ?? null;
const placement = getActivityAnchorScreenPlacement(lane.node.id); const placement = getActivityAnchorScreenPlacement(lane.node.id);
const nodeScreen = getNodeScreenPosition(lane.node.id); if (!placement?.visible) {
if (!placement || !placement.visible || !nodeScreen || !nodeScreen.visible) {
shell.style.opacity = '0'; shell.style.opacity = '0';
if (connector) { if (connector) {
connector.style.opacity = '0'; connector.style.opacity = '0';
@ -163,6 +162,11 @@ export function GraphActivityHud({
shell.style.transform = `translate(${Math.round(placement.x)}px, ${Math.round(placement.y)}px) scale(${placement.scale.toFixed(3)})`; shell.style.transform = `translate(${Math.round(placement.x)}px, ${Math.round(placement.y)}px) scale(${placement.scale.toFixed(3)})`;
if (connector && connectorPath) { if (connector && connectorPath) {
const nodeScreen = getNodeScreenPosition(lane.node.id);
if (!nodeScreen?.visible) {
connector.style.opacity = '0';
continue;
}
const scaledWidth = (shell.offsetWidth || 296) * placement.scale; const scaledWidth = (shell.offsetWidth || 296) * placement.scale;
const laneCenterX = placement.x + scaledWidth / 2; const laneCenterX = placement.x + scaledWidth / 2;
const laneIsLeft = laneCenterX < nodeScreen.x; const laneIsLeft = laneCenterX < nodeScreen.x;
@ -377,4 +381,4 @@ export function GraphActivityHud({
/> />
</> </>
); );
} };

View file

@ -54,7 +54,7 @@ export interface GraphBlockingEdgePopoverProps {
onOpenTaskDetail?: (taskId: string) => void; onOpenTaskDetail?: (taskId: string) => void;
} }
export function GraphBlockingEdgePopover({ export const GraphBlockingEdgePopover = ({
teamName, teamName,
edge, edge,
sourceNode, sourceNode,
@ -62,7 +62,7 @@ export function GraphBlockingEdgePopover({
onClose, onClose,
onSelectNode, onSelectNode,
onOpenTaskDetail, onOpenTaskDetail,
}: GraphBlockingEdgePopoverProps): React.JSX.Element { }: GraphBlockingEdgePopoverProps): React.JSX.Element => {
const teamData = useStore((state) => selectTeamDataForName(state, teamName)); const teamData = useStore((state) => selectTeamDataForName(state, 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)),
@ -153,7 +153,7 @@ export function GraphBlockingEdgePopover({
</div> </div>
</div> </div>
); );
} };
function resolveEdgeTaskPreview( function resolveEdgeTaskPreview(
node: GraphNode | undefined, node: GraphNode | undefined,
@ -173,7 +173,7 @@ function resolveEdgeTaskPreview(
.slice(0, 4); .slice(0, 4);
} }
function HiddenTaskPreview({ const HiddenTaskPreview = ({
title, title,
tasks, tasks,
onOpenTaskDetail, onOpenTaskDetail,
@ -183,10 +183,10 @@ function HiddenTaskPreview({
tasks: TeamTaskWithKanban[]; tasks: TeamTaskWithKanban[];
onOpenTaskDetail?: (taskId: string) => void; onOpenTaskDetail?: (taskId: string) => void;
onClose: () => void; onClose: () => void;
}): React.JSX.Element { }): React.JSX.Element => {
return ( return (
<div className="mt-2 rounded border border-red-500/15 bg-red-500/5 px-2 py-2"> <div className="mt-2 rounded border border-red-500/15 bg-red-500/5 p-2">
<div className="text-[10px] uppercase tracking-[0.1em] text-red-300/80">{title}</div> <div className="text-[10px] uppercase tracking-widest text-red-300/80">{title}</div>
<div className="mt-1 space-y-1"> <div className="mt-1 space-y-1">
{tasks.map((task) => ( {tasks.map((task) => (
<button <button
@ -204,4 +204,4 @@ function HiddenTaskPreview({
</div> </div>
</div> </div>
); );
} };

View file

@ -17,6 +17,7 @@ import { ExternalLink, Loader2, MessageSquare, Plus, User } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { isTaskInReviewCycle, resolveTaskReviewer } from '../utils/taskGraphSemantics'; import { isTaskInReviewCycle, resolveTaskReviewer } from '../utils/taskGraphSemantics';
import { GraphTaskCard } from './GraphTaskCard'; import { GraphTaskCard } from './GraphTaskCard';
import type { GraphNode } from '@claude-teams/agent-graph'; import type { GraphNode } from '@claude-teams/agent-graph';
@ -225,7 +226,7 @@ const OverflowPopoverContent = ({
<button <button
key={task.id} key={task.id}
type="button" type="button"
className="flex w-full items-start justify-between gap-2 rounded border border-[var(--color-border)] bg-[var(--color-surface-secondary)] px-2 py-2 text-left transition-colors hover:border-[var(--color-border-emphasis)]" className="flex w-full items-start justify-between gap-2 rounded border border-[var(--color-border)] bg-[var(--color-surface-secondary)] p-2 text-left transition-colors hover:border-[var(--color-border-emphasis)]"
onClick={() => { onClick={() => {
onOpenTaskDetail?.(task.id); onOpenTaskDetail?.(task.id);
onClose(); onClose();

View file

@ -1,5 +1,9 @@
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { DISPLAY_STEPS } from '@renderer/components/team/provisioningSteps';
import { StepProgressBar } from '@renderer/components/team/StepProgressBar';
import { TeamProvisioningPanel } from '@renderer/components/team/TeamProvisioningPanel';
import { useTeamProvisioningPresentation } from '@renderer/components/team/useTeamProvisioningPresentation';
import { Badge } from '@renderer/components/ui/badge'; import { Badge } from '@renderer/components/ui/badge';
import { import {
Dialog, Dialog,
@ -8,10 +12,6 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from '@renderer/components/ui/dialog'; } from '@renderer/components/ui/dialog';
import { TeamProvisioningPanel } from '@renderer/components/team/TeamProvisioningPanel';
import { StepProgressBar } from '@renderer/components/team/StepProgressBar';
import { useTeamProvisioningPresentation } from '@renderer/components/team/useTeamProvisioningPresentation';
import { DISPLAY_STEPS } from '@renderer/components/team/provisioningSteps';
import { cn } from '@renderer/lib/utils'; import { cn } from '@renderer/lib/utils';
import { AlertTriangle, CheckCircle2, ExternalLink, Loader2, X } from 'lucide-react'; import { AlertTriangle, CheckCircle2, ExternalLink, Loader2, X } from 'lucide-react';
@ -87,12 +87,12 @@ export interface GraphProvisioningHudProps {
enabled?: boolean; enabled?: boolean;
} }
export function GraphProvisioningHud({ export const GraphProvisioningHud = ({
teamName, teamName,
leadNodeId, leadNodeId,
getLaunchAnchorScreenPlacement, getLaunchAnchorScreenPlacement,
enabled = true, enabled = true,
}: GraphProvisioningHudProps): React.JSX.Element | null { }: GraphProvisioningHudProps): React.JSX.Element | null => {
const { presentation, runInstanceKey } = useTeamProvisioningPresentation(teamName); const { presentation, runInstanceKey } = useTeamProvisioningPresentation(teamName);
const shellRef = useRef<HTMLDivElement>(null); const shellRef = useRef<HTMLDivElement>(null);
const lastActiveStepRef = useRef(-1); const lastActiveStepRef = useRef(-1);
@ -180,7 +180,7 @@ export function GraphProvisioningHud({
> >
<div <div
className={cn( className={cn(
'rounded-xl border px-3 py-3 text-slate-100 shadow-[0_18px_48px_rgba(5,5,16,0.38)] backdrop-blur-xl', 'rounded-xl border p-3 text-slate-100 shadow-[0_18px_48px_rgba(5,5,16,0.38)] backdrop-blur-xl',
tone.border tone.border
)} )}
> >
@ -227,7 +227,7 @@ export function GraphProvisioningHud({
<button <button
type="button" type="button"
className="border-cyan-300/12 mt-3 w-full rounded-xl border bg-[rgba(4,10,20,0.58)] px-3 py-3 text-left transition-colors hover:bg-[rgba(8,18,32,0.76)]" className="border-cyan-300/12 mt-3 w-full rounded-xl border bg-[rgba(4,10,20,0.58)] p-3 text-left transition-colors hover:bg-[rgba(8,18,32,0.76)]"
style={HUD_STEPPER_STYLE} style={HUD_STEPPER_STYLE}
onClick={() => setDetailsOpen(true)} onClick={() => setDetailsOpen(true)}
aria-label="Open full launch details" aria-label="Open full launch details"
@ -256,4 +256,4 @@ export function GraphProvisioningHud({
</Dialog> </Dialog>
</div> </div>
); );
} };

View file

@ -8,19 +8,19 @@ import { useCallback, useMemo } from 'react';
import { GraphView } from '@claude-teams/agent-graph'; import { GraphView } from '@claude-teams/agent-graph';
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import type {
MemberActivityFilter,
MemberDetailTab,
} from '@renderer/components/team/members/memberDetailTypes';
import { useTeamGraphAdapter } from '../adapters/useTeamGraphAdapter'; import { useTeamGraphAdapter } from '../adapters/useTeamGraphAdapter';
import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover';
import { GraphActivityHud } from './GraphActivityHud'; import { GraphActivityHud } from './GraphActivityHud';
import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover';
import { GraphNodePopover } from './GraphNodePopover'; import { GraphNodePopover } from './GraphNodePopover';
import { GraphProvisioningHud } from './GraphProvisioningHud'; import { GraphProvisioningHud } from './GraphProvisioningHud';
import type { GraphDomainRef, GraphEventPort } from '@claude-teams/agent-graph'; import type { GraphDomainRef, GraphEventPort } from '@claude-teams/agent-graph';
import type {
MemberActivityFilter,
MemberDetailTab,
} from '@renderer/components/team/members/memberDetailTypes';
export interface TeamGraphOverlayProps { export interface TeamGraphOverlayProps {
teamName: string; teamName: string;

View file

@ -8,19 +8,19 @@ import { lazy, Suspense, useCallback, useMemo, useState } from 'react';
import { GraphView } from '@claude-teams/agent-graph'; import { GraphView } from '@claude-teams/agent-graph';
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost'; import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost';
import { useStore } from '@renderer/store'; import { useStore } from '@renderer/store';
import type {
MemberActivityFilter,
MemberDetailTab,
} from '@renderer/components/team/members/memberDetailTypes';
import { useTeamGraphAdapter } from '../adapters/useTeamGraphAdapter'; import { useTeamGraphAdapter } from '../adapters/useTeamGraphAdapter';
import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover';
import { GraphActivityHud } from './GraphActivityHud'; import { GraphActivityHud } from './GraphActivityHud';
import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover';
import { GraphNodePopover } from './GraphNodePopover'; import { GraphNodePopover } from './GraphNodePopover';
import { GraphProvisioningHud } from './GraphProvisioningHud'; import { GraphProvisioningHud } from './GraphProvisioningHud';
import type { GraphDomainRef, GraphEventPort, GraphNode } from '@claude-teams/agent-graph'; import type { GraphDomainRef, GraphEventPort, GraphNode } from '@claude-teams/agent-graph';
import type {
MemberActivityFilter,
MemberDetailTab,
} from '@renderer/components/team/members/memberDetailTypes';
const TeamGraphOverlay = lazy(() => const TeamGraphOverlay = lazy(() =>
import('./TeamGraphOverlay').then((m) => ({ default: m.TeamGraphOverlay })) import('./TeamGraphOverlay').then((m) => ({ default: m.TeamGraphOverlay }))
@ -32,10 +32,10 @@ export interface TeamGraphTabProps {
isPaneFocused?: boolean; isPaneFocused?: boolean;
} }
type OpenProfileOptions = { interface OpenProfileOptions {
initialTab?: MemberDetailTab; initialTab?: MemberDetailTab;
initialActivityFilter?: MemberActivityFilter; initialActivityFilter?: MemberActivityFilter;
}; }
export const TeamGraphTab = ({ export const TeamGraphTab = ({
teamName, teamName,

View file

@ -7,11 +7,11 @@ import type { GraphActivityItem } from '@claude-teams/agent-graph';
import type { import type {
AttachmentMeta, AttachmentMeta,
InboxMessage, InboxMessage,
ResolvedTeamMember,
TaskAttachmentMeta, TaskAttachmentMeta,
TaskComment, TaskComment,
TaskRef, TaskRef,
TeamTaskWithKanban, TeamTaskWithKanban,
ResolvedTeamMember,
} from '@shared/types/team'; } from '@shared/types/team';
export interface InlineActivityEntry { export interface InlineActivityEntry {
@ -169,8 +169,8 @@ export function buildInlineActivityEntries({
function collectTaskComments( function collectTaskComments(
tasks: readonly TeamTaskWithKanban[] tasks: readonly TeamTaskWithKanban[]
): Array<{ task: TeamTaskWithKanban; comment: TaskComment }> { ): { task: TeamTaskWithKanban; comment: TaskComment }[] {
const items: Array<{ task: TeamTaskWithKanban; comment: TaskComment }> = []; const items: { task: TeamTaskWithKanban; comment: TaskComment }[] = [];
for (const task of tasks) { for (const task of tasks) {
for (const comment of task.comments ?? []) { for (const comment of task.comments ?? []) {
items.push({ task, comment }); items.push({ task, comment });

View file

@ -1,4 +1,4 @@
import type { KanbanTaskState, KanbanColumnId, TeamTask, TeamTaskWithKanban } from '@shared/types'; import type { KanbanColumnId, KanbanTaskState, TeamTask, TeamTaskWithKanban } from '@shared/types';
type TaskColumnInput = Pick<TeamTaskWithKanban, 'status' | 'reviewState' | 'kanbanColumn'>; type TaskColumnInput = Pick<TeamTaskWithKanban, 'status' | 'reviewState' | 'kanbanColumn'>;
type TaskReviewerInput = Pick<TeamTaskWithKanban, 'reviewer' | 'reviewState' | 'kanbanColumn'>; type TaskReviewerInput = Pick<TeamTaskWithKanban, 'reviewer' | 'reviewState' | 'kanbanColumn'>;

View file

@ -195,7 +195,7 @@ export function initializeNotificationListeners(): () => void {
const nav: NavigatorWithUserAgentData | null = const nav: NavigatorWithUserAgentData | null =
typeof navigator !== 'undefined' ? (navigator as NavigatorWithUserAgentData) : null; typeof navigator !== 'undefined' ? (navigator as NavigatorWithUserAgentData) : null;
// Prefer UA-CH when available; fall back to deprecated-but-still-supported navigator.platform. // Prefer UA-CH when available; fall back to deprecated-but-still-supported navigator.platform.
// eslint-disable-next-line sonarjs/deprecation -- navigator.platform is deprecated but needed as fallback
const platform: string = const platform: string =
nav?.userAgentData?.platform ?? nav?.platform ?? nav?.userAgent ?? ''; nav?.userAgentData?.platform ?? nav?.platform ?? nav?.userAgent ?? '';
const isWindows = platform.toLowerCase().includes('win'); const isWindows = platform.toLowerCase().includes('win');

View file

@ -484,31 +484,31 @@ export function buildMemberLaunchPresentation({
const keepLaunchSettlingVisuals = isTeamProvisioning === true || isLaunchSettling; const keepLaunchSettlingVisuals = isTeamProvisioning === true || isLaunchSettling;
let launchVisualState: MemberLaunchVisualState = null; let launchVisualState: MemberLaunchVisualState = null;
if (isTeamAlive === false && !isTeamProvisioning) { if (isTeamAlive !== false || isTeamProvisioning) {
launchVisualState = null; if (spawnLaunchState === 'failed_to_start' || spawnStatus === 'error') {
} else if (spawnLaunchState === 'failed_to_start' || spawnStatus === 'error') { launchVisualState = 'error';
launchVisualState = 'error'; } else if (
} else if ( spawnLaunchState === 'runtime_pending_bootstrap' &&
spawnLaunchState === 'runtime_pending_bootstrap' && spawnStatus === 'online' &&
spawnStatus === 'online' && spawnRuntimeAlive === true
spawnRuntimeAlive === true ) {
) { launchVisualState = 'runtime_pending';
launchVisualState = 'runtime_pending'; } else if (
} else if ( isLaunchStillStarting(
isLaunchStillStarting( spawnStatus,
spawnStatus, spawnLaunchState,
spawnLaunchState, spawnRuntimeAlive,
spawnRuntimeAlive, keepLaunchSettlingVisuals
keepLaunchSettlingVisuals )
) ) {
) { launchVisualState = spawnStatus === 'spawning' ? 'spawning' : 'waiting';
launchVisualState = spawnStatus === 'spawning' ? 'spawning' : 'waiting'; } else if (
} else if ( isLaunchSettling &&
isLaunchSettling && spawnStatus === 'online' &&
spawnStatus === 'online' && spawnLaunchState === 'confirmed_alive'
spawnLaunchState === 'confirmed_alive' ) {
) { launchVisualState = 'settling';
launchVisualState = 'settling'; }
} }
const spawnBadgeLabel = const spawnBadgeLabel =

View file

@ -38,12 +38,12 @@ import type {
} from './schedule'; } from './schedule';
import type { import type {
AddMemberRequest, AddMemberRequest,
BoardTaskActivityEntry,
BoardTaskLogStreamResponse,
BoardTaskExactLogDetailResult,
BoardTaskExactLogSummariesResponse,
AddTaskCommentRequest, AddTaskCommentRequest,
AttachmentFileData, AttachmentFileData,
BoardTaskActivityEntry,
BoardTaskExactLogDetailResult,
BoardTaskExactLogSummariesResponse,
BoardTaskLogStreamResponse,
CreateTaskRequest, CreateTaskRequest,
CrossTeamMessage, CrossTeamMessage,
CrossTeamSendRequest, CrossTeamSendRequest,

View file

@ -634,11 +634,15 @@ describe('ChangeExtractorService', () => {
await writeTaskFile(tmpDir); await writeTaskFile(tmpDir);
const first = deferred<ReturnType<typeof makeTaskChangeResult>>(); const first = deferred<ReturnType<typeof makeTaskChangeResult>>();
const firstStarted = deferred<void>();
const worker = { const worker = {
isAvailable: vi.fn(() => true), isAvailable: vi.fn(() => true),
computeTaskChanges: vi computeTaskChanges: vi
.fn() .fn()
.mockImplementationOnce(() => first.promise) .mockImplementationOnce(() => {
firstStarted.resolve();
return first.promise;
})
.mockImplementationOnce(async () => .mockImplementationOnce(async () =>
makeTaskChangeResult(TASK_ID, { filePath: '/repo/src/newer.ts' }) makeTaskChangeResult(TASK_ID, { filePath: '/repo/src/newer.ts' })
), ),
@ -649,6 +653,7 @@ describe('ChangeExtractorService', () => {
}); });
const stalePromise = service.getTaskChanges(TEAM_NAME, TASK_ID, SUMMARY_OPTIONS); const stalePromise = service.getTaskChanges(TEAM_NAME, TASK_ID, SUMMARY_OPTIONS);
await firstStarted.promise;
await service.invalidateTaskChangeSummaries(TEAM_NAME, [TASK_ID], { deletePersisted: true }); await service.invalidateTaskChangeSummaries(TEAM_NAME, [TASK_ID], { deletePersisted: true });
const freshPromise = service.getTaskChanges(TEAM_NAME, TASK_ID, SUMMARY_OPTIONS); const freshPromise = service.getTaskChanges(TEAM_NAME, TASK_ID, SUMMARY_OPTIONS);
// Flush microtasks so freshPromise advances past its internal awaits // Flush microtasks so freshPromise advances past its internal awaits

View file

@ -556,8 +556,8 @@ describe('ProviderRuntimeSettingsDialog Codex connection flows', () => {
const icon = host.querySelector('[data-testid="provider-api-key-icon"]'); const icon = host.querySelector('[data-testid="provider-api-key-icon"]');
expect(icon).not.toBeNull(); expect(icon).not.toBeNull();
expect(icon?.className).toContain('h-8'); expect(icon?.className).toContain('size-8');
expect(icon?.className).toContain('w-8'); expect(icon?.className).not.toContain('w-8');
expect(icon?.className).toContain('shrink-0'); expect(icon?.className).toContain('shrink-0');
}); });