fix(ci): restore workspace validation
This commit is contained in:
parent
0eb38387a3
commit
f536e7e715
28 changed files with 86 additions and 86 deletions
|
|
@ -115,9 +115,7 @@ export function useTeamGraphAdapter(teamName: string): GraphDataPort {
|
|||
const currentAssignment = slotAssignments[stableOwnerId];
|
||||
const defaultAssignment = defaultSeed.assignments[stableOwnerId];
|
||||
return (
|
||||
currentAssignment &&
|
||||
defaultAssignment &&
|
||||
currentAssignment.ringIndex === defaultAssignment.ringIndex &&
|
||||
currentAssignment?.ringIndex === defaultAssignment?.ringIndex &&
|
||||
currentAssignment.sectorIndex === defaultAssignment.sectorIndex
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* into ui/, hooks/, or core/ directly.
|
||||
*/
|
||||
|
||||
export type { InlineActivityEntry } from '../core/domain/buildInlineActivityEntries';
|
||||
export { buildInlineActivityEntries } from '../core/domain/buildInlineActivityEntries';
|
||||
export { buildGraphMemberNodeIdForMember } from '../core/domain/graphOwnerIdentity';
|
||||
export { TeamGraphAdapter } from './adapters/TeamGraphAdapter';
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ function resolveHistoryOpenedAt(lookup: HistoryLookup, projectPath: string): num
|
|||
}
|
||||
|
||||
const foldedMatch = lookup.folded.get(foldHistoryPath(normalizedPath));
|
||||
if (!foldedMatch || foldedMatch.exactPaths.size !== 1) {
|
||||
if (foldedMatch?.exactPaths.size !== 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ export class TmuxWslService {
|
|||
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', POWERSHELL_FEATURE_QUERY],
|
||||
6_000
|
||||
);
|
||||
if (!result || result.exitCode !== 0 || !result.stdout.trim()) {
|
||||
if (result?.exitCode !== 0 || !result.stdout.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ import {
|
|||
} from './services/extensions';
|
||||
import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor';
|
||||
import { HttpServer } from './services/infrastructure/HttpServer';
|
||||
import { clearAutoResumeService } from './services/team/AutoResumeService';
|
||||
import {
|
||||
buildTeamControlApiBaseUrl,
|
||||
clearTeamControlApiState,
|
||||
|
|
@ -100,7 +101,6 @@ import {
|
|||
type TeamReconcileTrigger,
|
||||
} from './services/team/TeamReconcileDrainScheduler';
|
||||
import { TeamSentMessagesStore } from './services/team/TeamSentMessagesStore';
|
||||
import { clearAutoResumeService } from './services/team/AutoResumeService';
|
||||
import { getAppIconPath } from './utils/appIcon';
|
||||
import { getProjectsBasePath, getTeamsBasePath, getTodosBasePath } from './utils/pathDecoder';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -839,7 +839,7 @@ async function handleGetData(
|
|||
const projectPath = data.config.projectPath;
|
||||
const live = provisioning.getLiveLeadProcessMessages(tn);
|
||||
const durableMessages = Array.isArray((data as { messages?: unknown }).messages)
|
||||
? (((data as { messages?: typeof live }).messages ?? []) as typeof live)
|
||||
? ((data as { messages?: typeof live }).messages ?? [])
|
||||
: [];
|
||||
|
||||
if (live.length === 0) {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export class ApiKeyService {
|
|||
);
|
||||
}
|
||||
if (!request.value) throw new Error('Key value is required');
|
||||
if (request.scope === 'project' && (!request.projectPath || !request.projectPath.trim())) {
|
||||
if (request.scope === 'project' && !request.projectPath?.trim()) {
|
||||
throw new Error('Project-scoped API keys require a project path');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ function classifyFailedProbe(
|
|||
|
||||
export class CliProviderModelAvailabilityService {
|
||||
private readonly cache = new Map<string, ProviderModelAvailabilityCacheEntry>();
|
||||
private readonly queue: Array<() => void> = [];
|
||||
private readonly queue: (() => void)[] = [];
|
||||
private activeProbeCount = 0;
|
||||
|
||||
constructor(private readonly onUpdate?: ProviderAvailabilityUpdateHandler) {}
|
||||
|
|
|
|||
|
|
@ -223,8 +223,7 @@ export function createPersistedLaunchSnapshot(params: {
|
|||
for (const name of expectedMembers) {
|
||||
const member = members[name];
|
||||
if (
|
||||
member &&
|
||||
member.launchState === 'starting' &&
|
||||
member?.launchState === 'starting' &&
|
||||
!member.agentToolAccepted &&
|
||||
!member.runtimeAlive &&
|
||||
!member.bootstrapConfirmed &&
|
||||
|
|
|
|||
|
|
@ -390,7 +390,7 @@ export class TeamMessageFeedService {
|
|||
|
||||
const feedRevision = toFeedRevision(messages);
|
||||
const nextEntry =
|
||||
cached && cached.feedRevision === feedRevision
|
||||
cached?.feedRevision === feedRevision
|
||||
? cached
|
||||
: {
|
||||
feedRevision,
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ import { resolveTeamProviderId } from '../runtime/providerRuntimeEnv';
|
|||
|
||||
import { buildActionModeProtocol } from './actionModeInstructions';
|
||||
import { atomicWriteAsync } from './atomicWrite';
|
||||
import { peekAutoResumeService } from './AutoResumeService';
|
||||
import { ClaudeBinaryResolver } from './ClaudeBinaryResolver';
|
||||
import { withFileLock } from './fileLock';
|
||||
import {
|
||||
|
|
@ -112,7 +113,6 @@ import { TeamMembersMetaStore } from './TeamMembersMetaStore';
|
|||
import { TeamMetaStore } from './TeamMetaStore';
|
||||
import { TeamSentMessagesStore } from './TeamSentMessagesStore';
|
||||
import { TeamTaskReader } from './TeamTaskReader';
|
||||
import { peekAutoResumeService } from './AutoResumeService';
|
||||
|
||||
/**
|
||||
* Kill a team CLI process using SIGKILL (uncatchable).
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { atomicWriteAsync } from '@main/utils/atomicWrite';
|
||||
import { extractCwd } from '@main/utils/jsonl';
|
||||
import {
|
||||
encodePath,
|
||||
|
|
@ -5,7 +6,6 @@ import {
|
|||
getProjectsBasePath,
|
||||
getTeamsBasePath,
|
||||
} from '@main/utils/pathDecoder';
|
||||
import { atomicWriteAsync } from '@main/utils/atomicWrite';
|
||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
import { createReadStream, type Dirent } from 'fs';
|
||||
|
|
@ -219,7 +219,8 @@ export class TeamTranscriptProjectResolver {
|
|||
...config,
|
||||
projectPath: resolution.effectiveProjectPath,
|
||||
projectPathHistory: this.buildRepairedProjectPathHistory(
|
||||
config,
|
||||
config.projectPath,
|
||||
config.projectPathHistory,
|
||||
resolution.effectiveProjectPath
|
||||
),
|
||||
}
|
||||
|
|
@ -598,25 +599,11 @@ export class TeamTranscriptProjectResolver {
|
|||
|
||||
parsed.projectPath = normalizedNextPath;
|
||||
|
||||
const history: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const pushHistory = (value: unknown): void => {
|
||||
const normalized = normalizeProjectPathCandidate(value);
|
||||
if (!normalized || normalized === normalizedNextPath || seen.has(normalized)) {
|
||||
return;
|
||||
}
|
||||
seen.add(normalized);
|
||||
history.push(normalized);
|
||||
};
|
||||
|
||||
if (Array.isArray(parsed.projectPathHistory)) {
|
||||
for (const value of parsed.projectPathHistory) {
|
||||
pushHistory(value);
|
||||
}
|
||||
}
|
||||
pushHistory(rawProjectPath);
|
||||
|
||||
parsed.projectPathHistory = history.slice(-500);
|
||||
parsed.projectPathHistory = this.buildRepairedProjectPathHistory(
|
||||
rawProjectPath,
|
||||
parsed.projectPathHistory,
|
||||
normalizedNextPath
|
||||
);
|
||||
await atomicWriteAsync(configPath, JSON.stringify(parsed, null, 2));
|
||||
logger.info(
|
||||
`[${teamName}] Repaired transcript projectPath via exact session match: ${normalizedNextPath}`
|
||||
|
|
@ -663,7 +650,11 @@ export class TeamTranscriptProjectResolver {
|
|||
return orderedSessionIds;
|
||||
}
|
||||
|
||||
private buildRepairedProjectPathHistory(config: TeamConfig, nextProjectPath: string): string[] {
|
||||
private buildRepairedProjectPathHistory(
|
||||
currentProjectPath: unknown,
|
||||
rawProjectPathHistory: unknown,
|
||||
nextProjectPath: string
|
||||
): string[] {
|
||||
const normalizedNextPath = normalizeProjectPathCandidate(nextProjectPath);
|
||||
const history: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
|
@ -676,12 +667,12 @@ export class TeamTranscriptProjectResolver {
|
|||
history.push(normalized);
|
||||
};
|
||||
|
||||
if (Array.isArray(config.projectPathHistory)) {
|
||||
for (const value of config.projectPathHistory) {
|
||||
if (Array.isArray(rawProjectPathHistory)) {
|
||||
for (const value of rawProjectPathHistory) {
|
||||
pushHistory(value);
|
||||
}
|
||||
}
|
||||
pushHistory(config.projectPath);
|
||||
pushHistory(currentProjectPath);
|
||||
|
||||
return history.slice(-500);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
export {
|
||||
AutoResumeService,
|
||||
clearAutoResumeService,
|
||||
getAutoResumeService,
|
||||
initializeAutoResumeService,
|
||||
} from './AutoResumeService';
|
||||
export { BranchStatusService } from './BranchStatusService';
|
||||
export { CascadeGuard } from './CascadeGuard';
|
||||
export { ChangeExtractorService } from './ChangeExtractorService';
|
||||
|
|
@ -16,12 +22,6 @@ export { BoardTaskActivityService } from './taskLogs/activity/BoardTaskActivityS
|
|||
export { BoardTaskExactLogDetailService } from './taskLogs/exact/BoardTaskExactLogDetailService';
|
||||
export { BoardTaskExactLogsService } from './taskLogs/exact/BoardTaskExactLogsService';
|
||||
export { BoardTaskLogStreamService } from './taskLogs/stream/BoardTaskLogStreamService';
|
||||
export {
|
||||
AutoResumeService,
|
||||
clearAutoResumeService,
|
||||
getAutoResumeService,
|
||||
initializeAutoResumeService,
|
||||
} from './AutoResumeService';
|
||||
export { TeamAttachmentStore } from './TeamAttachmentStore';
|
||||
export { TeamBackupService } from './TeamBackupService';
|
||||
export { TeamConfigReader } from './TeamConfigReader';
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import { Button } from '@renderer/components/ui/button';
|
|||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
||||
import { useStore } from '@renderer/store';
|
||||
import { formatCompactNumber, formatRelativeTime } from '@renderer/utils/formatters';
|
||||
import { getDefaultMcpSharedScope } from '@shared/utils/mcpScopes';
|
||||
import {
|
||||
getMcpInstallationSummaryLabel,
|
||||
getMcpOperationKey,
|
||||
sanitizeMcpServerName,
|
||||
} from '@shared/utils/extensionNormalizers';
|
||||
import { getDefaultMcpSharedScope } from '@shared/utils/mcpScopes';
|
||||
import { Clock, Cloud, Globe, KeyRound, Lock, Monitor, Star, Tag, Wrench } from 'lucide-react';
|
||||
import { Github as GithubIcon } from 'lucide-react';
|
||||
|
||||
|
|
|
|||
|
|
@ -25,18 +25,18 @@ import {
|
|||
SelectValue,
|
||||
} from '@renderer/components/ui/select';
|
||||
import { useStore } from '@renderer/store';
|
||||
import {
|
||||
getDefaultMcpSharedScope,
|
||||
getMcpScopeLabel,
|
||||
isProjectScopedMcpScope,
|
||||
isSharedMcpScope,
|
||||
} from '@shared/utils/mcpScopes';
|
||||
import {
|
||||
getMcpInstallationSummaryLabel,
|
||||
getMcpOperationKey,
|
||||
getPreferredMcpInstallationEntry,
|
||||
sanitizeMcpServerName,
|
||||
} from '@shared/utils/extensionNormalizers';
|
||||
import {
|
||||
getDefaultMcpSharedScope,
|
||||
getMcpScopeLabel,
|
||||
isProjectScopedMcpScope,
|
||||
isSharedMcpScope,
|
||||
} from '@shared/utils/mcpScopes';
|
||||
import { ExternalLink, Lock, Plus, Star, Trash2, Wrench } from 'lucide-react';
|
||||
|
||||
import { InstallButton } from '../common/InstallButton';
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import {
|
|||
SelectValue,
|
||||
} from '@renderer/components/ui/select';
|
||||
import { useStore } from '@renderer/store';
|
||||
import { getCliProviderExtensionCapability } from '@shared/utils/providerExtensionCapabilities';
|
||||
import { inferCapabilities, normalizeCategory } from '@shared/utils/extensionNormalizers';
|
||||
import { getCliProviderExtensionCapability } from '@shared/utils/providerExtensionCapabilities';
|
||||
import { ArrowUpDown, Filter, Puzzle, Search } from 'lucide-react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ import { validateSkillFolderName } from './skillValidationUtils';
|
|||
import type {
|
||||
SkillDetail,
|
||||
SkillInvocationMode,
|
||||
SkillRootKind,
|
||||
SkillReviewPreview,
|
||||
SkillRootKind,
|
||||
} from '@shared/types/extensions';
|
||||
|
||||
type EditorMode = 'create' | 'edit';
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ function formatRuntimeAudienceLabel(providerNames: readonly string[]): string {
|
|||
return 'the configured runtime';
|
||||
}
|
||||
if (providerNames.length === 1) {
|
||||
return providerNames[0]!;
|
||||
return providerNames[0];
|
||||
}
|
||||
if (providerNames.length === 2) {
|
||||
return `${providerNames[0]} and ${providerNames[1]}`;
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ import { AdvancedCliSection } from './AdvancedCliSection';
|
|||
import { EffortLevelSelector } from './EffortLevelSelector';
|
||||
import { resolveLaunchDialogPrefill } from './launchDialogPrefill';
|
||||
import { OptionalSettingsSection } from './OptionalSettingsSection';
|
||||
import { buildProviderPrepareModelCacheKey } from './providerPrepareCacheKey';
|
||||
import { ProjectPathSelector } from './ProjectPathSelector';
|
||||
import { buildProviderPrepareModelCacheKey } from './providerPrepareCacheKey';
|
||||
import {
|
||||
buildReusableProviderPrepareModelResults,
|
||||
getProviderPrepareCachedSnapshot,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ export const MemberCard = ({
|
|||
!isRemoved &&
|
||||
presenceLabel === 'starting' &&
|
||||
spawnLaunchState !== 'failed_to_start' &&
|
||||
!activityTask;
|
||||
!activityTask &&
|
||||
!runtimeSummary;
|
||||
const showStartingBadge = !isRemoved && presenceLabel === 'starting' && !activityTask;
|
||||
const showRuntimeAdvisoryBadge =
|
||||
!isRemoved &&
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import { selectMemberMessagesForTeamMember } from '@renderer/store/slices/teamSl
|
|||
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||
import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react';
|
||||
|
||||
import { MemberDetailHeader } from './MemberDetailHeader';
|
||||
import { buildMemberActivityEntries } from './memberActivityEntries';
|
||||
import { MemberDetailHeader } from './MemberDetailHeader';
|
||||
import { MemberDetailStats } from './MemberDetailStats';
|
||||
import { type MemberActivityFilter, type MemberDetailTab } from './memberDetailTypes';
|
||||
import { MemberLogsTab } from './MemberLogsTab';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { buildInlineActivityEntries } from '@features/agent-graph/renderer';
|
|||
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
|
||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||
|
||||
import type { InlineActivityEntry } from '@features/agent-graph/core/domain/buildInlineActivityEntries';
|
||||
import type { InlineActivityEntry } from '@features/agent-graph/renderer';
|
||||
import type { InboxMessage, ResolvedTeamMember, TeamTaskWithKanban } from '@shared/types';
|
||||
|
||||
export function buildMemberActivityEntries({
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ export function initializeNotificationListeners(): () => void {
|
|||
const headResult = await current.refreshTeamMessagesHead(teamName);
|
||||
const latest = useStore.getState();
|
||||
const meta = latest.memberActivityMetaByTeam[teamName];
|
||||
if (headResult.feedChanged || !meta || meta.feedRevision !== headResult.feedRevision) {
|
||||
if (headResult.feedChanged || meta?.feedRevision !== headResult.feedRevision) {
|
||||
await latest.refreshMemberActivityMeta(teamName);
|
||||
}
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -1656,7 +1656,7 @@ export function selectResolvedMembersForTeamName(
|
|||
const meta = state.memberActivityMetaByTeam[teamName];
|
||||
const metaMembers = meta?.members;
|
||||
const cached = resolvedMembersSelectorCache.get(teamName);
|
||||
if (cached && cached.snapshotRef === snapshot.members && cached.metaMembersRef === metaMembers) {
|
||||
if (cached?.snapshotRef === snapshot.members && cached.metaMembersRef === metaMembers) {
|
||||
return cached.result;
|
||||
}
|
||||
|
||||
|
|
@ -1690,7 +1690,7 @@ export function selectResolvedMemberForTeamName(
|
|||
const metaEntry = state.memberActivityMetaByTeam[teamName]?.members[memberName];
|
||||
const cacheKey = `${teamName}:${memberName}`;
|
||||
const cached = resolvedMemberSelectorCache.get(cacheKey);
|
||||
if (cached && cached.snapshotMemberRef === snapshotMember && cached.metaEntryRef === metaEntry) {
|
||||
if (cached?.snapshotMemberRef === snapshotMember && cached.metaEntryRef === metaEntry) {
|
||||
return cached.result;
|
||||
}
|
||||
|
||||
|
|
@ -1735,8 +1735,7 @@ export function selectTeamMessages(
|
|||
const entry = getTeamMessagesCacheEntry(state, teamName);
|
||||
const cached = mergedMessagesSelectorCache.get(teamName);
|
||||
if (
|
||||
cached &&
|
||||
cached.canonicalRef === entry.canonicalMessages &&
|
||||
cached?.canonicalRef === entry.canonicalMessages &&
|
||||
cached.optimisticRef === entry.optimisticMessages
|
||||
) {
|
||||
return cached.result;
|
||||
|
|
@ -1763,7 +1762,7 @@ export function selectMemberMessagesForTeamMember(
|
|||
const messages = selectTeamMessages(state, teamName);
|
||||
const cacheKey = `${teamName}:${memberName}`;
|
||||
const cached = memberMessagesSelectorCache.get(cacheKey);
|
||||
if (cached && cached.messagesRef === messages) {
|
||||
if (cached?.messagesRef === messages) {
|
||||
return cached.result;
|
||||
}
|
||||
|
||||
|
|
@ -3530,8 +3529,10 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
return queuedRequest;
|
||||
}
|
||||
|
||||
let request!: Promise<RefreshTeamMessagesHeadResult>;
|
||||
request = (async (): Promise<RefreshTeamMessagesHeadResult> => {
|
||||
const requestRef: { current: Promise<RefreshTeamMessagesHeadResult> | null } = {
|
||||
current: null,
|
||||
};
|
||||
requestRef.current = (async (): Promise<RefreshTeamMessagesHeadResult> => {
|
||||
const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
|
||||
set((state) => ({
|
||||
teamMessagesByName: {
|
||||
|
|
@ -3621,7 +3622,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
}));
|
||||
throw error;
|
||||
} finally {
|
||||
if (inFlightTeamMessagesHeadRequests.get(teamName) === request) {
|
||||
if (inFlightTeamMessagesHeadRequests.get(teamName) === requestRef.current) {
|
||||
inFlightTeamMessagesHeadRequests.delete(teamName);
|
||||
if (pendingFreshTeamMessagesHeadRefreshes.delete(teamName)) {
|
||||
void get().refreshTeamMessagesHead(teamName);
|
||||
|
|
@ -3630,6 +3631,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
}
|
||||
})();
|
||||
|
||||
const request = requestRef.current;
|
||||
inFlightTeamMessagesHeadRequests.set(teamName, request);
|
||||
return request;
|
||||
},
|
||||
|
|
@ -3662,8 +3664,8 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
return;
|
||||
}
|
||||
|
||||
let request!: Promise<void>;
|
||||
request = (async (): Promise<void> => {
|
||||
const requestRef: { current: Promise<void> | null } = { current: null };
|
||||
requestRef.current = (async (): Promise<void> => {
|
||||
const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
|
||||
set((state) => ({
|
||||
teamMessagesByName: {
|
||||
|
|
@ -3747,12 +3749,13 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
},
|
||||
}));
|
||||
} finally {
|
||||
if (inFlightTeamMessagesOlderRequests.get(teamName) === request) {
|
||||
if (inFlightTeamMessagesOlderRequests.get(teamName) === requestRef.current) {
|
||||
inFlightTeamMessagesOlderRequests.delete(teamName);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
const request = requestRef.current;
|
||||
inFlightTeamMessagesOlderRequests.set(teamName, request);
|
||||
return request;
|
||||
},
|
||||
|
|
@ -3769,8 +3772,8 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
return existingRequest;
|
||||
}
|
||||
|
||||
let request!: Promise<void>;
|
||||
request = (async (): Promise<void> => {
|
||||
const requestRef: { current: Promise<void> | null } = { current: null };
|
||||
requestRef.current = (async (): Promise<void> => {
|
||||
const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
|
||||
try {
|
||||
const meta = await unwrapIpc('team:getMemberActivityMeta', () =>
|
||||
|
|
@ -3786,7 +3789,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
return {};
|
||||
}
|
||||
const existing = state.memberActivityMetaByTeam[teamName];
|
||||
if (existing && existing.feedRevision === meta.feedRevision) {
|
||||
if (existing?.feedRevision === meta.feedRevision) {
|
||||
return {};
|
||||
}
|
||||
const sharedMembers = structurallyShareMemberActivityFacts(
|
||||
|
|
@ -3794,8 +3797,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
meta.members
|
||||
);
|
||||
const nextMeta =
|
||||
existing &&
|
||||
existing.members === sharedMembers &&
|
||||
existing?.members === sharedMembers &&
|
||||
existing.feedRevision === meta.feedRevision &&
|
||||
existing.computedAt === meta.computedAt
|
||||
? existing
|
||||
|
|
@ -3816,7 +3818,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
}
|
||||
throw error;
|
||||
} finally {
|
||||
if (inFlightTeamMemberActivityMetaRequests.get(teamName) === request) {
|
||||
if (inFlightTeamMemberActivityMetaRequests.get(teamName) === requestRef.current) {
|
||||
inFlightTeamMemberActivityMetaRequests.delete(teamName);
|
||||
if (pendingFreshTeamMemberActivityMetaRefreshes.delete(teamName)) {
|
||||
void get().refreshMemberActivityMeta(teamName);
|
||||
|
|
@ -3825,6 +3827,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
}
|
||||
})();
|
||||
|
||||
const request = requestRef.current;
|
||||
inFlightTeamMemberActivityMetaRequests.set(teamName, request);
|
||||
return request;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -148,11 +148,7 @@ export function buildDisplayItems(
|
|||
// Build display items
|
||||
for (const step of steps) {
|
||||
// Skip the last output step
|
||||
if (
|
||||
lastOutputStepRef &&
|
||||
step.id === lastOutputStepRef.id &&
|
||||
step.type === lastOutputStepRef.type
|
||||
) {
|
||||
if (step.id === lastOutputStepRef?.id && step.type === lastOutputStepRef.type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,10 +104,10 @@ function parseRelativeResetDuration(text: string): number | null {
|
|||
const match = LEADING_TIME_VALUE_RE.exec(tail);
|
||||
if (!match) return null;
|
||||
|
||||
const amount = Number.parseFloat(match[1]!);
|
||||
const amount = Number.parseFloat(match[1]);
|
||||
if (!Number.isFinite(amount) || amount < 0) return null;
|
||||
|
||||
const unit = match[2]!.toLowerCase();
|
||||
const unit = match[2].toLowerCase();
|
||||
if (['second', 'seconds', 'sec', 'secs', 's'].includes(unit)) {
|
||||
return Math.round(amount * 1000);
|
||||
}
|
||||
|
|
@ -157,7 +157,7 @@ function parseAbsoluteResetClockTime(text: string, now: Date): Date | null {
|
|||
const afterMatch = tail.slice(tzTokenLength);
|
||||
if (DAY_SHIFT_QUALIFIER_RE.test(afterMatch)) return null;
|
||||
|
||||
const hourRaw = Number.parseInt(match[1]!, 10);
|
||||
const hourRaw = Number.parseInt(match[1], 10);
|
||||
const minuteRaw = match[2] ? Number.parseInt(match[2], 10) : 0;
|
||||
const ampm = match[3]?.toLowerCase() ?? null;
|
||||
const parenthesizedTz = parenthesizedTzMatch?.[1]?.toUpperCase() ?? '';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface TeamGraphDefaultLayoutSeed {
|
|||
assignments: Record<string, GraphOwnerSlotAssignment>;
|
||||
}
|
||||
|
||||
const SMALL_TEAM_CARDINAL_SLOT_PRESETS: ReadonlyArray<ReadonlyArray<GraphOwnerSlotAssignment>> = [
|
||||
const SMALL_TEAM_CARDINAL_SLOT_PRESETS: readonly (readonly GraphOwnerSlotAssignment[])[] = [
|
||||
[],
|
||||
[{ ringIndex: 0, sectorIndex: 0 }],
|
||||
[
|
||||
|
|
@ -83,7 +83,7 @@ export function buildTeamGraphDefaultLayoutSeed(
|
|||
const preset = SMALL_TEAM_CARDINAL_SLOT_PRESETS[orderedVisibleOwnerIds.length];
|
||||
const assignments: Record<string, GraphOwnerSlotAssignment> = {};
|
||||
|
||||
if (preset && preset.length === orderedVisibleOwnerIds.length) {
|
||||
if (preset?.length === orderedVisibleOwnerIds.length) {
|
||||
orderedVisibleOwnerIds.forEach((stableOwnerId, index) => {
|
||||
assignments[stableOwnerId] = preset[index]!;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -308,6 +308,17 @@ describe('MessagesPanel idle summary invariants', () => {
|
|||
];
|
||||
|
||||
await act(async () => {
|
||||
storeState.teamMessagesByName['atlas-hq'] = {
|
||||
canonicalMessages: messages,
|
||||
optimisticMessages: [],
|
||||
feedRevision: 'rev-1',
|
||||
nextCursor: null,
|
||||
hasMore: false,
|
||||
lastFetchedAt: Date.now(),
|
||||
loadingHead: false,
|
||||
loadingOlder: false,
|
||||
headHydrated: true,
|
||||
};
|
||||
root.render(
|
||||
React.createElement(MessagesPanel, {
|
||||
teamName: 'atlas-hq',
|
||||
|
|
|
|||
Loading…
Reference in a new issue