diff --git a/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts b/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts index 8e341bc2..2506d5a0 100644 --- a/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts +++ b/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts @@ -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 ); }); diff --git a/src/features/agent-graph/renderer/index.ts b/src/features/agent-graph/renderer/index.ts index 91f45840..ba6320ac 100644 --- a/src/features/agent-graph/renderer/index.ts +++ b/src/features/agent-graph/renderer/index.ts @@ -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'; diff --git a/src/features/recent-projects/renderer/utils/recentProjectOpenHistory.ts b/src/features/recent-projects/renderer/utils/recentProjectOpenHistory.ts index 25e60cab..66c4cd3f 100644 --- a/src/features/recent-projects/renderer/utils/recentProjectOpenHistory.ts +++ b/src/features/recent-projects/renderer/utils/recentProjectOpenHistory.ts @@ -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; } diff --git a/src/features/tmux-installer/main/infrastructure/wsl/TmuxWslService.ts b/src/features/tmux-installer/main/infrastructure/wsl/TmuxWslService.ts index edbd8a4d..335a5e69 100644 --- a/src/features/tmux-installer/main/infrastructure/wsl/TmuxWslService.ts +++ b/src/features/tmux-installer/main/infrastructure/wsl/TmuxWslService.ts @@ -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; } diff --git a/src/main/index.ts b/src/main/index.ts index 5aa9c1d9..f73c9012 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -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 { diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index 9f941e7b..01fd2952 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -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) { diff --git a/src/main/services/extensions/apikeys/ApiKeyService.ts b/src/main/services/extensions/apikeys/ApiKeyService.ts index 19c4500a..bb9280fa 100644 --- a/src/main/services/extensions/apikeys/ApiKeyService.ts +++ b/src/main/services/extensions/apikeys/ApiKeyService.ts @@ -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'); } diff --git a/src/main/services/runtime/CliProviderModelAvailabilityService.ts b/src/main/services/runtime/CliProviderModelAvailabilityService.ts index 75ee0d79..0637839a 100644 --- a/src/main/services/runtime/CliProviderModelAvailabilityService.ts +++ b/src/main/services/runtime/CliProviderModelAvailabilityService.ts @@ -161,7 +161,7 @@ function classifyFailedProbe( export class CliProviderModelAvailabilityService { private readonly cache = new Map(); - private readonly queue: Array<() => void> = []; + private readonly queue: (() => void)[] = []; private activeProbeCount = 0; constructor(private readonly onUpdate?: ProviderAvailabilityUpdateHandler) {} diff --git a/src/main/services/team/TeamLaunchStateEvaluator.ts b/src/main/services/team/TeamLaunchStateEvaluator.ts index c3ca6bb5..331d7f79 100644 --- a/src/main/services/team/TeamLaunchStateEvaluator.ts +++ b/src/main/services/team/TeamLaunchStateEvaluator.ts @@ -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 && diff --git a/src/main/services/team/TeamMessageFeedService.ts b/src/main/services/team/TeamMessageFeedService.ts index 2436c15e..b40d01f8 100644 --- a/src/main/services/team/TeamMessageFeedService.ts +++ b/src/main/services/team/TeamMessageFeedService.ts @@ -390,7 +390,7 @@ export class TeamMessageFeedService { const feedRevision = toFeedRevision(messages); const nextEntry = - cached && cached.feedRevision === feedRevision + cached?.feedRevision === feedRevision ? cached : { feedRevision, diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 69de02f0..5f98d2ee 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -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). diff --git a/src/main/services/team/TeamTranscriptProjectResolver.ts b/src/main/services/team/TeamTranscriptProjectResolver.ts index 5bbe4edd..f6c65a01 100644 --- a/src/main/services/team/TeamTranscriptProjectResolver.ts +++ b/src/main/services/team/TeamTranscriptProjectResolver.ts @@ -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(); - 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(); @@ -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); } diff --git a/src/main/services/team/index.ts b/src/main/services/team/index.ts index a87be45a..f6290a2f 100644 --- a/src/main/services/team/index.ts +++ b/src/main/services/team/index.ts @@ -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'; diff --git a/src/renderer/components/extensions/mcp/McpServerCard.tsx b/src/renderer/components/extensions/mcp/McpServerCard.tsx index 90a34c4c..10844f74 100644 --- a/src/renderer/components/extensions/mcp/McpServerCard.tsx +++ b/src/renderer/components/extensions/mcp/McpServerCard.tsx @@ -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'; diff --git a/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx b/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx index c96d428d..97845c01 100644 --- a/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +++ b/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx @@ -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'; diff --git a/src/renderer/components/extensions/plugins/PluginsPanel.tsx b/src/renderer/components/extensions/plugins/PluginsPanel.tsx index 1f846423..8adde28f 100644 --- a/src/renderer/components/extensions/plugins/PluginsPanel.tsx +++ b/src/renderer/components/extensions/plugins/PluginsPanel.tsx @@ -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'; diff --git a/src/renderer/components/extensions/skills/SkillEditorDialog.tsx b/src/renderer/components/extensions/skills/SkillEditorDialog.tsx index 8af5f748..65885733 100644 --- a/src/renderer/components/extensions/skills/SkillEditorDialog.tsx +++ b/src/renderer/components/extensions/skills/SkillEditorDialog.tsx @@ -41,8 +41,8 @@ import { validateSkillFolderName } from './skillValidationUtils'; import type { SkillDetail, SkillInvocationMode, - SkillRootKind, SkillReviewPreview, + SkillRootKind, } from '@shared/types/extensions'; type EditorMode = 'create' | 'edit'; diff --git a/src/renderer/components/extensions/skills/SkillsPanel.tsx b/src/renderer/components/extensions/skills/SkillsPanel.tsx index c04b1495..9c95959d 100644 --- a/src/renderer/components/extensions/skills/SkillsPanel.tsx +++ b/src/renderer/components/extensions/skills/SkillsPanel.tsx @@ -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]}`; diff --git a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx index ca366588..be3fe54d 100644 --- a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx @@ -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, diff --git a/src/renderer/components/team/members/MemberCard.tsx b/src/renderer/components/team/members/MemberCard.tsx index 3937e09f..bcf7ebc2 100644 --- a/src/renderer/components/team/members/MemberCard.tsx +++ b/src/renderer/components/team/members/MemberCard.tsx @@ -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 && diff --git a/src/renderer/components/team/members/MemberDetailDialog.tsx b/src/renderer/components/team/members/MemberDetailDialog.tsx index 1a179cf9..6096df73 100644 --- a/src/renderer/components/team/members/MemberDetailDialog.tsx +++ b/src/renderer/components/team/members/MemberDetailDialog.tsx @@ -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'; diff --git a/src/renderer/components/team/members/memberActivityEntries.ts b/src/renderer/components/team/members/memberActivityEntries.ts index c935e7e4..d3e1edf1 100644 --- a/src/renderer/components/team/members/memberActivityEntries.ts +++ b/src/renderer/components/team/members/memberActivityEntries.ts @@ -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({ diff --git a/src/renderer/store/index.ts b/src/renderer/store/index.ts index a2ffff64..77857dde 100644 --- a/src/renderer/store/index.ts +++ b/src/renderer/store/index.ts @@ -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 { diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts index 60fb75ee..baa59f01 100644 --- a/src/renderer/store/slices/teamSlice.ts +++ b/src/renderer/store/slices/teamSlice.ts @@ -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 = (set, return queuedRequest; } - let request!: Promise; - request = (async (): Promise => { + const requestRef: { current: Promise | null } = { + current: null, + }; + requestRef.current = (async (): Promise => { const teamStateEpoch = captureTeamLocalStateEpoch(teamName); set((state) => ({ teamMessagesByName: { @@ -3621,7 +3622,7 @@ export const createTeamSlice: StateCreator = (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 = (set, } })(); + const request = requestRef.current; inFlightTeamMessagesHeadRequests.set(teamName, request); return request; }, @@ -3662,8 +3664,8 @@ export const createTeamSlice: StateCreator = (set, return; } - let request!: Promise; - request = (async (): Promise => { + const requestRef: { current: Promise | null } = { current: null }; + requestRef.current = (async (): Promise => { const teamStateEpoch = captureTeamLocalStateEpoch(teamName); set((state) => ({ teamMessagesByName: { @@ -3747,12 +3749,13 @@ export const createTeamSlice: StateCreator = (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 = (set, return existingRequest; } - let request!: Promise; - request = (async (): Promise => { + const requestRef: { current: Promise | null } = { current: null }; + requestRef.current = (async (): Promise => { const teamStateEpoch = captureTeamLocalStateEpoch(teamName); try { const meta = await unwrapIpc('team:getMemberActivityMeta', () => @@ -3786,7 +3789,7 @@ export const createTeamSlice: StateCreator = (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 = (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 = (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 = (set, } })(); + const request = requestRef.current; inFlightTeamMemberActivityMetaRequests.set(teamName, request); return request; }, diff --git a/src/renderer/utils/displayItemBuilder.ts b/src/renderer/utils/displayItemBuilder.ts index d6ee8102..5b6667ed 100644 --- a/src/renderer/utils/displayItemBuilder.ts +++ b/src/renderer/utils/displayItemBuilder.ts @@ -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; } diff --git a/src/shared/utils/rateLimitDetector.ts b/src/shared/utils/rateLimitDetector.ts index 20f54acf..2d1252a1 100644 --- a/src/shared/utils/rateLimitDetector.ts +++ b/src/shared/utils/rateLimitDetector.ts @@ -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() ?? ''; diff --git a/src/shared/utils/teamGraphDefaultLayout.ts b/src/shared/utils/teamGraphDefaultLayout.ts index 005883ae..c100a189 100644 --- a/src/shared/utils/teamGraphDefaultLayout.ts +++ b/src/shared/utils/teamGraphDefaultLayout.ts @@ -15,7 +15,7 @@ export interface TeamGraphDefaultLayoutSeed { assignments: Record; } -const SMALL_TEAM_CARDINAL_SLOT_PRESETS: ReadonlyArray> = [ +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 = {}; - if (preset && preset.length === orderedVisibleOwnerIds.length) { + if (preset?.length === orderedVisibleOwnerIds.length) { orderedVisibleOwnerIds.forEach((stableOwnerId, index) => { assignments[stableOwnerId] = preset[index]!; }); diff --git a/test/renderer/components/team/messages/MessagesPanel.test.ts b/test/renderer/components/team/messages/MessagesPanel.test.ts index d6477425..72fbed31 100644 --- a/test/renderer/components/team/messages/MessagesPanel.test.ts +++ b/test/renderer/components/team/messages/MessagesPanel.test.ts @@ -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',